bug solving, inline grid editing
This commit is contained in:
parent
8474f34528
commit
ae70018a9c
20 changed files with 246 additions and 215 deletions
|
@ -4,6 +4,7 @@ buildscript {
|
||||||
}
|
}
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
maven { url "https://oss.sonatype.org/content/repositories/vaadin-snapshots/"}
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
|
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
|
||||||
|
@ -40,7 +41,9 @@ dependencies {
|
||||||
compile 'commons-fileupload:commons-fileupload:1.3.2'
|
compile 'commons-fileupload:commons-fileupload:1.3.2'
|
||||||
compile 'org.ocpsoft.prettytime:prettytime:3.2.7.Final'
|
compile 'org.ocpsoft.prettytime:prettytime:3.2.7.Final'
|
||||||
compile 'net.sf.proguard:proguard-retrace:5.3.3'
|
compile 'net.sf.proguard:proguard-retrace:5.3.3'
|
||||||
compile 'org.jfree:jfreechart:1.0.19'
|
compile ('org.jfree:jfreechart:1.0.19') {
|
||||||
|
exclude group:'javax.servlet'
|
||||||
|
}
|
||||||
//local
|
//local
|
||||||
compileOnly project(':annotation')
|
compileOnly project(':annotation')
|
||||||
apt project(':annotationprocessor')
|
apt project(':annotationprocessor')
|
||||||
|
@ -59,7 +62,7 @@ configurations {
|
||||||
|
|
||||||
dependencyManagement {
|
dependencyManagement {
|
||||||
imports {
|
imports {
|
||||||
mavenBom "com.vaadin:vaadin-bom:8.0.6"
|
mavenBom "com.vaadin:vaadin-bom:8.1-SNAPSHOT"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.faendir.acra.mongod.data;
|
||||||
|
|
||||||
|
import com.faendir.acra.mongod.model.Bug;
|
||||||
|
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Lukas
|
||||||
|
* @since 31.05.2017
|
||||||
|
*/
|
||||||
|
interface BugRepository extends MongoRepository<Bug, Bug.Identification> {
|
||||||
|
List<Bug> findByApp(String app);
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package com.faendir.acra.mongod.data;
|
package com.faendir.acra.mongod.data;
|
||||||
|
|
||||||
import com.faendir.acra.mongod.model.App;
|
import com.faendir.acra.mongod.model.App;
|
||||||
|
import com.faendir.acra.mongod.model.Bug;
|
||||||
import com.faendir.acra.mongod.model.ProguardMapping;
|
import com.faendir.acra.mongod.model.ProguardMapping;
|
||||||
import com.faendir.acra.mongod.model.Report;
|
import com.faendir.acra.mongod.model.Report;
|
||||||
import com.mongodb.BasicDBObjectBuilder;
|
import com.mongodb.BasicDBObjectBuilder;
|
||||||
|
@ -9,12 +10,9 @@ import org.json.JSONObject;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.data.domain.Example;
|
|
||||||
import org.springframework.data.domain.ExampleMatcher;
|
|
||||||
import org.springframework.data.mongodb.core.query.Criteria;
|
import org.springframework.data.mongodb.core.query.Criteria;
|
||||||
import org.springframework.data.mongodb.core.query.Query;
|
import org.springframework.data.mongodb.core.query.Query;
|
||||||
import org.springframework.data.mongodb.gridfs.GridFsTemplate;
|
import org.springframework.data.mongodb.gridfs.GridFsTemplate;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.util.Base64Utils;
|
import org.springframework.util.Base64Utils;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
@ -33,16 +31,18 @@ import java.util.List;
|
||||||
public class DataManager {
|
public class DataManager {
|
||||||
private final MappingRepository mappingRepository;
|
private final MappingRepository mappingRepository;
|
||||||
private final ReportRepository reportRepository;
|
private final ReportRepository reportRepository;
|
||||||
|
private final AppRepository appRepository;
|
||||||
|
private final BugRepository bugRepository;
|
||||||
private final List<ReportChangeListener> listeners;
|
private final List<ReportChangeListener> listeners;
|
||||||
private final GridFsTemplate gridFsTemplate;
|
private final GridFsTemplate gridFsTemplate;
|
||||||
private final Logger logger;
|
private final Logger logger;
|
||||||
private final SecureRandom secureRandom;
|
private final SecureRandom secureRandom;
|
||||||
private final AppRepository appRepository;
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public DataManager(SecureRandom secureRandom, AppRepository appRepository, GridFsTemplate gridFsTemplate, MappingRepository mappingRepository, ReportRepository reportRepository) {
|
public DataManager(SecureRandom secureRandom, AppRepository appRepository, GridFsTemplate gridFsTemplate, MappingRepository mappingRepository, ReportRepository reportRepository, BugRepository bugRepository) {
|
||||||
this.secureRandom = secureRandom;
|
this.secureRandom = secureRandom;
|
||||||
this.appRepository = appRepository;
|
this.appRepository = appRepository;
|
||||||
|
this.bugRepository = bugRepository;
|
||||||
logger = LoggerFactory.getLogger(DataManager.class);
|
logger = LoggerFactory.getLogger(DataManager.class);
|
||||||
this.gridFsTemplate = gridFsTemplate;
|
this.gridFsTemplate = gridFsTemplate;
|
||||||
this.mappingRepository = mappingRepository;
|
this.mappingRepository = mappingRepository;
|
||||||
|
@ -50,45 +50,31 @@ public class DataManager {
|
||||||
this.listeners = new ArrayList<>();
|
this.listeners = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void createNewApp(String name){
|
public synchronized void createNewApp(String name) {
|
||||||
byte[] bytes = new byte[12];
|
byte[] bytes = new byte[12];
|
||||||
secureRandom.nextBytes(bytes);
|
secureRandom.nextBytes(bytes);
|
||||||
appRepository.save(new App(name, Base64Utils.encodeToString(bytes)));
|
appRepository.save(new App(name, Base64Utils.encodeToString(bytes)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<App> getApps(){
|
public List<App> getApps() {
|
||||||
return appRepository.findAll();
|
return appRepository.findAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
public App getApp(String id){
|
public App getApp(String id) {
|
||||||
return appRepository.findOne(id);
|
return appRepository.findOne(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deleteApp(String id){
|
public synchronized void deleteApp(String id) {
|
||||||
appRepository.delete(id);
|
appRepository.delete(id);
|
||||||
getReports(id).forEach(this::remove);
|
getReportsForApp(id).forEach(this::deleteReport);
|
||||||
mappingRepository.delete(getMappings(id));
|
mappingRepository.delete(getMappings(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveAttachments(String report, List<MultipartFile> attachments) {
|
|
||||||
for (MultipartFile a : attachments) {
|
|
||||||
try {
|
|
||||||
gridFsTemplate.store(a.getInputStream(), a.getOriginalFilename(), a.getContentType(), new BasicDBObjectBuilder().add("reportId", report).get());
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.warn("Failed to load attachment", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<GridFSDBFile> getAttachments(String report) {
|
public List<GridFSDBFile> getAttachments(String report) {
|
||||||
return gridFsTemplate.find(new Query(Criteria.where("metadata.reportId").is(report)));
|
return gridFsTemplate.find(new Query(Criteria.where("metadata.reportId").is(report)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeAttachments(String report) {
|
public synchronized void addMapping(String app, int version, String mappings) {
|
||||||
gridFsTemplate.delete(new Query(Criteria.where("metadata.reportId").is(report)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addMapping(String app, int version, String mappings) {
|
|
||||||
mappingRepository.save(new ProguardMapping(app, version, mappings));
|
mappingRepository.save(new ProguardMapping(app, version, mappings));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,33 +83,74 @@ public class DataManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ProguardMapping> getMappings(String app) {
|
public List<ProguardMapping> getMappings(String app) {
|
||||||
return mappingRepository.findAll(Example.of(new ProguardMapping(app, -1, null), ExampleMatcher.matchingAny()));
|
return mappingRepository.findByApp(app);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void newReport(JSONObject content) {
|
public void newReport(String app, JSONObject content) {
|
||||||
newReport(content, Collections.emptyList());
|
newReport(app, content, Collections.emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void newReport(JSONObject content, List<MultipartFile> attachments) {
|
public synchronized void newReport(String app, JSONObject content, List<MultipartFile> attachments) {
|
||||||
Report report = reportRepository.save(new Report(content, SecurityContextHolder.getContext().getAuthentication().getName()));
|
Report report = reportRepository.save(new Report(content, app));
|
||||||
saveAttachments(report.getId(), attachments);
|
for (MultipartFile a : attachments) {
|
||||||
|
try {
|
||||||
|
gridFsTemplate.store(a.getInputStream(), a.getOriginalFilename(), a.getContentType(), new BasicDBObjectBuilder().add("reportId", report.getId()).get());
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("Failed to load attachment", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Bug bug = bugRepository.findOne(new Bug.Identification(report.getStacktrace().hashCode(), report.getVersionCode()));
|
||||||
|
if (bug == null) {
|
||||||
|
bugRepository.save(new Bug(app, report.getStacktrace(), report.getVersionCode()));
|
||||||
|
}
|
||||||
listeners.forEach(ReportChangeListener::onChange);
|
listeners.forEach(ReportChangeListener::onChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Report> getReports(String app) {
|
public List<Report> getReportsForApp(String app) {
|
||||||
return reportRepository.findAll(Example.of(new Report(null, app)));
|
return reportRepository.findByApp(app);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Report getReport(String id) {
|
public Report getReport(String id) {
|
||||||
return reportRepository.findOne(id);
|
return reportRepository.findOne(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void remove(Report report){
|
public synchronized void deleteReport(Report report) {
|
||||||
reportRepository.delete(report);
|
reportRepository.delete(report);
|
||||||
removeAttachments(report.getId());
|
gridFsTemplate.delete(new Query(Criteria.where("metadata.reportId").is(report.getId())));
|
||||||
|
if(reportRepository.countByBug(report.getStacktrace(), report.getVersionCode()) == 0){
|
||||||
|
bugRepository.delete(bugRepository.findOne(new Bug.Identification(report.getStacktrace().hashCode(), report.getVersionCode())));
|
||||||
|
}
|
||||||
listeners.forEach(ReportChangeListener::onChange);
|
listeners.forEach(ReportChangeListener::onChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Bug> getBugs(String app) {
|
||||||
|
return bugRepository.findByApp(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Report> getReportsForBug(Bug bug) {
|
||||||
|
return reportRepository.findByBug(bug.getStacktrace(), bug.getVersionCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
public int countReportsForBug(Bug bug){
|
||||||
|
return reportRepository.countByBug(bug.getStacktrace(), bug.getVersionCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String retrace(Report report){
|
||||||
|
ProguardMapping mapping = getMapping(report.getApp(), report.getVersionCode());
|
||||||
|
if (mapping != null) {
|
||||||
|
try {
|
||||||
|
return ReportUtils.retrace(report.getStacktrace(), mapping);
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return report.getStacktrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBugSolved(Bug bug, boolean solved){
|
||||||
|
bug.setSolved(solved);
|
||||||
|
bugRepository.save(bug);
|
||||||
|
}
|
||||||
|
|
||||||
public boolean addListener(ReportChangeListener reportChangeListener) {
|
public boolean addListener(ReportChangeListener reportChangeListener) {
|
||||||
return listeners.add(reportChangeListener);
|
return listeners.add(reportChangeListener);
|
||||||
}
|
}
|
||||||
|
@ -135,4 +162,5 @@ public class DataManager {
|
||||||
public interface ReportChangeListener {
|
public interface ReportChangeListener {
|
||||||
void onChange();
|
void onChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,15 @@ package com.faendir.acra.mongod.data;
|
||||||
|
|
||||||
import com.faendir.acra.mongod.model.ProguardMapping;
|
import com.faendir.acra.mongod.model.ProguardMapping;
|
||||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||||
|
import org.springframework.data.mongodb.repository.Query;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Lukas
|
* @author Lukas
|
||||||
* @since 19.05.2017
|
* @since 19.05.2017
|
||||||
*/
|
*/
|
||||||
interface MappingRepository extends MongoRepository<ProguardMapping, ProguardMapping.MetaData> {
|
interface MappingRepository extends MongoRepository<ProguardMapping, ProguardMapping.MetaData> {
|
||||||
|
@Query("{id.app:?0}")
|
||||||
|
List<ProguardMapping> findByApp(String app);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,22 @@
|
||||||
package com.faendir.acra.mongod.data;
|
package com.faendir.acra.mongod.data;
|
||||||
|
|
||||||
import com.faendir.acra.mongod.model.Report;
|
import com.faendir.acra.mongod.model.Report;
|
||||||
|
import org.springframework.data.mongodb.repository.CountQuery;
|
||||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||||
|
import org.springframework.data.mongodb.repository.Query;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Lukas
|
* @author Lukas
|
||||||
* @since 22.03.2017
|
* @since 22.03.2017
|
||||||
*/
|
*/
|
||||||
interface ReportRepository extends MongoRepository<Report, String> {
|
interface ReportRepository extends MongoRepository<Report, String> {
|
||||||
|
List<Report> findByApp(String app);
|
||||||
|
|
||||||
|
@Query("{content.map.STACK_TRACE:?0,content.map.APP_VERSION_CODE:?1}")
|
||||||
|
List<Report> findByBug(String stacktrace, int versionCode);
|
||||||
|
|
||||||
|
@CountQuery("{content.map.STACK_TRACE:?0,content.map.APP_VERSION_CODE:?1}")
|
||||||
|
int countByBug(String stacktrace, int versionCode);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package com.faendir.acra.mongod.data;
|
package com.faendir.acra.mongod.data;
|
||||||
|
|
||||||
import com.faendir.acra.mongod.model.Bug;
|
|
||||||
import com.faendir.acra.mongod.model.ProguardMapping;
|
import com.faendir.acra.mongod.model.ProguardMapping;
|
||||||
import com.faendir.acra.mongod.model.Report;
|
import com.faendir.acra.mongod.model.Report;
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
|
@ -14,7 +13,6 @@ import java.io.StringReader;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
@ -26,18 +24,6 @@ import java.util.Locale;
|
||||||
public final class ReportUtils {
|
public final class ReportUtils {
|
||||||
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX", Locale.ENGLISH);
|
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX", Locale.ENGLISH);
|
||||||
|
|
||||||
public static List<Bug> getBugs(List<Report> reports) {
|
|
||||||
List<Bug> bugs = new ArrayList<>();
|
|
||||||
for (Report report : reports) {
|
|
||||||
bugs.stream().filter(bug -> bug.is(report)).findAny().orElseGet(() -> {
|
|
||||||
Bug bug = new Bug(report);
|
|
||||||
bugs.add(bug);
|
|
||||||
return bug;
|
|
||||||
}).getReports().add(report);
|
|
||||||
}
|
|
||||||
return bugs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Date getDateFromString(String s) {
|
public static Date getDateFromString(String s) {
|
||||||
try {
|
try {
|
||||||
return dateFormat.parse(s);
|
return dateFormat.parse(s);
|
||||||
|
@ -56,4 +42,8 @@ public final class ReportUtils {
|
||||||
return writer.toString();
|
return writer.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Date getLastReportDate(List<Report> reports) {
|
||||||
|
return reports.stream().map(Report::getDate).reduce((d1, d2) -> d1.after(d2) ? d1 : d2).orElse(new Date());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,41 +1,58 @@
|
||||||
package com.faendir.acra.mongod.model;
|
package com.faendir.acra.mongod.model;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import org.springframework.data.mongodb.core.index.Indexed;
|
||||||
import java.util.Date;
|
import org.springframework.data.mongodb.core.mapping.Document;
|
||||||
import java.util.List;
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Lukas
|
* @author Lukas
|
||||||
* @since 13.05.2017
|
* @since 13.05.2017
|
||||||
*/
|
*/
|
||||||
|
@Document
|
||||||
public class Bug {
|
public class Bug {
|
||||||
private List<Report> reports;
|
private Identification id;
|
||||||
private String trace;
|
@Indexed
|
||||||
private int versionCode;
|
private String app;
|
||||||
|
private boolean solved;
|
||||||
|
private String stacktrace;
|
||||||
|
|
||||||
public Bug(Report report){
|
public Bug(){
|
||||||
reports = new ArrayList<>();
|
}
|
||||||
this.trace = report.getStacktrace();
|
|
||||||
this.versionCode = report.getVersionCode();
|
public Bug(String app, String stacktrace, int versionCode){
|
||||||
|
this.id = new Identification(stacktrace.hashCode(), versionCode);
|
||||||
|
this.app = app;
|
||||||
|
this.stacktrace = stacktrace;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean is(Report report){
|
public boolean is(Report report){
|
||||||
return report.getStacktrace().equals(trace) && report.getVersionCode() == versionCode;
|
return report.getStacktrace().hashCode() == id.stacktraceHash && report.getVersionCode() == id.versionCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Report> getReports() {
|
public boolean isSolved() {
|
||||||
return reports;
|
return solved;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Date getLastDate(){
|
public void setSolved(boolean solved) {
|
||||||
return reports.stream().map(Report::getDate).reduce((d1, d2) -> d1.after(d2) ? d1 : d2).orElse(new Date());
|
this.solved = solved;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getTrace() {
|
public String getStacktrace() {
|
||||||
return trace;
|
return stacktrace;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getVersionCode() {
|
public int getVersionCode() {
|
||||||
return versionCode;
|
return id.versionCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Identification implements Serializable {
|
||||||
|
private int stacktraceHash;
|
||||||
|
private int versionCode;
|
||||||
|
|
||||||
|
public Identification(int stacktraceHash, int versionCode) {
|
||||||
|
this.stacktraceHash = stacktraceHash;
|
||||||
|
this.versionCode = versionCode;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
package com.faendir.acra.mongod.model;
|
package com.faendir.acra.mongod.model;
|
||||||
|
|
||||||
import com.faendir.acra.mongod.data.DataManager;
|
|
||||||
import com.faendir.acra.mongod.data.ReportUtils;
|
import com.faendir.acra.mongod.data.ReportUtils;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import org.springframework.data.mongodb.core.index.Indexed;
|
import org.springframework.data.mongodb.core.index.Indexed;
|
||||||
import org.springframework.data.mongodb.core.mapping.Document;
|
import org.springframework.data.mongodb.core.mapping.Document;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
@ -21,7 +19,6 @@ public class Report {
|
||||||
@Indexed
|
@Indexed
|
||||||
private String app;
|
private String app;
|
||||||
private JSONObject content;
|
private JSONObject content;
|
||||||
private String deObfuscatedTrace;
|
|
||||||
|
|
||||||
public Report() {
|
public Report() {
|
||||||
}
|
}
|
||||||
|
@ -30,7 +27,6 @@ public class Report {
|
||||||
this.content = content;
|
this.content = content;
|
||||||
this.app = app;
|
this.app = app;
|
||||||
id = content == null ? null : getValueSafe("REPORT_ID", content::getString, "");
|
id = content == null ? null : getValueSafe("REPORT_ID", content::getString, "");
|
||||||
deObfuscatedTrace = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public JSONObject getContent() {
|
public JSONObject getContent() {
|
||||||
|
@ -45,23 +41,12 @@ public class Report {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getStacktrace() {
|
public String getApp() {
|
||||||
return getValueSafe("STACK_TRACE", content::getString, "");
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDeObfuscatedStacktrace(DataManager dataManager){
|
public String getStacktrace() {
|
||||||
if (deObfuscatedTrace != null) {
|
return getValueSafe("STACK_TRACE", content::getString, "");
|
||||||
return deObfuscatedTrace;
|
|
||||||
}
|
|
||||||
ProguardMapping mapping = dataManager.getMapping(app, getVersionCode());
|
|
||||||
if (mapping != null) {
|
|
||||||
try {
|
|
||||||
deObfuscatedTrace = ReportUtils.retrace(getStacktrace(), mapping);
|
|
||||||
return deObfuscatedTrace;
|
|
||||||
} catch (IOException ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return getStacktrace();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getVersionCode() {
|
public int getVersionCode() {
|
||||||
|
|
|
@ -47,9 +47,11 @@ public class UserManager {
|
||||||
public User getUser(String username) {
|
public User getUser(String username) {
|
||||||
User user = userRepository.findOne(username);
|
User user = userRepository.findOne(username);
|
||||||
if (user == null && defaultUser.equals(username)) {
|
if (user == null && defaultUser.equals(username)) {
|
||||||
return getDefaultUser();
|
user = getDefaultUser();
|
||||||
|
}
|
||||||
|
if(user != null) {
|
||||||
|
ensureValidPermissions(user);
|
||||||
}
|
}
|
||||||
ensureValidPermissions(user);
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ import org.springframework.web.multipart.MultipartHttpServletRequest;
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.Principal;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -35,14 +36,14 @@ public class ReportService {
|
||||||
|
|
||||||
@PreAuthorize("hasRole('REPORTER')")
|
@PreAuthorize("hasRole('REPORTER')")
|
||||||
@RequestMapping(value = "/report", consumes = MediaType.APPLICATION_JSON_VALUE)
|
@RequestMapping(value = "/report", consumes = MediaType.APPLICATION_JSON_VALUE)
|
||||||
public void report(@RequestBody String content) throws IOException {
|
public void report(@RequestBody String content, Principal principal) throws IOException {
|
||||||
JSONObject jsonObject = new JSONObject(content);
|
JSONObject jsonObject = new JSONObject(content);
|
||||||
dataManager.newReport(jsonObject);
|
dataManager.newReport(principal.getName(), jsonObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreAuthorize("hasRole('REPORTER')")
|
@PreAuthorize("hasRole('REPORTER')")
|
||||||
@RequestMapping(value = "/report", consumes = "multipart/mixed")
|
@RequestMapping(value = "/report", consumes = "multipart/mixed")
|
||||||
public ResponseEntity report(MultipartHttpServletRequest request) throws IOException, ServletException {
|
public ResponseEntity report(MultipartHttpServletRequest request, Principal principal) throws IOException, ServletException {
|
||||||
MultiValueMap<String, MultipartFile> fileMap = request.getMultiFileMap();
|
MultiValueMap<String, MultipartFile> fileMap = request.getMultiFileMap();
|
||||||
List<MultipartFile> files = fileMap.get(null);
|
List<MultipartFile> files = fileMap.get(null);
|
||||||
JSONObject jsonObject = null;
|
JSONObject jsonObject = null;
|
||||||
|
@ -56,7 +57,7 @@ public class ReportService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(jsonObject != null) {
|
if(jsonObject != null) {
|
||||||
dataManager.newReport(jsonObject, attachments);
|
dataManager.newReport(principal.getName(), jsonObject, attachments);
|
||||||
return ResponseEntity.ok().build();
|
return ResponseEntity.ok().build();
|
||||||
}else {
|
}else {
|
||||||
return ResponseEntity.badRequest().build();
|
return ResponseEntity.badRequest().build();
|
||||||
|
|
|
@ -14,6 +14,7 @@ import com.vaadin.ui.Alignment;
|
||||||
import com.vaadin.ui.Button;
|
import com.vaadin.ui.Button;
|
||||||
import com.vaadin.ui.HorizontalLayout;
|
import com.vaadin.ui.HorizontalLayout;
|
||||||
import com.vaadin.ui.LoginForm;
|
import com.vaadin.ui.LoginForm;
|
||||||
|
import com.vaadin.ui.Notification;
|
||||||
import com.vaadin.ui.UI;
|
import com.vaadin.ui.UI;
|
||||||
import com.vaadin.ui.VerticalLayout;
|
import com.vaadin.ui.VerticalLayout;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
@ -54,15 +55,14 @@ public class BackendUI extends UI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean login(String username, String password) {
|
private void login(String username, String password) {
|
||||||
try {
|
try {
|
||||||
Authentication token = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username.toLowerCase(), password));
|
Authentication token = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username.toLowerCase(), password));
|
||||||
VaadinService.reinitializeSession(VaadinService.getCurrentRequest());
|
VaadinService.reinitializeSession(VaadinService.getCurrentRequest());
|
||||||
SecurityContextHolder.getContext().setAuthentication(token);
|
SecurityContextHolder.getContext().setAuthentication(token);
|
||||||
showMain();
|
showMain();
|
||||||
return true;
|
|
||||||
} catch (AuthenticationException ex) {
|
} catch (AuthenticationException ex) {
|
||||||
return false;
|
Notification.show("Unknown username/password combination", Notification.Type.ERROR_MESSAGE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,7 +64,7 @@ public class Overview extends NamedView {
|
||||||
grid.setSizeFull();
|
grid.setSizeFull();
|
||||||
grid.setSelectionMode(Grid.SelectionMode.NONE);
|
grid.setSelectionMode(Grid.SelectionMode.NONE);
|
||||||
grid.addColumn(App::getName, "Name");
|
grid.addColumn(App::getName, "Name");
|
||||||
grid.addColumn(app -> dataManager.getReports(app.getId()).size(), "Reports");
|
grid.addColumn(app -> dataManager.getReportsForApp(app.getId()).size(), "Reports");
|
||||||
VerticalLayout layout = new VerticalLayout(grid);
|
VerticalLayout layout = new VerticalLayout(grid);
|
||||||
if(SecurityUtils.hasRole(UserManager.ROLE_ADMIN)){
|
if(SecurityUtils.hasRole(UserManager.ROLE_ADMIN)){
|
||||||
Button add = new Button("New App", e -> addApp());
|
Button add = new Button("New App", e -> addApp());
|
||||||
|
|
|
@ -87,7 +87,7 @@ public class ReportView extends NamedView {
|
||||||
summaryGrid.addComponents(new Label("Version", ContentMode.PREFORMATTED), new Label(report.getVersionName(), ContentMode.PREFORMATTED));
|
summaryGrid.addComponents(new Label("Version", ContentMode.PREFORMATTED), new Label(report.getVersionName(), ContentMode.PREFORMATTED));
|
||||||
summaryGrid.addComponents(new Label("Email", ContentMode.PREFORMATTED), new Label(report.getUserEmail(), ContentMode.PREFORMATTED));
|
summaryGrid.addComponents(new Label("Email", ContentMode.PREFORMATTED), new Label(report.getUserEmail(), ContentMode.PREFORMATTED));
|
||||||
summaryGrid.addComponents(new Label("Comment", ContentMode.PREFORMATTED), new Label(report.getUserComment(), ContentMode.PREFORMATTED));
|
summaryGrid.addComponents(new Label("Comment", ContentMode.PREFORMATTED), new Label(report.getUserComment(), ContentMode.PREFORMATTED));
|
||||||
summaryGrid.addComponents(new Label("De-obfuscated Stacktrace", ContentMode.PREFORMATTED), new Label(report.getDeObfuscatedStacktrace(dataManager), ContentMode.PREFORMATTED));
|
summaryGrid.addComponents(new Label("De-obfuscated Stacktrace", ContentMode.PREFORMATTED), new Label(dataManager.retrace(report), ContentMode.PREFORMATTED));
|
||||||
summaryGrid.addComponents(new Label("Attachments", ContentMode.PREFORMATTED), attachments);
|
summaryGrid.addComponents(new Label("Attachments", ContentMode.PREFORMATTED), attachments);
|
||||||
summaryGrid.setDefaultComponentAlignment(Alignment.MIDDLE_LEFT);
|
summaryGrid.setDefaultComponentAlignment(Alignment.MIDDLE_LEFT);
|
||||||
summaryGrid.setSizeFull();
|
summaryGrid.setSizeFull();
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
package com.faendir.acra.ui.view.base;
|
||||||
|
|
||||||
|
import com.vaadin.ui.CheckBox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Lukas
|
||||||
|
* @since 31.05.2017
|
||||||
|
*/
|
||||||
|
public class MyCheckBox extends CheckBox {
|
||||||
|
public MyCheckBox(boolean value, ValueChangeListener<Boolean> changeListener) {
|
||||||
|
this(value, true, changeListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MyCheckBox(boolean value, boolean enabled, ValueChangeListener<Boolean> changeListener) {
|
||||||
|
setValue(value);
|
||||||
|
setEnabled(enabled);
|
||||||
|
addValueChangeListener(changeListener);
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,10 +18,12 @@ import java.util.function.Supplier;
|
||||||
* @since 14.05.2017
|
* @since 14.05.2017
|
||||||
*/
|
*/
|
||||||
public class ReportList extends MyGrid<Report> implements DataManager.ReportChangeListener {
|
public class ReportList extends MyGrid<Report> implements DataManager.ReportChangeListener {
|
||||||
|
public static final String CAPTION = "Reports";
|
||||||
private final Supplier<List<Report>> reportSupplier;
|
private final Supplier<List<Report>> reportSupplier;
|
||||||
|
|
||||||
public ReportList(String app, NavigationManager navigationManager, DataManager dataManager, Supplier<List<Report>> reportSupplier) {
|
public ReportList(String app, NavigationManager navigationManager, DataManager dataManager, Supplier<List<Report>> reportSupplier) {
|
||||||
super("Reports", reportSupplier.get());
|
super(CAPTION, reportSupplier.get());
|
||||||
|
setId(CAPTION);
|
||||||
this.reportSupplier = reportSupplier;
|
this.reportSupplier = reportSupplier;
|
||||||
setSizeFull();
|
setSizeFull();
|
||||||
setSelectionMode(SelectionMode.NONE);
|
setSelectionMode(SelectionMode.NONE);
|
||||||
|
@ -31,7 +33,7 @@ public class ReportList extends MyGrid<Report> implements DataManager.ReportChan
|
||||||
addColumn(Report::getPhoneModel, "Device");
|
addColumn(Report::getPhoneModel, "Device");
|
||||||
addColumn(report -> report.getStacktrace().split("\n", 2)[0], "Stacktrace").setExpandRatio(1);
|
addColumn(report -> report.getStacktrace().split("\n", 2)[0], "Stacktrace").setExpandRatio(1);
|
||||||
if (SecurityUtils.hasPermission(app, Permission.Level.EDIT)) {
|
if (SecurityUtils.hasPermission(app, Permission.Level.EDIT)) {
|
||||||
addColumn(report -> "Delete", new ButtonRenderer<>(e -> dataManager.remove(e.getItem())));
|
addColumn(report -> "Delete", new ButtonRenderer<>(e -> dataManager.deleteReport(e.getItem())));
|
||||||
}
|
}
|
||||||
addItemClickListener(e -> navigationManager.navigateTo(ReportView.class, e.getItem().getId()));
|
addItemClickListener(e -> navigationManager.navigateTo(ReportView.class, e.getItem().getId()));
|
||||||
addAttachListener(e -> dataManager.addListener(this));
|
addAttachListener(e -> dataManager.addListener(this));
|
||||||
|
|
|
@ -3,15 +3,24 @@ package com.faendir.acra.ui.view.tabs;
|
||||||
import com.faendir.acra.mongod.data.DataManager;
|
import com.faendir.acra.mongod.data.DataManager;
|
||||||
import com.faendir.acra.mongod.data.ReportUtils;
|
import com.faendir.acra.mongod.data.ReportUtils;
|
||||||
import com.faendir.acra.mongod.model.Bug;
|
import com.faendir.acra.mongod.model.Bug;
|
||||||
|
import com.faendir.acra.mongod.model.Permission;
|
||||||
|
import com.faendir.acra.security.SecurityUtils;
|
||||||
import com.faendir.acra.ui.NavigationManager;
|
import com.faendir.acra.ui.NavigationManager;
|
||||||
|
import com.faendir.acra.ui.view.base.MyCheckBox;
|
||||||
import com.faendir.acra.ui.view.base.MyGrid;
|
import com.faendir.acra.ui.view.base.MyGrid;
|
||||||
import com.faendir.acra.ui.view.base.ReportList;
|
import com.faendir.acra.ui.view.base.ReportList;
|
||||||
import com.faendir.acra.util.Style;
|
import com.faendir.acra.util.Style;
|
||||||
import com.faendir.acra.util.TimeSpanRenderer;
|
import com.faendir.acra.util.TimeSpanRenderer;
|
||||||
import com.vaadin.event.selection.SelectionEvent;
|
import com.vaadin.event.selection.SelectionEvent;
|
||||||
import com.vaadin.shared.data.sort.SortDirection;
|
import com.vaadin.shared.data.sort.SortDirection;
|
||||||
|
import com.vaadin.ui.Alignment;
|
||||||
|
import com.vaadin.ui.CheckBox;
|
||||||
|
import com.vaadin.ui.Component;
|
||||||
import com.vaadin.ui.VerticalLayout;
|
import com.vaadin.ui.VerticalLayout;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Lukas
|
* @author Lukas
|
||||||
* @since 17.05.2017
|
* @since 17.05.2017
|
||||||
|
@ -22,19 +31,29 @@ public class BugTab extends VerticalLayout implements DataManager.ReportChangeLi
|
||||||
private final NavigationManager navigationManager;
|
private final NavigationManager navigationManager;
|
||||||
private final DataManager dataManager;
|
private final DataManager dataManager;
|
||||||
private final MyGrid<Bug> bugs;
|
private final MyGrid<Bug> bugs;
|
||||||
|
private final CheckBox hideSolved;
|
||||||
|
|
||||||
public BugTab(String app, NavigationManager navigationManager, DataManager dataManager) {
|
public BugTab(String app, NavigationManager navigationManager, DataManager dataManager) {
|
||||||
this.app = app;
|
this.app = app;
|
||||||
this.navigationManager = navigationManager;
|
this.navigationManager = navigationManager;
|
||||||
this.dataManager = dataManager;
|
this.dataManager = dataManager;
|
||||||
bugs = new MyGrid<>(null, ReportUtils.getBugs(dataManager.getReports(app)));
|
hideSolved = new CheckBox("Hide solved", true);
|
||||||
|
hideSolved.addValueChangeListener(e -> onChange());
|
||||||
|
addComponent(hideSolved);
|
||||||
|
setComponentAlignment(hideSolved, Alignment.MIDDLE_RIGHT);
|
||||||
|
bugs = new MyGrid<>(null, getBugs());
|
||||||
bugs.setSizeFull();
|
bugs.setSizeFull();
|
||||||
bugs.addColumn(bug -> bug.getReports().size(), "Reports");
|
bugs.addColumn(dataManager::countReportsForBug, "Reports");
|
||||||
bugs.sort(bugs.addColumn(Bug::getLastDate, new TimeSpanRenderer(), "Latest Report"), SortDirection.DESCENDING);
|
bugs.sort(bugs.addColumn(bug -> ReportUtils.getLastReportDate(dataManager.getReportsForBug(bug)), new TimeSpanRenderer(), "Latest Report"), SortDirection.DESCENDING);
|
||||||
bugs.addColumn(Bug::getVersionCode, "Version");
|
bugs.addColumn(Bug::getVersionCode, "Version");
|
||||||
bugs.addColumn(bug -> bug.getTrace().split("\n", 2)[0], "Stacktrace").setExpandRatio(1);
|
bugs.addColumn(bug -> bug.getStacktrace().split("\n", 2)[0], "Stacktrace").setExpandRatio(1);
|
||||||
bugs.addSelectionListener(this::handleBugSelection);
|
bugs.addSelectionListener(this::handleBugSelection);
|
||||||
|
bugs.addComponentColumn(bug -> new MyCheckBox(bug.isSolved(), SecurityUtils.hasPermission(app, Permission.Level.EDIT), e -> {
|
||||||
|
dataManager.setBugSolved(bug, e.getValue());
|
||||||
|
onChange();
|
||||||
|
})).setCaption("Solved");
|
||||||
addComponent(bugs);
|
addComponent(bugs);
|
||||||
|
setExpandRatio(bugs, 1);
|
||||||
Style.NO_PADDING.apply(this);
|
Style.NO_PADDING.apply(this);
|
||||||
setSizeFull();
|
setSizeFull();
|
||||||
setCaption(CAPTION);
|
setCaption(CAPTION);
|
||||||
|
@ -43,14 +62,28 @@ public class BugTab extends VerticalLayout implements DataManager.ReportChangeLi
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleBugSelection(SelectionEvent<Bug> e) {
|
private void handleBugSelection(SelectionEvent<Bug> e) {
|
||||||
if (getComponentCount() == 2) {
|
for (Component component : this) {
|
||||||
removeComponent(getComponent(1));
|
if (ReportList.CAPTION.equals(component.getId())) {
|
||||||
|
removeComponent(component);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
e.getFirstSelectedItem().ifPresent(bug -> addComponent(new ReportList(app, navigationManager, dataManager, bug::getReports)));
|
e.getFirstSelectedItem().ifPresent(bug -> {
|
||||||
|
ReportList reportList = new ReportList(app, navigationManager, dataManager, () -> dataManager.getReportsForBug(bug));
|
||||||
|
addComponent(reportList);
|
||||||
|
setExpandRatio(reportList, 1);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onChange() {
|
public void onChange() {
|
||||||
bugs.setItems(ReportUtils.getBugs(dataManager.getReports(app)));
|
bugs.setItems(getBugs());
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Bug> getBugs() {
|
||||||
|
List<Bug> bugs = dataManager.getBugs(app);
|
||||||
|
if (hideSolved.getValue()) {
|
||||||
|
return bugs.stream().filter(bug -> !bug.isSolved()).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
return bugs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,6 @@ import com.faendir.acra.ui.view.base.ReportList;
|
||||||
*/
|
*/
|
||||||
public class ReportTab extends ReportList {
|
public class ReportTab extends ReportList {
|
||||||
public ReportTab(String app, NavigationManager navigationManager, DataManager dataManager) {
|
public ReportTab(String app, NavigationManager navigationManager, DataManager dataManager) {
|
||||||
super(app, navigationManager, dataManager, () -> dataManager.getReports(app));
|
super(app, navigationManager, dataManager, () -> dataManager.getReportsForApp(app));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ public class StatisticsTab extends HorizontalLayout {
|
||||||
numberField.setValue(30);
|
numberField.setValue(30);
|
||||||
numberField.setMinValue(5);
|
numberField.setMinValue(5);
|
||||||
numberField.addValueChangeListener(e -> setTimeChart(e.getValue()));
|
numberField.addValueChangeListener(e -> setTimeChart(e.getValue()));
|
||||||
reports = dataManager.getReports(app);
|
reports = dataManager.getReportsForApp(app);
|
||||||
timeLayout = new VerticalLayout(numberField);
|
timeLayout = new VerticalLayout(numberField);
|
||||||
Style.NO_PADDING.apply(timeLayout);
|
Style.NO_PADDING.apply(timeLayout);
|
||||||
addComponent(timeLayout);
|
addComponent(timeLayout);
|
||||||
|
|
|
@ -1,86 +0,0 @@
|
||||||
package com.faendir.acra.ui.view.user;
|
|
||||||
|
|
||||||
import com.faendir.acra.mongod.data.DataManager;
|
|
||||||
import com.faendir.acra.mongod.model.Permission;
|
|
||||||
import com.faendir.acra.ui.view.base.MyGrid;
|
|
||||||
import com.vaadin.data.HasValue;
|
|
||||||
import com.vaadin.shared.Registration;
|
|
||||||
import com.vaadin.ui.ComboBox;
|
|
||||||
import com.vaadin.ui.Grid;
|
|
||||||
import org.vaadin.hene.popupbutton.PopupButton;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Lukas
|
|
||||||
* @since 20.05.2017
|
|
||||||
*/
|
|
||||||
public class PermissionEditor extends PopupButton implements HasValue<Collection<Permission>> {
|
|
||||||
|
|
||||||
private Collection<Permission> items;
|
|
||||||
private MyGrid<Permission> grid;
|
|
||||||
|
|
||||||
public PermissionEditor(DataManager dataManager) {
|
|
||||||
super("Editor");
|
|
||||||
items = new ArrayList<>();
|
|
||||||
grid = new MyGrid<>(null, items);
|
|
||||||
grid.addColumn(permission -> dataManager.getApp(permission.getApp()).getName(), "App");
|
|
||||||
ComboBox<Permission.Level> levelComboBox = new ComboBox<>("", Arrays.asList(Permission.Level.values()));
|
|
||||||
grid.addColumn(permission -> permission.getLevel().name(), "Level")
|
|
||||||
.setEditorBinding(grid.getEditor().getBinder().bind(levelComboBox, Permission::getLevel, (permission, level) -> {
|
|
||||||
Collection<Permission> newValue = items.stream().map(p -> new Permission(p.getApp(), p.getLevel())).collect(Collectors.toList());
|
|
||||||
newValue.stream().filter(p -> p.getApp().equals(permission.getApp())).findAny().ifPresent(p -> p.setLevel(level));
|
|
||||||
setValue(newValue, true);
|
|
||||||
}));
|
|
||||||
grid.getEditor().setEnabled(true).setBuffered(false);
|
|
||||||
grid.setSelectionMode(Grid.SelectionMode.NONE);
|
|
||||||
setContent(grid);
|
|
||||||
setPopupVisible(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setValue(Collection<Permission> value) {
|
|
||||||
setValue(value, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setValue(Collection<Permission> value, boolean userOriginated) {
|
|
||||||
Collection<Permission> oldValue = items;
|
|
||||||
items = value;
|
|
||||||
grid.setItems(items);
|
|
||||||
grid.setHeightByRows(items.size());
|
|
||||||
fireEvent(new ValueChangeEvent<>(this, oldValue, userOriginated));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Permission> getValue() {
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setRequiredIndicatorVisible(boolean requiredIndicatorVisible) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isRequiredIndicatorVisible() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Registration addValueChangeListener(ValueChangeListener<Collection<Permission>> listener) {
|
|
||||||
return addListener(ValueChangeEvent.class, listener,
|
|
||||||
ValueChangeListener.VALUE_CHANGE_METHOD);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setReadOnly(boolean readOnly) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isReadOnly() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +1,20 @@
|
||||||
package com.faendir.acra.ui.view.user;
|
package com.faendir.acra.ui.view.user;
|
||||||
|
|
||||||
import com.faendir.acra.mongod.data.DataManager;
|
import com.faendir.acra.mongod.data.DataManager;
|
||||||
|
import com.faendir.acra.mongod.model.App;
|
||||||
|
import com.faendir.acra.mongod.model.Permission;
|
||||||
import com.faendir.acra.mongod.model.User;
|
import com.faendir.acra.mongod.model.User;
|
||||||
import com.faendir.acra.mongod.user.UserManager;
|
import com.faendir.acra.mongod.user.UserManager;
|
||||||
import com.faendir.acra.security.SecurityUtils;
|
import com.faendir.acra.security.SecurityUtils;
|
||||||
|
import com.faendir.acra.ui.view.base.MyCheckBox;
|
||||||
import com.faendir.acra.ui.view.base.MyGrid;
|
import com.faendir.acra.ui.view.base.MyGrid;
|
||||||
import com.faendir.acra.ui.view.base.NamedView;
|
import com.faendir.acra.ui.view.base.NamedView;
|
||||||
import com.faendir.acra.util.Style;
|
import com.faendir.acra.util.Style;
|
||||||
import com.vaadin.data.Binder;
|
|
||||||
import com.vaadin.navigator.ViewChangeListener;
|
import com.vaadin.navigator.ViewChangeListener;
|
||||||
import com.vaadin.server.UserError;
|
import com.vaadin.server.UserError;
|
||||||
import com.vaadin.spring.annotation.UIScope;
|
import com.vaadin.spring.annotation.UIScope;
|
||||||
import com.vaadin.ui.Button;
|
import com.vaadin.ui.Button;
|
||||||
import com.vaadin.ui.CheckBox;
|
import com.vaadin.ui.ComboBox;
|
||||||
import com.vaadin.ui.Grid;
|
import com.vaadin.ui.Grid;
|
||||||
import com.vaadin.ui.PasswordField;
|
import com.vaadin.ui.PasswordField;
|
||||||
import com.vaadin.ui.TextField;
|
import com.vaadin.ui.TextField;
|
||||||
|
@ -22,7 +24,7 @@ import com.vaadin.ui.Window;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.Arrays;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Lukas
|
* @author Lukas
|
||||||
|
@ -54,17 +56,28 @@ public class UserManagerView extends NamedView {
|
||||||
@Override
|
@Override
|
||||||
public void enter(ViewChangeListener.ViewChangeEvent event) {
|
public void enter(ViewChangeListener.ViewChangeEvent event) {
|
||||||
userGrid = new MyGrid<>("Users", userManager.getUsers());
|
userGrid = new MyGrid<>("Users", userManager.getUsers());
|
||||||
userGrid.getEditor().setEnabled(true).setBuffered(false);
|
|
||||||
userGrid.setSelectionMode(Grid.SelectionMode.NONE);
|
userGrid.setSelectionMode(Grid.SelectionMode.NONE);
|
||||||
userGrid.addColumn(User::getUsername, "Username");
|
userGrid.addColumn(User::getUsername, "Username");
|
||||||
Binder<User> binder = userGrid.getEditor().getBinder();
|
userGrid.addComponentColumn(user -> new MyCheckBox(user.getRoles().contains(UserManager.ROLE_ADMIN), e -> {
|
||||||
userGrid.addColumn(user -> user.getRoles().contains(UserManager.ROLE_ADMIN) ? "Yes" : "No").setCaption("Admin")
|
if (!e.getValue() && user.getUsername().equals(SecurityUtils.getUsername())) {
|
||||||
.setEditorBinding(binder.forField(new CheckBox())
|
MyCheckBox checkBox = ((MyCheckBox) e.getComponent());
|
||||||
.withValidator(bool -> bool || !binder.getBean().getUsername().equals(SecurityUtils.getUsername()), "Cannot revoke own admin privileges")
|
checkBox.setComponentError(new UserError("Cannot revoke own admin privileges"));
|
||||||
.bind(user -> user.getRoles().contains(UserManager.ROLE_ADMIN), userManager::setAdmin));
|
checkBox.setValue(true);
|
||||||
userGrid.addColumn(this::getPermissionString, "App Permissions")
|
} else {
|
||||||
.setEditorBinding(userGrid.getEditor().getBinder().bind(new PermissionEditor(dataManager), User::getPermissions,
|
userManager.setAdmin(user, e.getValue());
|
||||||
((user, permissions) -> permissions.forEach(p -> userManager.setPermission(user, p.getApp(), p.getLevel())))));
|
}
|
||||||
|
})).setCaption("Admin");
|
||||||
|
for (App app : dataManager.getApps()) {
|
||||||
|
userGrid.addComponentColumn(user -> {
|
||||||
|
Permission permission = user.getPermissions().stream().filter(p -> p.getApp().equals(app.getId())).findAny().orElseThrow(IllegalStateException::new);
|
||||||
|
ComboBox<Permission.Level> levelComboBox = new ComboBox<>(null, Arrays.asList(Permission.Level.values()));
|
||||||
|
levelComboBox.setEmptySelectionAllowed(false);
|
||||||
|
levelComboBox.setValue(permission.getLevel());
|
||||||
|
levelComboBox.addValueChangeListener(e -> userManager.setPermission(user, permission.getApp(), e.getValue()));
|
||||||
|
return levelComboBox;
|
||||||
|
}).setCaption("Access Permission for " + app.getName());
|
||||||
|
}
|
||||||
|
userGrid.setRowHeight(42);
|
||||||
Button newUser = new Button("New User", e -> newUser());
|
Button newUser = new Button("New User", e -> newUser());
|
||||||
VerticalLayout layout = new VerticalLayout(userGrid, newUser);
|
VerticalLayout layout = new VerticalLayout(userGrid, newUser);
|
||||||
layout.setExpandRatio(userGrid, 1);
|
layout.setExpandRatio(userGrid, 1);
|
||||||
|
@ -76,12 +89,6 @@ public class UserManagerView extends NamedView {
|
||||||
Style.apply(this, Style.PADDING_LEFT, Style.PADDING_RIGHT, Style.PADDING_BOTTOM);
|
Style.apply(this, Style.PADDING_LEFT, Style.PADDING_RIGHT, Style.PADDING_BOTTOM);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getPermissionString(User user) {
|
|
||||||
return user.getPermissions().stream()
|
|
||||||
.map(permission -> dataManager.getApp(permission.getApp()).getName() + ": " + permission.getLevel().name())
|
|
||||||
.collect(Collectors.joining(", "));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void newUser() {
|
private void newUser() {
|
||||||
Window window = new Window("New User");
|
Window window = new Window("New User");
|
||||||
TextField name = new TextField("Username");
|
TextField name = new TextField("Username");
|
||||||
|
|
Loading…
Reference in a new issue