Files
dbx/crates/dbx-core/src/mq/mod.rs
T
miracleandt8y2 2a59d3143e feat(mq): implement Apache Pulsar message queue admin
* feat(mq): implement Apache Pulsar message queue admin

- Add complete MQ admin adapter layer with version detection
  * Pulsar adapter with multi-version API support
  * OAuth2 token caching and authentication
  * JWT token signing for Pulsar clients
  * Transport layer (SSH/Proxy) integration

- Implement comprehensive admin operations
  * Tenant, namespace, topic management
  * Subscription and consumer/producer monitoring
  * Rate limits, backlog quotas, retention policies
  * Permissions and role-based access control
  * Raw admin API for advanced operations

- Add desktop UI components
  * MQ admin console with tabbed interface
  * Tenant, namespace, topic panels
  * Subscription management and cursor operations
  * Monitoring panel with stats visualization
  * Policy configuration UI

- Add web routes for MQ admin operations
- Add secret store support for MQ auth credentials
- Add comprehensive test coverage

* fix: del local test files

* fix(mq): mark message queue as unsupported mcpMode in driver manifest

MQ admin connections do not use the MCP query bridge - they use a
separate REST admin API. Mark mcpMode as 'unsupported' to align with
the test expectations and reflect the actual architecture.

Fixes CI test: driver-manifest.test.ts

* fix(mq): fix TypeScript errors in MQ components

- Fix typo in SubscriptionsPanel.vue: messagformatError(e)Id -> message.messageId
- Use formatError() helper in RawApiPanel.vue instead of accessing e?.message
- Fix undefined 'tab' reference in ConnectionTree.vue: use config.db_type instead

Fixes frontend CI TypeScript check errors.

* fix(mcp): add 'mq' to MCP server connection type description

The MCP server's connection type description must include all database
types from the driver manifest. Add 'mq' to the list to fix the test:
'add connection database type description includes every manifest database type'

Fixes: packages/mcp-server/tests/driver-manifest.test.ts

* fix(mq): add missing ConnectionConfig fields for Rust compilation

- Add gbase_server and keepalive_interval_secs fields to all ConnectionConfig
  test constructors in mq/config.rs, mq/service.rs, and storage.rs
- Import uses_bare_mysql_pool in connection.rs tests module

Fixes compilation errors from upstream ConnectionConfig changes.

* chore(ui): simplify Pulsar icon to logo-only version

Remove text from Pulsar icon (127x30 -> 35x30), keeping only
the wave logo symbol for a cleaner, more consistent appearance
with other database icons.

* fix(mq): return removed pools from drain_connection_pools

---------

Co-authored-by: t8y2 <1156263951@qq.com>
2026-06-16 21:57:17 +08:00

117 lines
4.4 KiB
Rust

//! Message queue admin console support.
//!
//! Provides a pluggable, port-adapter abstraction for managing message queue
//! systems (Apache Pulsar today; Kafka / RocketMQ are reserved). The module is
//! gated behind the `mq-admin` Cargo feature so builds that don't need it pay
//! nothing.
//!
//! Architecture mirrors the existing `agent_kv` pattern: business logic lives in
//! `service::*_core` functions shared by the desktop command layer and the web
//! route layer; this module owns the trait, the typed model, and the registry
//! that caches one adapter per connection.
pub mod adapters;
pub mod auth;
pub mod config;
pub mod port;
pub mod service;
pub mod token;
pub mod types;
pub(crate) mod util;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::{Mutex, RwLock};
use crate::models::connection::ConnectionConfig;
use crate::mq::adapters::pulsar::PulsarAdmin;
use crate::mq::config::MqAdminConfig;
use crate::mq::port::MessageQueueAdmin;
use crate::mq::types::MqSystemKind as MqSystemKindInternal;
pub use crate::mq::auth::MqAuth;
pub use crate::mq::config::MqAdminConfig as MqConfig;
pub use crate::mq::types::*;
/// Caches one live admin adapter per connection id. Adapters are built lazily on
/// first use and dropped when the connection is closed.
///
/// Each connection has its own build-lock so concurrent first-use requests for
/// the same connection block until the first builder finishes, rather than both
/// racing to construct an adapter.
#[derive(Default)]
pub struct MqAdminRegistry {
instances: RwLock<HashMap<String, Arc<dyn MessageQueueAdmin>>>,
build_locks: RwLock<HashMap<String, Arc<Mutex<()>>>>,
}
impl MqAdminRegistry {
pub fn new() -> Self {
Self { instances: RwLock::new(HashMap::new()), build_locks: RwLock::new(HashMap::new()) }
}
/// Return the cached adapter for this connection, building it from the
/// connection's `external_config` if not already present.
pub async fn get_or_build(&self, cfg: &ConnectionConfig) -> Result<Arc<dyn MessageQueueAdmin>, String> {
let mqc = MqAdminConfig::from_connection(cfg)?;
self.get_or_build_config(&cfg.id, mqc).await
}
pub async fn get_or_build_config(
&self,
connection_id: &str,
mqc: MqAdminConfig,
) -> Result<Arc<dyn MessageQueueAdmin>, String> {
// Fast path: return the cached adapter.
if let Some(adapter) = self.instances.read().await.get(connection_id) {
return Ok(adapter.clone());
}
// Slow path: acquire a per-connection build lock so only one task
// constructs the adapter at a time.
let lock = {
let mut locks = self.build_locks.write().await;
locks.entry(connection_id.to_string()).or_insert_with(|| Arc::new(Mutex::new(()))).clone()
};
let _guard = lock.lock().await;
// Another task may have built it while we were waiting for the lock.
if let Some(adapter) = self.instances.read().await.get(connection_id) {
return Ok(adapter.clone());
}
let adapter = build_adapter(mqc).await?;
self.instances.write().await.insert(connection_id.to_string(), adapter.clone());
Ok(adapter)
}
/// Drop the cached adapter for a connection (called on disconnect).
pub async fn drop_connection(&self, connection_id: &str) {
self.instances.write().await.remove(connection_id);
self.build_locks.write().await.remove(connection_id);
}
/// Build a fresh adapter without caching it — used for connection tests
/// where we don't want to retain state.
pub async fn build_transient(&self, cfg: &ConnectionConfig) -> Result<Arc<dyn MessageQueueAdmin>, String> {
let mqc = MqAdminConfig::from_connection(cfg)?;
self.build_transient_config(mqc).await
}
pub async fn build_transient_config(&self, mqc: MqAdminConfig) -> Result<Arc<dyn MessageQueueAdmin>, String> {
build_adapter(mqc).await
}
}
async fn build_adapter(mqc: MqAdminConfig) -> Result<Arc<dyn MessageQueueAdmin>, String> {
match mqc.system_kind {
MqSystemKindInternal::Pulsar => {
let adapter = PulsarAdmin::new(mqc).await?;
Ok(Arc::new(adapter))
}
MqSystemKindInternal::Kafka => Err("Kafka admin is not yet implemented".to_string()),
MqSystemKindInternal::RocketMq => Err("RocketMQ admin is not yet implemented".to_string()),
}
}