diff --git a/dashboard/components/Layout.tsx b/dashboard/components/Layout.tsx index 7be9f727bddec..6daa7e821ce3c 100644 --- a/dashboard/components/Layout.tsx +++ b/dashboard/components/Layout.tsx @@ -140,6 +140,7 @@ function Layout({ children }: { children: React.ReactNode }) { Internal Tables Sinks Views + Subscriptions
Streaming diff --git a/dashboard/components/Relations.tsx b/dashboard/components/Relations.tsx index 8f536897fb808..988cd7be4f8c6 100644 --- a/dashboard/components/Relations.tsx +++ b/dashboard/components/Relations.tsx @@ -183,12 +183,14 @@ export function Relations( {extraColumns.map((c) => ( {c.content(r)} ))} - - {r.columns - .filter((col) => ("isHidden" in col ? !col.isHidden : true)) - .map((col) => extractColumnInfo(col)) - .join(", ")} - + {r.columns && r.columns.length > 0 && ( + + {r.columns + .filter((col) => ("isHidden" in col ? !col.isHidden : true)) + .map((col) => extractColumnInfo(col)) + .join(", ")} + + )} ))} diff --git a/dashboard/lib/api/streaming.ts b/dashboard/lib/api/streaming.ts index 1a8e97081caa4..1dc31d407258b 100644 --- a/dashboard/lib/api/streaming.ts +++ b/dashboard/lib/api/streaming.ts @@ -24,6 +24,7 @@ import { Source, Table, View, + Subscription, } from "../../proto/gen/catalog" import { ListObjectDependenciesResponse_ObjectDependencies as ObjectDependencies, @@ -47,9 +48,9 @@ export interface Relation { owner: number schemaId: number databaseId: number - columns: (ColumnCatalog | Field)[] // For display + columns?: (ColumnCatalog | Field)[] ownerName?: string schemaName?: string databaseName?: string @@ -98,7 +99,8 @@ export async function getRelations() { await getTables(), await getIndexes(), await getSinks(), - await getSources() + await getSources(), + await getSubscriptions(), ) relations = sortBy(relations, (x) => x.id) return relations @@ -150,6 +152,12 @@ export async function getViews() { return views } +export async function getSubscriptions() { + let subscriptions: Subscription[] = (await api.get("/subscriptions")).map(Subscription.fromJSON) + subscriptions = sortBy(subscriptions, (x) => x.id) + return subscriptions +} + export async function getUsers() { let users: UserInfo[] = (await api.get("/users")).map(UserInfo.fromJSON) users = sortBy(users, (x) => x.id) diff --git a/dashboard/mock-server.js b/dashboard/mock-server.js index 2db52df788e22..50c55e12686b8 100644 --- a/dashboard/mock-server.js +++ b/dashboard/mock-server.js @@ -45,6 +45,10 @@ app.get("/indexes", (req, res, next) => { res.json(require("./mock/indexes.json")) }) +app.get("/indexes", (req, res, next) => { + res.json(require("./mock/indexes.json")) +}) + app.get("/internal_tables", (req, res, next) => { res.json(require("./mock/internal_tables.json")) }) diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json index 1c462e1675207..e7395077e86ad 100644 --- a/dashboard/package-lock.json +++ b/dashboard/package-lock.json @@ -6,6 +6,7 @@ "": { "hasInstallScript": true, "dependencies": { + "16": "^0.0.2", "@chakra-ui/react": "^2.3.1", "@emotion/react": "^11.10.4", "@emotion/styled": "^11.10.4", @@ -3350,6 +3351,14 @@ "resolved": "https://registry.npmjs.org/@zag-js/focus-visible/-/focus-visible-0.1.0.tgz", "integrity": "sha512-PeaBcTmdZWcFf7n1aM+oiOdZc+sy14qi0emPIeUuGMTjbP0xLGrZu43kdpHnWSXy7/r4Ubp/vlg50MCV8+9Isg==" }, + "node_modules/16": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/16/-/16-0.0.2.tgz", + "integrity": "sha512-AhG4lpdn+/it+U5Xl1bm5SbaHYTH5NfU/vXZkP7E7CHjtVtITuFVZKa3AZP3gN38RDJHYYtEqWmqzCutlXaR7w==", + "dependencies": { + "numeric": "^1.2.6" + } + }, "node_modules/abab": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", @@ -8680,6 +8689,11 @@ "set-blocking": "^2.0.0" } }, + "node_modules/numeric": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/numeric/-/numeric-1.2.6.tgz", + "integrity": "sha512-avBiDAP8siMa7AfJgYyuxw1oyII4z2sswS23+O+ZfV28KrtNzy0wxUFwi4f3RyM4eeeXNs1CThxR7pb5QQcMiw==" + }, "node_modules/nuqs": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/nuqs/-/nuqs-1.14.1.tgz", @@ -11586,6 +11600,14 @@ } }, "dependencies": { + "16": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/16/-/16-0.0.2.tgz", + "integrity": "sha512-AhG4lpdn+/it+U5Xl1bm5SbaHYTH5NfU/vXZkP7E7CHjtVtITuFVZKa3AZP3gN38RDJHYYtEqWmqzCutlXaR7w==", + "requires": { + "numeric": "^1.2.6" + } + }, "@aashutoshrathi/word-wrap": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", @@ -18034,6 +18056,11 @@ "set-blocking": "^2.0.0" } }, + "numeric": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/numeric/-/numeric-1.2.6.tgz", + "integrity": "sha512-avBiDAP8siMa7AfJgYyuxw1oyII4z2sswS23+O+ZfV28KrtNzy0wxUFwi4f3RyM4eeeXNs1CThxR7pb5QQcMiw==" + }, "nuqs": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/nuqs/-/nuqs-1.14.1.tgz", diff --git a/dashboard/package.json b/dashboard/package.json index 1e84bcb8abb2d..77f7e489f9825 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -13,6 +13,7 @@ "clean": "rm -rf .next/ && rm -rf out/" }, "dependencies": { + "16": "^0.0.2", "@chakra-ui/react": "^2.3.1", "@emotion/react": "^11.10.4", "@emotion/styled": "^11.10.4", diff --git a/dashboard/pages/subscriptions.tsx b/dashboard/pages/subscriptions.tsx new file mode 100644 index 0000000000000..238a8f1f7f5ae --- /dev/null +++ b/dashboard/pages/subscriptions.tsx @@ -0,0 +1,39 @@ +/* + * Copyright 2024 RisingWave Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { Column, Relations} from "../components/Relations" +import { getSubscriptions } from "../lib/api/streaming" +import { + Subscription as RwSubscription + } from "../proto/gen/catalog" + +export default function Subscriptions() { + const subscriptionRetentionSeconds: Column = { + name: "Retention Seconds", + width: 3, + content: (r) => r.retentionSeconds ?? "unknown", + } + + const subscriptionDependentTableId: Column = { + name: "Dependent Table Id", + width: 3, + content: (r) => r.dependentTableId ?? "unknown", + } + return Relations("Subscriptions", getSubscriptions, [ + subscriptionRetentionSeconds, + subscriptionDependentTableId,]) +} \ No newline at end of file diff --git a/src/meta/src/dashboard/mod.rs b/src/meta/src/dashboard/mod.rs index ec52c3a3ee533..1229554032614 100644 --- a/src/meta/src/dashboard/mod.rs +++ b/src/meta/src/dashboard/mod.rs @@ -55,7 +55,7 @@ pub(super) mod handlers { use itertools::Itertools; use risingwave_common_heap_profiling::COLLAPSED_SUFFIX; use risingwave_pb::catalog::table::TableType; - use risingwave_pb::catalog::{PbDatabase, PbSchema, Sink, Source, Table, View}; + use risingwave_pb::catalog::{PbDatabase, PbSchema, Sink, Source, Subscription, Table, View}; use risingwave_pb::common::{WorkerNode, WorkerType}; use risingwave_pb::meta::list_object_dependencies_response::PbObjectDependencies; use risingwave_pb::meta::PbTableFragments; @@ -141,6 +141,21 @@ pub(super) mod handlers { list_table_catalogs_inner(&srv.metadata_manager, TableType::Index).await } + pub async fn list_subscription( + Extension(srv): Extension, + ) -> Result>> { + let subscriptions = match &srv.metadata_manager { + MetadataManager::V1(mgr) => mgr.catalog_manager.list_subscriptions().await, + MetadataManager::V2(mgr) => mgr + .catalog_controller + .list_subscriptions() + .await + .map_err(err)?, + }; + + Ok(Json(subscriptions)) + } + pub async fn list_internal_tables( Extension(srv): Extension, ) -> Result>> { @@ -417,6 +432,7 @@ impl DashboardService { .route("/materialized_views", get(list_materialized_views)) .route("/tables", get(list_tables)) .route("/indexes", get(list_indexes)) + .route("/subscriptions", get(list_subscription)) .route("/internal_tables", get(list_internal_tables)) .route("/sources", get(list_sources)) .route("/sinks", get(list_sinks))