Skip to content

Commit fcce2ce

Browse files
authored
[ISSUE-142] Add ability to search brokers, configs, topics (#150)
* Add backend changes to allow searching topics * Add backend code to support removing topics * Add initial UI to remove topics * Better UI for confirming topic removal * First pass search over topics * First pass search over topics * Update version and CHANGELOG * fix changelog * Disable topic deletion for now * Slightly refactor search logic, add search to consumers * More searching * More searching * Update changelog
1 parent bd8b328 commit fcce2ce

File tree

17 files changed

+709
-33
lines changed

17 files changed

+709
-33
lines changed

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22
The format is based on [Keep a Changelog](http://keepachangelog.com/)
33
and this project adheres to [Semantic Versioning](http://semver.org/).
44

5-
## 2.1.5 (UNRELEASED)
5+
## 2.2.0 (UNRELEASED)
66

77
#### Bug fixes
88
- [ISSUE-143](https://github.com/SourceLabOrg/kafka-webview/issues/143) Fix URLs for stream connections when running Kafka-Webview behind a reverse proxy with a URL Prefix.
99

10-
#### New features
10+
#### New Features
11+
- Ability to search various datatables within the Cluster section of the application.
1112

1213
## 2.1.4 (02/19/2019)
1314

dev-cluster/pom.xml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
<parent>
66
<artifactId>kafka-webview</artifactId>
77
<groupId>org.sourcelab</groupId>
8-
<version>2.1.5</version>
8+
<version>2.2.0</version>
99
</parent>
1010
<modelVersion>4.0.0</modelVersion>
1111

1212
<artifactId>dev-cluster</artifactId>
13-
<version>2.1.5</version>
13+
<version>2.2.0</version>
1414

1515
<!-- Require Maven 3.3.9 -->
1616
<prerequisites>
@@ -127,6 +127,4 @@
127127
</plugin>
128128
</plugins>
129129
</build>
130-
131-
132130
</project>

kafka-webview-plugin/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<parent>
66
<groupId>org.sourcelab</groupId>
77
<artifactId>kafka-webview</artifactId>
8-
<version>2.1.5</version>
8+
<version>2.2.0</version>
99
</parent>
1010
<modelVersion>4.0.0</modelVersion>
1111
<artifactId>kafka-webview-plugin</artifactId>

kafka-webview-ui/pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55
<parent>
66
<artifactId>kafka-webview</artifactId>
77
<groupId>org.sourcelab</groupId>
8-
<version>2.1.5</version>
8+
<version>2.2.0</version>
99
</parent>
1010
<modelVersion>4.0.0</modelVersion>
1111
<artifactId>kafka-webview-ui</artifactId>
12-
<version>2.1.5</version>
12+
<version>2.2.0</version>
1313

1414
<!-- Module Description and Ownership -->
1515
<name>Kafka WebView UI</name>

kafka-webview-ui/src/main/frontend/js/app.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,23 @@ var ApiClient = {
326326
}
327327
});
328328
},
329+
removeTopic: function(clusterId, name, callback) {
330+
var payload = JSON.stringify({
331+
name: name
332+
});
333+
jQuery.ajax({
334+
type: 'POST',
335+
url: '/api/cluster/' + clusterId + '/delete/topic',
336+
data: payload,
337+
dataType: 'json',
338+
headers: ApiClient.getCsrfHeader(),
339+
success: callback,
340+
error: ApiClient.defaultErrorHandler,
341+
beforeSend: function(xhr) {
342+
xhr.setRequestHeader('Content-type', 'application/json; charset=utf-8');
343+
}
344+
});
345+
},
329346
modifyTopicConfig: function(clusterId, topic, configName, configValue, callback) {
330347
var payloadJson = {
331348
topic: topic,
@@ -454,3 +471,28 @@ var DateTools = {
454471
return timezone_standard;
455472
}
456473
};
474+
475+
/**
476+
* Common Search Tooling.
477+
*/
478+
var SearchTools = {
479+
doesMatchText : function(searchStr, content) {
480+
// If empty search String, assume match all.
481+
if (searchStr === null || searchStr.length === 0) {
482+
return true;
483+
}
484+
485+
// If content is null, cannot match.
486+
if (content === null) {
487+
return false;
488+
}
489+
490+
// Otherwise check to see if it matches
491+
if (content.toLowerCase().indexOf(searchStr.toLowerCase()) === -1) {
492+
// Doesn't match
493+
return false;
494+
}
495+
// Matches
496+
return true;
497+
}
498+
};

kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/configuration/SecurityConfig.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,9 @@ private void enableUserAuth(final HttpSecurity http) throws Exception {
196196
// Modify topic
197197
"/api/cluster/*/modify/**",
198198

199+
// Delete topic
200+
"/api/cluster/*/delete/**",
201+
199202
// Remove consumer group
200203
"/api/cluster/*/consumer/remove"
201204

kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/controller/api/ApiController.java

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.sourcelab.kafka.webview.ui.controller.api.requests.ConsumeRequest;
3131
import org.sourcelab.kafka.webview.ui.controller.api.requests.ConsumerRemoveRequest;
3232
import org.sourcelab.kafka.webview.ui.controller.api.requests.CreateTopicRequest;
33+
import org.sourcelab.kafka.webview.ui.controller.api.requests.DeleteTopicRequest;
3334
import org.sourcelab.kafka.webview.ui.controller.api.requests.ModifyTopicConfigRequest;
3435
import org.sourcelab.kafka.webview.ui.controller.api.responses.ResultResponse;
3536
import org.sourcelab.kafka.webview.ui.manager.kafka.KafkaOperations;
@@ -70,6 +71,7 @@
7071
import org.springframework.web.bind.annotation.RequestBody;
7172
import org.springframework.web.bind.annotation.RequestMapping;
7273
import org.springframework.web.bind.annotation.RequestMethod;
74+
import org.springframework.web.bind.annotation.RequestParam;
7375
import org.springframework.web.bind.annotation.ResponseBody;
7476
import org.springframework.web.bind.annotation.ResponseStatus;
7577

@@ -225,13 +227,22 @@ public Collection<Integer> getPartitionsForView(@PathVariable final Long id) {
225227
*/
226228
@ResponseBody
227229
@RequestMapping(path = "/cluster/{id}/topics/list", method = RequestMethod.GET, produces = "application/json")
228-
public List<TopicListing> getTopics(@PathVariable final Long id) {
230+
public List<TopicListing> getTopics(@PathVariable final Long id, @RequestParam(required = false) final String search) {
229231
// Retrieve cluster
230232
final Cluster cluster = retrieveClusterById(id);
231233

232234
// Create new Operational Client
233235
try (final KafkaOperations operations = createOperationsClient(cluster)) {
234-
final TopicList topics = operations.getAvailableTopics();
236+
// Get all topics available on cluster.
237+
TopicList topics = operations.getAvailableTopics();
238+
239+
// If search value supplied
240+
if (search != null && !search.trim().isEmpty()) {
241+
// filter
242+
topics = topics.filterByTopicName(search);
243+
}
244+
245+
// return matched topics.
235246
return topics.getTopics();
236247
} catch (final Exception e) {
237248
throw new ApiException("Topics", e);
@@ -349,7 +360,7 @@ public ResultResponse createTopic(@PathVariable final Long id, @RequestBody fina
349360
final boolean result = operations.createTopic(createTopic);
350361

351362
// Quick n dirty json response
352-
return new ResultResponse("CreateTopic", result, "");
363+
return new ResultResponse("CreateTopic", result, "Created topic '" + createTopicRequest.getName() + "'");
353364
} catch (final Exception e) {
354365
throw new ApiException("CreateTopic", e);
355366
}
@@ -386,6 +397,34 @@ public List<ConfigItem> modifyTopicConfig(
386397
}
387398
}
388399

400+
/**
401+
* POST Delete existing topic on cluster.
402+
* This should require ADMIN role.
403+
*
404+
* @TODO explicitly disabled until custom user roles. https://github.com/SourceLabOrg/kafka-webview/issues/157
405+
*/
406+
// @ResponseBody
407+
// @RequestMapping(path = "/cluster/{id}/delete/topic", method = RequestMethod.POST, produces = "application/json")
408+
public ResultResponse deleteTopic(@PathVariable final Long id, @RequestBody final DeleteTopicRequest deleteTopicRequest) {
409+
// Retrieve cluster
410+
final Cluster cluster = retrieveClusterById(id);
411+
412+
final String name = deleteTopicRequest.getName();
413+
if (name == null || name.trim().isEmpty()) {
414+
throw new ApiException("DeleteTopic", "Invalid topic name");
415+
}
416+
417+
// Create new Operational Client
418+
try (final KafkaOperations operations = createOperationsClient(cluster)) {
419+
final boolean result = operations.removeTopic(deleteTopicRequest.getName());
420+
421+
// Quick n dirty json response
422+
return new ResultResponse("DeleteTopic", result, "Removed topic '" + deleteTopicRequest.getName() + "'");
423+
} catch (final Exception e) {
424+
throw new ApiException("DeleteTopic", e);
425+
}
426+
}
427+
389428
/**
390429
* GET Nodes within a cluster.
391430
*/
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/**
2+
* MIT License
3+
*
4+
* Copyright (c) 2017, 2018, 2019 SourceLab.org (https://github.com/SourceLabOrg/kafka-webview/)
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
25+
package org.sourcelab.kafka.webview.ui.controller.api.requests;
26+
27+
/**
28+
* Represents a request to remove an existing topic from kafka cluster.
29+
*/
30+
public class DeleteTopicRequest {
31+
private String name;
32+
33+
public String getName() {
34+
return name;
35+
}
36+
37+
public void setName(final String name) {
38+
this.name = name;
39+
}
40+
41+
@Override
42+
public String toString() {
43+
return "DeleteTopicRequest{"
44+
+ "name='" + name + '\''
45+
+ '}';
46+
}
47+
}

kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/manager/kafka/KafkaOperations.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
import org.apache.kafka.clients.admin.ConfigEntry;
3131
import org.apache.kafka.clients.admin.CreateTopicsResult;
3232
import org.apache.kafka.clients.admin.DeleteConsumerGroupsResult;
33+
import org.apache.kafka.clients.admin.DeleteTopicsOptions;
34+
import org.apache.kafka.clients.admin.DeleteTopicsResult;
3335
import org.apache.kafka.clients.admin.DescribeConfigsResult;
3436
import org.apache.kafka.clients.admin.DescribeConsumerGroupsResult;
3537
import org.apache.kafka.clients.admin.ListConsumerGroupOffsetsResult;
@@ -291,6 +293,26 @@ public TopicConfig alterTopicConfig(final String topic, final Map<String, String
291293
}
292294
}
293295

296+
/**
297+
* Remove a topic from a kafka cluster. This is destructive!
298+
* @param topic name of the topic to remove.
299+
* @return true on success.
300+
*/
301+
public boolean removeTopic(final String topic) {
302+
try {
303+
final DeleteTopicsResult result = adminClient.deleteTopics(Collections.singletonList(topic));
304+
305+
// Wait for the async request to process.
306+
result.all().get();
307+
308+
// return true?
309+
return true;
310+
} catch (final InterruptedException | ExecutionException exception) {
311+
// TODO Handle this
312+
throw new RuntimeException(exception.getMessage(), exception);
313+
}
314+
}
315+
294316
/**
295317
* List all consumer groups on server.
296318
* @return Immutable sorted list of Consumer Group Identifiers.

kafka-webview-ui/src/main/java/org/sourcelab/kafka/webview/ui/manager/kafka/dto/TopicList.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,28 @@ public List<String> getTopicNames() {
6464
return Collections.unmodifiableList(topicNames);
6565
}
6666

67+
/**
68+
* Given a search value for topic names, return all entries that match search.
69+
* @param search search string.
70+
* @return filtered TopicList.
71+
*/
72+
public TopicList filterByTopicName(final String search) {
73+
// Null means match nothing.
74+
if (search == null) {
75+
return new TopicList(Collections.emptyList());
76+
}
77+
final String normalizedSearch = search.toLowerCase();
78+
79+
final List<TopicListing> topicListings = new ArrayList<>();
80+
for (final TopicListing topicListing : getTopics()) {
81+
if (topicListing.getName().toLowerCase().contains(normalizedSearch)) {
82+
topicListings.add(topicListing);
83+
}
84+
}
85+
86+
return new TopicList(topicListings);
87+
}
88+
6789
@Override
6890
public String toString() {
6991
return "TopicList{"

0 commit comments

Comments
 (0)