2626
2727import com .fasterxml .jackson .core .JsonProcessingException ;
2828import com .fasterxml .jackson .databind .ObjectMapper ;
29+ import org .apache .kafka .common .errors .TimeoutException ;
2930import org .sourcelab .kafka .webview .ui .controller .BaseController ;
3031import org .sourcelab .kafka .webview .ui .controller .configuration .view .forms .ViewForm ;
3132import org .sourcelab .kafka .webview .ui .manager .kafka .KafkaOperations ;
3536import org .sourcelab .kafka .webview .ui .manager .model .view .ViewCopyManager ;
3637import org .sourcelab .kafka .webview .ui .manager .ui .BreadCrumbManager ;
3738import org .sourcelab .kafka .webview .ui .manager .ui .FlashMessage ;
39+ import org .sourcelab .kafka .webview .ui .manager .ui .datatable .ActionTemplate ;
40+ import org .sourcelab .kafka .webview .ui .manager .ui .datatable .Datatable ;
41+ import org .sourcelab .kafka .webview .ui .manager .ui .datatable .DatatableColumn ;
42+ import org .sourcelab .kafka .webview .ui .manager .ui .datatable .DatatableFilter ;
43+ import org .sourcelab .kafka .webview .ui .manager .ui .datatable .LinkTemplate ;
3844import org .sourcelab .kafka .webview .ui .model .Cluster ;
3945import org .sourcelab .kafka .webview .ui .model .Filter ;
4046import org .sourcelab .kafka .webview .ui .model .MessageFormat ;
4753import org .sourcelab .kafka .webview .ui .repository .ViewRepository ;
4854import org .sourcelab .kafka .webview .ui .repository .ViewToFilterOptionalRepository ;
4955import org .springframework .beans .factory .annotation .Autowired ;
56+ import org .springframework .data .domain .Pageable ;
5057import org .springframework .stereotype .Controller ;
5158import org .springframework .transaction .annotation .Transactional ;
5259import org .springframework .ui .Model ;
6471import java .util .ArrayList ;
6572import java .util .HashMap ;
6673import java .util .HashSet ;
74+ import java .util .List ;
6775import java .util .Map ;
6876import java .util .Optional ;
6977import java .util .Set ;
@@ -94,17 +102,122 @@ public class ViewConfigController extends BaseController {
94102 @ Autowired
95103 private KafkaOperationsFactory kafkaOperationsFactory ;
96104
105+
97106 /**
98- * GET Displays main configuration index.
107+ * GET views index.
99108 */
100109 @ RequestMapping (path = "" , method = RequestMethod .GET )
101- public String index (final Model model ) {
110+ public String datatable (
111+ final Model model ,
112+ @ RequestParam (name = "cluster.id" , required = false ) final Long clusterId ,
113+ final Pageable pageable ,
114+ @ RequestParam Map <String ,String > allParams
115+ ) {
102116 // Setup breadcrumbs
103117 setupBreadCrumbs (model , null , null );
104118
105- // Retrieve all message formats
106- final Iterable <View > viewList = viewRepository .findAllByOrderByNameAsc ();
107- model .addAttribute ("views" , viewList );
119+ // Determine if we actually have any clusters setup
120+ // Retrieve all clusters and index by id
121+ final Map <Long , Cluster > clustersById = new HashMap <>();
122+ clusterRepository
123+ .findAllByOrderByNameAsc ()
124+ .forEach ((cluster ) -> clustersById .put (cluster .getId (), cluster ));
125+
126+ // Create a filter
127+ final List <DatatableFilter .FilterOption > filterOptions = new ArrayList <>();
128+ clustersById
129+ .forEach ((id , cluster ) -> filterOptions .add (new DatatableFilter .FilterOption (String .valueOf (id ), cluster .getName ())));
130+ final DatatableFilter filter = new DatatableFilter ("Cluster" , "clusterId" , filterOptions );
131+ model .addAttribute ("filters" , new DatatableFilter [] { filter });
132+
133+ final Datatable .Builder <View > builder = Datatable .newBuilder (View .class )
134+ .withRepository (viewRepository )
135+ .withPageable (pageable )
136+ .withRequestParams (allParams )
137+ .withUrl ("/configuration/view" )
138+ .withLabel ("Views" )
139+ .withCreateLink ("/configuration/view/create" )
140+
141+ // Name Column
142+ .withColumn (DatatableColumn .newBuilder (View .class )
143+ .withFieldName ("name" )
144+ .withLabel ("Name" )
145+ .withIsSortable (true )
146+ .withRenderTemplate (new LinkTemplate <>(
147+ (record ) -> "/view/" + record .getId (),
148+ View ::getName
149+ )).build ())
150+
151+ // Cluster Column
152+ .withColumn (DatatableColumn .newBuilder (View .class )
153+ .withFieldName ("cluster.name" )
154+ .withLabel ("Cluster" )
155+ .withRenderTemplate (new LinkTemplate <>(
156+ (record ) -> "/cluster/" + record .getCluster ().getId (),
157+ (record ) -> record .getCluster ().getName ()
158+ ))
159+ .withIsSortable (true )
160+ .build ())
161+
162+ // Topic Column
163+ .withColumn (DatatableColumn .newBuilder (View .class )
164+ .withFieldName ("topic" )
165+ .withLabel ("Topic" )
166+ .withRenderFunction (View ::getTopic )
167+ .withIsSortable (true )
168+ .build ())
169+
170+ // Partitions Column
171+ .withColumn (DatatableColumn .newBuilder (View .class )
172+ .withFieldName ("partitions" )
173+ .withLabel ("Partitions" )
174+ .withRenderFunction (View ::displayPartitions )
175+ .withIsSortable (false )
176+ .build ())
177+
178+ // Key Format Column
179+ .withColumn (DatatableColumn .newBuilder (View .class )
180+ .withFieldName ("keyMessageFormat.name" )
181+ .withLabel ("Key Format" )
182+ .withRenderFunction ((view ) -> view .getKeyMessageFormat ().getName ())
183+ .withIsSortable (true )
184+ .build ())
185+
186+ // Value Format Column
187+ .withColumn (DatatableColumn .newBuilder (View .class )
188+ .withFieldName ("valueMessageFormat.name" )
189+ .withLabel ("Value Format" )
190+ .withRenderFunction ((view ) -> view .getValueMessageFormat ().getName ())
191+ .withIsSortable (true )
192+ .build ())
193+
194+ // Action Column
195+ .withColumn (DatatableColumn .newBuilder (View .class )
196+ .withLabel ("Action" )
197+ .withFieldName ("id" )
198+ .withIsSortable (false )
199+ .withHeaderAlignRight ()
200+ .withRenderTemplate (ActionTemplate .newBuilder (View .class )
201+ // Edit Link
202+ .withEditLink (View .class , (record ) -> "/configuration/view/edit/" + record .getId ())
203+ // Copy Link
204+ .withCopyLink (View .class , (record ) -> "/configuration/view/copy/" + record .getId ())
205+ // Delete Link
206+ .withDeleteLink (View .class , (record ) -> "/configuration/view/delete/" + record .getId ())
207+ .build ())
208+ .build ())
209+
210+ // Filters
211+ .withFilter (new DatatableFilter ("Cluster" , "cluster.id" , filterOptions ))
212+ .withSearch ("name" );
213+
214+ // Add datatable attribute
215+ model .addAttribute ("datatable" , builder .build ());
216+
217+ // Determine if we have no clusters setup so we can show appropriate inline help.
218+ model .addAttribute ("hasClusters" , !clustersById .isEmpty ());
219+ model .addAttribute ("hasNoClusters" , clustersById .isEmpty ());
220+ model .addAttribute ("hasViews" , viewRepository .count () > 0 );
108221
109222 return "configuration/view/index" ;
110223 }
@@ -113,7 +226,7 @@ public String index(final Model model) {
113226 * GET Displays create view form.
114227 */
115228 @ RequestMapping (path = "/create" , method = RequestMethod .GET )
116- public String createViewForm (final ViewForm viewForm , final Model model ) {
229+ public String createViewForm (final ViewForm viewForm , final Model model , final RedirectAttributes redirectAttributes ) {
117230 // Setup breadcrumbs
118231 if (!model .containsAttribute ("BreadCrumbs" )) {
119232 setupBreadCrumbs (model , "Create" , null );
@@ -147,10 +260,28 @@ public String createViewForm(final ViewForm viewForm, final Model model) {
147260 final TopicDetails topicDetails = operations .getTopicDetails (viewForm .getTopic ());
148261 model .addAttribute ("partitions" , topicDetails .getPartitions ());
149262 }
263+ redirectAttributes .getFlashAttributes ().remove ("ClusterError" );
264+ } catch (final TimeoutException timeoutException ) {
265+ final String reason = timeoutException .getMessage () + " (This may indicate an authentication or connection problem)" ;
266+ // Set error msg
267+ redirectAttributes .addFlashAttribute ("ClusterError" , true );
268+ redirectAttributes .addFlashAttribute (
269+ "FlashMessage" ,
270+ FlashMessage .newDanger (
271+ "Error connecting to cluster '" + cluster .getName () + "': " + reason ,
272+ timeoutException
273+ )
274+ );
150275 }
151276 });
152277 }
153278
279+ // If we had an error.
280+ if (redirectAttributes .getFlashAttributes ().containsKey ("ClusterError" )) {
281+ // Redirect instead.
282+ return "redirect:/configuration/view" ;
283+ }
284+
154285 return "configuration/view/create" ;
155286 }
156287
@@ -215,7 +346,7 @@ public String editViewForm(
215346 }
216347 viewForm .setOptionalFilters (optionalFilterIds );
217348
218- return createViewForm (viewForm , model );
349+ return createViewForm (viewForm , model , redirectAttributes );
219350 }
220351
221352 /**
@@ -246,7 +377,7 @@ public String updateView(
246377
247378 // If we have errors
248379 if (bindingResult .hasErrors ()) {
249- return createViewForm (viewForm , model );
380+ return createViewForm (viewForm , model , redirectAttributes );
250381 }
251382
252383 // If we're updating
@@ -484,9 +615,13 @@ public String copyView(@PathVariable final Long id, final RedirectAttributes red
484615 viewRepository .findById (id ).ifPresent ((view ) -> {
485616 // Create Copy manager
486617 final ViewCopyManager copyManager = new ViewCopyManager (viewRepository );
487- copyManager .copy (view , "Copy of " + view .getName ());
618+ final String newName = "Copy of " + view .getName ();
619+ copyManager .copy (view , newName );
488620
489- redirectAttributes .addFlashAttribute ("FlashMessage" , FlashMessage .newSuccess ("Copied view!" ));
621+ redirectAttributes .addFlashAttribute (
622+ "FlashMessage" ,
623+ FlashMessage .newSuccess ("Copied view using name '" + newName + "'!" )
624+ );
490625 });
491626 }
492627
0 commit comments