feat: aggiunta console giocosa all'interno del sistema. fix: correzione di un warning.

This commit is contained in:
Gabriele Cimaschi committed 2026-09-16 15:55:57 +02:00
1 parent dcc0456070
commit 60259427d3
7 files changed
+805 -11

No files matched your search

+500
View File
@@ -0,0 +1,500 @@
/**
* ConsoleView — Linux Terminal Simulation for OTRS Turbo
* Shortcut: Ctrl + Alt + T
*/
const ConsoleView = {
isOpen: false,
history: [],
historyIndex: -1,
currentTheme: 'dark', // dark, green, amber, cyber
activeAnimation: null,
toggle() {
if (this.isOpen) {
this.close();
} else {
this.open();
}
},
open() {
let modal = document.getElementById('linux-console-modal');
if (!modal) {
this.createModal();
modal = document.getElementById('linux-console-modal');
}
modal.style.display = 'flex';
this.isOpen = true;
const input = document.getElementById('console-input');
if (input) {
input.focus();
}
},
close() {
const modal = document.getElementById('linux-console-modal');
if (modal) {
modal.style.display = 'none';
}
this.stopAnimation();
this.isOpen = false;
},
createModal() {
const overlay = document.createElement('div');
overlay.id = 'linux-console-modal';
overlay.className = 'console-modal-overlay theme-dark';
overlay.innerHTML = `
<div class="console-window">
<div class="console-header">
<div class="console-window-buttons">
<span class="console-btn btn-close" id="console-btn-close" title="Chiudi (Esc)"></span>
<span class="console-btn btn-minimize" id="console-btn-min"></span>
<span class="console-btn btn-maximize" id="console-btn-max"></span>
</div>
<div class="console-title">otrs-turbo@linux-server:~</div>
<div class="console-header-right">
<span class="console-badge">BASH v5.2.15</span>
</div>
</div>
<div class="console-body" id="console-body">
<div class="console-welcome">
<pre class="console-banner">
____ _____ ____ ____ _____ _ _ ____ ____ ___
/ ___|_ _| _ \\/ ___| |_ _| | | | _ \\/ __ ) / _ \\
| | | | | |_) \\___ \\ | | | | | | |_) | _ \\| | | |
| |___ | | | _ < ___) | | | | |_| | _ <| |_) | |_| |
\\____| |_| |_| \\_\\____/ |_| \\___/|_| \\_\\____/ \\___/
</pre>
<div class="console-welcome-text">
Benvenuto nella console di simulazione Linux per <strong>OTRS Turbo</strong>.<br>
Digita <span class="cmd-highlight">help</span> per vedere l'elenco dei comandi disponibili.
</div>
</div>
<div id="console-output"></div>
<div class="console-input-row" id="console-input-row">
<span class="console-prompt"><span class="user-part">agent@otrs-turbo</span>:<span class="path-part">~</span>$</span>
<input type="text" id="console-input" class="console-input" autocomplete="off" spellcheck="false" autocorrect="off" autocapitalize="none" />
</div>
</div>
</div>
`;
document.body.appendChild(overlay);
// Event Bindings
overlay.querySelector('#console-btn-close').addEventListener('click', () => this.close());
overlay.addEventListener('click', (e) => {
if (e.target === overlay) this.close();
else {
const input = document.getElementById('console-input');
if (input && document.activeElement !== input) input.focus();
}
});
const input = overlay.querySelector('#console-input');
input.addEventListener('keydown', (e) => this.handleKeyDown(e));
},
stopAnimation() {
if (this.activeAnimation) {
clearInterval(this.activeAnimation);
this.activeAnimation = null;
}
},
handleKeyDown(e) {
if (e.key === 'Escape') {
e.preventDefault();
this.close();
return;
}
if (e.key === 'Enter') {
const input = e.target;
const cmdText = input.value.trim();
input.value = '';
if (cmdText) {
this.history.push(cmdText);
this.historyIndex = this.history.length;
this.executeCommand(cmdText);
} else {
this.printPrompt('');
}
return;
}
if (e.key === 'ArrowUp') {
e.preventDefault();
if (this.history.length > 0 && this.historyIndex > 0) {
this.historyIndex--;
e.target.value = this.history[this.historyIndex];
}
return;
}
if (e.key === 'ArrowDown') {
e.preventDefault();
if (this.historyIndex < this.history.length - 1) {
this.historyIndex++;
e.target.value = this.history[this.historyIndex];
} else {
this.historyIndex = this.history.length;
e.target.value = '';
}
return;
}
if (e.key === 'Tab') {
e.preventDefault();
this.autocomplete(e.target);
return;
}
if (e.ctrlKey && e.key.toLowerCase() === 'c') {
if (this.activeAnimation) {
e.preventDefault();
this.stopAnimation();
this.printOutput('<span class="text-warning">^C Animation interrupted</span>');
this.showInputRow();
}
}
},
autocomplete(inputEl) {
const val = inputEl.value.trim();
const commands = ['help', 'neofetch', 'otrsfetch', 'tickets', 'ls', 'cat', 'matrix', 'sl', 'sudo', 'whoami', 'theme', 'clear', 'history', 'exit'];
const matches = commands.filter(c => c.startsWith(val));
if (matches.length === 1) {
inputEl.value = matches[0] + ' ';
} else if (matches.length > 1) {
this.printOutput(`<span class="text-muted">${matches.join(' ')}</span>`);
}
},
printPrompt(cmdText) {
const outputDiv = document.getElementById('console-output');
const line = document.createElement('div');
line.className = 'console-line';
line.innerHTML = `<span class="console-prompt"><span class="user-part">agent@otrs-turbo</span>:<span class="path-part">~</span>$</span> <span class="cmd-executed">${App.escapeHtml(cmdText)}</span>`;
outputDiv.appendChild(line);
this.scrollToBottom();
},
printOutput(htmlContent) {
const outputDiv = document.getElementById('console-output');
const line = document.createElement('div');
line.className = 'console-output-line';
line.innerHTML = htmlContent;
outputDiv.appendChild(line);
this.scrollToBottom();
},
scrollToBottom() {
const body = document.getElementById('console-body');
if (body) {
body.scrollTop = body.scrollHeight;
}
},
showInputRow() {
const row = document.getElementById('console-input-row');
if (row) row.style.display = 'flex';
const input = document.getElementById('console-input');
if (input) input.focus();
},
hideInputRow() {
const row = document.getElementById('console-input-row');
if (row) row.style.display = 'none';
},
async executeCommand(rawCmd) {
this.stopAnimation();
this.printPrompt(rawCmd);
const parts = rawCmd.trim().split(/\s+/);
const mainCmd = parts[0].toLowerCase();
const args = parts.slice(1);
switch (mainCmd) {
case 'help':
this.cmdHelp();
break;
case 'clear':
document.getElementById('console-output').innerHTML = '';
break;
case 'whoami':
const agent = App.activeAgent || 'gabriele.cimaschi';
this.printOutput(`<span class="text-success">${App.escapeHtml(agent)}</span> (OTRS Turbo Operator)`);
break;
case 'sudo':
this.printOutput(`<span class="text-danger">agent is not in the sudoers file. This incident will be reported to OTRS Admins.</span>`);
break;
case 'ls':
this.cmdLs();
break;
case 'cat':
this.cmdCat(args[0]);
break;
case 'history':
this.cmdHistory();
break;
case 'neofetch':
case 'otrsfetch':
await this.cmdNeofetch();
break;
case 'tickets':
await this.cmdTickets();
break;
case 'theme':
this.cmdTheme(args[0]);
break;
case 'matrix':
this.cmdMatrix();
break;
case 'sl':
this.cmdSl();
break;
case 'exit':
this.close();
break;
default:
this.printOutput(`<span class="text-danger">bash: ${App.escapeHtml(mainCmd)}: command not found.</span> Digita <span class="cmd-highlight">help</span> per la lista dei comandi.`);
break;
}
},
cmdHelp() {
const helpHtml = `
<div class="console-help-table">
<div><span class="cmd-name">help</span><span class="cmd-desc">Mostra questo messaggio di aiuto</span></div>
<div><span class="cmd-name">neofetch</span> / <span class="cmd-name">otrsfetch</span><span class="cmd-desc">Visualizza informazioni ed ASCII art di OTRS Turbo</span></div>
<div><span class="cmd-name">tickets</span><span class="cmd-desc">Elenca i ticket recenti ed i conteggi direttamente dal DB</span></div>
<div><span class="cmd-name">ls</span><span class="cmd-desc">Elenca i file della directory corrente</span></div>
<div><span class="cmd-name">cat &lt;file&gt;</span><span class="cmd-desc">Legge il contenuto di un file virtuale</span></div>
<div><span class="cmd-name">whoami</span><span class="cmd-desc">Mostra l'agente attualmente autenticato</span></div>
<div><span class="cmd-name">theme &lt;dark|green|amber|cyber&gt;</span><span class="cmd-desc">Cambia il tema cromatico del terminale</span></div>
<div><span class="cmd-name">matrix</span><span class="cmd-desc">Avvia l'animazione della pioggia digitale Matrix (Ctrl+C per uscire)</span></div>
<div><span class="cmd-name">sl</span><span class="cmd-desc">Fai passare la locomotiva a vapore ASCII nel terminale</span></div>
<div><span class="cmd-name">sudo &lt;comando&gt;</span><span class="cmd-desc">Esegue un comando con privilegi di amministratore</span></div>
<div><span class="cmd-name">history</span><span class="cmd-desc">Mostra lo storico dei comandi usati</span></div>
<div><span class="cmd-name">clear</span><span class="cmd-desc">Pulisce lo schermo della console</span></div>
<div><span class="cmd-name">exit</span><span class="cmd-desc">Chiude la finestra del terminale</span></div>
</div>`;
this.printOutput(helpHtml);
},
cmdLs() {
this.printOutput(`
<span class="dir-item">drwxr-xr-x 2 agent otrs 4096 Sep 16 15:00 .</span>
<span class="dir-item">drwxr-xr-x 10 agent otrs 4096 Sep 16 15:00 ..</span>
<span class="file-item">-rw-r--r-- 1 agent otrs 2450 Sep 16 15:00 tickets.db</span>
<span class="file-item">-rw-r--r-- 1 agent otrs 777 Sep 16 15:00 motivational.txt</span>
<span class="file-item">-rw-r--r-- 1 agent otrs 1180 Sep 16 15:00 demotivational.txt</span>
<span class="file-item">-rw-r--r-- 1 agent otrs 512 Sep 16 15:00 secret.key</span>
<span class="file-item">-rwxr-xr-x 1 agent otrs 8192 Sep 16 15:00 system.log</span>
`);
},
cmdCat(fileName) {
if (!fileName) {
this.printOutput('<span class="text-danger">cat: specifica un nome file. Es: cat motivational.txt</span>');
return;
}
const name = fileName.toLowerCase();
if (name === 'motivational.txt') {
const phrases = App.motivationalPhrases || [
"Ogni ticket chiuso è una vittoria verso la fine della giornata!",
"La velocità è la nostra virtù con OTRS Turbo!",
"Mantieni la calma ed applica le macro."
];
const random = phrases[Math.floor(Math.random() * phrases.length)];
this.printOutput(`<span class="text-info">💡 ${App.escapeHtml(random)}</span>`);
} else if (name === 'demotivational.txt') {
const phrases = App.demotivationalPhrases || [
"Nessuna buona azione resta impunita: ogni ticket chiuso ne genera due nuovi.",
"Il weekend si avvicina, ma la coda di ticket non dorme mai.",
"Hai davvero controllato se il cavo di rete è collegato?"
];
const random = phrases[Math.floor(Math.random() * phrases.length)];
this.printOutput(`<span class="text-warning">💀 ${App.escapeHtml(random)}</span>`);
} else if (name === 'secret.key') {
this.printOutput('<span class="text-danger">🔒 Access Denied: 0x8840192A (Encryption key locked by OTRS Turbo core)</span>');
} else if (name === 'tickets.db') {
this.printOutput('<span class="text-muted">SQLite/PG Binary format header. Usare il comando "tickets" per consultare i dati.</span>');
} else if (name === 'system.log') {
this.printOutput('<span class="text-muted">[INFO] 2026-09-16 15:10:00 - OTRS Turbo Engine v1.0 running cleanly.\n[INFO] LDAP auto-sync skipped (within 24h window).\n[OK] Database query latency &lt; 4ms.</span>');
} else {
this.printOutput(`<span class="text-danger">cat: ${App.escapeHtml(fileName)}: No such file or directory</span>`);
}
},
cmdHistory() {
if (this.history.length === 0) {
this.printOutput('<span class="text-muted">Nessun comando nello storico.</span>');
return;
}
const lines = this.history.map((c, i) => ` ${i + 1} ${App.escapeHtml(c)}`).join('\n');
this.printOutput(`<pre class="text-muted">${lines}</pre>`);
},
async cmdNeofetch() {
let ticketStats = { total: 0, open: 0 };
try {
const data = await App.api('/api/tickets/stats');
if (data) {
ticketStats.total = data.total_tickets || data.total || 0;
ticketStats.open = data.open_tickets || data.open || 0;
}
} catch (e) {}
const agent = App.activeAgent || 'gabriele.cimaschi';
const html = `
<div class="neofetch-container">
<pre class="neofetch-logo">
██████╗ ████████╗██████╗ ██████╗
██╔═══██╗╚══██╔══╝██╔══██╗██╔════╝
██║ ██║ ██║ ██████╔╝╚█████╗
██║ ██║ ██║ ██╔══██╗ ╚═══██╗
╚██████╔╝ ██║ ██║ ██║██████╔╝
╚═════╝ ╚═╝ ╚═╝ ╚═╝╚═════╝
⚡ T U R B O E N G I N E
</pre>
<div class="neofetch-info">
<div><strong style="color:var(--accent-primary);">${App.escapeHtml(agent)}</strong>@<strong style="color:var(--accent-primary);">otrs-turbo</strong></div>
<div style="color:var(--border-subtle)">----------------------------------</div>
<div><strong>OS:</strong> OTRS Turbo Linux Web Console</div>
<div><strong>Host:</strong> Enterprise Ticket Engine 1.0</div>
<div><strong>Uptime:</strong> 17 hours, 42 mins</div>
<div><strong>Shell:</strong> OTRS-Bash 5.2</div>
<div><strong>Engine:</strong> Express / PG SQL Node.js 18.x</div>
<div><strong>Tickets In DB:</strong> ${ticketStats.total} total (${ticketStats.open} aperti)</div>
<div><strong>Theme:</strong> ${this.currentTheme}</div>
<div class="color-palette" style="margin-top:6px;">
<span style="background:#ef4444; display:inline-block; width:12px; height:12px; border-radius:2px; margin-right:3px;"></span>
<span style="background:#f59e0b; display:inline-block; width:12px; height:12px; border-radius:2px; margin-right:3px;"></span>
<span style="background:#10b981; display:inline-block; width:12px; height:12px; border-radius:2px; margin-right:3px;"></span>
<span style="background:#3b82f6; display:inline-block; width:12px; height:12px; border-radius:2px; margin-right:3px;"></span>
<span style="background:#8b5cf6; display:inline-block; width:12px; height:12px; border-radius:2px; margin-right:3px;"></span>
<span style="background:#ec4899; display:inline-block; width:12px; height:12px; border-radius:2px; margin-right:3px;"></span>
</div>
</div>
</div>`;
this.printOutput(html);
},
async cmdTickets() {
this.printOutput('<span class="text-muted">Caricamento ultimi ticket dal database...</span>');
try {
const res = await App.api('/api/tickets?page=1&per_page=8');
const tickets = res.tickets || [];
if (tickets.length === 0) {
this.printOutput('<span class="text-warning">Nessun ticket trovato.</span>');
return;
}
let rows = `
<table class="console-table">
<thead>
<tr>
<th>TN</th>
<th>Titolo</th>
<th>Stato</th>
<th>Coda</th>
<th>Cliente</th>
</tr>
</thead>
<tbody>
`;
tickets.forEach(t => {
rows += `
<tr>
<td class="text-accent">#${App.escapeHtml(t.tn)}</td>
<td>${App.escapeHtml((t.title || '').substring(0, 35))}</td>
<td><span class="badge-mini">${App.escapeHtml(t.state_name || '')}</span></td>
<td>${App.escapeHtml(t.queue_name || '')}</td>
<td>${App.escapeHtml(t.customer_first ? `${t.customer_first} ${t.customer_last}` : (t.customer_user_id || '—'))}</td>
</tr>`;
});
rows += `</tbody></table>`;
this.printOutput(rows);
} catch (err) {
this.printOutput(`<span class="text-danger">Errore recupero ticket: ${App.escapeHtml(err.message)}</span>`);
}
},
cmdTheme(themeName) {
const valid = ['dark', 'green', 'amber', 'cyber'];
if (!themeName || !valid.includes(themeName.toLowerCase())) {
this.printOutput('<span class="text-warning">Uso: theme &lt;dark|green|amber|cyber&gt;</span>');
return;
}
const t = themeName.toLowerCase();
const overlay = document.getElementById('linux-console-modal');
if (overlay) {
overlay.className = `console-modal-overlay theme-${t}`;
}
this.currentTheme = t;
this.printOutput(`<span class="text-success">Tema terminale cambiato in: <strong>${t}</strong></span>`);
},
cmdMatrix() {
this.printOutput('<span class="text-success">Inizializzazione matrice in corso... premi Esc o Ctrl+C per fermare.</span>');
this.hideInputRow();
const outputDiv = document.getElementById('console-output');
const matrixBox = document.createElement('pre');
matrixBox.className = 'matrix-stream';
outputDiv.appendChild(matrixBox);
const chars = '0123456789ABCDEFヲアウエオカキケコサシスセソタツテナニヌネハヒホマミムメモヤユラリワ';
const cols = 60;
this.activeAnimation = setInterval(() => {
let line = '';
for (let i = 0; i < cols; i++) {
const char = chars[Math.floor(Math.random() * chars.length)];
line += char + ' ';
}
matrixBox.textContent += line + '\n';
this.scrollToBottom();
if (matrixBox.textContent.length > 3000) {
matrixBox.textContent = matrixBox.textContent.substring(1500);
}
}, 80);
},
cmdSl() {
this.printOutput('<span class="text-warning">Choo Choo! Locomotiva in transito...</span>');
this.hideInputRow();
const outputDiv = document.getElementById('console-output');
const trainBox = document.createElement('pre');
trainBox.className = 'sl-train-box';
outputDiv.appendChild(trainBox);
const trainAscii = [
" ==== ________ _____",
" _D _| |_______/ \\__I_I_____===__|__ |",
" | (_)--- ___/ OTRS \\_____/___======|___|",
" /___/____/______TURBO________/________(O)___/",
" $(O)(O)(O) (O)(O)(O)"
];
let pos = 80;
this.activeAnimation = setInterval(() => {
pos -= 3;
if (pos < -60) {
this.stopAnimation();
this.showInputRow();
trainBox.remove();
this.printOutput('<span class="text-success">La locomotiva ha completato la corsa!</span>');
return;
}
const spaces = ' '.repeat(Math.max(0, pos));
trainBox.textContent = trainAscii.map(l => spaces + l).join('\n');
this.scrollToBottom();
}, 50);
}
};
window.ConsoleView = ConsoleView;
+7
View File
@@ -79,6 +79,13 @@ const ShortcutsView = {
context: 'Globale',
defaultCombo: { ctrl: true, alt: false, shift: true, key: 'Backspace', enabled: true }
},
{
id: 'open_terminal_console',
name: 'Apri Terminale Linux',
description: 'Apre la finestra di simulazione del terminale Linux in overlay',
context: 'Globale',
defaultCombo: { ctrl: true, alt: true, shift: false, key: 't', enabled: true }
},
{
id: 'create_ticket_submit',
name: 'Invia Nuovo Ticket',