import * as plugins from '../plugins.js'; import { apiService, type IMongoIndex } from '../services/index.js'; const { html, css, cssManager, customElement, property, state, DeesElement } = plugins; @customElement('tsview-mongo-indexes') export class TsviewMongoIndexes extends DeesElement { @property({ type: String }) public accessor databaseName: string = ''; @property({ type: String }) public accessor collectionName: string = ''; @state() private accessor indexes: IMongoIndex[] = []; @state() private accessor loading: boolean = false; @state() private accessor showCreateDialog: boolean = false; @state() private accessor newIndexKeys: string = ''; @state() private accessor newIndexUnique: boolean = false; @state() private accessor newIndexSparse: boolean = false; public static styles = [ cssManager.defaultStyles, css` :host { display: block; height: 100%; overflow: hidden; } .indexes-container { display: flex; flex-direction: column; height: 100%; } .toolbar { display: flex; justify-content: flex-end; padding: 12px; border-bottom: 1px solid #333; } .create-btn { padding: 8px 16px; background: rgba(34, 197, 94, 0.2); border: 1px solid #22c55e; color: #4ade80; border-radius: 6px; cursor: pointer; font-size: 13px; } .create-btn:hover { background: rgba(34, 197, 94, 0.3); } .indexes-list { flex: 1; overflow-y: auto; padding: 12px; } .index-card { background: rgba(0, 0, 0, 0.2); border: 1px solid #333; border-radius: 8px; padding: 16px; margin-bottom: 12px; } .index-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; } .index-name { font-size: 14px; font-weight: 500; color: #fff; } .index-badges { display: flex; gap: 8px; } .badge { font-size: 11px; padding: 2px 8px; border-radius: 10px; background: rgba(255, 255, 255, 0.1); color: #888; } .badge.unique { background: rgba(99, 102, 241, 0.2); color: #818cf8; } .badge.sparse { background: rgba(251, 191, 36, 0.2); color: #fbbf24; } .index-keys { font-family: monospace; font-size: 12px; color: #888; background: rgba(0, 0, 0, 0.2); padding: 8px 12px; border-radius: 4px; margin-bottom: 12px; } .index-actions { display: flex; justify-content: flex-end; } .drop-btn { padding: 6px 12px; background: transparent; border: 1px solid #ef4444; color: #f87171; border-radius: 4px; cursor: pointer; font-size: 12px; } .drop-btn:hover { background: rgba(239, 68, 68, 0.2); } .drop-btn:disabled { opacity: 0.5; cursor: not-allowed; } .empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 200px; color: #666; } .loading-state { display: flex; align-items: center; justify-content: center; height: 200px; color: #888; } /* Dialog styles */ .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: 1px solid #333; border-radius: 12px; padding: 24px; width: 400px; max-width: 90%; } .dialog-title { font-size: 16px; font-weight: 500; margin-bottom: 16px; } .dialog-field { margin-bottom: 16px; } .dialog-label { display: block; font-size: 13px; color: #888; margin-bottom: 6px; } .dialog-input { width: 100%; padding: 8px 12px; background: rgba(0, 0, 0, 0.3); border: 1px solid #444; border-radius: 6px; color: #fff; font-size: 13px; font-family: monospace; } .dialog-input:focus { outline: none; border-color: #6366f1; } .dialog-checkbox { display: flex; align-items: center; gap: 8px; font-size: 13px; } .dialog-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 24px; } .dialog-btn { padding: 8px 16px; border-radius: 6px; cursor: pointer; font-size: 13px; } .dialog-btn.secondary { background: transparent; border: 1px solid #444; color: #888; } .dialog-btn.secondary:hover { border-color: #666; color: #aaa; } .dialog-btn.primary { background: rgba(99, 102, 241, 0.2); border: 1px solid #6366f1; color: #818cf8; } .dialog-btn.primary:hover { background: rgba(99, 102, 241, 0.3); } `, ]; async connectedCallback() { super.connectedCallback(); await this.loadIndexes(); } updated(changedProperties: Map) { if (changedProperties.has('databaseName') || changedProperties.has('collectionName')) { this.loadIndexes(); } } private async loadIndexes() { if (!this.databaseName || !this.collectionName) return; this.loading = true; try { this.indexes = await apiService.listIndexes(this.databaseName, this.collectionName); } catch (err) { console.error('Error loading indexes:', err); this.indexes = []; } this.loading = false; } private openCreateDialog() { this.newIndexKeys = ''; this.newIndexUnique = false; this.newIndexSparse = false; this.showCreateDialog = true; } private closeCreateDialog() { this.showCreateDialog = false; } private async createIndex() { try { const keys = JSON.parse(this.newIndexKeys); await apiService.createIndex(this.databaseName, this.collectionName, keys, { unique: this.newIndexUnique, sparse: this.newIndexSparse, }); this.closeCreateDialog(); await this.loadIndexes(); } catch (err) { console.error('Error creating index:', err); alert('Invalid JSON or index creation failed'); } } private async dropIndex(indexName: string) { if (indexName === '_id_') { alert('Cannot drop the _id index'); return; } if (!confirm(`Drop index "${indexName}"?`)) return; try { await apiService.dropIndex(this.databaseName, this.collectionName, indexName); await this.loadIndexes(); } catch (err) { console.error('Error dropping index:', err); } } private formatKeys(keys: Record): string { return JSON.stringify(keys); } render() { return html`
${this.loading ? html`
Loading...
` : this.indexes.length === 0 ? html`
No indexes found
` : this.indexes.map( (idx) => html`
${idx.name}
${idx.unique ? html`unique` : ''} ${idx.sparse ? html`sparse` : ''}
${this.formatKeys(idx.keys)}
` )}
${this.showCreateDialog ? html`
e.stopPropagation()}>
Create Index
(this.newIndexKeys = (e.target as HTMLInputElement).value)} />
` : ''} `; } }