diff --git a/assets/js/woocow-admin.js b/assets/js/woocow-admin.js
index 53a294c..2ead658 100644
--- a/assets/js/woocow-admin.js
+++ b/assets/js/woocow-admin.js
@@ -952,6 +952,168 @@
$('#wc-log-wrap').html(html);
};
+ // ── Syslog-style renderer (postfix, dovecot, sogo, netfilter, acme) ──
+
+ const priorityBadge = (priority) => {
+ const p = (priority || 'info').toLowerCase();
+ const styles = {
+ debug: 'background:#f0f0f0;color:#888',
+ info: 'background:#e9ecef;color:#495057',
+ notice: 'background:#cce5ff;color:#004085',
+ warn: 'background:#fff3cd;color:#856404',
+ warning: 'background:#fff3cd;color:#856404',
+ err: 'background:#f8d7da;color:#721c24',
+ error: 'background:#f8d7da;color:#721c24',
+ crit: 'background:#721c24;color:#fff',
+ critical: 'background:#721c24;color:#fff',
+ alert: 'background:#721c24;color:#fff',
+ emerg: 'background:#1a1a1a;color:#fff',
+ };
+ const style = styles[p] || 'background:#e9ecef;color:#495057';
+ const label = p.charAt(0).toUpperCase() + p.slice(1);
+ return `${label}`;
+ };
+
+ const renderSyslogTable = (entries, label) => {
+ const hasProgram = entries.some(e => e.program);
+ const hasPriority = entries.some(e => e.priority);
+ let html = `
+ ${esc(label)}
+ ${entries.length} entries
+
+
+
+ | Time |
+ ${hasPriority ? 'Priority | ' : ''}
+ ${hasProgram ? 'Process | ' : ''}
+ Message |
+
`;
+ entries.forEach(e => {
+ const dt = e.time ? new Date(parseInt(e.time) * 1000).toLocaleString() : '—';
+ html += `
+ | ${esc(dt)} |
+ ${hasPriority ? `${priorityBadge(e.priority)} | ` : ''}
+ ${hasProgram ? `${esc(e.program || '—')} | ` : ''}
+ ${esc(e.message || '—')} |
+
`;
+ });
+ html += '
';
+ $('#wc-log-wrap').html(html);
+ };
+
+ // ── API access log renderer ───────────────────────────────────────────
+
+ const renderApiLog = (entries) => {
+ const methodBadge = (method) => {
+ const colors = { GET:'#27ae60', POST:'#2271b1', PUT:'#e67e22', DELETE:'#c0392b', PATCH:'#8e44ad' };
+ const bg = colors[(method || '').toUpperCase()] || '#555';
+ return `${esc(method || '?')}`;
+ };
+ let html = `
+ API Access Log
+ ${entries.length} requests
+
+
+
+ | Time |
+ Method |
+ Endpoint |
+ Remote IP |
+ Data |
+
`;
+ entries.forEach(e => {
+ const dt = e.time ? new Date(e.time * 1000).toLocaleString() : '—';
+ html += `
+ | ${esc(dt)} |
+ ${methodBadge(e.method)} |
+ ${esc(e.uri || '—')} |
+ ${esc(e.remote || '—')} |
+ ${esc(e.data || '—')} |
+
`;
+ });
+ html += '
';
+ $('#wc-log-wrap').html(html);
+ };
+
+ // ── Autodiscover log renderer ─────────────────────────────────────────
+
+ const renderAutodiscoverLog = (entries) => {
+ const svcBadge = (svc) => {
+ const map = {
+ activesync: ['woocow-badge-blue', 'ActiveSync'],
+ caldav: ['woocow-badge-green', 'CalDAV'],
+ carddav: ['woocow-badge-green', 'CardDAV'],
+ imap: ['woocow-badge-grey', 'IMAP'],
+ smtp: ['woocow-badge-grey', 'SMTP'],
+ };
+ const [cls, label] = map[(svc || '').toLowerCase()] || ['woocow-badge-grey', esc(svc || '—')];
+ return `${label}`;
+ };
+ let html = `
+ Autodiscover Log
+ ${entries.length} requests
+
+
+
+ | Time |
+ User |
+ Service |
+ User Agent |
+
`;
+ entries.forEach(e => {
+ const dt = e.time ? new Date(e.time * 1000).toLocaleString() : '—';
+ html += `
+ | ${esc(dt)} |
+ ${esc(e.user || '—')} |
+ ${svcBadge(e.service)} |
+ ${esc(e.ua || '—')} |
+
`;
+ });
+ html += '
';
+ $('#wc-log-wrap').html(html);
+ };
+
+ // ── Watchdog log renderer ─────────────────────────────────────────────
+
+ const renderWatchdogLog = (entries) => {
+ const healthBadge = (lvl) => {
+ const n = parseInt(lvl) || 0;
+ if (n >= 100) return 'Healthy';
+ if (n >= 50) return 'Degraded';
+ return 'Critical';
+ };
+ let html = `
+ Watchdog Log
+ ${entries.length} entries
+
+
+
+ | Time |
+ Service |
+ Status |
+ Processes |
+ Change |
+
`;
+ entries.forEach(e => {
+ const dt = e.time ? new Date(parseInt(e.time) * 1000).toLocaleString() : '—';
+ const diff = parseInt(e.hpdiff) || 0;
+ const diffHtml = diff > 0
+ ? `+${diff}`
+ : diff < 0
+ ? `${diff}`
+ : `±0`;
+ html += `
+ | ${esc(dt)} |
+ ${esc(e.service || '—')} |
+ ${healthBadge(e.lvl)} |
+ ${esc(e.hpnow || '0')}/${esc(e.hptotal || '0')} |
+ ${diffHtml} |
+
`;
+ });
+ html += '
';
+ $('#wc-log-wrap').html(html);
+ };
+
// ── Log load ──────────────────────────────────────────────────────────
$('#wc-log-load').on('click', () => {
@@ -969,24 +1131,14 @@
$('#wc-log-wrap').html('No log entries.
');
return;
}
-
+ const typeLabel = $('#wc-log-type option:selected').text();
if (type === 'rspamd-history') { renderRspamdLog(entries); return; }
if (type === 'ratelimited') { renderRatelimitLog(entries); return; }
-
- // Plain text log
- const typeLabel = $('#wc-log-type option:selected').text();
- const text = entries.map(e => {
- if (typeof e === 'string') return e;
- if (e.time && e.message) return `[${e.time}] ${e.message}`;
- return JSON.stringify(e);
- }).join('\n');
- $('#wc-log-wrap').html(`
-
- ${esc(typeLabel)} log
- ${entries.length} entries
-
- ${esc(text)}
- `);
+ if (type === 'api') { renderApiLog(entries); return; }
+ if (type === 'autodiscover') { renderAutodiscoverLog(entries); return; }
+ if (type === 'watchdog') { renderWatchdogLog(entries); return; }
+ // postfix, dovecot, sogo, netfilter, acme → syslog table
+ renderSyslogTable(entries, typeLabel + ' Log');
});
});
}