233 lines
5.6 KiB
JavaScript
233 lines
5.6 KiB
JavaScript
/**
|
|
* Utility functions for domain consensus plugin
|
|
*/
|
|
|
|
/**
|
|
* Format a timestamp to human-readable string
|
|
*/
|
|
function formatTimestamp(timestamp) {
|
|
if (!timestamp) return 'Never';
|
|
const date = new Date(timestamp);
|
|
const now = new Date();
|
|
const diff = now - date;
|
|
|
|
if (diff < 60000) {
|
|
return `${Math.floor(diff / 1000)}s ago`;
|
|
} else if (diff < 3600000) {
|
|
return `${Math.floor(diff / 60000)}m ago`;
|
|
} else if (diff < 86400000) {
|
|
return `${Math.floor(diff / 3600000)}h ago`;
|
|
} else {
|
|
return date.toLocaleString();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Format a peer ID to shortened version
|
|
*/
|
|
function formatPeerId(peerId) {
|
|
if (!peerId) return 'N/A';
|
|
if (peerId.length <= 16) return peerId;
|
|
return `${peerId.slice(0, 8)}...${peerId.slice(-8)}`;
|
|
}
|
|
|
|
/**
|
|
* Format a hash to shortened version
|
|
*/
|
|
function formatHash(hash) {
|
|
if (!hash) return 'N/A';
|
|
if (hash.length <= 20) return hash;
|
|
return `${hash.slice(0, 10)}...${hash.slice(-10)}`;
|
|
}
|
|
|
|
/**
|
|
* Get status badge class based on consensus status
|
|
*/
|
|
function getStatusBadgeClass(status) {
|
|
switch (status) {
|
|
case 'resolved':
|
|
return 'status-badge resolved';
|
|
case 'insufficient_quorum':
|
|
return 'status-badge insufficient_quorum';
|
|
case 'tie':
|
|
return 'status-badge tie';
|
|
case 'no_claims':
|
|
return 'status-badge no_claims';
|
|
case 'error':
|
|
return 'status-badge error';
|
|
default:
|
|
return 'status-badge no_claims';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get status text for consensus status
|
|
*/
|
|
function getStatusText(status) {
|
|
switch (status) {
|
|
case 'resolved':
|
|
return 'Resolved';
|
|
case 'insufficient_quorum':
|
|
return 'Insufficient Quorum';
|
|
case 'tie':
|
|
return 'Tie';
|
|
case 'no_claims':
|
|
return 'No Claims';
|
|
case 'error':
|
|
return 'Error';
|
|
default:
|
|
return 'Unknown';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get status color for consensus status
|
|
*/
|
|
function getStatusColor(status) {
|
|
switch (status) {
|
|
case 'resolved':
|
|
return '#10b981'; // green-500
|
|
case 'insufficient_quorum':
|
|
return '#eab308'; // yellow-500
|
|
case 'tie':
|
|
return '#f97316'; // orange-500
|
|
case 'no_claims':
|
|
return '#6b7280'; // gray-500
|
|
case 'error':
|
|
return '#ef4444'; // red-500
|
|
default:
|
|
return '#6b7280'; // gray-500
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculate quorum percentage
|
|
*/
|
|
function calculateQuorumPercentage(totalVotes, minVotes) {
|
|
if (minVotes === 0) return 0;
|
|
return Math.min(100, Math.round((totalVotes / minVotes) * 100));
|
|
}
|
|
|
|
/**
|
|
* Escape HTML to prevent XSS
|
|
*/
|
|
function escapeHtml(text) {
|
|
const div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
|
|
/**
|
|
* Copy text to clipboard
|
|
*/
|
|
async function copyToClipboard(text) {
|
|
try {
|
|
await navigator.clipboard.writeText(text);
|
|
return true;
|
|
} catch (err) {
|
|
// Fallback for older browsers
|
|
const textArea = document.createElement('textarea');
|
|
textArea.value = text;
|
|
textArea.style.position = 'fixed';
|
|
textArea.style.opacity = '0';
|
|
document.body.appendChild(textArea);
|
|
textArea.select();
|
|
try {
|
|
document.execCommand('copy');
|
|
document.body.removeChild(textArea);
|
|
return true;
|
|
} catch (err) {
|
|
document.body.removeChild(textArea);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Debounce function
|
|
*/
|
|
function debounce(func, wait) {
|
|
let timeout;
|
|
return function executedFunction(...args) {
|
|
const later = () => {
|
|
clearTimeout(timeout);
|
|
func(...args);
|
|
};
|
|
clearTimeout(timeout);
|
|
timeout = setTimeout(later, wait);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Format number with commas
|
|
*/
|
|
function formatNumber(num) {
|
|
if (num === null || num === undefined) return '0';
|
|
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
}
|
|
|
|
/**
|
|
* Create a progress bar element
|
|
*/
|
|
function createProgressBar(percentage, color = '#3b82f6') {
|
|
const bar = document.createElement('div');
|
|
bar.className = 'w-full progress-bar-container';
|
|
bar.innerHTML = `
|
|
<div class="progress-bar" style="width: ${percentage}%; background-color: ${color};"></div>
|
|
`;
|
|
return bar;
|
|
}
|
|
|
|
/**
|
|
* Update sidebar stats (can be called from any view)
|
|
*/
|
|
async function updateSidebarStats() {
|
|
try {
|
|
// Fetch overview and peers data to get stats
|
|
const [overviewData, peersData] = await Promise.all([
|
|
window.apiClient.getOverview(),
|
|
window.apiClient.getPeers()
|
|
]);
|
|
|
|
const stats = overviewData.stats || {};
|
|
const activePeers = peersData.activePeers || 0;
|
|
|
|
const totalDomainsEl = document.getElementById('statTotalDomains');
|
|
const resolvedEl = document.getElementById('statResolved');
|
|
const activePeersEl = document.getElementById('statActivePeers');
|
|
|
|
if (totalDomainsEl) totalDomainsEl.textContent = formatNumber(stats.totalDomains || 0);
|
|
if (resolvedEl) resolvedEl.textContent = formatNumber(stats.resolved || 0);
|
|
if (activePeersEl) activePeersEl.textContent = formatNumber(activePeers);
|
|
} catch (error) {
|
|
console.error('Error updating sidebar stats:', error);
|
|
// Set to 0 on error
|
|
const totalDomainsEl = document.getElementById('statTotalDomains');
|
|
const resolvedEl = document.getElementById('statResolved');
|
|
const activePeersEl = document.getElementById('statActivePeers');
|
|
if (totalDomainsEl) totalDomainsEl.textContent = '0';
|
|
if (resolvedEl) resolvedEl.textContent = '0';
|
|
if (activePeersEl) activePeersEl.textContent = '0';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Export functions
|
|
*/
|
|
window.utils = {
|
|
formatTimestamp,
|
|
formatPeerId,
|
|
formatHash,
|
|
getStatusBadgeClass,
|
|
getStatusText,
|
|
getStatusColor,
|
|
calculateQuorumPercentage,
|
|
escapeHtml,
|
|
copyToClipboard,
|
|
debounce,
|
|
formatNumber,
|
|
createProgressBar,
|
|
updateSidebarStats
|
|
};
|
|
|