import { readShineUserPdaByRef } from '../../js/services/shine-user-pda-service.js'; import { bytesToBase58 } from '../../js/services/crypto-utils.js'; import { $, clearStatus, formatBigInt, formatTimestamp, setStatus } from './server-ui-shared.js'; function hex(bytes) { const data = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes || []); return Array.from(data).map((x) => x.toString(16).padStart(2, '0')).join(''); } function base58(bytes) { const data = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes || []); return bytesToBase58(data); } function renderRows(rows) { const container = $('summaryRows'); container.innerHTML = ''; for (const row of rows) { const el = document.createElement('div'); el.className = 'field-row'; const label = document.createElement('div'); label.className = 'field-label'; label.textContent = row.label; const value = document.createElement('div'); value.className = `field-value${row.muted ? ' muted' : ''}`; value.textContent = row.value; el.append(label, value); container.appendChild(el); } } function renderMiniGrid(items) { const wrap = document.createElement('div'); wrap.className = 'mini-grid'; for (const item of items) { const node = document.createElement('div'); node.className = 'mini-item'; const label = document.createElement('div'); label.className = 'mini-label'; label.textContent = item.label; const value = document.createElement('div'); value.className = 'mini-value'; value.textContent = item.value; node.append(label, value); wrap.appendChild(node); } return wrap; } function renderBlock(title, subtitle, items) { const card = document.createElement('div'); card.className = 'block-card'; const ttl = document.createElement('div'); ttl.className = 'block-title'; ttl.textContent = title; card.appendChild(ttl); if (subtitle) { const sub = document.createElement('div'); sub.className = 'block-subtitle'; sub.textContent = subtitle; card.appendChild(sub); } card.appendChild(renderMiniGrid(items)); return card; } function renderArrayLine(values, emptyLabel = '—') { const arr = Array.isArray(values) ? values : []; return arr.length ? arr.join(', ') : emptyLabel; } function renderSessionList(sessions) { const list = document.createElement('div'); list.className = 'details-wrap'; const details = document.createElement('details'); details.open = true; const summary = document.createElement('summary'); summary.textContent = `Сессии (${Array.isArray(sessions) ? sessions.length : 0})`; const body = document.createElement('div'); body.className = 'details-body'; if (!Array.isArray(sessions) || sessions.length === 0) { body.innerHTML = '
Сессий нет.
'; } else { const grid = document.createElement('div'); grid.className = 'block-list'; sessions.forEach((session, idx) => { grid.appendChild(renderBlock( `Session #${idx + 1}`, `type=${session.sessionType}, version=${session.sessionVersion}`, [ { label: 'Имя', value: session.sessionName || '—' }, { label: 'PubKey', value: base58(session.sessionPubKey32) }, ], )); }); body.appendChild(grid); } details.append(summary, body); list.appendChild(details); return list; } function renderParsed(parsed) { $('summaryCard').style.display = 'block'; $('blocksCard').style.display = 'block'; $('rawCard').style.display = 'block'; renderRows([ { label: 'PDA адрес', value: parsed.pdaAddress }, { label: 'Логин', value: parsed.login }, { label: 'Статус', value: parsed.isServer ? 'server' : 'not server' }, { label: 'recordNumber', value: String(parsed.recordNumber) }, { label: 'createdAtMs', value: `${parsed.createdAtMs} · ${formatTimestamp(parsed.createdAtMs)}` }, { label: 'updatedAtMs', value: `${parsed.updatedAtMs} · ${formatTimestamp(parsed.updatedAtMs)}` }, { label: 'recordLen', value: String(parsed.recordLen) }, { label: 'prevRecordHash32', value: hex(parsed.prevRecordHash) }, { label: 'signature64', value: base58(parsed.signature) }, { label: 'unsignedBytesLen', value: String(parsed.unsignedBytes?.length || 0) }, ]); const badgeLine = $('badgeLine'); badgeLine.innerHTML = ''; const badges = [ { label: `server=${parsed.isServer ? '1' : '0'}`, kind: parsed.isServer ? 'ok' : 'warn' }, { label: `trusted=${Number(parsed.trustedCount || 0)}`, kind: Number(parsed.trustedCount || 0) > 0 ? 'ok' : 'warn' }, { label: `sessions=${Array.isArray(parsed.sessions) ? parsed.sessions.length : 0}`, kind: 'info' }, { label: `access=${Array.isArray(parsed.accessServers) ? parsed.accessServers.length : 0}`, kind: 'info' }, { label: `sync=${Array.isArray(parsed.syncServers) ? parsed.syncServers.length : 0}`, kind: 'info' }, ]; badges.forEach((item) => { const badge = document.createElement('span'); badge.className = `badge ${item.kind}`; badge.textContent = item.label; badgeLine.appendChild(badge); }); const blocks = $('blocksList'); blocks.innerHTML = ''; blocks.appendChild(renderBlock('RecoveryKeyBlock', 'block_type=0', [ { label: 'recoveryKey32', value: base58(parsed.recoveryKey) }, ])); blocks.appendChild(renderBlock('RootKeyBlock', 'block_type=1', [ { label: 'rootKey32', value: base58(parsed.rootKey) }, ])); blocks.appendChild(renderBlock('ClientKeyBlock', 'block_type=2', [ { label: 'clientKey32', value: base58(parsed.clientKey) }, ])); blocks.appendChild(renderBlock('BlockchainRegistryBlock', 'block_type=3', [ { label: 'blockchainType', value: String(parsed.blockchain?.blockchainType ?? 0) }, { label: 'blockchainName', value: parsed.blockchain?.blockchainName || '—' }, { label: 'blockchainPublicKey32', value: base58(parsed.blockchain?.blockchainPublicKey) }, { label: 'paidLimitBytes', value: formatBigInt(parsed.blockchain?.paidLimitBytes || 0n) }, { label: 'usedBytes', value: formatBigInt(parsed.blockchain?.usedBytes || 0n) }, { label: 'lastBlockNumber', value: String(parsed.blockchain?.lastBlockNumber ?? 0) }, { label: 'lastBlockHash32', value: hex(parsed.blockchain?.lastBlockHash) }, { label: 'lastBlockSignature64', value: base58(parsed.blockchain?.lastBlockSignature) }, { label: 'arweaveTxId', value: parsed.blockchain?.arweaveTxId || '—' }, ])); blocks.appendChild(renderBlock('ServerProfileBlock', 'block_type=30', [ { label: 'isServer', value: parsed.isServer ? '1' : '0' }, { label: 'addressFormatType', value: String(parsed.addressFormatType ?? 0) }, { label: 'addressFormatVersion', value: String(parsed.addressFormatVersion ?? 0) }, { label: 'serverAddress', value: parsed.serverAddress || '—' }, { label: 'syncServersCount', value: String(parsed.syncServers?.length || 0) }, { label: 'syncServers', value: renderArrayLine(parsed.syncServers) }, ])); blocks.appendChild(renderBlock('AccessServersBlock', 'block_type=40', [ { label: 'accessServersCount', value: String(parsed.accessServers?.length || 0) }, { label: 'accessServers', value: renderArrayLine(parsed.accessServers) }, ])); blocks.appendChild(renderBlock('SessionsBlock', 'block_type=50', [ { label: 'sessionsMode', value: String(parsed.sessionsMode ?? 0) }, { label: 'sessionsCount', value: String(parsed.sessions?.length || 0) }, { label: 'sessions', value: parsed.sessions?.length ? 'ниже' : '—' }, ])); blocks.appendChild(renderBlock('TrustedStateBlock', 'block_type=70', [ { label: 'trustedCount', value: String(parsed.trustedCount ?? 0) }, ])); blocks.appendChild(renderSessionList(parsed.sessions)); $('rawJson').textContent = JSON.stringify({ pdaAddress: parsed.pdaAddress, login: parsed.login, isServer: parsed.isServer, recordNumber: parsed.recordNumber, createdAtMs: parsed.createdAtMs.toString(), updatedAtMs: parsed.updatedAtMs.toString(), recordLen: parsed.recordLen, trustedCount: parsed.trustedCount, addressFormatType: parsed.addressFormatType, addressFormatVersion: parsed.addressFormatVersion, serverAddress: parsed.serverAddress, syncServers: parsed.syncServers, accessServers: parsed.accessServers, sessionsMode: parsed.sessionsMode, sessions: parsed.sessions.map((s) => ({ sessionType: s.sessionType, sessionVersion: s.sessionVersion, sessionName: s.sessionName, sessionPubKey32: base58(s.sessionPubKey32), })), recoveryKey: base58(parsed.recoveryKey), rootKey: base58(parsed.rootKey), clientKey: base58(parsed.clientKey), blockchain: { blockchainType: parsed.blockchain.blockchainType, blockchainName: parsed.blockchain.blockchainName, blockchainPublicKey: base58(parsed.blockchain.blockchainPublicKey), paidLimitBytes: parsed.blockchain.paidLimitBytes.toString(), usedBytes: parsed.blockchain.usedBytes.toString(), lastBlockNumber: parsed.blockchain.lastBlockNumber, lastBlockHash: hex(parsed.blockchain.lastBlockHash), lastBlockSignature: base58(parsed.blockchain.lastBlockSignature), arweaveTxId: parsed.blockchain.arweaveTxId || '', }, }, null, 2); } $('btnLoad').addEventListener('click', async () => { clearStatus($('status')); $('btnLoad').disabled = true; $('summaryCard').style.display = 'none'; $('blocksCard').style.display = 'none'; $('rawCard').style.display = 'none'; try { const endpoint = String($('endpoint').value || '').trim(); const ref = String($('ref').value || '').trim(); if (!endpoint) throw new Error('Укажите Solana endpoint'); if (!ref) throw new Error('Укажите логин или адрес PDA'); setStatus($('status'), 'Чтение PDA...', 'info'); const parsed = await readShineUserPdaByRef({ value: ref, solanaEndpoint: endpoint }); renderParsed(parsed); setStatus($('status'), 'PDA загружена.', 'success'); } catch (error) { setStatus($('status'), error?.message || String(error), 'error'); } finally { $('btnLoad').disabled = false; } }); document.body.dataset.ready = '1';