psiphon-pilot / components /log-console.js
hosseinhimself's picture
the vpn is run using this script in terminal ubuntu:
a8d9584 verified
class LogConsole extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.entries = [];
this.maxEntries = 100;
}
connectedCallback() {
this.render();
}
render() {
this.shadowRoot.innerHTML = `
<style>
:host {
display: flex;
flex-direction: column;
flex: 1;
overflow: hidden;
background: #0f172a;
font-family: 'JetBrains Mono', 'Fira Code', monospace;
font-size: 0.875rem;
}
.console-content {
flex: 1;
overflow-y: auto;
padding: 16px;
display: flex;
flex-direction: column;
gap: 4px;
}
.entry {
display: flex;
gap: 12px;
line-height: 1.5;
animation: fadeIn 0.2s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateX(-10px); }
to { opacity: 1; transform: translateX(0); }
}
.timestamp {
color: #6b7280;
user-select: none;
white-space: nowrap;
}
.message {
color: #e5e7eb;
word-break: break-all;
}
.type-system { color: #60a5fa; }
.type-info { color: #34d399; }
.type-success { color: #10b981; }
.type-warning { color: #fbbf24; }
.type-error { color: #ef4444; }
.type-process { color: #a78bfa; }
.type-network { color: #22d3ee; }
.type-debug { color: #9ca3af; font-style: italic; }
.empty-state {
color: #4b5563;
text-align: center;
padding: 40px;
font-style: italic;
}
.scroll-indicator {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 40px;
background: linear-gradient(transparent, #0f172a);
pointer-events: none;
}
</style>
<div class="console-content" id="consoleContent">
<div class="empty-state">Waiting for process output...</div>
</div>
`;
this.contentEl = this.shadowRoot.getElementById('consoleContent');
}
addEntry(text, type = 'info') {
// Remove empty state if present
if (this.entries.length === 0) {
this.contentEl.innerHTML = '';
}
const entry = document.createElement('div');
entry.className = 'entry';
const typeClass = `type-${type}`;
// Parse timestamp if included in text
let displayText = text;
let timestamp = '';
if (text.startsWith('[') && text.includes(']')) {
const match = text.match(/\[(.*?)\](.*)/);
if (match) {
timestamp = `[${match[1]}]`;
displayText = match[2].trim();
}
}
entry.innerHTML = `
<span class="timestamp ${typeClass}">${timestamp || new Date().toLocaleTimeString('en-US', { hour12: false })}</span>
<span class="message ${typeClass}">${this.escapeHtml(displayText)}</span>
`;
this.contentEl.appendChild(entry);
this.entries.push(entry);
// Limit entries
if (this.entries.length > this.maxEntries) {
this.entries.shift().remove();
}
// Auto-scroll to bottom
this.contentEl.scrollTop = this.contentEl.scrollHeight;
}
clear() {
this.entries = [];
this.contentEl.innerHTML = '<div class="empty-state">Console cleared. Waiting for process output...</div>';
}
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
}
customElements.define('log-console', LogConsole);