Files
SHiNE-server/shine-UI/js/pages/my-blockchain-view.js
T

50 lines
4.0 KiB
JavaScript

import { createTopBar } from '../components/topbar.js';
import { authService } from '../state.js';
import { KeyRotationClient } from '../services/key-rotation-service.js';
export const pageMeta = { id: 'my-blockchain-view', title: 'Мой блокчейн' };
const TYPE_LABELS = new Map([
['0:0','Начало блокчейна'], ['0:2','Смена ключей / fork'],
['1:1','Публикация'], ['1:2','Ответ'], ['1:3','Редактирование'],
['2:1','Реакция'], ['3:1','Связь'], ['4:1','Статус'],
]);
function shortHash(v){const s=String(v||'');return s.length>18?`${s.slice(0,9)}…${s.slice(-7)}`:s;}
function eventLabel(item){return TYPE_LABELS.get(`${item.msgType}:${item.msgSubType}`)||`Событие ${item.msgType}/${item.msgSubType}`;}
function formatDate(ms){try{return new Date(Number(ms)||0).toLocaleString('ru-RU');}catch{return '';}}
export function render({ navigate, chrome }) {
const screen=document.createElement('section'); screen.className='stack';
chrome?.setTopbar(createTopBar({ title:'Мой блокчейн', back:{label:'←',onClick:()=>navigate('settings-view')} }));
const intro=document.createElement('div'); intro.className='card stack';
intro.innerHTML=`<strong>История ваших действий</strong><span class="meta-muted">Это текущая активная версия вашего блокчейна. Хэши записей сохраняют логическую идентичность при fork.</span>`;
const rotate=document.createElement('button'); rotate.type='button'; rotate.className='primary-btn'; rotate.textContent='Сменить пароль / ключи'; rotate.addEventListener('click',()=>navigate('key-rotation-view'));
intro.append(rotate); screen.append(intro);
const list=document.createElement('div'); list.className='stack'; screen.append(list);
const more=document.createElement('button'); more.type='button'; more.className='secondary-btn'; more.textContent='Показать более ранние записи'; more.hidden=true; screen.append(more);
const client=new KeyRotationClient(authService); let before=null; let loading=false; let disposed=false;
async function load(reset=false){
if(loading||disposed)return; loading=true; more.disabled=true;
if(reset){list.innerHTML='';before=null;}
try{
const data=await client.getMyBlockchain({beforeBlock:before,limit:50,includeBlockBytes:false});
if(reset){const head=document.createElement('div');head.className='card stack';head.innerHTML=`<strong>${data.blockchainName||'—'}</strong><span class="meta-muted">Последний блок: #${Number(data.tipBlockNumber??-1)} · ${shortHash(data.tipBlockHash)}</span>`;list.append(head);}
for(const item of data.blocks||[]){
const card=document.createElement('div');card.className='card stack';
const target=item.toLogin?`<span class="meta-muted">Цель: ${escapeHtml(item.toLogin)} #${item.toBlockNumber??'—'} · ${shortHash(item.toBlockHash)}</span>`:'';
card.innerHTML=`<div><strong>#${item.blockNumber} · ${escapeHtml(eventLabel(item))}</strong></div><span class="meta-muted">${formatDate(item.timestampMs)} · hash ${shortHash(item.blockHash)}</span>${target}`;
list.append(card);
}
before=data.nextBeforeBlock;
more.hidden=before==null;
if(!(data.blocks||[]).length && reset){const empty=document.createElement('div');empty.className='card';empty.textContent='Записей пока нет.';list.append(empty);}
}catch(e){const err=document.createElement('div');err.className='card';err.textContent=`Не удалось загрузить блокчейн: ${e?.message||e}`;list.append(err);}
finally{loading=false;more.disabled=false;}
}
more.addEventListener('click',()=>void load(false)); void load(true);
screen.cleanup=()=>{disposed=true;}; return screen;
}
function escapeHtml(value){return String(value??'').replace(/[&<>"]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]));}