368 lines
18 KiB
JavaScript
368 lines
18 KiB
JavaScript
/**
|
|
* Env Config View
|
|
* Manage and edit .env file content with local SQLite version history & restore capabilities.
|
|
*/
|
|
const EnvConfigView = {
|
|
currentContent: '',
|
|
history: [],
|
|
selectedPreview: null,
|
|
|
|
async render() {
|
|
const container = document.getElementById('view-container');
|
|
container.innerHTML = `
|
|
<div class="loading-screen">
|
|
<div class="spinner"></div>
|
|
<p>Caricamento configurazione .env...</p>
|
|
</div>
|
|
`;
|
|
|
|
try {
|
|
const data = await App.api('/api/config/env');
|
|
this.currentContent = data.content || '';
|
|
this.history = data.history || [];
|
|
|
|
this.renderContent(container);
|
|
this.bindEvents();
|
|
} catch (err) {
|
|
console.error('Error loading env config:', err);
|
|
container.innerHTML = `
|
|
<div class="empty-state">
|
|
<div class="empty-state-icon">⚠️</div>
|
|
<div class="empty-state-text">Errore caricamento configurazione .env</div>
|
|
<div class="empty-state-sub">${App.escapeHtml(err.message)}</div>
|
|
</div>
|
|
`;
|
|
}
|
|
},
|
|
|
|
renderContent(container) {
|
|
container.innerHTML = `
|
|
<!-- Header -->
|
|
<div class="page-header" style="margin-bottom: var(--space-lg);">
|
|
<div style="display:flex; align-items:center; gap:var(--space-md);">
|
|
<div style="display:inline-flex; align-items:center; justify-content:center; width:40px; height:40px; background:linear-gradient(135deg, rgba(79,70,229,0.15), rgba(16,185,129,0.15)); border-radius:var(--radius-md); color:var(--accent-primary); font-size:1.3rem;">⚙️</div>
|
|
<div>
|
|
<h1 style="margin:0; font-size:1.3rem; font-weight:700; color:var(--text-primary);">Configurazione File .env</h1>
|
|
<p style="margin:2px 0 0 0; font-size:0.83rem; color:var(--text-secondary);">
|
|
Modifica le variabili d'ambiente (.env). Ad ogni salvataggio viene creata automaticamente una copia di backup nello storico locale SQLite (internal.db).
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Main Layout: 2 Columns -->
|
|
<div style="display:grid; grid-template-columns: 1fr 380px; gap: var(--space-lg); align-items: start;">
|
|
|
|
<!-- Left: Editor Card -->
|
|
<div class="card" style="background:var(--bg-card); border:1px solid var(--border-subtle); border-radius:var(--radius-lg); padding:var(--space-lg); box-shadow:var(--shadow-sm);">
|
|
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:var(--space-md);">
|
|
<div style="display:flex; align-items:center; gap:8px;">
|
|
<h3 style="margin:0; font-size:1rem; font-weight:600; color:var(--text-primary);">📝 Editor File .env</h3>
|
|
<span class="badge" style="background:var(--bg-tertiary); color:var(--text-tertiary); font-size:0.75rem;">Modifica Diretta</span>
|
|
</div>
|
|
<button class="btn btn-ghost btn-xs" id="env-reload-btn" title="Ricarica contenuto da disco">
|
|
🔄 Ricarica
|
|
</button>
|
|
</div>
|
|
|
|
<div style="margin-bottom: var(--space-md);">
|
|
<textarea id="env-editor-content" rows="22" spellcheck="false" style="width:100%; font-family: 'Consolas', 'Courier New', monospace; font-size:0.88rem; line-height:1.45; padding:12px; background:var(--bg-tertiary); color:var(--text-primary); border:1px solid var(--border-subtle); border-radius:var(--radius-md); resize:vertical; box-sizing:border-box; outline:none;" placeholder="# Inserisci qui le configurazioni .env...">${App.escapeHtml(this.currentContent)}</textarea>
|
|
</div>
|
|
|
|
<div style="display:flex; flex-direction:column; gap:var(--space-md);">
|
|
<div>
|
|
<label style="display:block; font-size:0.78rem; font-weight:600; text-transform:uppercase; letter-spacing:0.04em; color:var(--text-tertiary); margin-bottom:4px;">
|
|
Nota / Descrizione Modifica (Opzionale)
|
|
</label>
|
|
<input type="text" id="env-note-input" class="form-input" placeholder="Es: Modificata porta DB, aggiornata chiave API Azure..." style="width:100%; box-sizing:border-box;" />
|
|
</div>
|
|
|
|
<div style="display:flex; align-items:center; justify-content:space-between; gap:var(--space-md);">
|
|
<div style="font-size:0.78rem; color:var(--text-tertiary);" id="env-status-msg">
|
|
Le modifiche avranno effetto sui moduli del server al riavvio.
|
|
</div>
|
|
<button class="btn btn-primary" id="env-save-btn" style="padding:0 var(--space-xl); height:38px; font-size:0.88rem; font-weight:600; min-width:140px; display:inline-flex; align-items:center; justify-content:center; gap:6px;">
|
|
<span>💾 Salva Modifiche</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Right: History Panel -->
|
|
<div class="card" style="background:var(--bg-card); border:1px solid var(--border-subtle); border-radius:var(--radius-lg); padding:var(--space-lg); box-shadow:var(--shadow-sm);">
|
|
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:var(--space-md); padding-bottom:var(--space-xs); border-bottom:1px solid var(--border-subtle);">
|
|
<h3 style="margin:0; font-size:1rem; font-weight:600; color:var(--text-primary);">📜 Storico Modifiche</h3>
|
|
<span class="badge" style="background:rgba(16,185,129,0.15); color:#10b981; font-size:0.75rem; font-weight:600;">${this.history.length} Versioni</span>
|
|
</div>
|
|
|
|
<div id="env-history-list" style="display:flex; flex-direction:column; gap:var(--space-sm); max-height: 620px; overflow-y:auto; padding-right:4px;">
|
|
${this.renderHistoryList()}
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- Preview Modal Container -->
|
|
<div id="env-preview-modal-container"></div>
|
|
`;
|
|
},
|
|
|
|
renderHistoryList() {
|
|
if (!this.history || this.history.length === 0) {
|
|
return `
|
|
<div style="text-align:center; padding:30px 10px; color:var(--text-tertiary); font-size:0.85rem;">
|
|
Nessuna versione registrata nello storico. Salva una modifica per iniziare l'archiviazione.
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
return this.history.map((h, idx) => {
|
|
const isCurrentVersion = idx === 0;
|
|
const formattedDate = App.formatDateTime ? App.formatDateTime(h.created_at) : h.created_at;
|
|
|
|
return `
|
|
<div class="env-history-card" data-history-id="${h.id}" style="background:var(--bg-tertiary); border:1px solid var(--border-subtle); border-radius:var(--radius-md); padding:10px 12px; transition:all 0.15s ease;">
|
|
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:4px;">
|
|
<div style="display:flex; align-items:center; gap:6px;">
|
|
<span class="badge" style="background:var(--bg-secondary); border:1px solid var(--border-subtle); font-weight:700; font-size:0.75rem; color:var(--text-primary);">#${h.id}</span>
|
|
<button class="env-history-delete-btn" data-id="${h.id}" style="background:var(--bg-tertiary); border:1px solid var(--border-subtle); border-radius:var(--radius-sm); cursor:pointer; padding:2px 5px; display:inline-flex; align-items:center; justify-content:center; color:var(--danger); transition:all 0.15s ease;" title="Elimina questa versione dallo storico">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:13px; height:13px;"><polyline points="3 6 5 6 21 6"/><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"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/></svg>
|
|
</button>
|
|
${isCurrentVersion ? `<span class="badge" style="background:rgba(16,185,129,0.2); color:#10b981; font-weight:600; font-size:0.7rem;">Attuale</span>` : ''}
|
|
</div>
|
|
<span style="font-size:0.75rem; color:var(--text-tertiary); font-family:monospace;">${formattedDate}</span>
|
|
</div>
|
|
|
|
<div style="font-size:0.82rem; color:var(--text-secondary); margin-bottom:6px; word-break:break-word;">
|
|
<strong>${App.escapeHtml(h.agent_name || 'Agente')}</strong>: ${App.escapeHtml(h.note || 'Modifica senza nota')}
|
|
</div>
|
|
|
|
<div style="display:flex; align-items:center; justify-content:flex-end; gap:6px; padding-top:4px; border-top:1px dashed var(--border-subtle);">
|
|
<button class="btn btn-ghost btn-xs env-history-preview-btn" data-id="${h.id}" style="height:22px; padding:0 8px; font-size:0.75rem;" title="Anteprima del file .env di questa versione">
|
|
👁️ Anteprima
|
|
</button>
|
|
|
|
${!isCurrentVersion ? `
|
|
<button class="btn btn-primary btn-xs env-history-restore-btn" data-id="${h.id}" style="height:22px; padding:0 8px; font-size:0.75rem; background:var(--accent-primary); border-color:var(--accent-primary);" title="Ripristina questa versione su disco .env">
|
|
🔄 Ripristina
|
|
</button>
|
|
` : ''}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
},
|
|
|
|
bindEvents() {
|
|
const saveBtn = document.getElementById('env-save-btn');
|
|
const reloadBtn = document.getElementById('env-reload-btn');
|
|
const editorTextarea = document.getElementById('env-editor-content');
|
|
const noteInput = document.getElementById('env-note-input');
|
|
|
|
if (saveBtn) {
|
|
saveBtn.addEventListener('click', async () => {
|
|
const content = editorTextarea.value;
|
|
const note = noteInput.value.trim();
|
|
|
|
try {
|
|
saveBtn.disabled = true;
|
|
saveBtn.textContent = 'Salvataggio...';
|
|
|
|
const res = await App.api('/api/config/env', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ content, note })
|
|
});
|
|
|
|
Toast.success(res.message || 'File .env aggiornato con successo!');
|
|
if (noteInput) noteInput.value = '';
|
|
|
|
this.currentContent = content;
|
|
this.history = res.history || [];
|
|
|
|
try {
|
|
await App.ensureLookups(true);
|
|
App.updateDailyTimer();
|
|
} catch (_) {}
|
|
|
|
// Re-render history panel
|
|
const historyListDiv = document.getElementById('env-history-list');
|
|
if (historyListDiv) {
|
|
historyListDiv.innerHTML = this.renderHistoryList();
|
|
this.bindHistoryEvents();
|
|
}
|
|
|
|
} catch (err) {
|
|
Toast.error('Errore salvataggio file .env: ' + err.message);
|
|
} finally {
|
|
saveBtn.disabled = false;
|
|
saveBtn.innerHTML = '<span>💾 Salva Modifiche</span>';
|
|
}
|
|
});
|
|
}
|
|
|
|
if (reloadBtn) {
|
|
reloadBtn.addEventListener('click', () => {
|
|
this.render();
|
|
});
|
|
}
|
|
|
|
this.bindHistoryEvents();
|
|
},
|
|
|
|
bindHistoryEvents() {
|
|
// Preview buttons
|
|
document.querySelectorAll('.env-history-preview-btn').forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
const id = parseInt(btn.dataset.id, 10);
|
|
const record = this.history.find(h => h.id === id);
|
|
if (record) {
|
|
this.showPreviewModal(record);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Restore buttons
|
|
document.querySelectorAll('.env-history-restore-btn').forEach(btn => {
|
|
btn.addEventListener('click', async () => {
|
|
const id = parseInt(btn.dataset.id, 10);
|
|
const record = this.history.find(h => h.id === id);
|
|
if (!record) return;
|
|
|
|
const confirmed = await App.confirm(
|
|
`Ripristino File .env (Versione #${record.id})`,
|
|
`Sei sicuro di voler ripristinare il file .env alla versione #${record.id} salvata il ${record.created_at}?\nL'operazione sovrascriverà il file .env attuale e salverà una nuova voce di ripristino nello storico.`
|
|
);
|
|
|
|
if (!confirmed) return;
|
|
|
|
try {
|
|
btn.disabled = true;
|
|
const res = await App.api(`/api/config/env/restore/${id}`, {
|
|
method: 'POST'
|
|
});
|
|
|
|
Toast.success(res.message || `Versione #${id} ripristinata con successo!`);
|
|
this.currentContent = res.content || record.content;
|
|
this.history = res.history || [];
|
|
|
|
try {
|
|
await App.ensureLookups(true);
|
|
App.updateDailyTimer();
|
|
} catch (_) {}
|
|
|
|
const editorTextarea = document.getElementById('env-editor-content');
|
|
if (editorTextarea) {
|
|
editorTextarea.value = this.currentContent;
|
|
}
|
|
|
|
const historyListDiv = document.getElementById('env-history-list');
|
|
if (historyListDiv) {
|
|
historyListDiv.innerHTML = this.renderHistoryList();
|
|
this.bindHistoryEvents();
|
|
}
|
|
} catch (err) {
|
|
Toast.error('Errore durante il ripristino: ' + err.message);
|
|
} finally {
|
|
btn.disabled = false;
|
|
}
|
|
});
|
|
});
|
|
|
|
// Delete buttons
|
|
document.querySelectorAll('.env-history-delete-btn').forEach(btn => {
|
|
btn.addEventListener('click', async () => {
|
|
const id = parseInt(btn.dataset.id, 10);
|
|
const record = this.history.find(h => h.id === id);
|
|
if (!record) return;
|
|
|
|
const confirmed = await App.confirm(
|
|
`Eliminazione Versione #${record.id}`,
|
|
`Sei sicuro di voler eliminare permanentemente la versione #${record.id} dallo storico?`
|
|
);
|
|
|
|
if (!confirmed) return;
|
|
|
|
try {
|
|
btn.disabled = true;
|
|
let res;
|
|
try {
|
|
res = await App.api(`/api/config/env/history/${id}`, { method: 'DELETE' });
|
|
} catch (delErr) {
|
|
// Fallback to POST endpoint if DELETE route returned 404
|
|
res = await App.api(`/api/config/env/history/${id}/delete`, { method: 'POST' });
|
|
}
|
|
|
|
Toast.success(res.message || `Versione #${id} eliminata dallo storico!`);
|
|
this.history = res.history || [];
|
|
|
|
const historyListDiv = document.getElementById('env-history-list');
|
|
if (historyListDiv) {
|
|
historyListDiv.innerHTML = this.renderHistoryList();
|
|
this.bindHistoryEvents();
|
|
}
|
|
} catch (err) {
|
|
Toast.error('Errore durante l\'eliminazione dello storico: ' + err.message);
|
|
btn.disabled = false;
|
|
}
|
|
});
|
|
});
|
|
},
|
|
|
|
showPreviewModal(record) {
|
|
const modalContainer = document.getElementById('env-preview-modal-container');
|
|
if (!modalContainer) return;
|
|
|
|
modalContainer.innerHTML = `
|
|
<div class="modal-overlay" style="position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.6); z-index:2000; display:flex; align-items:center; justify-content:center; backdrop-filter:blur(4px);">
|
|
<div class="modal-content" style="background:var(--bg-card); border:1px solid var(--border-subtle); border-radius:var(--radius-lg); width:750px; max-width:92vw; max-height:85vh; display:flex; flex-direction:column; box-shadow:var(--shadow-xl); overflow:hidden;">
|
|
|
|
<div style="padding:var(--space-md) var(--space-lg); border-bottom:1px solid var(--border-subtle); display:flex; align-items:center; justify-content:space-between; background:var(--bg-secondary);">
|
|
<div style="display:flex; align-items:center; gap:8px;">
|
|
<span class="badge" style="background:var(--accent-primary); color:#fff; font-weight:700;">Versione #${record.id}</span>
|
|
<span style="font-size:0.85rem; font-weight:600; color:var(--text-primary);">${App.escapeHtml(record.agent_name || 'Agente')}</span>
|
|
<span style="font-size:0.78rem; color:var(--text-tertiary); font-family:monospace;">(${App.formatDateTime ? App.formatDateTime(record.created_at) : record.created_at})</span>
|
|
</div>
|
|
<button id="preview-modal-close" class="btn btn-ghost btn-xs" style="font-size:1.1rem; line-height:1;">✕</button>
|
|
</div>
|
|
|
|
<div style="padding:var(--space-md) var(--space-lg); border-bottom:1px solid var(--border-subtle); font-size:0.83rem; color:var(--text-secondary); background:var(--bg-tertiary);">
|
|
<strong>Nota:</strong> ${App.escapeHtml(record.note || 'Nessuna nota fornita')}
|
|
</div>
|
|
|
|
<div style="padding:var(--space-md) var(--space-lg); overflow-y:auto; flex:1;">
|
|
<pre style="margin:0; font-family:'Consolas', 'Courier New', monospace; font-size:0.85rem; line-height:1.45; white-space:pre-wrap; word-break:break-all; background:var(--bg-tertiary); padding:12px; border-radius:var(--radius-md); border:1px solid var(--border-subtle); color:var(--text-primary);">${App.escapeHtml(record.content)}</pre>
|
|
</div>
|
|
|
|
<div style="padding:var(--space-md) var(--space-lg); border-top:1px solid var(--border-subtle); display:flex; align-items:center; justify-content:space-between; background:var(--bg-secondary);">
|
|
<button class="btn btn-ghost btn-sm" id="preview-copy-btn">📋 Copia Testo</button>
|
|
<div style="display:flex; gap:8px;">
|
|
<button class="btn btn-ghost btn-sm" id="preview-modal-cancel">Chiudi</button>
|
|
<button class="btn btn-primary btn-sm" id="preview-modal-restore-btn" style="background:var(--accent-primary); border-color:var(--accent-primary);">🔄 Ripristina Questa Versione</button>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
const closeModal = () => { modalContainer.innerHTML = ''; };
|
|
|
|
document.getElementById('preview-modal-close')?.addEventListener('click', closeModal);
|
|
document.getElementById('preview-modal-cancel')?.addEventListener('click', closeModal);
|
|
|
|
document.getElementById('preview-copy-btn')?.addEventListener('click', () => {
|
|
navigator.clipboard.writeText(record.content).then(() => {
|
|
Toast.success('Contenuto copiato negli appunti!');
|
|
}).catch(err => Toast.error('Errore durante la copia: ' + err.message));
|
|
});
|
|
|
|
document.getElementById('preview-modal-restore-btn')?.addEventListener('click', async () => {
|
|
closeModal();
|
|
const restoreBtn = document.querySelector(`.env-history-restore-btn[data-id="${record.id}"]`);
|
|
if (restoreBtn) {
|
|
restoreBtn.click();
|
|
}
|
|
});
|
|
}
|
|
};
|