diff --git a/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/controller/cluster/ClusterController.java b/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/controller/cluster/ClusterController.java index ea661c6f..ce71ea67 100644 --- a/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/controller/cluster/ClusterController.java +++ b/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/controller/cluster/ClusterController.java @@ -98,6 +98,7 @@ public String datatable( .withRequestParams(allParams) .withUrl("/cluster") .withLabel("Kafka Clusters") + // Name Column .withColumn(DatatableColumn.newBuilder(Cluster.class) .withFieldName("name") .withLabel("Cluster") @@ -107,6 +108,7 @@ public String datatable( )) .withIsSortable(true) .build()) + // Views Column .withColumn(DatatableColumn.newBuilder(Cluster.class) .withFieldName("id") .withLabel("Views") @@ -116,12 +118,20 @@ public String datatable( )) .withIsSortable(false) .build()) + // SSL Column .withColumn(DatatableColumn.newBuilder(Cluster.class) .withFieldName("isSslEnabled") - .withLabel("SSL") + .withLabel("SSL Enabled") .withRenderTemplate(new YesNoBadgeTemplate<>(Cluster::isSslEnabled)) .withIsSortable(true) .build()) + // SASL Column + .withColumn(DatatableColumn.newBuilder(Cluster.class) + .withFieldName("isSaslEnabled") + .withLabel("SASL Enabled") + .withRenderTemplate(new YesNoBadgeTemplate<>(Cluster::isSaslEnabled)) + .withIsSortable(true) + .build()) .withSearch("name"); // Add datatable attribute diff --git a/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/controller/configuration/user/UserController.java b/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/controller/configuration/user/UserController.java index 1f8a6ced..82ccac6a 100644 --- a/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/controller/configuration/user/UserController.java +++ b/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/controller/configuration/user/UserController.java @@ -29,11 +29,16 @@ import org.sourcelab.kafka.webview.ui.controller.configuration.user.forms.UserForm; import org.sourcelab.kafka.webview.ui.manager.ui.BreadCrumbManager; import org.sourcelab.kafka.webview.ui.manager.ui.FlashMessage; +import org.sourcelab.kafka.webview.ui.manager.ui.datatable.ActionTemplate; +import org.sourcelab.kafka.webview.ui.manager.ui.datatable.ConstraintOperator; +import org.sourcelab.kafka.webview.ui.manager.ui.datatable.Datatable; +import org.sourcelab.kafka.webview.ui.manager.ui.datatable.DatatableColumn; import org.sourcelab.kafka.webview.ui.manager.user.UserManager; import org.sourcelab.kafka.webview.ui.model.User; import org.sourcelab.kafka.webview.ui.model.UserRole; import org.sourcelab.kafka.webview.ui.repository.UserRepository; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; @@ -41,11 +46,13 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.servlet.mvc.support.RedirectAttributes; import javax.validation.Valid; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Optional; /** @@ -68,7 +75,12 @@ public class UserController extends BaseController { * GET Displays main user index. */ @RequestMapping(path = "", method = RequestMethod.GET) - public String index(final UserForm userForm, final Model model, final RedirectAttributes redirectAttributes) { + public String index( + final Model model, + final Pageable pageable, + @RequestParam Map allParams, + final RedirectAttributes redirectAttributes + ) { // Setup breadcrumbs setupBreadCrumbs(model, null, null); @@ -77,10 +89,71 @@ public String index(final UserForm userForm, final Model model, final RedirectAt return "redirect:/"; } - // Retrieve all users - final Iterable usersList = userRepository.findAllByIsActiveOrderByEmailAsc(true); - model.addAttribute("users", usersList); - + final Datatable.Builder builder = Datatable.newBuilder(User.class) + .withRepository(userRepository) + .withPageable(pageable) + .withRequestParams(allParams) + .withUrl("/configuration/user") + .withLabel("Users") + // Only show active users. + .withConstraint("isActive", true, ConstraintOperator.EQUALS) + // With Create Link + .withCreateLink("/configuration/user/create") + // Email Column + .withColumn(DatatableColumn.newBuilder(User.class) + .withFieldName("email") + .withLabel("Email") + .withRenderFunction(User::getEmail) + .withIsSortable(true) + .build()) + // Name Column + .withColumn(DatatableColumn.newBuilder(User.class) + .withFieldName("displayName") + .withLabel("Name") + .withRenderFunction(User::getDisplayName) + .withIsSortable(true) + .build()) + // Role Column + .withColumn(DatatableColumn.newBuilder(User.class) + .withFieldName("role") + .withLabel("Role") + .withRenderFunction((user) -> { + switch (user.getRole()) { + case ROLE_ADMIN: + return "Admin"; + case ROLE_USER: + return "User"; + default: + return "Unknown"; + } + }) + .withIsSortable(true) + .build()) + // Action Column + .withColumn(DatatableColumn.newBuilder(User.class) + .withLabel("Action") + .withFieldName("id") + .withIsSortable(false) + .withHeaderAlignRight() + .withRenderTemplate(ActionTemplate.newBuilder(User.class) + // Edit Link + .withEditLink(User.class, (record) -> "/configuration/user/edit/" + record.getId()) + // Delete Link + .withDeleteLink(User.class, (record) -> "/configuration/user/delete/" + record.getId()) + .build()) + .build()) + .withSearch("email", "displayName"); + // TODO fix filters with enums +// .withFilter(DatatableFilter.newBuilder() +// .withField("role") +// .withLabel("Role") +// .withOption(UserRole.ROLE_ADMIN.name(), "Admin") +// .withOption(UserRole.ROLE_USER.name(), "User") +// .build() +// ); + + // Add datatable attribute + model.addAttribute("datatable", builder.build()); return "configuration/user/index"; } diff --git a/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/manager/ui/datatable/ActionTemplate.java b/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/manager/ui/datatable/ActionTemplate.java new file mode 100644 index 00000000..e91b9cfb --- /dev/null +++ b/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/manager/ui/datatable/ActionTemplate.java @@ -0,0 +1,241 @@ +/** + * MIT License + * + * Copyright (c) 2017, 2018, 2019 SourceLab.org (https://github.com/SourceLabOrg/kafka-webview/) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.sourcelab.kafka.webview.ui.manager.ui.datatable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; + +/** + * Provides Action links template. + * @param Record type. + */ +public class ActionTemplate extends RenderTemplate { + private final List> links; + + /** + * Constructor. + */ + public ActionTemplate(final List> actions) { + super("fragments/datatable/fields/Action", "display"); + this.links = Collections.unmodifiableList(new ArrayList<>(actions)); + } + + public List> getLinks() { + return links; + } + + @Override + List getParameters(final T record) { + // Only parameters are our links. + final List params = new ArrayList<>(); + params.add("_record_"); + params.add("_this_"); + return params; + } + + /** + * Create new ActionLink Builder instance. + * @param type Record type. + * @param Record type. + * @return new Builder instance. + */ + public static Builder newBuilder(Class type) { + return new Builder<>(); + } + + /** + * Defines an Action Link. + * @param Record type. + */ + public static class ActionLink { + private final Function urlFunction; + private final Function labelFunction; + private final String icon; + private final boolean isPost; + + /** + * Constructor. See Builder instance. + * @param urlFunction Function to generate URL. + * @param labelFunction Function to generate label. + * @param icon Icon to display. + * @param isPost Should the link be a POST or get. + */ + public ActionLink( + final Function urlFunction, + final Function labelFunction, + final String icon, + final boolean isPost + ) { + this.urlFunction = urlFunction; + this.labelFunction = labelFunction; + this.icon = icon; + this.isPost = isPost; + } + + public static ActionLinkBuilder newBuilder(final Class type) { + return new ActionLinkBuilder(); + } + + public String getUrl(T record) { + return urlFunction.apply(record); + } + + public String getLabel(T record) { + return labelFunction.apply(record); + } + + public String getIcon() { + return icon; + } + + /** + * Does this action link have an icon. + * @return True if yes, false if not. + */ + public boolean hasIcon() { + if (icon == null || icon.trim().isEmpty()) { + return false; + } + return true; + } + + public boolean isPost() { + return isPost; + } + } + + /** + * ActionLink Builder instance. + * @param Record type. + */ + public static class Builder { + private List> links = new ArrayList<>(); + + private Builder() { + } + + /** + * Add multiple links. + * @param links Links to add. + * @return Builder instance. + */ + public Builder withLinks(List> links) { + Objects.requireNonNull(links); + this.links.clear(); + this.links.addAll(links); + return this; + } + + public Builder withLink(final ActionLink link) { + this.links.add(link); + return this; + } + + /** + * Add a standard Edit link. + * @param type Record type. + * @param urlFunction The url to link to. + * @return Builder instance. + */ + public Builder withEditLink( + final Class type, + final Function urlFunction + ) { + return withLink( + ActionTemplate.ActionLink.newBuilder(type) + .withLabelFunction((record) -> "Edit") + .withUrlFunction(urlFunction) + .withIcon("fa-edit") + .build() + ); + } + + /** + * Add a standard Delete link. + * @param type Record type. + * @param urlFunction The url to link to. + * @return Builder instance. + */ + public Builder withDeleteLink( + final Class type, + final Function urlFunction + ) { + return withLink( + ActionTemplate.ActionLink.newBuilder(type) + .withLabelFunction((record) -> "Delete") + .withUrlFunction(urlFunction) + .withIcon("fa-remove") + .withIsPost(true) + .build() + ); + } + + /** + * Create new ActionTemplate instance from Builder. + * @return ActionType instance. + */ + public ActionTemplate build() { + return new ActionTemplate(links); + } + } + + /** + * Link builder instance. + * @param Record type. + */ + public static class ActionLinkBuilder { + private Function urlFunction; + private Function labelFunction; + private boolean isPost = false; + private String icon; + + public ActionLinkBuilder withUrlFunction(final Function urlFunction) { + this.urlFunction = urlFunction; + return this; + } + + public ActionLinkBuilder withLabelFunction(final Function textFunction) { + this.labelFunction = textFunction; + return this; + } + + public ActionLinkBuilder withIsPost(final boolean post) { + isPost = post; + return this; + } + + public ActionLinkBuilder withIcon(final String icon) { + this.icon = icon; + return this; + } + + public ActionLink build() { + return new ActionLink(urlFunction, labelFunction, icon, isPost); + } + } +} diff --git a/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/manager/ui/datatable/ConstraintOperator.java b/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/manager/ui/datatable/ConstraintOperator.java new file mode 100644 index 00000000..7e6f17af --- /dev/null +++ b/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/manager/ui/datatable/ConstraintOperator.java @@ -0,0 +1,32 @@ +/** + * MIT License + * + * Copyright (c) 2017, 2018, 2019 SourceLab.org (https://github.com/SourceLabOrg/kafka-webview/) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.sourcelab.kafka.webview.ui.manager.ui.datatable; + +/** + * Defines operator for a constraint. + */ +public enum ConstraintOperator { + EQUALS; +} diff --git a/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/manager/ui/datatable/Datatable.java b/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/manager/ui/datatable/Datatable.java index e7f935d5..36e0375a 100644 --- a/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/manager/ui/datatable/Datatable.java +++ b/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/manager/ui/datatable/Datatable.java @@ -31,10 +31,12 @@ import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import javax.persistence.criteria.Path; +import javax.persistence.criteria.Predicate; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -44,6 +46,8 @@ import java.util.Objects; import java.util.Set; +import static org.sourcelab.kafka.webview.ui.manager.ui.datatable.ConstraintOperator.EQUALS; + /** * Aims to be a re-usable datatable UI component backed by a JPA Repository. * @param Type of object being rendered in the datatable. @@ -55,6 +59,11 @@ public class Datatable { private final String url; private final String label; + /** + * Defines query constraints. + */ + private final List constraints; + /** * Columns to display in the table. */ @@ -65,6 +74,11 @@ public class Datatable { */ private final List filters; + /** + * Zero or more links at the top of the table. + */ + private final List links; + /** * Search Field. */ @@ -93,22 +107,26 @@ public class Datatable { public Datatable( final JpaSpecificationExecutor repository, final Pageable pageable, + final List constraints, final Map requestParams, final String url, final String label, final List columns, final List filters, + final List links, final DatatableSearch datatableSearch, final String noRecordsFoundTemplatePath ) { this.repository = Objects.requireNonNull(repository); this.pageable = Objects.requireNonNull(pageable); + this.constraints = Objects.requireNonNull(constraints); this.requestParams = Collections.unmodifiableMap(new HashMap<>(requestParams)); this.url = url; this.label = label; - this.columns = columns; - this.filters = filters; + this.columns = Collections.unmodifiableList(new ArrayList<>(columns)); + this.filters = Collections.unmodifiableList(new ArrayList<>(filters)); this.datatableSearch = datatableSearch; + this.links = Collections.unmodifiableList(new ArrayList<>(links)); this.noRecordsFoundTemplatePath = Objects.requireNonNull(noRecordsFoundTemplatePath); } @@ -276,6 +294,10 @@ public List getFilters() { return filters; } + public List getLinks() { + return links; + } + public boolean hasFilters() { return filters != null && !filters.isEmpty(); } @@ -284,6 +306,10 @@ public boolean hasSearch() { return datatableSearch != null; } + public boolean hasLinks() { + return links != null && !links.isEmpty(); + } + public DatatableSearch getSearch() { return datatableSearch; } @@ -306,18 +332,19 @@ private Page getPage() { searchValue = null; } } - Specification specification; - if (searchValue == null) { - specification = Specification.where(null); - } else { - specification = Specification.where((root, query, builder) -> - builder.like( - builder.lower(root.get(getSearch().getField())), "%" + getSearch().getCurrentSearchTerm().toLowerCase() + "%" - ) - ); + Specification specification = Specification.where(null); + if (searchValue != null) { + // OR all of the search fields + for (final String field : getSearch().getFields()) { + specification = specification.or((root, query, builder) -> + builder.like( + builder.lower(root.get(field)), "%" + getSearch().getCurrentSearchTerm().toLowerCase() + "%" + ) + ); + } } - // Add filter criterias + // Add filter criteria for (final DatatableFilter filter : getFilters()) { // Skip non-provided filters. if (getCurrentFilterValueFor(filter).isEmpty()) { @@ -336,6 +363,20 @@ private Page getPage() { ); } + // Add enforced constraints + for (final DatatableConstraint constraint : constraints) { + specification = specification.and((root, query, builder) -> { + final Path queryPath = root.get(constraint.getField()); + + switch (constraint.getOperator()) { + case EQUALS: + return builder.equal(queryPath, constraint.getValue()); + default: + throw new RuntimeException("Unhandled operator"); + } + }); + } + // Execute page = repository.findAll(specification, pageable); return page; @@ -421,6 +462,8 @@ public static final class Builder { private List columns = new ArrayList<>(); private List filters = new ArrayList<>(); private DatatableSearch datatableSearch; + private List constraints = new ArrayList<>(); + private List links = new ArrayList<>(); // Default no records found template. private String noRecordsFoundTemplatePath = "fragments/datatable/DefaultNoRecordsFound"; @@ -488,16 +531,29 @@ public Builder withSearch(DatatableSearch datatableSearch) { return this; } - public Builder withSearch(final String name) { - return withSearch("Search...", name, null); + public Builder withSearch(final String ... fields) { + return withSearch("Search...", Arrays.asList(fields)); + } + + public Builder withSearch(final String label, final List fields) { + return withSearch(label, fields, null); + } + + public Builder withSearch(final String search, final List fields, final String currentSearchTerm) { + return withSearch(new DatatableSearch(search, fields, currentSearchTerm)); } - public Builder withSearch(final String search, final String name) { - return withSearch(search, name, null); + public Builder withCreateLink(final String url) { + return withLink(new DatatableLink(url, "Create new", "icon-settings")); } - public Builder withSearch(final String search, final String name, final String currentSearchTerm) { - return withSearch(new DatatableSearch(search, name, currentSearchTerm)); + public Builder withLink(final String url, final String label, final String icon) { + return withLink(new DatatableLink(url, label, icon)); + } + + public Builder withLink(final DatatableLink link) { + links.add(link); + return this; } public Builder withNoRecordsFoundTemplate(final String templatePath) { @@ -505,17 +561,26 @@ public Builder withNoRecordsFoundTemplate(final String templatePath) { return this; } + public Builder withConstraint(final String field, final Object value, final ConstraintOperator operator) { + return this.withConstraint(new DatatableConstraint(field, value, operator)); + } + + public Builder withConstraint(final DatatableConstraint constraint) { + this.constraints.add(constraint); + return this; + } + /** * Create new Datatable instance from builder. * @return New datatable instance. */ public Datatable build() { // Inject current search term from request parameters if available and not already set. - if (datatableSearch != null && datatableSearch.getField() != null && datatableSearch.getCurrentSearchTerm() == null) { + if (datatableSearch != null && datatableSearch.getCurrentSearchTerm() == null) { if (requestParams.containsKey("search")) { datatableSearch = new DatatableSearch( datatableSearch.getLabel(), - datatableSearch.getField(), + datatableSearch.getFields(), requestParams.get("search") ); } @@ -524,11 +589,13 @@ public Datatable build() { return new Datatable<>( repository, pageable, + constraints, requestParams, url, label, columns, filters, + links, datatableSearch, noRecordsFoundTemplatePath ); diff --git a/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/manager/ui/datatable/DatatableColumn.java b/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/manager/ui/datatable/DatatableColumn.java index 41fb51fb..1b01d754 100644 --- a/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/manager/ui/datatable/DatatableColumn.java +++ b/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/manager/ui/datatable/DatatableColumn.java @@ -24,10 +24,12 @@ package org.sourcelab.kafka.webview.ui.manager.ui.datatable; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.function.Function; +import java.util.stream.Collectors; /** * Defines a column within a datatable. @@ -38,6 +40,7 @@ public class DatatableColumn { private final String label; private final int colSpan; private final boolean isSortable; + private final List headerCssClasses; private Function renderFunction; private final RenderTemplate renderTemplate; @@ -54,6 +57,7 @@ public class DatatableColumn { */ public DatatableColumn( final String fieldName, final String label, final int colSpan, final boolean isSortable, + final List headerCssClasses, final Function renderFunction, final RenderTemplate renderTemplate ) { @@ -61,6 +65,7 @@ public DatatableColumn( this.label = Objects.requireNonNull(label); this.colSpan = colSpan; this.isSortable = isSortable; + this.headerCssClasses = Collections.unmodifiableList(new ArrayList<>(headerCssClasses)); // One of these may not be null. this.renderFunction = renderFunction; @@ -111,6 +116,10 @@ public boolean isSortable() { return isSortable; } + public String getHeaderCssClasses() { + return String.join(" ", headerCssClasses).trim(); + } + /** * Create a new Builder instance. * @param type The type of record being rendered. @@ -140,6 +149,7 @@ public static final class Builder { private boolean isSortable = true; private Function renderFunction = null; private RenderTemplate renderTemplate = null; + private List headerCssClasses = new ArrayList<>(); private Builder() { } @@ -164,6 +174,11 @@ public Builder withIsSortable(boolean isSortable) { return this; } + public Builder withHeaderAlignRight() { + this.headerCssClasses.add("text-right"); + return this; + } + public Builder withRenderFunction(Function renderFunction) { this.renderFunction = renderFunction; return this; @@ -179,7 +194,7 @@ public Builder withRenderTemplate(final RenderTemplate renderTemplate) { * @return new DatatableColumn. */ public DatatableColumn build() { - return new DatatableColumn(fieldName, label, colSpan, isSortable, renderFunction, renderTemplate); + return new DatatableColumn(fieldName, label, colSpan, isSortable, headerCssClasses, renderFunction, renderTemplate); } } } diff --git a/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/manager/ui/datatable/DatatableConstraint.java b/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/manager/ui/datatable/DatatableConstraint.java new file mode 100644 index 00000000..ce7096be --- /dev/null +++ b/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/manager/ui/datatable/DatatableConstraint.java @@ -0,0 +1,69 @@ +/** + * MIT License + * + * Copyright (c) 2017, 2018, 2019 SourceLab.org (https://github.com/SourceLabOrg/kafka-webview/) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.sourcelab.kafka.webview.ui.manager.ui.datatable; + +import java.util.Objects; + +/** + * Enforced constraint on datatable query. + */ +public class DatatableConstraint { + private final String field; + private final Object value; + private final ConstraintOperator operator; + + /** + * Constructor. + * @param field The field to constrain. + * @param value The value to constraint to. + * @param operator Which operator to use. + */ + public DatatableConstraint(final String field, final Object value, final ConstraintOperator operator) { + this.field = Objects.requireNonNull(field); + this.value = Objects.requireNonNull(value); + this.operator = Objects.requireNonNull(operator); + } + + public String getField() { + return field; + } + + public Object getValue() { + return value; + } + + public ConstraintOperator getOperator() { + return operator; + } + + @Override + public String toString() { + return "DatatableConstraint{" + + "field='" + field + '\'' + + ", value=" + value + + ", operator=" + operator + + '}'; + } +} diff --git a/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/manager/ui/datatable/DatatableFilter.java b/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/manager/ui/datatable/DatatableFilter.java index a4100088..d331c4d8 100644 --- a/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/manager/ui/datatable/DatatableFilter.java +++ b/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/manager/ui/datatable/DatatableFilter.java @@ -24,6 +24,7 @@ package org.sourcelab.kafka.webview.ui.manager.ui.datatable; +import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Objects; @@ -107,4 +108,68 @@ public String toString() { + '}'; } } + + /** + * New Builder instance. + * @return Builder instance. + */ + public static Builder newBuilder() { + return new Builder(); + } + + /** + * Builder instance. + */ + public static final class Builder { + private String label; + private String field; + private List options = new ArrayList<>(); + + private Builder() { + } + + + public Builder withLabel(String label) { + this.label = label; + return this; + } + + public Builder withField(String field) { + this.field = field; + return this; + } + + /** + * Add multiple options for the filter. + * @param options Defined options to add. + * @return Builder instance. + */ + public Builder withOptions(final List options) { + Objects.requireNonNull(options); + this.options.clear(); + this.options.addAll(options); + return this; + } + + public Builder withOption(final String value, final String label) { + return withOption(new DatatableFilter.FilterOption(value, label)); + } + + public Builder withOption(final Number value, final String label) { + return withOption(value.toString(), label); + } + + public Builder withOption(final DatatableFilter.FilterOption option) { + this.options.add(option); + return this; + } + + /** + * Create new DatatableFilter. + * @return DatatableFilter instance. + */ + public DatatableFilter build() { + return new DatatableFilter(label, field, options); + } + } } diff --git a/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/manager/ui/datatable/DatatableLink.java b/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/manager/ui/datatable/DatatableLink.java new file mode 100644 index 00000000..4e282bbb --- /dev/null +++ b/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/manager/ui/datatable/DatatableLink.java @@ -0,0 +1,77 @@ +/** + * MIT License + * + * Copyright (c) 2017, 2018, 2019 SourceLab.org (https://github.com/SourceLabOrg/kafka-webview/) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.sourcelab.kafka.webview.ui.manager.ui.datatable; + +/** + * Defines an action link at the top of a datatable. + */ +public class DatatableLink { + private final String url; + private final String label; + private final String icon; + + /** + * Constructor. + * @param url Url to link to. + * @param label Label for the link. + */ + public DatatableLink(final String url, final String label, final String icon) { + this.url = url; + this.label = label; + this.icon = icon; + } + + public String getUrl() { + return url; + } + + public String getLabel() { + return label; + } + + public String getIcon() { + return icon; + } + + /** + * Does this link have an icon? + * @return true if yes, false if not. + */ + public boolean hasIcon() { + if (icon == null || icon.isEmpty()) { + return false; + } + return true; + } + + @Override + public String toString() { + return "DatatableLink{" + + "url='" + url + '\'' + + ", label='" + label + '\'' + + ", icon='" + icon + '\'' + + '}'; + } +} diff --git a/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/manager/ui/datatable/DatatableSearch.java b/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/manager/ui/datatable/DatatableSearch.java index 7c00b8d4..9021a558 100644 --- a/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/manager/ui/datatable/DatatableSearch.java +++ b/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/manager/ui/datatable/DatatableSearch.java @@ -24,23 +24,30 @@ package org.sourcelab.kafka.webview.ui.manager.ui.datatable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + /** * Represents a searchable field on a datatable. */ public class DatatableSearch { private final String label; - private final String field; + private final List fields; private final String currentSearchTerm; /** * Constructor. - * @param label Human readable display label. - * @param field The underlying field to search over. + * @param label Display label. + * @param fields One or more fields to search across. * @param currentSearchTerm The current search term if defined in a request. */ - public DatatableSearch(final String label, final String field, final String currentSearchTerm) { - this.label = label; - this.field = field; + public DatatableSearch(final String label, final List fields, final String currentSearchTerm) { + Objects.requireNonNull(fields); + + this.label = Objects.requireNonNull(label); + this.fields = Collections.unmodifiableList(new ArrayList<>(fields)); this.currentSearchTerm = currentSearchTerm; } @@ -48,8 +55,8 @@ public String getLabel() { return label; } - public String getField() { - return field; + public List getFields() { + return fields; } public String getCurrentSearchTerm() { @@ -60,7 +67,8 @@ public String getCurrentSearchTerm() { public String toString() { return "DatatableSearch{" + "label='" + label + '\'' - + ", field='" + field + '\'' + + ", fields=" + fields + + ", currentSearchTerm='" + currentSearchTerm + '\'' + '}'; } } diff --git a/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/manager/ui/datatable/RenderTemplate.java b/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/manager/ui/datatable/RenderTemplate.java index 9f8020db..b0a821eb 100644 --- a/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/manager/ui/datatable/RenderTemplate.java +++ b/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/manager/ui/datatable/RenderTemplate.java @@ -80,6 +80,15 @@ public String getParametersStr(T record) { .map((param) -> { if (param instanceof Boolean || param instanceof Number) { return "" + param; + } else if (param instanceof String && "_column_".equals(param)) { + // include reference of column. eh. hacky. + return "${column}"; + } else if (param instanceof String && "_this_".equals(param)) { + return "${column.getRenderTemplate()}"; + } else if (param instanceof String && "_record_".equals(param)) { + return "${record}"; + } else if (param instanceof String && "_datatable_".equals(param)) { + return "${datatable}"; } return "'" + param + "'"; }) diff --git a/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/repository/UserRepository.java b/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/repository/UserRepository.java index 84db028d..90d8bd32 100644 --- a/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/repository/UserRepository.java +++ b/kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/repository/UserRepository.java @@ -25,6 +25,8 @@ package org.sourcelab.kafka.webview.ui.repository; import org.sourcelab.kafka.webview.ui.model.User; +import org.sourcelab.kafka.webview.ui.model.View; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Repository; @@ -32,7 +34,7 @@ * For interacting w/ the User database table. */ @Repository -public interface UserRepository extends CrudRepository { +public interface UserRepository extends CrudRepository, JpaSpecificationExecutor { /** * Find all users, ordered by email, where isActive = parameter diff --git a/kafka-webview-ui/src/main/resources/templates/configuration/user/create.html b/kafka-webview-ui/src/main/resources/templates/configuration/user/create.html index 5240245d..24260bb5 100644 --- a/kafka-webview-ui/src/main/resources/templates/configuration/user/create.html +++ b/kafka-webview-ui/src/main/resources/templates/configuration/user/create.html @@ -46,7 +46,7 @@
diff --git a/kafka-webview-ui/src/main/resources/templates/configuration/user/index.html b/kafka-webview-ui/src/main/resources/templates/configuration/user/index.html index deaa3c45..99938dd1 100644 --- a/kafka-webview-ui/src/main/resources/templates/configuration/user/index.html +++ b/kafka-webview-ui/src/main/resources/templates/configuration/user/index.html @@ -11,61 +11,8 @@
-
-
-
-
- - Users - -
-
- - - - - - - - - - - - - - - - - -
EmailNameRoleAction
- -
-
-
-
- -
+ +
diff --git a/kafka-webview-ui/src/main/resources/templates/fragments/datatable/Columns.html b/kafka-webview-ui/src/main/resources/templates/fragments/datatable/Columns.html index 35acfef5..9516cdc4 100644 --- a/kafka-webview-ui/src/main/resources/templates/fragments/datatable/Columns.html +++ b/kafka-webview-ui/src/main/resources/templates/fragments/datatable/Columns.html @@ -14,7 +14,10 @@ - + diff --git a/kafka-webview-ui/src/main/resources/templates/fragments/datatable/Datatable.html b/kafka-webview-ui/src/main/resources/templates/fragments/datatable/Datatable.html index 63909005..32ea93d3 100644 --- a/kafka-webview-ui/src/main/resources/templates/fragments/datatable/Datatable.html +++ b/kafka-webview-ui/src/main/resources/templates/fragments/datatable/Datatable.html @@ -17,11 +17,15 @@ [[${datatable.getLabel()}]] ([[${datatable.getTotalElements()}]]) + +
+
+
diff --git a/kafka-webview-ui/src/main/resources/templates/fragments/datatable/Filters.html b/kafka-webview-ui/src/main/resources/templates/fragments/datatable/Filters.html index a9fac443..f14a0651 100644 --- a/kafka-webview-ui/src/main/resources/templates/fragments/datatable/Filters.html +++ b/kafka-webview-ui/src/main/resources/templates/fragments/datatable/Filters.html @@ -33,6 +33,7 @@
+
+ +
+ \ No newline at end of file diff --git a/kafka-webview-ui/src/main/resources/templates/fragments/datatable/fields/Action.html b/kafka-webview-ui/src/main/resources/templates/fragments/datatable/fields/Action.html new file mode 100644 index 00000000..08924b3e --- /dev/null +++ b/kafka-webview-ui/src/main/resources/templates/fragments/datatable/fields/Action.html @@ -0,0 +1,40 @@ + + + + + + + + + + + + \ No newline at end of file