Edit Code · SiloWallet
Back to CMS
Open Page
Source Code Viewer
20236 bytes loaded
<?php require_once __DIR__ . '/config.php'; $profile = sw_account_profile(); $walletUid = sw_wallet_uid(); $logo = "/silocloud/apps/silowallet/assets/wallet1.png"; $platformQuery = [ 'source' => 'silowallet', 'user_uid' => $profile['uid'], 'display_name' => $profile['name'], ]; if ($profile['email'] !== '') $platformQuery['email'] = $profile['email']; $platformLinks = [ ['label'=>'SiloDrive','url'=>'https://silocloud.dev/silocloud/apps/drive/index.php?' . http_build_query($platformQuery),'icon'=>'/silocloud/apps/silowallet/assets/platforms/silodrive-logo.png'], ['label'=>'SiloArray','url'=>'https://siloarray.com/runtime-appliance/launch-from-drive.php?user_uid=' . rawurlencode($profile['uid']),'icon'=>'/silocloud/apps/silowallet/assets/platforms/siloarray-logo.png'], ['label'=>'SiloTalk','url'=>'https://siloarray.com/runtime-appliance/launch-communications.php?app=silotalk','icon'=>'/silocloud/apps/silowallet/assets/platforms/silotalk-logo.png'], ['label'=>'SiloStreamDeck','url'=>'https://silostreamdeck.com/products.php?' . http_build_query($platformQuery),'icon'=>'/silocloud/apps/silowallet/assets/platforms/streamdeck-logo.png'], ]; ?> <!doctype html> <html lang="en"> <head><link rel="icon" type="image/png" href="/silocloud/assets/images/cloud.png"> <meta charset="utf-8"> <title>SiloWallet Finance</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <style> :root{ --red:#b00020; --orange:#f97316; --blue:#2563eb; --bg:#f4f6fb; --card:#ffffff; --line:#dde3ee; --text:#101828; --muted:#667085; --green:#15803d; --danger:#dc2626; --shadow:0 16px 38px rgba(15,23,42,.08); --r:10px; } *{box-sizing:border-box} html,body{height:100%;margin:0;overflow:hidden;font-family:Arial,Helvetica,sans-serif;color:var(--text);background: radial-gradient(circle at top left, rgba(176,0,32,.13), transparent 28%), radial-gradient(circle at top right, rgba(37,99,235,.13), transparent 28%), linear-gradient(135deg,#fff,#eef2f8)} .app{height:100vh;display:grid;grid-template-columns:238px minmax(0,1fr)} .sidebar{background:rgba(255,255,255,.94);border-right:1px solid var(--line);padding:18px 14px;position:relative} .brand{display:flex;gap:12px;align-items:center;margin-bottom:22px} .brand img{width:42px;height:42px;border-radius:8px;border:1px solid var(--line);background:white} .brand b{font-size:21px}.brand small{font-size:12px;color:var(--muted)} .nav button{ width:100%; height:46px; border:1px solid transparent; background:transparent; display:flex; align-items:center; gap:12px; padding:0 14px; border-radius:8px; margin-bottom:8px; font-weight:800; font-size:14px; color:#344054; cursor:pointer; transition:.18s ease; } .nav button svg{ width:18px; height:18px; flex:none; opacity:.92; } .nav button.active, .nav button:hover{ background: linear-gradient( 90deg, rgba(176,0,32,.10), rgba(249,115,22,.10), rgba(37,99,235,.08) ); border-color:rgba(176,0,32,.14); color:var(--red); box-shadow: 0 8px 20px rgba(176,0,32,.08); transform:translateY(-1px); } .nav button.active,.nav button:hover{background:linear-gradient(90deg,rgba(176,0,32,.10),rgba(249,115,22,.10));color:var(--red)} .advanced{position:absolute;bottom:18px;left:14px;right:14px;font-size:13px;color:#475467} .main{display:grid;grid-template-rows:72px minmax(0,1fr);overflow:hidden} .top{background:white;border-bottom:1px solid var(--line);display:flex;align-items:center;gap:18px;padding:0 24px} .top h1{margin:0;font-size:25px} .search{flex:1;max-width:620px}.search input{width:100%;height:40px;border:1px solid var(--line);border-radius:8px;padding:0 14px} .topRight{margin-left:auto;display:flex;align-items:center;gap:10px}.platform-switcher{display:flex;align-items:center;gap:8px;padding-right:12px;border-right:1px solid var(--line)}.platform-link{width:38px;height:38px;border:1px solid var(--line);background:#fff;border-radius:10px;display:grid;place-items:center;text-decoration:none;box-shadow:0 6px 16px rgba(15,23,42,.06)}.platform-link:hover{border-color:var(--blue);box-shadow:0 0 0 3px #eef4ff}.platform-link img{max-width:28px;max-height:28px;object-fit:contain;display:block} .status{font-size:13px;font-weight:900;color:var(--green)} .profile{display:flex;align-items:center;gap:10px;padding-left:12px;border-left:1px solid var(--line);text-align:left;font-size:13px}.profileAvatar{width:38px;height:38px;border-radius:50%;background:#111827;color:#fff;display:grid;place-items:center;font-weight:900;overflow:hidden}.profileAvatar img{width:100%;height:100%;object-fit:cover}.profile small{color:#475467} .workspace{height:100%;min-height:0;display:grid;grid-template-columns:minmax(0,1fr) 370px;gap:14px;padding:14px;overflow:hidden} .left{display:grid;grid-template-rows:158px 280px minmax(0,1fr);gap:14px;overflow:hidden} .summary{display:grid;grid-template-columns:1.12fr .94fr .94fr;gap:14px} .card{background:rgba(255,255,255,.95);border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow);overflow:hidden} .balance-card{background:linear-gradient(135deg,var(--red),var(--orange) 55%,var(--blue));color:white;padding:20px;border-radius:var(--r);box-shadow:var(--shadow);position:relative;overflow:hidden} .balance-card:after{content:"";position:absolute;right:-60px;top:-60px;width:170px;height:170px;border-radius:50%;background:rgba(255,255,255,.16)} .balance-card img{position:absolute;right:20px;top:18px;width:46px;height:46px;border-radius:8px;background:white;padding:6px} .balance-card small{color:rgba(255,255,255,.82)} .balance-card strong{display:block;font-size:31px;margin:15px 0 30px} .mini{padding:18px}.mini small{color:var(--muted)}.mini strong{display:block;font-size:24px;margin:11px 0} .metric{display:flex;justify-content:space-between;border-top:1px solid #edf1f6;padding:8px 0;font-size:13px} .good{color:var(--green);font-weight:900}.bad{color:var(--danger);font-weight:900}.warn{color:var(--orange);font-weight:900} .action-panel{padding:18px} .tabs{display:flex;gap:8px;margin-bottom:14px} .tabs button{border:1px solid var(--line);background:#f6f8fb;border-radius:8px;height:36px;padding:0 14px;font-weight:900;cursor:pointer} .tabs button.active{background:linear-gradient(90deg,var(--red),var(--orange));color:white;border-color:transparent} .form-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:10px} .field label{display:block;font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.08em;margin-bottom:5px} .field input,.field select{width:100%;height:38px;border:1px solid var(--line);border-radius:8px;padding:0 10px;background:white} .submit{height:38px;border:0;border-radius:8px;background:var(--blue);color:white;font-weight:900;align-self:end;cursor:pointer} .result{margin-top:12px;padding:10px;border-radius:8px;background:#f8fafc;border:1px solid var(--line);font-size:13px;color:#344054;min-height:38px} .table{display:grid;grid-template-rows:52px minmax(0,1fr)} .table-head,.row{display:grid;grid-template-columns:112px 1fr 110px 110px 130px;align-items:center;padding:0 18px} .table-head{border-bottom:1px solid var(--line);font-size:12px;font-weight:900;color:#475467;text-transform:uppercase;letter-spacing:.07em} .rows{overflow:auto} .row{min-height:54px;border-bottom:1px solid #edf1f6;font-size:13px} .row span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis} .amount{font-weight:900}.plus{color:var(--green)}.minus{color:var(--danger)} .right{display:grid;grid-template-rows:220px 1fr 220px;gap:14px;overflow:hidden} .api,.notify,.reserve{padding:18px} .api h2,.notify h2,.reserve h2{margin:0 0 14px;font-size:18px} .api-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px} .api-tile{border:1px solid var(--line);border-radius:8px;padding:12px;background:#f8fafc} .api-tile b{display:block;font-size:20px}.api-tile small{color:var(--muted)} .notice{display:grid;grid-template-columns:34px 1fr auto;gap:10px;align-items:center;min-height:56px;border-bottom:1px solid #edf1f6} .ico{width:30px;height:30px;border-radius:8px;background:linear-gradient(135deg,var(--red),var(--orange));color:white;display:flex;align-items:center;justify-content:center;font-weight:900} .notice b{font-size:13px}.notice small{color:var(--muted)} .plan{margin-bottom:16px}.plan-line{display:flex;justify-content:space-between;font-size:13px;margin-bottom:8px}.track{height:8px;background:#e5e7eb;border-radius:99px;overflow:hidden}.fill{height:100%;background:linear-gradient(90deg,var(--red),var(--orange),var(--blue))} @media(max-width:1300px){.workspace{grid-template-columns:1fr}.right{display:none}.summary{grid-template-columns:1fr}.left{grid-template-rows:auto auto minmax(0,1fr)}} </style> </head> <body> <div class="app"> <aside class="sidebar"> <div class="brand"><img src="<?= htmlspecialchars($logo) ?>"><div><b>SiloWallet</b><br><small>Finance Console</small></div></div> <nav class="nav"> <button class="active" data-tab="transfer">↗ Transfer</button> <button data-tab="buy">+ Buy</button> <button data-tab="sell">- Sell</button> <button data-tab="convert">⇄ Convert</button> <button onclick="refreshAll()">⟳ Refresh</button> <button onclick="window.open('./api/status.php','_blank')">● API Status</button> </nav> <div class="advanced">Bridge-only production controls</div> </aside> <main class="main"> <header class="top"> <h1>Silo Finance</h1> <div class="search"><input id="search" placeholder="Search transactions, wallet, memo..."></div> <div class="topRight"> <nav class="platform-switcher" aria-label="Core Silo platforms"> <?php foreach ($platformLinks as $link): ?> <a class="platform-link" href="<?= htmlspecialchars($link['url'], ENT_QUOTES, 'UTF-8') ?>" title="Open <?= htmlspecialchars($link['label'], ENT_QUOTES, 'UTF-8') ?>" aria-label="Open <?= htmlspecialchars($link['label'], ENT_QUOTES, 'UTF-8') ?>"> <img src="<?= htmlspecialchars($link['icon'], ENT_QUOTES, 'UTF-8') ?>" alt=""> </a> <?php endforeach; ?> </nav> <div class="status" id="status">● Loading API</div> <div class="profile"> <div class="profileAvatar"><?= $profile['avatar'] ? '<img src="' . htmlspecialchars($profile['avatar'], ENT_QUOTES, 'UTF-8') . '" alt="' . htmlspecialchars($profile['name'], ENT_QUOTES, 'UTF-8') . '">' : htmlspecialchars($profile['initials'], ENT_QUOTES, 'UTF-8') ?></div> <div><b><?= htmlspecialchars($profile['name'], ENT_QUOTES, 'UTF-8') ?></b><br><small><?= htmlspecialchars(($profile['email'] ?: $profile['uid']) . ' · ' . $profile['role'], ENT_QUOTES, 'UTF-8') ?><br>Wallet: <span id="walletLabel"><?= htmlspecialchars($walletUid) ?></span></small></div> </div> </div> </header> <section class="workspace"> <div class="left"> <section class="summary"> <div class="balance-card"> <img src="<?= htmlspecialchars($logo) ?>"> <small>Available Balance</small> <strong id="balance">0.00 SILO</strong> <small id="bridgeLine">Bridge checking...</small> </div> <div class="card mini"> <small>Asset Position</small> <strong id="assetPosition">0.00 SILO</strong> <div class="metric"><span>Coin Reserve</span><b id="coinReserve">$0.00</b></div> <div class="metric"><span>Pending</span><b id="pending">0.00</b></div> <div class="metric"><span>Ledger</span><b class="good" id="ledger">checking</b></div> </div> <div class="card mini"> <small>Activity</small> <strong id="txCount">0 tx</strong> <div class="metric"><span>Last Action</span><b id="lastAction">none</b></div> <div class="metric"><span>API</span><b class="good" id="apiState">checking</b></div> <div class="metric"><span>Mode</span><b class="warn" id="mode">loading</b></div> </div> </section> <section class="card action-panel"> <div class="tabs"> <button class="active" data-tab="transfer">Transfer</button> <button data-tab="buy">Buy</button> <button data-tab="sell">Sell</button> <button data-tab="convert">Convert</button> </div> <div class="form-grid"> <div class="field" id="toWrap"> <label>Recipient Wallet</label> <input id="to_wallet_uid" placeholder="recipient wallet_uid"> </div> <div class="field"> <label>Amount</label> <input id="amount" type="number" min="0" step="0.01" placeholder="0.00"> </div> <div class="field"> <label>Asset</label> <select id="asset_code"> <option>SILO</option> <option>SILOCOIN</option> <option>SILOTOKEN</option> <option>USD</option> </select> </div> <button class="submit" id="submitBtn">Submit Transfer</button> </div> <div class="form-grid" style="margin-top:10px"> <div class="field"> <label>Convert To</label> <select id="to_asset_code"> <option>SILOTOKEN</option> <option>SILOCOIN</option> <option>USD</option> <option>SILO</option> </select> </div> <div class="field" style="grid-column:span 2"> <label>Memo</label> <input id="memo" placeholder="ENUM / ledger memo"> </div> </div> <div class="result" id="actionResult">Select an action and submit. All live movement routes through the API bridge.</div> </section> <section class="card table"> <div class="table-head"><span>ID</span><span>Memo</span><span>Type</span><span>Status</span><span>Amount</span></div> <div class="rows" id="txRows"></div> </section> </div> <aside class="right"> <section class="card api"> <h2>Wallet API</h2> <div class="api-grid"> <div class="api-tile"><b id="apiDb">--</b><small>DB</small></div> <div class="api-tile"><b id="apiBridge">--</b><small>Bridge</small></div> <div class="api-tile"><b id="apiRoute">Live</b><small>Route</small></div> <div class="api-tile"><b id="apiTime">--</b><small>Updated</small></div> </div> </section> <section class="card notify"> <h2>Live Console</h2> <div id="notices"></div> </section> <section class="card reserve"> <h2>Reserve Movement</h2> <div class="plan"><div class="plan-line"><b>Silo Red</b><span>Wallet Core</span></div><div class="track"><div class="fill" style="width:88%"></div></div></div> <div class="plan"><div class="plan-line"><b>Silo Orange</b><span>Bridge Flow</span></div><div class="track"><div class="fill" style="width:64%"></div></div></div> <div class="plan"><div class="plan-line"><b>Silo Blue</b><span>Ledger Sync</span></div><div class="track"><div class="fill" style="width:72%"></div></div></div> </section> </aside> </section> </main> </div> <script> const walletUid = <?= json_encode($walletUid) ?>; let activeAction = 'transfer'; let allTx = []; const $ = id => document.getElementById(id); const money = n => Number(n || 0).toLocaleString(undefined,{minimumFractionDigits:2,maximumFractionDigits:2}); const notice = (title, sub='') => { const row = document.createElement('div'); row.className = 'notice'; row.innerHTML = `<div class="ico">●</div><div><b>${title}</b><br><small>${sub}</small></div><small>now</small>`; $('notices').prepend(row); }; function setAction(action){ activeAction = action; document.querySelectorAll('[data-tab]').forEach(b => b.classList.toggle('active', b.dataset.tab === action)); $('toWrap').style.display = action === 'transfer' ? 'block' : 'none'; $('submitBtn').textContent = 'Submit ' + action.charAt(0).toUpperCase() + action.slice(1); $('actionResult').textContent = `${action.toUpperCase()} ready.`; } document.querySelectorAll('[data-tab]').forEach(b => b.addEventListener('click',()=>setAction(b.dataset.tab))); async function refreshStatus(){ const j = await fetch('./api/status.php').then(r=>r.json()); $('apiDb').textContent = j.db_connected ? 'On' : 'Off'; $('apiBridge').textContent = 'Bridge'; $('apiTime').textContent = new Date().toLocaleTimeString(); $('apiState').textContent = j.ok ? 'online' : 'error'; $('status').textContent = j.ok ? '● Wallet API Online' : '● API Error'; } async function refreshBalance(){ const b = await fetch('./api/balance.php?wallet_uid=' + encodeURIComponent(walletUid)).then(r=>r.json()); $('balance').textContent = money(b.available_balance) + ' ' + (b.asset_code || 'SILO'); $('assetPosition').textContent = money(b.available_balance) + ' ' + (b.asset_code || 'SILO'); $('coinReserve').textContent = '$' + money(b.coin_reserve); $('pending').textContent = money(b.pending_balance || 0); $('ledger').textContent = b.ledger_sync || 'api'; $('mode').textContent = b.mode || 'api'; $('bridgeLine').textContent = 'Bridge: ' + (b.bridge_status || 'api'); } function renderTx(rows){ const q = $('search').value.toLowerCase().trim(); const filtered = rows.filter(tx => JSON.stringify(tx).toLowerCase().includes(q)); $('txCount').textContent = filtered.length + ' tx'; $('txRows').innerHTML = filtered.length ? filtered.map(tx=>{ const amt = Number(tx.amount || 0); const status = tx.status || 'pending'; return `<div class="row"> <span>${tx.tx_uid || 'TX'}</span> <span>${tx.memo || tx.name || 'Wallet action'}</span> <span>${tx.tx_type || 'transaction'}</span> <span class="${status === 'success' ? 'good' : 'warn'}">${status}</span> <span class="amount ${amt >= 0 ? 'plus':'minus'}">${amt >= 0 ? '+' : ''}${money(amt)} ${tx.asset_code || 'SILO'}</span> </div>`; }).join('') : `<div class="row"><span>--</span><span>No live transactions returned by API.</span><span>--</span><span>--</span><span>--</span></div>`; } async function refreshHistory(){ const h = await fetch('./api/history.php?wallet_uid=' + encodeURIComponent(walletUid)).then(r=>r.json()); allTx = Array.isArray(h.transactions) ? h.transactions : []; renderTx(allTx); } async function submitAction(){ const body = { wallet_uid: walletUid, to_wallet_uid: $('to_wallet_uid').value.trim(), amount: Number($('amount').value), asset_code: $('asset_code').value, to_asset_code: $('to_asset_code').value, memo: $('memo').value.trim() || activeAction }; if (!body.amount || body.amount <= 0) { $('actionResult').textContent = 'Enter a valid amount.'; return; } if (activeAction === 'transfer' && !body.to_wallet_uid) { $('actionResult').textContent = 'Enter recipient wallet_uid.'; return; } $('actionResult').textContent = 'Submitting ' + activeAction + '...'; const r = await fetch('./api/' + activeAction + '.php', { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(body) }); const j = await r.json(); $('actionResult').textContent = j.message || j.error || 'Action completed.'; $('lastAction').textContent = activeAction; notice(activeAction.toUpperCase(), j.message || j.error || 'API returned response'); if (j.ok) { $('amount').value = ''; $('memo').value = ''; if (activeAction === 'transfer') $('to_wallet_uid').value = ''; } await refreshAll(); } async function refreshAll(){ try{ await refreshStatus(); await refreshBalance(); await refreshHistory(); }catch(e){ $('status').textContent = '● Wallet API Error'; notice('API Error', e.message || String(e)); } } $('submitBtn').addEventListener('click', submitAction); $('search').addEventListener('input',()=>renderTx(allTx)); setAction('transfer'); refreshAll(); setInterval(refreshAll, 30000); </script> </body> </html>