ACIL FM
Dark
Refresh
Current DIR:
/home/benbot/bot/widgets
/
home
benbot
bot
widgets
Upload
Zip Selected
Delete Selected
Pilih semua
Nama
Ukuran
Permission
Aksi
pnl-card.js
4.1 MB
chmod
View
DL
Edit
Rename
Delete
Edit file: /home/benbot/bot/widgets/pnl-card.js
/* [MOD] OpsDeck Widget: PnL Card (ES Module) - Standalone UI widget for realized/unrealized PnL - No external deps. Works with SSE (/events) or manual push() - Will be wired by opsdeck-basic.html in the next step */ export default function createPnlCard(rootEl, opts = {}) { const cfg = { title: opts.title || "PnL", currency: opts.currency || "USDT", precision: Number.isInteger(opts.precision) ? opts.precision : 2, }; if (!rootEl) throw new Error("pnl-card: root element is required"); /* [MOD] State */ const state = { realized: 0, unrealized: 0, fees: 0, pnlPct: 0, lastTs: 0, }; /* [MOD] UI */ rootEl.classList.add("card"); rootEl.innerHTML = ` <h3>${cfg.title}</h3> <div style="display:flex;gap:16px;flex-wrap:wrap"> <div> <div class="muted">Realized</div> <div id="pc-realized" style="font-weight:700;font-size:18px">0.00 ${cfg.currency}</div> </div> <div> <div class="muted">Unrealized</div> <div id="pc-unrealized" style="font-weight:700;font-size:18px">0.00 ${cfg.currency}</div> </div> <div> <div class="muted">Fees</div> <div id="pc-fees" style="font-weight:700;font-size:18px">0.00 ${cfg.currency}</div> </div> <div> <div class="muted">PnL %</div> <div id="pc-pct" style="font-weight:700;font-size:18px">0.00 %</div> </div> </div> <div class="muted" id="pc-note" style="margin-top:8px">waiting for events…</div> `; const $ = (sel) => rootEl.querySelector(sel); const elRealized = $("#pc-realized"); const elUnrealized = $("#pc-unrealized"); const elFees = $("#pc-fees"); const elPct = $("#pc-pct"); const elNote = $("#pc-note"); /* [MOD] Helpers */ const fmt = (n, p = cfg.precision) => (Number(n) || 0).toLocaleString(undefined, { minimumFractionDigits: p, maximumFractionDigits: p, }); function repaint() { elRealized.textContent = `${fmt(state.realized)} ${cfg.currency}`; elUnrealized.textContent = `${fmt(state.unrealized)} ${cfg.currency}`; elFees.textContent = `${fmt(state.fees)} ${cfg.currency}`; elPct.textContent = `${fmt(state.pnlPct, 2)} %`; // color cue const pct = Number(state.pnlPct) || 0; elPct.style.color = pct > 0 ? "#2ecc71" : pct < 0 ? "#e74c3c" : ""; const now = new Date(state.lastTs || Date.now()).toLocaleTimeString(); elNote.textContent = `updated ${now}`; } /* [MOD] Public API */ function pushPnL({ realized, unrealized, fees, pnlPct, ts }) { if (Number.isFinite(realized)) state.realized = realized; if (Number.isFinite(unrealized)) state.unrealized = unrealized; if (Number.isFinite(fees)) state.fees = fees; if (Number.isFinite(pnlPct)) state.pnlPct = pnlPct; state.lastTs = ts || Date.now(); repaint(); } function attachEventSource(es) { if (!es) return; // Generic SSE handlers: // - event: log {level,message,meta:{pnl,pnlPct,realized,unrealized,fees}} // - event: pnl {realized,unrealized,fees,pnlPct,ts} es.addEventListener("pnl", (ev) => { try { const data = JSON.parse(ev.data || "{}"); pushPnL(data); } catch {} }); es.addEventListener("log", (ev) => { try { const msg = JSON.parse(ev.data || "{}"); const m = msg?.meta || msg?.data || {}; if ( m && (Number.isFinite(m.pnl) || Number.isFinite(m.pnlPct) || Number.isFinite(m.realized) || Number.isFinite(m.unrealized) || Number.isFinite(m.fees)) ) { pushPnL({ realized: m.realized, unrealized: m.unrealized, fees: m.fees, pnlPct: m.pnlPct ?? m.pnl, ts: msg.t, }); } } catch {} }); } // initial paint repaint(); return { push: pushPnL, attachEventSource, getState: () => ({ ...state }), setTitle: (t) => (rootEl.querySelector("h3").textContent = t || cfg.title), }; }
Simpan
Batal
Isi Zip:
Unzip
Create
Buat Folder
Buat File
Terminal / Execute
Run
Chmod Bulk
All File
All Folder
All File dan Folder
Apply