diff --git a/index.js b/index.js
index ad175ed..f3630c2 100644
--- a/index.js
+++ b/index.js
@@ -3196,10 +3196,490 @@ function displayJsonData(jsonData, type) {
quotesOnKeys: false,
quotesOnValues: false
};
-
+
// Show JSON data in SweetAlert popup
outputAlert(jsonData, type);
}
+window.displayJsonData = displayJsonData;
+
+// ========== RICH GUI DISPLAY FUNCTIONS FOR INFRA DATA ==========
+
+// Returns a colored status badge HTML span
+function getInfraStatusBadge(status) {
+ const s = (status || '').toLowerCase();
+ let color = '#6c757d', icon = 'โช';
+ if (s === 'running') { color = '#28a745'; icon = '๐ข'; }
+ else if (s === 'failed' || s.includes('fail')) { color = '#dc3545'; icon = '๐ด'; }
+ else if (s === 'preparing' || s === 'prepared' || s.includes('provision')) { color = '#ffc107'; icon = '๐ก'; }
+ else if (s === 'terminated' || s === 'deleted') { color = '#6c757d'; icon = 'โซ'; }
+ return `${icon} ${window.escapeHtml ? window.escapeHtml(status || 'Unknown') : (status || 'Unknown')}`;
+}
+
+// Stores SSH commands indexed by position so onclick can reference them safely
+window._guiSshCommands = [];
+window.copyGuiSshCommand = function(idx) {
+ const cmd = window._guiSshCommands[idx] || '';
+ navigator.clipboard.writeText(cmd).then(() => {
+ const el = document.getElementById('gui-ssh-cmd-' + idx);
+ if (el) { const orig = el.style.background; el.style.background = '#155724'; setTimeout(() => el.style.background = orig, 1500); }
+ }).catch(err => console.error('copy failed:', err));
+};
+
+// Builds the node-by-nodeGroup summary HTML block (shared by status + dynamic result views)
+function buildInfraNodeSummaryHtml(data) {
+ const esc = window.escapeHtml || (s => String(s));
+ const nodes = data.node || [];
+
+ // Group nodes by nodeGroupId
+ const groups = {};
+ nodes.forEach(nd => {
+ const gid = nd.nodeGroupId || 'default';
+ if (!groups[gid]) groups[gid] = [];
+ groups[gid].push(nd);
+ });
+ const groupCount = Object.keys(groups).length;
+
+ // Unique providers
+ const providers = [...new Set(nodes.map(nd =>
+ nd.connectionConfig?.providerName || nd.connectionName?.split('-')[0] || null
+ ).filter(Boolean))];
+
+ // Node status counts
+ const sc = data.statusCount || {};
+ const runningCount = sc.countRunning ?? nodes.filter(nd => (nd.status || '').toLowerCase() === 'running').length;
+ const failedCount = sc.countFailed ?? nodes.filter(nd => (nd.status || '').toLowerCase().includes('fail')).length;
+ const totalCount = sc.countTotal ?? nodes.length;
+
+ // Estimated hourly cost
+ let totalCost = 0, hasCost = false;
+ nodes.forEach(nd => { if (nd.spec?.costPerHour > 0) { totalCost += nd.spec.costPerHour; hasCost = true; } });
+
+ // newNodeList set for quick lookup
+ const newNodeSet = new Set(data.newNodeList || []);
+
+ // โโ Summary cards โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ const nodeColor = failedCount > 0 ? '#dc3545' : (runningCount === totalCount && totalCount > 0 ? '#28a745' : '#ffc107');
+ const hasActiveAction = data.targetAction && data.targetAction !== 'None' && data.targetAction !== '';
+
+ let html = `
`;
+ html += `
+
+
${runningCount}/${totalCount}
+
Running Nodes
+
+
+
${groupCount}
+
NodeGroups
+
+
+
${providers.length ? providers.map(p => esc(p)).join('
') : '-'}
+
Providers
+
+
+
${hasCost ? '$' + totalCost.toFixed(4) : '-'}
+
Est. Cost/hr
+
+
+ ${getInfraStatusBadge(data.status)}
+ ${hasActiveAction ? `
โณ ${esc(data.targetAction)}${data.targetStatus ? ' โ ' + esc(data.targetStatus) : ''}
` : ''}
+
Status
+
`;
+ html += `
`;
+
+ // โโ Status distribution pills โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ if (totalCount > 0 && (sc.countFailed > 0 || sc.countCreating > 0 || sc.countSuspended > 0 || sc.countTerminated > 0 || sc.countUndefined > 0)) {
+ const pills = [
+ { label: 'Running', count: sc.countRunning || 0, color: '#28a745' },
+ { label: 'Creating', count: sc.countCreating || 0, color: '#17a2b8' },
+ { label: 'Failed', count: sc.countFailed || 0, color: '#dc3545' },
+ { label: 'Suspended', count: sc.countSuspended || 0, color: '#6c757d' },
+ { label: 'Terminated', count: sc.countTerminated || 0, color: '#495057' },
+ { label: 'Undefined', count: sc.countUndefined || 0, color: '#856404' },
+ ].filter(p => p.count > 0);
+ html += ``;
+ pills.forEach(p => { html += `${p.label}: ${p.count}`; });
+ html += `
`;
+ }
+
+ // โโ creationErrors block โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ const ce = data.creationErrors;
+ if (ce && (ce.failedNodeCount > 0 || (ce.nodeCreationErrors || []).length > 0 || (ce.nodeObjectCreationErrors || []).length > 0)) {
+ const allErrors = [...(ce.nodeObjectCreationErrors || []), ...(ce.nodeCreationErrors || [])];
+ html += `
+
+
+ โ Creation Errors โ ${ce.failedNodeCount || allErrors.length} failed / ${ce.totalNodeCount || totalCount} total
+ ${ce.failureHandlingStrategy ? `(strategy: ${esc(ce.failureHandlingStrategy)})` : ''}
+
+ ${allErrors.length > 0 ? `
+
+ | Node |
+ Phase |
+ Error |
+ Time |
+
+ ${allErrors.map(e => `
+
+ | ${esc(e.nodeName || '-')} |
+ ${esc(e.phase || '-')} |
+ ${esc(e.error || '-')} |
+ ${esc((e.timestamp || '').split('T')[0] || e.timestamp || '-')} |
+
`).join('')}
+
+
` : ''}
+
`;
+ }
+
+ // โโ Infra-level systemMessage โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ const sysMessages = Array.isArray(data.systemMessage) ? data.systemMessage.filter(Boolean) : (data.systemMessage ? [data.systemMessage] : []);
+ if (sysMessages.length > 0) {
+ html += `
+ โ ๏ธ ${sysMessages.map(m => esc(m)).join('
')}
+
`;
+ }
+
+ // โโ Infra labels โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ const labels = data.label && typeof data.label === 'object' ? Object.entries(data.label).filter(([k]) => k) : [];
+ if (labels.length > 0) {
+ html += ``;
+ labels.forEach(([k, v]) => {
+ html += v
+ ? `${esc(k)}: ${esc(v)}`
+ : `${esc(k)}`;
+ });
+ html += `
`;
+ }
+
+ // โโ Per-NodeGroup sections โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ const COL_COUNT = 6; // node-varying columns: Node ID, Status, Public IP, Private IP, CSP Resource ID, Created
+ Object.entries(groups).forEach(([gid, gnodes]) => {
+ const first = gnodes[0] || {};
+
+ // โโ Group-level common values (derived from first node) โโโโโโ
+ const provider = first.connectionConfig?.providerName || first.connectionName?.split('-')[0] || '';
+ const platform = (provider || '').toLowerCase();
+ const providerColor = cspGenericColors[platform] || cspGenericColors[platform.split('-')[0]] || '#6c757d';
+ const connName = first.connectionName || '';
+ const region = first.region?.region || first.connectionConfig?.regionZoneInfo?.assignedRegion || '';
+ const zone = first.region?.zone || first.connectionConfig?.regionZoneInfo?.assignedZone || '';
+ const sshUser = first.nodeUserName || '';
+ const sshPort = first.sshPort || 22;
+
+ // Check if all nodes share same sshKeyId
+ const allSshKeys = [...new Set(gnodes.map(nd => nd.sshKeyId).filter(Boolean))];
+ const uniformSshKey = allSshKeys.length === 1 ? allSshKeys[0].split('+').pop() || allSshKeys[0] : '';
+
+ // Spec summary (group-level)
+ let specHtml = '';
+ if (first.spec?.vCPU || first.spec?.memoryGiB) {
+ const parts = [];
+ if (first.spec.vCPU) parts.push(`${first.spec.vCPU} vCPU`);
+ if (first.spec.memoryGiB) parts.push(`${first.spec.memoryGiB} GiB`);
+ if (first.spec.acceleratorType && first.spec.acceleratorType !== 'none')
+ parts.push(`+${esc(first.spec.acceleratorType.toUpperCase())}(${first.spec.acceleratorCount || 1})`);
+ specHtml = parts.join(' / ');
+ if (first.cspSpecName) specHtml += ` (${esc(first.cspSpecName)})`;
+ if (first.spec.costPerHour > 0) specHtml += ` ยท $${first.spec.costPerHour}/h`;
+ } else if (first.specId) {
+ const seg = first.specId.split('+').pop() || first.specId;
+ specHtml = `${esc(seg)}`;
+ if (first.cspSpecName) specHtml += ` (${esc(first.cspSpecName)})`;
+ }
+
+ // OS summary (group-level)
+ let osText = '';
+ if (first.image?.osDistribution) osText = first.image.osDistribution;
+ else if (first.image?.osType) osText = first.image.osType;
+ else if (first.imageId) osText = first.imageId.split('+').pop() || first.imageId;
+
+ // Disk summary (group-level)
+ const diskType = first.rootDiskType && first.rootDiskType !== 'default' ? first.rootDiskType : '';
+ const diskSize = first.rootDiskSize > 0 ? `${first.rootDiskSize}GB` : '';
+ const diskText = (diskType || diskSize) ? `${diskType}${diskType && diskSize ? ' ' : ''}${diskSize}` : '';
+
+ // Build CSP badge
+ const providerBadge = provider
+ ? `${esc(provider.toUpperCase())}`
+ : '';
+
+ // Build info chips for the group header details row
+ const chips = [];
+ if (provider) chips.push(`โ๏ธ ${providerBadge}`);
+ if (connName) chips.push(`๐ ${esc(connName)}`);
+ if (region) chips.push(`๐ ${esc(region)}${zone ? ` / ${esc(zone)}` : ''}`);
+ if (sshUser) chips.push(`๐ค ${esc(sshUser)}:${sshPort !== 22 ? `${sshPort}` : sshPort}${uniformSshKey ? ` ๐${esc(uniformSshKey)}` : ''}`);
+ if (specHtml) chips.push(`โ๏ธ ${specHtml}`);
+ if (osText) chips.push(`๐ฟ ${esc(osText)}`);
+ if (diskText) chips.push(`๐พ ${esc(diskText)}`);
+
+ html += `
+
+
+
+ ๐ฆ NodeGroup: ${esc(gid)}
+ ${gnodes.length} node(s)
+
+
+ ${chips.join('')}
+
+
+
+
+
+ | Node ID |
+ Status |
+ Public IP |
+ Private IP |
+ CSP Resource ID |
+ Created |
+
+ `;
+
+ gnodes.forEach((nd, i) => {
+ const bg = i % 2 === 0 ? '#0d1b2a' : '#0a1520';
+ const isNew = newNodeSet.has(nd.id);
+
+ // Status cell โ include monAgentStatus + networkAgentStatus + targetAction
+ const monIcon = nd.monAgentStatus === 'installed' ? '๐กโ' : nd.monAgentStatus === 'failed' ? '๐กโ' : '';
+ const netIcon = nd.networkAgentStatus === 'installed' ? '๐โ' : nd.networkAgentStatus === 'failed' ? '๐โ' : '';
+ const agentIcons = [monIcon, netIcon].filter(Boolean).join(' ');
+ const statusCell = getInfraStatusBadge(nd.status)
+ + (agentIcons ? `${agentIcons}` : '')
+ + (nd.targetAction && nd.targetAction !== 'None' ? `โณ${esc(nd.targetAction)}
` : '');
+
+ // CSP resource ID
+ const cspId = nd.cspResourceId || nd.cspResourceName || '-';
+ const cspIdShort = cspId.length > 28 ? cspId.slice(0, 26) + 'โฆ' : cspId;
+
+ // Created date
+ const created = nd.createdTime ? esc(nd.createdTime.split(' ')[0]) : '-';
+
+ // Row border highlight for new nodes
+ const rowStyle = isNew ? `background:${bg};outline:1px solid #28a745;` : `background:${bg};`;
+
+ // SSH key per-node (shown only if nodes have different keys)
+ const nodeKeyId = nd.sshKeyId ? nd.sshKeyId.split('+').pop() || nd.sshKeyId : '';
+ const nodeIdCell = isNew
+ ? `NEW ${esc(nd.id || '-')}`
+ : esc(nd.id || '-');
+ const nodeIdSuffix = (!uniformSshKey && nodeKeyId)
+ ? `๐${esc(nodeKeyId)}
` : '';
+
+ html += `
+
+ | ${nodeIdCell}${nodeIdSuffix} |
+ ${statusCell} |
+ ${esc(nd.publicIP || '-')} |
+ ${esc(nd.privateIP || '-')} |
+ ${esc(cspIdShort)} |
+ ${created} |
+
`;
+
+ // Sub-row: node labels (show all labels; keys with empty values shown as plain tags)
+ const ndLabels = nd.label && typeof nd.label === 'object' ? Object.entries(nd.label).filter(([k]) => k) : [];
+ if (ndLabels.length > 0) {
+ const pillsHtml = ndLabels.map(([k, v]) =>
+ v ? `${esc(k)}: ${esc(v)}`
+ : `${esc(k)}`
+ ).join('');
+ html += `| ๐ท๏ธ ${pillsHtml} |
`;
+ }
+
+ // Sub-row: network / security details (vNetId, subnetId, securityGroupIds)
+ const netDetails = [];
+ if (nd.vNetId) netDetails.push(`vNet: ${esc(nd.vNetId)}`);
+ if (nd.subnetId) netDetails.push(`Subnet: ${esc(nd.subnetId)}`);
+ if (nd.securityGroupIds?.length) netDetails.push(`SG: ${nd.securityGroupIds.map(esc).join(', ')}`);
+ if (netDetails.length > 0) {
+ html += `| ๐ ${netDetails.join(' ยท ')} |
`;
+ }
+
+ // Sub-row: systemMessage (especially important for failed nodes)
+ if (nd.systemMessage) {
+ const isFail = (nd.status || '').toLowerCase().includes('fail');
+ const msgColor = isFail ? '#f88' : '#ffc107';
+ const msgBg = isFail ? '#2a1010' : '#1a1500';
+ html += `|
+ ${isFail ? 'โ' : 'โน๏ธ'} ${esc(nd.systemMessage)}
+ |
`;
+ }
+ });
+ html += `
`;
+ });
+
+ return html;
+}
+
+// Rich GUI for Infra status view (replaces raw JSON in statusInfra)
+function displayInfraStatusGui(data) {
+ window._currentJsonOutput = data;
+ window._currentJsonString = JSON.stringify(data, null, 2);
+ const esc = window.escapeHtml || (s => String(s));
+ const infraId = data.id || data.name || 'Infra';
+
+ // Meta tags row (description, CLADNet, etc.)
+ const metaItems = [];
+ if (data.description) metaItems.push(`๐ ${esc(data.description)}`);
+ if (data.configureCloudAdaptiveNetwork === 'yes') metaItems.push(`๐ CLADNet: enabled`);
+ const metaHtml = metaItems.length
+ ? `${metaItems.join(' ยท ')}
`
+ : '';
+
+ Swal.fire({
+ title: `๐ Infra Status: ${esc(infraId)}`,
+ html: `
+
+ ${metaHtml}
+ ${buildInfraNodeSummaryHtml(data)}
+
+
+
+
+
`,
+ background: '#0e1746',
+ color: '#e0e0e0',
+ showConfirmButton: false,
+ showCancelButton: true,
+ cancelButtonText: 'โ Close',
+ width: '90%',
+ });
+}
+
+// Rich GUI for Access Info view (replaces raw JSON in getAccessInfo)
+function displayAccessInfoGui(data, infraId) {
+ window._currentJsonOutput = data;
+ window._currentJsonString = JSON.stringify(data, null, 2);
+ const esc = window.escapeHtml || (s => String(s));
+ window._guiSshCommands = [];
+ const groups = data.InfraNodeGroupAccessInfo || [];
+ let cmdIdx = 0;
+
+ let html = ``;
+ html += `
+
+
+
+
`;
+
+ if (groups.length === 0) {
+ html += `
No access information available.
`;
+ }
+
+ groups.forEach(group => {
+ const gid = group.NodeGroupId || 'default';
+ const nodeList = group.NodeAccessInfo || [];
+ html += `
+
+
+ ๐ฆ NodeGroup: ${esc(gid)} (${nodeList.length} node(s))
+
+
+
+ | Node ID |
+ Public IP |
+ Private IP |
+ User |
+ Port |
+ SSH Command (click to copy) |
+
+ `;
+ nodeList.forEach((nd, i) => {
+ const bg = i % 2 === 0 ? '#0d1b2a' : '#0a1520';
+ const user = nd.nodeUserName || 'ubuntu';
+ const port = nd.sshPort || 22;
+ let sshCmd = '-';
+ if (nd.publicIP) {
+ sshCmd = `ssh -i ${nd.nodeId || 'key'}.pem -p ${port} ${user}@${nd.publicIP}`;
+ } else if (nd.bastionPublicIp) {
+ sshCmd = `ssh -J ${user}@${nd.bastionPublicIp}:${nd.bastionSshPort || 22} -i key.pem -p ${port} ${user}@${nd.privateIP}`;
+ }
+ const thisIdx = cmdIdx++;
+ window._guiSshCommands.push(sshCmd);
+ html += `
+
+ | ${esc(nd.nodeId || '-')} |
+ ${esc(nd.publicIP || '-')} |
+ ${esc(nd.privateIP || '-')} |
+ ${esc(user)} |
+ ${esc(String(port))} |
+
+
+ ${esc(sshCmd)}
+
+ |
+
`;
+ });
+ html += `
`;
+ });
+ html += `
`;
+
+ Swal.fire({
+ title: `๐ Access Info${infraId ? ': ' + esc(infraId) : ''}`,
+ html: html,
+ background: '#0e1746',
+ color: '#e0e0e0',
+ showConfirmButton: false,
+ showCancelButton: true,
+ cancelButtonText: 'โ Close',
+ width: '88%',
+ });
+}
+
+// Rich GUI for infraDynamic provisioning result (replaces raw JSON in proceedWithInfraCreation)
+function displayInfraDynamicResultGui(data) {
+ window._currentJsonOutput = data;
+ window._currentJsonString = JSON.stringify(data, null, 2);
+ const esc = window.escapeHtml || (s => String(s));
+ const infraId = data.id || data.name || 'Infra';
+ const nodes = data.node || [];
+ const runningCount = nodes.filter(nd => (nd.status || '').toLowerCase() === 'running').length;
+
+ Swal.fire({
+ title: `โ
Infra Created: ${esc(infraId)}`,
+ html: `
+
+
+
๐ Infrastructure provisioning completed!
+
+ ${runningCount} / ${nodes.length} node(s) running
+ ${data.description ? ' ยท ' + esc(data.description) : ''}
+
+
+ ${buildInfraNodeSummaryHtml(data)}
+
+
+
+
+
`,
+ background: '#0e1746',
+ color: '#e0e0e0',
+ showConfirmButton: false,
+ showCancelButton: true,
+ cancelButtonText: 'โ Close',
+ width: '90%',
+ });
+}
// Handle Infra without Nodes (preparing, prepared, empty states)
function handleInfraWithoutNodes(infraItem) {
@@ -5035,7 +5515,7 @@ function proceedWithInfraCreation(createInfraReq, url, username, password) {
console.log('Failed to activate control tab:', error);
}
- displayJsonData(res.data, typeInfo);
+ displayInfraDynamicResultGui(res.data);
handleAxiosResponse(res);
updateInfraList();
@@ -11008,7 +11488,7 @@ function statusInfra() {
})
.then((res) => {
console.log("[Status Infra]");
- displayJsonData(res.data, typeInfo);
+ displayInfraStatusGui(res.data);
})
.catch(function (error) {
if (error.response) {
@@ -17769,7 +18249,7 @@ function getAccessInfo() {
},
}).then((res) => {
console.log(res); // for debug
- displayJsonData(res.data, typeInfo);
+ displayAccessInfoGui(res.data, infraid);
});
}
window.getAccessInfo = getAccessInfo;