diff --git a/README.md b/README.md index 94b19d1..fa988dd 100644 --- a/README.md +++ b/README.md @@ -23,5 +23,5 @@ Mesages displayed in the message list can be exportet in csv format and played i Allows for defining and configurating topics once and apply them to different clusters with one file, see the [Wiki]("https://github.com/patschuh/KafkaEsque/wiki/Topic-Templates") for Details *** ### Message Books -Allows for playing a set of Messages over different topics into a cluster, see [Wiki]("https://github.com/patschuh/KafkaEsque/wiki/Message-Books") for details +Allows for playing a set of Messages over different topics into a cluster, see the [Wiki]("https://github.com/patschuh/KafkaEsque/wiki/Message-Books") for details *** diff --git a/src/main/java/at/esque/kafka/Controller.java b/src/main/java/at/esque/kafka/Controller.java index 93c2208..0c7b420 100644 --- a/src/main/java/at/esque/kafka/Controller.java +++ b/src/main/java/at/esque/kafka/Controller.java @@ -7,6 +7,7 @@ import at.esque.kafka.cluster.ClusterConfig; import at.esque.kafka.cluster.KafkaesqueAdminClient; import at.esque.kafka.cluster.TopicMessageTypeConfig; +import at.esque.kafka.controls.FilterableListView; import at.esque.kafka.controls.JsonTreeView; import at.esque.kafka.dialogs.ClusterConfigDialog; import at.esque.kafka.dialogs.DeleteClustersDialog; @@ -160,12 +161,10 @@ public class Controller { @FXML private TableColumn messageTimestampColumn; @FXML - private ListView topicListView; + private FilterableListView topicListView; @FXML private ComboBox clusterComboBox; @FXML - private Button refreshTopicListButton; - @FXML private MenuItem playMessageBookMenu; @FXML private ComboBox fetchModeCombobox; @@ -186,8 +185,6 @@ public class Controller { @FXML private Label taskProgressLabel; @FXML - private TextField topicFilterTextField; - @FXML private TextField messageSearchTextField; @FXML private Button interruptMessagePollingButton; @@ -198,7 +195,7 @@ public class Controller { private YAMLMapper yamlMapper = new YAMLMapper(); private String selectedTopic() { - return topicListView.getSelectionModel().getSelectedItem(); + return topicListView.getListView().getSelectionModel().getSelectedItem(); } @@ -264,7 +261,7 @@ public void setup(Stage controlledStage) { refreshTopicList(newValue); }); - topicListView.setCellFactory(lv -> topicListCellFactory()); + topicListView.getListView().setCellFactory(lv -> topicListCellFactory()); messageSearchTextField.textProperty().addListener((observable, oldValue, newValue) -> filteredMessages.setPredicate(km -> (km.getKey() != null && StringUtils.containsIgnoreCase(km.getKey(), newValue) || (km.getValue() != null && StringUtils.containsIgnoreCase(km.getValue(), newValue))))); @@ -281,9 +278,6 @@ public void setup(Stage controlledStage) { setupClusterCombobox(); clusterComboBox.setItems(configHandler.loadOrCreateConfigs().getClusterConfigs()); - topicFilterTextField.textProperty().addListener(((observable, oldValue, newValue) -> - ((FilteredList) topicListView.getItems()).setPredicate(t -> StringUtils.containsIgnoreCase(t, newValue)) - )); jsonTreeView.jsonStringProperty().bind(valueTextArea.textProperty()); jsonTreeView.visibleProperty().bind(formatJsonToggle.selectedProperty()); bindDisableProperties(); @@ -313,7 +307,6 @@ private void bindDisableProperties() { publishMessageButton.disableProperty().bind(backgroundTaskInProgressProperty); clusterComboBox.disableProperty().bind(backgroundTaskInProgressProperty); playMessageBookMenu.disableProperty().bind(backgroundTaskInProgressProperty); - refreshTopicListButton.disableProperty().bind(backgroundTaskInProgressProperty); editClusterButton.disableProperty().bind(clusterComboBox.getSelectionModel().selectedItemProperty().isNull()); } @@ -435,8 +428,6 @@ private ListCell topicListCellFactory() { } private void refreshTopicList(ClusterConfig newValue) { - Platform.runLater(() -> topicListView.getItems().clear()); - backGroundTaskHolder.setBackGroundTaskDescription("getting Topics..."); runInDaemonThread(() -> getTopicsForCluster(newValue)); } @@ -447,9 +438,7 @@ private void getTopicsForCluster(ClusterConfig clusterConfig) { stopWatch.start(); LOGGER.info("Started getting topics for cluster"); backGroundTaskHolder.setIsInProgress(true); - ObservableList topics = FXCollections.observableArrayList(adminClient.getTopics()); - FilteredList filteredTopics = new FilteredList<>(topics.sorted(), t -> true); - Platform.runLater(() -> topicListView.setItems(filteredTopics)); + Platform.runLater(() -> topicListView.setItems(adminClient.getTopics())); } finally { stopWatch.stop(); LOGGER.info("Finished getting topics for cluster [{}]", stopWatch); @@ -983,7 +972,7 @@ public void playMessageBook(ActionEvent event) { List messagesToSend = new ArrayList<>(); Platform.runLater(() -> backGroundTaskHolder.setBackGroundTaskDescription("Playing Message Book: scanning messages")); listedFiles.forEach(file -> { - if (!topicListView.getItems().contains(file.getName())) { + if (!topicListView.getBaseList().contains(file.getName())) { throw new RuntimeException(String.format("No such topic [%s] in current cluster", file.getName())); } addMessagesToSend(messagesToSend, file); diff --git a/src/main/java/at/esque/kafka/CrossClusterController.java b/src/main/java/at/esque/kafka/CrossClusterController.java index d9ca77b..981978f 100644 --- a/src/main/java/at/esque/kafka/CrossClusterController.java +++ b/src/main/java/at/esque/kafka/CrossClusterController.java @@ -6,6 +6,7 @@ import at.esque.kafka.cluster.CrossClusterOperation; import at.esque.kafka.cluster.KafkaesqueAdminClient; import at.esque.kafka.cluster.TopicMessageTypeConfig; +import at.esque.kafka.controls.FilterableListView; import at.esque.kafka.handlers.ConfigHandler; import at.esque.kafka.handlers.ConsumerHandler; import at.esque.kafka.handlers.CrossClusterOperationHandler; @@ -52,9 +53,9 @@ public class CrossClusterController { private TextField amountLimit; @FXML - private ListView fromClusterTopicsList; + private FilterableListView fromClusterTopicsList; @FXML - private ListView toClusterTopicsList; + private FilterableListView toClusterTopicsList; @FXML private ListView runningOperationsList; @FXML @@ -123,7 +124,7 @@ public void setup() { refreshOperationList(null); } - private void setupClusterControls(ClusterConfig clusterConfig, KafkaesqueAdminClient adminClient, ListView topicList) { + private void setupClusterControls(ClusterConfig clusterConfig, KafkaesqueAdminClient adminClient, FilterableListView topicList) { if (adminClient != null) { adminClient.close(); } @@ -131,7 +132,7 @@ private void setupClusterControls(ClusterConfig clusterConfig, KafkaesqueAdminCl KafkaesqueAdminClient finalAdminClient = adminClient; runInDaemonThread(() -> { ObservableList topics = FXCollections.observableArrayList(finalAdminClient.getTopics()); - Platform.runLater(() -> topicList.setItems(topics.sorted())); + Platform.runLater(() -> topicList.setItems(topics)); }); } @@ -198,8 +199,8 @@ public void startOperationClick(ActionEvent actionEvent) { ClusterConfig fromCluster = fromClusterComboBox.getSelectionModel().getSelectedItem(); ClusterConfig toCluster = toClusterComboBox.getSelectionModel().getSelectedItem(); - String fromTopicName = fromClusterTopicsList.getSelectionModel().getSelectedItem(); - String toTopicName = toClusterTopicsList.getSelectionModel().getSelectedItem(); + String fromTopicName = fromClusterTopicsList.getListView().getSelectionModel().getSelectedItem(); + String toTopicName = toClusterTopicsList.getListView().getSelectionModel().getSelectedItem(); TopicMessageTypeConfig fromTopic = configHandler.getConfigForTopic(fromCluster.getIdentifier(), fromTopicName); TopicMessageTypeConfig toTopic = configHandler.getConfigForTopic(toCluster.getIdentifier(), toTopicName); diff --git a/src/main/java/at/esque/kafka/SchemaRegistryBrowserController.java b/src/main/java/at/esque/kafka/SchemaRegistryBrowserController.java index a9fa9b9..8f4b81b 100644 --- a/src/main/java/at/esque/kafka/SchemaRegistryBrowserController.java +++ b/src/main/java/at/esque/kafka/SchemaRegistryBrowserController.java @@ -1,20 +1,24 @@ package at.esque.kafka; import at.esque.kafka.alerts.ErrorAlert; +import at.esque.kafka.controls.FilterableListView; import at.esque.kafka.controls.JsonTreeView; import io.confluent.kafka.schemaregistry.client.rest.RestService; import javafx.collections.FXCollections; +import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.scene.control.ComboBox; import javafx.scene.control.ListView; import javafx.scene.control.TextArea; +import java.util.Arrays; + public class SchemaRegistryBrowserController { private RestService schemaRegistryRestService; @FXML - private ListView subjectListView; + private FilterableListView subjectListView; @FXML private TextArea schemaTextArea; @FXML @@ -27,17 +31,17 @@ public void setup(String schemaregistryUrl) { jsonTreeView.jsonStringProperty().bind(schemaTextArea.textProperty()); try { versionComboBox.getSelectionModel().selectedItemProperty().addListener(((observable1, oldValue1, newValue1) -> { - if(newValue1 == null){ + if (newValue1 == null) { schemaTextArea.setText(null); return; } try { - schemaTextArea.setText(JsonUtils.formatJson(schemaRegistryRestService.getVersion(subjectListView.getSelectionModel().getSelectedItem(), newValue1).getSchema())); + schemaTextArea.setText(JsonUtils.formatJson(schemaRegistryRestService.getVersion(subjectListView.getListView().getSelectionModel().getSelectedItem(), newValue1).getSchema())); } catch (Exception e) { ErrorAlert.show(e); } })); - subjectListView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { + subjectListView.getListView().getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { try { versionComboBox.setItems(FXCollections.observableArrayList(schemaRegistryRestService.getAllVersions(newValue))); if (versionComboBox.getItems().size() > 0) { diff --git a/src/main/java/at/esque/kafka/controls/FilterableListView.java b/src/main/java/at/esque/kafka/controls/FilterableListView.java new file mode 100644 index 0000000..34e1c95 --- /dev/null +++ b/src/main/java/at/esque/kafka/controls/FilterableListView.java @@ -0,0 +1,162 @@ +package at.esque.kafka.controls; + +import javafx.beans.binding.Bindings; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ObjectPropertyBase; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.collections.transformation.FilteredList; +import javafx.collections.transformation.SortedList; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.control.Button; +import javafx.scene.control.ListView; +import javafx.scene.control.TextField; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; +import org.apache.commons.lang3.StringUtils; + +import java.io.IOException; +import java.util.Collection; + +public class FilterableListView extends VBox { + @FXML + private TextField filtertextField; + @FXML + private ListView listView; + @FXML + private HBox toolbarBox; + @FXML + private Button addButton; + @FXML + private Button refreshButton; + + private ObservableList baseList; + private FilteredList filteredList; + private SortedList sortedList; + + public BooleanProperty addButtonVisible = new SimpleBooleanProperty(true); + public BooleanProperty refreshButtonVisible = new SimpleBooleanProperty(true); + + public FilterableListView() { + FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource( + "/fxml/controls/filterableListView.fxml")); + fxmlLoader.setRoot(this); + fxmlLoader.setController(this); + try { + fxmlLoader.load(); + } catch (IOException exception) { + throw new RuntimeException(exception); + } + setup(); + } + + private void setup() { + baseList = FXCollections.observableArrayList(); + filteredList = new FilteredList<>(baseList); + sortedList = new SortedList<>(filteredList); + listView.setItems(sortedList); + + filtertextField.textProperty().addListener((observable, oldValue, newValue) -> { + filteredList.setPredicate(item -> StringUtils.isEmpty(newValue) + || StringUtils.containsIgnoreCase(item, newValue)); + }); + sortedList.setComparator(String::compareTo); + + bindButtonProperties(); + } + + private void bindButtonProperties() { + addButton.onActionProperty().bind(onAddActionProperty()); + addButton.visibleProperty().bind(addButtonVisibleProperty()); + refreshButton.visibleProperty().bind(refreshButtonVisibleProperty()); + addButton.maxWidthProperty().bind(Bindings.when(addButtonVisibleProperty()).then(Region.USE_COMPUTED_SIZE).otherwise(0)); + addButton.minWidthProperty().bind(Bindings.when(addButtonVisibleProperty()).then(Region.USE_COMPUTED_SIZE).otherwise(0)); + refreshButton.maxWidthProperty().bind(Bindings.when(refreshButtonVisibleProperty()).then(Region.USE_COMPUTED_SIZE).otherwise(0)); + refreshButton.minWidthProperty().bind(Bindings.when(refreshButtonVisibleProperty()).then(Region.USE_COMPUTED_SIZE).otherwise(0)); + } + + public ObservableList getBaseList() { + return baseList; + } + + public void setItems(Collection items) { + baseList.clear(); + baseList.addAll(items); + } + + public void addItems(Collection items) { + baseList.addAll(items); + } + + public ListView getListView() { + return listView; + } + + public boolean isAddButtonVisible() { + return addButtonVisible.get(); + } + + public BooleanProperty addButtonVisibleProperty() { + return addButtonVisible; + } + + public void setAddButtonVisible(boolean addButtonVisible) { + this.addButtonVisible.set(addButtonVisible); + } + + public boolean isRefreshButtonVisible() { + return refreshButtonVisible.get(); + } + + public BooleanProperty refreshButtonVisibleProperty() { + return refreshButtonVisible; + } + + public void setRefreshButtonVisible(boolean refreshButtonVisible) { + this.refreshButtonVisible.set(refreshButtonVisible); + } + + public final ObjectProperty> onAddActionProperty() { return onAddAction; } + public final void setOnAddAction(EventHandler value) { onAddActionProperty().set(value); } + public final EventHandler getOnAddAction() { return onAddActionProperty().get(); } + private ObjectProperty> onAddAction = new ObjectPropertyBase>() { + @Override protected void invalidated() { + setEventHandler(ActionEvent.ACTION, get()); + } + + @Override + public Object getBean() { + return FilterableListView.this; + } + + @Override + public String getName() { + return "onAddAction"; + } + }; + + public final ObjectProperty> onRefreshActionProperty() { return onRefreshAction; } + public final void setOnRefreshAction(EventHandler value) { onRefreshActionProperty().set(value); } + public final EventHandler getOnRefreshAction() { return onRefreshActionProperty().get(); } + private ObjectProperty> onRefreshAction = new ObjectPropertyBase>() { + @Override protected void invalidated() { + setEventHandler(ActionEvent.ACTION, get()); + } + + @Override + public Object getBean() { + return FilterableListView.this; + } + + @Override + public String getName() { + return "onRefreshAction"; + } + }; +} diff --git a/src/main/resources/fxml/controls/filterableListView.fxml b/src/main/resources/fxml/controls/filterableListView.fxml new file mode 100644 index 0000000..1e02514 --- /dev/null +++ b/src/main/resources/fxml/controls/filterableListView.fxml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/fxml/crossClusterOperation.fxml b/src/main/resources/fxml/crossClusterOperation.fxml index bcdbebe..29620a5 100644 --- a/src/main/resources/fxml/crossClusterOperation.fxml +++ b/src/main/resources/fxml/crossClusterOperation.fxml @@ -1,5 +1,7 @@ + + @@ -12,102 +14,91 @@ - - + + - + - - - + + + - - - - - - + + + + + + - - - - + + - - + + - - - + diff --git a/src/main/resources/fxml/mainScene.fxml b/src/main/resources/fxml/mainScene.fxml index e45c264..d6b563d 100644 --- a/src/main/resources/fxml/mainScene.fxml +++ b/src/main/resources/fxml/mainScene.fxml @@ -1,20 +1,20 @@ + + - - + - @@ -25,19 +25,14 @@ - - + +
-
- -
@@ -85,38 +80,14 @@ - - - - - - - - +
+ +
@@ -210,10 +181,9 @@ - + - + diff --git a/src/main/resources/fxml/schemaRegistryBrowser.fxml b/src/main/resources/fxml/schemaRegistryBrowser.fxml index e66c2f8..ca250a0 100644 --- a/src/main/resources/fxml/schemaRegistryBrowser.fxml +++ b/src/main/resources/fxml/schemaRegistryBrowser.fxml @@ -9,11 +9,12 @@ +
- +