tradingterminal / components /bot-card.js
Zero Hedge
Completely revamp the design
397d289 verified
// Bot Card Web Component - Quantum Theme
class BotCard extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
this._bot = null;
this._logs = [];
this._metrics = new Map();
this._quotes = { bid: "-", ask: "-", spread: "-" };
this._progress = 0;
this._pausedOverride = null;
}
set bot(b) {
this._bot = JSON.parse(JSON.stringify(b));
this.render();
}
get bot() {
return this._bot;
}
setPaused(p) {
if (!this._bot) return;
this._bot.paused = p;
this.render();
}
togglePaused() {
if (!this._bot) return;
this._bot.paused = !this._bot.paused;
this.render();
}
log(msg) {
const line = `[${new Date().toLocaleTimeString()}] ${msg}`;
this._logs.unshift(line);
if (this._logs.length > 4) this._logs.length = 4;
this.updateUI();
}
setMetric(key, val) {
this._metrics.set(key, val);
this.updateUI();
}
setProgress(pct) {
this._progress = pct;
this.updateUI();
}
setQuotes({ bid, ask, spread }) {
this._quotes = {
bid: bid != null ? bid.toFixed(2) : "-",
ask: ask != null ? ask.toFixed(2) : "-",
spread: spread != null ? spread.toFixed(2) : "-",
};
this.updateUI();
}
updateUI() {
if (!this.shadowRoot) return;
const root = this.shadowRoot.querySelector("#root");
if (!root) return;
const pausedBadge = root.querySelector("#pausedBadge");
const pauseBtn = root.querySelector("#pauseBtn");
const quotes = root.querySelector("#quotes");
const metrics = root.querySelector("#metrics");
const progress = root.querySelector("#progress");
const progressFill = root.querySelector("#progressFill");
const logs = root.querySelector("#logs");
// Status badge
if (this._bot?.paused) {
pausedBadge.classList.remove("hidden");
} else {
pausedBadge.classList.add("hidden");
}
// Pause/Resume button
if (pauseBtn) {
pauseBtn.innerHTML = this._bot?.paused
? `<i data-feather="play-circle" class="w-4 h-4"></i> Resume`
: `<i data-feather="pause-circle" class="w-4 h-4"></i> Pause`;
}
// Quotes
quotes.innerHTML = `
<div class="space-y-1">
<div class="flex justify-between text-xs">
<span class="text-gray-400">Bid</span>
<span class="font-mono font-semibold">${this._quotes.bid}</span>
</div>
<div class="flex justify-between text-xs">
<span class="text-gray-400">Ask</span>
<span class="font-mono font-semibold">${this._quotes.ask}</span>
</div>
<div class="flex justify-between text-xs">
<span class="text-gray-400">Spread</span>
<span class="font-mono font-semibold text-accent">${this._quotes.spread}</span>
</div>
</div>
`;
// Metrics
const metricHtml = Array.from(this._metrics.entries())
.map(([k, v]) => `<div class="flex justify-between text-xs"><span class="text-gray-400">${k}</span><span class="font-mono text-indigo-300">${v}</span></div>`)
.join("");
metrics.innerHTML = metricHtml;
// Progress
progress.classList.toggle("hidden", this._bot?.type !== "TWAP");
if (!progress.classList.contains("hidden")) {
progressFill.style.width = `${Math.max(0, Math.min(100, this._progress))}%`;
}
// Logs
logs.innerHTML = this._logs.map((l) => `<div class="text-[11px] text-gray-500 truncate font-mono">${l}</div>`).join("");
// Re-apply feather icons if needed
if (typeof feather !== "undefined") feather.replace();
}
render() {
if (!this._bot) return;
const colorMap = {
green: "bg-gradient-to-br from-emerald-500 to-green-600",
blue: "bg-gradient-to-br from-blue-500 to-cyan-600",
purple: "bg-gradient-to-br from-purple-500 to-pink-600",
amber: "bg-gradient-to-br from-amber-500 to-orange-600",
};
const color = colorMap[this._bot.color] || "bg-gradient-to-br from-indigo-500 to-purple-600";
const paused = this._bot.paused;
this.shadowRoot.innerHTML = `
<style>
:host { display: block; }
.card {
background: rgba(15, 23, 42, 0.6);
backdrop-filter: blur(20px);
border: 1px solid rgba(99, 102, 241, 0.2);
border-radius: 12px;
overflow: hidden;
transition: all 0.3s ease;
}
.card:hover {
border-color: rgba(99, 102, 241, 0.4);
box-shadow: 0 0 20px rgba(99, 102, 241, 0.2);
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 12px;
border-bottom: 1px solid rgba(99, 102, 241, 0.1);
}
.left {
display: flex;
align-items: center;
gap: 8px;
}
.dot {
width: 12px;
height: 12px;
border-radius: 50%;
box-shadow: 0 0 10px rgba(99, 102, 241, 0.5);
}
.body {
padding: 12px;
display: grid;
grid-template-columns: 1fr;
gap: 12px;
}
.section {
padding: 8px;
background: rgba(30, 41, 59, 0.5);
border-radius: 8px;
border: 1px solid rgba(99, 102, 241, 0.1);
}
.meta {
font-size: 11px;
color: #94a3b8;
}
button {
background: rgba(99, 102, 241, 0.1);
border: 1px solid rgba(99, 102, 241, 0.3);
color: #fff;
border-radius: 6px;
padding: 4px 8px;
font-size: 11px;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
gap: 4px;
}
button:hover {
background: rgba(99, 102, 241, 0.2);
border-color: rgba(99, 102, 241, 0.5);
}
#progress {
height: 8px;
background: rgba(30, 41, 59, 0.8);
border-radius: 4px;
overflow: hidden;
}
#progressFill {
height: 100%;
background: linear-gradient(to right, #6366f1, #a855f7);
width: 0%;
transition: width 0.3s ease;
}
#logs {
background: rgba(15, 23, 42, 0.8);
border: 1px solid rgba(99, 102, 241, 0.1);
padding: 8px;
border-radius: 6px;
max-height: 80px;
overflow: hidden;
}
</style>
<div class="card" id="root">
<div class="header">
<div class="left">
<div class="dot ${color}"></div>
<div>
<div class="font-semibold text-sm text-white">${this._bot.name}</div>
<div class="meta">${this._bot.type}${this._bot.symbol}</div>
</div>
</div>
<div class="flex items-center gap-2">
<span id="pausedBadge" class="text-[10px] px-2 py-1 rounded-full bg-red-500/20 text-red-400 border border-red-500/30 ${paused ? "" : "hidden"}">PAUSED</span>
<button id="pauseBtn" title="Pause/Resume">
${paused ? "Resume" : "Pause"}
</button>
</div>
</div>
<div class="body">
<div class="section">
<div class="text-xs font-semibold text-gray-300 mb-2">Quotes</div>
<div id="quotes"></div>
</div>
<div class="section">
<div class="text-xs font-semibold text-gray-300 mb-2">Metrics</div>
<div id="metrics"></div>
</div>
<div id="progress" class="hidden">
<div id="progressFill"></div>
</div>
<div class="section">
<div class="text-xs font-semibold text-gray-300 mb-2">Activity Log</div>
<div id="logs"></div>
</div>
</div>
</div>
`;
this.updateUI();
const pauseBtn = this.shadowRoot.querySelector("#pauseBtn");
pauseBtn.addEventListener("click", () => this.togglePaused());
}
}
customElements.define("bot-card", BotCard);