This commit is contained in:
f43nd1r 2018-08-16 16:34:25 +02:00
parent fd7a82ec9f
commit dd89443df2
53 changed files with 1503 additions and 316 deletions

View file

@ -95,6 +95,7 @@ dependencies {
compile "com.querydsl:querydsl-apt:$queryDslVersion:jpa"
//vaadin
compile 'com.vaadin:vaadin-spring-boot-starter'
compile 'org.vaadin.spring.addons:vaadin-spring-addon-i18n:2.0.0.RELEASE'
compile 'com.vaadin:vaadin-push'
compile('org.vaadin.addon:jfreechartwrapper:4.0.0') {
exclude group: 'javax.servlet', module: 'servlet-api'
@ -138,6 +139,15 @@ task generateThemeClasses(type: com.faendir.acra.gradle.ThemeClassGenerator) {
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")
packageName 'com.faendir.acra.i18n'
className 'Messages'
}
compileJava.dependsOn(generateMessageClasses)
test {
testLogging {
events "failed"

View file

@ -21,4 +21,5 @@ repositories {
dependencies {
gradleApi()
compile 'com.squareup:javapoet:1.11.1'
compile 'com.google.guava:guava:26.0-jre'
}

View file

@ -0,0 +1,71 @@
/*
* (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.google.common.base.CaseFormat
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.plugins.ide.idea.IdeaPlugin
import static javax.lang.model.element.Modifier.*
/**
* @author lukas
* @since 15.08.18
*/
@CacheableTask
class I18nClassGenerator extends DefaultTask {
@SkipWhenEmpty
@InputDirectory
File inputDirectory
@OutputDirectory
File outputDirectory
String packageName
String className
I18nClassGenerator() {
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() {
Set<String> keys = new HashSet<>()
for (File file : project.fileTree(inputDirectory)) {
try {
Properties properties = new Properties()
properties.load(file.newReader())
keys.addAll(properties.keys() as Collection<String>)
} catch (ignored){
}
}
TypeSpec.Builder classBuilder = TypeSpec.classBuilder(className).addModifiers(PUBLIC, FINAL)
for (String key : keys) {
classBuilder.addField(FieldSpec.builder(String, CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, key), PUBLIC, STATIC, FINAL).initializer('$S', key).build())
}
JavaFile.builder(packageName, classBuilder.build())
.skipJavaLangImports(true)
.indent(" ")
.build()
.writeTo(outputDirectory)
}
}

View file

@ -0,0 +1,26 @@
/*
* (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,53 @@
/*
* (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

@ -0,0 +1,55 @@
/*
* (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

@ -0,0 +1,55 @@
/*
* (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

@ -0,0 +1,50 @@
/*
* (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

@ -0,0 +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.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

@ -0,0 +1,58 @@
/*
* (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

@ -0,0 +1,51 @@
/*
* (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, captionId, params);
setContentMode(contentMode);
}
public I18nLabel(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

@ -0,0 +1,74 @@
/*
* (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) {
I18nMenuItem item = new I18nMenuItem(icon, null, i18n, Messages.BLANK);
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);
getItems().add(item);
return item;
}
@Override
public void updateMessageStrings(Locale locale) {
setText(i18n.get(captionId, params));
getItems().stream().filter(Translatable.class::isInstance).map(Translatable.class::cast).forEach(translatable -> translatable.updateMessageStrings(locale));
}
}
}

View file

@ -0,0 +1,50 @@
/*
* (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

@ -0,0 +1,50 @@
/*
* (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

@ -0,0 +1,50 @@
/*
* (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

@ -0,0 +1,45 @@
/*
* (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

@ -0,0 +1,55 @@
/*
* (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

@ -15,6 +15,9 @@
*/
package com.faendir.acra.ui;
import com.faendir.acra.i18n.I18nLabel;
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;
@ -35,10 +38,8 @@ import com.vaadin.ui.Alignment;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Label;
import com.vaadin.ui.LoginForm;
import com.vaadin.ui.MenuBar;
import com.vaadin.ui.Notification;
import com.vaadin.ui.Panel;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.themes.AcraTheme;
import com.vaadin.ui.themes.DarkAcraTheme;
@ -53,6 +54,8 @@ 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;
@ -64,17 +67,19 @@ import java.util.Optional;
@Theme(AcraTheme.THEME_NAME)
@Widgetset("com.faendir.acra.AppWidgetset")
@Viewport("width=device-width, initial-scale=1")
public class BackendUI extends UI {
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) {
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);
@ -82,7 +87,7 @@ public class BackendUI extends UI {
}
@Override
protected void init(VaadinRequest request) {
protected void initUI(VaadinRequest request) {
if (isDarkTheme()) {
setTheme(DarkAcraTheme.THEME_NAME);
}
@ -97,14 +102,14 @@ public class BackendUI extends UI {
try {
Authentication token = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username.toLowerCase(), password));
if (!token.getAuthorities().contains(User.Role.USER)) {
throw new InsufficientAuthenticationException("Missing required role");
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("Unknown username/password combination", Notification.Type.ERROR_MESSAGE);
Notification.show(i18n.get(Messages.LOGIN_FAILED), Notification.Type.ERROR_MESSAGE);
}
}
@ -127,21 +132,23 @@ public class BackendUI extends UI {
private void showMain() {
NavigationManager navigationManager = applicationContext.getBean(NavigationManager.class);
MenuBar menuBar = new MenuBar();
MenuBar.MenuItem user = menuBar.addItem("", VaadinIcons.USER, null);
I18nMenuBar menuBar = new I18nMenuBar(i18n);
I18nMenuBar.I18nMenuItem user = menuBar.addItem(VaadinIcons.USER);
user.addItem(SecurityUtils.getUsername()).setEnabled(false);
user.addSeparator();
MenuBar.MenuItem theme = user.addItem("Dark Theme",
e -> getPage().setLocation(UriComponentsBuilder.fromUri(getPage().getLocation()).replaceQueryParam(DARK_THEME, e.isChecked()).build().toUri()));
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("User Manager", e -> navigationManager.cleanNavigateTo(UserManagerView.class));
user.addItem(e -> navigationManager.cleanNavigateTo(UserManagerView.class), Messages.USER_MANAGER);
user.addSeparator();
}
user.addItem("Change Password", e -> navigationManager.cleanNavigateTo(ChangePasswordView.class));
user.addItem("Logout", e -> logout());
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);
@ -150,9 +157,7 @@ public class BackendUI extends UI {
HorizontalLayout footer = new HorizontalLayout();
footer.setDefaultComponentAlignment(Alignment.MIDDLE_CENTER);
Label footerLabel = new Label(
"Acrarium is developed by <a href=https://github.com/F43nd1r>F43nd1r</a>." + " <a href=https://github.com/F43nd1r/acra-backend>Code</a> is licensed under"
+ " <a href=https://github.com/F43nd1r/acra-backend/blob/master/LICENSE>Apache License v2</a>.", ContentMode.HTML);
Label footerLabel = new I18nLabel(ContentMode.HTML, i18n, Messages.FOOTER);
footerLabel.setWidth(100, Unit.PERCENTAGE);
footerLabel.addStyleName(AcraTheme.CENTER_TEXT);
footer.addComponent(footerLabel);

View file

@ -16,6 +16,7 @@
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;
@ -28,6 +29,7 @@ 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;
@ -47,7 +49,7 @@ public class NavigationManager implements Serializable {
@NonNull private final MyNavigator navigator;
@Autowired
public NavigationManager(@NonNull UI ui, @NonNull Panel mainView, @NonNull Path mainPath, @NonNull MyNavigator navigator) {
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);
@ -57,7 +59,7 @@ public class NavigationManager implements Serializable {
SingleViewProvider provider = (SingleViewProvider) hierarchy.getLast().getProvider();
String title = provider.getTitle(provider.getParameters(hierarchy.getLast().getNavState()));
if (hierarchy.size() > 1) {
title += " - Acrarium";
title += " - " + i18n.get(Messages.ACRARIUM);
}
ui.getPage().setTitle(title);
});

View file

@ -16,6 +16,8 @@
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;
@ -24,6 +26,8 @@ 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
@ -32,9 +36,10 @@ import com.vaadin.ui.VerticalLayout;
@SpringComponent
@UIScope
public class ErrorView extends Composite implements View {
public ErrorView() {
@Autowired
public ErrorView(I18N i18n) {
VerticalLayout layout = new VerticalLayout();
Label label = new Label("This page does not exist or you do not have the permission to view it.");
Label label = new I18nLabel(i18n, Messages.ERROR4XX);
layout.addComponent(label);
layout.setComponentAlignment(label, Alignment.MIDDLE_CENTER);
layout.setSizeFull();

View file

@ -15,6 +15,12 @@
*/
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;
@ -36,16 +42,14 @@ 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.CheckBox;
import com.vaadin.ui.Grid;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Label;
import com.vaadin.ui.TextField;
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
@ -55,50 +59,52 @@ import org.vaadin.risto.stepper.IntStepper;
@ViewScope
public class Overview extends BaseView {
@NonNull private final DataService dataService;
@NonNull private final I18N i18n;
private MyGrid<VApp> grid;
@Autowired
public Overview(@NonNull DataService dataService) {
public Overview(@NonNull DataService dataService, @NonNull I18N i18n) {
this.dataService = dataService;
this.i18n = i18n;
}
@Override
public void enter(ViewChangeListener.ViewChangeEvent event) {
grid = new MyGrid<>("Apps", dataService.getAppProvider());
grid = new MyGrid<>(dataService.getAppProvider(), i18n, Messages.APPS);
grid.setResponsive(true);
grid.setSizeToRows();
grid.setSelectionMode(Grid.SelectionMode.NONE);
grid.addColumn(VApp::getName, QApp.app.name, "Name");
grid.addColumn(VApp::getBugCount, QBug.bug.countDistinct(), "Bugs");
grid.addColumn(VApp::getReportCount, QReport.report.count(), "Reports");
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 Button("New App", e -> {
TextField name = new TextField("Name");
new Popup().setTitle("New App").addComponent(name).addCreateButton(popup -> {
popup.clear().addComponent(new ConfigurationLabel(dataService.createNewApp(name.getValue()))).addCloseButton().show();
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 Button("Import from Acralyzer", e -> {
TextField host = new TextField("Host", "localhost");
IntStepper port = new IntStepper("Port");
port.setValue(5984);
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);
CheckBox ssl = new CheckBox("Use ssl", false);
TextField databaseName = new TextField("Database Name", "acra-myapp");
new Popup().setTitle("Import from Acralyzer")
.addValidatedField(ValidatedField.of(host), true)
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 Label(String.format("Import successful for %d of %d reports.", importResult.getSuccessCount(), importResult.getTotalCount())))
.addComponent(new ConfigurationLabel(importResult.getUser()))
.addComponent(new I18nLabel(i18n, Messages.IMPORT_SUCCESS, importResult.getSuccessCount(), importResult.getTotalCount()))
.addComponent(new ConfigurationLabel(importResult.getUser(), i18n))
.addCloseButton()
.show();
grid.getDataProvider().refreshAll();
@ -120,13 +126,16 @@ public class Overview extends BaseView {
@SpringComponent
@UIScope
public static class Provider extends SingleViewProvider<Overview> {
protected Provider() {
@NonNull private final I18N i18n;
protected Provider(@NonNull I18N i18n) {
super(Overview.class);
this.i18n = i18n;
}
@Override
public String getTitle(String parameter) {
return "Acrarium";
return i18n.get(Messages.ACRARIUM);
}
@Override

View file

@ -56,10 +56,9 @@ public class AppView extends ParametrizedBaseView<Pair<App, String>> {
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().getCaption()));
tabSheet.addMiddleClickListener(e -> getNavigationManager().openInNewTab(parameter.getFirst().getId() + "/" + e.getTab().getCaption()));
if (tabSheet.getCaptions().contains(parameter.getSecond())) tabSheet.setInitialTab(parameter.getSecond());
else tabSheet.setFirstTabAsInitialTab();
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);

View file

@ -15,6 +15,7 @@
*/
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;
@ -24,6 +25,7 @@ import com.vaadin.spring.annotation.SpringComponent;
import com.vaadin.spring.annotation.ViewScope;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.vaadin.spring.i18n.I18N;
import java.util.List;
@ -35,14 +37,22 @@ import java.util.List;
@SpringComponent
@ViewScope
public class AdminTab extends PanelFlexTab<App> implements AppTab {
@NonNull private final I18N i18n;
@Autowired
public AdminTab(@NonNull List<AdminPanel> panels) {
public AdminTab(@NonNull List<AdminPanel> panels, @NonNull I18N i18n) {
super(panels);
this.i18n = i18n;
}
@Override
public String getCaption() {
return "Admin";
return i18n.get(Messages.ADMIN);
}
@Override
public String getId() {
return "admin";
}
@Override

View file

@ -13,9 +13,11 @@
* 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;
import com.faendir.acra.model.QBug;
@ -45,6 +47,7 @@ import com.vaadin.ui.renderers.ComponentRenderer;
import com.vaadin.ui.themes.AcraTheme;
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;
@ -58,15 +61,22 @@ import java.util.stream.Collectors;
@ViewScope
public class BugTab implements AppTab {
@NonNull private final DataService dataService;
@NonNull private final I18N i18n;
@Autowired
public BugTab(@NonNull DataService dataService) {
public BugTab(@NonNull DataService dataService, @NonNull I18N i18n) {
this.dataService = dataService;
this.i18n = i18n;
}
@Override
public String getCaption() {
return "Bugs";
return i18n.get(Messages.BUGS);
}
@Override
public String getId() {
return "bug";
}
@Override
@ -77,38 +87,39 @@ public class BugTab implements AppTab {
header.addStyleName(AcraTheme.PADDING_TOP);
layout.addComponent(header);
layout.setComponentAlignment(header, Alignment.MIDDLE_LEFT);
CheckBox hideSolved = new CheckBox("Hide solved", true);
MyGrid<VBug> bugs = new MyGrid<>(null, dataService.getBugProvider(app, hideSolved::getValue));
CheckBox hideSolved = new I18nCheckBox(true, i18n, Messages.HIDE_SOLVED);
MyGrid<VBug> bugs = new MyGrid<>(dataService.getBugProvider(app, hideSolved::getValue));
bugs.setSelectionMode(Grid.SelectionMode.MULTI);
hideSolved.addValueChangeListener(e -> layout.getUI().access(() -> {
bugs.deselectAll();
bugs.getDataProvider().refreshAll();
}));
Button merge = new Button("Merge bugs", e -> {
Button merge = new I18nButton(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().setTitle("Choose title for bug group").addComponent(titles).addCreateButton(p -> {
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));
bugs.deselectAll();
bugs.getDataProvider().refreshAll();
}, true).show();
} else {
Notification.show("Please select at least two bugs", Notification.Type.ERROR_MESSAGE);
Notification.show(i18n.get(Messages.ONLY_ONE_BUG_SELECTED), Notification.Type.ERROR_MESSAGE);
}
});
}, i18n, Messages.MERGE_BUGS);
header.addComponent(merge);
header.addComponent(hideSolved);
header.setComponentAlignment(hideSolved, Alignment.MIDDLE_RIGHT);
bugs.addColumn(VBug::getReportCount,QReport.report.count(), "Reports");
bugs.sort(bugs.addColumn(VBug::getLastReport, new TimeSpanRenderer(), QReport.report.date.max(), "Latest Report"), SortDirection.DESCENDING);
bugs.addColumn(VBug::getHighestVersionCode, QReport.report.stacktrace.version.code.max(), "Latest Version");
bugs.addColumn(VBug::getUserCount, QReport.report.installationId.countDistinct(), "Affected Users");
bugs.addColumn(bug -> bug.getBug().getTitle(), QBug.bug.title, "Title").setExpandRatio(1).setMinimumWidthFromContent(false);
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(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, "Solved");
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();

View file

@ -16,6 +16,7 @@
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;
@ -26,6 +27,7 @@ import com.vaadin.spring.annotation.ViewScope;
import com.vaadin.ui.Component;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.vaadin.spring.i18n.I18N;
/**
* @author Lukas
@ -36,23 +38,30 @@ import org.springframework.lang.NonNull;
public class ReportTab implements AppTab {
@NonNull private final DataService dataService;
@NonNull private final AvatarService avatarService;
@NonNull private final I18N i18n;
@Autowired
public ReportTab(@NonNull DataService dataService, @NonNull AvatarService avatarService) {
public ReportTab(@NonNull DataService dataService, @NonNull AvatarService avatarService, @NonNull I18N i18n) {
this.dataService = dataService;
this.avatarService = avatarService;
this.i18n = i18n;
}
@Override
public Component createContent(@NonNull App app, @NonNull NavigationManager navigationManager) {
Component content = new ReportList(app, navigationManager, avatarService, dataService::delete, dataService.getReportProvider(app));
Component content = new ReportList(app, navigationManager, avatarService, dataService::delete, dataService.getReportProvider(app), i18n);
content.setSizeFull();
return content;
}
@Override
public String getCaption() {
return ReportList.CAPTION;
return i18n.get(Messages.REPORTS);
}
@Override
public String getId() {
return "report";
}
@Override

View file

@ -16,6 +16,7 @@
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.QReport;
import com.faendir.acra.service.DataService;
@ -28,6 +29,7 @@ import com.vaadin.ui.Panel;
import com.vaadin.ui.themes.AcraTheme;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.vaadin.spring.i18n.I18N;
/**
* @author Lukas
@ -37,15 +39,17 @@ import org.springframework.lang.NonNull;
@ViewScope
public class StatisticsTab implements AppTab {
@NonNull private final DataService dataService;
@NonNull private final I18N i18n;
@Autowired
public StatisticsTab(@NonNull DataService dataService) {
public StatisticsTab(@NonNull DataService dataService, @NonNull I18N i18n) {
this.dataService = dataService;
this.i18n = i18n;
}
@Override
public Component createContent(@NonNull App app, @NonNull NavigationManager navigationManager) {
Panel root = new Panel(new Statistics(QReport.report.stacktrace.bug.app.eq(app), dataService));
Panel root = new Panel(new Statistics(QReport.report.stacktrace.bug.app.eq(app), dataService, i18n));
root.setSizeFull();
root.addStyleNames(AcraTheme.NO_BACKGROUND, AcraTheme.NO_BORDER);
return root;
@ -53,7 +57,12 @@ public class StatisticsTab implements AppTab {
@Override
public String getCaption() {
return "Statistics";
return i18n.get(Messages.STATISTICS);
}
@Override
public String getId() {
return "statistics";
}
@Override

View file

@ -15,6 +15,10 @@
*/
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;
@ -33,13 +37,12 @@ import com.vaadin.ui.Button;
import com.vaadin.ui.ComboBox;
import com.vaadin.ui.Component;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Label;
import com.vaadin.ui.Slider;
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
@ -49,31 +52,29 @@ import org.vaadin.risto.stepper.IntStepper;
@ViewScope
public class DangerPanel implements AdminPanel {
@NonNull private final DataService dataService;
@NonNull private final I18N i18n;
@Autowired
public DangerPanel(@NonNull DataService dataService) {
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 Button("Create new ACRA Configuration",
e -> new Popup().setTitle("Confirm")
.addComponent(new Label("Are you sure you want to create a new ACRA configuration?<br>The existing configuration will be invalidated", ContentMode.HTML))
.addYesNoButtons(popup -> popup.clear().addComponent(new ConfigurationLabel(dataService.recreateReporterUser(app))).addCloseButton().show())
.show());
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 Button("Configure bug matching", e -> {
Button matchingButton = new I18nButton(e -> {
App.Configuration configuration = app.getConfiguration();
Slider score = new Slider("Minimum score", 0, 100);
I18nSlider score = new I18nSlider(0, 100, i18n, Messages.MIN_SCORE);
score.setValue((double) configuration.getMinScore());
new Popup().addValidatedField(ValidatedField.of(score), true)
.addComponent(new Label("Are you sure you want to save this configuration? " + "All bugs will be checked for merging, which may take some time. "
+ "Note that a higher minimum score does not unmerge already merged bugs."))
.addYesNoButtons(p -> dataService.changeConfiguration(app,
new App.Configuration(score.getValue().intValue())), true)
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);
@ -83,7 +84,10 @@ public class DangerPanel implements AdminPanel {
purgeAge.setDefaultComponentAlignment(Alignment.MIDDLE_CENTER);
purgeAge.setWidth(100, Sizeable.Unit.PERCENTAGE);
purgeAge.addStyleName(AcraTheme.NO_MARGIN);
purgeAge.addComponents(new Button("Purge", e -> dataService.deleteReportsOlderThanDays(app, age.getValue())), new Label(" Reports older than "), age, new Label(" Days"));
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(QReport.report.stacktrace.bug.app.eq(app), QReport.report.stacktrace.version.code));
versionBox.setEmptySelectionAllowed(false);
@ -92,17 +96,16 @@ public class DangerPanel implements AdminPanel {
purgeVersion.setWidth(100, Sizeable.Unit.PERCENTAGE);
purgeVersion.setDefaultComponentAlignment(Alignment.MIDDLE_CENTER);
purgeVersion.addStyleName(AcraTheme.NO_MARGIN);
purgeVersion.addComponents(new Button("Purge", e -> {
purgeVersion.addComponents(new I18nButton(e -> {
if (versionBox.getValue() != null) {
dataService.deleteReportsBeforeVersion(app, versionBox.getValue());
}
}), new Label(" Reports before Version "), versionBox);
}, i18n, Messages.PURGE), new I18nLabel(i18n, Messages.REPORTS_BEFORE_VERSION), versionBox);
purgeVersion.setExpandRatio(versionBox, 1);
Button deleteButton = new Button("Delete App",
e -> new Popup().setTitle("Confirm").addComponent(new Label("Are you sure you want to delete this app and all its associated content?")).addYesNoButtons(popup -> {
dataService.delete(app);
navigationManager.navigateBack();
}, true).show());
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();
@ -112,7 +115,12 @@ public class DangerPanel implements AdminPanel {
@Override
public String getCaption() {
return "Danger Zone";
return i18n.get(Messages.DANGER_ZONE);
}
@Override
public String getId() {
return "danger-zone";
}
@Override

View file

@ -16,6 +16,9 @@
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;
@ -33,6 +36,7 @@ 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
@ -42,22 +46,24 @@ import org.springframework.web.util.UriComponentsBuilder;
@ViewScope
public class ExportPanel implements AdminPanel {
@NonNull private final DataService dataService;
@NonNull private final I18N i18n;
@Autowired
public ExportPanel(@NonNull DataService dataService) {
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 ComboBox<>("By Email Address", dataService.getFromReports(QReport.report.stacktrace.bug.app.eq(app), QReport.report.userEmail));
ComboBox<String> mailBox = new I18nComboBox<>(dataService.getFromReports(QReport.report.stacktrace.bug.app.eq(app), QReport.report.userEmail), i18n, Messages.BY_MAIL);
mailBox.setEmptySelectionAllowed(true);
mailBox.setSizeFull();
ComboBox<String> idBox = new ComboBox<>("By Installation ID", dataService.getFromReports(QReport.report.stacktrace.bug.app.eq(app), QReport.report.installationId));
ComboBox<String> idBox = new I18nComboBox<>(dataService.getFromReports(QReport.report.stacktrace.bug.app.eq(app), QReport.report.installationId), i18n, Messages.BY_ID);
idBox.setEmptySelectionAllowed(true);
idBox.setSizeFull();
Button download = new Button("Download", e -> {
Button download = new I18nButton(e -> {
if (idBox.getValue() == null && mailBox.getValue() == null) {
Notification.show("Nothing selected", Notification.Type.WARNING_MESSAGE);
Notification.show(i18n.get(Messages.NOTHING_SELECTED), Notification.Type.WARNING_MESSAGE);
} else {
Page page = UI.getCurrent().getPage();
page.open(UriComponentsBuilder.fromUri(page.getLocation())
@ -69,14 +75,19 @@ public class ExportPanel implements AdminPanel {
.build()
.toUriString(), null);
}
});
}, i18n, Messages.DOWNLOAD);
download.setSizeFull();
return new VerticalLayout(mailBox, idBox, download);
}
@Override
public String getCaption() {
return "Export";
return i18n.get(Messages.EXPORT);
}
@Override
public String getId() {
return "export";
}
@Override

View file

@ -16,6 +16,10 @@
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;
@ -31,9 +35,7 @@ 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.Button;
import com.vaadin.ui.Component;
import com.vaadin.ui.Label;
import com.vaadin.ui.ProgressBar;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.renderers.ButtonRenderer;
@ -41,6 +43,7 @@ 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
@ -50,20 +53,22 @@ import org.vaadin.risto.stepper.IntStepper;
@ViewScope
public class ProguardPanel implements AdminPanel{
@NonNull private final DataService dataService;
@NonNull private final I18N i18n;
@Autowired
public ProguardPanel(@NonNull DataService dataService) {
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<>(null, dataService.getMappingProvider(app));
MyGrid<ProguardMapping> grid = new MyGrid<>(dataService.getMappingProvider(app));
grid.setSizeToRows();
grid.sort(grid.addColumn(ProguardMapping::getVersionCode, QProguardMapping.proguardMapping.versionCode, "Version"), SortDirection.ASCENDING);
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().setTitle("Confirm")
.addComponent(new Label("Are you sure you want to delete the mapping for version " + e.getItem().getVersionCode() + "?"))
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();
@ -76,30 +81,34 @@ public class ProguardPanel implements AdminPanel{
layout.addComponent(grid);
layout.addStyleName(AcraTheme.NO_PADDING);
if (SecurityUtils.hasPermission(app, Permission.Level.EDIT)) {
layout.addComponent(new Button("Add File", e -> {
IntStepper version = new IntStepper("Version code");
version.setValue(dataService.getMaximumMappingVersion(app).map(i -> i + 1).orElse(1));
InMemoryUpload upload = new InMemoryUpload("Mapping file:");
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().setTitle("New Mapping Configuration")
new Popup(i18n, Messages.NEW_MAPPING)
.addComponent(version)
.addValidatedField(ValidatedField.of(upload, () -> upload, consumer -> upload.addFinishedListener(event -> consumer.accept(upload)))
.addValidator(InMemoryUpload::isUploaded, "Upload failed"))
.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 "De-Obfuscation";
return i18n.get(Messages.DE_OBFUSCATION);
}
@Override
public String getId() {
return "proguard";
}
@Override

View file

@ -16,23 +16,21 @@
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 com.vaadin.ui.Label;
import org.vaadin.spring.i18n.I18N;
/**
* @author Lukas
* @since 18.12.2017
*/
public class ConfigurationLabel extends Label {
public ConfigurationLabel(PlainTextUser user) {
super(String.format("Take note of the following ACRA configuration. It cannot be viewed later:<br><code>"
+ "@AcraCore(reportFormat = StringFormat.JSON)<br>"
+ "@AcraHttpSender(uri = \"%s%s\",<br>"
+ "basicAuthLogin = \"%s\",<br>"
+ "basicAuthPassword = \"%s\",<br>"
+ "httpMethod = HttpSender.Method.POST)<br></code>", Utils.getUrlWithFragment(null), RestReportInterface.REPORT_PATH, user.getUsername(), user.getPlaintextPassword()), ContentMode.HTML);
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

@ -16,26 +16,36 @@
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 {
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(String caption) {
public InMemoryUpload(I18N i18n, String captionId, Object... params) {
super();
this.i18n = i18n;
this.captionId = captionId;
this.params = params;
outputStream = new ByteArrayOutputStream();
finished = false;
setCaption(caption);
setReceiver((filename, mimeType) -> outputStream);
addSucceededListener(event -> finished = true);
addFailedListener(event -> finished = false);
updateMessageStrings(i18n.getLocale());
}
public boolean isUploaded() {
@ -45,4 +55,14 @@ public class InMemoryUpload extends Upload {
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

@ -16,6 +16,8 @@
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;
@ -30,10 +32,10 @@ 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.Label;
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;
@ -42,22 +44,20 @@ import java.util.function.Consumer;
* @since 14.05.2017
*/
public class ReportList extends MyGrid<Report> {
public static final String CAPTION = "Reports";
public ReportList(@NonNull App app, @NonNull NavigationManager navigationManager, @NonNull AvatarService avatarService, @NonNull Consumer<Report> reportDeleter, @NonNull QueryDslDataProvider<Report> reportProvider) {
super(CAPTION, reportProvider);
setId(CAPTION);
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, "User");
sort(addColumn(Report::getDate, new TimeSpanRenderer(), QReport.report.date, "Date"), SortDirection.DESCENDING);
addColumn(report -> report.getStacktrace().getVersion().getCode(), QReport.report.stacktrace.version.code, "App Version");
addColumn(Report::getAndroidVersion, QReport.report.androidVersion, "Android Version");
addColumn(Report::getPhoneModel, QReport.report.phoneModel, "Device");
addColumn(report -> report.getStacktrace().getStacktrace().split("\n", 2)[0], QReport.report.stacktrace.stacktrace, "Stacktrace").setExpandRatio(1)
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().setTitle("Confirm")
.addComponent(new Label("Are you sure you want to delete this report?"))
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();

View file

@ -29,4 +29,6 @@ public interface ComponentFactory<T> extends Ordered {
Component createContent(@NonNull T t, @NonNull NavigationManager navigationManager);
String getCaption();
String getId();
}

View file

@ -13,7 +13,6 @@
* 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;
@ -30,10 +29,16 @@ 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.Collection;
import java.util.EventObject;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
@ -41,37 +46,58 @@ import java.util.function.Function;
* @author Lukas
* @since 14.05.2017
*/
public class MyGrid<T> extends Composite {
public class MyGrid<T> extends Composite implements Translatable {
private final I18N i18n;
private final String captionId;
private final Object[] params;
private final Map<Grid.Column<T, ?>, Pair<String, Object[]>> columnCaptions;
private final ExposingGrid<T> grid;
private final QueryDslDataProvider<T> dataProvider;
public MyGrid(String caption, QueryDslDataProvider<T> dataProvider) {
public MyGrid(QueryDslDataProvider<T> dataProvider) {
this(dataProvider, null, null);
}
public MyGrid(QueryDslDataProvider<T> dataProvider, I18N i18n, String captionId, Object... params) {
this.dataProvider = dataProvider;
grid = new ExposingGrid<>(caption, dataProvider);
grid = new ExposingGrid<>(dataProvider);
setCompositionRoot(grid);
setSizeFull();
MiddleClickExtension.extend(this);
this.i18n = i18n;
this.captionId = captionId;
columnCaptions = new HashMap<>();
this.params = params;
if (i18n != null) {
updateMessageStrings(i18n.getLocale());
}
}
@NonNull
public <R> Grid.Column<T, R> addColumn(@NonNull ValueProvider<T, R> valueProvider, @NonNull String caption) {
return addColumn(valueProvider, new TextRenderer(), caption);
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 caption) {
return grid.addColumn(valueProvider, renderer).setCaption(caption).setSortable(false);
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 caption) {
return addColumn(valueProvider, new TextRenderer(), sort, caption);
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 caption) {
return grid.addColumn(valueProvider, renderer).setId(dataProvider.addSortable(sort)).setCaption(caption);
@NonNull Expression<? extends Comparable> sort, @NonNull String captionId, Object... params) {
Grid.Column<T, R> column = grid.addColumn(valueProvider, renderer).setId(dataProvider.addSortable(sort)).setCaption(i18n.get(captionId, params));
columnCaptions.put(column, Pair.of(captionId, params));
return column;
}
@NonNull
@ -116,8 +142,7 @@ public class MyGrid<T> extends Composite {
grid.deselectAll();
}
public void addOnClickNavigation(@NonNull NavigationManager navigationManager, Class<? extends BaseView> namedView,
Function<Grid.ItemClick<T>, String> parameterGetter) {
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);
@ -128,6 +153,12 @@ public class MyGrid<T> extends Composite {
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())));
}
public static class MiddleClickExtension<T> extends Grid.AbstractGridExtension<T> {
private MiddleClickExtension(MyGrid<T> myGrid) {
ExposingGrid<T> grid = myGrid.grid;
@ -161,8 +192,8 @@ public class MyGrid<T> extends Composite {
}
private static class ExposingGrid<T> extends Grid<T> {
ExposingGrid(String caption, DataProvider<T, ?> dataProvider) {
super(caption, dataProvider);
ExposingGrid(DataProvider<T, ?> dataProvider) {
super(dataProvider);
}
@Override

View file

@ -29,10 +29,6 @@ import org.springframework.lang.NonNull;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
/**
@ -52,27 +48,14 @@ public class MyTabSheet<T> extends TabSheet {
MiddleClickExtension.extend(this);
}
public List<String> getCaptions() {
return StreamSupport.stream(Spliterators.spliterator(iterator(), getComponentCount(), Spliterator.ORDERED), false).map(Component::getCaption).collect(Collectors.toList());
}
public void addTab(ComponentFactory<T> tab) {
TabWrapper<T> wrapper = new TabWrapper<>(t, navigationManager, tab);
addComponent(wrapper);
addSelectedTabChangeListener(wrapper);
}
public void setInitialTab(String caption) {
StreamSupport.stream(spliterator(), false).filter(c -> c.getCaption().equals(caption)).findAny().ifPresent(component -> {
if (getSelectedTab() == component && component instanceof SelectedTabChangeListener) {
((SelectedTabChangeListener) component).selectedTabChange(new SelectedTabChangeEvent(this, true));
}
setSelectedTab(component);
});
}
public void setFirstTabAsInitialTab() {
Component component = getTab(0).getComponent();
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));
}

View file

@ -16,6 +16,8 @@
package com.faendir.acra.ui.view.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;
@ -25,10 +27,13 @@ import com.vaadin.ui.Window;
import com.vaadin.ui.themes.AcraTheme;
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;
@ -36,21 +41,22 @@ import java.util.function.Consumer;
* @author Lukas
* @since 19.12.2017
*/
public class Popup extends Window {
public class Popup extends Window implements Translatable {
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;
public Popup() {
public Popup(I18N i18n, String captionId, Object... params) {
this.i18n = i18n;
this.captionId = captionId;
this.params = params;
components = new ArrayList<>();
fields = new HashMap<>();
buttons = new ArrayList<>();
}
@NonNull
public Popup setTitle(@NonNull String title) {
super.setCaption(title);
return this;
updateMessageStrings(i18n.getLocale());
}
@NonNull
@ -60,18 +66,18 @@ public class Popup extends Window {
@NonNull
public Popup addCreateButton(@NonNull Consumer<Popup> onCreateAction, boolean closeAfter) {
buttons.add(new Button("Create", event -> {
buttons.add(new I18nButton(event -> {
onCreateAction.accept(this);
if (closeAfter) {
close();
}
}));
}, i18n, Messages.CREATE));
return this;
}
@NonNull
public Popup addCloseButton() {
buttons.add(new Button("Close", event -> close()));
buttons.add(new I18nButton(event -> close(), i18n, Messages.CLOSE));
return this;
}
@ -82,13 +88,13 @@ public class Popup extends Window {
@NonNull
public Popup addYesNoButtons(@NonNull Consumer<Popup> onYesAction, boolean closeAfter) {
buttons.add(new Button("Yes", event -> {
buttons.add(new I18nButton(event -> {
onYesAction.accept(this);
if (closeAfter) {
close();
}
}));
buttons.add(new Button("No", event -> close()));
}, i18n, Messages.YES));
buttons.add(new I18nButton(event -> close(), i18n, Messages.NO));
return this;
}
@ -147,4 +153,9 @@ public class Popup extends Window {
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));
}
}

View file

@ -16,10 +16,12 @@
package com.faendir.acra.ui.view.base.popup;
import com.faendir.acra.i18n.HasI18n;
import com.vaadin.server.UserError;
import com.vaadin.ui.AbstractComponent;
import com.vaadin.ui.AbstractField;
import org.springframework.lang.NonNull;
import org.vaadin.spring.i18n.I18N;
import java.util.ArrayList;
import java.util.HashMap;
@ -36,29 +38,31 @@ import java.util.function.Supplier;
public class ValidatedField<V, T extends AbstractComponent> {
private final T field;
private final Supplier<V> valueSupplier;
private final I18N i18n;
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) {
private ValidatedField(T field, Supplier<V> valueSupplier, Consumer<Consumer<V>> listenerRegistration, I18N i18n) {
this.field = field;
this.valueSupplier = valueSupplier;
this.i18n = i18n;
this.validators = new HashMap<>();
this.listeners = new ArrayList<>();
this.valid = false;
listenerRegistration.accept(this::validate);
}
public static <V, T extends AbstractField<V>> ValidatedField<V, T> of(T field) {
return new ValidatedField<>(field, field::getValue, vConsumer -> field.addValueChangeListener(event -> vConsumer.accept(event.getValue())));
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 AbstractComponent> ValidatedField<V, T> of(T field, Supplier<V> valueSupplier, Consumer<Consumer<V>> listenerRegistration) {
return new ValidatedField<>(field, valueSupplier, listenerRegistration);
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 ValidatedField<V, T> addValidator(Function<V, Boolean> validator, String errorMessage) {
validators.put(validator, errorMessage);
public ValidatedField<V, T> addValidator(Function<V, Boolean> validator, String errorMessageId) {
validators.put(validator, errorMessageId);
return this;
}
@ -76,7 +80,7 @@ public class ValidatedField<V, T extends AbstractComponent> {
field.setComponentError(null);
return true;
} else {
field.setComponentError(new UserError(entry.getValue()));
field.setComponentError(new UserError(i18n.get(entry.getValue())));
return false;
}
});

View file

@ -13,9 +13,9 @@
* 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;
@ -23,26 +23,41 @@ import com.vaadin.ui.themes.AcraTheme;
import org.jfree.chart.JFreeChart;
import org.springframework.lang.NonNull;
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 {
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(@NonNull String caption) {
Chart(I18N i18n, String captionId, Object... params) {
this.i18n = i18n;
this.captionId = captionId;
this.params = params;
panel = new Panel();
panel.setCaption(caption);
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) {
JFreeChart chart = createChart(map);
chart = createChart(map);
JFreeChartWrapper content = new JFreeChartWrapper(chart);
content.setWidth(100, Unit.PERCENTAGE);
content.setHeight(100, Unit.PERCENTAGE);
@ -54,4 +69,13 @@ abstract class Chart<T> extends Composite {
}
protected abstract JFreeChart createChart(@NonNull Map<T, Long> map);
protected JFreeChart getChart() {
return chart;
}
@Override
public I18N getI18n() {
return i18n;
}
}

View file

@ -24,6 +24,7 @@ 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.util.List;
@ -34,8 +35,8 @@ import java.util.Map;
* @since 01.06.18
*/
class PieChart extends Chart<String> {
PieChart(@NonNull String caption) {
super(caption);
PieChart(I18N i18n, String captionId, Object... params) {
super(i18n, captionId, params);
}
@Override

View file

@ -15,6 +15,7 @@
*/
package com.faendir.acra.ui.view.base.statistics;
import com.faendir.acra.i18n.I18nCheckBox;
import com.faendir.acra.service.DataService;
import com.querydsl.core.types.Expression;
import com.querydsl.core.types.dsl.BooleanExpression;
@ -28,6 +29,7 @@ import com.vaadin.ui.ComboBox;
import com.vaadin.ui.Component;
import com.vaadin.ui.ComponentContainer;
import org.vaadin.risto.stepper.IntStepper;
import org.vaadin.spring.i18n.I18N;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
@ -46,9 +48,9 @@ class Property<F, C extends Component & HasValue<F>, T> {
private final Chart<T> chart;
private final Expression<T> select;
private Property(String filterText, C filterComponent, Function<F, BooleanExpression> filter, Chart<T> chart, DataService dataService, Expression<T> select) {
private Property(C filterComponent, Function<F, BooleanExpression> filter, Chart<T> chart, DataService dataService, Expression<T> select, I18N i18n, String filterTextId) {
this.dataService = dataService;
this.checkBox = new CheckBox(filterText);
this.checkBox = new I18nCheckBox(i18n, filterTextId);
this.filterComponent = filterComponent;
this.filter = filter;
this.chart = chart;
@ -85,22 +87,23 @@ class Property<F, C extends Component & HasValue<F>, T> {
this.expression = expression;
}
Property<?, ?, ?> createStringProperty(String filterText, String chartTitle, ComparableExpressionBase<String> stringExpression) {
Property<?, ?, ?> createStringProperty(ComparableExpressionBase<String> stringExpression, I18N i18n, String filterTextId, String chartTitleId) {
ComboBox<String> comboBox = new ComboBox<>(null, dataService.getFromReports(expression, stringExpression));
comboBox.setEmptySelectionAllowed(false);
return new Property<>(filterText, comboBox, stringExpression::eq, new PieChart(chartTitle), dataService, stringExpression);
return new Property<>(comboBox, stringExpression::eq, new PieChart(i18n, chartTitleId), dataService, stringExpression, i18n, filterTextId);
}
Property<?, ?, ?> createAgeProperty(String filterText, String chartTitle, DateTimePath<ZonedDateTime> dateTimeExpression) {
Property<?, ?, ?> createAgeProperty(DateTimePath<ZonedDateTime> dateTimeExpression, I18N i18n, String filterTextId, String chartTitleId) {
IntStepper stepper = new IntStepper();
stepper.setValue(30);
stepper.setMinValue(1);
return new Property<>(filterText,
stepper,
return new Property<>(stepper,
days -> dateTimeExpression.after(ZonedDateTime.now().minus(days, ChronoUnit.DAYS)),
new TimeChart(chartTitle),
new TimeChart(i18n, chartTitleId),
dataService,
SQLExpressions.date(Date.class, dateTimeExpression));
SQLExpressions.date(Date.class, dateTimeExpression),
i18n,
filterTextId);
}
}
}

View file

@ -16,6 +16,9 @@
package com.faendir.acra.ui.view.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.QReport;
import com.faendir.acra.service.DataService;
import com.faendir.acra.ui.view.base.layout.FlexLayout;
@ -27,6 +30,7 @@ import com.vaadin.ui.GridLayout;
import com.vaadin.ui.Panel;
import com.vaadin.ui.themes.AcraTheme;
import org.vaadin.risto.stepper.IntStepper;
import org.vaadin.spring.i18n.I18N;
import java.awt.*;
import java.util.ArrayList;
@ -43,7 +47,7 @@ public class Statistics extends Composite {
private final BooleanExpression baseExpression;
private final List<Property<?, ?, ?>> properties;
public Statistics(BooleanExpression baseExpression, DataService dataService) {
public Statistics(BooleanExpression baseExpression, DataService dataService, I18N i18n) {
this.baseExpression = baseExpression;
properties = new ArrayList<>();
GridLayout filterLayout = new GridLayout(2, 1);
@ -57,21 +61,20 @@ public class Statistics extends Composite {
dayStepper.setValue(30);
dayStepper.setMinValue(1);
Property.Factory factory = new Property.Factory(dataService, baseExpression);
properties.add(factory.createAgeProperty("Last X days", "Reports over time", QReport.report.date));
properties.add(factory.createStringProperty("Android Version", "Reports per Android Version", QReport.report.androidVersion));
properties.add(factory.createStringProperty("App Version", "Reports per App Version", QReport.report.stacktrace.version.name));
properties.add(factory.createStringProperty("Phone Model", "Reports per Phone Model", QReport.report.phoneModel));
properties.add(factory.createStringProperty("Phone Brand", "Reports per Brand", QReport.report.brand));
properties.add(factory.createAgeProperty(QReport.report.date, i18n, Messages.LAST_X_DAYS, Messages.REPORTS_OVER_TIME));
properties.add(factory.createStringProperty(QReport.report.androidVersion, i18n, Messages.ANDROID_VERSION, Messages.REPORTS_PER_ANDROID_VERSION));
properties.add(factory.createStringProperty(QReport.report.stacktrace.version.name, i18n, Messages.APP_VERSION, Messages.REPORTS_PER_APP_VERSION));
properties.add(factory.createStringProperty(QReport.report.phoneModel, i18n, Messages.PHONE_MODEL, Messages.REPORTS_PER_PHONE_MODEL));
properties.add(factory.createStringProperty(QReport.report.brand, i18n, Messages.PHONE_BRAND, Messages.REPORTS_PER_BRAND));
Panel filterPanel = new Panel(filterLayout);
Panel filterPanel = new I18nPanel(filterLayout, i18n, Messages.FILTER);
filterPanel.addStyleName(AcraTheme.NO_BACKGROUND);
filterPanel.setCaption("Filter");
FlexLayout layout = new FlexLayout(filterPanel);
layout.setWidth(100, Unit.PERCENTAGE);
properties.forEach(property -> property.addTo(filterLayout, layout));
Button applyButton = new Button("Apply", e -> update());
Button applyButton = new I18nButton(e -> update(), i18n, Messages.APPLY);
applyButton.setWidth(100, Unit.PERCENTAGE);
filterLayout.space();
filterLayout.addComponent(applyButton);

View file

@ -13,9 +13,9 @@
* 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.Messages;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberTickUnitSource;
@ -28,9 +28,11 @@ 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;
/**
@ -38,16 +40,24 @@ import java.util.Map;
* @since 01.06.18
*/
class TimeChart extends Chart<Date> {
TimeChart(@NonNull String caption) {
super(caption);
TimeChart(I18N i18n, String captionId, Object... params) {
super(i18n, captionId, params);
}
@Override
public JFreeChart createChart(@NonNull Map<Date, Long> map) {
TimeSeries series = new TimeSeries("Date");
TimeSeries series = new TimeSeries(getI18n().get(Messages.DATE));
series.add(new Day(new Date()), 0);
map.forEach((date, count) -> series.addOrUpdate(new Day(date), count));
JFreeChart chart = ChartFactory.createXYBarChart("", "Date", true, "Reports", new TimeSeriesCollection(series), PlotOrientation.VERTICAL, false, false, false);
JFreeChart chart = ChartFactory.createXYBarChart("",
getI18n().get(Messages.DATE),
true,
getI18n().get(Messages.REPORTS),
new TimeSeriesCollection(series),
PlotOrientation.VERTICAL,
false,
false,
false);
chart.setBackgroundPaint(null);
XYPlot plot = chart.getXYPlot();
plot.getRangeAxis().setStandardTickUnits(new NumberTickUnitSource(true));
@ -73,4 +83,13 @@ class TimeChart extends Chart<Date> {
barRenderer.setMargin(0.2);
return chart;
}
@Override
public void updateMessageStrings(Locale locale) {
super.updateMessageStrings(locale);
XYPlot plot = getChart().getXYPlot();
plot.getDomainAxis().setLabel(getI18n().get(Messages.DATE));
plot.getRangeAxis().setLabel(getI18n().get(Messages.REPORTS));
markAsDirtyRecursive();
}
}

View file

@ -60,8 +60,7 @@ public class BugView extends ParametrizedBaseView<Pair<Bug, String>> {
MyTabSheet<Bug> tabSheet = new MyTabSheet<>(bug, getNavigationManager(), tabs);
tabSheet.setSizeFull();
tabSheet.addSelectedTabChangeListener(e -> getNavigationManager().updatePageParameters(bug.getId() + "/" + e.getTabSheet().getSelectedTab().getCaption()));
if (tabSheet.getCaptions().contains(parameter.getSecond())) tabSheet.setInitialTab(parameter.getSecond());
else tabSheet.setFirstTabAsInitialTab();
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);

View file

@ -15,6 +15,7 @@
*/
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;
@ -24,6 +25,7 @@ import com.vaadin.spring.annotation.SpringComponent;
import com.vaadin.spring.annotation.ViewScope;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.vaadin.spring.i18n.I18N;
import java.util.List;
@ -35,15 +37,22 @@ import java.util.List;
@SpringComponent("bugAdminTab")
@ViewScope
public class AdminTab extends PanelFlexTab<Bug> implements BugTab {
private final I18N i18n;
@Autowired
public AdminTab(@NonNull List<AdminPanel> panels) {
public AdminTab(@NonNull List<AdminPanel> panels, I18N i18n) {
super(panels);
this.i18n = i18n;
}
@Override
public String getCaption() {
return "Admin";
return i18n.get(Messages.ADMIN);
}
@Override
public String getId() {
return "admin";
}
@Override

View file

@ -16,6 +16,7 @@
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;
@ -26,6 +27,7 @@ import com.vaadin.spring.annotation.ViewScope;
import com.vaadin.ui.Component;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.vaadin.spring.i18n.I18N;
/**
* @author Lukas
@ -36,23 +38,30 @@ import org.springframework.lang.NonNull;
public class ReportTab implements BugTab {
@NonNull private final DataService dataService;
@NonNull private final AvatarService avatarService;
@NonNull private final I18N i18n;
@Autowired
public ReportTab(@NonNull DataService dataService, @NonNull AvatarService avatarService) {
public ReportTab(@NonNull DataService dataService, @NonNull AvatarService avatarService, @NonNull I18N i18n) {
this.dataService = dataService;
this.avatarService = avatarService;
this.i18n = i18n;
}
@Override
public Component createContent(@NonNull Bug bug, @NonNull NavigationManager navigationManager) {
Component content = new ReportList(bug.getApp(), navigationManager, avatarService, dataService::delete, dataService.getReportProvider(bug));
Component content = new ReportList(bug.getApp(), navigationManager, avatarService, dataService::delete, dataService.getReportProvider(bug), i18n);
content.setSizeFull();
return content;
}
@Override
public String getCaption() {
return ReportList.CAPTION;
return i18n.get(Messages.REPORTS);
}
@Override
public String getId() {
return "report";
}
@Override

View file

@ -15,6 +15,8 @@
*/
package com.faendir.acra.ui.view.bug.tabs;
import com.faendir.acra.i18n.I18nAccordion;
import com.faendir.acra.i18n.Messages;
import com.faendir.acra.model.Bug;
import com.faendir.acra.model.ProguardMapping;
import com.faendir.acra.model.Stacktrace;
@ -24,11 +26,11 @@ import com.faendir.acra.util.Utils;
import com.vaadin.shared.ui.ContentMode;
import com.vaadin.spring.annotation.SpringComponent;
import com.vaadin.spring.annotation.ViewScope;
import com.vaadin.ui.Accordion;
import com.vaadin.ui.Component;
import com.vaadin.ui.Label;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.vaadin.spring.i18n.I18N;
import java.util.Optional;
@ -40,22 +42,24 @@ import java.util.Optional;
@ViewScope
public class StackTraceTab implements BugTab {
@NonNull private final DataService dataService;
@NonNull private final I18N i18n;
@Autowired
public StackTraceTab(@NonNull DataService dataService) {
public StackTraceTab(@NonNull DataService dataService, @NonNull I18N i18n) {
this.dataService = dataService;
this.i18n = i18n;
}
@Override
public Component createContent(@NonNull Bug bug, @NonNull NavigationManager navigationManager) {
Accordion accordion = new Accordion();
I18nAccordion accordion = new I18nAccordion(i18n);
for (Stacktrace stacktrace : dataService.getStacktraces(bug)) {
Optional<ProguardMapping> mapping = dataService.findMapping(bug.getApp(), stacktrace.getVersion().getCode());
String trace = stacktrace.getStacktrace();
if (mapping.isPresent()) {
trace = Utils.retrace(trace, mapping.get().getMappings());
}
accordion.addTab(new Label(trace, ContentMode.PREFORMATTED)).setCaption("Version \"" + stacktrace.getVersion().getName() + "\": " + trace.split("\n", 2)[0]);
accordion.addTab(new Label(trace, ContentMode.PREFORMATTED), Messages.STACKTRACE_TITLE, stacktrace.getVersion().getName(), trace.split("\n", 2)[0]);
}
accordion.setSizeFull();
return accordion;
@ -63,7 +67,12 @@ public class StackTraceTab implements BugTab {
@Override
public String getCaption() {
return "Stacktraces";
return i18n.get(Messages.STACKTRACES);
}
@Override
public String getId() {
return "stacktrace";
}
@Override

View file

@ -16,6 +16,7 @@
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.QReport;
import com.faendir.acra.service.DataService;
@ -28,6 +29,7 @@ import com.vaadin.ui.Panel;
import com.vaadin.ui.themes.AcraTheme;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.vaadin.spring.i18n.I18N;
/**
* @author lukas
@ -37,15 +39,17 @@ import org.springframework.lang.NonNull;
@ViewScope
public class StatisticsTab implements BugTab {
@NonNull private final DataService dataService;
@NonNull private final I18N i18n;
@Autowired
public StatisticsTab(@NonNull DataService dataService) {
public StatisticsTab(@NonNull DataService dataService, @NonNull I18N i18n) {
this.dataService = dataService;
this.i18n = i18n;
}
@Override
public Component createContent(@NonNull Bug bug, @NonNull NavigationManager navigationManager) {
Panel root = new Panel(new Statistics(QReport.report.stacktrace.bug.id.eq(bug.getId()), dataService));
Panel root = new Panel(new Statistics(QReport.report.stacktrace.bug.id.eq(bug.getId()), dataService, i18n));
root.setSizeFull();
root.addStyleNames(AcraTheme.NO_BACKGROUND, AcraTheme.NO_BORDER);
return root;
@ -53,7 +57,12 @@ public class StatisticsTab implements BugTab {
@Override
public String getCaption() {
return "Statistics";
return i18n.get(Messages.STATISTICS);
}
@Override
public String getId() {
return "statistics";
}
@Override

View file

@ -15,6 +15,9 @@
*/
package com.faendir.acra.ui.view.bug.tabs.panels;
import com.faendir.acra.i18n.I18nButton;
import com.faendir.acra.i18n.I18nLabel;
import com.faendir.acra.i18n.Messages;
import com.faendir.acra.model.Bug;
import com.faendir.acra.service.DataService;
import com.faendir.acra.ui.navigation.NavigationManager;
@ -31,6 +34,7 @@ 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.spring.i18n.I18N;
/**
* @author lukas
@ -40,32 +44,39 @@ import org.springframework.lang.NonNull;
@ViewScope
public class DangerPanel implements AdminPanel {
@NonNull private final DataService dataService;
private final I18N i18n;
@Autowired
public DangerPanel(@NonNull DataService dataService) {
public DangerPanel(@NonNull DataService dataService, I18N i18n) {
this.dataService = dataService;
this.i18n = i18n;
}
@Override
public Component createContent(@NonNull Bug bug, @NonNull NavigationManager navigationManager) {
Button unmerge = new Button("Disjoin Bug",
e -> new Popup().setTitle("Confirm").addComponent(new Label("Are you sure you want to revert merging this bug group?")).addYesNoButtons(p -> {
Button unmerge = new I18nButton(
e -> new Popup(i18n, Messages.CONFIRM).addComponent(new I18nLabel(i18n, Messages.UNMERGE_BUG_CONFIRM)).addYesNoButtons(p -> {
dataService.unmergeBug(bug);
navigationManager.navigateBack();
}, true).show());
}, true).show(), i18n, Messages.UNMERGE_BUG);
unmerge.setWidth(100, Sizeable.Unit.PERCENTAGE);
Button delete = new Button("Delete Bug",
e -> new Popup().setTitle("Confirm").addComponent(new Label("Are you sure you want to delete this bug and all its reports?")).addYesNoButtons(p -> {
Button delete = new I18nButton(
e -> new Popup(i18n, Messages.CONFIRM).addComponent(new Label(Messages.DELETE_BUG_CONFIRM)).addYesNoButtons(p -> {
dataService.delete(bug);
navigationManager.navigateBack();
}, true).show());
}, true).show(), i18n, Messages.DELETE_BUG);
delete.setWidth(100, Sizeable.Unit.PERCENTAGE);
return new VerticalLayout(unmerge, delete);
}
@Override
public String getCaption() {
return "Danger Zone";
return i18n.get(Messages.DANGER_ZONE);
}
@Override
public String getId() {
return "danger-zone";
}
@Override

View file

@ -16,6 +16,9 @@
package com.faendir.acra.ui.view.bug.tabs.panels;
import com.faendir.acra.i18n.I18nButton;
import com.faendir.acra.i18n.I18nTextArea;
import com.faendir.acra.i18n.Messages;
import com.faendir.acra.model.Bug;
import com.faendir.acra.service.DataService;
import com.faendir.acra.ui.navigation.NavigationManager;
@ -28,6 +31,7 @@ import com.vaadin.ui.TextArea;
import com.vaadin.ui.VerticalLayout;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.vaadin.spring.i18n.I18N;
/**
* @author lukas
@ -37,28 +41,35 @@ import org.springframework.lang.NonNull;
@ViewScope
public class PropertiesPanel implements AdminPanel {
@NonNull private final DataService dataService;
@NonNull private final I18N i18n;
@Autowired
public PropertiesPanel(@NonNull DataService dataService) {
public PropertiesPanel(@NonNull DataService dataService, @NonNull I18N i18n) {
this.dataService = dataService;
this.i18n = i18n;
}
@Override
public Component createContent(@NonNull Bug bug, @NonNull NavigationManager navigationManager) {
TextArea title = new TextArea("Title");
TextArea title = new I18nTextArea(i18n, Messages.TITLE);
title.setValue(bug.getTitle());
title.setSizeFull();
Button save = new Button("Save", e -> {
Button save = new I18nButton(e -> {
bug.setTitle(title.getValue());
dataService.store(bug);
});
}, i18n, Messages.SAVE);
save.setWidth(100, Sizeable.Unit.PERCENTAGE);
return new VerticalLayout(title, save);
}
@Override
public String getCaption() {
return "Properties";
return i18n.get(Messages.PROPERTIES);
}
@Override
public String getId() {
return "properties";
}
@Override

View file

@ -13,9 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faendir.acra.ui.view.report;
import com.faendir.acra.i18n.I18nLabel;
import com.faendir.acra.i18n.I18nPanel;
import com.faendir.acra.i18n.Messages;
import com.faendir.acra.model.App;
import com.faendir.acra.model.Attachment;
import com.faendir.acra.model.Permission;
@ -45,6 +47,7 @@ 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.spring.i18n.I18N;
import java.io.InputStream;
import java.util.Comparator;
@ -64,11 +67,13 @@ import java.util.stream.Stream;
public class ReportView extends ParametrizedBaseView<Report> {
@NonNull private final DataService dataService;
@NonNull private final AvatarService avatarService;
@NonNull private final I18N i18n;
@Autowired
public ReportView(@NonNull DataService dataService, @NonNull AvatarService avatarService) {
public ReportView(@NonNull DataService dataService, @NonNull AvatarService avatarService, @NonNull I18N i18n) {
this.dataService = dataService;
this.avatarService = avatarService;
this.i18n = i18n;
}
@Override
@ -76,30 +81,31 @@ public class ReportView extends ParametrizedBaseView<Report> {
HorizontalLayout attachments = new HorizontalLayout();
for (Attachment file : dataService.findAttachments(parameter)) {
Button button = new Button(file.getFilename());
new FileDownloader(new StreamResource(new ExceptionAwareStreamSource(file.getContent()::getBinaryStream), file.getFilename())).extend(button);
new FileDownloader(new StreamResource(new ExceptionAwareStreamSource(() -> file.getContent().getBinaryStream()), file.getFilename())).extend(button);
attachments.addComponent(button);
}
attachments.addStyleNames(AcraTheme.MARGIN_BOTTOM, AcraTheme.MARGIN_TOP, AcraTheme.MARGIN_LEFT, AcraTheme.MARGIN_RIGHT);
GridLayout summaryGrid = new GridLayout(2, 1);
summaryGrid.addStyleName(AcraTheme.BORDERED_GRIDLAYOUT);
summaryGrid.addComponents(new Label("Version", ContentMode.PREFORMATTED), new Label(parameter.getStacktrace().getVersion().getName(), ContentMode.PREFORMATTED));
summaryGrid.addComponents(new Label("User", ContentMode.PREFORMATTED), new HorizontalLayout(new Image(null, avatarService.getAvatar(parameter)), new Label(parameter.getInstallationId(), ContentMode.PREFORMATTED)));
summaryGrid.addComponents(new Label("Email", ContentMode.PREFORMATTED), new Label(parameter.getUserEmail(), ContentMode.PREFORMATTED));
summaryGrid.addComponents(new Label("Comment", ContentMode.PREFORMATTED), new Label(parameter.getUserComment(), ContentMode.PREFORMATTED));
summaryGrid.addComponents(new I18nLabel(ContentMode.PREFORMATTED, i18n, Messages.VERSION),
new Label(parameter.getStacktrace().getVersion().getName(), ContentMode.PREFORMATTED));
summaryGrid.addComponents(new I18nLabel(ContentMode.PREFORMATTED, i18n, Messages.USER),
new HorizontalLayout(new Image(null, avatarService.getAvatar(parameter)), new Label(parameter.getInstallationId(), ContentMode.PREFORMATTED)));
summaryGrid.addComponents(new I18nLabel(ContentMode.PREFORMATTED, i18n, Messages.EMAIL), new Label(parameter.getUserEmail(), ContentMode.PREFORMATTED));
summaryGrid.addComponents(new I18nLabel(ContentMode.PREFORMATTED, i18n, Messages.COMMENT), new Label(parameter.getUserComment(), ContentMode.PREFORMATTED));
Optional<ProguardMapping> mapping = dataService.findMapping(parameter.getStacktrace().getBug().getApp(), parameter.getStacktrace().getVersion().getCode());
if (mapping.isPresent()) {
summaryGrid.addComponents(new Label("De-obfuscated Stacktrace", ContentMode.PREFORMATTED),
summaryGrid.addComponents(new I18nLabel(ContentMode.PREFORMATTED, i18n, Messages.DE_OBFUSCATED_STACKTRACE),
new Label(Utils.retrace(parameter.getStacktrace().getStacktrace(), mapping.get().getMappings()), ContentMode.PREFORMATTED));
} else {
summaryGrid.addComponents(new Label("Stacktrace (No mapping found)", ContentMode.PREFORMATTED), new Label(parameter.getStacktrace().getStacktrace(), ContentMode.PREFORMATTED));
summaryGrid.addComponents(new I18nLabel(ContentMode.PREFORMATTED, i18n, Messages.NO_MAPPING_STACKTRACE),
new Label(parameter.getStacktrace().getStacktrace(), ContentMode.PREFORMATTED));
}
summaryGrid.addComponents(new Label("Attachments", ContentMode.PREFORMATTED), attachments);
summaryGrid.addComponents(new I18nLabel(ContentMode.PREFORMATTED, i18n, Messages.ATTACHMENTS), attachments);
summaryGrid.setDefaultComponentAlignment(Alignment.MIDDLE_LEFT);
summaryGrid.setSizeFull();
Panel summary = new Panel(summaryGrid);
summary.setCaption("Summary");
Panel details = new Panel(getLayoutForMap(parameter.getJsonObject().toMap()));
details.setCaption("Details");
Panel summary = new I18nPanel(summaryGrid, i18n, Messages.SUMMARY);
Panel details = new I18nPanel(getLayoutForMap(parameter.getJsonObject().toMap()), i18n, Messages.DETAILS);
VerticalLayout layout = new VerticalLayout(summary, details);
layout.setSizeUndefined();
layout.setExpandRatio(details, 1);
@ -117,11 +123,13 @@ public class ReportView extends ParametrizedBaseView<Report> {
@NonNull
private GridLayout getLayoutForMap(@NonNull Map<String, ?> map) {
GridLayout layout = new GridLayout(2, 1, map.entrySet()
.stream()
.sorted(Comparator.comparing(Map.Entry::getKey))
.flatMap(entry -> getLayoutForEntry(entry.getKey(), entry.getValue()))
.toArray(Component[]::new));
GridLayout layout = new GridLayout(2,
1,
map.entrySet()
.stream()
.sorted(Comparator.comparing(Map.Entry::getKey))
.flatMap(entry -> getLayoutForEntry(entry.getKey(), entry.getValue()))
.toArray(Component[]::new));
layout.setDefaultComponentAlignment(Alignment.MIDDLE_LEFT);
layout.setSpacing(false);
layout.setMargin(false);
@ -146,6 +154,11 @@ public class ReportView extends ParametrizedBaseView<Report> {
return new Label(String.valueOf(value), ContentMode.PREFORMATTED);
}
@FunctionalInterface
private interface ThrowingSupplier<T> {
T get() throws Exception;
}
private static class ExceptionAwareStreamSource implements StreamResource.StreamSource {
private final ThrowingSupplier<InputStream> supplier;
@ -155,7 +168,7 @@ public class ReportView extends ParametrizedBaseView<Report> {
@Override
public InputStream getStream() {
try{
try {
return supplier.get();
} catch (Exception e) {
e.printStackTrace();
@ -164,11 +177,6 @@ public class ReportView extends ParametrizedBaseView<Report> {
}
}
@FunctionalInterface
private interface ThrowingSupplier<T> {
T get() throws Exception;
}
@SpringComponent
@UIScope
public static class Provider extends SingleParametrizedViewProvider<Report, ReportView> {

View file

@ -16,12 +16,15 @@
package com.faendir.acra.ui.view.user;
import com.faendir.acra.security.SecurityUtils;
import com.faendir.acra.i18n.I18nButton;
import com.faendir.acra.i18n.I18nPasswordField;
import com.faendir.acra.i18n.Messages;
import com.faendir.acra.model.User;
import com.faendir.acra.security.SecurityUtils;
import com.faendir.acra.service.UserService;
import com.faendir.acra.ui.BackendUI;
import com.faendir.acra.ui.view.base.navigation.BaseView;
import com.faendir.acra.ui.navigation.SingleViewProvider;
import com.faendir.acra.ui.view.base.navigation.BaseView;
import com.vaadin.navigator.ViewChangeListener;
import com.vaadin.server.UserError;
import com.vaadin.spring.annotation.SpringComponent;
@ -34,7 +37,7 @@ import com.vaadin.ui.PasswordField;
import com.vaadin.ui.VerticalLayout;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.vaadin.spring.i18n.I18N;
/**
* @author Lukas
@ -45,33 +48,35 @@ import org.springframework.lang.Nullable;
public class ChangePasswordView extends BaseView {
@NonNull private final UserService userService;
@NonNull private final BackendUI backendUI;
@NonNull private final I18N i18n;
@Autowired
public ChangePasswordView(@NonNull UserService userService, @NonNull BackendUI backendUI) {
public ChangePasswordView(@NonNull UserService userService, @NonNull BackendUI backendUI, @NonNull I18N i18n) {
this.userService = userService;
this.backendUI = backendUI;
this.i18n = i18n;
}
@Override
public void enter(ViewChangeListener.ViewChangeEvent event) {
PasswordField oldPassword = new PasswordField("Old Password");
PasswordField newPassword = new PasswordField("New Password");
PasswordField repeatPassword = new PasswordField("Repeat Password");
Button confirm = new Button("Confirm", e -> {
PasswordField oldPassword = new I18nPasswordField(i18n, Messages.OLD_PASSWORD);
PasswordField newPassword = new I18nPasswordField(i18n, Messages.NEW_PASSWORD);
PasswordField repeatPassword = new I18nPasswordField(i18n, Messages.REPEAT_PASSWORD);
Button confirm = new I18nButton(e -> {
User user = userService.getUser(SecurityUtils.getUsername());
assert user != null;
if (newPassword.getValue().equals(repeatPassword.getValue())) {
if (userService.changePassword(user, oldPassword.getValue(), newPassword.getValue())) {
Notification.show("Successful!");
Notification.show(i18n.get(Messages.SUCCESS));
getNavigationManager().navigateBack();
backendUI.logout();
} else {
oldPassword.setComponentError(new UserError("Incorrect password"));
oldPassword.setComponentError(new UserError(i18n.get(Messages.INCORRECT_PASSWORD)));
}
} else {
repeatPassword.setComponentError(new UserError("Passwords do not match"));
repeatPassword.setComponentError(new UserError(i18n.get(Messages.PASSWORDS_NOT_MATCHING)));
}
});
}, i18n, Messages.CONFIRM);
confirm.setSizeFull();
VerticalLayout layout = new VerticalLayout(oldPassword, newPassword, repeatPassword, confirm);
layout.setSizeUndefined();
@ -84,13 +89,16 @@ public class ChangePasswordView extends BaseView {
@SpringComponent
@UIScope
public static class Provider extends SingleViewProvider<ChangePasswordView> {
protected Provider() {
@NonNull private final I18N i18n;
protected Provider(@NonNull I18N i18n) {
super(ChangePasswordView.class);
this.i18n = i18n;
}
@Override
public String getTitle(String parameter) {
return "Change Password";
return i18n.get(Messages.CHANGE_PASSWORD);
}
@Override

View file

@ -16,6 +16,10 @@
package com.faendir.acra.ui.view.user;
import com.faendir.acra.i18n.I18nButton;
import com.faendir.acra.i18n.I18nPasswordField;
import com.faendir.acra.i18n.I18nTextField;
import com.faendir.acra.i18n.Messages;
import com.faendir.acra.model.App;
import com.faendir.acra.model.Permission;
import com.faendir.acra.model.QUser;
@ -25,9 +29,9 @@ import com.faendir.acra.service.DataService;
import com.faendir.acra.service.UserService;
import com.faendir.acra.ui.annotation.RequiresRole;
import com.faendir.acra.ui.navigation.SingleViewProvider;
import com.faendir.acra.ui.view.base.navigation.BaseView;
import com.faendir.acra.ui.view.base.MyCheckBox;
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.vaadin.navigator.ViewChangeListener;
@ -37,13 +41,12 @@ import com.vaadin.spring.annotation.ViewScope;
import com.vaadin.ui.Button;
import com.vaadin.ui.ComboBox;
import com.vaadin.ui.Grid;
import com.vaadin.ui.PasswordField;
import com.vaadin.ui.TextField;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.renderers.ComponentRenderer;
import com.vaadin.ui.themes.AcraTheme;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.vaadin.spring.i18n.I18N;
import java.util.Arrays;
@ -57,25 +60,27 @@ import java.util.Arrays;
public class UserManagerView extends BaseView {
@NonNull private final UserService userService;
@NonNull private final DataService dataService;
@NonNull private final I18N i18n;
private MyGrid<User> userGrid;
@Autowired
public UserManagerView(@NonNull UserService userService, @NonNull DataService dataService) {
public UserManagerView(@NonNull UserService userService, @NonNull DataService dataService, @NonNull I18N i18n) {
this.userService = userService;
this.dataService = dataService;
this.i18n = i18n;
}
@Override
public void enter(ViewChangeListener.ViewChangeEvent event) {
userGrid = new MyGrid<>("Users", userService.getUserProvider());
userGrid = new MyGrid<>(userService.getUserProvider(), i18n, Messages.USERS);
userGrid.setSelectionMode(Grid.SelectionMode.NONE);
userGrid.setBodyRowHeight(42);
userGrid.setSizeToRows();
userGrid.addColumn(User::getUsername, QUser.user.username, "Username");
userGrid.addColumn(User::getUsername, QUser.user.username, Messages.USERNAME);
userGrid.addColumn(user -> new MyCheckBox(user.getRoles().contains(User.Role.ADMIN), !user.getUsername().equals(SecurityUtils.getUsername()), e -> {
userService.setAdmin(user, e.getValue());
userGrid.getDataProvider().refreshAll();
}), new ComponentRenderer(), "Admin");
}), new ComponentRenderer(), Messages.ADMIN);
for (App app : dataService.findAllApps()) {
userGrid.addColumn(user -> {
Permission.Level permission = SecurityUtils.getPermission(app, user);
@ -84,9 +89,9 @@ public class UserManagerView extends BaseView {
levelComboBox.setValue(permission);
levelComboBox.addValueChangeListener(e -> userService.setPermission(user, app, e.getValue()));
return levelComboBox;
}, new ComponentRenderer(), "Access Permission for " + app.getName());
}, new ComponentRenderer(), Messages.ACCESS_PERMISSION, app.getName());
}
Button newUser = new Button("New User", e -> newUser());
Button newUser = new I18nButton( e -> newUser(), i18n, Messages.NEW_USER);
VerticalLayout layout = new VerticalLayout(userGrid, newUser);
layout.addStyleName(AcraTheme.NO_PADDING);
setCompositionRoot(layout);
@ -94,12 +99,12 @@ public class UserManagerView extends BaseView {
}
private void newUser() {
TextField name = new TextField("Username");
PasswordField password = new PasswordField("Password");
new Popup().setTitle("New User")
.addValidatedField(ValidatedField.of(name).addValidator(s -> !s.isEmpty(), "Username cannot be empty"))
.addValidatedField(ValidatedField.of(password).addValidator(s -> !s.isEmpty(), "Password cannot be empty"))
.addValidatedField(ValidatedField.of(new PasswordField("Repeat Password")).addValidator(s -> s.equals(password.getValue()), "Passwords do not match"))
I18nTextField name = new I18nTextField(i18n, Messages.USERNAME);
I18nPasswordField password = new I18nPasswordField(i18n, Messages.PASSWORD);
new Popup(i18n, Messages.NEW_USER)
.addValidatedField(ValidatedField.of(name).addValidator(s -> !s.isEmpty(), Messages.USERNAME_EMPTY))
.addValidatedField(ValidatedField.of(password).addValidator(s -> !s.isEmpty(), Messages.PASSWORD_EMPTY))
.addValidatedField(ValidatedField.of(new I18nPasswordField(i18n, Messages.REPEAT_PASSWORD)).addValidator(s -> s.equals(password.getValue()), Messages.PASSWORDS_NOT_MATCHING))
.addCreateButton(popup -> {
userService.createUser(name.getValue().toLowerCase(), password.getValue());
userGrid.getDataProvider().refreshAll();
@ -110,13 +115,16 @@ public class UserManagerView extends BaseView {
@SpringComponent
@UIScope
public static class Provider extends SingleViewProvider<UserManagerView> {
protected Provider() {
@NonNull private final I18N i18n;
protected Provider(@NonNull I18N i18n) {
super(UserManagerView.class);
this.i18n = i18n;
}
@Override
public String getTitle(String parameter) {
return "User Manager";
return i18n.get(Messages.USER_MANAGER);
}
@Override

View file

@ -0,0 +1,129 @@
#
# (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.
#
newApp=New App
apps=Apps
name=Name
bugs=Bugs
reports=Reports
importAcralyzer=Import from Acralyzer
host=Host
port=Port
ssl=Use SSL
databaseName=Database Name
importSuccess=Import successful for {0} of {1} reports.
acrarium=Acrarium
configurationLabel=Take note of the following ACRA configuration. It cannot be viewed later:<br><code>\
@AcraCore(reportFormat = StringFormat.JSON)<br>\
@AcraHttpSender(uri = "{0}{1}",<br>\
basicAuthLogin = "{2}",<br>\
basicAuthPassword = "{3}",<br>\
httpMethod = HttpSender.Method.POST)<br></code>
create=Create
close=Close
yes=Yes
no=No
error4xx=This page does not exist or you do not have the permission to view it.
admin=Admin
hideSolved=Hide Solved
mergeBugs=Merge Bugs
chooseBugGroupTitle=Choose title for bug group
onlyOneBugSelected=Please select at least two bugs
latestReport=Latest Report
latestVersion=Latest Version
affectedUsers=Affected Users
title=Title
solved=Solved
statistics=Statistics
newAcraConfig=Create new ACRA Configuration
confirm=Confirm
newAcraConfigConfirm=Are you sure you want to create a new ACRA configuration?<br>The existing configuration will be invalidated
newBugConfig=Configure Bug matching
minScore=Minimum Score
newBugConfigConfirm=Are you sure you want to save this configuration? All bugs will be checked for merging, which may take some time. Note that a higher minimum score does not unmerge already merged bugs.
purge=Purge
reportsOlderThan1= Reports older than
reportsOlderThan2= Days
reportsBeforeVersion= Reports before Version
deleteApp=Delete App
deleteAppConfirm=Are you sure you want to delete this app and all its associated content?
dangerZone=Danger Zone
byMail=By Email Address
byId=By Installation ID
download=Download
nothingSelected=Nothing selected
export=Export
version=Version
deleteMappingConfirm=Are you sure you want to delete the mapping for version {0}?
newFile=Add File
versionCode=Version code
mappingFile=Mapping file
newMapping=New Mapping Configuration
errorUpload=Upload failed
deObfuscation=De-Obfuscation
lastXDays=Last X Days
reportsOverTime=Reports over time
androidVersion=Android Version
reportsPerAndroidVersion=Reports per Android Version
appVersion=App Version
reportsPerAppVersion=Reports per App Version
phoneModel=Phone Model
reportsPerPhoneModel=Reports per Phone Model
phoneBrand=Phone Brand
reportsPerBrand=Reports per Brand
filter=Filter
apply=Apply
user=User
date=Date
device=Device
stacktrace=Stacktrace
deleteReportConfirm=Are you sure you want to delete this report?
unmergeBug=Disjoin Bug
unmergeBugConfirm=Are you sure you want to revert merging this bug group?
deleteBug=Delete Bug
deleteBugConfirm=Are you sure you want to delete this bug and all its reports?
save=Save
properties=Properties
stacktraces=stacktraces
stacktraceTitle=Version "{0}": {1}
email=Email
comment=Comment
DeObfuscatedStacktrace=De-obfuscated Stacktrace
noMappingStacktrace=Stacktrace (No mapping found)
attachments=Attachments
summary=Summary
details=Details
oldPassword=Old Password
newPassword=New Password
repeatPassword=Repeat Password
incorrectPassword=Incorrect Password
passwordsNotMatching=Passwords do not match
changePassword=Change Password
success=Successful!
users=Users
username=Username
accessPermission=Access Permission for {0}
newUser=New User
password=Password
usernameEmpty=Username cannot be empty
passwordEmpty=Password cannot be empty
userManager=User Manager
loginFailed=Unknown username/password combination
missingRole=Missing required role
darkTheme=Dark Theme
logout=Logout
footer=Acrarium is developed by <a href=https://github.com/F43nd1r>F43nd1r</a>. <a href=https://github.com/F43nd1r/acra-backend>Code</a> is licensed under <a href=https://github.com/F43nd1r/acra-backend/blob/master/LICENSE>Apache License v2</a>.
blank=