Merge pull request #13 from F43nd1r/vaadin10

[WIP] Migrate to Vaadin 10
This commit is contained in:
F43nd1r 2018-11-22 03:18:43 +01:00 committed by GitHub
commit f956bc8573
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
136 changed files with 2622 additions and 6015 deletions

View file

@ -1,6 +1,8 @@
[![Build Status](https://travis-ci.org/F43nd1r/Acrarium.svg?branch=master)](https://travis-ci.org/F43nd1r/Acrarium)
# Acrarium (formerly Acra-backend)
<h1 align=center>
<img src="src/main/webapp/frontend/logo.png" width=50%>
</h1>
A Backend for ACRA written in Java using Spring Boot, Vaadin and MySQL
# Setup
@ -49,3 +51,7 @@ Binary distributions may contain dependencies licensed under any of the followin
- [LGPL 2.1](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt)
- [EPL 1.0](http://www.eclipse.org/legal/epl-v10.html)
- [MPL 1.1](https://www.mozilla.org/en-US/MPL/1.1/)
# Credits
Thanks to [Mirza Zulfan](https://github.com/mirzazulfan) for creating the logo.

View file

@ -13,21 +13,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
plugins {
id 'java'
id 'idea'
id 'war'
id 'org.springframework.boot' version '2.0.4.RELEASE'
id 'com.devsoap.plugin.vaadin' version '1.3.1'
id 'org.springframework.boot' version '2.1.0.RELEASE'
id 'com.devsoap.vaadin-flow' version '1.0.0.M6'
id 'io.spring.dependency-management' version '1.0.6.RELEASE'
id 'cn.bestwu.propdeps' version '0.0.10'
id 'net.researchgate.release' version '2.7.0'
}
vaadin {
version '12.0.0.beta1'
submitStatistics false
}
repositories {
jcenter()
maven { url 'https://maven.google.com' }
maven { url 'https://maven.vaadin.com/vaadin-addons' }
vaadin.repositories()
}
group 'com.faendir'
@ -37,6 +41,7 @@ targetCompatibility = 1.8
ext {
generated = file("$buildDir/generated")
queryDslOutput = file("$generated/querydsl/java")
messagesOutput = file("$generated/faendir/java")
}
compileJava {
@ -50,77 +55,57 @@ idea {
module {
sourceDirs += queryDslOutput
generatedSourceDirs += queryDslOutput
sourceDirs += messagesOutput
generatedSourceDirs += messagesOutput
}
}
vaadin {
version '8.5.1'
push true
}
vaadinCompile {
style 'PRETTY'
outputDirectory 'src/main/resources'
strict true
widgetset 'com.faendir.acra.AppWidgetset'
}
vaadinThemeCompile {
themesDirectory 'src/main/resources/VAADIN/themes'
}
vaadinSuperDevMode {
noserver false
extraArgs ""
}
dependencyManagement {
imports {
mavenBom "com.vaadin:vaadin-bom:${vaadin.version}"
}
configurations {
all*.exclude module : 'slf4j-simple'
}
dependencies {
//spring
compile 'org.springframework.boot:spring-boot-starter-data-jpa'
compile 'mysql:mysql-connector-java'
compile 'org.springframework.boot:spring-boot-starter-security'
compile 'org.liquibase:liquibase-core'
compile 'org.yaml:snakeyaml'
optional 'org.springframework.boot:spring-boot-configuration-processor'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'mysql:mysql-connector-java'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.liquibase:liquibase-core'
implementation 'org.yaml:snakeyaml'
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
def queryDslVersion = '4.2.1'
compile "com.querydsl:querydsl-jpa:$queryDslVersion"
compile "com.querydsl:querydsl-sql:$queryDslVersion"
compile "com.querydsl:querydsl-apt:$queryDslVersion:jpa"
compile "com.fasterxml.jackson.datatype:jackson-datatype-jdk8"
implementation "com.querydsl:querydsl-jpa:$queryDslVersion"
implementation "com.querydsl:querydsl-sql:$queryDslVersion"
annotationProcessor "com.querydsl:querydsl-apt:$queryDslVersion:jpa"
annotationProcessor "javax.persistence:javax.persistence-api:2.2"
annotationProcessor "javax.annotation:javax.annotation-api:1.3.2"
implementation "com.fasterxml.jackson.datatype:jackson-datatype-jdk8"
//vaadin
compile 'com.vaadin:vaadin-spring-boot-starter'
compile 'com.vaadin:vaadin-push'
compile('org.vaadin.addon:jfreechartwrapper:4.0.0') {
exclude group: 'javax.servlet', module: 'servlet-api'
exclude group: 'jfree'
}
compile 'com.vaadin:vaadin-icons:3.0.1'
compile 'org.jfree:jfreechart:1.5.0'
compile 'javax.servlet:javax.servlet-api:3.1.0'
compile 'org.vaadin.addons:stepper:2.4.0'
implementation vaadin.bom()
implementation vaadin.platform()
implementation vaadin.dependency('icons-flow')
implementation vaadin.dependency('spring-boot-starter')
implementation 'com.vaadin:vaadin-grid-flow:2.1.2'
implementation 'org.jfree:jfreechart:1.5.0'
implementation 'org.apache.xmlgraphics:batik-svggen:1.7'
implementation 'javax.servlet:javax.servlet-api:4.0.1'
implementation 'org.webjars.bowergithub.simpleelements:simple-dropdown:1.0.0'
//utility
compile 'org.codeartisans:org.json:20161124'
compile 'org.apache.commons:commons-text:1.4'
compile 'org.xbib:time:1.0.0'
compile 'ch.acra:acra-javacore:5.1.3'
compile 'com.faendir.proguard:retrace:1.3'
compile 'javax.xml.bind:jaxb-api:2.3.0'
compile 'com.github.ziplet:ziplet:2.3.0'
compile 'me.xdrop:fuzzywuzzy:1.1.10'
compile 'com.talanlabs:avatar-generator:1.1.0'
compile 'org.ektorp:org.ektorp.spring:1.5.0'
implementation 'org.codeartisans:org.json:20161124'
implementation 'org.apache.commons:commons-text:1.4'
implementation 'org.xbib:time:1.0.0'
implementation 'ch.acra:acra-javacore:5.1.3'
implementation 'com.faendir.proguard:retrace:1.3'
implementation 'javax.xml.bind:jaxb-api:2.3.0'
implementation 'com.github.ziplet:ziplet:2.3.0'
implementation 'me.xdrop:fuzzywuzzy:1.1.10'
implementation 'com.talanlabs:avatar-generator:1.1.0'
implementation 'org.ektorp:org.ektorp.spring:1.5.0'
//testing
testCompile 'org.springframework.boot:spring-boot-starter-test'
testCompile 'org.springframework.security:spring-security-test'
testCompile 'com.h2database:h2'
testCompile files('libs/ojdbc6.jar')
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testImplementation 'com.h2database:h2'
testImplementation files('libs/ojdbc6.jar')
}
compileJava.dependsOn(processResources)
@ -129,19 +114,11 @@ war {
archiveName = 'acra.war'
version = version
enabled = true
dependsOn vaadinThemeCompile, vaadinCompile
}
task generateThemeClasses(type: com.faendir.acra.gradle.ThemeClassGenerator) {
themesDirectory file('src/main/resources/VAADIN/themes')
outputDirectory file("$generated/faendir/java")
}
compileJava.dependsOn(generateThemeClasses)
task generateMessageClasses(type: com.faendir.acra.gradle.I18nClassGenerator) {
inputDirectory file('src/main/resources/i18n/com/faendir/acra')
outputDirectory file("$generated/faendir/java")
outputDirectory messagesOutput
packageName 'com.faendir.acra.i18n'
className 'Messages'
}
@ -154,9 +131,3 @@ test {
exceptionFormat "full"
}
}
release {
failOnUnversionedFiles = false
tagTemplate = 'v$version'
}

View file

@ -1,3 +1,6 @@
plugins {
id 'groovy'
}
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
@ -19,7 +22,8 @@ repositories {
}
dependencies {
gradleApi()
compile localGroovy()
compile gradleApi()
compile 'com.squareup:javapoet:1.11.1'
compile 'com.google.guava:guava:26.0-jre'
}

View file

@ -21,7 +21,11 @@ import com.squareup.javapoet.FieldSpec
import com.squareup.javapoet.JavaFile
import com.squareup.javapoet.TypeSpec
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.*
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.SkipWhenEmpty
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
import org.gradle.plugins.ide.idea.IdeaPlugin
import static javax.lang.model.element.Modifier.*

View file

@ -1,78 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.gradle
import com.squareup.javapoet.FieldSpec
import com.squareup.javapoet.JavaFile
import com.squareup.javapoet.TypeSpec
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.SkipWhenEmpty
import org.gradle.api.tasks.TaskAction
import org.gradle.plugins.ide.idea.IdeaPlugin
import java.util.regex.Matcher
import java.util.regex.Pattern
import static javax.lang.model.element.Modifier.*
/**
* @author lukas
* @since 05.06.18
*/
@CacheableTask
@SuppressWarnings("GroovyUnusedDeclaration")
class ThemeClassGenerator extends DefaultTask {
@SkipWhenEmpty
@InputDirectory
File themesDirectory
@OutputDirectory
File outputDirectory
ThemeClassGenerator() {
project.afterEvaluate {
project.sourceSets.main.java.srcDirs outputs.files
project.plugins.withType(IdeaPlugin.class, { IdeaPlugin idea -> idea.model.module.generatedSourceDirs += outputs.files })
}
}
@TaskAction
void exec() {
Pattern pattern = Pattern.compile("\\.(-?[_a-zA-Z]+[_a-zA-Z0-9-]*)")
themesDirectory.eachDir {
Set<String> cssClasses = new LinkedHashSet<>();
project.fileTree(dir: it, include: "*.scss", exclude: "styles.scss").forEach {
Matcher matcher = pattern.matcher(it.text.replaceAll("//.*\\n", "").replaceAll("/\\*.*\\*/", "").replaceAll("\".*\"", ""))
while (matcher.find()) {
cssClasses.add(matcher.group(1))
}
}
cssClasses.removeIf { it.startsWith("v-") }
TypeSpec.Builder classBuilder = TypeSpec.classBuilder(it.name).addModifiers(PUBLIC, FINAL)
classBuilder.addField(FieldSpec.builder(String, "THEME_NAME", PUBLIC, STATIC, FINAL).initializer('$S', it.name).build())
for (String cssClass : cssClasses) {
classBuilder.addField(FieldSpec.builder(String, cssClass.toUpperCase().replace("-", "_"), PUBLIC, STATIC, FINAL).initializer('$S', cssClass).build())
}
JavaFile.builder("com.vaadin.ui.themes", classBuilder.build())
.skipJavaLangImports(true)
.indent(" ")
.build()
.writeTo(outputDirectory)
}
}
}

Binary file not shown.

View file

@ -1,6 +1,5 @@
#Sat May 20 23:53:35 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.7-all.zip

8
gradlew vendored
View file

@ -28,16 +28,16 @@ APP_NAME="Gradle"
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=""
DEFAULT_JVM_OPTS='"-Xmx64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
warn () {
echo "$*"
}
die ( ) {
die () {
echo
echo "$*"
echo
@ -155,7 +155,7 @@ if $cygwin ; then
fi
# Escape application args
save ( ) {
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}

2
gradlew.bat vendored
View file

@ -14,7 +14,7 @@ set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DEFAULT_JVM_OPTS="-Xmx64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome

2
settings.gradle Normal file
View file

@ -0,0 +1,2 @@
include 'buildSrc'
enableFeaturePreview('IMPROVED_POM_SUPPORT')

View file

@ -1,57 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.client;
import com.faendir.acra.ui.view.base.MiddleClickExtension;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.vaadin.client.ComponentConnector;
import com.vaadin.client.MouseEventDetailsBuilder;
import com.vaadin.client.ServerConnector;
import com.vaadin.client.extensions.AbstractExtensionConnector;
import com.vaadin.shared.MouseEventDetails;
import com.vaadin.shared.communication.ServerRpc;
import com.vaadin.shared.ui.Connect;
/**
* @author lukas
* @since 19.06.18
*/
@Connect(MiddleClickExtension.class)
public class MiddleClickExtensionConnector extends AbstractExtensionConnector {
@Override
protected void extend(ServerConnector target) {
getParent().getWidget().addDomHandler(event -> {
if (event.getNativeButton() == NativeEvent.BUTTON_MIDDLE) {
event.preventDefault();
middleClick(event);
}
}, MouseDownEvent.getType());
}
protected void middleClick(MouseDownEvent event) {
getRpcProxy(Rpc.class).middleClick(MouseEventDetailsBuilder.buildMouseEventDetails(event.getNativeEvent(), event.getRelativeElement()));
}
@Override
public ComponentConnector getParent() {
return (ComponentConnector) super.getParent();
}
public interface Rpc extends ServerRpc {
void middleClick(MouseEventDetails details);
}
}

View file

@ -1,55 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.client.mygrid;
import com.faendir.acra.client.MiddleClickExtensionConnector;
import com.faendir.acra.ui.view.base.layout.MyGrid;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.vaadin.client.MouseEventDetailsBuilder;
import com.vaadin.client.connectors.grid.GridConnector;
import com.vaadin.client.widget.grid.CellReference;
import com.vaadin.shared.MouseEventDetails;
import com.vaadin.shared.communication.ServerRpc;
import com.vaadin.shared.data.DataCommunicatorConstants;
import com.vaadin.shared.ui.Connect;
import elemental.json.JsonObject;
/**
* @author Lukas
* @since 15.01.2018
*/
@Connect(MyGrid.MiddleClickExtension.class)
public class GridMiddleClickExtensionConnector extends MiddleClickExtensionConnector {
@Override
protected void middleClick(MouseDownEvent event) {
CellReference<JsonObject> cell = getParent().getWidget().getEventCell();
if(cell != null) {
getRpcProxy(Rpc.class).middleClick(cell.getRowIndex(), cell.getRow().getString(DataCommunicatorConstants.KEY), getParent().getColumnId(cell.getColumn()),
MouseEventDetailsBuilder.buildMouseEventDetails(event.getNativeEvent(), event.getRelativeElement()));
}
}
@Override
public GridConnector getParent() {
return (GridConnector) super.getParent();
}
public interface Rpc extends ServerRpc {
void middleClick(int rowIndex, String rowKey, String columnInternalId, MouseEventDetails details);
}
}

View file

@ -1,65 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.client.mytabsheet;
import com.faendir.acra.ui.view.base.layout.MyTabSheet;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.regexp.shared.MatchResult;
import com.google.gwt.regexp.shared.RegExp;
import com.google.gwt.user.client.DOM;
import com.vaadin.client.MouseEventDetailsBuilder;
import com.vaadin.client.ServerConnector;
import com.vaadin.client.extensions.AbstractExtensionConnector;
import com.vaadin.client.ui.tabsheet.TabsheetConnector;
import com.vaadin.shared.MouseEventDetails;
import com.vaadin.shared.communication.ServerRpc;
import com.vaadin.shared.ui.Connect;
/**
* @author lukas
* @since 19.06.18
*/
@Connect(MyTabSheet.MiddleClickExtension.class)
public class TabSheetMiddleClickExtensionConnector extends AbstractExtensionConnector {
@Override
protected void extend(ServerConnector target) {
getParent().getWidget().addDomHandler(event -> {
if (event.getNativeButton() == NativeEvent.BUTTON_MIDDLE) {
event.preventDefault();
Element element = Element.as(event.getNativeEvent().getEventTarget());
String id = getParent().getWidget().getSubPartName(DOM.asOld(element));
if (id != null) {
MatchResult result = RegExp.compile("tab\\[(\\d)\\]").exec(id);
if (result != null) {
getRpcProxy(Rpc.class).middleClick(Integer.parseInt(result.getGroup(1)),
MouseEventDetailsBuilder.buildMouseEventDetails(event.getNativeEvent(), event.getRelativeElement()));
}
}
}
}, MouseDownEvent.getType());
}
@Override
public TabsheetConnector getParent() {
return (TabsheetConnector) super.getParent();
}
public interface Rpc extends ServerRpc {
void middleClick(int tabIndex, MouseEventDetails details);
}
}

View file

@ -20,10 +20,10 @@ import com.querydsl.core.types.Expression;
import com.querydsl.core.types.Order;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.jpa.impl.JPAQuery;
import com.vaadin.data.provider.AbstractBackEndDataProvider;
import com.vaadin.data.provider.Query;
import com.vaadin.data.provider.QuerySortOrder;
import com.vaadin.shared.data.sort.SortDirection;
import com.vaadin.flow.data.provider.AbstractBackEndDataProvider;
import com.vaadin.flow.data.provider.Query;
import com.vaadin.flow.data.provider.QuerySortOrder;
import com.vaadin.flow.data.provider.SortDirection;
import java.util.ArrayList;
import java.util.HashMap;

View file

@ -1,26 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.i18n;
import org.vaadin.spring.i18n.I18N;
/**
* @author lukas
* @since 16.08.18
*/
public interface HasI18n {
I18N getI18n();
}

View file

@ -0,0 +1,18 @@
package com.faendir.acra.i18n;
import com.vaadin.flow.i18n.I18NProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author lukas
* @since 10.11.18
*/
@Configuration
public class I18NConfiguration {
@Bean
public I18NProvider i18NProvider() {
return new ResourceBundleI18NProvider("i18n.com.faendir.acra.messages");
}
}

View file

@ -1,53 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.i18n;
import com.vaadin.ui.Accordion;
import com.vaadin.ui.Component;
import com.vaadin.ui.TabSheet;
import org.springframework.data.util.Pair;
import org.vaadin.spring.i18n.I18N;
import org.vaadin.spring.i18n.support.Translatable;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
/**
* @author lukas
* @since 16.08.18
*/
public class I18nAccordion extends Accordion implements Translatable {
private final I18N i18n;
private final Map<TabSheet.Tab, Pair<String, Object[]>> tabCaptionIds;
public I18nAccordion(I18N i18n) {
this.i18n = i18n;
tabCaptionIds = new HashMap<>();
}
public Tab addTab(Component c, String captionId, Object... parameters) {
Tab tab = super.addTab(c, i18n.get(captionId, parameters));
tabCaptionIds.put(tab, Pair.of(captionId, parameters));
return tab;
}
@Override
public void updateMessageStrings(Locale locale) {
tabCaptionIds.forEach((tab, pair) -> tab.setCaption(i18n.get(pair.getFirst(), locale, pair.getSecond())));
}
}

View file

@ -1,55 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.i18n;
import com.vaadin.ui.Button;
import org.vaadin.spring.i18n.I18N;
import org.vaadin.spring.i18n.support.Translatable;
import java.util.Locale;
/**
* @author lukas
* @since 15.08.18
*/
public class I18nButton extends Button implements Translatable, HasI18n {
private final I18N i18n;
private final String captionId;
private final Object[] params;
public I18nButton(ClickListener clickListener, I18N i18n, String captionId, Object... params) {
this(i18n, captionId, params);
addClickListener(clickListener);
}
public I18nButton(I18N i18n, String captionId, Object... params) {
this.i18n = i18n;
this.captionId = captionId;
this.params = params;
updateMessageStrings(i18n.getLocale());
}
@Override
public void updateMessageStrings(Locale locale) {
setCaption(i18n.get(captionId, locale, params));
}
@Override
public I18N getI18n() {
return i18n;
}
}

View file

@ -1,55 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.i18n;
import com.vaadin.ui.CheckBox;
import org.vaadin.spring.i18n.I18N;
import org.vaadin.spring.i18n.support.Translatable;
import java.util.Locale;
/**
* @author lukas
* @since 15.08.18
*/
public class I18nCheckBox extends CheckBox implements Translatable, HasI18n {
private final I18N i18n;
private final String captionId;
private final Object[] params;
public I18nCheckBox(boolean value, I18N i18n, String captionId, Object... params) {
this(i18n, captionId, params);
setValue(value);
}
public I18nCheckBox(I18N i18n, String captionId, Object... params) {
this.i18n = i18n;
this.captionId = captionId;
this.params = params;
updateMessageStrings(i18n.getLocale());
}
@Override
public void updateMessageStrings(Locale locale) {
setCaption(i18n.get(captionId, locale, params));
}
@Override
public I18N getI18n() {
return i18n;
}
}

View file

@ -1,50 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.i18n;
import com.vaadin.ui.ComboBox;
import org.vaadin.spring.i18n.I18N;
import org.vaadin.spring.i18n.support.Translatable;
import java.util.Collection;
import java.util.Locale;
/**
* @author lukas
* @since 15.08.18
*/
public class I18nComboBox<T> extends ComboBox<T> implements Translatable {
private final I18N i18n;
private final String captionId;
private final Object[] params;
public I18nComboBox(Collection<T> values, I18N i18n, String captionId, Object... params) {
this(i18n, captionId, params);
setItems(values);
}
public I18nComboBox(I18N i18n, String captionId, Object... params) {
this.i18n = i18n;
this.captionId = captionId;
this.params = params;
updateMessageStrings(i18n.getLocale());
}
@Override
public void updateMessageStrings(Locale locale) {
setCaption(i18n.get(captionId, locale, params));
}
}

View file

@ -1,37 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.i18n;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.vaadin.spring.i18n.MessageProvider;
import org.vaadin.spring.i18n.ResourceBundleMessageProvider;
import org.vaadin.spring.i18n.annotation.EnableI18N;
/**
* @author lukas
* @since 15.08.18
*/
@EnableI18N
@Configuration
public class I18nConfiguration {
@Bean
MessageProvider messageProvider() {
return new ResourceBundleMessageProvider("i18n.com.faendir.acra.messages");
}
}

View file

@ -1,58 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.i18n;
import org.vaadin.risto.stepper.IntStepper;
import org.vaadin.spring.i18n.I18N;
import org.vaadin.spring.i18n.support.Translatable;
import java.util.Locale;
/**
* @author lukas
* @since 15.08.18
*/
public class I18nIntStepper extends IntStepper implements Translatable, HasI18n {
private final I18N i18n;
private final String captionId;
private final Object[] params;
public I18nIntStepper(int value) {
this(value, null, null);
}
public I18nIntStepper(int value, I18N i18n, String captionId, Object... params) {
this.i18n = i18n;
this.captionId = captionId;
this.params = params;
setValue(value);
if (i18n != null) {
updateMessageStrings(i18n.getLocale());
}
}
@Override
public void updateMessageStrings(Locale locale) {
if (i18n != null) {
setCaption(i18n.get(captionId, locale, params));
}
}
@Override
public I18N getI18n() {
return i18n;
}
}

View file

@ -1,51 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.i18n;
import com.vaadin.shared.ui.ContentMode;
import com.vaadin.ui.Label;
import org.vaadin.spring.i18n.I18N;
import org.vaadin.spring.i18n.support.Translatable;
import java.util.Locale;
/**
* @author lukas
* @since 15.08.18
*/
public class I18nLabel extends Label implements Translatable {
private final I18N i18n;
private final String captionId;
private final Object[] params;
public I18nLabel(ContentMode contentMode, I18N i18n, String captionId, Object... params) {
this.i18n = i18n;
this.captionId = captionId;
this.params = params;
setContentMode(contentMode);
updateMessageStrings(i18n.getLocale());
}
public I18nLabel(I18N i18n, String captionId, Object... params) {
this(ContentMode.TEXT, i18n, captionId, params);
}
@Override
public void updateMessageStrings(Locale locale) {
setValue(i18n.get(captionId, locale, params));
}
}

View file

@ -1,49 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.i18n;
import com.vaadin.ui.LoginForm;
import org.vaadin.spring.i18n.I18N;
import org.vaadin.spring.i18n.support.Translatable;
import java.util.Locale;
/**
* @author lukas
* @since 16.08.18
*/
public class I18nLoginForm extends LoginForm implements Translatable {
private final I18N i18n;
private final String usernameId;
private final String passwordId;
private final String loginId;
public I18nLoginForm(I18N i18n, String usernameId, String passwordId, String loginId) {
this.i18n = i18n;
this.usernameId = usernameId;
this.passwordId = passwordId;
this.loginId = loginId;
updateMessageStrings(i18n.getLocale());
}
@Override
public void updateMessageStrings(Locale locale) {
setUsernameCaption(i18n.get(usernameId));
setPasswordCaption(i18n.get(passwordId));
setLoginButtonCaption(i18n.get(loginId));
}
}

View file

@ -1,79 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.i18n;
import com.vaadin.server.Resource;
import com.vaadin.ui.MenuBar;
import org.springframework.lang.Nullable;
import org.vaadin.spring.i18n.I18N;
import org.vaadin.spring.i18n.support.Translatable;
import java.util.Locale;
/**
* @author lukas
* @since 16.08.18
*/
public class I18nMenuBar extends MenuBar implements Translatable {
private final I18N i18n;
public I18nMenuBar(I18N i18n) {
this.i18n = i18n;
}
public I18nMenuItem addItem(Resource icon, String captionId, Object... params) {
I18nMenuItem item = new I18nMenuItem(icon, null, i18n, captionId, params);
getItems().add(item);
return item;
}
@Override
public void updateMessageStrings(Locale locale) {
getItems().stream().filter(Translatable.class::isInstance).map(Translatable.class::cast).forEach(translatable -> translatable.updateMessageStrings(locale));
}
public class I18nMenuItem extends MenuItem implements Translatable {
private final I18N i18n;
private final String captionId;
private final Object[] params;
private I18nMenuItem(@Nullable Resource icon, Command command, I18N i18n, String captionId, Object... params) {
//noinspection ConstantConditions
super(i18n.get(captionId, params), icon, command);
this.i18n = i18n;
this.captionId = captionId;
this.params = params;
}
public I18nMenuItem addItem(Command command, String captionId, Object... params) {
I18nMenuItem item = new I18nMenuItem(null, command, i18n, captionId, params);
if(getChildren() == null) {
removeItem(addItem(""));
}
getChildren().add(item);
return item;
}
@Override
public void updateMessageStrings(Locale locale) {
setText(i18n.get(captionId, params));
if(getChildren() != null) {
getChildren().stream().filter(Translatable.class::isInstance).map(Translatable.class::cast).forEach(translatable -> translatable.updateMessageStrings(locale));
}
}
}
}

View file

@ -1,50 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.i18n;
import com.vaadin.ui.Component;
import com.vaadin.ui.Panel;
import org.vaadin.spring.i18n.I18N;
import org.vaadin.spring.i18n.support.Translatable;
import java.util.Locale;
/**
* @author lukas
* @since 16.08.18
*/
public class I18nPanel extends Panel implements Translatable {
private final I18N i18n;
private final String captionId;
private final Object[] params;
public I18nPanel(Component content, I18N i18n, String captionId, Object... params) {
this(i18n, captionId, params);
setContent(content);
}
public I18nPanel(I18N i18n, String captionId, Object... params) {
this.i18n = i18n;
this.captionId = captionId;
this.params = params;
updateMessageStrings(i18n.getLocale());
}
@Override
public void updateMessageStrings(Locale locale) {
setCaption(i18n.get(captionId, locale, params));
}
}

View file

@ -1,50 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.i18n;
import com.vaadin.ui.PasswordField;
import org.vaadin.spring.i18n.I18N;
import org.vaadin.spring.i18n.support.Translatable;
import java.util.Locale;
/**
* @author lukas
* @since 16.08.18
*/
public class I18nPasswordField extends PasswordField implements Translatable, HasI18n {
private final I18N i18n;
private final String captionId;
private final Object[] params;
public I18nPasswordField(I18N i18n, String captionId, Object... params) {
this.i18n = i18n;
this.captionId = captionId;
this.params = params;
updateMessageStrings(i18n.getLocale());
}
@Override
public void updateMessageStrings(Locale locale) {
setCaption(i18n.get(captionId, locale, params));
}
@Override
public I18N getI18n() {
return i18n;
}
}

View file

@ -1,50 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.i18n;
import com.vaadin.ui.Slider;
import org.vaadin.spring.i18n.I18N;
import org.vaadin.spring.i18n.support.Translatable;
import java.util.Locale;
/**
* @author lukas
* @since 15.08.18
*/
public class I18nSlider extends Slider implements Translatable, HasI18n {
private final I18N i18n;
private final String captionId;
private final Object[] params;
public I18nSlider(int min, int max, I18N i18n, String captionId, Object... params) {
super(min, max);
this.i18n = i18n;
this.captionId = captionId;
this.params = params;
updateMessageStrings(i18n.getLocale());
}
@Override
public void updateMessageStrings(Locale locale) {
setCaption(i18n.get(captionId, locale, params));
}
@Override
public I18N getI18n() {
return i18n;
}
}

View file

@ -1,45 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.i18n;
import com.vaadin.ui.TextArea;
import org.vaadin.spring.i18n.I18N;
import org.vaadin.spring.i18n.support.Translatable;
import java.util.Locale;
/**
* @author lukas
* @since 16.08.18
*/
public class I18nTextArea extends TextArea implements Translatable {
private final I18N i18n;
private final String captionId;
private final Object[] params;
public I18nTextArea(I18N i18n, String captionId, Object... params) {
this.i18n = i18n;
this.captionId = captionId;
this.params = params;
updateMessageStrings(i18n.getLocale());
}
@Override
public void updateMessageStrings(Locale locale) {
setCaption(i18n.get(captionId, locale, params));
}
}

View file

@ -1,55 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.i18n;
import com.vaadin.ui.TextField;
import org.vaadin.spring.i18n.I18N;
import org.vaadin.spring.i18n.support.Translatable;
import java.util.Locale;
/**
* @author lukas
* @since 15.08.18
*/
public class I18nTextField extends TextField implements Translatable, HasI18n {
private final I18N i18n;
private final String captionId;
private final Object[] params;
public I18nTextField(String value, I18N i18n, String captionId, Object... params) {
this(i18n, captionId, params);
setValue(value);
}
public I18nTextField(I18N i18n, String captionId, Object... params) {
this.i18n = i18n;
this.captionId = captionId;
this.params = params;
updateMessageStrings(i18n.getLocale());
}
@Override
public void updateMessageStrings(Locale locale) {
setCaption(i18n.get(captionId, locale, params));
}
@Override
public I18N getI18n() {
return i18n;
}
}

View file

@ -1,5 +1,6 @@
package org.vaadin.spring.i18n;
package com.faendir.acra.i18n;
import com.vaadin.flow.i18n.I18NProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -8,57 +9,46 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.text.MessageFormat;
import java.util.List;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Implementation of {@link MessageProvider} that reads messages
* from {@link ResourceBundle}s with a specific base name.
* Implementation of {@link I18NProvider} that reads messages from {@link ResourceBundle}s with a specific base name.
*
* @author Petter Holmström (petter@vaadin.com)
* @author lukas
* @since 10.11.18
*/
public class ResourceBundleMessageProvider implements MessageProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(ResourceBundleMessageProvider.class);
public class ResourceBundleI18NProvider implements I18NProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(ResourceBundleI18NProvider.class);
private final String baseName;
private final String encoding;
/**
* Creates a new {@code ResourceBundleMessageProvider} with the given base name and UTF-8 encoding.
* Creates a new {@code ResourceBundleI18NProvider} with the given base name and UTF-8 encoding.
*
* @param baseName the base name to use, must not be {@code null}.
*/
public ResourceBundleMessageProvider(String baseName) {
public ResourceBundleI18NProvider(String baseName) {
this(baseName, "UTF-8");
}
/**
* Creates a new {@code ResourceBundleMessageProvider} with the given base name and encoding.
* Creates a new {@code ResourceBundleI18NProvider} with the given base name and encoding.
*
* @param baseName the base name to use, must not be {@code null}.
* @param encoding the encoding to use when reading the resource bundle, must not be {@code null}.
*/
public ResourceBundleMessageProvider(String baseName, String encoding) {
public ResourceBundleI18NProvider(String baseName, String encoding) {
this.baseName = baseName;
this.encoding = encoding;
}
@Override
public MessageFormat resolveCode(String s, Locale locale) {
final ResourceBundle resourceBundle = getResourceBundle(locale);
final String message = getString(resourceBundle, s);
return getMessageFormat(message, locale);
}
@Override
public void clearCache() {
ResourceBundle.clearCache(this.getClass().getClassLoader());
}
private ResourceBundle getResourceBundle(Locale locale) {
try {
return ResourceBundle.getBundle(baseName, locale, this.getClass().getClassLoader(), new MessageControl());
@ -79,34 +69,33 @@ public class ResourceBundleMessageProvider implements MessageProvider {
}
}
private static MessageFormat getMessageFormat(String message, Locale locale) {
if (message == null) {
return null;
}
return new MessageFormat(message, locale);
@Override
public List<Locale> getProvidedLocales() {
return Stream.of(Locale.getAvailableLocales()).filter(locale -> getResourceBundle(locale) != null).collect(Collectors.toList());
}
@Override
public String getTranslation(String key, Locale locale, Object... params) {
final ResourceBundle resourceBundle = getResourceBundle(locale);
final String message = getString(resourceBundle, key);
return message == null ? null : String.format(message, params);
}
private class MessageControl extends ResourceBundle.Control {
@Override
public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader,
boolean reload) throws IllegalAccessException, InstantiationException, IOException {
boolean reload) throws IllegalAccessException, InstantiationException, IOException {
if ("java.properties".equals(format)) {
final String resourceName = toResourceName(toBundleName(baseName, locale), "properties");
final InputStream stream = loader.getResourceAsStream(resourceName);
if (stream == null) {
return null; // Not found
}
Reader reader = null;
try {
reader = new InputStreamReader(stream, encoding);
try (Reader reader = new InputStreamReader(stream, encoding)) {
return new PropertyResourceBundle(reader);
} catch (UnsupportedEncodingException ex) {
stream.close();
throw ex;
} finally {
if (reader != null) {
reader.close();
}
}
} else {
return super.newBundle(baseName, locale, format, loader, reload);

View file

@ -15,8 +15,8 @@
*/
package com.faendir.acra.security;
import com.vaadin.server.VaadinService;
import com.vaadin.server.VaadinSession;
import com.vaadin.flow.server.VaadinService;
import com.vaadin.flow.server.VaadinSession;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.NonNull;
import org.springframework.security.core.context.SecurityContext;

View file

@ -13,18 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.service;
import com.faendir.acra.model.Report;
import com.talanlabs.avatargenerator.Avatar;
import com.talanlabs.avatargenerator.IdenticonAvatar;
import com.vaadin.server.ExternalResource;
import org.springframework.cache.annotation.Cacheable;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.html.Image;
import com.vaadin.flow.server.StreamResource;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Service;
import org.springframework.util.Base64Utils;
import java.io.ByteArrayInputStream;
/**
* @author lukas
@ -39,9 +40,9 @@ public class AvatarService {
avatar = IdenticonAvatar.newAvatarBuilder().size(32, 32).build();
}
@Cacheable(cacheNames = "avatars", key = "#report.installationId")
public ExternalResource getAvatar(@NonNull Report report) {
//@Cacheable(cacheNames = "avatars", key = "#report.installationId")
public Component getAvatar(@NonNull Report report) {
byte[] bytes = avatar.createAsPngBytes(report.getInstallationId().hashCode());
return new ExternalResource("data:image/png;base64," + Base64Utils.encodeToString(bytes));
return new Image(new StreamResource("", () -> new ByteArrayInputStream(bytes)), "");
}
}

View file

@ -63,6 +63,7 @@ import javax.persistence.EntityManager;
import javax.validation.constraints.Size;
import java.io.IOException;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Collection;
@ -392,7 +393,7 @@ public class DataService implements Serializable {
@NonNull
@PreAuthorize("T(com.faendir.acra.security.SecurityUtils).hasPermission(#app, T(com.faendir.acra.model.Permission$Level).VIEW)")
public <T> Map<T, Long> countReports(@NonNull App app, @NonNull Predicate where, @NonNull Expression<T> select) {
public <T> Map<T, Long> countReports(@NonNull App app, @Nullable Predicate where, @NonNull Expression<T> select) {
List<Tuple> result = ((JPAQuery<?>) new JPAQuery<>(entityManager)).from(report)
.where(report.stacktrace.bug.app.eq(app).and(where))
.groupBy(select)
@ -442,7 +443,7 @@ public class DataService implements Serializable {
if (!id.startsWith("_design")) {
total++;
try {
JSONObject report = new JSONObject(IOUtils.toString(db.getAsStream(id)));
JSONObject report = new JSONObject(IOUtils.toString(db.getAsStream(id), StandardCharsets.UTF_8));
fixStringIsArray(report, ReportField.STACK_TRACE);
fixStringIsArray(report, ReportField.LOGCAT);
createNewReport(user.getUsername(), report.toString(), Collections.emptyList());

View file

@ -1,192 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.ui;
import com.faendir.acra.i18n.I18nLabel;
import com.faendir.acra.i18n.I18nLoginForm;
import com.faendir.acra.i18n.I18nMenuBar;
import com.faendir.acra.i18n.Messages;
import com.faendir.acra.model.User;
import com.faendir.acra.security.SecurityUtils;
import com.faendir.acra.ui.navigation.NavigationManager;
import com.faendir.acra.ui.view.base.navigation.Path;
import com.faendir.acra.ui.view.user.ChangePasswordView;
import com.faendir.acra.ui.view.user.UserManagerView;
import com.vaadin.annotations.Theme;
import com.vaadin.annotations.Viewport;
import com.vaadin.annotations.Widgetset;
import com.vaadin.icons.VaadinIcons;
import com.vaadin.server.VaadinRequest;
import com.vaadin.server.VaadinService;
import com.vaadin.shared.communication.PushMode;
import com.vaadin.shared.ui.ContentMode;
import com.vaadin.spring.annotation.SpringUI;
import com.vaadin.spring.annotation.UIScope;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Label;
import com.vaadin.ui.LoginForm;
import com.vaadin.ui.Notification;
import com.vaadin.ui.Panel;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.themes.AcraTheme;
import com.vaadin.ui.themes.DarkAcraTheme;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.lang.NonNull;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.util.UriComponentsBuilder;
import org.vaadin.spring.i18n.I18N;
import org.vaadin.spring.i18n.support.TranslatableUI;
import java.util.Optional;
/**
* @author Lukas
* @since 22.03.2017
*/
@SpringUI
@Theme(AcraTheme.THEME_NAME)
@Widgetset("com.faendir.acra.AppWidgetset")
@Viewport("width=device-width, initial-scale=1")
public class BackendUI extends TranslatableUI {
private static final String DARK_THEME = "dark";
@NonNull private final AuthenticationManager authenticationManager;
@NonNull private final ApplicationContext applicationContext;
@NonNull private final I18N i18n;
@NonNull private final Panel content;
@NonNull private final Path path;
@Autowired
public BackendUI(@NonNull AuthenticationManager authenticationManager, @NonNull ApplicationContext applicationContext, @NonNull I18N i18n) {
this.authenticationManager = authenticationManager;
this.applicationContext = applicationContext;
this.i18n = i18n;
content = new Panel();
content.setSizeFull();
content.addStyleNames(AcraTheme.NO_PADDING, AcraTheme.NO_BACKGROUND, AcraTheme.NO_BORDER);
path = new Path();
}
@Override
protected void initUI(VaadinRequest request) {
if (isDarkTheme()) {
setTheme(DarkAcraTheme.THEME_NAME);
}
if (SecurityUtils.isLoggedIn()) {
showMain();
} else {
showLogin();
}
setLocale(request.getLocale());
}
private void login(@NonNull String username, @NonNull String password) {
try {
Authentication token = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username.toLowerCase(), password));
if (!token.getAuthorities().contains(User.Role.USER)) {
throw new InsufficientAuthenticationException(i18n.get(Messages.MISSING_ROLE));
}
VaadinService.reinitializeSession(VaadinService.getCurrentRequest());
SecurityContextHolder.getContext().setAuthentication(token);
showMain();
getPushConfiguration().setPushMode(PushMode.AUTOMATIC);
} catch (AuthenticationException ex) {
Notification.show(i18n.get(Messages.LOGIN_FAILED), Notification.Type.ERROR_MESSAGE);
}
}
public void logout() {
getPushConfiguration().setPushMode(PushMode.DISABLED);
SecurityContextHolder.clearContext();
getPage().reload();
getSession().close();
}
private void showLogin() {
LoginForm loginForm = new I18nLoginForm(i18n, Messages.USERNAME, Messages.PASSWORD, Messages.LOGIN);
loginForm.addLoginListener(event -> login(event.getLoginParameter("username"), event.getLoginParameter("password")));
VerticalLayout layout = new VerticalLayout(loginForm);
layout.setSizeFull();
layout.setComponentAlignment(loginForm, Alignment.MIDDLE_CENTER);
setContent(layout);
}
private void showMain() {
NavigationManager navigationManager = applicationContext.getBean(NavigationManager.class);
I18nMenuBar menuBar = new I18nMenuBar(i18n);
I18nMenuBar.I18nMenuItem user = menuBar.addItem(VaadinIcons.USER, Messages.ONE_ARG, SecurityUtils.getUsername());
I18nMenuBar.I18nMenuItem theme = user.addItem(e -> getPage().setLocation(UriComponentsBuilder.fromUri(getPage().getLocation())
.replaceQueryParam(DARK_THEME, e.isChecked())
.build()
.toUri()), Messages.DARK_THEME);
theme.setCheckable(true);
theme.setChecked(isDarkTheme());
user.addSeparator();
if (SecurityUtils.hasRole(User.Role.ADMIN)) {
user.addItem(e -> navigationManager.cleanNavigateTo(UserManagerView.class), Messages.USER_MANAGER);
user.addSeparator();
}
user.addItem(e -> navigationManager.cleanNavigateTo(ChangePasswordView.class), Messages.CHANGE_PASSWORD);
user.addItem(e -> logout(), Messages.LOGOUT);
HorizontalLayout header = new HorizontalLayout(path, menuBar);
header.setExpandRatio(path, 1);
header.setWidth(100, Unit.PERCENTAGE);
header.addStyleNames(AcraTheme.PADDING_TOP, AcraTheme.PADDING_LEFT, AcraTheme.PADDING_RIGHT, AcraTheme.PADDING_BOTTOM, AcraTheme.BACKGROUND_HEADER);
HorizontalLayout footer = new HorizontalLayout();
footer.setDefaultComponentAlignment(Alignment.MIDDLE_CENTER);
Label footerLabel = new I18nLabel(ContentMode.HTML, i18n, Messages.FOOTER);
footerLabel.setWidth(100, Unit.PERCENTAGE);
footerLabel.addStyleName(AcraTheme.CENTER_TEXT);
footer.addComponent(footerLabel);
footer.setSpacing(false);
footer.setWidth(100, Unit.PERCENTAGE);
footer.addStyleNames(AcraTheme.BACKGROUND_FOOTER, AcraTheme.PADDING_LEFT, AcraTheme.PADDING_TOP, AcraTheme.PADDING_RIGHT, AcraTheme.PADDING_BOTTOM);
VerticalLayout root = new VerticalLayout(header, content, footer);
root.setExpandRatio(content, 1);
root.setSpacing(false);
root.setSizeFull();
root.addStyleName(AcraTheme.NO_PADDING);
setContent(root);
}
private boolean isDarkTheme() {
return Optional.ofNullable(UriComponentsBuilder.fromUri(getPage().getLocation()).build().getQueryParams().get(DARK_THEME)).map(list -> list.contains("true")).orElse(false);
}
@NonNull
@UIScope
@Bean
public Panel mainView() {
return content;
}
@NonNull
@UIScope
@Bean
public Path mainPath() {
return path;
}
}

View file

@ -1,35 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.ui.annotation;
import com.faendir.acra.model.Permission;
import org.springframework.lang.NonNull;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author Lukas
* @since 21.06.2017
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface RequiresAppPermission {
@NonNull Permission.Level value();
}

View file

@ -1,35 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.ui.annotation;
import com.faendir.acra.model.User;
import org.springframework.lang.NonNull;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author Lukas
* @since 31.05.2017
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface RequiresRole {
@NonNull User.Role value();
}

View file

@ -0,0 +1,9 @@
package com.faendir.acra.ui.base;
/**
* @author lukas
* @since 11.10.18
*/
public interface ActiveChildAware<C, P> {
void setActiveChild(C child, P parameter);
}

View file

@ -0,0 +1,29 @@
package com.faendir.acra.ui.base;
import com.faendir.acra.i18n.Messages;
import com.faendir.acra.rest.RestReportInterface;
import com.faendir.acra.ui.view.Overview;
import com.faendir.acra.util.PlainTextUser;
import com.vaadin.flow.component.Text;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.i18n.LocaleChangeEvent;
import com.vaadin.flow.i18n.LocaleChangeObserver;
import org.springframework.lang.NonNull;
/**
* @author lukas
* @since 09.11.18
*/
public class ConfigurationLabel extends Text implements LocaleChangeObserver {
private final PlainTextUser user;
public ConfigurationLabel(@NonNull PlainTextUser user) {
super("");
this.user = user;
}
@Override
public void localeChange(LocaleChangeEvent event) {
setText(getTranslation(Messages.CONFIGURATION_LABEL, UI.getCurrent().getRouter().getUrl(Overview.class), RestReportInterface.REPORT_PATH, user.getUsername(), user.getPlaintextPassword()));
}
}

View file

@ -0,0 +1,74 @@
package com.faendir.acra.ui.base;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.router.AfterNavigationEvent;
import com.vaadin.flow.router.BeforeEvent;
import com.vaadin.flow.router.HasUrlParameter;
import com.vaadin.flow.router.Location;
import com.vaadin.flow.router.NavigationTrigger;
import com.vaadin.flow.router.Router;
import org.springframework.context.ApplicationContext;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import java.util.ArrayList;
import java.util.List;
/**
* @author lukas
* @since 18.10.18
*/
public interface HasRoute {
@NonNull
Path.Element<?> getPathElement();
@Nullable
default Parent<?> getLogicalParent() {
return null;
}
default List<Path.Element<?>> getPathElements(ApplicationContext applicationContext, AfterNavigationEvent afterNavigationEvent) {
List<Path.Element<?>> list = new ArrayList<>();
list.add(getPathElement());
Parent<?> parent = getLogicalParent();
if (parent != null) {
list.addAll(parent.get(applicationContext, afterNavigationEvent).getPathElements(applicationContext, afterNavigationEvent));
}
return list;
}
class Parent<T extends HasRoute> {
private final Class<T> parentClass;
public Parent(Class<T> parentClass) {
this.parentClass = parentClass;
}
public T get(ApplicationContext applicationContext, AfterNavigationEvent afterNavigationEvent) {
return applicationContext.getBean(parentClass);
}
}
class ParametrizedParent<T extends HasRoute & HasUrlParameter<P>, P> extends Parent<T> {
private final P parameter;
public ParametrizedParent(Class<T> parentClass, P parameter) {
super(parentClass);
this.parameter = parameter;
}
@Override
public T get(ApplicationContext applicationContext, AfterNavigationEvent afterNavigationEvent) {
T t = super.get(applicationContext, afterNavigationEvent);
t.setParameter(new BeforeRouteEvent(afterNavigationEvent.getSource(), afterNavigationEvent.getLocation(), afterNavigationEvent.getActiveChain().get(0).getClass()), parameter);
return t;
}
}
class BeforeRouteEvent extends BeforeEvent {
public BeforeRouteEvent(Router router, Location location, Class<?> navigationTarget) {
super(router, NavigationTrigger.PAGE_LOAD,location, navigationTarget, UI.getCurrent());
}
}
}

View file

@ -0,0 +1,21 @@
package com.faendir.acra.ui.base;
import com.faendir.acra.security.SecurityUtils;
import com.vaadin.flow.router.BeforeEvent;
import com.vaadin.flow.router.HasUrlParameter;
/**
* @author lukas
* @since 19.11.18
*/
public
interface HasSecureIntParameter extends HasUrlParameter<Integer> {
@Override
default void setParameter(BeforeEvent event, Integer parameter) {
if (SecurityUtils.isLoggedIn()) {
setParameterSecure(event, parameter);
}
}
void setParameterSecure(BeforeEvent event, Integer parameter);
}

View file

@ -0,0 +1,21 @@
package com.faendir.acra.ui.base;
import com.faendir.acra.security.SecurityUtils;
import com.vaadin.flow.router.BeforeEvent;
import com.vaadin.flow.router.HasUrlParameter;
/**
* @author lukas
* @since 19.11.18
*/
public
interface HasSecureStringParameter extends HasUrlParameter<String> {
@Override
default void setParameter(BeforeEvent event, String parameter) {
if (SecurityUtils.isLoggedIn()) {
setParameterSecure(event, parameter);
}
}
void setParameterSecure(BeforeEvent event, String parameter);
}

View file

@ -0,0 +1,149 @@
package com.faendir.acra.ui.base;
import com.faendir.acra.ui.component.HasSize;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.HasStyle;
import com.vaadin.flow.component.Tag;
import com.vaadin.flow.server.InputStreamFactory;
import com.vaadin.flow.server.StreamResource;
import org.apache.batik.svggen.SVGGraphics2D;
import org.jfree.chart.JFreeChart;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.awt.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
/**
* @author lukas
* @since 11.10.18
*/
@Tag("object")
public class JFreeChartWrapper extends Component implements HasSize, HasStyle {
// 809x 500 ~g olden ratio
private static final int DEFAULT_WIDTH = 809;
private static final int DEFAULT_HEIGHT = 500;
private final JFreeChart chart;
private int graphWidthInPixels = -1;
private int graphHeightInPixels = -1;
private String aspectRatio = "none";
public JFreeChartWrapper(JFreeChart chartToBeWrapped) {
this.chart = chartToBeWrapped;
getElement().setAttribute("type", "image/svg+xml");
//getElement().getStyle().set("display", "block");
getElement().setAttribute("data", new StreamResource("chart" + System.currentTimeMillis() + ".svg", (InputStreamFactory) () -> {
int width = getGraphWidth();
int height = getGraphHeight();
DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder;
try {
docBuilder = docBuilderFactory.newDocumentBuilder();
} catch (ParserConfigurationException e1) {
throw new RuntimeException(e1);
}
Document document = docBuilder.newDocument();
Element svgelem = document.createElement("svg");
document.appendChild(svgelem);
// Create an instance of the SVG Generator
SVGGraphics2D svgGenerator = new SVGGraphics2D(document);
// draw the chart in the SVG generator
chart.draw(svgGenerator, new Rectangle(width, height));
Element el = svgGenerator.getRoot();
el.setAttributeNS(null, "viewBox", "0 0 " + width + " " + height + "");
el.setAttributeNS(null, "style", "width:100%;height:100%;");
el.setAttributeNS(null, "preserveAspectRatio", getSvgAspectRatio());
// Write svg to buffer
try (ByteArrayOutputStream stream = new ByteArrayOutputStream();
Writer out = new OutputStreamWriter(stream, StandardCharsets.UTF_8)) {
/*
* don't use css, FF3 can'd deal with the result perfectly: wrong font sizes
*/
boolean useCSS = false;
svgGenerator.stream(el, out, useCSS, false);
stream.flush();
return new ByteArrayInputStream(stream.toByteArray());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}));
}
/**
* This method may be used to tune rendering of the chart when using
* relative sizes. Most commonly you should use just use common methods
* inherited from {@link HasSize} interface.
* <p>
* Sets the pixel size of the area where the graph is rendered. Most commonly developer may need to fine tune the value when the {@link JFreeChartWrapper} has a relative size.
*
* @see JFreeChartWrapper#getGraphWidth()
* @see #setSvgAspectRatio(String)
*/
public void setGraphWidth(int width) {
graphWidthInPixels = width;
}
/**
* This method may be used to tune rendering of the chart when using
* relative sizes. Most commonly you should use just use common methods
* inherited from {@link HasSize} interface.
* <p>
* Sets the pixel size of the area where the graph is rendered. Most commonly developer may need to fine tune the value when the {@link JFreeChartWrapper} has a relative size.
*
* @see JFreeChartWrapper#getGraphHeight()
* @see #setSvgAspectRatio(String)
*/
public void setGraphHeight(int height) {
graphHeightInPixels = height;
}
/**
* Gets the pixel width into which the graph is rendered. Unless explicitly
* set, the value is derived from the components size, except when the
* component has relative size.
*/
public int getGraphWidth() {
if (graphWidthInPixels > 0) {
return graphWidthInPixels;
}
return DEFAULT_WIDTH;
}
/**
* Gets the pixel height into which the graph is rendered. Unless explicitly
* set, the value is derived from the components size, except when the
* component has relative size.
*/
public int getGraphHeight() {
if (graphHeightInPixels > 0) {
return graphHeightInPixels;
}
return DEFAULT_HEIGHT;
}
public String getSvgAspectRatio() {
return aspectRatio;
}
/**
* See SVG spec from W3 for more information.
* Default is "none" (stretch), another common value is "xMidYMid" (stretch
* proportionally, align middle of the area).
*/
public void setSvgAspectRatio(String svgAspectRatioSetting) {
aspectRatio = svgAspectRatioSetting;
}
}

View file

@ -0,0 +1,117 @@
package com.faendir.acra.ui.base;
import com.faendir.acra.dataprovider.QueryDslDataProvider;
import com.faendir.acra.ui.component.HasSize;
import com.querydsl.core.types.Expression;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.Composite;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.grid.ItemClickEvent;
import com.vaadin.flow.data.renderer.Renderer;
import com.vaadin.flow.data.selection.SelectionListener;
import com.vaadin.flow.function.ValueProvider;
import com.vaadin.flow.i18n.LocaleChangeEvent;
import com.vaadin.flow.i18n.LocaleChangeObserver;
import com.vaadin.flow.router.HasUrlParameter;
import com.vaadin.flow.shared.Registration;
import org.springframework.data.util.Pair;
import org.springframework.lang.NonNull;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
/**
* @author lukas
* @since 13.07.18
*/
public class MyGrid<T> extends Composite<Grid<T>> implements LocaleChangeObserver, HasSize {
private final QueryDslDataProvider<T> dataProvider;
private final Map<Grid.Column<T>, Pair<String, Object[]>> columnCaptions;
public MyGrid(QueryDslDataProvider<T> dataProvider) {
this.dataProvider = dataProvider;
getContent().setDataProvider(dataProvider);
getContent().setSizeFull();
getContent().setMultiSort(true);
getContent().setColumnReorderingAllowed(true);
columnCaptions = new HashMap<>();
}
@NonNull
public Grid.Column<T> addColumn(@NonNull ValueProvider<T, ?> valueProvider, @NonNull String captionId, Object... params) {
return setupColumn(getContent().addColumn(valueProvider), captionId, params);
}
@NonNull
public Grid.Column<T> addColumn(@NonNull ValueProvider<T, ?> valueProvider, @NonNull Expression<? extends Comparable> sort, @NonNull String captionId, Object... params) {
return setupSortableColumn(addColumn(valueProvider, captionId, params), sort);
}
@NonNull
public Grid.Column<T> addColumn(@NonNull Renderer<T> renderer) {
return getContent().addColumn(renderer);
}
@NonNull
public Grid.Column<T> addColumn(@NonNull Renderer<T> renderer, @NonNull String captionId, Object... params) {
return setupColumn(getContent().addColumn(renderer), captionId, params);
}
@NonNull
public Grid.Column<T> addColumn(@NonNull Renderer<T> renderer, @NonNull Expression<? extends Comparable> sort, @NonNull String captionId, Object... params) {
return setupSortableColumn(addColumn(renderer, captionId, params), sort);
}
private Grid.Column<T> setupColumn(@NonNull Grid.Column<T> column, @NonNull String captionId, Object... params) {
String caption = getTranslation(captionId, params);
column = column.setHeader(caption).setResizable(true).setFlexGrow(0).setWidth(Math.max(50, caption.length() * 10 + 20) + "px");
columnCaptions.put(column, Pair.of(captionId, params));
return column;
}
private Grid.Column<T> setupSortableColumn(@NonNull Grid.Column<T> column, @NonNull Expression<? extends Comparable> sort) {
column.setId(dataProvider.addSortable(sort));
column.setSortable(true);
return column;
}
public Registration addItemClickListener(ComponentEventListener<ItemClickEvent<T>> listener) {
return getContent().addItemClickListener(listener);
}
public <C extends Component & HasUrlParameter<R>, R> void addOnClickNavigation(Class<C> target, Function<T, R> parameterTransformer) {
getContent().addItemClickListener(e -> getUI().ifPresent(e.getButton() == 1 ? (ui -> ui.getPage().executeJavaScript("window.open(\"" + ui.getRouter().getUrl(target, parameterTransformer.apply(e.getItem())) + "\", \"blank\", \"\");")) : (ui -> ui.navigate(target, parameterTransformer.apply(e.getItem())))));
}
public Registration addSelectionListener(SelectionListener<Grid<T>, T> listener) {
return getContent().addSelectionListener(listener);
}
public void setSelectionMode(Grid.SelectionMode selectionMode) {
getContent().setSelectionMode(selectionMode);
}
public void deselectAll() {
getContent().deselectAll();
}
public QueryDslDataProvider<T> getDataProvider() {
return dataProvider;
}
public Set<T> getSelectedItems() {
return getContent().getSelectedItems();
}
public void setHeightToRows() {
getContent().setHeightByRows(true);
}
@Override
public void localeChange(LocaleChangeEvent event) {
columnCaptions.forEach((column, caption) -> column.setHeader(getTranslation(caption.getFirst(), caption.getSecond())));
}
}

View file

@ -0,0 +1,49 @@
package com.faendir.acra.ui.base;
import com.faendir.acra.ui.component.FlexLayout;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.HasElement;
import com.vaadin.flow.router.RouterLayout;
/**
* @author lukas
* @since 13.07.18
*/
public class ParentLayout extends FlexLayout implements RouterLayout {
private Component content;
private HasElement routerRoot;
public ParentLayout(HasElement routerRoot) {
this();
this.routerRoot = routerRoot;
}
public ParentLayout() {
this.routerRoot = this;
}
private void setContent(HasElement content, HasElement root) {
if (root == this) {
this.content = content instanceof Component ? (Component) content : null;
}
root.getElement().removeAllChildren();
root.getElement().appendChild(content.getElement());
}
public HasElement getContent() {
return content;
}
public void setContent(HasElement content) {
setContent(content, this);
}
public void setRouterRoot(HasElement routerRoot) {
this.routerRoot = routerRoot;
}
@Override
public void showRouterLayoutContent(HasElement content) {
setContent(content, routerRoot);
}
}

View file

@ -0,0 +1,131 @@
package com.faendir.acra.ui.base;
import com.faendir.acra.ui.component.FlexLayout;
import com.faendir.acra.ui.component.HasSize;
import com.faendir.acra.ui.component.Label;
import com.faendir.acra.ui.component.Translatable;
import com.vaadin.flow.component.AttachEvent;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.Composite;
import com.vaadin.flow.component.DetachEvent;
import com.vaadin.flow.component.html.Image;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.router.AfterNavigationEvent;
import com.vaadin.flow.router.AfterNavigationListener;
import com.vaadin.flow.router.HasUrlParameter;
import com.vaadin.flow.router.RouterLink;
import com.vaadin.flow.shared.Registration;
import org.springframework.context.ApplicationContext;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author lukas
* @since 18.10.18
*/
public class Path extends Composite<FlexLayout> implements AfterNavigationListener {
private final ApplicationContext applicationContext;
private Registration registration;
public Path(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
getContent().setAlignItems(FlexComponent.Alignment.CENTER);
getContent().setFlexWrap(FlexLayout.FlexWrap.WRAP);
}
@Override
public void afterNavigation(AfterNavigationEvent event) {
getContent().removeAll();
List<Element<?>> elements = event.getActiveChain().stream().filter(HasRoute.class::isInstance).map(HasRoute.class::cast).flatMap(e -> e.getPathElements(applicationContext, event).stream()).collect(Collectors.toList());
if (!elements.isEmpty()) {
Collections.reverse(elements);
getContent().add(elements.remove(0).toComponent());
for (Element<?> element : elements) {
getContent().add(VaadinIcon.CARET_RIGHT.create(), element.toComponent());
}
}
}
@Override
protected void onAttach(AttachEvent attachEvent) {
super.onAttach(attachEvent);
registration = attachEvent.getUI().addAfterNavigationListener(this);
}
@Override
protected void onDetach(DetachEvent detachEvent) {
registration.remove();
super.onDetach(detachEvent);
}
public static class Element<T extends Component> {
final Class<T> target;
final String titleId;
final Object[] params;
public Element(Class<T> target, String titleId, Object... params) {
this.target = target;
this.titleId = titleId;
this.params = params;
}
protected RouterLink createRouterLink() {
return new RouterLink("", target);
}
protected Component createContent() {
Translatable<Label> p = Translatable.createLabel(titleId, params);
p.setMargin(0, HasSize.Unit.PIXEL);
p.getStyle().set("padding-top", "2px");
return p;
}
public Component toComponent() {
RouterLink routerLink = createRouterLink();
Component component = createContent();
routerLink.add(component);
routerLink.getStyle().set("line-height", "32px");
routerLink.getStyle().set("padding", "1rem");
routerLink.getStyle().set("font-size", "130%");
routerLink.getStyle().set("text-decoration","none");
routerLink.getStyle().set("color","inherit");
return routerLink;
}
}
public static class ParametrizedTextElement<T extends Component & HasUrlParameter<P>, P> extends Element<T> {
final P parameter;
public ParametrizedTextElement(Class<T> target, P parameter, String titleId, Object... params) {
super(target, titleId, params);
this.parameter = parameter;
}
@Override
protected RouterLink createRouterLink() {
return new RouterLink("", target, parameter);
}
}
public static class ImageElement<T extends Component> extends Element<T> {
private final String src;
public ImageElement(Class<T> target, String src, String titleId, Object... params) {
super(target, titleId, params);
this.src = src;
}
@Override
protected Component createContent() {
Translatable<Image> image = Translatable.createImage(src, titleId, params);
image.setHeight(32, HasSize.Unit.PIXEL);
return image;
}
}
}

View file

@ -0,0 +1,48 @@
package com.faendir.acra.ui.base;
import com.faendir.acra.dataprovider.QueryDslDataProvider;
import com.faendir.acra.i18n.Messages;
import com.faendir.acra.model.App;
import com.faendir.acra.model.Permission;
import com.faendir.acra.model.QReport;
import com.faendir.acra.model.Report;
import com.faendir.acra.security.SecurityUtils;
import com.faendir.acra.service.AvatarService;
import com.faendir.acra.ui.base.popup.Popup;
import com.faendir.acra.ui.view.report.ReportView;
import com.faendir.acra.util.TimeSpanRenderer;
import com.vaadin.flow.component.ClickEvent;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.data.renderer.ComponentRenderer;
import org.springframework.lang.NonNull;
import java.util.function.Consumer;
/**
* @author lukas
* @since 17.09.18
*/
public class ReportList extends MyGrid<Report>{
public ReportList(@NonNull App app, @NonNull QueryDslDataProvider<Report> dataProvider, @NonNull AvatarService avatarService, @NonNull Consumer<Report> reportDeleter) {
super(dataProvider);
setSelectionMode(Grid.SelectionMode.NONE);
addColumn(new ComponentRenderer<>(avatarService::getAvatar) , QReport.report.installationId, Messages.USER);
addColumn(new TimeSpanRenderer<>(Report::getDate), QReport.report.date, Messages.DATE);
addColumn(report -> report.getStacktrace().getVersion().getCode(), QReport.report.stacktrace.version.code, Messages.APP_VERSION);
addColumn(Report::getAndroidVersion, QReport.report.androidVersion, Messages.ANDROID_VERSION);
addColumn(Report::getPhoneModel, QReport.report.phoneModel, Messages.DEVICE);
addColumn(report -> report.getStacktrace().getStacktrace().split("\n", 2)[0], QReport.report.stacktrace.stacktrace, Messages.STACKTRACE).setFlexGrow(1);
if (SecurityUtils.hasPermission(app, Permission.Level.EDIT)) {
addColumn(new ComponentRenderer<>(report -> new Button(new Icon(VaadinIcon.TRASH),
(ComponentEventListener<ClickEvent<Button>>) event -> new Popup().setTitle(Messages.DELETE_REPORT_CONFIRM).addYesNoButtons(p -> {
reportDeleter.accept(report);
getDataProvider().refreshAll();
}, true).show())));
}
addOnClickNavigation(ReportView.class, Report::getId);
}
}

View file

@ -14,26 +14,22 @@
* limitations under the License.
*/
package com.faendir.acra.ui.view.base.popup;
package com.faendir.acra.ui.base.popup;
import com.faendir.acra.i18n.I18nButton;
import com.faendir.acra.i18n.Messages;
import com.vaadin.ui.Button;
import com.vaadin.ui.Component;
import com.vaadin.ui.FormLayout;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.UI;
import com.vaadin.ui.Window;
import com.vaadin.ui.themes.AcraTheme;
import com.faendir.acra.ui.component.Translatable;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.HasSize;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.dialog.Dialog;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import org.springframework.data.util.Pair;
import org.springframework.lang.NonNull;
import org.vaadin.spring.i18n.I18N;
import org.vaadin.spring.i18n.support.Translatable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.Consumer;
@ -41,22 +37,21 @@ import java.util.function.Consumer;
* @author Lukas
* @since 19.12.2017
*/
public class Popup extends Window implements Translatable {
public class Popup extends Dialog {
private final List<Component> components;
private final Map<ValidatedField<?, ?>, Pair<Boolean, ValidatedField.Listener>> fields;
private final List<Button> buttons;
private final I18N i18n;
private final String captionId;
private final Object[] params;
private final List<Translatable<Button>> buttons;
public Popup(I18N i18n, String captionId, Object... params) {
this.i18n = i18n;
this.captionId = captionId;
this.params = params;
public Popup() {
components = new ArrayList<>();
fields = new HashMap<>();
buttons = new ArrayList<>();
updateMessageStrings(i18n.getLocale());
}
@NonNull
public Popup setTitle(@NonNull String titleId, Object... params) {
components.add(0, Translatable.createText(titleId, params));
return this;
}
@NonNull
@ -66,18 +61,18 @@ public class Popup extends Window implements Translatable {
@NonNull
public Popup addCreateButton(@NonNull Consumer<Popup> onCreateAction, boolean closeAfter) {
buttons.add(new I18nButton(event -> {
buttons.add(Translatable.createButton(event -> {
onCreateAction.accept(this);
if (closeAfter) {
close();
}
}, i18n, Messages.CREATE));
}, Messages.CREATE));
return this;
}
@NonNull
public Popup addCloseButton() {
buttons.add(new I18nButton(event -> close(), i18n, Messages.CLOSE));
buttons.add(Translatable.createButton(event -> close(), Messages.CLOSE));
return this;
}
@ -88,13 +83,13 @@ public class Popup extends Window implements Translatable {
@NonNull
public Popup addYesNoButtons(@NonNull Consumer<Popup> onYesAction, boolean closeAfter) {
buttons.add(new I18nButton(event -> {
buttons.add(Translatable.createButton(event -> {
onYesAction.accept(this);
if (closeAfter) {
close();
}
}, i18n, Messages.YES));
buttons.add(new I18nButton(event -> close(), i18n, Messages.NO));
}, Messages.YES));
buttons.add(Translatable.createButton(event -> close(), Messages.NO));
return this;
}
@ -134,28 +129,30 @@ public class Popup extends Window implements Translatable {
} else if (buttons.size() > 1) {
HorizontalLayout buttonLayout = new HorizontalLayout();
components.add(buttonLayout);
buttons.forEach(buttonLayout::addComponent);
buttons.forEach(button -> button.setWidth(100, Unit.PERCENTAGE));
buttons.forEach(buttonLayout::add);
buttons.forEach(com.faendir.acra.ui.component.HasSize::setWidthFull);
}
components.forEach(component -> component.setWidth(100, Unit.PERCENTAGE));
components.forEach(component -> {
if(component instanceof HasSize) {
try {
((HasSize)component).setWidth("100%");
}catch (UnsupportedOperationException e) {
}
}
});
FormLayout layout = new FormLayout();
components.forEach(layout::addComponent);
layout.setResponsiveSteps(new FormLayout.ResponsiveStep("0px", 1));
components.forEach(c -> layout.addFormItem(c, ""));
checkValid();
layout.addStyleNames(AcraTheme.PADDING_LEFT, AcraTheme.PADDING_RIGHT);
setContent(layout);
center();
if (!isAttached()) {
UI.getCurrent().addWindow(this);
removeAll();
add(layout);
if (!isOpened()) {
open();
}
}
private void checkValid() {
boolean valid = fields.values().stream().map(Pair::getFirst).reduce(Boolean::logicalAnd).orElse(true);
buttons.forEach(button -> button.setEnabled(valid));
}
@Override
public void updateMessageStrings(Locale locale) {
setCaption(i18n.get(captionId, locale, params));
buttons.forEach(button -> button.getContent().setEnabled(valid));
}
}

View file

@ -14,14 +14,13 @@
* limitations under the License.
*/
package com.faendir.acra.ui.view.base.popup;
package com.faendir.acra.ui.base.popup;
import com.faendir.acra.i18n.HasI18n;
import com.vaadin.server.UserError;
import com.vaadin.ui.AbstractComponent;
import com.vaadin.ui.AbstractField;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.Composite;
import com.vaadin.flow.component.HasValidation;
import com.vaadin.flow.component.HasValue;
import org.springframework.lang.NonNull;
import org.vaadin.spring.i18n.I18N;
import java.util.ArrayList;
import java.util.HashMap;
@ -35,34 +34,38 @@ import java.util.function.Supplier;
* @author Lukas
* @since 22.06.2017
*/
public class ValidatedField<V, T extends AbstractComponent> {
public class ValidatedField<V, T extends Component> {
private final T field;
private final Supplier<V> valueSupplier;
private final I18N i18n;
private final Consumer<String> messageSetter;
private final Map<Function<V, Boolean>, String> validators;
private final List<Listener> listeners;
private boolean valid;
private ValidatedField(T field, Supplier<V> valueSupplier, Consumer<Consumer<V>> listenerRegistration, I18N i18n) {
private ValidatedField(T field, Supplier<V> valueSupplier, Consumer<Consumer<V>> listenerRegistration, Consumer<String> messageSetter) {
this.field = field;
this.valueSupplier = valueSupplier;
this.i18n = i18n;
this.messageSetter = messageSetter;
this.validators = new HashMap<>();
this.listeners = new ArrayList<>();
this.valid = false;
listenerRegistration.accept(this::validate);
}
public static <V, T extends AbstractField<V> & HasI18n> ValidatedField<V, T> of(T field) {
return new ValidatedField<>(field, field::getValue, vConsumer -> field.addValueChangeListener(event -> vConsumer.accept(event.getValue())), field.getI18n());
public static <V, T extends Component & HasValue<?, V> & HasValidation> ValidatedField<V, T> of(T field) {
return new ValidatedField<>(field, field::getValue, vConsumer -> field.addValueChangeListener(event -> vConsumer.accept(event.getValue())), field::setErrorMessage);
}
public static <V, T extends AbstractComponent & HasI18n> ValidatedField<V, T> of(T field, Supplier<V> valueSupplier, Consumer<Consumer<V>> listenerRegistration) {
return new ValidatedField<>(field, valueSupplier, listenerRegistration, field.getI18n());
public static <V, C extends Composite<T>, T extends Component & HasValue<?, V> & HasValidation> ValidatedField<V, C> of(C field) {
return new ValidatedField<>(field, () -> field.getContent().getValue(), vConsumer -> field.getContent().addValueChangeListener(event -> vConsumer.accept(event.getValue())), m -> field.getContent().setErrorMessage(m));
}
public ValidatedField<V, T> addValidator(Function<V, Boolean> validator, String errorMessageId) {
validators.put(validator, errorMessageId);
public static <V, T extends Component & HasValue<?, V> & HasValidation> ValidatedField<V, T> of(T field, Supplier<V> valueSupplier, Consumer<Consumer<V>> listenerRegistration) {
return new ValidatedField<>(field, valueSupplier, listenerRegistration, field::setErrorMessage);
}
public ValidatedField<V, T> addValidator(Function<V, Boolean> validator, String errorMessage) {
validators.put(validator, errorMessage);
return this;
}
@ -77,10 +80,10 @@ public class ValidatedField<V, T extends AbstractComponent> {
private boolean validate(V value) {
boolean valid = validators.entrySet().stream().allMatch(entry -> {
if (entry.getKey().apply(value)) {
field.setComponentError(null);
messageSetter.accept(null);
return true;
} else {
field.setComponentError(new UserError(i18n.get(entry.getValue())));
messageSetter.accept(entry.getValue());
return false;
}
});

View file

@ -0,0 +1,62 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.ui.base.statistics;
import com.faendir.acra.ui.component.Card;
import com.faendir.acra.ui.base.JFreeChartWrapper;
import com.faendir.acra.ui.component.HasSize;
import com.faendir.acra.ui.component.Translatable;
import com.vaadin.flow.component.Composite;
import org.jfree.chart.JFreeChart;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import java.awt.*;
import java.util.Map;
/**
* @author lukas
* @since 01.06.18
*/
abstract class Chart<T> extends Composite<Card> {
private JFreeChart chart;
Chart(@NonNull String captionId, @NonNull Object... params) {
getContent().setWidth(500, HasSize.Unit.PIXEL);
getContent().setMaxWidthFull();
getContent().setHeader(Translatable.createText(captionId, params));
}
public void setContent(@NonNull Map<T, Long> map) {
chart = createChart(map);
JFreeChartWrapper content = new JFreeChartWrapper(chart);
content.setSvgAspectRatio("xMidYMid");
content.setWidthFull();
getContent().removeAll();
getContent().add(content);
}
Paint getForegroundColor() {
return /*UI.getCurrent().getTheme().toLowerCase().contains("dark")*/false ? Statistics.FOREGROUND_DARK : Statistics.FOREGROUND_LIGHT;
}
protected abstract JFreeChart createChart(@NonNull Map<T, Long> map);
@Nullable
JFreeChart getChart() {
return chart;
}
}

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.faendir.acra.ui.view.base.statistics;
package com.faendir.acra.ui.base.statistics;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
@ -24,9 +24,11 @@ import org.jfree.chart.plot.PiePlot;
import org.jfree.chart.util.SortOrder;
import org.jfree.data.general.DefaultPieDataset;
import org.springframework.lang.NonNull;
import org.vaadin.spring.i18n.I18N;
import java.awt.*;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@ -35,18 +37,25 @@ import java.util.Map;
* @since 01.06.18
*/
class PieChart extends Chart<String> {
PieChart(I18N i18n, String captionId, Object... params) {
super(i18n, captionId, params);
private static final int MAX_PARTS = 4;
PieChart(@NonNull String captionId, @NonNull Object... params) {
super(captionId, params);
}
@Override
public JFreeChart createChart(@NonNull Map<String, Long> map) {
List<Map.Entry<String, Long>> values = new ArrayList<>(map.entrySet());
values.sort((e1, e2) -> Long.compare(e2.getValue(), e1.getValue()));
DefaultPieDataset dataset = new DefaultPieDataset();
map.forEach((label, count) -> dataset.insertValue(0, label, count));
dataset.sortByKeys(SortOrder.ASCENDING);
values.subList(0, Math.min(MAX_PARTS, values.size())).forEach(e -> dataset.insertValue(0, e.getKey(), e.getValue()));
dataset.sortByValues(SortOrder.DESCENDING);
if (values.size() > MAX_PARTS) {
dataset.insertValue(dataset.getItemCount(), "Other", values.subList(MAX_PARTS, values.size()).stream().mapToLong(Map.Entry::getValue).sum());
}
JFreeChart chart = ChartFactory.createPieChart("", dataset, false, false, false);
chart.setBackgroundPaint(null);
PiePlot plot = (PiePlot) chart.getPlot();
plot.setStartAngle(0);
plot.setShadowPaint(null);
plot.setBackgroundAlpha(0);
plot.setOutlineVisible(false);
@ -55,9 +64,10 @@ class PieChart extends Chart<String> {
plot.setLabelShadowPaint(null);
Paint foregroundColor = getForegroundColor();
plot.setLabelPaint(foregroundColor);
plot.setLabelFont(Statistics.LABEL_FONT);
plot.setLabelLinkPaint(foregroundColor);
plot.setLabelLinkStyle(PieLabelLinkStyle.QUAD_CURVE);
plot.setLabelGenerator(new StandardPieSectionLabelGenerator("{0} ({2})"));
plot.setLabelGenerator(new StandardPieSectionLabelGenerator("{0} ({2})", NumberFormat.getNumberInstance(), new DecimalFormat("0.0%")));
//noinspection unchecked
((List<String>) dataset.getKeys()).forEach(key -> plot.setExplodePercent(key, 0.01));
return chart;

View file

@ -13,66 +13,69 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.ui.view.base.statistics;
package com.faendir.acra.ui.base.statistics;
import com.faendir.acra.i18n.I18nCheckBox;
import com.faendir.acra.model.App;
import com.faendir.acra.service.DataService;
import com.faendir.acra.ui.component.Translatable;
import com.querydsl.core.types.Expression;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.ComparableExpressionBase;
import com.querydsl.core.types.dsl.DateTimePath;
import com.querydsl.sql.SQLExpressions;
import com.vaadin.data.HasValue;
import com.vaadin.server.Sizeable;
import com.vaadin.ui.CheckBox;
import com.vaadin.ui.ComboBox;
import com.vaadin.ui.Component;
import com.vaadin.ui.ComponentContainer;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.HasEnabled;
import com.vaadin.flow.component.HasSize;
import com.vaadin.flow.component.HasStyle;
import com.vaadin.flow.component.HasValue;
import com.vaadin.flow.component.checkbox.Checkbox;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.component.textfield.TextField;
import org.springframework.lang.Nullable;
import org.vaadin.risto.stepper.IntStepper;
import org.vaadin.spring.i18n.I18N;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.List;
import java.util.function.Function;
/**
* @author lukas
* @since 01.06.18
*/
class Property<F, C extends Component & HasValue<F>, T> {
class Property<F, C extends Component & HasValue<?, F> & HasEnabled & HasSize & HasStyle, T> {
private final App app;
private final DataService dataService;
private final CheckBox checkBox;
private final Translatable<Checkbox> checkBox;
private final C filterComponent;
private final Function<F, BooleanExpression> filter;
private final Chart<T> chart;
private final Expression<T> select;
private Property(App app, C filterComponent, Function<F, BooleanExpression> filter, Chart<T> chart, DataService dataService, Expression<T> select, I18N i18n, String filterTextId) {
private Property(App app, C filterComponent, Function<F, BooleanExpression> filter, Chart<T> chart, DataService dataService, Expression<T> select, String filterTextId, Object... params) {
this.app = app;
this.dataService = dataService;
this.checkBox = new I18nCheckBox(i18n, filterTextId);
this.checkBox = Translatable.createCheckbox(false, filterTextId, params);
checkBox.preventWhiteSpaceBreaking();
this.filterComponent = filterComponent;
this.filter = filter;
this.chart = chart;
this.select = select;
filterComponent.setEnabled(false);
filterComponent.setWidth(100, Sizeable.Unit.PERCENTAGE);
checkBox.setValue(false);
checkBox.addValueChangeListener(e -> filterComponent.setEnabled(e.getValue()));
filterComponent.setWidth("100%");
checkBox.getContent().addValueChangeListener(e -> filterComponent.setEnabled(e.getValue()));
}
void addTo(ComponentContainer filterLayout, ComponentContainer chartLayout) {
filterLayout.addComponent(checkBox);
filterLayout.addComponent(filterComponent);
chartLayout.addComponent(chart);
void addTo(FormLayout filterLayout, FlexComponent chartLayout) {
filterLayout.addFormItem(filterComponent, checkBox);
chartLayout.add(chart);
chartLayout.expand(chart);
}
BooleanExpression applyFilter(@Nullable BooleanExpression expression) {
if (checkBox.getValue() && filterComponent.getValue() != null) {
if (checkBox.getContent().getValue() && filterComponent.getValue() != null) {
return filter.apply(filterComponent.getValue()).and(expression);
}
return expression;
@ -91,22 +94,23 @@ class Property<F, C extends Component & HasValue<F>, T> {
this.expression = expression;
}
Property<?, ?, ?> createStringProperty(App app, ComparableExpressionBase<String> stringExpression, I18N i18n, String filterTextId, String chartTitleId) {
ComboBox<String> comboBox = new ComboBox<>(null, dataService.getFromReports(app, expression, stringExpression));
comboBox.setEmptySelectionAllowed(false);
return new Property<>(app, comboBox, stringExpression::eq, new PieChart(i18n, chartTitleId), dataService, stringExpression, i18n, filterTextId);
Property<?, ?, ?> createStringProperty(App app, ComparableExpressionBase<String> stringExpression, String filterTextId, String chartTitleId) {
List<String> list = dataService.getFromReports(app, expression, stringExpression);
ComboBox<String> comboBox = new ComboBox<>(null, list);
comboBox.setAllowCustomValue(false);
comboBox.setRequired(true);
comboBox.setValue(list.get(0));
return new Property<>(app, comboBox, stringExpression::eq, new PieChart(chartTitleId), dataService, stringExpression, filterTextId);
}
Property<?, ?, ?> createAgeProperty(App app, DateTimePath<ZonedDateTime> dateTimeExpression, I18N i18n, String filterTextId, String chartTitleId) {
IntStepper stepper = new IntStepper();
stepper.setValue(30);
stepper.setMinValue(1);
Property<?, ?, ?> createAgeProperty(App app, DateTimePath<ZonedDateTime> dateTimeExpression, String filterTextId, String chartTitleId) {
TextField stepper = new TextField();
stepper.setValue("30");
return new Property<>(app, stepper,
days -> dateTimeExpression.after(ZonedDateTime.now().minus(days, ChronoUnit.DAYS)),
new TimeChart(i18n, chartTitleId),
days -> dateTimeExpression.after(ZonedDateTime.now().minus(Integer.parseInt(days), ChronoUnit.DAYS)),
new TimeChart(chartTitleId),
dataService,
SQLExpressions.date(Date.class, dateTimeExpression),
i18n,
filterTextId);
}
}

View file

@ -14,25 +14,21 @@
* limitations under the License.
*/
package com.faendir.acra.ui.view.base.statistics;
package com.faendir.acra.ui.base.statistics;
import com.faendir.acra.i18n.I18nButton;
import com.faendir.acra.i18n.I18nPanel;
import com.faendir.acra.i18n.Messages;
import com.faendir.acra.model.App;
import com.faendir.acra.model.QReport;
import com.faendir.acra.service.DataService;
import com.faendir.acra.ui.view.base.layout.FlexLayout;
import com.faendir.acra.ui.component.Card;
import com.faendir.acra.ui.component.FlexLayout;
import com.faendir.acra.ui.component.HasSize;
import com.faendir.acra.ui.component.Translatable;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.Composite;
import com.vaadin.ui.GridLayout;
import com.vaadin.ui.Panel;
import com.vaadin.ui.themes.AcraTheme;
import com.vaadin.flow.component.Composite;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.textfield.TextField;
import org.springframework.lang.Nullable;
import org.vaadin.risto.stepper.IntStepper;
import org.vaadin.spring.i18n.I18N;
import java.awt.*;
import java.util.ArrayList;
@ -42,50 +38,43 @@ import java.util.List;
* @author lukas
* @since 21.05.18
*/
public class Statistics extends Composite {
public class Statistics extends Composite<FlexLayout> {
static final Color BLUE = new Color(0x197de1); //vaadin blue
static final Color FOREGROUND_DARK = new Color(0xcacecf);
static final Color FOREGROUND_LIGHT = new Color(0x464646);
@Nullable private final BooleanExpression baseExpression;
static final Font LABEL_FONT = new Font("Roboto", Font.PLAIN, 18);
@Nullable
private final BooleanExpression baseExpression;
private final List<Property<?, ?, ?>> properties;
public Statistics(App app, @Nullable BooleanExpression baseExpression, DataService dataService, I18N i18n) {
public Statistics(App app, @Nullable BooleanExpression baseExpression, DataService dataService) {
this.baseExpression = baseExpression;
properties = new ArrayList<>();
GridLayout filterLayout = new GridLayout(2, 1);
filterLayout.setDefaultComponentAlignment(Alignment.MIDDLE_LEFT);
filterLayout.setSpacing(true);
filterLayout.setSizeFull();
filterLayout.addStyleNames(AcraTheme.PADDING_LEFT, AcraTheme.PADDING_TOP, AcraTheme.PADDING_RIGHT, AcraTheme.PADDING_BOTTOM);
filterLayout.setColumnExpandRatio(1, 1);
FormLayout filterLayout = new FormLayout();
filterLayout.setResponsiveSteps(new FormLayout.ResponsiveStep("0px", 1));
filterLayout.setWidth("100%");
Card card = new Card(filterLayout);
card.setHeader(Translatable.createText(Messages.FILTER));
card.setWidth(500, HasSize.Unit.PIXEL);
IntStepper dayStepper = new IntStepper();
dayStepper.setValue(30);
dayStepper.setMinValue(1);
TextField dayStepper = new TextField();
dayStepper.setValue("30");
Property.Factory factory = new Property.Factory(dataService, baseExpression);
properties.add(factory.createAgeProperty(app, QReport.report.date, i18n, Messages.LAST_X_DAYS, Messages.REPORTS_OVER_TIME));
properties.add(factory.createStringProperty(app, QReport.report.androidVersion, i18n, Messages.ANDROID_VERSION, Messages.REPORTS_PER_ANDROID_VERSION));
properties.add(factory.createStringProperty(app, QReport.report.stacktrace.version.name, i18n, Messages.APP_VERSION, Messages.REPORTS_PER_APP_VERSION));
properties.add(factory.createStringProperty(app, QReport.report.phoneModel, i18n, Messages.PHONE_MODEL, Messages.REPORTS_PER_PHONE_MODEL));
properties.add(factory.createStringProperty(app, QReport.report.brand, i18n, Messages.PHONE_BRAND, Messages.REPORTS_PER_BRAND));
properties.add(factory.createAgeProperty(app, QReport.report.date, Messages.LAST_X_DAYS, Messages.REPORTS_OVER_TIME));
properties.add(factory.createStringProperty(app, QReport.report.androidVersion, Messages.ANDROID_VERSION, Messages.REPORTS_PER_ANDROID_VERSION));
properties.add(factory.createStringProperty(app, QReport.report.stacktrace.version.name, Messages.APP_VERSION, Messages.REPORTS_PER_APP_VERSION));
properties.add(factory.createStringProperty(app, QReport.report.phoneModel, Messages.PHONE_MODEL, Messages.REPORTS_PER_PHONE_MODEL));
properties.add(factory.createStringProperty(app, QReport.report.brand, Messages.PHONE_BRAND, Messages.REPORTS_PER_BRAND));
Panel filterPanel = new I18nPanel(filterLayout, i18n, Messages.FILTER);
filterPanel.addStyleName(AcraTheme.NO_BACKGROUND);
FlexLayout layout = new FlexLayout(filterPanel);
layout.setWidth(100, Unit.PERCENTAGE);
getContent().setFlexWrap(FlexLayout.FlexWrap.WRAP);
getContent().setWidthFull();
getContent().removeAll();
properties.forEach(property -> property.addTo(filterLayout, layout));
getContent().add(card);
getContent().expand(card);
properties.forEach(property -> property.addTo(filterLayout, getContent()));
Button applyButton = new I18nButton(e -> update(), i18n, Messages.APPLY);
applyButton.setWidth(100, Unit.PERCENTAGE);
filterLayout.space();
filterLayout.addComponent(applyButton);
filterLayout.newLine();
Panel root = new Panel(layout);
root.setSizeFull();
root.addStyleNames(AcraTheme.NO_BACKGROUND, AcraTheme.NO_BORDER);
setCompositionRoot(root);
filterLayout.add(Translatable.createButton(e -> update(), Messages.APPLY));
update();
}

View file

@ -13,9 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.ui.view.base.statistics;
package com.faendir.acra.ui.base.statistics;
import com.faendir.acra.i18n.Messages;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberTickUnitSource;
@ -28,11 +27,9 @@ import org.jfree.data.time.Day;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
import org.springframework.lang.NonNull;
import org.vaadin.spring.i18n.I18N;
import java.awt.*;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
/**
@ -40,19 +37,19 @@ import java.util.Map;
* @since 01.06.18
*/
class TimeChart extends Chart<Date> {
TimeChart(I18N i18n, String captionId, Object... params) {
super(i18n, captionId, params);
TimeChart(@NonNull String captionId, @NonNull Object... params) {
super(captionId, params);
}
@Override
public JFreeChart createChart(@NonNull Map<Date, Long> map) {
TimeSeries series = new TimeSeries(getI18n().get(Messages.DATE));
TimeSeries series = new TimeSeries("Date");
series.add(new Day(new Date()), 0);
map.forEach((date, count) -> series.addOrUpdate(new Day(date), count));
JFreeChart chart = ChartFactory.createXYBarChart("",
getI18n().get(Messages.DATE),
"Date",
true,
getI18n().get(Messages.REPORTS),
"Reports",
new TimeSeriesCollection(series),
PlotOrientation.VERTICAL,
false,
@ -68,11 +65,15 @@ class TimeChart extends Chart<Date> {
plot.setRangeGridlinePaint(foregroundColor);
ValueAxis domainAxis = plot.getDomainAxis();
domainAxis.setLabelPaint(foregroundColor);
domainAxis.setLabelFont(Statistics.LABEL_FONT);
domainAxis.setTickLabelFont(Statistics.LABEL_FONT);
domainAxis.setTickLabelPaint(foregroundColor);
domainAxis.setAxisLinePaint(foregroundColor);
domainAxis.setTickMarkPaint(foregroundColor);
ValueAxis rangeAxis = plot.getRangeAxis();
rangeAxis.setLabelPaint(foregroundColor);
rangeAxis.setLabelFont(Statistics.LABEL_FONT);
rangeAxis.setTickLabelFont(Statistics.LABEL_FONT);
rangeAxis.setTickLabelPaint(foregroundColor);
rangeAxis.setAxisLinePaint(foregroundColor);
rangeAxis.setTickMarkPaint(foregroundColor);
@ -83,15 +84,4 @@ class TimeChart extends Chart<Date> {
barRenderer.setMargin(0.2);
return chart;
}
@Override
public void updateMessageStrings(Locale locale) {
super.updateMessageStrings(locale);
if(getChart() != null) {
XYPlot plot = getChart().getXYPlot();
plot.getDomainAxis().setLabel(getI18n().get(Messages.DATE));
plot.getRangeAxis().setLabel(getI18n().get(Messages.REPORTS));
markAsDirtyRecursive();
}
}
}

View file

@ -0,0 +1,94 @@
package com.faendir.acra.ui.component;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.Composite;
import com.vaadin.flow.component.HasStyle;
import com.vaadin.flow.component.html.Div;
/**
* @author lukas
* @since 18.10.18
*/
public class Card extends Composite<Div> implements HasSize, HasStyle {
private final Div header;
private final Div content;
private boolean allowCollapse = false;
public Card() {
getContent().getStyle().set("box-shadow", "0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23)");
getContent().getStyle().set("border-radius", "2px");
getContent().getStyle().set("margin", "1rem");
getContent().getStyle().set("display", "inline-block");
header = new Div();
header.getStyle().set("padding", "1rem");
header.getStyle().set("box-sizing", "border-box");
header.getStyle().set("background-color", "var(--lumo-contrast-5pct)");
header.getStyle().set("display", "inline-block");
header.setWidth("100%");
header.addClickListener(e -> {
if (allowCollapse) {
if (isCollapsed()) {
expand();
} else {
collapse();
}
}
});
content = new Div();
content.getStyle().set("padding", "1rem");
content.getStyle().set("box-sizing", "border-box");
content.getStyle().set("display", "inline-block");
content.setSizeFull();
getContent().add(header, content);
}
public Card(Component... components) {
this();
add(components);
}
public void removeAll() {
content.removeAll();
}
public void addComponentAtIndex(int index, Component component) {
content.addComponentAtIndex(index, component);
}
public void addComponentAsFirst(Component component) {
content.addComponentAsFirst(component);
}
public void add(Component... components) {
content.add(components);
}
public void remove(Component... components) {
content.remove(components);
}
public void setHeader(Component... components) {
header.removeAll();
header.add(components);
}
public boolean allowsCollapse() {
return allowCollapse;
}
public void setAllowCollapse(boolean allowCollapse) {
this.allowCollapse = allowCollapse;
}
public void collapse() {
content.getStyle().set("display", "none");
}
public void expand() {
content.getStyle().set("display", "inline-block");
}
public boolean isCollapsed() {
return content.getStyle().get("display").equals("none");
}
}

View file

@ -0,0 +1,61 @@
package com.faendir.acra.ui.component;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.html.Div;
import org.springframework.lang.NonNull;
/**
* @author lukas
* @since 19.11.18
*/
public class CssGrid extends Div implements HasSize, HasStyle {
public CssGrid() {
getStyle().set("display", "grid");
}
public CssGrid(Component... components) {
this();
add(components);
}
public void setTemplateColumns(String template) {
getStyle().set("grid-template-columns", template);
}
public void setColumnGap(int size, Unit unit) {
getStyle().set("grid-column-gap", size + unit.getText());
}
public void setJustifyItems(JustifyMode justifyMode) {
getStyle().set("justify-items", justifyMode.value);
}
public void setAlignItems(AlignMode alignMode) {
getStyle().set("align-items", alignMode.value);
}
public void alignItems(AlignMode alignMode, com.vaadin.flow.component.HasStyle... components) {
for (com.vaadin.flow.component.HasStyle component : components) {
component.getStyle().set("align-self", alignMode.value);
}
}
public enum JustifyMode {
START("start");
private final String value;
JustifyMode(@NonNull String value) {
this.value = value;
}
}
public enum AlignMode {
CENTER("center"),
FIRST_BASELINE("first baseline");
private final String value;
AlignMode(@NonNull String value) {
this.value = value;
}
}
}

View file

@ -0,0 +1,17 @@
package com.faendir.acra.ui.component;
import com.vaadin.flow.component.html.Anchor;
import com.vaadin.flow.server.AbstractStreamResource;
import org.springframework.lang.NonNull;
/**
* @author lukas
* @since 15.11.18
*/
public class DownloadButton extends Anchor {
public DownloadButton(@NonNull AbstractStreamResource href, @NonNull String captionId, @NonNull Object... params) {
super(href, "");
getElement().setAttribute("download", true);
add(Translatable.createButton(captionId, params));
}
}

View file

@ -0,0 +1,53 @@
package com.faendir.acra.ui.component;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.HasComponents;
import com.vaadin.flow.component.Tag;
import com.vaadin.flow.component.dependency.HtmlImport;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* @author lukas
* @since 15.11.18
*/
@Tag("simple-dropdown")
@HtmlImport("bower_components/simple-dropdown/simple-dropdown.html")
public class DropdownMenu extends Component implements HasComponents, HasSize, HasStyle {
public DropdownMenu() {
}
public DropdownMenu(Component... components) {
this();
add(components);
}
public enum Origin {
LEFT, TOP, RIGHT, BOTTOM, CENTER;
}
public void setOpen(boolean open) {
getElement().setProperty("active", open);
}
public boolean isOpen() {
return getElement().getProperty("active", false);
}
public void setOrigin(Origin... origin) {
getElement().setProperty("origin", Stream.of(origin).map(Origin::name).map(String::toLowerCase).collect(Collectors.joining(" ")));
}
public Origin[] getOrigin() {
return Stream.of(getElement().getProperty("origin").split(" ")).map(String::toUpperCase).map(Origin::valueOf).toArray(Origin[]::new);
}
public void setLabel(String label) {
getElement().setProperty("label", label);
}
public String getLabel(){
return getElement().getProperty("label");
}
}

View file

@ -0,0 +1,44 @@
package com.faendir.acra.ui.component;
import com.vaadin.flow.component.Component;
import org.springframework.lang.NonNull;
/**
* @author lukas
* @since 14.11.18
*/
public class FlexLayout extends com.vaadin.flow.component.orderedlayout.FlexLayout implements HasSize, HasStyle {
public FlexLayout(Component... components) {
super(components);
}
public FlexLayout() {
}
public void setFlexDirection(FlexDirection flexDirection) {
getStyle().set("flex-direction", flexDirection.value);
}
public void setFlexWrap(FlexWrap flexWrap) {
getStyle().set("flex-wrap", flexWrap.value);
}
public enum FlexWrap {
WRAP("wrap");
private final String value;
FlexWrap(@NonNull String value) {
this.value = value;
}
}
public enum FlexDirection {
COLUMN("column"),
ROW("row");
private final String value;
FlexDirection(@NonNull String value) {
this.value = value;
}
}
}

View file

@ -0,0 +1,73 @@
package com.faendir.acra.ui.component;
import com.vaadin.flow.component.HasStyle;
/**
* @author lukas
* @since 14.11.18
*/
public interface HasSize extends com.vaadin.flow.component.HasSize, HasStyle {
enum Unit {
PERCENTAGE("%"),
PIXEL("px"),
REM("rem"),
EM("em");
private final String text;
Unit(String text) {
this.text = text;
}
public String getText() {
return text;
}
}
default void setWidthFull() {
setWidth(100, Unit.PERCENTAGE);
}
default void setWidth(int value, Unit unit) {
setWidth(value + unit.getText());
}
default void setMaxWidth(int value, Unit unit) {
getStyle().set("max-width", value + unit.getText());
}
default void setMaxWidthFull(){
setMaxWidth(100, Unit.PERCENTAGE);
}
default void setMinWidth(int value, Unit unit) {
getStyle().set("min-width", value + unit.getText());
}
default void setMinWidthFull(){
setMinWidth(100, Unit.PERCENTAGE);
}
default void setHeightFull() {
setHeight(100, Unit.PERCENTAGE);
}
default void setHeight(int value, Unit unit) {
setHeight(value + unit.getText());
}
default void setMaxHeight(int value, Unit unit) {
getStyle().set("max-height", value + unit.getText());
}
default void setMaxHeightFull(){
setMaxHeight(100, Unit.PERCENTAGE);
}
default void setMinHeight(int value, Unit unit) {
getStyle().set("min-height", value + unit.getText());
}
default void setMinHeightFull(){
setMinHeight(100, Unit.PERCENTAGE);
}
}

View file

@ -0,0 +1,24 @@
package com.faendir.acra.ui.component;
/**
* @author lukas
* @since 15.11.18
*/
public interface HasStyle extends com.vaadin.flow.component.HasStyle {
default void preventWhiteSpaceBreaking() {
getStyle().set("white-space", "nowrap");
}
default void setMargin(int value, HasSize.Unit unit) {
getStyle().set("margin", value + unit.getText());
}
default void setDefaultTextStyle() {
getStyle().set("text-decoration","none");
getStyle().set("color","inherit");
}
default void setPadding(int value, HasSize.Unit unit) {
getStyle().set("padding", value + unit.getText());
}
}

View file

@ -0,0 +1,24 @@
package com.faendir.acra.ui.component;
/**
* @author lukas
* @since 19.11.18
*/
public class Label extends com.vaadin.flow.component.html.Label {
public Label() {
}
public Label(String text) {
super(text);
}
public Label secondary() {
getStyle().set("color", "var(--lumo-secondary-text-color)");
return this;
}
public Label honorWhitespaces() {
getStyle().set("white-space","pre");
return this;
}
}

View file

@ -0,0 +1,24 @@
package com.faendir.acra.ui.component;
import com.vaadin.flow.i18n.LocaleChangeEvent;
import com.vaadin.flow.i18n.LocaleChangeObserver;
import org.springframework.lang.NonNull;
/**
* @author lukas
* @since 15.11.18
*/
public class Tab extends com.vaadin.flow.component.tabs.Tab implements LocaleChangeObserver {
private final String captionId;
private final Object[] params;
public Tab(@NonNull String captionId, @NonNull Object... params) {
this.captionId = captionId;
this.params = params;
}
@Override
public void localeChange(LocaleChangeEvent event) {
setLabel(getTranslation(captionId, params));
}
}

View file

@ -0,0 +1,111 @@
package com.faendir.acra.ui.component;
import com.vaadin.flow.component.ClickEvent;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.Composite;
import com.vaadin.flow.component.HasText;
import com.vaadin.flow.component.Text;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.checkbox.Checkbox;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.html.Image;
import com.vaadin.flow.component.textfield.PasswordField;
import com.vaadin.flow.component.textfield.TextArea;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.i18n.LocaleChangeEvent;
import com.vaadin.flow.i18n.LocaleChangeObserver;
import com.vaadin.flow.router.HasUrlParameter;
import com.vaadin.flow.router.RouterLink;
import org.springframework.lang.NonNull;
import java.util.Collection;
import java.util.function.Consumer;
/**
* @author lukas
* @since 14.11.18
*/
public class Translatable<T extends Component> extends Composite<T> implements LocaleChangeObserver, HasSize, HasStyle {
private final T t;
private final Consumer<T> setter;
protected Translatable(@NonNull T t, @NonNull Consumer<T> setter) {
this.t = t;
this.setter = setter;
}
@Override
protected T initContent() {
return t;
}
@Override
public void localeChange(LocaleChangeEvent event) {
setter.accept(getContent());
}
public Translatable<T> with(Consumer<T> consumer) {
consumer.accept(t);
return this;
}
@NonNull
public static Translatable<Text> createText(@NonNull String captionId, @NonNull Object... params) {
return create(new Text(""), captionId, params);
}
private static <T extends Component & HasText> Translatable<T> create(T component, @NonNull String captionId, @NonNull Object... params) {
return new Translatable<>(component, text -> text.setText(text.getTranslation(captionId, params)));
}
@NonNull
public static Translatable<Button> createButton(@NonNull String captionId, @NonNull Object... params) {
return create(new Button(), captionId, params);
}
@NonNull
public static Translatable<Button> createButton(@NonNull ComponentEventListener<ClickEvent<Button>> clickListener, @NonNull String captionId, @NonNull Object... params) {
return create(new Button("", clickListener), captionId, params);
}
@NonNull
public static Translatable<TextField> createTextField(@NonNull String initialValue, @NonNull String captionId, @NonNull Object... params) {
return new Translatable<>(new TextField("", initialValue, ""), textField -> textField.setLabel(textField.getTranslation(captionId, params)));
}
@NonNull
public static Translatable<PasswordField> createPasswordField(@NonNull String captionId, @NonNull Object... params) {
return new Translatable<>(new PasswordField(), textField -> textField.setLabel(textField.getTranslation(captionId, params)));
}
@NonNull
public static Translatable<TextArea> createTextArea(@NonNull String initialValue, @NonNull String captionId, @NonNull Object... params) {
return new Translatable<>(new TextArea("", initialValue, ""), textField -> textField.setLabel(textField.getTranslation(captionId, params)));
}
public static <T> Translatable<ComboBox<T>> createComboBox(@NonNull Collection<T> items, @NonNull String captionId, @NonNull Object... params) {
return new Translatable<>(new ComboBox<>("", items), tComboBox -> tComboBox.setLabel(tComboBox.getTranslation(captionId, params)));
}
@NonNull
public static Translatable<Checkbox> createCheckbox(boolean initialValue, @NonNull String captionId, @NonNull Object... params) {
return new Translatable<>(new Checkbox(initialValue), checkbox -> checkbox.setLabel(checkbox.getTranslation(captionId, params)));
}
public static Translatable<RouterLink> createRouterLink(@NonNull Class<? extends Component> target, @NonNull String captionId, @NonNull Object... params) {
return create(new RouterLink("", target), captionId, params);
}
public static <T extends Component & HasUrlParameter<P>, P> Translatable<RouterLink> createRouterLink(@NonNull Class<T> target, @NonNull P parameter, @NonNull String captionId, @NonNull Object... params) {
return create(new RouterLink("", target, parameter), captionId, params);
}
public static Translatable<Label> createLabel(@NonNull String captionId, @NonNull Object... params) {
return create(new Label(), captionId, params);
}
public static Translatable<Image> createImage(@NonNull String src, @NonNull String captionId, @NonNull Object... params) {
return new Translatable<>(new Image(src, ""), image -> image.setAlt(image.getTranslation(captionId, params)));
}
}

View file

@ -1,189 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.ui.navigation;
import com.vaadin.navigator.View;
import com.vaadin.navigator.ViewChangeListener;
import com.vaadin.navigator.ViewProvider;
import com.vaadin.shared.util.SharedUtil;
import com.vaadin.spring.annotation.UIScope;
import com.vaadin.spring.navigator.SpringNavigator;
import com.vaadin.spring.navigator.ViewActivationListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.List;
import java.util.Optional;
/**
* @author Lukas
* @since 21.06.2017
*/
@Component
@UIScope
public class MyNavigator extends SpringNavigator {
public static final char SEPARATOR_CHAR = '/';
public static final String SEPARATOR = "" + SEPARATOR_CHAR;
@NonNull private final List<SingleViewProvider<?>> providers;
@NonNull private final List<ActivationListenerWrapper> activationListenerWrappers;
@NonNull private String navState;
@Nullable private ViewProvider errorProvider;
@Autowired
public MyNavigator(@NonNull List<SingleViewProvider<?>> providers) {
this.providers = providers;
this.activationListenerWrappers = new ArrayList<>();
navState = "";
providers.forEach(this::addProvider);
}
public Optional<SingleViewProvider<?>> getViewProvider(@NonNull Class<? extends View> clazz) {
return providers.stream().filter(p -> p.getClazz().equals(clazz)).findAny();
}
@Override
public void navigateTo(@Nullable String navigationState) {
if (navigationState == null) {
return;
}
String oldNavState = navState;
navState = navigationState;
View view = null;
String viewName = "";
HierarchyElement findResult = findViewProvider(navigationState);
if (findResult != null) {
viewName = findResult.getProvider().getViewName(findResult.getNavState());
view = findResult.getProvider().getView(viewName);
}
if (view == null) {
if (errorProvider != null) {
view = errorProvider.getView(navigationState);
}
if (view == null) {
throw new IllegalArgumentException("Trying to navigate to an unknown state '" + navigationState + "' and an error view provider not present");
}
}
String parameters = "";
if (navState.length() > navState.indexOf(viewName) + viewName.length() + 1) {
parameters = navState.substring(navState.indexOf(viewName) + viewName.length() + 1);
} else if (navState.endsWith("/")) {
navState = navState.substring(0, navState.length() - 1);
}
if (getCurrentView() == null || !SharedUtil.equals(getCurrentView(), view) || !SharedUtil.equals(oldNavState, navState)) {
navigateTo(view, navState, parameters);
} else {
updateNavigationState(new ViewChangeListener.ViewChangeEvent(this, getCurrentView(), view, navState, parameters));
}
}
@Nullable
private HierarchyElement findViewProvider(String navigationState) {
List<String> fragments = Arrays.asList(navigationState.split(SEPARATOR));
if(fragments.isEmpty()) return null;
int size = fragments.size();
int fragmentIndex;
String newNavState;
ViewProvider viewProvider;
fragmentIndex = size;
do {
fragmentIndex--;
newNavState = String.join(SEPARATOR, fragments.subList(fragmentIndex, size));
viewProvider = getViewProvider(newNavState);
} while (fragmentIndex > 0 && viewProvider == null);
if (viewProvider != null) return new HierarchyElement(newNavState, viewProvider);
return null;
}
@Override
public void setErrorProvider(ViewProvider provider) {
super.setErrorProvider(provider);
errorProvider = provider;
}
@Override
public void addViewActivationListener(ViewActivationListener listener) {
ActivationListenerWrapper wrapper = new ActivationListenerWrapper(listener);
activationListenerWrappers.add(wrapper);
super.addViewActivationListener(wrapper);
}
@Override
public void removeViewActivationListener(ViewActivationListener listener) {
activationListenerWrappers.stream().filter(w -> w.getListener().equals(listener)).findAny().ifPresent(super::removeViewActivationListener);
}
public Deque<HierarchyElement> getHierarchy() {
Deque<HierarchyElement> hierarchy = new ArrayDeque<>();
String navPart = navState;
while (!navPart.isEmpty()) {
HierarchyElement element = findViewProvider(navPart);
if (element == null) break;
hierarchy.addFirst(element);
if (navPart.length() == element.getNavState().length()) break;
navPart = navPart.substring(0, navPart.length() - element.getNavState().length() - 1);
}
HierarchyElement top = findViewProvider("");
if (top != null) hierarchy.addFirst(top);
return hierarchy;
}
public static class HierarchyElement {
@NonNull private final String navState;
@NonNull private final ViewProvider provider;
private HierarchyElement(@NonNull String navState, @NonNull ViewProvider provider) {
this.navState = navState;
this.provider = provider;
}
@NonNull
public String getNavState() {
return navState;
}
@NonNull
public ViewProvider getProvider() {
return provider;
}
}
private class ActivationListenerWrapper implements ViewActivationListener {
private final ViewActivationListener listener;
private ActivationListenerWrapper(ViewActivationListener listener) {
this.listener = listener;
}
@Override
public void viewActivated(ViewActivationEvent event) {
HierarchyElement hierarchyElement = findViewProvider(event.getViewName());
if (hierarchyElement != null) {
listener.viewActivated(new ViewActivationEvent(MyNavigator.this, event.isActivated(), hierarchyElement.getNavState()));
}
}
public ViewActivationListener getListener() {
return listener;
}
}
}

View file

@ -1,128 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.ui.navigation;
import com.faendir.acra.i18n.Messages;
import com.faendir.acra.ui.view.ErrorView;
import com.faendir.acra.ui.view.base.navigation.BaseView;
import com.faendir.acra.ui.view.base.navigation.Path;
import com.faendir.acra.util.Utils;
import com.vaadin.spring.annotation.UIScope;
import com.vaadin.ui.Panel;
import com.vaadin.ui.UI;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.vaadin.spring.i18n.I18N;
import java.io.Serializable;
import java.util.Deque;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* @author Lukas
* @since 14.05.2017
*/
@UIScope
@Component
@Configurable
public class NavigationManager implements Serializable {
@NonNull private final Path path;
@NonNull private final MyNavigator navigator;
@Autowired
public NavigationManager(@NonNull UI ui, @NonNull Panel mainView, @NonNull Path mainPath, @NonNull MyNavigator navigator, @NonNull I18N i18n) {
this.path = mainPath;
this.navigator = navigator;
this.navigator.init(ui, mainView);
this.navigator.addViewActivationListener(e -> {
Deque<MyNavigator.HierarchyElement> hierarchy = navigator.getHierarchy();
path.set(hierarchy);
SingleViewProvider provider = (SingleViewProvider) hierarchy.getLast().getProvider();
String title = provider.getTitle(provider.getParameters(hierarchy.getLast().getNavState()));
if (hierarchy.size() > 1) {
title += " - " + i18n.get(Messages.ACRARIUM);
}
ui.getPage().setTitle(title);
});
navigator.setErrorView(ErrorView.class);
String target = Optional.ofNullable(ui.getPage().getLocation().getFragment()).orElse("").replace("!", "");
ui.access(() -> navigateTo(target));
}
public void navigateTo(@NonNull Class<? extends BaseView> namedView, @Nullable String parameters, boolean newTab) {
String target = Stream.of(path.asUrlFragment(), navigator.getViewProvider(namedView).map(SingleViewProvider::getId).orElse(""), parameters)
.filter(s -> s != null && !s.isEmpty())
.collect(Collectors.joining(MyNavigator.SEPARATOR));
if (newTab) {
navigator.getUI().getPage().open(Utils.getUrlWithFragment(target), "_blank", false);
} else if (path.isEmpty() || !path.getLast().getId().equals(target)) {
navigateTo(target);
}
}
public void cleanNavigateTo(@NonNull Class<? extends BaseView> namedView) {
cleanHistory();
navigateTo(namedView, "", false);
}
private void navigateTo(@NonNull String fragment) {
navigator.navigateTo(fragment);
}
private void cleanHistory() {
path.clear();
}
public void navigateBack() {
if (path.getSize() < 2) {
if (path.isEmpty() || !"".equals(path.getLast().getId())) {
path.goUp();
navigateTo("");
}
} else {
path.goUp();
navigateTo(path.asUrlFragment());
}
}
public void updatePageParameters(@Nullable String parameters) {
String currentView = navigator.getViewProvider(navigator.getCurrentView().getClass()).map(SingleViewProvider::getId).orElse("");
Path.Element element = path.goUp();
String hierarchy = path.asUrlFragment();
String target = "!" + (hierarchy.isEmpty() ? "" : hierarchy + MyNavigator.SEPARATOR_CHAR) + currentView + (parameters == null ?
"" :
MyNavigator.SEPARATOR_CHAR + parameters);
path.goTo(element.getLabel(), currentView + (parameters == null ? "" : MyNavigator.SEPARATOR_CHAR + parameters));
navigator.getUI().getPage().setUriFragment(target, false);
}
public void openInNewTab(@Nullable String parameters) {
String currentView = navigator.getViewProvider(navigator.getCurrentView().getClass()).map(SingleViewProvider::getId).orElse("");
Path.Element element = path.goUp();
String hierarchy = path.asUrlFragment();
path.goTo(element);
String target = "!" + (hierarchy.isEmpty() ? "" : hierarchy + MyNavigator.SEPARATOR_CHAR) + currentView + (parameters == null ?
"" :
MyNavigator.SEPARATOR_CHAR + parameters);
navigator.getUI().getPage().open(Utils.getUrlWithFragment(target), "_blank", false);
}
}

View file

@ -1,83 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.ui.navigation;
import com.faendir.acra.model.App;
import com.faendir.acra.security.SecurityUtils;
import com.faendir.acra.ui.annotation.RequiresAppPermission;
import com.faendir.acra.ui.view.base.navigation.ParametrizedBaseView;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author Lukas
* @since 24.03.2018
*/
public abstract class SingleParametrizedViewProvider<T, V extends ParametrizedBaseView<T>> extends SingleViewProvider<V> {
private String lastParameter;
private T lastT;
public SingleParametrizedViewProvider(Class<V> clazz) {
super(clazz);
}
@Override
protected boolean isValidParameter(String parameter) {
T t = parseParameterInternal(parameter);
RequiresAppPermission annotation;
return isValidParameter(t) && ((annotation = getClazz().getAnnotation(RequiresAppPermission.class)) == null || SecurityUtils.hasPermission(toApp(t), annotation.value()));
}
@Override
public V getView(String viewName) {
V view = super.getView(viewName);
if (view != null) {
view.setParameterParser(e -> {
String parameters = null;
Matcher matcher = Pattern.compile("(^|/)(" + getId() + ")($|/)").matcher(e.getViewName());
if (matcher.find()) {
parameters = getParameters(e.getViewName().substring(matcher.start(2)) + (e.getParameters().isEmpty() ? "" : MyNavigator.SEPARATOR_CHAR + e.getParameters()));
}
return parameters != null ? parseParameterInternal(parameters) : null;
});
}
return view;
}
@Override
public final String getTitle(String parameter) {
return getTitle(parseParameterInternal(parameter));
}
protected abstract String getTitle(T parameter);
protected abstract boolean isValidParameter(T parameter);
protected abstract T parseParameter(String parameter);
protected abstract App toApp(T parameter);
private T parseParameterInternal(String parameter) {
if (parameter.equals(lastParameter)) {
return lastT;
}
T t = parseParameter(parameter);
lastParameter = parameter;
lastT = t;
return t;
}
}

View file

@ -1,107 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.ui.navigation;
import com.faendir.acra.security.SecurityUtils;
import com.faendir.acra.ui.annotation.RequiresRole;
import com.faendir.acra.ui.view.base.navigation.BaseView;
import com.vaadin.navigator.ViewProvider;
import com.vaadin.spring.internal.ViewCache;
import com.vaadin.spring.internal.ViewScopeImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.lang.NonNull;
/**
* @author Lukas
* @since 24.03.2018
*/
public abstract class SingleViewProvider<V extends BaseView> implements ViewProvider {
private final Class<V> clazz;
private ApplicationContext applicationContext;
protected SingleViewProvider(Class<V> clazz) {
this.clazz = clazz;
}
@Autowired
public void setApplicationContext(@NonNull ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Override
public String getViewName(String viewAndParameters) {
return getParameters(viewAndParameters) != null ? viewAndParameters : null;
}
public String getParameters(String viewAndParameters) {
String result = null;
String id = getId();
if (viewAndParameters.startsWith(id)) {
String params = viewAndParameters.substring(id.length());
RequiresRole annotation = getClazz().getAnnotation(RequiresRole.class);
if (annotation == null || SecurityUtils.hasRole(annotation.value())) {
if (params.isEmpty()) {
result = "";
} else if (params.charAt(0) == MyNavigator.SEPARATOR_CHAR && isValidParameter(params.substring(1))) {
result = params.substring(1);
}
}
}
return result;
}
public Class<V> getClazz() {
return clazz;
}
protected boolean isValidParameter(String parameter) {
return false;
}
@Override
public V getView(String viewName) {
V view = null;
String id = getId();
if (viewName.startsWith(id)) {
String params = viewName.substring(id.length());
if (params.isEmpty() || params.charAt(0) == MyNavigator.SEPARATOR_CHAR && isValidParameter(params.substring(1))) {
RequiresRole annotation = getClazz().getAnnotation(RequiresRole.class);
if (annotation == null || SecurityUtils.hasRole(annotation.value())) {
if (ViewScopeImpl.VAADIN_VIEW_SCOPE_NAME.equals(((BeanDefinitionRegistry) applicationContext.getAutowireCapableBeanFactory()).getBeanDefinition(
applicationContext.getBeanNamesForType(getClazz())[0]).getScope())) {
final ViewCache viewCache = ViewScopeImpl.getViewCacheRetrievalStrategy().getViewCache(applicationContext);
viewCache.creatingView(viewName);
try {
view = applicationContext.getBean(getClazz());
} finally {
viewCache.viewCreated(viewName, view);
}
} else {
view = applicationContext.getBean(getClazz());
}
}
}
}
return view;
}
public abstract String getTitle(String parameter);
public abstract String getId();
}

View file

@ -1,52 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.ui.view;
import com.faendir.acra.i18n.I18nLabel;
import com.faendir.acra.i18n.Messages;
import com.vaadin.navigator.View;
import com.vaadin.navigator.ViewChangeListener;
import com.vaadin.spring.annotation.SpringComponent;
import com.vaadin.spring.annotation.UIScope;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Composite;
import com.vaadin.ui.Label;
import com.vaadin.ui.VerticalLayout;
import org.springframework.beans.factory.annotation.Autowired;
import org.vaadin.spring.i18n.I18N;
/**
* @author Lukas
* @since 21.05.2017
*/
@SpringComponent
@UIScope
public class ErrorView extends Composite implements View {
@Autowired
public ErrorView(I18N i18n) {
VerticalLayout layout = new VerticalLayout();
Label label = new I18nLabel(i18n, Messages.ERROR4XX);
layout.addComponent(label);
layout.setComponentAlignment(label, Alignment.MIDDLE_CENTER);
layout.setSizeFull();
setCompositionRoot(layout);
}
@Override
public void enter(ViewChangeListener.ViewChangeEvent event) {
}
}

View file

@ -0,0 +1,140 @@
package com.faendir.acra.ui.view;
import com.faendir.acra.i18n.Messages;
import com.faendir.acra.model.User;
import com.faendir.acra.security.SecurityUtils;
import com.faendir.acra.ui.base.ParentLayout;
import com.faendir.acra.ui.base.Path;
import com.faendir.acra.ui.base.popup.Popup;
import com.faendir.acra.ui.component.DropdownMenu;
import com.faendir.acra.ui.component.FlexLayout;
import com.faendir.acra.ui.component.Translatable;
import com.faendir.acra.ui.view.user.ChangePasswordView;
import com.faendir.acra.ui.view.user.UserManager;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.checkbox.Checkbox;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.Image;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.textfield.PasswordField;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.router.RouterLink;
import com.vaadin.flow.server.VaadinService;
import com.vaadin.flow.spring.annotation.SpringComponent;
import com.vaadin.flow.spring.annotation.UIScope;
import com.vaadin.flow.theme.lumo.Lumo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.lang.NonNull;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
/**
* @author lukas
* @since 13.07.18
*/
@UIScope
@SpringComponent
public class MainView extends ParentLayout {
private final AuthenticationManager authenticationManager;
private final ApplicationContext applicationContext;
private ParentLayout layout;
@Autowired
public MainView(AuthenticationManager authenticationManager, ApplicationContext applicationContext) {
this.authenticationManager = authenticationManager;
this.applicationContext = applicationContext;
setAlignItems(Alignment.CENTER);
setJustifyContentMode(JustifyContentMode.CENTER);
setSizeFull();
getStyle().set("flex-direction", "column");
layout = new ParentLayout();
layout.setWidth("100%");
expand(layout);
layout.getStyle().set("overflow", "hidden");
setRouterRoot(layout);
if (SecurityUtils.isLoggedIn()) {
showMain();
} else {
showLogin();
}
}
private void showMain() {
Translatable<Checkbox> darkTheme = Translatable.createCheckbox(false, Messages.DARK_THEME);
darkTheme.getContent().addValueChangeListener(e -> {
UI.getCurrent().getElement().setAttribute("theme", e.getValue() ? Lumo.DARK : Lumo.LIGHT);
});
Translatable<RouterLink> userManager = Translatable.createRouterLink(UserManager.class, Messages.USER_MANAGER);
userManager.setDefaultTextStyle();
Translatable<RouterLink> changePassword = Translatable.createRouterLink(ChangePasswordView.class, Messages.CHANGE_PASSWORD);
changePassword.setDefaultTextStyle();
Translatable<Button> logout = Translatable.createButton(e -> logout(), Messages.LOGOUT);
logout.getElement().setAttribute("theme", "tertiary");
logout.setDefaultTextStyle();
Translatable<Button> about = Translatable.createButton(e -> {
Div content = new Div();
content.getElement().setProperty("innerHTML", getTranslation(Messages.FOOTER));
new Popup().addComponent(content).addCloseButton().show();
}, Messages.ABOUT);
about.getElement().setAttribute("theme", "tertiary");
about.setDefaultTextStyle();
DropdownMenu menu = new DropdownMenu(new FormLayout(darkTheme, userManager, changePassword, logout, about));
menu.getStyle().set("margin", "1rem");
menu.setLabel(SecurityUtils.getUsername());
menu.setMinWidth(130, Unit.PIXEL);
menu.setOrigin(DropdownMenu.Origin.RIGHT);
Path path = new Path(applicationContext);
FlexLayout header = new FlexLayout(path, menu);
header.expand(path);
header.setWidthFull();
header.getStyle().set("box-shadow", "0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23)");
header.getStyle().set("border-radius", "2px");
removeAll();
add(header, layout);
}
private void showLogin() {
Translatable<Image> logo = Translatable.createImage("frontend/logo.png", Messages.ACRARIUM);
logo.setWidth(0, Unit.PIXEL);
logo.setPadding(1, Unit.REM);
FlexLayout logoWrapper = new FlexLayout(logo);
logoWrapper.expand(logo);
TextField username = new TextField();
PasswordField password = new PasswordField();
Translatable<Button> login = Translatable.createButton(event -> login(username.getValue(), password.getValue()), Messages.LOGIN);
login.setWidthFull();
FlexLayout loginForm = new FlexLayout(logoWrapper, username, password, login);
loginForm.setFlexDirection(FlexDirection.COLUMN);
loginForm.setSizeUndefined();
setContent(loginForm);
}
private void login(@NonNull String username, @NonNull String password) {
try {
Authentication token = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username.toLowerCase(), password));
if (!token.getAuthorities().contains(User.Role.USER)) {
throw new InsufficientAuthenticationException("Missing required role");
}
VaadinService.reinitializeSession(VaadinService.getCurrentRequest());
SecurityContextHolder.getContext().setAuthentication(token);
UI.getCurrent().getPage().reload();
} catch (AuthenticationException ex) {
Notification.show(getTranslation(Messages.LOGIN_FAILED), 5000, Notification.Position.MIDDLE);
}
}
public void logout() {
SecurityContextHolder.clearContext();
getUI().ifPresent(ui -> {
ui.getPage().reload();
ui.getSession().close();
});
}
}

View file

@ -1,146 +1,57 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.ui.view;
import com.faendir.acra.i18n.I18nButton;
import com.faendir.acra.i18n.I18nCheckBox;
import com.faendir.acra.i18n.I18nIntStepper;
import com.faendir.acra.i18n.I18nLabel;
import com.faendir.acra.i18n.I18nTextField;
import com.faendir.acra.i18n.Messages;
import com.faendir.acra.model.QApp;
import com.faendir.acra.model.QBug;
import com.faendir.acra.model.QReport;
import com.faendir.acra.model.User;
import com.faendir.acra.model.view.VApp;
import com.faendir.acra.security.SecurityUtils;
import com.faendir.acra.service.DataService;
import com.faendir.acra.ui.navigation.SingleViewProvider;
import com.faendir.acra.ui.view.app.AppView;
import com.faendir.acra.ui.view.base.ConfigurationLabel;
import com.faendir.acra.ui.view.base.layout.MyGrid;
import com.faendir.acra.ui.view.base.navigation.BaseView;
import com.faendir.acra.ui.view.base.popup.Popup;
import com.faendir.acra.ui.view.base.popup.ValidatedField;
import com.faendir.acra.util.ImportResult;
import com.vaadin.navigator.ViewChangeListener;
import com.vaadin.spring.annotation.SpringComponent;
import com.vaadin.spring.annotation.UIScope;
import com.vaadin.spring.annotation.ViewScope;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.Grid;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.TextField;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.themes.AcraTheme;
import com.faendir.acra.ui.base.HasRoute;
import com.faendir.acra.ui.base.MyGrid;
import com.faendir.acra.ui.base.Path;
import com.faendir.acra.ui.view.app.tabs.BugTab;
import com.vaadin.flow.component.AttachEvent;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import com.vaadin.flow.spring.annotation.UIScope;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.vaadin.spring.i18n.I18N;
/**
* @author Lukas
* @since 23.03.2017
* @author lukas
* @since 13.07.18
*/
@UIScope
@SpringComponent
@ViewScope
public class Overview extends BaseView {
@NonNull private final DataService dataService;
@NonNull private final I18N i18n;
private MyGrid<VApp> grid;
@Route(value = "", layout = MainView.class)
public class Overview extends VerticalLayout implements ComponentEventListener<AttachEvent>, HasRoute {
private final DataService dataService;
@Autowired
public Overview(@NonNull DataService dataService, @NonNull I18N i18n) {
public Overview(DataService dataService) {
this.dataService = dataService;
this.i18n = i18n;
addAttachListener(this);
}
@Override
public void enter(ViewChangeListener.ViewChangeEvent event) {
grid = new MyGrid<>(dataService.getAppProvider(), i18n, Messages.APPS);
grid.setResponsive(true);
grid.setSizeToRows();
public void onComponentEvent(AttachEvent event) {
removeAll();
MyGrid<VApp> grid = new MyGrid<>(dataService.getAppProvider());
grid.setSelectionMode(Grid.SelectionMode.NONE);
grid.addColumn(VApp::getName, QApp.app.name, Messages.NAME);
grid.addColumn(VApp::getBugCount, QBug.bug.countDistinct(), Messages.BUGS);
grid.addColumn(VApp::getReportCount, QReport.report.count(), Messages.REPORTS);
grid.addOnClickNavigation(getNavigationManager(), AppView.class, e -> String.valueOf(e.getItem().getId()));
VerticalLayout layout = new VerticalLayout();
if (SecurityUtils.hasRole(User.Role.ADMIN)) {
Button add = new I18nButton(i18n, Messages.NEW_APP);
add.addClickListener(e -> {
TextField name = new I18nTextField(i18n, Messages.NAME);
new Popup(i18n, Messages.NEW_APP).addComponent(name).addCreateButton(popup -> {
popup.clear().addComponent(new ConfigurationLabel(dataService.createNewApp(name.getValue()), i18n)).addCloseButton().show();
grid.getDataProvider().refreshAll();
}).show();
});
Button importButton = new I18nButton(i18n, Messages.IMPORT_ACRALYZER);
importButton.addClickListener(e -> {
I18nTextField host = new I18nTextField("localhost", i18n, Messages.HOST);
I18nIntStepper port = new I18nIntStepper(5984, i18n, Messages.PORT);
port.setMinValue(0);
port.setMaxValue(65535);
I18nCheckBox ssl = new I18nCheckBox(i18n, Messages.SSL);
I18nTextField databaseName = new I18nTextField("acra-myapp", i18n, Messages.DATABASE_NAME);
new Popup(i18n, Messages.IMPORT_ACRALYZER).addValidatedField(ValidatedField.of(host), true)
.addValidatedField(ValidatedField.of(port), true)
.addValidatedField(ValidatedField.of(ssl), true)
.addValidatedField(ValidatedField.of(databaseName), true)
.addCreateButton(popup -> {
ImportResult importResult = dataService.importFromAcraStorage(host.getValue(), port.getValue(), ssl.getValue(), databaseName.getValue());
popup.clear()
.addComponent(new I18nLabel(i18n, Messages.IMPORT_SUCCESS, importResult.getSuccessCount(), importResult.getTotalCount()))
.addComponent(new ConfigurationLabel(importResult.getUser(), i18n))
.addCloseButton()
.show();
grid.getDataProvider().refreshAll();
})
.show();
});
layout.addComponent(new HorizontalLayout(add, importButton));
}
layout.addComponent(grid);
layout.addStyleNames(AcraTheme.NO_PADDING, AcraTheme.PADDING_LEFT, AcraTheme.PADDING_RIGHT, AcraTheme.PADDING_BOTTOM, AcraTheme.MAX_WIDTH_728);
VerticalLayout root = new VerticalLayout(layout);
root.setSpacing(false);
root.setSizeFull();
root.setComponentAlignment(layout, Alignment.TOP_CENTER);
root.addStyleName(AcraTheme.NO_PADDING);
setCompositionRoot(root);
grid.addOnClickNavigation(BugTab.class, VApp::getId);
setSizeFull();
add(grid);
}
@SpringComponent
@UIScope
public static class Provider extends SingleViewProvider<Overview> {
@NonNull private final I18N i18n;
protected Provider(@NonNull I18N i18n) {
super(Overview.class);
this.i18n = i18n;
}
@Override
public String getTitle(String parameter) {
return i18n.get(Messages.ACRARIUM);
}
@Override
public String getId() {
return "";
}
@Override
@NonNull
public Path.Element<?> getPathElement() {
return new Path.ImageElement<>(getClass(), "frontend/logo.png", Messages.ACRARIUM);
}
}

View file

@ -1,111 +1,68 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.ui.view.app;
import com.faendir.acra.i18n.Messages;
import com.faendir.acra.model.App;
import com.faendir.acra.model.Permission;
import com.faendir.acra.service.DataService;
import com.faendir.acra.ui.annotation.RequiresAppPermission;
import com.faendir.acra.ui.navigation.MyNavigator;
import com.faendir.acra.ui.navigation.SingleParametrizedViewProvider;
import com.faendir.acra.ui.base.ActiveChildAware;
import com.faendir.acra.ui.base.ParentLayout;
import com.faendir.acra.ui.component.Tab;
import com.faendir.acra.ui.view.MainView;
import com.faendir.acra.ui.view.app.tabs.AdminTab;
import com.faendir.acra.ui.view.app.tabs.AppTab;
import com.faendir.acra.ui.view.base.layout.MyTabSheet;
import com.faendir.acra.ui.view.base.navigation.ParametrizedBaseView;
import com.vaadin.spring.annotation.SpringComponent;
import com.vaadin.spring.annotation.UIScope;
import com.vaadin.spring.annotation.ViewScope;
import com.vaadin.ui.Panel;
import com.vaadin.ui.themes.AcraTheme;
import com.faendir.acra.ui.view.app.tabs.BugTab;
import com.faendir.acra.ui.view.app.tabs.ReportTab;
import com.faendir.acra.ui.view.app.tabs.StatisticsTab;
import com.vaadin.flow.component.tabs.Tabs;
import com.vaadin.flow.router.RoutePrefix;
import com.vaadin.flow.spring.annotation.SpringComponent;
import com.vaadin.flow.spring.annotation.UIScope;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.util.Pair;
import org.springframework.lang.NonNull;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
/**
* @author Lukas
* @since 13.05.2017
* @author lukas
* @since 13.07.18
*/
@UIScope
@SpringComponent
@ViewScope
@RequiresAppPermission(Permission.Level.VIEW)
public class AppView extends ParametrizedBaseView<Pair<App, String>> {
private final List<AppTab> tabs;
@RoutePrefix("app")
@com.vaadin.flow.router.ParentLayout(MainView.class)
public class AppView extends ParentLayout implements ActiveChildAware<AppTab<?>, App> {
private App app;
private final Tabs tabs;
@Autowired
public AppView(@Lazy List<AppTab> tabs) {
this.tabs = tabs;
public AppView() {
tabs = new Tabs(Stream.of(TabDef.values()).map(def -> new Tab(def.labelId)).toArray(Tab[]::new));
tabs.addSelectedChangeListener(e -> getUI().ifPresent(ui -> ui.navigate(TabDef.values()[e.getSource().getSelectedIndex()].tabClass, app.getId())));
setSizeFull();
ParentLayout content = new ParentLayout();
content.setWidthFull();
expand(content);
content.getStyle().set("overflow","auto");
setRouterRoot(content);
getStyle().set("flex-direction","column");
removeAll();
add(tabs, content);
}
@Override
protected void enter(@NonNull Pair<App, String> parameter) {
MyTabSheet<App> tabSheet = new MyTabSheet<>(parameter.getFirst(), getNavigationManager(), tabs);
tabSheet.setSizeFull();
tabSheet.addSelectedTabChangeListener(e -> getNavigationManager().updatePageParameters(parameter.getFirst().getId() + "/" + e.getTabSheet().getSelectedTab().getId()));
tabSheet.addMiddleClickListener(e -> getNavigationManager().openInNewTab(parameter.getFirst().getId() + "/" + e.getTab().getId()));
tabSheet.guessInitialTab(parameter.getSecond());
Panel panel = new Panel(tabSheet);
panel.setSizeFull();
panel.addStyleNames(AcraTheme.NO_BORDER, AcraTheme.NO_BACKGROUND, AcraTheme.NO_PADDING, AcraTheme.PADDING_LEFT, AcraTheme.PADDING_RIGHT);
setCompositionRoot(panel);
public void setActiveChild(AppTab<?> child, App parameter) {
app = parameter;
Stream.of(TabDef.values()).filter(def -> def.tabClass.equals(child.getClass())).findAny().ifPresent(def -> tabs.setSelectedIndex(def.ordinal()));
}
@SpringComponent
@UIScope
public static class Provider extends SingleParametrizedViewProvider<Pair<App, String>, AppView> {
@NonNull private final DataService dataService;
private enum TabDef {
BUG(Messages.BUGS, BugTab.class),
REPORT(Messages.REPORTS, ReportTab.class),
STATISTICS(Messages.STATISTICS, StatisticsTab.class),
ADMIN(Messages.ADMIN, AdminTab.class);
private String labelId;
private Class<? extends AppTab<?>> tabClass;
@Autowired
public Provider(@NonNull DataService dataService) {
super(AppView.class);
this.dataService = dataService;
}
@Override
protected boolean isValidParameter(Pair<App, String> parameter) {
return parameter != null;
}
@Override
protected Pair<App, String> parseParameter(String parameter) {
String[] parameters = parameter.split(MyNavigator.SEPARATOR);
if (parameters.length > 0) {
Optional<App> app = dataService.findApp(parameters[0]);
if (app.isPresent()) {
return Pair.of(app.get(), parameters.length == 1 ? "" : parameters[1]);
}
}
return null;
}
@Override
protected App toApp(Pair<App, String> parameter) {
return parameter.getFirst();
}
@Override
public String getTitle(Pair<App, String> parameter) {
return parameter.getFirst().getName();
}
@Override
public String getId() {
return "app";
TabDef(String labelId, Class<? extends AppTab<?>> tabClass) {
this.labelId = labelId;
this.tabClass = tabClass;
}
}
}

View file

@ -1,62 +1,179 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.ui.view.app.tabs;
import com.faendir.acra.i18n.Messages;
import com.faendir.acra.model.App;
import com.faendir.acra.model.Permission;
import com.faendir.acra.ui.annotation.RequiresAppPermission;
import com.faendir.acra.ui.view.app.tabs.panels.AdminPanel;
import com.faendir.acra.ui.view.base.layout.PanelFlexTab;
import com.vaadin.spring.annotation.SpringComponent;
import com.vaadin.spring.annotation.ViewScope;
import com.faendir.acra.model.ProguardMapping;
import com.faendir.acra.model.QProguardMapping;
import com.faendir.acra.model.QReport;
import com.faendir.acra.security.SecurityUtils;
import com.faendir.acra.service.DataService;
import com.faendir.acra.ui.component.Card;
import com.faendir.acra.ui.base.ConfigurationLabel;
import com.faendir.acra.ui.base.MyGrid;
import com.faendir.acra.ui.base.popup.Popup;
import com.faendir.acra.ui.component.DownloadButton;
import com.faendir.acra.ui.component.FlexLayout;
import com.faendir.acra.ui.component.HasSize;
import com.faendir.acra.ui.component.Translatable;
import com.faendir.acra.ui.view.Overview;
import com.faendir.acra.ui.view.app.AppView;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.html.Input;
import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.component.upload.Upload;
import com.vaadin.flow.component.upload.receivers.MemoryBuffer;
import com.vaadin.flow.data.renderer.ComponentRenderer;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.StreamResource;
import com.vaadin.flow.spring.annotation.SpringComponent;
import com.vaadin.flow.spring.annotation.UIScope;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.vaadin.spring.i18n.I18N;
import org.springframework.util.StreamUtils;
import java.io.ByteArrayInputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.stream.Collectors;
import static com.faendir.acra.model.QReport.report;
import java.util.List;
/**
* @author Lukas
* @since 19.05.2017
* @author lukas
* @since 18.10.18
*/
@RequiresAppPermission(Permission.Level.ADMIN)
@UIScope
@SpringComponent
@ViewScope
public class AdminTab extends PanelFlexTab<App> implements AppTab {
@NonNull private final I18N i18n;
@Route(value = "admin", layout = AppView.class)
public class AdminTab extends AppTab<FlexLayout> {
@Autowired
public AdminTab(@NonNull List<AdminPanel> panels, @NonNull I18N i18n) {
super(panels);
this.i18n = i18n;
public AdminTab(DataService dataService) {
super(dataService);
}
@Override
public String getCaption() {
return i18n.get(Messages.ADMIN);
}
void init(App app) {
getContent().removeAll();
getContent().setFlexWrap(FlexLayout.FlexWrap.WRAP);
getContent().setWidthFull();
MyGrid<ProguardMapping> mappingGrid = new MyGrid<>(getDataService().getMappingProvider(app));
mappingGrid.setHeightToRows();
mappingGrid.addColumn(ProguardMapping::getVersionCode, QProguardMapping.proguardMapping.versionCode, Messages.VERSION);
Card mappingCard = new Card(mappingGrid);
mappingCard.setHeader(Translatable.createText(Messages.DE_OBFUSCATION));
mappingCard.setWidth(500, HasSize.Unit.PIXEL);
if (SecurityUtils.hasPermission(app, Permission.Level.EDIT)) {
mappingGrid.addColumn(new ComponentRenderer<>(mapping -> new Button(new Icon(VaadinIcon.TRASH), e -> new Popup().addComponent(Translatable.createText(Messages.DELETE_MAPPING_CONFIRM, mapping.getVersionCode())).addYesNoButtons(p -> {
getDataService().delete(mapping);
mappingGrid.getDataProvider().refreshAll();
}, true).show())));
mappingCard.add(Translatable.createButton(e -> {
Translatable<TextField> version = Translatable.createTextField(String.valueOf(getDataService().getMaximumMappingVersion(app).map(i -> i + 1).orElse(1)), Messages.VERSION_CODE);
MemoryBuffer buffer = new MemoryBuffer();
Upload upload = new Upload(buffer);
new Popup()
.setTitle(Messages.NEW_MAPPING)
.addComponent(version)
.addComponent(upload)
.addCreateButton(popup -> {
try {
getDataService().store(new ProguardMapping(app, Integer.valueOf(version.getContent().getValue()), StreamUtils.copyToString(buffer.getInputStream(), Charset.defaultCharset())));
} catch (Exception ex) {
//TODO
}
mappingGrid.getDataProvider().refreshAll();
}, true)
.show();
}, Messages.NEW_FILE));
}
getContent().add(mappingCard);
getContent().expand(mappingCard);
@Override
public String getId() {
return "admin";
}
Translatable<ComboBox<String>> mailBox = Translatable.createComboBox(getDataService().getFromReports(app, null, QReport.report.userEmail), Messages.BY_MAIL);
mailBox.setWidthFull();
Translatable<ComboBox<String>> idBox = Translatable.createComboBox(getDataService().getFromReports(app, null, QReport.report.installationId), Messages.BY_ID);
idBox.setWidthFull();
DownloadButton download = new DownloadButton(new StreamResource("reports.json", () -> {
BooleanExpression where = null;
String name = "";
String mail = mailBox.getContent().getValue();
String id = idBox.getContent().getValue();
if (mail != null && !mail.isEmpty()) {
where = report.userEmail.eq(mail);
name += "_" + mail;
}
if (id != null && !id.isEmpty()) {
where = report.installationId.eq(id).and(where);
name += "_" + id;
}
if (name.isEmpty()) {
return new ByteArrayInputStream(new byte[0]);
}
return new ByteArrayInputStream(getDataService().getFromReports(app, where, report.content, report.id).stream().collect(Collectors.joining(", ", "[", "]")).getBytes(StandardCharsets.UTF_8));
}), Messages.DOWNLOAD);
download.setSizeFull();
Card exportCard = new Card(mailBox, idBox, download);
exportCard.setHeader(Translatable.createText(Messages.EXPORT));
exportCard.setWidth(500, HasSize.Unit.PIXEL);
getContent().add(exportCard);
getContent().expand(exportCard);
@Override
public int getOrder() {
return 4;
Translatable<Button> configButton = Translatable.createButton(e -> new Popup().setTitle(Messages.NEW_ACRA_CONFIG_CONFIRM)
.addYesNoButtons(popup -> popup.clear().addComponent(new ConfigurationLabel(getDataService().recreateReporterUser(app))).addCloseButton().show())
.show(), Messages.NEW_ACRA_CONFIG);
configButton.setWidthFull();
Translatable<Button> matchingButton = Translatable.createButton(e -> {
App.Configuration configuration = app.getConfiguration();
Input score = new Input();
score.setType("range");
score.getElement().setProperty("min", 0);
score.getElement().setProperty("max", 100);
score.setValue(String.valueOf(configuration.getMinScore()));
new Popup().addComponent(score)
.setTitle(Messages.NEW_BUG_CONFIG_CONFIRM)
.addYesNoButtons(p -> getDataService().changeConfiguration(app, new App.Configuration(Integer.parseInt(score.getValue()))), true)
.show();
}, Messages.NEW_BUG_CONFIG);
matchingButton.setWidthFull();
TextField age = new TextField();
age.setValue("30");
age.setWidth("100%");
FlexLayout purgeAge = new FlexLayout();
purgeAge.setWidthFull();
purgeAge.preventWhiteSpaceBreaking();
purgeAge.setAlignItems(FlexComponent.Alignment.CENTER);
purgeAge.add(Translatable.createButton(e -> getDataService().deleteReportsOlderThanDays(app, Integer.parseInt(age.getValue())), Messages.PURGE),
Translatable.createLabel(Messages.REPORTS_OLDER_THAN1),
age, Translatable.createLabel(Messages.REPORTS_OLDER_THAN2));
purgeAge.expand(age);
ComboBox<Integer> versionBox = new ComboBox<>(null, getDataService().getFromReports(app, null, QReport.report.stacktrace.version.code));
versionBox.setWidth("100%");
FlexLayout purgeVersion = new FlexLayout();
purgeVersion.setWidthFull();
purgeVersion.preventWhiteSpaceBreaking();
purgeVersion.setAlignItems(FlexComponent.Alignment.CENTER);
purgeVersion.add(Translatable.createButton(e -> {
if (versionBox.getValue() != null) {
getDataService().deleteReportsBeforeVersion(app, versionBox.getValue());
}
}, Messages.PURGE), Translatable.createLabel(Messages.REPORTS_BEFORE_VERSION), versionBox);
purgeVersion.expand(versionBox);
Translatable<Button> deleteButton = Translatable.createButton(e -> new Popup().setTitle(Messages.DELETE_APP_CONFIRM).addYesNoButtons(popup -> {
getDataService().delete(app);
UI.getCurrent().navigate(Overview.class);
}, true).show(), Messages.DELETE_APP);
deleteButton.setWidthFull();
Card dangerCard = new Card(configButton, matchingButton, purgeAge, purgeVersion, deleteButton);
dangerCard.setHeader(Translatable.createText(Messages.DANGER_ZONE));
dangerCard.setWidth(500, HasSize.Unit.PIXEL);
getContent().add(dangerCard);
getContent().expand(dangerCard);
}
}

View file

@ -1,29 +1,73 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.ui.view.app.tabs;
import com.faendir.acra.i18n.Messages;
import com.faendir.acra.model.App;
import com.faendir.acra.ui.view.base.layout.ComponentFactory;
import com.faendir.acra.service.DataService;
import com.faendir.acra.ui.base.ActiveChildAware;
import com.faendir.acra.ui.base.HasRoute;
import com.faendir.acra.ui.base.HasSecureIntParameter;
import com.faendir.acra.ui.base.Path;
import com.faendir.acra.ui.view.Overview;
import com.vaadin.flow.component.AttachEvent;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.Composite;
import com.vaadin.flow.router.BeforeEvent;
import org.springframework.lang.NonNull;
import java.io.Serializable;
import java.util.Optional;
/**
* @author Lukas
* @since 27.03.2018
* @author lukas
* @since 14.07.18
*/
public interface AppTab extends ComponentFactory<App>, Serializable {
public abstract class AppTab<T extends Component> extends Composite<T> implements HasSecureIntParameter, HasRoute {
private final DataService dataService;
private App app;
public AppTab(DataService dataService) {
this.dataService = dataService;
}
public DataService getDataService() {
return dataService;
}
@Override
public void setParameterSecure(BeforeEvent event, Integer parameter) {
Optional<App> app = dataService.findApp(parameter);
if (app.isPresent()) {
this.app = app.get();
} else {
event.rerouteToError(IllegalArgumentException.class);
}
}
abstract void init(App app);
@Override
protected void onAttach(AttachEvent attachEvent) {
super.onAttach(attachEvent);
init(this.app);
Optional<Component> parent = getParent();
while (parent.isPresent()) {
if (parent.get() instanceof ActiveChildAware) {
//noinspection unchecked
((ActiveChildAware<AppTab<?>, App>) parent.get()).setActiveChild(this, app);
break;
}
parent = parent.get().getParent();
}
}
@Override
@NonNull
public Path.Element<?> getPathElement() {
//noinspection unchecked
return new Path.ParametrizedTextElement<>((Class<? extends AppTab<?>>) getClass(), app.getId(), Messages.ONE_ARG, app.getName());
}
@Override
public Parent<?> getLogicalParent() {
return new Parent<>(Overview.class);
}
}

View file

@ -1,22 +1,5 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.ui.view.app.tabs;
import com.faendir.acra.i18n.I18nButton;
import com.faendir.acra.i18n.I18nCheckBox;
import com.faendir.acra.i18n.Messages;
import com.faendir.acra.model.App;
import com.faendir.acra.model.Permission;
@ -25,110 +8,89 @@ import com.faendir.acra.model.QReport;
import com.faendir.acra.model.view.VBug;
import com.faendir.acra.security.SecurityUtils;
import com.faendir.acra.service.DataService;
import com.faendir.acra.ui.navigation.NavigationManager;
import com.faendir.acra.ui.view.base.MyCheckBox;
import com.faendir.acra.ui.view.base.layout.MyGrid;
import com.faendir.acra.ui.view.base.popup.Popup;
import com.faendir.acra.ui.base.MyGrid;
import com.faendir.acra.ui.base.popup.Popup;
import com.faendir.acra.ui.component.Translatable;
import com.faendir.acra.ui.view.app.AppView;
import com.faendir.acra.ui.view.bug.tabs.ReportTab;
import com.faendir.acra.util.TimeSpanRenderer;
import com.vaadin.server.Sizeable;
import com.vaadin.shared.data.sort.SortDirection;
import com.vaadin.spring.annotation.SpringComponent;
import com.vaadin.spring.annotation.ViewScope;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.CheckBox;
import com.vaadin.ui.Component;
import com.vaadin.ui.Grid;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Notification;
import com.vaadin.ui.RadioButtonGroup;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.renderers.ComponentRenderer;
import com.vaadin.ui.themes.AcraTheme;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.checkbox.Checkbox;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.radiobutton.RadioButtonGroup;
import com.vaadin.flow.data.renderer.ComponentRenderer;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import com.vaadin.flow.spring.annotation.UIScope;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.vaadin.spring.i18n.I18N;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author Lukas
* @since 17.05.2017
* @author lukas
* @since 14.07.18
*/
@UIScope
@SpringComponent
@ViewScope
public class BugTab implements AppTab {
@NonNull private final DataService dataService;
@NonNull private final I18N i18n;
@Route(value = "bug", layout = AppView.class)
public class BugTab extends AppTab<VerticalLayout> {
@Autowired
public BugTab(@NonNull DataService dataService, @NonNull I18N i18n) {
this.dataService = dataService;
this.i18n = i18n;
public BugTab(DataService dataService) {
super(dataService);
}
@Override
public String getCaption() {
return i18n.get(Messages.BUGS);
}
@Override
public String getId() {
return "bugs";
}
@Override
public Component createContent(@NonNull App app, @NonNull NavigationManager navigationManager) {
VerticalLayout layout = new VerticalLayout();
void init(App app) {
HorizontalLayout header = new HorizontalLayout();
header.setWidth(100, Sizeable.Unit.PERCENTAGE);
header.addStyleName(AcraTheme.PADDING_TOP);
layout.addComponent(header);
layout.setComponentAlignment(header, Alignment.MIDDLE_LEFT);
CheckBox hideSolved = new I18nCheckBox(true, i18n, Messages.HIDE_SOLVED);
MyGrid<VBug> bugs = new MyGrid<>(dataService.getBugProvider(app, hideSolved::getValue), i18n);
header.setWidth("100%");
getContent().add(header);
getContent().setAlignItems(FlexComponent.Alignment.START);
Translatable<Checkbox> hideSolved = Translatable.createCheckbox(true, Messages.HIDE_SOLVED);
MyGrid<VBug> bugs = new MyGrid<>(getDataService().getBugProvider(app, () -> hideSolved.getContent().getValue()));
bugs.setSelectionMode(Grid.SelectionMode.MULTI);
hideSolved.addValueChangeListener(e -> layout.getUI().access(() -> {
hideSolved.getContent().addValueChangeListener(e -> getUI().ifPresent(ui -> ui.access(() -> {
bugs.deselectAll();
bugs.getDataProvider().refreshAll();
}));
Button merge = new I18nButton(e -> {
})));
Translatable<Button> merge = Translatable.createButton(e -> {
List<VBug> selectedItems = new ArrayList<>(bugs.getSelectedItems());
if (selectedItems.size() > 1) {
RadioButtonGroup<String> titles = new RadioButtonGroup<>("", selectedItems.stream().map(bug -> bug.getBug().getTitle()).collect(Collectors.toList()));
titles.setSelectedItem(selectedItems.get(0).getBug().getTitle());
new Popup(i18n, Messages.CHOOSE_BUG_GROUP_TITLE).addComponent(titles).addCreateButton(p -> {
dataService.mergeBugs(selectedItems.stream().map(VBug::getBug).collect(Collectors.toList()), titles.getSelectedItem().orElseThrow(IllegalStateException::new));
RadioButtonGroup<String> titles = new RadioButtonGroup<>();
titles.setItems(selectedItems.stream().map(bug -> bug.getBug().getTitle()).collect(Collectors.toList()));
titles.setValue(selectedItems.get(0).getBug().getTitle());
new Popup().setTitle(Messages.CHOOSE_BUG_GROUP_TITLE).addComponent(titles).addCreateButton(p -> {
getDataService().mergeBugs(selectedItems.stream().map(VBug::getBug).collect(Collectors.toList()), titles.getValue());
bugs.deselectAll();
bugs.getDataProvider().refreshAll();
}, true).show();
} else {
Notification.show(i18n.get(Messages.ONLY_ONE_BUG_SELECTED), Notification.Type.ERROR_MESSAGE);
Notification.show(Messages.ONLY_ONE_BUG_SELECTED);
}
}, i18n, Messages.MERGE_BUGS);
header.addComponent(merge);
header.addComponent(hideSolved);
header.setComponentAlignment(hideSolved, Alignment.MIDDLE_RIGHT);
}, Messages.MERGE_BUGS);
header.add(merge, hideSolved);
header.setSpacing(false);
header.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN);
bugs.addColumn(VBug::getReportCount, QReport.report.count(), Messages.REPORTS);
bugs.sort(bugs.addColumn(VBug::getLastReport, new TimeSpanRenderer(), QReport.report.date.max(), Messages.LATEST_REPORT), SortDirection.DESCENDING);
bugs.addColumn(new TimeSpanRenderer<>(VBug::getLastReport), QReport.report.date.max(), Messages.LATEST_REPORT);
bugs.addColumn(VBug::getHighestVersionCode, QReport.report.stacktrace.version.code.max(), Messages.LATEST_VERSION);
bugs.addColumn(VBug::getUserCount, QReport.report.installationId.countDistinct(), Messages.AFFECTED_USERS);
bugs.addColumn(bug -> bug.getBug().getTitle(), QBug.bug.title, Messages.TITLE).setExpandRatio(1).setMinimumWidthFromContent(false);
bugs.addOnClickNavigation(navigationManager, com.faendir.acra.ui.view.bug.BugView.class, bugItemClick -> String.valueOf(bugItemClick.getItem().getBug().getId()));
bugs.addColumn(bug -> new MyCheckBox(bug.getBug().isSolved(),
SecurityUtils.hasPermission(app, Permission.Level.EDIT),
e -> dataService.setBugSolved(bug.getBug(), e.getValue())), new ComponentRenderer(), QBug.bug.solved, Messages.SOLVED);
layout.addComponent(bugs);
layout.setExpandRatio(bugs, 1);
layout.setSizeFull();
layout.addStyleName(AcraTheme.NO_PADDING);
return layout;
}
@Override
public int getOrder() {
return 0;
bugs.addColumn(bug -> bug.getBug().getTitle(), QBug.bug.title, Messages.TITLE).setFlexGrow(1);
bugs.addColumn(new ComponentRenderer<>(bug -> {
Checkbox checkbox = new Checkbox(bug.getBug().isSolved());
checkbox.setEnabled(SecurityUtils.hasPermission(app, Permission.Level.EDIT));
checkbox.addValueChangeListener(e -> getDataService().setBugSolved(bug.getBug(), e.getValue()));
return checkbox;
}), QBug.bug.solved, Messages.SOLVED);
bugs.addOnClickNavigation(ReportTab.class, bug -> bug.getBug().getId());
getContent().removeAll();
getContent().add(bugs);
getContent().setFlexGrow(1, bugs);
getContent().setSizeFull();
}
}

View file

@ -1,71 +1,37 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.ui.view.app.tabs;
import com.faendir.acra.i18n.Messages;
import com.faendir.acra.model.App;
import com.faendir.acra.service.AvatarService;
import com.faendir.acra.service.DataService;
import com.faendir.acra.ui.navigation.NavigationManager;
import com.faendir.acra.ui.view.base.ReportList;
import com.vaadin.spring.annotation.SpringComponent;
import com.vaadin.spring.annotation.ViewScope;
import com.vaadin.ui.Component;
import com.faendir.acra.ui.base.ReportList;
import com.faendir.acra.ui.view.app.AppView;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import com.vaadin.flow.spring.annotation.UIScope;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.vaadin.spring.i18n.I18N;
/**
* @author Lukas
* @since 29.05.2017
* @author lukas
* @since 14.07.18
*/
@UIScope
@SpringComponent
@ViewScope
public class ReportTab implements AppTab {
@NonNull private final DataService dataService;
@Route(value = "report", layout = AppView.class)
public class ReportTab extends AppTab<Div> {
@NonNull private final AvatarService avatarService;
@NonNull private final I18N i18n;
@Autowired
public ReportTab(@NonNull DataService dataService, @NonNull AvatarService avatarService, @NonNull I18N i18n) {
this.dataService = dataService;
public ReportTab(@NonNull DataService dataService, @NonNull AvatarService avatarService) {
super(dataService);
this.avatarService = avatarService;
this.i18n = i18n;
getContent().setSizeFull();
}
@Override
public Component createContent(@NonNull App app, @NonNull NavigationManager navigationManager) {
Component content = new ReportList(app, navigationManager, avatarService, dataService::delete, dataService.getReportProvider(app), i18n);
content.setSizeFull();
return content;
}
@Override
public String getCaption() {
return i18n.get(Messages.REPORTS);
}
@Override
public String getId() {
return "reports";
}
@Override
public int getOrder() {
return 1;
void init(App app) {
getContent().removeAll();
getContent().add(new ReportList(app, getDataService().getReportProvider(app), avatarService, getDataService()::delete));
}
}

View file

@ -1,71 +1,33 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.ui.view.app.tabs;
import com.faendir.acra.i18n.Messages;
import com.faendir.acra.model.App;
import com.faendir.acra.service.DataService;
import com.faendir.acra.ui.navigation.NavigationManager;
import com.faendir.acra.ui.view.base.statistics.Statistics;
import com.vaadin.spring.annotation.SpringComponent;
import com.vaadin.spring.annotation.ViewScope;
import com.vaadin.ui.Component;
import com.vaadin.ui.Panel;
import com.vaadin.ui.themes.AcraTheme;
import com.faendir.acra.ui.base.statistics.Statistics;
import com.faendir.acra.ui.view.app.AppView;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import com.vaadin.flow.spring.annotation.UIScope;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.vaadin.spring.i18n.I18N;
/**
* @author Lukas
* @since 22.05.2017
* @author lukas
* @since 11.10.18
*/
@UIScope
@SpringComponent
@ViewScope
public class StatisticsTab implements AppTab {
@NonNull private final DataService dataService;
@NonNull private final I18N i18n;
@Route(value = "statistics", layout = AppView.class)
public class StatisticsTab extends AppTab<Div> {
@Autowired
public StatisticsTab(@NonNull DataService dataService, @NonNull I18N i18n) {
this.dataService = dataService;
this.i18n = i18n;
public StatisticsTab(@NonNull DataService dataService) {
super(dataService);
getContent().setSizeFull();
}
@Override
public Component createContent(@NonNull App app, @NonNull NavigationManager navigationManager) {
Panel root = new Panel(new Statistics(app, null, dataService, i18n));
root.setSizeFull();
root.addStyleNames(AcraTheme.NO_BACKGROUND, AcraTheme.NO_BORDER);
return root;
}
@Override
public String getCaption() {
return i18n.get(Messages.STATISTICS);
}
@Override
public String getId() {
return "statistics";
}
@Override
public int getOrder() {
return 2;
void init(App app) {
getContent().removeAll();
getContent().add(new Statistics(app, null, getDataService()));
}
}

View file

@ -1,27 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.ui.view.app.tabs.panels;
import com.faendir.acra.model.App;
import com.faendir.acra.ui.view.base.layout.PanelContentFactory;
/**
* @author lukas
* @since 17.06.18
*/
public interface AdminPanel extends PanelContentFactory<App> {
}

View file

@ -1,140 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.ui.view.app.tabs.panels;
import com.faendir.acra.i18n.I18nButton;
import com.faendir.acra.i18n.I18nLabel;
import com.faendir.acra.i18n.I18nSlider;
import com.faendir.acra.i18n.Messages;
import com.faendir.acra.model.App;
import com.faendir.acra.model.QReport;
import com.faendir.acra.service.DataService;
import com.faendir.acra.ui.navigation.NavigationManager;
import com.faendir.acra.ui.view.base.ConfigurationLabel;
import com.faendir.acra.ui.view.base.popup.Popup;
import com.faendir.acra.ui.view.base.popup.ValidatedField;
import com.vaadin.icons.VaadinIcons;
import com.vaadin.server.Resource;
import com.vaadin.server.Sizeable;
import com.vaadin.shared.ui.ContentMode;
import com.vaadin.spring.annotation.SpringComponent;
import com.vaadin.spring.annotation.ViewScope;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.ComboBox;
import com.vaadin.ui.Component;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.themes.AcraTheme;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.vaadin.risto.stepper.IntStepper;
import org.vaadin.spring.i18n.I18N;
/**
* @author lukas
* @since 17.06.18
*/
@SpringComponent
@ViewScope
public class DangerPanel implements AdminPanel {
@NonNull private final DataService dataService;
@NonNull private final I18N i18n;
@Autowired
public DangerPanel(@NonNull DataService dataService, @NonNull I18N i18n) {
this.dataService = dataService;
this.i18n = i18n;
}
@Override
public Component createContent(@NonNull App app, @NonNull NavigationManager navigationManager) {
Button configButton = new I18nButton(e -> new Popup(i18n, Messages.CONFIRM).addComponent(new I18nLabel(ContentMode.HTML, i18n, Messages.NEW_ACRA_CONFIG_CONFIRM))
.addYesNoButtons(popup -> popup.clear().addComponent(new ConfigurationLabel(dataService.recreateReporterUser(app), i18n)).addCloseButton().show())
.show(), i18n, Messages.NEW_ACRA_CONFIG);
configButton.setWidth(100, Sizeable.Unit.PERCENTAGE);
Button matchingButton = new I18nButton(e -> {
App.Configuration configuration = app.getConfiguration();
I18nSlider score = new I18nSlider(0, 100, i18n, Messages.MIN_SCORE);
score.setValue((double) configuration.getMinScore());
new Popup(i18n, Messages.CONFIRM).addValidatedField(ValidatedField.of(score), true)
.addComponent(new I18nLabel(i18n, Messages.NEW_BUG_CONFIG_CONFIRM))
.addYesNoButtons(p -> dataService.changeConfiguration(app, new App.Configuration(score.getValue().intValue())), true)
.show();
}, i18n, Messages.NEW_BUG_CONFIG);
matchingButton.setWidth(100, Sizeable.Unit.PERCENTAGE);
IntStepper age = new IntStepper();
age.setValue(30);
age.setMinValue(0);
age.setWidth(100, Sizeable.Unit.PERCENTAGE);
HorizontalLayout purgeAge = new HorizontalLayout();
purgeAge.setDefaultComponentAlignment(Alignment.MIDDLE_CENTER);
purgeAge.setWidth(100, Sizeable.Unit.PERCENTAGE);
purgeAge.addStyleName(AcraTheme.NO_MARGIN);
purgeAge.addComponents(new I18nButton(e -> dataService.deleteReportsOlderThanDays(app, age.getValue()), i18n, Messages.PURGE),
new I18nLabel(i18n, Messages.REPORTS_OLDER_THAN1),
age,
new I18nLabel(i18n, Messages.REPORTS_OLDER_THAN2));
purgeAge.setExpandRatio(age, 1);
ComboBox<Integer> versionBox = new ComboBox<>(null, dataService.getFromReports(app, null, QReport.report.stacktrace.version.code));
versionBox.setEmptySelectionAllowed(false);
versionBox.setWidth(100, Sizeable.Unit.PERCENTAGE);
HorizontalLayout purgeVersion = new HorizontalLayout();
purgeVersion.setWidth(100, Sizeable.Unit.PERCENTAGE);
purgeVersion.setDefaultComponentAlignment(Alignment.MIDDLE_CENTER);
purgeVersion.addStyleName(AcraTheme.NO_MARGIN);
purgeVersion.addComponents(new I18nButton(e -> {
if (versionBox.getValue() != null) {
dataService.deleteReportsBeforeVersion(app, versionBox.getValue());
}
}, i18n, Messages.PURGE), new I18nLabel(i18n, Messages.REPORTS_BEFORE_VERSION), versionBox);
purgeVersion.setExpandRatio(versionBox, 1);
Button deleteButton = new I18nButton(e -> new Popup(i18n, Messages.CONFIRM).addComponent(new I18nLabel(i18n, Messages.DELETE_APP_CONFIRM)).addYesNoButtons(popup -> {
dataService.delete(app);
navigationManager.navigateBack();
}, true).show(), i18n, Messages.DELETE_APP);
VerticalLayout layout = new VerticalLayout(configButton, matchingButton, purgeAge, purgeVersion, deleteButton);
deleteButton.setWidth(100, Sizeable.Unit.PERCENTAGE);
layout.setSizeFull();
layout.addStyleName(AcraTheme.NO_PADDING);
return layout;
}
@Override
public String getCaption() {
return i18n.get(Messages.DANGER_ZONE);
}
@Override
public String getId() {
return "danger-zone";
}
@Override
public Resource getIcon() {
return VaadinIcons.EXCLAMATION;
}
@Override
public String getStyleName() {
return AcraTheme.RED_PANEL_HEADER;
}
@Override
public int getOrder() {
return 2;
}
}

View file

@ -1,97 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.ui.view.app.tabs.panels;
import com.faendir.acra.i18n.I18nButton;
import com.faendir.acra.i18n.I18nComboBox;
import com.faendir.acra.i18n.Messages;
import com.faendir.acra.model.App;
import com.faendir.acra.model.QReport;
import com.faendir.acra.rest.RestReportInterface;
import com.faendir.acra.service.DataService;
import com.faendir.acra.ui.navigation.NavigationManager;
import com.vaadin.server.Page;
import com.vaadin.spring.annotation.SpringComponent;
import com.vaadin.spring.annotation.ViewScope;
import com.vaadin.ui.Button;
import com.vaadin.ui.ComboBox;
import com.vaadin.ui.Component;
import com.vaadin.ui.Notification;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.springframework.web.util.UriComponentsBuilder;
import org.vaadin.spring.i18n.I18N;
/**
* @author lukas
* @since 17.06.18
*/
@SpringComponent
@ViewScope
public class ExportPanel implements AdminPanel {
@NonNull private final DataService dataService;
@NonNull private final I18N i18n;
@Autowired
public ExportPanel(@NonNull DataService dataService, @NonNull I18N i18n) {
this.dataService = dataService;
this.i18n = i18n;
}
@Override
public Component createContent(@NonNull App app, @NonNull NavigationManager navigationManager) {
ComboBox<String> mailBox = new I18nComboBox<>(dataService.getFromReports(app, null, QReport.report.userEmail), i18n, Messages.BY_MAIL);
mailBox.setEmptySelectionAllowed(true);
mailBox.setSizeFull();
ComboBox<String> idBox = new I18nComboBox<>(dataService.getFromReports(app, null, QReport.report.installationId), i18n, Messages.BY_ID);
idBox.setEmptySelectionAllowed(true);
idBox.setSizeFull();
Button download = new I18nButton(e -> {
if (idBox.getValue() == null && mailBox.getValue() == null) {
Notification.show(i18n.get(Messages.NOTHING_SELECTED), Notification.Type.WARNING_MESSAGE);
} else {
Page page = UI.getCurrent().getPage();
page.open(UriComponentsBuilder.fromUri(page.getLocation())
.fragment(null)
.pathSegment(RestReportInterface.EXPORT_PATH)
.queryParam(RestReportInterface.PARAM_APP, app.getId())
.queryParam(RestReportInterface.PARAM_ID, idBox.getValue())
.queryParam(RestReportInterface.PARAM_MAIL, mailBox.getValue())
.build()
.toUriString(), null);
}
}, i18n, Messages.DOWNLOAD);
download.setSizeFull();
return new VerticalLayout(mailBox, idBox, download);
}
@Override
public String getCaption() {
return i18n.get(Messages.EXPORT);
}
@Override
public String getId() {
return "export";
}
@Override
public int getOrder() {
return 1;
}
}

View file

@ -1,118 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.ui.view.app.tabs.panels;
import com.faendir.acra.i18n.I18nButton;
import com.faendir.acra.i18n.I18nIntStepper;
import com.faendir.acra.i18n.I18nLabel;
import com.faendir.acra.i18n.Messages;
import com.faendir.acra.model.App;
import com.faendir.acra.model.Permission;
import com.faendir.acra.model.ProguardMapping;
import com.faendir.acra.model.QProguardMapping;
import com.faendir.acra.security.SecurityUtils;
import com.faendir.acra.service.DataService;
import com.faendir.acra.ui.navigation.NavigationManager;
import com.faendir.acra.ui.view.base.InMemoryUpload;
import com.faendir.acra.ui.view.base.layout.MyGrid;
import com.faendir.acra.ui.view.base.popup.Popup;
import com.faendir.acra.ui.view.base.popup.ValidatedField;
import com.vaadin.icons.VaadinIcons;
import com.vaadin.shared.data.sort.SortDirection;
import com.vaadin.spring.annotation.SpringComponent;
import com.vaadin.spring.annotation.ViewScope;
import com.vaadin.ui.Component;
import com.vaadin.ui.ProgressBar;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.renderers.ButtonRenderer;
import com.vaadin.ui.themes.AcraTheme;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.vaadin.risto.stepper.IntStepper;
import org.vaadin.spring.i18n.I18N;
/**
* @author lukas
* @since 17.06.18
*/
@SpringComponent
@ViewScope
public class ProguardPanel implements AdminPanel{
@NonNull private final DataService dataService;
@NonNull private final I18N i18n;
@Autowired
public ProguardPanel(@NonNull DataService dataService, @NonNull I18N i18n) {
this.dataService = dataService;
this.i18n = i18n;
}
@Override
public Component createContent(@NonNull App app, @NonNull NavigationManager navigationManager) {
VerticalLayout layout = new VerticalLayout();
MyGrid<ProguardMapping> grid = new MyGrid<>(dataService.getMappingProvider(app), i18n);
grid.setSizeToRows();
grid.sort(grid.addColumn(ProguardMapping::getVersionCode, QProguardMapping.proguardMapping.versionCode, Messages.VERSION), SortDirection.ASCENDING);
if (SecurityUtils.hasPermission(app, Permission.Level.EDIT)) {
ButtonRenderer<ProguardMapping> renderer = new ButtonRenderer<>(e -> new Popup(i18n, Messages.CONFIRM)
.addComponent(new I18nLabel(i18n, Messages.DELETE_MAPPING_CONFIRM, e.getItem().getVersionCode()))
.addYesNoButtons(p -> {
dataService.delete(e.getItem());
grid.getDataProvider().refreshAll();
}, true)
.show());
renderer.setHtmlContentAllowed(true);
grid.addColumn(report -> VaadinIcons.TRASH.getHtml(),
renderer);
}
layout.addComponent(grid);
layout.addStyleName(AcraTheme.NO_PADDING);
if (SecurityUtils.hasPermission(app, Permission.Level.EDIT)) {
layout.addComponent(new I18nButton(e -> {
IntStepper version = new I18nIntStepper(dataService.getMaximumMappingVersion(app).map(i -> i + 1).orElse(1), i18n, Messages.VERSION_CODE);
InMemoryUpload upload = new InMemoryUpload(i18n, Messages.MAPPING_FILE);
ProgressBar progressBar = new ProgressBar();
upload.addProgressListener((readBytes, contentLength) -> layout.getUI().access(() -> progressBar.setValue((float) readBytes / contentLength)));
new Popup(i18n, Messages.NEW_MAPPING)
.addComponent(version)
.addValidatedField(ValidatedField.of(upload, () -> upload, consumer -> upload.addFinishedListener(event -> consumer.accept(upload)))
.addValidator(InMemoryUpload::isUploaded, Messages.ERROR_UPLOAD))
.addComponent(progressBar)
.addCreateButton(popup -> {
dataService.store(new ProguardMapping(app, version.getValue(), upload.getUploadedString()));
grid.getDataProvider().refreshAll();
}, true)
.show();
}, i18n, Messages.NEW_FILE));
}
return layout;
}
@Override
public String getCaption() {
return i18n.get(Messages.DE_OBFUSCATION);
}
@Override
public String getId() {
return "proguard";
}
@Override
public int getOrder() {
return 0;
}
}

View file

@ -1,36 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.ui.view.base;
import com.faendir.acra.i18n.I18nLabel;
import com.faendir.acra.i18n.Messages;
import com.faendir.acra.rest.RestReportInterface;
import com.faendir.acra.util.PlainTextUser;
import com.faendir.acra.util.Utils;
import com.vaadin.shared.ui.ContentMode;
import org.vaadin.spring.i18n.I18N;
/**
* @author Lukas
* @since 18.12.2017
*/
public class ConfigurationLabel extends I18nLabel {
public ConfigurationLabel(PlainTextUser user, I18N i18n) {
super(i18n, Messages.CONFIGURATION_LABEL, Utils.getUrlWithFragment(null), RestReportInterface.REPORT_PATH, user.getUsername(), user.getPlaintextPassword());
setContentMode(ContentMode.HTML);
}
}

View file

@ -1,68 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.ui.view.base;
import com.faendir.acra.i18n.HasI18n;
import com.vaadin.ui.Upload;
import org.vaadin.spring.i18n.I18N;
import org.vaadin.spring.i18n.support.Translatable;
import java.io.ByteArrayOutputStream;
import java.util.Locale;
/**
* @author Lukas
* @since 19.12.2017
*/
public class InMemoryUpload extends Upload implements Translatable, HasI18n {
private final I18N i18n;
private final String captionId;
private final Object[] params;
private final ByteArrayOutputStream outputStream;
private boolean finished;
public InMemoryUpload(I18N i18n, String captionId, Object... params) {
super();
this.i18n = i18n;
this.captionId = captionId;
this.params = params;
outputStream = new ByteArrayOutputStream();
finished = false;
setReceiver((filename, mimeType) -> outputStream);
addSucceededListener(event -> finished = true);
addFailedListener(event -> finished = false);
updateMessageStrings(i18n.getLocale());
}
public boolean isUploaded() {
return finished;
}
public String getUploadedString() {
return outputStream.toString();
}
@Override
public void updateMessageStrings(Locale locale) {
setCaption(i18n.get(captionId, locale, params));
}
@Override
public I18N getI18n() {
return i18n;
}
}

View file

@ -1,36 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.ui.view.base;
import com.faendir.acra.client.MiddleClickExtensionConnector;
import com.vaadin.event.MouseEvents;
import com.vaadin.server.AbstractExtension;
import com.vaadin.ui.AbstractComponent;
/**
* @author lukas
* @since 19.06.18
*/
public class MiddleClickExtension extends AbstractExtension {
private MiddleClickExtension(AbstractComponent component) {
super(component);
registerRpc(details -> fireEvent(new MouseEvents.ClickEvent(component, details)), MiddleClickExtensionConnector.Rpc.class);
}
public static void extend(AbstractComponent component, MouseEvents.ClickListener listener) {
new MiddleClickExtension(component).addListener(MouseEvents.ClickEvent.class, listener, MouseEvents.ClickListener.clickMethod);
}
}

View file

@ -1,32 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.ui.view.base;
import com.vaadin.ui.CheckBox;
import org.springframework.lang.NonNull;
/**
* @author Lukas
* @since 31.05.2017
*/
public class MyCheckBox extends CheckBox {
public MyCheckBox(boolean value, boolean enabled, @NonNull ValueChangeListener<Boolean> changeListener) {
setValue(value);
setEnabled(enabled);
addValueChangeListener(changeListener);
}
}

View file

@ -1,72 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.ui.view.base;
import com.faendir.acra.dataprovider.QueryDslDataProvider;
import com.faendir.acra.i18n.I18nLabel;
import com.faendir.acra.i18n.Messages;
import com.faendir.acra.model.App;
import com.faendir.acra.model.Permission;
import com.faendir.acra.model.QReport;
import com.faendir.acra.model.Report;
import com.faendir.acra.security.SecurityUtils;
import com.faendir.acra.service.AvatarService;
import com.faendir.acra.ui.navigation.NavigationManager;
import com.faendir.acra.ui.view.base.layout.MyGrid;
import com.faendir.acra.ui.view.base.popup.Popup;
import com.faendir.acra.ui.view.report.ReportView;
import com.faendir.acra.util.TimeSpanRenderer;
import com.vaadin.icons.VaadinIcons;
import com.vaadin.shared.data.sort.SortDirection;
import com.vaadin.ui.Grid;
import com.vaadin.ui.renderers.ButtonRenderer;
import com.vaadin.ui.renderers.ImageRenderer;
import org.springframework.lang.NonNull;
import org.vaadin.spring.i18n.I18N;
import java.util.function.Consumer;
/**
* @author Lukas
* @since 14.05.2017
*/
public class ReportList extends MyGrid<Report> {
public ReportList(@NonNull App app, @NonNull NavigationManager navigationManager, @NonNull AvatarService avatarService, @NonNull Consumer<Report> reportDeleter, @NonNull QueryDslDataProvider<Report> reportProvider, I18N i18n) {
super(reportProvider, i18n, Messages.REPORTS);
setSelectionMode(Grid.SelectionMode.NONE);
addColumn(avatarService::getAvatar, new ImageRenderer<>(), QReport.report.installationId, Messages.USER);
sort(addColumn(Report::getDate, new TimeSpanRenderer(), QReport.report.date, Messages.DATE), SortDirection.DESCENDING);
addColumn(report -> report.getStacktrace().getVersion().getCode(), QReport.report.stacktrace.version.code, Messages.APP_VERSION);
addColumn(Report::getAndroidVersion, QReport.report.androidVersion, Messages.ANDROID_VERSION);
addColumn(Report::getPhoneModel, QReport.report.phoneModel, Messages.DEVICE);
addColumn(report -> report.getStacktrace().getStacktrace().split("\n", 2)[0], QReport.report.stacktrace.stacktrace, Messages.STACKTRACE).setExpandRatio(1)
.setMinimumWidthFromContent(false);
if (SecurityUtils.hasPermission(app, Permission.Level.EDIT)) {
ButtonRenderer<Report> renderer = new ButtonRenderer<>(e -> new Popup(i18n, Messages.CONFIRM)
.addComponent(new I18nLabel(i18n, Messages.DELETE_REPORT_CONFIRM))
.addYesNoButtons(p -> {
reportDeleter.accept(e.getItem());
getDataProvider().refreshAll();
}, true)
.show());
renderer.setHtmlContentAllowed(true);
addColumn(report -> VaadinIcons.TRASH.getHtml(),
renderer).setSortable(false);
}
addOnClickNavigation(navigationManager, ReportView.class, e -> e.getItem().getId());
}
}

View file

@ -1,34 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.ui.view.base.layout;
import com.faendir.acra.ui.navigation.NavigationManager;
import com.vaadin.ui.Component;
import org.springframework.core.Ordered;
import org.springframework.lang.NonNull;
/**
* @author lukas
* @since 17.06.18
*/
public interface ComponentFactory<T> extends Ordered {
Component createContent(@NonNull T t, @NonNull NavigationManager navigationManager);
String getCaption();
String getId();
}

View file

@ -1,39 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.ui.view.base.layout;
import com.vaadin.ui.Component;
import com.vaadin.ui.CssLayout;
import com.vaadin.ui.themes.AcraTheme;
/**
* @author lukas
* @since 28.05.18
*/
public class FlexLayout extends CssLayout {
public FlexLayout(Component... children) {
super(children);
setResponsive(true);
addStyleName(AcraTheme.FLEX_LAYOUT);
}
@Override
public void addComponent(Component c) {
super.addComponent(c);
c.addStyleName(AcraTheme.FLEX_ITEM);
}
}

View file

@ -1,225 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.ui.view.base.layout;
import com.faendir.acra.client.mygrid.GridMiddleClickExtensionConnector;
import com.faendir.acra.dataprovider.QueryDslDataProvider;
import com.faendir.acra.i18n.HasI18n;
import com.faendir.acra.i18n.Messages;
import com.faendir.acra.ui.navigation.NavigationManager;
import com.faendir.acra.ui.view.base.navigation.BaseView;
import com.querydsl.core.types.Expression;
import com.vaadin.data.ValueProvider;
import com.vaadin.data.provider.DataProvider;
import com.vaadin.shared.MouseEventDetails;
import com.vaadin.shared.data.sort.SortDirection;
import com.vaadin.ui.Composite;
import com.vaadin.ui.Grid;
import com.vaadin.ui.renderers.AbstractRenderer;
import com.vaadin.ui.renderers.TextRenderer;
import elemental.json.JsonObject;
import org.springframework.data.util.Pair;
import org.springframework.lang.NonNull;
import org.vaadin.spring.i18n.I18N;
import org.vaadin.spring.i18n.support.Translatable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EventObject;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
/**
* @author Lukas
* @since 14.05.2017
*/
public class MyGrid<T> extends Composite implements Translatable, HasI18n {
private final I18N i18n;
private final String captionId;
private final Object[] params;
private final Map<Grid.Column<T, ?>, Pair<String, Object[]>> columnCaptions;
private final List<Translatable> translatables;
private final ExposingGrid<T> grid;
private final QueryDslDataProvider<T> dataProvider;
public MyGrid(QueryDslDataProvider<T> dataProvider, I18N i18n) {
this(dataProvider, i18n, Messages.BLANK);
}
public MyGrid(QueryDslDataProvider<T> dataProvider, I18N i18n, String captionId, Object... params) {
this.dataProvider = dataProvider;
grid = new ExposingGrid<>(dataProvider);
setCompositionRoot(grid);
setSizeFull();
MiddleClickExtension.extend(this);
this.i18n = i18n;
this.captionId = captionId;
columnCaptions = new HashMap<>();
translatables = new ArrayList<>();
this.params = params;
updateMessageStrings(i18n.getLocale());
}
@NonNull
public <R> Grid.Column<T, R> addColumn(@NonNull ValueProvider<T, R> valueProvider, @NonNull String captionId, Object... params) {
Grid.Column<T, R> column = addColumn(valueProvider, new TextRenderer(), i18n.get(captionId, params));
columnCaptions.put(column, Pair.of(captionId, params));
return column;
}
@NonNull
public <R> Grid.Column<T, R> addColumn(@NonNull ValueProvider<T, R> valueProvider, @NonNull AbstractRenderer<? super T, ? super R> renderer, @NonNull String captionId,
Object... params) {
Grid.Column<T, R> column = addColumn(valueProvider, renderer).setCaption(i18n.get(captionId, params));
columnCaptions.put(column, Pair.of(captionId, params));
return column;
}
@NonNull
public <R> Grid.Column<T, R> addColumn(@NonNull ValueProvider<T, R> valueProvider, @NonNull Expression<? extends Comparable> sort, @NonNull String captionId,
Object... params) {
return addColumn(valueProvider, new TextRenderer(), sort, captionId, params);
}
@NonNull
public <R> Grid.Column<T, R> addColumn(@NonNull ValueProvider<T, R> valueProvider, @NonNull AbstractRenderer<? super T, ? super R> renderer,
@NonNull Expression<? extends Comparable> sort, @NonNull String captionId, Object... params) {
Grid.Column<T, R> column = addColumn(valueProvider, renderer).setId(dataProvider.addSortable(sort)).setCaption(i18n.get(captionId, params)).setSortable(true);
columnCaptions.put(column, Pair.of(captionId, params));
return column;
}
@NonNull
public <R> Grid.Column<T, R> addColumn(@NonNull ValueProvider<T, R> valueProvider, @NonNull AbstractRenderer<? super T, ? super R> renderer) {
if(renderer instanceof Translatable) {
((Translatable) renderer).updateMessageStrings(i18n.getLocale());
translatables.add((Translatable) renderer);
}
return grid.addColumn(valueProvider, renderer).setSortable(false);
}
public void setItems(@NonNull Collection<T> items) {
grid.setItems(items);
setHeightByRows(items.size());
}
public void setHeightByRows(double rows) {
grid.setHeightByRows(rows >= 1 ? rows : 1);
}
public void setSizeToRows() {
dataProvider.addSizeListener(rows -> getUI().access(() -> setHeightByRows(rows)));
}
public void setBodyRowHeight(double rowHeight) {
grid.setBodyRowHeight(rowHeight);
}
public void setSelectionMode(Grid.SelectionMode selectionMode) {
grid.setSelectionMode(selectionMode);
}
public void sort(Grid.Column<T, ?> column, SortDirection direction) {
grid.sort(column, direction);
}
public Set<T> getSelectedItems() {
return grid.getSelectedItems();
}
public void select(T item) {
grid.select(item);
}
public void deselectAll() {
grid.deselectAll();
}
public void addOnClickNavigation(@NonNull NavigationManager navigationManager, Class<? extends BaseView> namedView, Function<Grid.ItemClick<T>, String> parameterGetter) {
grid.addItemClickListener(e -> {
boolean newTab = e.getMouseEventDetails().getButton() == MouseEventDetails.MouseButton.MIDDLE || e.getMouseEventDetails().isCtrlKey();
navigationManager.navigateTo(namedView, parameterGetter.apply(e), newTab);
});
}
public QueryDslDataProvider<T> getDataProvider() {
return dataProvider;
}
@Override
public void updateMessageStrings(Locale locale) {
setCaption(i18n.get(captionId, locale, params));
columnCaptions.forEach((column, caption) -> column.setCaption(i18n.get(caption.getFirst(), locale, caption.getSecond())));
translatables.forEach(translatable -> translatable.updateMessageStrings(locale));
}
@Override
public I18N getI18n() {
return i18n;
}
public static class MiddleClickExtension<T> extends Grid.AbstractGridExtension<T> {
private MiddleClickExtension(MyGrid<T> myGrid) {
ExposingGrid<T> grid = myGrid.grid;
super.extend(grid);
registerRpc((rowIndex, rowKey, columnInternalId, details) -> grid.fireEvent(new Grid.ItemClick<>(grid,
grid.getColumnByInternalId(columnInternalId),
grid.getDataCommunicator().getKeyMapper().get(rowKey),
details,
rowIndex)), GridMiddleClickExtensionConnector.Rpc.class);
}
public static void extend(MyGrid<?> grid) {
new MiddleClickExtension<>(grid);
}
@Override
public void generateData(Object item, JsonObject jsonObject) {
}
@Override
public void destroyData(Object item) {
}
@Override
public void destroyAllData() {
}
@Override
public void refreshData(Object item) {
}
}
private static class ExposingGrid<T> extends Grid<T> {
ExposingGrid(DataProvider<T, ?> dataProvider) {
super(dataProvider);
}
@Override
protected Column<T, ?> getColumnByInternalId(String columnId) {
return super.getColumnByInternalId(columnId);
}
@Override
protected void fireEvent(EventObject event) {
super.fireEvent(event);
}
}
}

View file

@ -1,132 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.ui.view.base.layout;
import com.faendir.acra.client.mytabsheet.TabSheetMiddleClickExtensionConnector;
import com.faendir.acra.ui.navigation.NavigationManager;
import com.vaadin.event.ConnectorEventListener;
import com.vaadin.event.MouseEvents;
import com.vaadin.server.AbstractExtension;
import com.vaadin.shared.MouseEventDetails;
import com.vaadin.ui.Component;
import com.vaadin.ui.CustomComponent;
import com.vaadin.ui.TabSheet;
import com.vaadin.util.ReflectTools;
import org.springframework.lang.NonNull;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.stream.StreamSupport;
/**
* @author Lukas
* @since 12.12.2017
*/
public class MyTabSheet<T> extends TabSheet {
private final T t;
private final NavigationManager navigationManager;
public MyTabSheet(@NonNull T t, @NonNull NavigationManager navigationManager, Collection<? extends ComponentFactory<T>> tabs) {
this.t = t;
this.navigationManager = navigationManager;
for (ComponentFactory<T> tab : tabs) {
addTab(tab);
}
MiddleClickExtension.extend(this);
}
public void addTab(ComponentFactory<T> tab) {
TabWrapper<T> wrapper = new TabWrapper<>(t, navigationManager, tab);
addComponent(wrapper);
addSelectedTabChangeListener(wrapper);
}
public void guessInitialTab(String id) {
Component component = StreamSupport.stream(spliterator(), false).filter(c -> c.getId().equals(id)).findAny().orElseGet(() -> getTab(0).getComponent());
if (getSelectedTab() == component && component instanceof SelectedTabChangeListener) {
((SelectedTabChangeListener) component).selectedTabChange(new SelectedTabChangeEvent(this, true));
}
setSelectedTab(component);
}
public void addMiddleClickListener(ClickListener listener) {
addListener(ClickEvent.class, listener, ClickListener.clickMethod);
}
@FunctionalInterface
public interface ClickListener extends ConnectorEventListener {
Method clickMethod = ReflectTools.findMethod(ClickListener.class, "click", ClickEvent.class);
void click(ClickEvent event);
}
private static class TabWrapper<T> extends CustomComponent implements SelectedTabChangeListener {
private final T t;
private final NavigationManager navigationManager;
private final ComponentFactory<T> tab;
private TabWrapper(@NonNull T t, @NonNull NavigationManager navigationManager, @NonNull ComponentFactory<T> tab) {
this.t = t;
this.navigationManager = navigationManager;
this.tab = tab;
setSizeFull();
}
@Override
public void selectedTabChange(@NonNull SelectedTabChangeEvent event) {
if (this == event.getTabSheet().getSelectedTab()) {
setCompositionRoot(tab.createContent(t, navigationManager));
} else {
setCompositionRoot(null);
}
}
@Override
public String getCaption() {
return tab.getCaption();
}
@Override
public String getId() {
return tab.getId();
}
}
public static class MiddleClickExtension extends AbstractExtension {
private MiddleClickExtension(MyTabSheet component) {
super(component);
registerRpc((tabIndex, details) -> component.fireEvent(new ClickEvent(component, ((TabWrapper) component.getTab(tabIndex).getComponent()).tab, details)),
TabSheetMiddleClickExtensionConnector.Rpc.class);
}
public static void extend(MyTabSheet component) {
new MiddleClickExtension(component);
}
}
public static class ClickEvent extends MouseEvents.ClickEvent {
private final ComponentFactory tab;
public ClickEvent(MyTabSheet source, ComponentFactory tab, MouseEventDetails mouseEventDetails) {
super(source, mouseEventDetails);
this.tab = tab;
}
public ComponentFactory getTab() {
return tab;
}
}
}

View file

@ -1,33 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.ui.view.base.layout;
import com.vaadin.server.Resource;
/**
* @author lukas
* @since 17.06.18
*/
public interface PanelContentFactory<T> extends ComponentFactory<T> {
default Resource getIcon() {
return null;
}
default String getStyleName() {
return null;
}
}

View file

@ -1,56 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.ui.view.base.layout;
import com.faendir.acra.ui.navigation.NavigationManager;
import com.vaadin.server.Sizeable;
import com.vaadin.ui.Component;
import com.vaadin.ui.Panel;
import com.vaadin.ui.themes.AcraTheme;
import org.springframework.lang.NonNull;
import java.util.List;
/**
* @author lukas
* @since 17.06.18
*/
public abstract class PanelFlexTab<T> implements ComponentFactory<T> {
@NonNull private final List<? extends PanelContentFactory<T>> factories;
public PanelFlexTab(@NonNull List<? extends PanelContentFactory<T>> factories) {
this.factories = factories;
}
@Override
public Component createContent(@NonNull T t, @NonNull NavigationManager navigationManager) {
FlexLayout layout = new FlexLayout(factories.stream().map(factory -> {
Panel panel = new Panel(factory.createContent(t, navigationManager));
panel.setCaption(factory.getCaption());
panel.setIcon(factory.getIcon());
panel.addStyleName(AcraTheme.NO_BACKGROUND);
if (factory.getStyleName() != null) {
panel.addStyleName(factory.getStyleName());
}
return panel;
}).toArray(Component[]::new));
layout.setWidth(100, Sizeable.Unit.PERCENTAGE);
Panel root = new Panel(layout);
root.setSizeFull();
root.addStyleNames(AcraTheme.NO_BACKGROUND, AcraTheme.NO_BORDER);
return root;
}
}

View file

@ -1,43 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.ui.view.base.navigation;
import com.faendir.acra.ui.navigation.NavigationManager;
import com.vaadin.navigator.View;
import com.vaadin.ui.Composite;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
/**
* Non-abstract subclasses must be annotated with {@link com.vaadin.spring.annotation.SpringView}
*
* @author Lukas
* @since 14.05.2017
*/
public abstract class BaseView extends Composite implements View {
private NavigationManager navigationManager;
protected NavigationManager getNavigationManager() {
return navigationManager;
}
@Lazy
@Autowired
public void setNavigationManager(NavigationManager navigationManager) {
this.navigationManager = navigationManager;
}
}

View file

@ -1,41 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.ui.view.base.navigation;
import com.vaadin.navigator.ViewChangeListener;
import org.springframework.lang.NonNull;
import java.util.function.Function;
/**
* @author Lukas
* @since 12.12.2017
*/
public abstract class ParametrizedBaseView<T> extends BaseView {
private Function<ViewChangeListener.ViewChangeEvent, T> parameterParser;
@Override
public final void enter(ViewChangeListener.ViewChangeEvent event) {
enter(parameterParser.apply(event));
}
protected abstract void enter(@NonNull T parameter);
public void setParameterParser(Function<ViewChangeListener.ViewChangeEvent, T> parameterParser) {
this.parameterParser = parameterParser;
}
}

View file

@ -1,140 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.ui.view.base.navigation;
import com.faendir.acra.ui.navigation.MyNavigator;
import com.faendir.acra.ui.navigation.SingleViewProvider;
import com.faendir.acra.ui.view.base.MiddleClickExtension;
import com.faendir.acra.util.Utils;
import com.vaadin.icons.VaadinIcons;
import com.vaadin.shared.ui.ContentMode;
import com.vaadin.ui.Button;
import com.vaadin.ui.Composite;
import com.vaadin.ui.CssLayout;
import com.vaadin.ui.Label;
import com.vaadin.ui.themes.AcraTheme;
import com.vaadin.ui.themes.ValoTheme;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.stream.Collectors;
/**
* @author Lukas
* @since 24.03.2018
*/
public class Path extends Composite {
private final Deque<Element> elements;
private final CssLayout layout;
public Path() {
elements = new ArrayDeque<>();
layout = new CssLayout();
layout.addStyleName(AcraTheme.BASIC_FLEX);
setCompositionRoot(layout);
}
public void set(Deque<MyNavigator.HierarchyElement> hierarchy) {
elements.clear();
layout.removeAllComponents();
hierarchy.forEach(e -> {
SingleViewProvider provider = (SingleViewProvider) e.getProvider();
goTo(provider.getTitle(provider.getParameters(e.getNavState())), e.getNavState());
});
}
public void goTo(String label, String id) {
goTo(new Element(label, id));
}
public void goTo(Element element) {
if (elements.stream().map(Element::getId).anyMatch(element.getId()::equals)) {
while (!getLast().getId().equals(element.getId())) {
goUp();
}
} else {
if (!elements.isEmpty()) {
Label icon = new Label(VaadinIcons.CARET_RIGHT.getHtml(), ContentMode.HTML);
layout.addComponent(icon);
}
Button button = new Button(element.getLabel());
button.addClickListener(e -> {
while (getLast() != element) goUp();
getUI().getNavigator().navigateTo(asUrlFragment());
});
MiddleClickExtension.extend(button, e -> {
while (getLast() != element) goUp();
getUI().getPage().open(Utils.getUrlWithFragment(asUrlFragment()), "_blank", false);
});
button.addStyleNames(ValoTheme.BUTTON_BORDERLESS, AcraTheme.PATH_ELEMENT);
layout.addComponent(button);
elements.addLast(element);
}
}
public int getSize() {
return elements.size();
}
public boolean isEmpty() {
return elements.isEmpty();
}
public Element getLast() {
return elements.getLast();
}
public Element goUp() {
if (elements.isEmpty()) {
return null;
}
removeLastComponent();
if (elements.size() != 1) removeLastComponent();
return elements.removeLast();
}
public void clear() {
layout.removeAllComponents();
elements.clear();
}
public String asUrlFragment() {
return elements.stream().map(Element::getId).collect(Collectors.joining(MyNavigator.SEPARATOR));
}
private void removeLastComponent() {
layout.removeComponent(layout.getComponent(layout.getComponentCount() - 1));
}
public static class Element {
private final String label;
private final String id;
public Element(String label, String id) {
this.label = label;
this.id = id;
}
public String getLabel() {
return label;
}
public String getId() {
return id;
}
}
}

View file

@ -1,83 +0,0 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.ui.view.base.statistics;
import com.faendir.acra.i18n.HasI18n;
import com.vaadin.ui.Composite;
import com.vaadin.ui.Panel;
import com.vaadin.ui.UI;
import com.vaadin.ui.themes.AcraTheme;
import org.jfree.chart.JFreeChart;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.vaadin.addon.JFreeChartWrapper;
import org.vaadin.spring.i18n.I18N;
import org.vaadin.spring.i18n.support.Translatable;
import java.awt.*;
import java.util.Locale;
import java.util.Map;
/**
* @author lukas
* @since 01.06.18
*/
abstract class Chart<T> extends Composite implements Translatable, HasI18n {
private final Panel panel;
private final I18N i18n;
private final String captionId;
private final Object[] params;
private JFreeChart chart;
Chart(I18N i18n, String captionId, Object... params) {
this.i18n = i18n;
this.captionId = captionId;
this.params = params;
panel = new Panel();
panel.addStyleName(AcraTheme.NO_BACKGROUND);
setCompositionRoot(panel);
updateMessageStrings(i18n.getLocale());
}
@Override
public void updateMessageStrings(Locale locale) {
setCaption(i18n.get(captionId, locale, params));
}
public void setContent(@NonNull Map<T, Long> map) {
chart = createChart(map);
JFreeChartWrapper content = new JFreeChartWrapper(chart);
content.setWidth(100, Unit.PERCENTAGE);
content.setHeight(100, Unit.PERCENTAGE);
panel.setContent(content);
}
Paint getForegroundColor() {
return UI.getCurrent().getTheme().toLowerCase().contains("dark") ? Statistics.FOREGROUND_DARK : Statistics.FOREGROUND_LIGHT;
}
protected abstract JFreeChart createChart(@NonNull Map<T, Long> map);
@Nullable
JFreeChart getChart() {
return chart;
}
@Override
public I18N getI18n() {
return i18n;
}
}

View file

@ -1,113 +1,68 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.ui.view.bug;
import com.faendir.acra.model.App;
import com.faendir.acra.i18n.Messages;
import com.faendir.acra.model.Bug;
import com.faendir.acra.model.Permission;
import com.faendir.acra.service.DataService;
import com.faendir.acra.ui.annotation.RequiresAppPermission;
import com.faendir.acra.ui.navigation.MyNavigator;
import com.faendir.acra.ui.navigation.SingleParametrizedViewProvider;
import com.faendir.acra.ui.view.base.layout.MyTabSheet;
import com.faendir.acra.ui.view.base.navigation.ParametrizedBaseView;
import com.faendir.acra.ui.base.ActiveChildAware;
import com.faendir.acra.ui.base.ParentLayout;
import com.faendir.acra.ui.component.Tab;
import com.faendir.acra.ui.view.MainView;
import com.faendir.acra.ui.view.bug.tabs.AdminTab;
import com.faendir.acra.ui.view.bug.tabs.BugTab;
import com.vaadin.spring.annotation.SpringComponent;
import com.vaadin.spring.annotation.UIScope;
import com.vaadin.spring.annotation.ViewScope;
import com.vaadin.ui.Panel;
import com.vaadin.ui.themes.AcraTheme;
import com.faendir.acra.ui.view.bug.tabs.ReportTab;
import com.faendir.acra.ui.view.bug.tabs.StacktraceTab;
import com.faendir.acra.ui.view.bug.tabs.StatisticsTab;
import com.vaadin.flow.component.tabs.Tabs;
import com.vaadin.flow.router.RoutePrefix;
import com.vaadin.flow.spring.annotation.SpringComponent;
import com.vaadin.flow.spring.annotation.UIScope;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.util.Pair;
import org.springframework.lang.NonNull;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
/**
* @author Lukas
* @since 21.03.2018
* @author lukas
* @since 08.09.18
*/
@UIScope
@SpringComponent
@ViewScope
@RequiresAppPermission(Permission.Level.VIEW)
public class BugView extends ParametrizedBaseView<Pair<Bug, String>> {
private final List<BugTab> tabs;
@RoutePrefix("bug")
@com.vaadin.flow.router.ParentLayout(MainView.class)
public class BugView extends ParentLayout implements ActiveChildAware<BugTab<?>, Bug> {
private Bug bug;
private final Tabs tabs;
@Autowired
public BugView(@NonNull @Lazy List<BugTab> tabs) {
this.tabs = tabs;
public BugView() {
tabs = new Tabs(Stream.of(TabDef.values()).map(def -> new Tab(def.labelId)).toArray(Tab[]::new));
tabs.addSelectedChangeListener(e -> getUI().ifPresent(ui -> ui.navigate(TabDef.values()[e.getSource().getSelectedIndex()].tabClass, bug.getId())));
setSizeFull();
ParentLayout content = new ParentLayout();
content.setWidthFull();
expand(content);
content.getStyle().set("overflow", "auto");
setRouterRoot(content);
getStyle().set("flex-direction", "column");
removeAll();
add(tabs, content);
}
@Override
protected void enter(@NonNull Pair<Bug, String> parameter) {
Bug bug = parameter.getFirst();
MyTabSheet<Bug> tabSheet = new MyTabSheet<>(bug, getNavigationManager(), tabs);
tabSheet.setSizeFull();
tabSheet.addSelectedTabChangeListener(e -> getNavigationManager().updatePageParameters(bug.getId() + "/" + e.getTabSheet().getSelectedTab().getCaption()));
tabSheet.guessInitialTab(parameter.getSecond());
Panel root = new Panel(tabSheet);
root.setSizeFull();
root.addStyleNames( AcraTheme.NO_BORDER, AcraTheme.NO_BACKGROUND, AcraTheme.NO_PADDING, AcraTheme.PADDING_LEFT, AcraTheme.PADDING_RIGHT);
setCompositionRoot(root);
public void setActiveChild(BugTab<?> child, Bug parameter) {
bug = parameter;
Stream.of(TabDef.values()).filter(def -> def.tabClass.equals(child.getClass())).findAny().ifPresent(def -> tabs.setSelectedIndex(def.ordinal()));
}
@SpringComponent
@UIScope
public static class Provider extends SingleParametrizedViewProvider<Pair<Bug, String>, BugView> {
@NonNull private final DataService dataService;
private enum TabDef {
REPORT(Messages.REPORTS, ReportTab.class),
STACKTRACE(Messages.STACKTRACES, StacktraceTab.class),
STATISTICS(Messages.STATISTICS, StatisticsTab.class),
ADMIN(Messages.ADMIN, AdminTab.class);
private String labelId;
private Class<? extends BugTab<?>> tabClass;
@Autowired
public Provider(@NonNull DataService dataService) {
super(BugView.class);
this.dataService = dataService;
}
@Override
protected String getTitle(Pair<Bug, String> parameter) {
return parameter.getFirst().getTitle();
}
@Override
protected boolean isValidParameter(Pair<Bug, String> parameter) {
return parameter != null;
}
@Override
protected Pair<Bug, String> parseParameter(String parameter) {
String[] parameters = parameter.split(MyNavigator.SEPARATOR);
if (parameters.length > 0) {
Optional<Bug> bug = dataService.findBug(parameters[0]);
if (bug.isPresent()) {
return Pair.of(bug.get(), parameters.length == 1 ? "" : parameters[1]);
}
}
return null;
}
@Override
protected App toApp(Pair<Bug, String> parameter) {
return parameter.getFirst().getApp();
}
@Override
public String getId() {
return "bug";
TabDef(String labelId, Class<? extends BugTab<?>> tabClass) {
this.labelId = labelId;
this.tabClass = tabClass;
}
}
}

View file

@ -1,62 +1,69 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.ui.view.bug.tabs;
import com.faendir.acra.i18n.Messages;
import com.faendir.acra.model.Bug;
import com.faendir.acra.model.Permission;
import com.faendir.acra.ui.annotation.RequiresAppPermission;
import com.faendir.acra.ui.view.base.layout.PanelFlexTab;
import com.faendir.acra.ui.view.bug.tabs.panels.AdminPanel;
import com.vaadin.spring.annotation.SpringComponent;
import com.vaadin.spring.annotation.ViewScope;
import com.faendir.acra.service.DataService;
import com.faendir.acra.ui.base.popup.Popup;
import com.faendir.acra.ui.component.Card;
import com.faendir.acra.ui.component.FlexLayout;
import com.faendir.acra.ui.component.HasSize;
import com.faendir.acra.ui.component.Translatable;
import com.faendir.acra.ui.view.Overview;
import com.faendir.acra.ui.view.bug.BugView;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.textfield.TextArea;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import com.vaadin.flow.spring.annotation.UIScope;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.vaadin.spring.i18n.I18N;
import java.util.List;
/**
* @author lukas
* @since 04.06.18
* @since 18.10.18
*/
@RequiresAppPermission(Permission.Level.ADMIN)
@UIScope
@SpringComponent("bugAdminTab")
@ViewScope
public class AdminTab extends PanelFlexTab<Bug> implements BugTab {
private final I18N i18n;
@Route(value = "admin", layout = BugView.class)
public class AdminTab extends BugTab<FlexLayout> {
@Autowired
public AdminTab(@NonNull List<AdminPanel> panels, I18N i18n) {
super(panels);
this.i18n = i18n;
public AdminTab(DataService dataService) {
super(dataService);
}
@Override
public String getCaption() {
return i18n.get(Messages.ADMIN);
}
void init(Bug bug) {
getContent().removeAll();
getContent().setFlexWrap(FlexLayout.FlexWrap.WRAP);
getContent().setWidthFull();
Translatable<TextArea> title = Translatable.createTextArea(bug.getTitle(), Messages.TITLE);
title.setWidthFull();
Translatable<Button> save = Translatable.createButton(e -> {
bug.setTitle(title.getContent().getValue());
getDataService().store(bug);
}, Messages.SAVE);
save.setWidthFull();
Card propertiesCard = new Card(title, save);
propertiesCard.setHeader(Translatable.createText(Messages.PROPERTIES));
propertiesCard.setWidth(500, HasSize.Unit.PIXEL);
getContent().add(propertiesCard);
getContent().expand(propertiesCard);
@Override
public String getId() {
return "admin";
}
@Override
public int getOrder() {
return 3;
Translatable<Button> unmergeButton = Translatable.createButton(e -> new Popup().setTitle(Messages.UNMERGE_BUG_CONFIRM).addYesNoButtons(p -> {
getDataService().unmergeBug(bug);
UI.getCurrent().navigate(Overview.class);
}, true), Messages.UNMERGE_BUG);
unmergeButton.setWidthFull();
Translatable<Button> deleteButton = Translatable.createButton(e -> new Popup().setTitle(Messages.DELETE_BUG_CONFIRM).addYesNoButtons(popup -> {
getDataService().delete(bug);
UI.getCurrent().navigate(Overview.class);
}, true).show(), Messages.DELETE_BUG);
deleteButton.setWidthFull();
Card dangerCard = new Card(unmergeButton, deleteButton);
dangerCard.setHeader(Translatable.createText(Messages.DANGER_ZONE));
dangerCard.setWidth(500, HasSize.Unit.PIXEL);
getContent().add(dangerCard);
getContent().expand(dangerCard);
}
}

View file

@ -1,29 +1,72 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.ui.view.bug.tabs;
import com.faendir.acra.i18n.Messages;
import com.faendir.acra.model.Bug;
import com.faendir.acra.ui.view.base.layout.ComponentFactory;
import com.faendir.acra.service.DataService;
import com.faendir.acra.ui.base.ActiveChildAware;
import com.faendir.acra.ui.base.HasRoute;
import com.faendir.acra.ui.base.HasSecureIntParameter;
import com.faendir.acra.ui.base.Path;
import com.vaadin.flow.component.AttachEvent;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.Composite;
import com.vaadin.flow.router.BeforeEvent;
import org.springframework.lang.NonNull;
import java.io.Serializable;
import java.util.Optional;
/**
* @author Lukas
* @since 27.03.2018
* @author lukas
* @since 19.11.18
*/
public interface BugTab extends ComponentFactory<Bug>, Serializable {
public abstract class BugTab <T extends Component> extends Composite<T> implements HasSecureIntParameter, HasRoute {
private final DataService dataService;
private Bug bug;
public BugTab(DataService dataService) {
this.dataService = dataService;
}
public DataService getDataService() {
return dataService;
}
@Override
public void setParameterSecure(BeforeEvent event, Integer parameter) {
Optional<Bug> bug = dataService.findBug(parameter);
if (bug.isPresent()) {
this.bug = bug.get();
} else {
event.rerouteToError(IllegalArgumentException.class);
}
}
abstract void init(Bug bug);
@Override
protected void onAttach(AttachEvent attachEvent) {
super.onAttach(attachEvent);
init(this.bug);
Optional<Component> parent = getParent();
while (parent.isPresent()) {
if (parent.get() instanceof ActiveChildAware) {
//noinspection unchecked
((ActiveChildAware<BugTab<?>, Bug>) parent.get()).setActiveChild(this, bug);
break;
}
parent = parent.get().getParent();
}
}
@Override
@NonNull
public Path.Element<?> getPathElement() {
//noinspection unchecked
return new Path.ParametrizedTextElement<>((Class<? extends BugTab<?>>) getClass(), bug.getId(), Messages.ONE_ARG, bug.getTitle());
}
@Override
public Parent<?> getLogicalParent() {
return new ParametrizedParent<>(com.faendir.acra.ui.view.app.tabs.BugTab.class, bug.getApp().getId());
}
}

View file

@ -1,71 +1,38 @@
/*
* (C) Copyright 2018 Lukas Morawietz (https://github.com/F43nd1r)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.ui.view.bug.tabs;
import com.faendir.acra.i18n.Messages;
import com.faendir.acra.model.Bug;
import com.faendir.acra.service.AvatarService;
import com.faendir.acra.service.DataService;
import com.faendir.acra.ui.navigation.NavigationManager;
import com.faendir.acra.ui.view.base.ReportList;
import com.vaadin.spring.annotation.SpringComponent;
import com.vaadin.spring.annotation.ViewScope;
import com.vaadin.ui.Component;
import com.faendir.acra.ui.base.ReportList;
import com.faendir.acra.ui.view.bug.BugView;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import com.vaadin.flow.spring.annotation.UIScope;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.vaadin.spring.i18n.I18N;
/**
* @author Lukas
* @since 21.03.2018
* @author lukas
* @since 19.11.18
*/
@UIScope
@SpringComponent("bugReportTab")
@ViewScope
public class ReportTab implements BugTab {
@NonNull private final DataService dataService;
@NonNull private final AvatarService avatarService;
@NonNull private final I18N i18n;
@Route(value = "report", layout = BugView.class)
public class ReportTab extends BugTab<Div> {
@NonNull
private final AvatarService avatarService;
@Autowired
public ReportTab(@NonNull DataService dataService, @NonNull AvatarService avatarService, @NonNull I18N i18n) {
this.dataService = dataService;
public ReportTab(@NonNull DataService dataService, @NonNull AvatarService avatarService) {
super(dataService);
this.avatarService = avatarService;
this.i18n = i18n;
getContent().setSizeFull();
}
@Override
public Component createContent(@NonNull Bug bug, @NonNull NavigationManager navigationManager) {
Component content = new ReportList(bug.getApp(), navigationManager, avatarService, dataService::delete, dataService.getReportProvider(bug), i18n);
content.setSizeFull();
return content;
}
@Override
public String getCaption() {
return i18n.get(Messages.REPORTS);
}
@Override
public String getId() {
return "report";
}
@Override
public int getOrder() {
return 0;
void init(Bug bug) {
getContent().removeAll();
getContent().add(new ReportList(bug.getApp(), getDataService().getReportProvider(bug), avatarService, getDataService()::delete));
}
}

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