From 08628704c72d2dbb954dfd12b84e1614ad15f2b6366f422ef1479d1da655104f Mon Sep 17 00:00:00 2001 From: AidarKC Date: Tue, 23 Jun 2026 17:05:57 +0400 Subject: [PATCH] =?UTF-8?q?UI:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D0=BF=D1=80=D0=BE=D1=81=D0=BC=D0=BE=D1=82=D1=80?= =?UTF-8?q?=20=D0=BB=D1=8E=D0=B1=D0=BE=D0=B3=D0=BE=20PDA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VERSION.properties | 4 +- .../js/services/shine-user-pda-service.js | 32 +++ shine-UI/server-ui.html | 35 ++- shine-UI/server-ui/create-server-pda.html | 1 + shine-UI/server-ui/js/read-pda-page.js | 242 ++++++++++++++++++ shine-UI/server-ui/read-pda.html | 91 +++++++ shine-UI/server-ui/update-server-pda.html | 1 + 7 files changed, 390 insertions(+), 16 deletions(-) create mode 100644 shine-UI/server-ui/js/read-pda-page.js create mode 100644 shine-UI/server-ui/read-pda.html diff --git a/VERSION.properties b/VERSION.properties index e1e5b1b..151884f 100644 --- a/VERSION.properties +++ b/VERSION.properties @@ -1,2 +1,2 @@ -client.version=1.2.244 -server.version=1.2.229 +client.version=1.2.245 +server.version=1.2.230 diff --git a/shine-UI/js/services/shine-user-pda-service.js b/shine-UI/js/services/shine-user-pda-service.js index 3ead3b8..33acae7 100644 --- a/shine-UI/js/services/shine-user-pda-service.js +++ b/shine-UI/js/services/shine-user-pda-service.js @@ -623,6 +623,38 @@ export async function readShineUserPda({ login, solanaEndpoint }) { }; } +export async function readShineUserPdaByAddress({ pdaAddress, solanaEndpoint }) { + const address = String(pdaAddress || '').trim(); + const endpoint = String(solanaEndpoint || '').trim(); + if (!address) throw new Error('Не указан адрес PDA'); + if (!endpoint) throw new Error('Не указан Solana RPC endpoint'); + const solana = await loadSolanaLib(); + const connection = new solana.Connection(endpoint, 'confirmed'); + const accountAddress = new solana.PublicKey(address); + const accountInfo = await connection.getAccountInfo(accountAddress, 'confirmed'); + if (!accountInfo?.data) throw new Error(`PDA не найдена: ${address}`); + return { + ...parseShineUserPda(accountInfo.data), + userPda: accountAddress.toBase58(), + pdaAddress: accountAddress.toBase58(), + endpoint, + }; +} + +export async function readShineUserPdaByRef({ value, solanaEndpoint }) { + const ref = String(value || '').trim(); + if (!ref) throw new Error('Не указан логин или адрес PDA'); + try { + const bytes = base58ToBytes(ref); + if (bytes.length === 32) { + return await readShineUserPdaByAddress({ pdaAddress: ref, solanaEndpoint }); + } + } catch { + // Если это не адрес, читаем как логин. + } + return readShineUserPda({ login: ref, solanaEndpoint }); +} + export async function getShineBlockchainUsage({ login, solanaEndpoint }) { const parsed = await readShineUserPda({ login, solanaEndpoint }); const bch = parsed.blockchain; diff --git a/shine-UI/server-ui.html b/shine-UI/server-ui.html index 9df708b..ca37ce6 100644 --- a/shine-UI/server-ui.html +++ b/shine-UI/server-ui.html @@ -14,21 +14,28 @@

Действия

-
- - - +
+ + + +
+
+ + + +
+
+ + + +
-
- - - -
-

Как это работает

diff --git a/shine-UI/server-ui/create-server-pda.html b/shine-UI/server-ui/create-server-pda.html index 40cc3c3..ecffab0 100644 --- a/shine-UI/server-ui/create-server-pda.html +++ b/shine-UI/server-ui/create-server-pda.html @@ -38,6 +38,7 @@

Регистрация серверного аккаунта

diff --git a/shine-UI/server-ui/js/read-pda-page.js b/shine-UI/server-ui/js/read-pda-page.js new file mode 100644 index 0000000..7cc8fb7 --- /dev/null +++ b/shine-UI/server-ui/js/read-pda-page.js @@ -0,0 +1,242 @@ +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'; diff --git a/shine-UI/server-ui/read-pda.html b/shine-UI/server-ui/read-pda.html new file mode 100644 index 0000000..08fcb27 --- /dev/null +++ b/shine-UI/server-ui/read-pda.html @@ -0,0 +1,91 @@ + + + + + + Просмотр PDA — SHiNE Server Admin + + + + +
+ + +

Просмотр PDA

+

Читает user_pda или server PDA по логину либо по адресу и показывает все поля

+ +
+

Параметры Solana

+
+ + +
+
+ +
+

Источник PDA

+
+ + +
Если введён base58-адрес длиной 32 байта, он читается напрямую. Иначе значение трактуется как логин.
+
+
+ +
+
+
+ + + + + + +
+ + + + diff --git a/shine-UI/server-ui/update-server-pda.html b/shine-UI/server-ui/update-server-pda.html index b4e9e5f..6b695ba 100644 --- a/shine-UI/server-ui/update-server-pda.html +++ b/shine-UI/server-ui/update-server-pda.html @@ -46,6 +46,7 @@

Обновление PDA сервера