feat: consolidate utility functions into P2NS Plugin SDK

- Centralized common formatting, DOM, and status utilities in `includes/plugins/sdk.js`
- Created `sdk.utils.format`, `sdk.utils.dom`, and `sdk.utils.status` namespaces
- Refactored `domain.consensus` and `peer.visualize` plugins to use the global SDK
- Updated `plugin-handler` to serve SDK utilities globally via `/sdk-utils.js`
- Enhanced SDK validation by delegating to the core validation infrastructure
- Fixed bugs in metric rendering and missing frontend utility functions
This commit is contained in:
Raven Scott
2025-12-17 22:21:58 -05:00
parent 5f5ed3848e
commit ad4784d05d
12 changed files with 1090 additions and 373 deletions
@@ -176,6 +176,7 @@
</div>
<!-- Scripts -->
<script src="/sdk-utils.js"></script>
<script src="/js/utils.js"></script>
<script src="/js/api.js"></script>
<script src="/js/websocket.js"></script>
+16 -96
View File
@@ -1,103 +1,48 @@
/**
* Utility functions for domain consensus plugin
* Now using P2NS Plugin SDK for common utilities
*/
/**
* 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();
}
return window.sdk?.utils?.format?.formatTimestamp(timestamp) || 'Never';
}
/**
* 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)}`;
return window.sdk?.utils?.format?.formatPeerId(peerId) || 'N/A';
}
/**
* 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)}`;
return window.sdk?.utils?.format?.formatHash(hash) || 'N/A';
}
/**
* 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';
}
return window.sdk?.utils?.status?.getConsensusBadgeClass(status) || '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';
}
return window.sdk?.utils?.status?.getConsensusText(status) || '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
}
return window.sdk?.utils?.status?.getConsensusColor(status) || '#6b7280';
}
/**
@@ -112,58 +57,34 @@ function calculateQuorumPercentage(totalVotes, minVotes) {
* Escape HTML to prevent XSS
*/
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
return window.sdk?.utils?.dom?.escapeHtml(text) || text;
}
/**
* 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;
}
if (window.sdk?.utils?.dom?.copyToClipboard) {
return await window.sdk.utils.dom.copyToClipboard(text);
}
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);
};
if (window.sdk?.utils?.dom?.debounce) {
return window.sdk.utils.dom.debounce(func, wait);
}
return func;
}
/**
* Format number with commas
*/
function formatNumber(num) {
if (num === null || num === undefined) return '0';
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
return window.sdk?.utils?.format?.formatNumber(num) || '0';
}
/**
@@ -229,4 +150,3 @@ window.utils = {
createProgressBar,
updateSidebarStats
};
@@ -261,6 +261,7 @@
</div>
<!-- Scripts -->
<script src="/sdk-utils.js"></script>
<script src="/js/utils.js"></script>
<script src="/js/data-processor.js"></script>
<script src="/js/websocket-client.js"></script>
+13 -62
View File
@@ -1,60 +1,27 @@
/**
* Utility functions for peer.visualize
* Now using P2NS Plugin SDK for common utilities
*/
/**
* Format peer ID (short or long)
*/
function formatPeerId(peerId, short = true) {
if (!peerId) return 'Unknown';
if (short) {
return peerId.slice(0, 16) + '...';
}
return peerId;
return window.sdk?.utils?.format?.formatPeerId(peerId, short) || 'Unknown';
}
/**
* Format timestamp to 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 'Just now';
} else if (diff < 3600000) {
const minutes = Math.floor(diff / 60000);
return `${minutes} minute${minutes !== 1 ? 's' : ''} ago`;
} else if (diff < 86400000) {
const hours = Math.floor(diff / 3600000);
return `${hours} hour${hours !== 1 ? 's' : ''} ago`;
} else {
return date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
}
return window.sdk?.utils?.format?.formatTimestamp(timestamp) || 'Never';
}
/**
* Format duration (milliseconds to readable string)
*/
function formatDuration(ms) {
if (!ms || ms === 0) return '0s';
const seconds = Math.floor(ms / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
if (days > 0) {
return `${days}d ${hours % 24}h`;
} else if (hours > 0) {
return `${hours}h ${minutes % 60}m`;
} else if (minutes > 0) {
return `${minutes}m ${seconds % 60}s`;
} else {
return `${seconds}s`;
}
return window.sdk?.utils?.format?.formatDuration(ms) || '0s';
}
/**
@@ -79,29 +46,20 @@ function getNodeColor(node) {
* Debounce function
*/
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
if (window.sdk?.utils?.dom?.debounce) {
return window.sdk.utils.dom.debounce(func, wait);
}
return func;
}
/**
* Throttle function
*/
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
if (window.sdk?.utils?.dom?.throttle) {
return window.sdk.utils.dom.throttle(func, limit);
}
return func;
}
/**
@@ -137,11 +95,7 @@ function arraysEqual(a, b) {
* Format bytes to human readable
*/
function formatBytes(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
return window.sdk?.utils?.format?.formatBytes(bytes) || '0 Bytes';
}
// Export for use in other modules
@@ -160,6 +114,3 @@ if (typeof module !== 'undefined' && module.exports) {
formatBytes
};
}