feat(tsview): add database and S3 handlers, tswatch/watch scripts, web utilities, assets and release config
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
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;
|
||||
|
||||
@@ -37,8 +38,15 @@ export class TsviewApp extends DeesElement {
|
||||
@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;
|
||||
@@ -47,7 +55,7 @@ export class TsviewApp extends DeesElement {
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: #1a1a2e;
|
||||
background: var(--tsview-bg-primary, #1a1a1a);
|
||||
color: #eee;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
}
|
||||
@@ -59,7 +67,7 @@ export class TsviewApp extends DeesElement {
|
||||
}
|
||||
|
||||
.app-header {
|
||||
background: #16162a;
|
||||
background: #141414;
|
||||
border-bottom: 1px solid #333;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -103,8 +111,8 @@ export class TsviewApp extends DeesElement {
|
||||
}
|
||||
|
||||
.nav-tab.active {
|
||||
background: rgba(99, 102, 241, 0.2);
|
||||
color: #818cf8;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
.app-main {
|
||||
@@ -114,7 +122,7 @@ export class TsviewApp extends DeesElement {
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
background: #1e1e38;
|
||||
background: #1e1e1e;
|
||||
border-right: 1px solid #333;
|
||||
overflow-y: auto;
|
||||
}
|
||||
@@ -148,8 +156,8 @@ export class TsviewApp extends DeesElement {
|
||||
}
|
||||
|
||||
.sidebar-item.selected {
|
||||
background: rgba(99, 102, 241, 0.2);
|
||||
color: #818cf8;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
.sidebar-item .count {
|
||||
@@ -219,8 +227,8 @@ export class TsviewApp extends DeesElement {
|
||||
}
|
||||
|
||||
.collection-item.selected {
|
||||
background: rgba(99, 102, 241, 0.15);
|
||||
color: #818cf8;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
.create-btn {
|
||||
@@ -229,18 +237,18 @@ export class TsviewApp extends DeesElement {
|
||||
gap: 6px;
|
||||
padding: 8px 12px;
|
||||
margin: 8px;
|
||||
background: rgba(99, 102, 241, 0.2);
|
||||
border: 1px dashed rgba(99, 102, 241, 0.4);
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border: 1px dashed rgba(255, 255, 255, 0.2);
|
||||
border-radius: 6px;
|
||||
color: #818cf8;
|
||||
color: #e0e0e0;
|
||||
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);
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border-color: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.dialog-overlay {
|
||||
@@ -257,7 +265,7 @@ export class TsviewApp extends DeesElement {
|
||||
}
|
||||
|
||||
.dialog {
|
||||
background: #1e1e38;
|
||||
background: #1e1e1e;
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
min-width: 400px;
|
||||
@@ -274,7 +282,7 @@ export class TsviewApp extends DeesElement {
|
||||
.dialog-input {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
background: #16162a;
|
||||
background: #141414;
|
||||
border: 1px solid #333;
|
||||
border-radius: 6px;
|
||||
color: #fff;
|
||||
@@ -285,7 +293,7 @@ export class TsviewApp extends DeesElement {
|
||||
|
||||
.dialog-input:focus {
|
||||
outline: none;
|
||||
border-color: #818cf8;
|
||||
border-color: #e0e0e0;
|
||||
}
|
||||
|
||||
.dialog-actions {
|
||||
@@ -314,19 +322,67 @@ export class TsviewApp extends DeesElement {
|
||||
}
|
||||
|
||||
.dialog-btn-create {
|
||||
background: #6366f1;
|
||||
background: #404040;
|
||||
border: none;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.dialog-btn-create:hover {
|
||||
background: #5558e8;
|
||||
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;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@@ -392,6 +448,53 @@ export class TsviewApp extends DeesElement {
|
||||
}
|
||||
}
|
||||
|
||||
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">
|
||||
@@ -433,6 +536,7 @@ export class TsviewApp extends DeesElement {
|
||||
</div>
|
||||
${this.renderCreateBucketDialog()}
|
||||
${this.renderCreateCollectionDialog()}
|
||||
${this.renderCreateDatabaseDialog()}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -498,6 +602,37 @@ export class TsviewApp extends DeesElement {
|
||||
`;
|
||||
}
|
||||
|
||||
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`
|
||||
@@ -519,7 +654,13 @@ export class TsviewApp extends DeesElement {
|
||||
class="sidebar-item ${bucket === this.selectedBucket ? 'selected' : ''}"
|
||||
@click=${() => this.selectBucket(bucket)}
|
||||
>
|
||||
${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>
|
||||
`
|
||||
)}
|
||||
@@ -532,6 +673,13 @@ export class TsviewApp extends DeesElement {
|
||||
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">
|
||||
@@ -573,7 +721,13 @@ export class TsviewApp extends DeesElement {
|
||||
<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>
|
||||
${db.name}
|
||||
<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>
|
||||
@@ -586,10 +740,18 @@ export class TsviewApp extends DeesElement {
|
||||
.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) {
|
||||
|
||||
Reference in New Issue
Block a user