<?php
// console_monitor.php β logs-first viewer.
// β’ Manual-only by default. Optional one-shot autorun with ?autorun=1
// β’ Buttons: Refresh HTML (clears + runs), Run PHP (clears + runs via AJAX), Copy Visible Log
// β’ Default tab: Errors
// β’ PHP run happens via AJAX (action=run_php) so logs/Viewer update inside the monitor
// β’ Captures runtime JS errors, unhandledrejection, console; PHP errors via set_error_handler
error_reporting(E_ALL);
ini_set('display_errors', 1);
session_start();
/* --------------------------- Params & Flags --------------------------- */
$executeFile = $_GET['file'] ?? '';
$autorun = isset($_GET['autorun']) && $_GET['autorun'] !== '0'; // ?autorun=1
if ($executeFile === basename(__FILE__)) $executeFile = '';
/* ------------------------- Session log buckets ------------------------ */
if (!isset($_SESSION['logs'])) {
$_SESSION['logs'] = [
'console' => [],
'errors' => [],
'php_errors' => [],
'network' => [],
];
}
/* --------------------------- PHP error hook -------------------------- */
set_error_handler(function($errno, $errstr, $errfile, $errline) {
$_SESSION['logs']['php_errors'][] = [
'message' => $errstr,
'filename' => $errfile,
'lineno' => $errline,
'timestamp' => date('c')
];
});
/* ------------------------------ AJAX API ----------------------------- */
if (isset($_POST['action'])) {
header('Content-Type: application/json; charset=utf-8');
switch ($_POST['action']) {
case 'log':
$_SESSION['logs']['console'][] = [
'level' => $_POST['level'] ?? 'log',
'message' => $_POST['message'] ?? '',
'timestamp' => date('c')
];
echo json_encode(['success' => true]);
break;
case 'error':
$_SESSION['logs']['errors'][] = [
'message' => $_POST['message'] ?? '',
'filename' => $_POST['filename'] ?? '',
'lineno' => $_POST['lineno'] ?? '',
'colno' => $_POST['colno'] ?? '',
'stack' => $_POST['stack'] ?? '',
'timestamp' => date('c')
];
echo json_encode(['success' => true]);
break;
case 'net':
$_SESSION['logs']['network'][] = [
'method' => $_POST['method'] ?? '',
'url' => $_POST['url'] ?? '',
'status' => $_POST['status'] ?? '',
'ok' => $_POST['ok'] ?? '',
'duration' => $_POST['duration'] ?? '',
'error' => $_POST['error'] ?? '',
'timestamp' => date('c')
];
echo json_encode(['success' => true]);
break;
case 'get_logs':
echo json_encode($_SESSION['logs']);
break;
case 'clear_logs':
$_SESSION['logs'] = ['console'=>[],'errors'=>[],'php_errors'=>[],'network'=>[]];
echo json_encode(['success' => true]);
break;
case 'run_php': {
// Run a PHP file only via AJAX (keeps console/Viewer in sync)
$file = $_POST['file'] ?? '';
$safe = basename($file);
$path = __DIR__ . '/' . $safe;
$ext = strtolower(pathinfo($safe, PATHINFO_EXTENSION));
if (!$file || !file_exists($path) || $ext !== 'php') {
echo json_encode(['success'=>false, 'error'=>'Invalid PHP file']);
break;
}
// Clear logs first (same behavior as Refresh HTML)
$_SESSION['logs'] = ['console'=>[],'errors'=>[],'php_errors'=>[],'network'=>[]];
ob_start();
try { include $path; }
catch (Throwable $t) {
// Also push a php_error entry (in case handler didnβt catch)
$_SESSION['logs']['php_errors'][] = [
'message' => $t->getMessage(),
'filename' => $t->getFile(),
'lineno' => $t->getLine(),
'timestamp' => date('c')
];
echo "Fatal error: " . htmlspecialchars($t->getMessage(), ENT_QUOTES, 'UTF-8');
}
$output = ob_get_clean();
echo json_encode(['success'=>true, 'output'=>$output]);
break;
}
}
exit;
}
/* --------------------- File type detection (view) -------------------- */
$output = '';
$fileContent = '';
$isHtmlFile = false;
$isPhpFile = false;
if ($executeFile) {
$path = __DIR__ . '/' . basename($executeFile);
$ext = strtolower(pathinfo($executeFile, PATHINFO_EXTENSION));
$allowed = ['php','html','htm'];
if (!file_exists($path) || !in_array($ext, $allowed, true)) {
$output = "Error: Invalid or inaccessible file.";
$executeFile = '';
} else {
$fileContent = file_get_contents($path);
if ($ext === 'php') {
// Donβt execute now β only via AJAX Run PHP
$isPhpFile = true;
$output = ''; // no placeholder
} else {
$isHtmlFile = true;
$output = ''; // no placeholder
}
}
}
$backUrl = 'editor.php' . ($executeFile ? ('?view=' . urlencode($executeFile)) : '');
$fileLabel = $executeFile;
?>
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Console Monitor</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;background:#f5f5f5;color:#111}
.header{background:#2c3e50;color:#fff;padding:12px 16px;display:flex;justify-content:space-between;align-items:center}
.back-btn{background:rgba(255,255,255,.1);color:#fff;padding:8px 12px;text-decoration:none;border-radius:6px;border:1px solid rgba(255,255,255,.2)}
.controls{padding:10px 16px;background:#f8f9fa;border-bottom:1px solid #ddd;display:flex;gap:10px;flex-wrap:wrap;position:sticky;top:0;z-index:10}
.btn{padding:10px 16px;border:none;border-radius:8px;cursor:pointer;font-size:14px}
.btn-success{background:#27ae60;color:#fff}
.btn-secondary{background:#6c757d;color:#fff}
.main{display:flex;flex-direction:column;gap:12px;padding:12px 16px}
.panel{background:#fff;border:1px solid #ddd;border-radius:10px;overflow:hidden}
.panel-header{padding:10px 14px;background:#f8f9fa;border-bottom:1px solid #eee;font-weight:600}
.panel-content{padding:0}
.tabs{display:flex;background:#f8f9fa;border-bottom:1px solid #eee}
.tab{padding:10px 14px;cursor:pointer;border-bottom:3px solid transparent;font-size:12px}
.tab.active{background:#fff;border-bottom-color:#3498db}
.logs-content{max-height:360px;min-height:240px;padding:14px;background:#1e1e1e;color:#fff;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;overflow:auto}
.log-entry{margin-bottom:8px;padding:6px;border-radius:4px}
.log-entry.error{background:rgba(231,76,60,.1);color:#ff6b6b}
.log-entry.warn{background:rgba(241,196,15,.1);color:#feca57}
.log-entry.info{color:#74b9ff}
.log-entry.log{color:#fff}
.timestamp{color:#95a5a6;font-size:11px}
.hidden{display:none}
.stack pre{white-space:pre-wrap;word-break:break-word}
.viewer-wrap{padding:14px}
.output-content{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:13px;white-space:pre-wrap;background:#fff;margin-bottom:10px;border:1px solid #eee;border-radius:8px;padding:10px;min-height:40px}
#htmlPreview{width:100%;height:520px;border:1px solid #ddd;border-radius:8px;background:#fff}
.code-wrap{padding:0}
.code-editor{width:100%;height:620px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:13px;line-height:1.4;border:none;background:#1e1e1e;color:#fff;padding:16px;resize:none;outline:none}
</style>
</head>
<body>
<div class="header">
<h1>Console Monitor <?= $executeFile ? ('- ' . htmlspecialchars($executeFile, ENT_QUOTES, 'UTF-8')) : '' ?></h1>
<a class="back-btn" href="<?= htmlspecialchars($backUrl, ENT_QUOTES, 'UTF-8') ?>">β Back to Editor</a>
</div>
<div class="controls">
<?php if ($isHtmlFile): ?>
<button class="btn btn-success" onclick="refreshHtml()">π Refresh HTML</button>
<?php endif; ?>
<?php if ($isPhpFile): ?>
<button class="btn btn-success" onclick="runPhp()">βΆ Run PHP</button>
<?php endif; ?>
<?php if ($isHtmlFile || $isPhpFile): ?>
<button class="btn btn-secondary" onclick="copyCurrentLog()">π Copy Visible Log</button>
<?php endif; ?>
</div>
<div class="main">
<!-- Logs -->
<div class="panel">
<div class="panel-header">Logs</div>
<div class="panel-content">
<div class="tabs">
<div class="tab" onclick="switchTab('console', this)">Console</div>
<div class="tab active" onclick="switchTab('errors', this)">Errors</div>
<div class="tab" onclick="switchTab('php_errors', this)">PHP Errors</div>
<div class="tab" onclick="switchTab('network', this)">Network</div>
</div>
<div id="console-logs" class="logs-content hidden"></div>
<div id="errors-logs" class="logs-content"></div>
<div id="php_errors-logs" class="logs-content hidden"></div>
<div id="network-logs" class="logs-content hidden"></div>
</div>
</div>
<!-- Viewer -->
<div class="panel">
<div class="panel-header">Viewer</div>
<div class="panel-content viewer-wrap">
<?php if (!$isHtmlFile && !$isPhpFile): ?>
<div class="output-content"><?= htmlspecialchars($output, ENT_QUOTES, 'UTF-8') ?></div>
<?php endif; ?>
<?php if ($isHtmlFile): ?>
<iframe id="htmlPreview" sandbox="allow-scripts allow-same-origin"></iframe>
<?php endif; ?>
<?php if ($isPhpFile): ?>
<div id="phpOutput" class="output-content"></div>
<?php endif; ?>
</div>
</div>
<!-- Code -->
<?php if ($fileContent): ?>
<div class="panel">
<div class="panel-header">Source Code</div>
<div class="panel-content code-wrap">
<textarea class="code-editor" readonly><?= htmlspecialchars($fileContent, ENT_QUOTES, 'UTF-8') ?></textarea>
</div>
</div>
<?php endif; ?>
</div>
<?php if ($isHtmlFile || $isPhpFile): ?>
<!-- JSON payload with file info/content -->
<script id="filePayload" type="application/json">
<?= json_encode(
['content' => $fileContent, 'filename' => $fileLabel, 'isHtml' => $isHtmlFile, 'isPhp' => $isPhpFile, 'autorun' => $autorun],
JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT
) . "\n" ?>
</script>
<script>
(function(){
try{
const payload = JSON.parse(document.getElementById('filePayload').textContent || '{}');
window.htmlContent = payload.content || '';
window.filename = payload.filename || '';
window.isHtml = !!payload.isHtml;
window.isPhp = !!payload.isPhp;
window.autorun = !!payload.autorun;
}catch(e){}
})();
</script>
<?php endif; ?>
<script>
let currentTab = 'errors';
let logs = { console:[], errors:[], php_errors:[], network:[] };
let _blobUrl = null;
// Optional one-shot autorun (only if you call ?autorun=1)
document.addEventListener('DOMContentLoaded', function(){
displayLogs();
if (window.autorun) {
if (window.isHtml) refreshHtml();
else if (window.isPhp) runPhp();
}
});
/* ------------------------- HTML workflow ------------------------- */
async function refreshHtml(){
// Clear logs on server + UI
try{
await fetch('console_monitor.php', {
method:'POST',
headers:{'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8'},
body:'action=clear_logs'
});
}catch(e){}
logs = { console:[], errors:[], php_errors:[], network:[] };
displayLogs();
// Run HTML fresh
runHtml(true);
// Pull first posts shortly after (no timer loop)
setTimeout(refreshLogs, 350);
}
function runHtml(fresh){
if (!window.htmlContent) return;
const frame = document.getElementById('htmlPreview'); if (!frame) return;
var monitorScript =
'<script>(function(){' +
' var FILE = ' + JSON.stringify(<?= json_encode($fileLabel, JSON_HEX_TAG|JSON_HEX_AMP|JSON_HEX_APOS|JSON_HEX_QUOT) ?>) + ';' +
' window.addEventListener("error", function(e){try{var t=e.target||null;var isRes=t&&(t.src||t.href);var msg=String(e.message||(isRes?"Resource load error":"Error"));var file=e.filename||(t&&(t.src||t.href))||FILE||"";var line=e.lineno||"";var col=e.colno||"";var stack=(e.error&&e.error.stack)?String(e.error.stack):"";parent.postMessage({type:"error",message:msg,filename:file,lineno:line,colno:col,stack:stack},"*");}catch(_){ }}, true);' +
' window.addEventListener("unhandledrejection", function(ev){try{var r=ev.reason;var msg=r&&(r.message||String(r))||"Unhandled rejection";var stack=r&&r.stack?String(r.stack):"";parent.postMessage({type:"error",message:msg,filename:FILE,lineno:"",colno:"",stack:stack},"*");}catch(_){ }});' +
' ["log","error","warn","info"].forEach(function(m){var o=console[m];console[m]=function(){try{o&&o.apply(console,arguments);}catch(_){ }try{var text=Array.from(arguments).map(function(a){try{return typeof a==="string"?a:JSON.stringify(a);}catch(_){return String(a);}}).join(" ");parent.postMessage({type:"console",level:m,message:text},"*");}catch(_){ }}});' +
'})();<\/script>';
let html = String(window.htmlContent);
if (html.includes('<head>')) html = html.replace('<head>', '<head>' + monitorScript);
else if (html.includes('</body>')) html = html.replace('</body>', monitorScript + '</body>');
else if (html.includes('<html')) html = html + monitorScript;
else html = '<!doctype html><html><head>' + monitorScript + '</head><body>' + html + '</body></html>';
if (_blobUrl && fresh) { try{ URL.revokeObjectURL(_blobUrl); }catch{} _blobUrl = null; }
_blobUrl = _blobUrl || URL.createObjectURL(new Blob([html], {type:'text/html'}));
frame.src = _blobUrl;
}
/* -------------------------- PHP workflow ------------------------- */
async function runPhp(){
if (!window.filename) return;
// Clear logs on server + UI (to mirror Refresh HTML behavior)
try{
await fetch('console_monitor.php', {
method:'POST',
headers:{'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8'},
body:'action=clear_logs'
});
}catch(e){}
logs = { console:[], errors:[], php_errors:[], network:[] };
displayLogs();
// Run PHP via AJAX so php_errors flow into the logs panel
const p = new URLSearchParams({ action:'run_php', file: window.filename });
const res = await fetch('console_monitor.php', {
method:'POST',
headers:{'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8'},
body:p.toString()
}).then(r=>r.json()).catch(()=>({success:false,error:'request failed'}));
const out = document.getElementById('phpOutput');
if (out) out.textContent = res && res.output ? res.output : (res.error || '');
// Pull the logs now that PHP has run
refreshLogs();
}
/* --------------------------- Messaging --------------------------- */
window.addEventListener('message', e => {
const d = e.data || {};
if (d.type === 'error') { sendError(d); refreshLogs(); }
if (d.type === 'console'){ sendLog(d.level, d.message); refreshLogs(); }
if (d.type === 'net') { sendNet(d); refreshLogs(); }
});
/* ------------------------- Server helpers ------------------------ */
function sendLog(level, message){
fetch('console_monitor.php', {
method:'POST',
headers:{'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8'},
body:'action=log&level='+encodeURIComponent(level)+'&message='+encodeURIComponent(message)
}).catch(()=>{});
}
function sendError(d){
const p = new URLSearchParams({
action:'error',
message:d.message||'',
filename:d.filename||'',
lineno:String(d.lineno||''),
colno:String(d.colno||''),
stack:d.stack||''
});
fetch('console_monitor.php', { method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8'}, body:p.toString() }).catch(()=>{});
}
function sendNet(d){
const p = new URLSearchParams({
action:'net',
method:d.method||'',
url:d.url||'',
status:String(d.status||''),
ok:String(d.ok||0),
duration:String(d.duration||''),
error:d.error||''
});
fetch('console_monitor.php', { method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8'}, body:p.toString() }).catch(()=>{});
}
/* ------------------------ Tabs & Rendering ----------------------- */
function switchTab(tab, el){
document.querySelectorAll('.tab').forEach(t=>t.classList.remove('active'));
if (el) el.classList.add('active');
document.querySelectorAll('.logs-content').forEach(c=>c.classList.add('hidden'));
const pane = document.getElementById(tab + '-logs'); if (pane) pane.classList.remove('hidden');
currentTab = tab; displayLogs();
}
function esc(s){ return String(s).replace(/&/g,'&').replace(/</g,'<'); }
function renderMessage(msg){
const MAX = 600, s = String(msg || '');
if (s.length <= MAX) return esc(s);
const head = esc(s.slice(0, MAX));
return head + 'β¦ ' +
'<a href="#" onclick="this.nextElementSibling.classList.remove(\'hidden\'); this.remove(); return false;">show more</a>' +
'<div class="hidden stack"><pre>' + esc(s) + '</pre></div>';
}
function displayLogs(){
// Console
(function(){
const c = document.getElementById('console-logs');
c.innerHTML = '';
const arr = logs.console || [];
if (arr.length === 0) { c.innerHTML = '<div class="log-entry info">No console logs</div>'; return; }
arr.forEach(l=>{
const ts = l.timestamp ? new Date(l.timestamp).toLocaleTimeString() : '';
const e = document.createElement('div');
e.className = 'log-entry ' + (l.level||'log');
e.innerHTML = `<span class="timestamp">[${ts}]</span> [${(l.level||'LOG').toUpperCase()}] ${esc(l.message)}`;
c.appendChild(e);
});
c.scrollTop = c.scrollHeight;
})();
// Errors
(function(){
const c = document.getElementById('errors-logs');
c.innerHTML = '';
const arr = logs.errors || [];
if (arr.length === 0) { c.innerHTML = '<div class="log-entry info">No errors</div>'; return; }
arr.forEach(l=>{
const ts = l.timestamp ? new Date(l.timestamp).toLocaleTimeString() : '';
const file = esc(l.filename||'');
const line = l.lineno ? (':'+esc(l.lineno)) : '';
const col = l.colno ? (':'+esc(l.colno)) : '';
const stack= l.stack ? `<div class="stack"><strong>Stack:</strong><pre>${renderMessage(l.stack)}</pre></div>` : '';
const e = document.createElement('div');
e.className = 'log-entry error';
e.innerHTML = `<span class="timestamp">[${ts}]</span>
<div><strong>Error:</strong> ${renderMessage(l.message)}</div>
<div><strong>File:</strong> ${file}${line}${col}</div>
${stack}`;
c.appendChild(e);
});
c.scrollTop = c.scrollHeight;
})();
// PHP Errors
(function(){
const c = document.getElementById('php_errors-logs');
c.innerHTML = '';
const arr = logs.php_errors || [];
if (arr.length === 0) { c.innerHTML = '<div class="log-entry info">No PHP errors</div>'; return; }
arr.forEach(l=>{
const ts = l.timestamp ? new Date(l.timestamp).toLocaleTimeString() : '';
const e = document.createElement('div');
e.className = 'log-entry error';
e.innerHTML = `<span class="timestamp">[${ts}]</span>
<div><strong>PHP:</strong> ${renderMessage(l.message)}</div>
<div><strong>File:</strong> ${esc(l.filename||'')}:${esc(l.lineno||'')}</div>`;
c.appendChild(e);
});
c.scrollTop = c.scrollHeight;
})();
// Network (if enabled server messages)
(function(){
const c = document.getElementById('network-logs');
c.innerHTML = '';
const arr = logs.network || [];
if (arr.length === 0) { c.innerHTML = '<div class="log-entry info">No network events</div>'; return; }
arr.forEach(n=>{
const ts = n.timestamp ? new Date(n.timestamp).toLocaleTimeString() : '';
const e = document.createElement('div');
e.className = 'log-entry info';
e.innerHTML = `<span class="timestamp">[${ts}]</span>
<div><strong>${esc(n.method||'')}</strong> ${esc(n.url||'')}</div>
<div>Status: ${esc(n.status||'')} β OK: ${n.ok ? 'β' : 'β'} β ${esc(n.duration||'')}ms</div>
${n.error ? `<div>Error: ${esc(n.error)}</div>` : ''}`;
c.appendChild(e);
});
c.scrollTop = c.scrollHeight;
})();
// keep the currentTab visible
const pane = document.getElementById(currentTab+'-logs');
if (pane) pane.classList.remove('hidden');
}
function refreshLogs(){
fetch('console_monitor.php', {
method:'POST',
headers:{'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8'},
body:'action=get_logs'
}).then(r=>r.json()).then(data => { logs = data || logs; displayLogs(); }).catch(()=>{});
}
async function copyCurrentLog(){
const pane = document.getElementById(currentTab + '-logs');
if (!pane) return;
const text = pane.innerText || '';
try{ await navigator.clipboard.writeText(text); }
catch(e){ alert('Copy failed (clipboard unavailable).'); }
}
</script>
</body>
</html>