feat(streaming): add real-time streaming (MongoDB change streams & S3 bucket watchers) with WebSocket subscriptions and activity stream UI
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import { apiService, type ICollectionStats } from '../services/index.js';
|
||||
import { apiService, changeStreamService, type ICollectionStats, type IMongoChangeEvent } from '../services/index.js';
|
||||
import { formatSize, formatCount } from '../utilities/index.js';
|
||||
import { themeStyles } from '../styles/index.js';
|
||||
|
||||
@@ -30,6 +30,14 @@ export class TsviewMongoBrowser extends DeesElement {
|
||||
@state()
|
||||
private accessor isResizingEditor: boolean = false;
|
||||
|
||||
@state()
|
||||
private accessor recentChangeCount: number = 0;
|
||||
|
||||
@state()
|
||||
private accessor isStreamConnected: boolean = false;
|
||||
|
||||
private changeSubscription: plugins.smartrx.rxjs.Subscription | null = null;
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
themeStyles,
|
||||
@@ -148,18 +156,114 @@ export class TsviewMongoBrowser extends DeesElement {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.change-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 4px 8px;
|
||||
background: rgba(34, 197, 94, 0.2);
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
color: #4ade80;
|
||||
}
|
||||
|
||||
.change-indicator.pulse {
|
||||
animation: pulse-green 1s ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes pulse-green {
|
||||
0% { background: rgba(34, 197, 94, 0.4); }
|
||||
100% { background: rgba(34, 197, 94, 0.2); }
|
||||
}
|
||||
|
||||
.stream-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 11px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.stream-dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: #888;
|
||||
}
|
||||
|
||||
.stream-dot.connected {
|
||||
background: #22c55e;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
async connectedCallback() {
|
||||
super.connectedCallback();
|
||||
await this.loadStats();
|
||||
this.subscribeToChanges();
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this.unsubscribeFromChanges();
|
||||
}
|
||||
|
||||
updated(changedProperties: Map<string, unknown>) {
|
||||
if (changedProperties.has('databaseName') || changedProperties.has('collectionName')) {
|
||||
this.loadStats();
|
||||
this.selectedDocumentId = '';
|
||||
this.recentChangeCount = 0;
|
||||
// Re-subscribe to the new collection
|
||||
this.unsubscribeFromChanges();
|
||||
this.subscribeToChanges();
|
||||
}
|
||||
}
|
||||
|
||||
private async subscribeToChanges() {
|
||||
if (!this.databaseName || !this.collectionName) return;
|
||||
|
||||
try {
|
||||
// Subscribe to collection changes
|
||||
const success = await changeStreamService.subscribeToCollection(this.databaseName, this.collectionName);
|
||||
this.isStreamConnected = success;
|
||||
|
||||
if (success) {
|
||||
// Listen for changes
|
||||
this.changeSubscription = changeStreamService
|
||||
.getCollectionChanges(this.databaseName, this.collectionName)
|
||||
.subscribe((event) => {
|
||||
this.handleChange(event);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[MongoBrowser] Failed to subscribe to changes:', error);
|
||||
this.isStreamConnected = false;
|
||||
}
|
||||
}
|
||||
|
||||
private unsubscribeFromChanges() {
|
||||
if (this.changeSubscription) {
|
||||
this.changeSubscription.unsubscribe();
|
||||
this.changeSubscription = null;
|
||||
}
|
||||
if (this.databaseName && this.collectionName) {
|
||||
changeStreamService.unsubscribeFromCollection(this.databaseName, this.collectionName);
|
||||
}
|
||||
this.isStreamConnected = false;
|
||||
}
|
||||
|
||||
private handleChange(event: IMongoChangeEvent) {
|
||||
console.log('[MongoBrowser] Received change:', event);
|
||||
this.recentChangeCount++;
|
||||
|
||||
// Refresh stats to reflect changes
|
||||
this.loadStats();
|
||||
|
||||
// Notify the documents component to refresh
|
||||
const documentsEl = this.shadowRoot?.querySelector('tsview-mongo-documents') as any;
|
||||
if (documentsEl?.refresh) {
|
||||
documentsEl.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,6 +323,17 @@ export class TsviewMongoBrowser extends DeesElement {
|
||||
</div>
|
||||
`
|
||||
: ''}
|
||||
<div class="stream-status">
|
||||
<span class="stream-dot ${this.isStreamConnected ? 'connected' : ''}"></span>
|
||||
${this.isStreamConnected ? 'Live' : 'Offline'}
|
||||
</div>
|
||||
${this.recentChangeCount > 0
|
||||
? html`
|
||||
<div class="change-indicator pulse">
|
||||
${this.recentChangeCount} change${this.recentChangeCount > 1 ? 's' : ''}
|
||||
</div>
|
||||
`
|
||||
: ''}
|
||||
</div>
|
||||
|
||||
<div class="tabs">
|
||||
|
||||
Reference in New Issue
Block a user