Add schema sync button to sidebar
- Add syncSchemas() API function calling /rpc/sync_all_schemas - Add sync button in TablesGraph sidebar - Show sync results per schema (ok, pending, error) - Auto-reload current schema after successful sync - Add CSS styles for sync button with spinning animation - Add 'storage' to visible categories Backend: PostgreSQL function infra.sync_all_schemas() syncs tables and columns from gitea and architect databases via dblink. HST and DECK remote sync pending configuration. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -174,3 +174,20 @@ export const fetchTableRelations = async (schemaId: string): Promise<TableRelati
|
||||
type: r.relation_type as 'fk' | 'ref' | 'logical'
|
||||
}));
|
||||
};
|
||||
|
||||
// Sync schemas from source databases
|
||||
export interface SyncResult {
|
||||
schema_id: string;
|
||||
tables_synced: number;
|
||||
columns_synced: number;
|
||||
status: string;
|
||||
}
|
||||
|
||||
export const syncSchemas = async (): Promise<SyncResult[]> => {
|
||||
const res = await fetch(`${API_BASE}/rpc/sync_all_schemas`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
if (!res.ok) throw new Error(`Sync error: ${res.status}`);
|
||||
return res.json();
|
||||
};
|
||||
|
||||
@@ -582,3 +582,70 @@ svg {
|
||||
max-height: calc(100vh - 150px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* Sync button styles */
|
||||
.btn-sync {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
transition: background 0.2s, opacity 0.2s;
|
||||
}
|
||||
|
||||
.btn-sync:hover:not(:disabled) {
|
||||
background: var(--primary-dark, #5a6be8);
|
||||
}
|
||||
|
||||
.btn-sync:disabled {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.sync-icon {
|
||||
font-size: 16px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.sync-icon.spinning {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.sync-status {
|
||||
margin-top: 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.sync-result {
|
||||
padding: 6px 8px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.sync-result.success {
|
||||
background: rgba(76, 175, 80, 0.15);
|
||||
color: #4CAF50;
|
||||
}
|
||||
|
||||
.sync-result.pending {
|
||||
background: rgba(255, 152, 0, 0.15);
|
||||
color: #FF9800;
|
||||
}
|
||||
|
||||
.sync-result.error {
|
||||
background: rgba(244, 67, 54, 0.15);
|
||||
color: #F44336;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { DBSchema, TableSchema, TableRelation } from '../api/schemas.ts';
|
||||
import { CATEGORY_COLORS, DB_SCHEMAS } from '../api/schemas.ts';
|
||||
import { fetchColumns, fetchTables, fetchTableRelations, type Column } from '../api/infrastructure.ts';
|
||||
import { fetchColumns, fetchTables, fetchTableRelations, syncSchemas, type Column, type SyncResult } from '../api/infrastructure.ts';
|
||||
|
||||
type D3Module = typeof import('d3');
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@@ -42,7 +42,7 @@ export class TablesGraph {
|
||||
this.container = container;
|
||||
this.serverName = serverName;
|
||||
this.schema = schema;
|
||||
this.showCategories = new Set(['core', 'api', 'graph', 'tree', 'library', 'data', 'system', 'directus', 'comm', 'repo', 'user', 'action', 'auth', 'agent', 'context', 'creds']);
|
||||
this.showCategories = new Set(['core', 'api', 'graph', 'tree', 'library', 'data', 'system', 'directus', 'comm', 'repo', 'user', 'action', 'auth', 'agent', 'context', 'creds', 'storage']);
|
||||
}
|
||||
|
||||
async mount(): Promise<void> {
|
||||
@@ -254,7 +254,7 @@ export class TablesGraph {
|
||||
const sidebar = this.container.querySelector('#graph-sidebar');
|
||||
if (!sidebar) return;
|
||||
|
||||
const categories: TableSchema['category'][] = ['core', 'api', 'graph', 'tree', 'library', 'data', 'directus', 'system', 'comm', 'repo', 'user', 'action', 'auth', 'agent', 'context', 'creds'];
|
||||
const categories: TableSchema['category'][] = ['core', 'api', 'graph', 'tree', 'library', 'data', 'directus', 'system', 'comm', 'repo', 'user', 'action', 'auth', 'agent', 'context', 'creds', 'storage'];
|
||||
|
||||
sidebar.innerHTML = `
|
||||
<div class="sidebar-header">
|
||||
@@ -323,6 +323,14 @@ export class TablesGraph {
|
||||
<input type="range" id="graph-link-dist" min="60" max="200" value="${this.linkDist}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="graph-section">
|
||||
<div class="graph-section-title">Sincronizacion</div>
|
||||
<button class="btn btn-sync" id="btn-sync">
|
||||
<span class="sync-icon">↻</span> Sincronizar Schemas
|
||||
</button>
|
||||
<div id="sync-status" class="sync-status"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
this.bindSidebarEvents(sidebar as HTMLElement);
|
||||
@@ -379,6 +387,37 @@ export class TablesGraph {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Sync button
|
||||
const syncBtn = sidebar.querySelector<HTMLButtonElement>('#btn-sync');
|
||||
const syncStatus = sidebar.querySelector('#sync-status');
|
||||
if (syncBtn && syncStatus) {
|
||||
syncBtn.onclick = async () => {
|
||||
syncBtn.disabled = true;
|
||||
syncBtn.innerHTML = '<span class="sync-icon spinning">↻</span> Sincronizando...';
|
||||
syncStatus.innerHTML = '';
|
||||
|
||||
try {
|
||||
const results = await syncSchemas();
|
||||
syncStatus.innerHTML = results.map(r =>
|
||||
`<div class="sync-result ${r.status === 'ok' ? 'success' : r.status.includes('pending') ? 'pending' : 'error'}">
|
||||
<strong>${r.schema_id}</strong>: ${r.status === 'ok' ? `${r.columns_synced} cols` : r.status}
|
||||
</div>`
|
||||
).join('');
|
||||
|
||||
// Reload current schema if it was synced
|
||||
const currentSynced = results.find(r => r.schema_id === this.serverName && r.status === 'ok');
|
||||
if (currentSynced) {
|
||||
setTimeout(() => this.mount(), 1000);
|
||||
}
|
||||
} catch (e) {
|
||||
syncStatus.innerHTML = `<div class="sync-result error">Error: ${e}</div>`;
|
||||
} finally {
|
||||
syncBtn.disabled = false;
|
||||
syncBtn.innerHTML = '<span class="sync-icon">↻</span> Sincronizar Schemas';
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private renderLegend(): void {
|
||||
|
||||
Reference in New Issue
Block a user