import * as plugins from '../plugins.js'; import { apiService } from '../services/index.js'; const { html, css, cssManager, customElement, state, DeesElement } = plugins; type TViewMode = 's3' | 'mongo' | 'settings'; @customElement('tsview-app') export class TsviewApp extends DeesElement { @state() private accessor viewMode: TViewMode = 's3'; @state() private accessor selectedBucket: string = ''; @state() private accessor selectedDatabase: string = ''; @state() private accessor selectedCollection: string = ''; @state() private accessor buckets: string[] = []; @state() private accessor databases: Array<{ name: string; sizeOnDisk?: number }> = []; @state() private accessor showCreateBucketDialog: boolean = false; @state() private accessor newBucketName: string = ''; @state() private accessor showCreateCollectionDialog: boolean = false; @state() private accessor newCollectionName: string = ''; public static styles = [ cssManager.defaultStyles, css` :host { display: block; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: #1a1a2e; color: #eee; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; } .app-container { display: grid; grid-template-rows: 48px 1fr; height: 100%; } .app-header { background: #16162a; border-bottom: 1px solid #333; display: flex; align-items: center; padding: 0 16px; gap: 24px; } .app-title { font-size: 18px; font-weight: 600; color: #fff; display: flex; align-items: center; gap: 8px; } .app-title svg { width: 24px; height: 24px; } .nav-tabs { display: flex; gap: 4px; } .nav-tab { padding: 8px 16px; background: transparent; border: none; color: #888; cursor: pointer; font-size: 14px; border-radius: 6px; transition: all 0.2s; } .nav-tab:hover { background: rgba(255, 255, 255, 0.05); color: #aaa; } .nav-tab.active { background: rgba(99, 102, 241, 0.2); color: #818cf8; } .app-main { display: grid; grid-template-columns: 240px 1fr; overflow: hidden; } .sidebar { background: #1e1e38; border-right: 1px solid #333; overflow-y: auto; } .sidebar-header { padding: 12px 16px; font-size: 12px; font-weight: 600; text-transform: uppercase; color: #666; border-bottom: 1px solid #333; } .sidebar-list { padding: 8px; } .sidebar-item { padding: 8px 12px; border-radius: 6px; cursor: pointer; font-size: 14px; display: flex; align-items: center; justify-content: space-between; transition: background 0.15s; } .sidebar-item:hover { background: rgba(255, 255, 255, 0.05); } .sidebar-item.selected { background: rgba(99, 102, 241, 0.2); color: #818cf8; } .sidebar-item .count { font-size: 12px; color: #666; background: rgba(255, 255, 255, 0.1); padding: 2px 6px; border-radius: 10px; } .content-area { overflow: auto; padding: 16px; } .empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; color: #666; } .empty-state svg { width: 64px; height: 64px; margin-bottom: 16px; opacity: 0.5; } .db-group { margin-bottom: 8px; } .db-group-header { padding: 8px 12px; font-size: 13px; font-weight: 500; color: #999; cursor: pointer; display: flex; align-items: center; gap: 8px; } .db-group-header:hover { color: #ccc; } .db-group-collections { padding-left: 12px; } .collection-item { padding: 6px 12px; border-radius: 4px; cursor: pointer; font-size: 13px; display: flex; align-items: center; justify-content: space-between; } .collection-item:hover { background: rgba(255, 255, 255, 0.05); } .collection-item.selected { background: rgba(99, 102, 241, 0.15); color: #818cf8; } .create-btn { display: flex; align-items: center; gap: 6px; padding: 8px 12px; margin: 8px; background: rgba(99, 102, 241, 0.2); border: 1px dashed rgba(99, 102, 241, 0.4); border-radius: 6px; color: #818cf8; cursor: pointer; font-size: 13px; transition: all 0.2s; } .create-btn:hover { background: rgba(99, 102, 241, 0.3); border-color: rgba(99, 102, 241, 0.6); } .dialog-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.7); display: flex; align-items: center; justify-content: center; z-index: 1000; } .dialog { background: #1e1e38; border-radius: 12px; padding: 24px; min-width: 400px; box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5); } .dialog-title { font-size: 18px; font-weight: 600; margin-bottom: 16px; color: #fff; } .dialog-input { width: 100%; padding: 10px 12px; background: #16162a; border: 1px solid #333; border-radius: 6px; color: #fff; font-size: 14px; margin-bottom: 16px; box-sizing: border-box; } .dialog-input:focus { outline: none; border-color: #818cf8; } .dialog-actions { display: flex; gap: 12px; justify-content: flex-end; } .dialog-btn { padding: 8px 16px; border-radius: 6px; font-size: 14px; cursor: pointer; transition: all 0.2s; } .dialog-btn-cancel { background: transparent; border: 1px solid #444; color: #aaa; } .dialog-btn-cancel:hover { background: rgba(255, 255, 255, 0.05); color: #fff; } .dialog-btn-create { background: #6366f1; border: none; color: #fff; } .dialog-btn-create:hover { background: #5558e8; } .dialog-btn-create:disabled { opacity: 0.5; cursor: not-allowed; } `, ]; async connectedCallback() { super.connectedCallback(); await this.loadData(); } private async loadData() { try { // Load buckets for S3 this.buckets = await apiService.listBuckets(); // Load databases for MongoDB this.databases = await apiService.listDatabases(); // Select first item if available if (this.viewMode === 's3' && this.buckets.length > 0 && !this.selectedBucket) { this.selectedBucket = this.buckets[0]; } if (this.viewMode === 'mongo' && this.databases.length > 0 && !this.selectedDatabase) { this.selectedDatabase = this.databases[0].name; } } catch (err) { console.error('Error loading data:', err); } } private setViewMode(mode: TViewMode) { this.viewMode = mode; } private selectBucket(bucket: string) { this.selectedBucket = bucket; } private selectDatabase(db: string) { this.selectedDatabase = db; this.selectedCollection = ''; } private selectCollection(collection: string) { this.selectedCollection = collection; } private async createBucket() { if (!this.newBucketName.trim()) return; const success = await apiService.createBucket(this.newBucketName.trim()); if (success) { this.buckets = [...this.buckets, this.newBucketName.trim()]; this.newBucketName = ''; this.showCreateBucketDialog = false; } } private async createCollection() { if (!this.newCollectionName.trim() || !this.selectedDatabase) return; const success = await apiService.createCollection(this.selectedDatabase, this.newCollectionName.trim()); if (success) { this.newCollectionName = ''; this.showCreateCollectionDialog = false; // Refresh will happen through the collections component } } render() { return html`
Select a bucket to browse
Select a collection to view documents
Configuration options coming soon.