From d7005e6cdb14c75065d0db6b7fe72313508c4010 Mon Sep 17 00:00:00 2001 From: andrewinci Date: Fri, 4 Nov 2022 21:12:33 +0000 Subject: [PATCH] feat: add button to delete a topic --- src-tauri/src/api/admin.rs | 7 +++ src-tauri/src/lib/admin/topic_admin.rs | 8 ++++ src-tauri/src/main.rs | 3 +- src/pages/schema-registry/schema.tsx | 4 +- src/pages/topics/main.tsx | 35 +++++++++------ src/pages/topics/topic.tsx | 59 ++++++++++++++++++++++---- src/tauri/admin.ts | 17 ++++---- 7 files changed, 100 insertions(+), 33 deletions(-) diff --git a/src-tauri/src/api/admin.rs b/src-tauri/src/api/admin.rs index c184cfa2..e56e5446 100644 --- a/src-tauri/src/api/admin.rs +++ b/src-tauri/src/api/admin.rs @@ -23,6 +23,13 @@ pub async fn get_topic_info(cluster_id: &str, topic_name: &str, state: tauri::St Ok(cluster.admin_client.get_topic_info(topic_name).await?) } +#[tauri::command] +pub async fn delete_topic(cluster_id: &str, topic_name: &str, state: tauri::State<'_, AppState>) -> Result<()> { + debug!("Retrieve topic info for {}", topic_name); + let cluster = state.get_cluster(cluster_id).await; + Ok(cluster.admin_client.delete_topic(topic_name).await?) +} + #[tauri::command] pub async fn create_topic( cluster_id: &str, diff --git a/src-tauri/src/lib/admin/topic_admin.rs b/src-tauri/src/lib/admin/topic_admin.rs index ba359c5e..b376c615 100644 --- a/src-tauri/src/lib/admin/topic_admin.rs +++ b/src-tauri/src/lib/admin/topic_admin.rs @@ -18,6 +18,7 @@ pub trait TopicAdmin { // topics async fn list_topics(&self) -> Result>; fn get_topic(&self, topic_name: &str) -> Result; + async fn delete_topic(&self, topic_name: &str) -> Result<()>; async fn get_topic_info(&self, topic_name: &str) -> Result; async fn create_topic(&self, topic_name: &str, partitions: i32, isr: i32, compacted: bool) -> Result<()>; async fn get_last_offsets(&self, topic_names: &[&str]) -> Result>>; @@ -48,6 +49,13 @@ impl TopicAdmin for KafkaAdmin { } } + async fn delete_topic(&self, topic_name: &str) -> Result<()> { + self.admin_client + .delete_topics(&[topic_name], &AdminOptions::default()) + .await?; + Ok(()) + } + async fn get_topic_info(&self, topic_name: &str) -> Result { let topic = self.get_topic(topic_name)?; diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 3c0431c0..88a61ceb 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -5,7 +5,7 @@ mod lib; use crate::api::{ admin::{ - create_topic, describe_consumer_group, get_consumer_group_state, get_last_offsets, get_topic_info, + create_topic, delete_topic, describe_consumer_group, get_consumer_group_state, get_last_offsets, get_topic_info, list_consumer_groups, list_topics, set_consumer_group, }, configuration::{get_configuration, write_configuration}, @@ -36,6 +36,7 @@ fn main() { list_topics, get_topic_info, create_topic, + delete_topic, get_last_offsets, // admin consumer groups get_consumer_group_state, diff --git a/src/pages/schema-registry/schema.tsx b/src/pages/schema-registry/schema.tsx index b7e5ce98..0c43fab8 100644 --- a/src/pages/schema-registry/schema.tsx +++ b/src/pages/schema-registry/schema.tsx @@ -46,7 +46,7 @@ export const Schema = ({ schemaName, clusterId }: SchemaProps) => { - {state?.version && } + {state?.version && } {!isLoading && subject && ( @@ -81,7 +81,7 @@ const CustomPrism = styled(Prism)` } `; -const Tool = ({ clusterId, subject, version }: { clusterId: string; subject: string; version: number }) => { +const Tools = ({ clusterId, subject, version }: { clusterId: string; subject: string; version: number }) => { const navigate = useNavigate(); const { success } = useNotifications(); const openDeleteSubjectModal = () => diff --git a/src/pages/topics/main.tsx b/src/pages/topics/main.tsx index 8560058a..73f6dbc8 100644 --- a/src/pages/topics/main.tsx +++ b/src/pages/topics/main.tsx @@ -9,19 +9,26 @@ export const TopicsPage = () => { if (!clusterId) { throw Error("Missing clusterId in path!"); } - return ( - - - navigate(`/cluster/${clusterId}/topic/${activeTopic}`)} - /> - - {topicName && ( - - - - )} - + const topicList = ( + navigate(`/cluster/${clusterId}/topic/${activeTopic}`)} + /> ); + if (topicName) { + return ( + + + {topicList} + + {topicName && ( + + + + )} + + ); + } else { + return topicList; + } }; diff --git a/src/pages/topics/topic.tsx b/src/pages/topics/topic.tsx index bf803472..524560b2 100644 --- a/src/pages/topics/topic.tsx +++ b/src/pages/topics/topic.tsx @@ -1,14 +1,17 @@ -import { ActionIcon, Badge, Button, Center, Container, Group, Loader, Tooltip, Text } from "@mantine/core"; -import { IconInfoCircle } from "@tabler/icons"; +import { ActionIcon, Badge, Button, Center, Container, Group, Loader, Text, Menu } from "@mantine/core"; +import { IconInfoCircle, IconTool, IconTrash } from "@tabler/icons"; import { RecordsList } from "./record-list"; import { getConsumerState, stopConsumer } from "../../tauri/consumer"; import { PageHeader } from "../../components"; import { openConsumerModal } from "./consumer-modal"; import { useQuery } from "@tanstack/react-query"; -import { getLastOffsets, getTopicInfo } from "../../tauri/admin"; +import { deleteTopic, getLastOffsets, getTopicInfo } from "../../tauri/admin"; import { useState } from "react"; import CodeEditor from "@uiw/react-textarea-code-editor"; import { Allotment } from "allotment"; +import { useNavigate } from "react-router-dom"; +import { openConfirmModal } from "@mantine/modals"; +import { useNotifications } from "../../providers"; export const Topic = ({ clusterId, topicName }: { clusterId: string; topicName: string }) => { const { data, isLoading } = useQuery( @@ -50,11 +53,7 @@ export const Topic = ({ clusterId, topicName }: { clusterId: string; topicName: subtitle={`Estimated Records: ${estimatedRecord ?? "..."}, Cleanup policy: ${ topicInfo?.cleanupPolicy ?? "..." }, Partitions: ${topicInfo?.partitionCount ?? "..."}`}> - - - - - + {isLoading && (
@@ -129,3 +128,47 @@ export const Topic = ({ clusterId, topicName }: { clusterId: string; topicName: ); }; + +const Tools = ({ clusterId, topic }: { clusterId: string; topic: string }) => { + const navigate = useNavigate(); + const { success } = useNotifications(); + const openDeleteTopicModal = () => + openConfirmModal({ + title: "Are you sure to delete this topic?", + children: ( + <> + + The topic {topic} will be deleted. This action is not reversible! + + Note: this operation may fail if the ACLs do not allow the deletion. + + ), + labels: { confirm: "Confirm", cancel: "Cancel" }, + onConfirm: async () => + await deleteTopic(clusterId, topic).then((_) => { + success(`Topic ${topic} delete successfully`); + navigate(`/cluster/${clusterId}/topics`); + }), + }); + + const openInfoModal = () => console.log("Not implemented yet"); + + return ( + + + + + + + + Tools + } onClick={openInfoModal}> + Topic info + + } onClick={openDeleteTopicModal}> + Delete topic + + + + ); +}; diff --git a/src/tauri/admin.ts b/src/tauri/admin.ts index 71c8ddc6..a348b5c4 100644 --- a/src/tauri/admin.ts +++ b/src/tauri/admin.ts @@ -11,7 +11,6 @@ export const setConsumerGroup = ( ): Promise => { return invoke("set_consumer_group", { clusterId, consumerGroupName, topics, offsetConfig }).catch( (err: TauriError) => { - console.error(err); addNotification({ type: "error", title: `Unable to create the consumer group ${consumerGroupName}`, @@ -24,7 +23,6 @@ export const setConsumerGroup = ( export const getConsumerGroupState = (clusterId: string, consumerGroupName: string): Promise => { return invoke("get_consumer_group_state", { clusterId, consumerGroupName }).catch((err: TauriError) => { - console.error(err); addNotification({ type: "error", title: `Unable to get the consumer group state`, @@ -41,7 +39,6 @@ export const describeConsumerGroup = ( ): Promise => { return invoke("describe_consumer_group", { clusterId, consumerGroupName, ignoreCache }).catch( (err: TauriError) => { - console.error(err); addNotification({ type: "error", title: `Unable to describe the consumer group`, @@ -54,7 +51,6 @@ export const describeConsumerGroup = ( export const getConsumerGroups = (clusterId: string): Promise => { return invoke("list_consumer_groups", { clusterId }).catch((err: TauriError) => { - console.error(err); addNotification({ type: "error", title: `Unable to retrieve the list of consumer groups`, @@ -72,7 +68,6 @@ export const createTopic = ( compacted: boolean ): Promise => { return invoke("create_topic", { clusterId, topicName, partitions, isr, compacted }).catch((err: TauriError) => { - console.error(err); addNotification({ type: "error", title: `Unable to create the new topic`, @@ -86,7 +81,6 @@ export const listTopics = (clusterId: string): Promise => invoke<{ name: string }[]>("list_topics", { clusterId }) .then((topics) => topics.map((t) => t.name)) .catch((err: TauriError) => { - console.error(err); addNotification({ type: "error", title: `Unable to retrieve the list of topics`, @@ -97,7 +91,6 @@ export const listTopics = (clusterId: string): Promise => export const getTopicInfo = (clusterId: string, topicName: string): Promise => invoke("get_topic_info", { clusterId, topicName }).catch((err: TauriError) => { - console.error(err); addNotification({ type: "error", title: `Unable to retrieve topic info`, @@ -106,9 +99,17 @@ export const getTopicInfo = (clusterId: string, topicName: string): Promise => + invoke("delete_topic", { clusterId, topicName }).catch((err: TauriError) => { + addNotification({ + type: "error", + title: `Unable to delete the topic`, + description: format(err), + }); + throw err; + }); export const getLastOffsets = (clusterId: string, topicNames: [string]): Promise> => invoke>("get_last_offsets", { clusterId, topicNames }).catch((err: TauriError) => { - console.error(err); addNotification({ type: "error", title: `Unable to retrieve the last offset`,