Skip to content
This repository has been archived by the owner on Oct 6, 2018. It is now read-only.

Feature to add and remove topics #20

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 66 additions & 1 deletion app/controllers/Topic.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import common.Util._
import play.api.libs.json._
import play.api.libs.json.JsObject
import play.api.libs.iteratee.{Concurrent, Iteratee}
import common.Registry
import common.{Message, Registry}
import common.Registry.PropertyConstants
import java.util
import kafka.javaapi.consumer.EventHandler
Expand All @@ -36,9 +36,25 @@ import com.twitter.zk.{ZNode, ZkClient}
import scala.util.Random
import okapies.finagle.Kafka
import kafka.api.OffsetRequest
import kafka.utils.{ZkUtils, ZKStringSerializer}
import play.api.data.{Forms, Form}
import play.api.libs.concurrent.Akka
import play.api.data.Forms._
import play.api.libs.json.JsArray
import scala.Some
import kafka.message.MessageAndMetadata
import play.api.libs.json.JsObject

object Topic extends Controller {

val topicForm = Forms.tuple(
"name" -> Forms.text,
"group" -> Forms.text,
"zookeeper" -> Forms.text,
"partitions" -> Forms.number,
"replications" -> Forms.number
)

object TopicsWrites extends Writes[List[Map[String, Object]]] {
def writes(l: List[Map[String, Object]]) = {
val topics = l.map { t =>
Expand Down Expand Up @@ -145,6 +161,55 @@ object Topic extends Controller {
(in, out)
}

def create() = Action { implicit request =>
val result = Form(topicForm).bindFromRequest.fold(
formFailure => BadRequest,
formSuccess => {

val name: String = formSuccess._1
val group: String = formSuccess._2
val zookeeper: String = formSuccess._3
val partitions: Int = formSuccess._4
val replications: Int = formSuccess._5

import org.I0Itec.zkclient.ZkClient

val zk = models.Zookeeper.findById(zookeeper).get

val zkClient = new ZkClient(zk.host+":"+zk.port, 30000, 30000, ZKStringSerializer)
try {
kafka.admin.AdminUtils.createTopic(zkClient, name, partitions, replications)

} finally {
zkClient.close()
}

Ok
}

)

result
}

def delete(name: String, zookeeper: String) = Action {

import org.I0Itec.zkclient.ZkClient

val zk = models.Zookeeper.findById(zookeeper).get

val zkClient = new ZkClient(zk.host+":"+zk.port, 30000, 30000, ZKStringSerializer)
try {
//How it is deleted in the DeleteTopicCommand
zkClient.deleteRecursive(ZkUtils.getTopicPath(name))
//kafka.admin.AdminUtils.deleteTopic(zkClient, name)

} finally {
zkClient.close()
}
Ok
}

private def createConsumerConfig(zookeeperAddress: String, gid: String): ConsumerConfig = {
val props = new Properties()
props.put("zookeeper.connect", zookeeperAddress)
Expand Down
2 changes: 2 additions & 0 deletions conf/routes
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ GET /topics controllers.As
GET /topics.json controllers.Topic.index()
GET /topics.json/:name/:zookeeper controllers.Topic.show(name, zookeeper)
GET /topics.json/:name/:zookeeper/feed controllers.Topic.feed(name, zookeeper)
POST /topics.json controllers.Topic.create()
DELETE /topics.json/:name/:zookeeper controllers.Topic.delete(name, zookeeper)
GET /topics/:name/:zookeeper controllers.IgnoreParamAssets.at(path="/public", file="html/partials/topic/show.html", name, zookeeper)

GET /consumergroups.json/:name/:topic/:zookeeper controllers.ConsumerGroup.show(name, topic, zookeeper)
Expand Down
121 changes: 91 additions & 30 deletions public/html/partials/topic/index.html
Original file line number Diff line number Diff line change
@@ -1,30 +1,91 @@
<div class="table-responsive">
<table class="table table-hover">
<thead>
<th></th>
<th>Zookeeper</th>
<th>Topic</th>
<th>Partition</th>
<th>Log Size</th>
</thead>
<tbody data-ng-repeat="topic in topics">
<tr>
<td>
<i class="fa fa-minus-square fa-lg" data-ng-if="topic.expanded" data-ng-click="topic.expanded = false"/>
<i class="fa fa-plus-square fa-lg" data-ng-if="!topic.expanded" data-ng-click="topic.expanded = true"/>
</td>
<td class="topic" data-ng-click="getTopic(topic);">{{topic.zookeeper}}</td>
<td class="topic" data-ng-click="getTopic(topic);">{{topic.name}}</td>
<td class="topic" data-ng-click="getTopic(topic);"></td>
<td class="topic" data-ng-click="getTopic(topic);">{{topic.logSize}}</td>
</tr>
<tr data-ng-if="topic.expanded" class="row-animation" data-ng-repeat="partition in topic.partitions track by $index">
<td></td>
<td></td>
<td></td>
<td>{{$index}}</td>
<td>{{partition.logSize}}</td>
</tr>
</tbody>
</table>
</div>
<ul class="nav nav-tabs">
<li class="active">
<a data-toggle="tab" data-target="#all-topics">All</a>
</li>
<li>
<a data-toggle="tab" data-target="#create-topic">Create Topic</a>
</li>
</ul>

<div class="tab-content">
<div class="tab-pane active" id="all-topics">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<th></th>
<th>Zookeeper</th>
<th>Topic</th>
<th>Partition</th>
<th>Log Size</th>
</thead>
<tbody data-ng-repeat="topic in topics">
<tr>
<td>
<i class="fa fa-minus-square fa-lg" data-ng-if="topic.expanded"
data-ng-click="topic.expanded = false"/>
<i class="fa fa-plus-square fa-lg" data-ng-if="!topic.expanded"
data-ng-click="topic.expanded = true"/>
</td>
<td class="topic" data-ng-click="getTopic(topic);">{{topic.zookeeper}}</td>
<td class="topic" data-ng-click="getTopic(topic);">{{topic.name}}</td>
<td class="topic" data-ng-click="getTopic(topic);"></td>
<td class="topic" data-ng-click="getTopic(topic);">{{topic.logSize}}</td>
<td>
<button type="button" class="btn btn-danger btn-xs" data-ng-click="removeTopic(topic)">
<i class="fa fa-times-circle-o fa-lg"></i></button>
</td>
</tr>
<tr data-ng-if="topic.expanded" class="row-animation"
data-ng-repeat="partition in topic.partitions track by $index">
<td></td>
<td></td>
<td></td>
<td>{{$index}}</td>
<td>{{partition.logSize}}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="tab-pane" id="create-topic">
<form role="form" class="topic-form" name="topicForm" data-ng-submit="createTopic(ctopic)" novalidate>
<div class="form-group"
data-ng-class="{ 'has-error' : topicForm.name.$invalid && !topicForm.name.$pristine }">
<label for="name">Name</label>
<input data-ng-required="true" type="text" id="name" name="name" data-ng-model="ctopic.name"
class="form-control" data-placement="bottom" title="Unique name to identify Topic"
data-toggle="tooltip" data-ng-pattern="/^[a-zA-Z0-9\-]+$/">

<p data-ng-show="topicForm.name.$invalid && !topicForm.name.$error.pattern && !topicForm.name.$pristine" class="help-block">A name
for Topic is required.</p>
<p data-ng-show="topicForm.name.$error.pattern && !topicForm.host.$pristine" class="help-block">
Topic must be in a valid format.</p>
</div>
<div class="form-group">
<label for="group">Zookeeper Group</label>
<select name="group" id="group" ng-change="selectGroup()" data-ng-model="ctopic.group"
data-ng-options="group.name for group in groups"></select>
</div>
<div class="form-group">
<label for="zookeeper">Zookeeper Instance</label>
<select name="zookeeper" id="zookeeper" data-ng-model="ctopic.zookeeper"
data-ng-options="zookeeper.name for zookeeper in zookeepers|filter: {status:'CONNECTED'}"></select>
</div>
<div class="form-group">
<label for="partitions"># of partitions</label>
<input data-ng-required="true" type="text" id="partitions" name="partitions" data-ng-model="ctopic.partitions"
class="form-control" data-placement="bottom" title="Number of partitions" value="1"
data-toggle="tooltip">
</div>
<div class="form-group">
<label for="replication">Replication factor</label>
<input data-ng-required="true" type="text" id="replication" name="replications" data-ng-model="ctopic.replications"
class="form-control" data-placement="bottom" title="Number of replications" value="1"
data-toggle="tooltip">
</div>
<p class="text-center">
<button class="btn btn-large btn-primary" data-ng-disabled="topicForm.$invalid">Create</button>
</p>
</form>
</div>
</div>
37 changes: 36 additions & 1 deletion public/javascripts/topics-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,42 @@ app.controller("TopicsController", function ($scope, $location, $http, $filter)
});
});

$scope.ctopic = {};
$scope.ctopic.partitions = 1;
$scope.ctopic.replications = 1;
$scope.groups = [
{name:'All'},
{name:'Development'},
{name:'Production'},
{name:'Staging'},
{name:'Test'}];

$scope.zookeepers = {};

$scope.getTopic = function (topic) {
$location.path('/topics/' + topic.name + '/' + topic.zookeeper);
};
});
$scope.getZookeepers = function (group) {
$http.get('/zookeepers.json/' + group).
success(function (data) {
$scope.zookeepers = data
});
};

$scope.selectGroup = function () {
$scope.getZookeepers($scope.ctopic.group.name)
};

$scope.createTopic = function (ctopic) {
console.log(ctopic)
$http.post('/topics.json', { name: ctopic.name, group: ctopic.group.name, zookeeper: ctopic.zookeeper.name, partitions: ctopic.partitions, replications: ctopic.replications}).success(function () {
location.reload();
});
};

$scope.removeTopic = function (topic) {
$http.delete('/topics.json/' + topic.name + '/'+topic.zookeeper).success(function () {
location.reload();
});
};
});