ipfs storage for images and other nontext items. for use with etica - runs on etica network and currencys https://collect.etica-stats.org
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

212 lines
8.9 KiB

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>IPFS.etica-stats.org</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="./etica.png" type="image/png" />
<meta name="theme-color" content="#0b0f14" />
<script>
(() => {
try {
const saved = localStorage.getItem('theme');
const prefersDark = matchMedia('(prefers-color-scheme: dark)').matches;
document.documentElement.dataset.theme =
(saved === 'light' || saved === 'dark') ? saved : (prefersDark ? 'dark' : 'light');
} catch {}
})();
</script>
<style>
:root{
--bg:#ffffff;--card:#f8fafc;--muted:#475569;--text:#0f172a;--accent:#2563eb;--warn:#b45309;--err:#dc2626;
--border:#e2e8f0;--btn:#f1f5f9;--btnh:#e2e8f0; color-scheme:light;
--mono: ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;
}
:root[data-theme="dark"]{
--bg:#0b0f14;--card:#101721;--muted:#7b8a9a;--text:#e6edf3;--accent:#6bc46d;--warn:#f0b429;--err:#ff6b6b;
--border:#1f2a37;--btn:#0f1620;--btnh:#141e2a; color-scheme:dark;
}
*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--text);font:16px/1.45 system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,"Helvetica Neue",Arial}
.topbar{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--border);position:sticky;top:0;z-index:10;background:linear-gradient(180deg,rgba(0,0,0,.03),transparent),var(--bg)}
.brand{display:flex;align-items:center;gap:10px}
.brand img{width:22px;height:22px;border-radius:4px}
.brand-title{font-weight:800}
.tb-right{display:flex;align-items:center;gap:10px}
.pill{display:inline-block;padding:4px 10px;border:1px solid var(--border);border-radius:999px;font-size:13px;color:var(--muted);background:var(--btn)}
.pill.wide{max-width:540px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
button{background:var(--btn);border:1px solid var(--border);color:var(--text);padding:8px 12px;border-radius:10px;cursor:pointer;font-weight:600}
button:hover{background:var(--btnh)}
.primary{background:var(--accent);border-color:transparent;color:#fff}
.primary:hover{filter:brightness(0.95)}
main{padding:18px;max-width:1200px;margin:0 auto}
.grid{display:grid;gap:16px}
.cols-2{grid-template-columns: repeat(2,minmax(0,1fr))}
.cols-3{grid-template-columns: repeat(3,minmax(0,1fr))}
.card{background:var(--card);border:1px solid var(--border);border-radius:14px;padding:16px}
.card h2{margin:0 0 10px 0;font-size:16px}
.kv{display:grid;grid-template-columns:180px 1fr;gap:6px;font-size:14px}
.kv div{padding:4px 0;border-bottom:1px dashed rgba(127,127,127,.22)}
.muted{color:var(--muted)} .small{font-size:12px} .mono{font-family:var(--mono)}
.row{display:flex;gap:10px;align-items:center;flex-wrap:wrap}
input[type="text"], select{width:100%;padding:10px 12px;border-radius:10px;border:1px solid var(--border);background:var(--bg);color:var(--text);outline:none}
input::file-selector-button{border:1px solid var(--border);background:var(--btn);color:var(--text);padding:8px 10px;border-radius:8px;margin-right:10px}
table{width:100%;border-collapse:collapse;font-size:14px}
th,td{padding:8px;border-bottom:1px solid var(--border);text-align:left}
tbody tr:hover{background:rgba(127,127,127,.08)}
.tag{border:1px solid var(--border);padding:2px 8px;border-radius:999px;font-size:12px}
.log{white-space:pre-wrap;background:var(--bg);border:1px solid var(--border);border-radius:10px;padding:10px;max-height:220px;overflow:auto}
.hidden{display:none}
.hr{height:1px;background:var(--border);margin:10px 0}
</style>
</head>
<body>
<div class="topbar">
<div class="brand">
<img src="./etica.png" alt="Etica" />
<div class="brand-title">IPFS.etica-stats.org</div>
</div>
<div class="tb-right">
<button id="btnTheme" title="Toggle light/dark">🌓</button>
<span id="tbAcct" class="pill wide">wallet: disconnected</span>
<button id="btnConnectTop">Connect MetaMask</button>
</div>
</div>
<main>
<!-- Row 1: Uploaded Files (full width) -->
<div class="grid">
<div class="card">
<h2>Uploaded Files</h2>
<div class="row">
<button id="btnRefreshFiles">Refresh</button>
<span id="filesStatus" class="muted small"></span>
</div>
<div id="filesEmpty" class="muted small" style="margin-top:8px">No files yet.</div>
<div id="filesTableWrap" class="hidden" style="margin-top:8px;overflow:auto">
<table>
<thead>
<tr>
<th>File</th>
<th>Size</th>
<th class="mono">CID</th>
<th class="mono">Path</th>
<th>Label</th>
<th>Status</th>
<th>When</th>
</tr>
</thead>
<tbody id="filesTbody"></tbody>
</table>
</div>
</div>
</div>
<!-- Row 2: Upload & Pay -->
<div class="grid" style="grid-template-columns: minmax(0,1fr); margin-top:16px">
<div class="card">
<h2>Upload & Pay</h2>
<div style="display:grid;gap:10px">
<div>
<label>Choose file</label><br />
<input type="file" id="file" />
</div>
<div>
<label>Path inside your files (optional)</label><br />
<input type="text" id="path" placeholder="e.g. videos/2025/august/" />
</div>
<div>
<label>CID label (optional, human-readable)</label><br />
<input type="text" id="label" placeholder="e.g. my-summer-clip" />
</div>
<div>
<label>Payment currency</label><br />
<select id="currencySel">
<option value="EGAZ">EGAZ (native)</option>
<option value="ETI">ETI (token)</option>
</select>
</div>
<div class="row">
<button id="btnQuote" class="primary">Get Quote</button>
<span id="quoteStatus" class="muted small"></span>
</div>
<div id="quoteBox" class="hidden">
<div class="kv">
<div>Quote ID</div><div id="qId" class="mono">–</div>
<div>File</div><div id="qFile">–</div>
<div>Size</div><div id="qSize">–</div>
<div>Tiers</div><div id="qTiers">–</div>
<div>Tier size</div><div id="qTierMb">–</div>
<div>Price (EGAZ)</div><div id="qEgaz">–</div>
<div>Price (ETI)</div><div id="qEti">–</div>
<div>Pay to</div><div id="qPayTo" class="mono">–</div>
<div>Expires</div><div id="qExp">–</div>
</div>
<div class="hr"></div>
<div class="row">
<button id="btnPay">Pay with MetaMask</button>
<button id="btnUploadNow" class="hidden">Upload Now</button>
<span id="payStatus" class="muted small"></span>
</div>
</div>
<div id="confirmBox" class="hidden" style="margin-top:10px">
<div><strong>Status:</strong> <span id="confStatus">pending</span></div>
<div><strong>Confirmations:</strong> <span id="confCount">0</span>/<span id="confReq">3</span></div>
<div class="small mono">txHash: <span id="confTx">–</span></div>
</div>
</div>
</div>
</div>
<!-- Row 3: Config / Health / Rates (bottom) -->
<div class="grid cols-3" style="margin-top:16px">
<div class="card">
<h2>Config</h2>
<div class="kv">
<div>Chain</div><div id="cfgChain">–</div>
<div>RPC</div><div class="mono" id="cfgRpc">–</div>
<div>Pay To</div><div class="mono" id="cfgPayTo">–</div>
<div>Tier</div><div id="cfgTier">–</div>
<div>Price per Tier</div><div id="cfgPpt">–</div>
<div>Supported</div><div id="cfgCurrencies">–</div>
<div>Conf threshold</div><div id="cfgConf">–</div>
</div>
</div>
<div class="card">
<h2>Health</h2>
<div class="kv">
<div>Latest block</div><div id="healthBlock">–</div>
<div>Required confs</div><div id="healthReq">–</div>
<div>Now</div><div id="healthNow">–</div>
</div>
</div>
<div class="card">
<h2>Rates</h2>
<div class="kv">
<div>Source</div><div id="rateSrc">–</div>
<div>1 EGAZ ≈</div><div id="rateEgazUsd">–</div>
<div>1 ETI ≈</div><div id="rateEtiUsd">–</div>
<div>1 EGAZ =</div><div id="rateEtiPerEgaz">–</div>
</div>
</div>
</div>
<div class="grid" style="margin-top:16px">
<div class="card">
<h2>Logs</h2>
<div id="log" class="log"></div>
</div>
</div>
</main>
<script src="./app.js"></script>
</body>
</html>