feat: aggiunta ricerca spudorata. progress: ripreso con il tentativo di invio delle mail che non creino nuovi ticket

This commit is contained in:
VVER440 committed 2026-08-25 23:24:48 +02:00
1 parent 3650bf5790
commit 8d24759a15
18 files changed
+1661 -98

No files matched your search

+315
View File
@@ -0,0 +1,315 @@
/**
* 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>
${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 || [];
// 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 || [];
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;
}
});
});
},
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();
}
});
}
};