811 lines
23 KiB
TypeScript
811 lines
23 KiB
TypeScript
import * as plugins from '../plugins.js';
|
|
import { apiService } from '../services/index.js';
|
|
import { themeStyles } from '../styles/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 = '';
|
|
|
|
@state()
|
|
private accessor showCreateDatabaseDialog: boolean = false;
|
|
|
|
@state()
|
|
private accessor newDatabaseName: string = '';
|
|
|
|
public static styles = [
|
|
cssManager.defaultStyles,
|
|
themeStyles,
|
|
css`
|
|
:host {
|
|
display: block;
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background: var(--tsview-bg-primary, #1a1a1a);
|
|
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: #141414;
|
|
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(255, 255, 255, 0.1);
|
|
color: #e0e0e0;
|
|
}
|
|
|
|
.app-main {
|
|
display: grid;
|
|
grid-template-columns: 240px 1fr;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.sidebar {
|
|
background: #1e1e1e;
|
|
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(255, 255, 255, 0.1);
|
|
color: #e0e0e0;
|
|
}
|
|
|
|
.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(255, 255, 255, 0.08);
|
|
color: #e0e0e0;
|
|
}
|
|
|
|
.create-btn {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
padding: 8px 12px;
|
|
margin: 8px;
|
|
background: rgba(255, 255, 255, 0.1);
|
|
border: 1px dashed rgba(255, 255, 255, 0.2);
|
|
border-radius: 6px;
|
|
color: #e0e0e0;
|
|
cursor: pointer;
|
|
font-size: 13px;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.create-btn:hover {
|
|
background: rgba(255, 255, 255, 0.15);
|
|
border-color: rgba(255, 255, 255, 0.3);
|
|
}
|
|
|
|
.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: #1e1e1e;
|
|
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: #141414;
|
|
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: #e0e0e0;
|
|
}
|
|
|
|
.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: #404040;
|
|
border: none;
|
|
color: #fff;
|
|
}
|
|
|
|
.dialog-btn-create:hover {
|
|
background: #505050;
|
|
}
|
|
|
|
.dialog-btn-create:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.dialog-btn-delete {
|
|
background: rgba(239, 68, 68, 0.2);
|
|
border: 1px solid #ef4444;
|
|
color: #f87171;
|
|
}
|
|
|
|
.dialog-btn-delete:hover {
|
|
background: rgba(239, 68, 68, 0.3);
|
|
}
|
|
|
|
.sidebar-item-content {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
.sidebar-item-name {
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.delete-btn {
|
|
opacity: 0;
|
|
padding: 4px;
|
|
background: transparent;
|
|
border: none;
|
|
color: #888;
|
|
cursor: pointer;
|
|
border-radius: 4px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
transition: all 0.15s;
|
|
}
|
|
|
|
.sidebar-item:hover .delete-btn,
|
|
.db-group-header:hover .delete-btn {
|
|
opacity: 1;
|
|
}
|
|
|
|
.delete-btn:hover {
|
|
background: rgba(239, 68, 68, 0.2);
|
|
color: #f87171;
|
|
}
|
|
`,
|
|
];
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
private async createDatabase() {
|
|
if (!this.newDatabaseName.trim()) return;
|
|
const success = await apiService.createDatabase(this.newDatabaseName.trim());
|
|
if (success) {
|
|
this.databases = [...this.databases, { name: this.newDatabaseName.trim() }];
|
|
this.newDatabaseName = '';
|
|
this.showCreateDatabaseDialog = false;
|
|
}
|
|
}
|
|
|
|
private async deleteBucket(bucket: string, e: Event) {
|
|
e.stopPropagation();
|
|
if (!confirm(`Delete bucket "${bucket}"? This will delete all objects in the bucket.`)) return;
|
|
const success = await apiService.deleteBucket(bucket);
|
|
if (success) {
|
|
this.buckets = this.buckets.filter(b => b !== bucket);
|
|
if (this.selectedBucket === bucket) {
|
|
this.selectedBucket = this.buckets[0] || '';
|
|
}
|
|
}
|
|
}
|
|
|
|
private async deleteDatabase(dbName: string, e: Event) {
|
|
e.stopPropagation();
|
|
if (!confirm(`Delete database "${dbName}"? This will delete all collections and documents.`)) return;
|
|
const success = await apiService.dropDatabase(dbName);
|
|
if (success) {
|
|
this.databases = this.databases.filter(d => d.name !== dbName);
|
|
if (this.selectedDatabase === dbName) {
|
|
this.selectedDatabase = this.databases[0]?.name || '';
|
|
this.selectedCollection = '';
|
|
}
|
|
}
|
|
}
|
|
|
|
private async deleteCollection(dbName: string, collectionName: string) {
|
|
if (!confirm(`Delete collection "${collectionName}"? This will delete all documents.`)) return;
|
|
const success = await apiService.dropCollection(dbName, collectionName);
|
|
if (success) {
|
|
if (this.selectedCollection === collectionName) {
|
|
this.selectedCollection = '';
|
|
}
|
|
// Force refresh of the collections list
|
|
this.requestUpdate();
|
|
}
|
|
}
|
|
|
|
render() {
|
|
return html`
|
|
<div class="app-container">
|
|
<header class="app-header">
|
|
<div class="app-title">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"></path>
|
|
<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"></path>
|
|
</svg>
|
|
TsView
|
|
</div>
|
|
|
|
<nav class="nav-tabs">
|
|
<button
|
|
class="nav-tab ${this.viewMode === 's3' ? 'active' : ''}"
|
|
@click=${() => this.setViewMode('s3')}
|
|
>
|
|
S3 Storage
|
|
</button>
|
|
<button
|
|
class="nav-tab ${this.viewMode === 'mongo' ? 'active' : ''}"
|
|
@click=${() => this.setViewMode('mongo')}
|
|
>
|
|
MongoDB
|
|
</button>
|
|
<button
|
|
class="nav-tab ${this.viewMode === 'settings' ? 'active' : ''}"
|
|
@click=${() => this.setViewMode('settings')}
|
|
>
|
|
Settings
|
|
</button>
|
|
</nav>
|
|
</header>
|
|
|
|
<main class="app-main">
|
|
${this.renderSidebar()}
|
|
${this.renderContent()}
|
|
</main>
|
|
</div>
|
|
${this.renderCreateBucketDialog()}
|
|
${this.renderCreateCollectionDialog()}
|
|
${this.renderCreateDatabaseDialog()}
|
|
`;
|
|
}
|
|
|
|
private renderCreateBucketDialog() {
|
|
if (!this.showCreateBucketDialog) return '';
|
|
return html`
|
|
<div class="dialog-overlay" @click=${() => this.showCreateBucketDialog = false}>
|
|
<div class="dialog" @click=${(e: Event) => e.stopPropagation()}>
|
|
<div class="dialog-title">Create New Bucket</div>
|
|
<input
|
|
type="text"
|
|
class="dialog-input"
|
|
placeholder="Bucket name"
|
|
.value=${this.newBucketName}
|
|
@input=${(e: InputEvent) => this.newBucketName = (e.target as HTMLInputElement).value}
|
|
@keydown=${(e: KeyboardEvent) => e.key === 'Enter' && this.createBucket()}
|
|
/>
|
|
<div class="dialog-actions">
|
|
<button class="dialog-btn dialog-btn-cancel" @click=${() => this.showCreateBucketDialog = false}>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
class="dialog-btn dialog-btn-create"
|
|
?disabled=${!this.newBucketName.trim()}
|
|
@click=${() => this.createBucket()}
|
|
>
|
|
Create
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
private renderCreateCollectionDialog() {
|
|
if (!this.showCreateCollectionDialog) return '';
|
|
return html`
|
|
<div class="dialog-overlay" @click=${() => this.showCreateCollectionDialog = false}>
|
|
<div class="dialog" @click=${(e: Event) => e.stopPropagation()}>
|
|
<div class="dialog-title">Create Collection in ${this.selectedDatabase}</div>
|
|
<input
|
|
type="text"
|
|
class="dialog-input"
|
|
placeholder="Collection name"
|
|
.value=${this.newCollectionName}
|
|
@input=${(e: InputEvent) => this.newCollectionName = (e.target as HTMLInputElement).value}
|
|
@keydown=${(e: KeyboardEvent) => e.key === 'Enter' && this.createCollection()}
|
|
/>
|
|
<div class="dialog-actions">
|
|
<button class="dialog-btn dialog-btn-cancel" @click=${() => this.showCreateCollectionDialog = false}>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
class="dialog-btn dialog-btn-create"
|
|
?disabled=${!this.newCollectionName.trim()}
|
|
@click=${() => this.createCollection()}
|
|
>
|
|
Create
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
private renderCreateDatabaseDialog() {
|
|
if (!this.showCreateDatabaseDialog) return '';
|
|
return html`
|
|
<div class="dialog-overlay" @click=${() => this.showCreateDatabaseDialog = false}>
|
|
<div class="dialog" @click=${(e: Event) => e.stopPropagation()}>
|
|
<div class="dialog-title">Create New Database</div>
|
|
<input
|
|
type="text"
|
|
class="dialog-input"
|
|
placeholder="Database name"
|
|
.value=${this.newDatabaseName}
|
|
@input=${(e: InputEvent) => this.newDatabaseName = (e.target as HTMLInputElement).value}
|
|
@keydown=${(e: KeyboardEvent) => e.key === 'Enter' && this.createDatabase()}
|
|
/>
|
|
<div class="dialog-actions">
|
|
<button class="dialog-btn dialog-btn-cancel" @click=${() => this.showCreateDatabaseDialog = false}>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
class="dialog-btn dialog-btn-create"
|
|
?disabled=${!this.newDatabaseName.trim()}
|
|
@click=${() => this.createDatabase()}
|
|
>
|
|
Create
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
private renderSidebar() {
|
|
if (this.viewMode === 's3') {
|
|
return html`
|
|
<aside class="sidebar">
|
|
<div class="sidebar-header">Buckets</div>
|
|
<button class="create-btn" @click=${() => this.showCreateBucketDialog = true}>
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<line x1="12" y1="5" x2="12" y2="19"></line>
|
|
<line x1="5" y1="12" x2="19" y2="12"></line>
|
|
</svg>
|
|
New Bucket
|
|
</button>
|
|
<div class="sidebar-list">
|
|
${this.buckets.length === 0
|
|
? html`<div class="sidebar-item" style="color: #666; cursor: default;">No buckets found</div>`
|
|
: this.buckets.map(
|
|
(bucket) => html`
|
|
<div
|
|
class="sidebar-item ${bucket === this.selectedBucket ? 'selected' : ''}"
|
|
@click=${() => this.selectBucket(bucket)}
|
|
>
|
|
<span class="sidebar-item-name">${bucket}</span>
|
|
<button class="delete-btn" @click=${(e: Event) => this.deleteBucket(bucket, e)} title="Delete bucket">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<polyline points="3 6 5 6 21 6"></polyline>
|
|
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
`
|
|
)}
|
|
</div>
|
|
</aside>
|
|
`;
|
|
}
|
|
|
|
if (this.viewMode === 'mongo') {
|
|
return html`
|
|
<aside class="sidebar">
|
|
<div class="sidebar-header">Databases & Collections</div>
|
|
<button class="create-btn" @click=${() => this.showCreateDatabaseDialog = true}>
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<line x1="12" y1="5" x2="12" y2="19"></line>
|
|
<line x1="5" y1="12" x2="19" y2="12"></line>
|
|
</svg>
|
|
New Database
|
|
</button>
|
|
${this.selectedDatabase ? html`
|
|
<button class="create-btn" @click=${() => this.showCreateCollectionDialog = true}>
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<line x1="12" y1="5" x2="12" y2="19"></line>
|
|
<line x1="5" y1="12" x2="19" y2="12"></line>
|
|
</svg>
|
|
New Collection
|
|
</button>
|
|
` : ''}
|
|
<div class="sidebar-list">
|
|
${this.databases.length === 0
|
|
? html`<div class="sidebar-item" style="color: #666; cursor: default;">No databases found</div>`
|
|
: this.databases.map((db) => this.renderDatabaseGroup(db))}
|
|
</div>
|
|
</aside>
|
|
`;
|
|
}
|
|
|
|
return html`
|
|
<aside class="sidebar">
|
|
<div class="sidebar-header">Settings</div>
|
|
<div class="sidebar-list">
|
|
<div class="sidebar-item">Connection</div>
|
|
<div class="sidebar-item">Display</div>
|
|
</div>
|
|
</aside>
|
|
`;
|
|
}
|
|
|
|
private renderDatabaseGroup(db: { name: string }) {
|
|
return html`
|
|
<div class="db-group">
|
|
<div
|
|
class="db-group-header ${this.selectedDatabase === db.name ? 'selected' : ''}"
|
|
@click=${() => this.selectDatabase(db.name)}
|
|
>
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<ellipse cx="12" cy="5" rx="9" ry="3"></ellipse>
|
|
<path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"></path>
|
|
<path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"></path>
|
|
</svg>
|
|
<span style="flex: 1;">${db.name}</span>
|
|
<button class="delete-btn" @click=${(e: Event) => this.deleteDatabase(db.name, e)} title="Delete database">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<polyline points="3 6 5 6 21 6"></polyline>
|
|
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
${this.selectedDatabase === db.name ? this.renderCollectionsList(db.name) : ''}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
private renderCollectionsList(dbName: string) {
|
|
return html`
|
|
<tsview-mongo-collections
|
|
.databaseName=${dbName}
|
|
.selectedCollection=${this.selectedCollection}
|
|
@collection-selected=${(e: CustomEvent) => this.selectCollection(e.detail)}
|
|
@collection-deleted=${(e: CustomEvent) => this.handleCollectionDeleted(e)}
|
|
></tsview-mongo-collections>
|
|
`;
|
|
}
|
|
|
|
private handleCollectionDeleted(e: CustomEvent) {
|
|
const { collectionName } = e.detail;
|
|
if (this.selectedCollection === collectionName) {
|
|
this.selectedCollection = '';
|
|
}
|
|
}
|
|
|
|
private renderContent() {
|
|
if (this.viewMode === 's3') {
|
|
if (!this.selectedBucket) {
|
|
return html`
|
|
<div class="content-area">
|
|
<div class="empty-state">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path>
|
|
</svg>
|
|
<p>Select a bucket to browse</p>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
return html`
|
|
<div class="content-area">
|
|
<tsview-s3-browser .bucketName=${this.selectedBucket}></tsview-s3-browser>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
if (this.viewMode === 'mongo') {
|
|
if (!this.selectedCollection) {
|
|
return html`
|
|
<div class="content-area">
|
|
<div class="empty-state">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<ellipse cx="12" cy="5" rx="9" ry="3"></ellipse>
|
|
<path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"></path>
|
|
<path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"></path>
|
|
</svg>
|
|
<p>Select a collection to view documents</p>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
return html`
|
|
<div class="content-area">
|
|
<tsview-mongo-browser
|
|
.databaseName=${this.selectedDatabase}
|
|
.collectionName=${this.selectedCollection}
|
|
></tsview-mongo-browser>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
return html`
|
|
<div class="content-area">
|
|
<h2>Settings</h2>
|
|
<p>Configuration options coming soon.</p>
|
|
</div>
|
|
`;
|
|
}
|
|
}
|