feat: implement Smart Recommendations gallery with ServiceCard and BulkSelect
- Replace table layout with PatternFly ServiceCard gallery - Add BulkSelect toolbar with select all/page/none options - Implement individual VPA application per workload - Add checkbox selection for each recommendation card - Support bulk apply and preview operations - Improve UX with card-based interface following PatternFly design - Add responsive grid layout for recommendation cards - Implement proper state management for selections
This commit is contained in:
@@ -830,6 +830,178 @@
|
|||||||
background-color: var(--pf-global--success-color--100);
|
background-color: var(--pf-global--success-color--100);
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Smart Recommendations Gallery Styles */
|
||||||
|
.gallery-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-card {
|
||||||
|
background: var(--pf-global--BackgroundColor--100);
|
||||||
|
border: 1px solid var(--pf-global--BorderColor--200);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-card:hover {
|
||||||
|
border-color: var(--pf-global--primary-color--100);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-card.selected {
|
||||||
|
border-color: var(--pf-global--primary-color--100);
|
||||||
|
background: var(--pf-global--primary-color--50);
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-card-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-card-icon {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
background: var(--pf-global--primary-color--100);
|
||||||
|
border-radius: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-right: 16px;
|
||||||
|
color: white;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-card-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--pf-global--Color--100);
|
||||||
|
margin: 0;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-card-checkbox {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-card-body {
|
||||||
|
flex: 1;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-card-description {
|
||||||
|
color: var(--pf-global--Color--200);
|
||||||
|
margin-bottom: 12px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-card-meta {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-card-meta-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--pf-global--Color--300);
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-card-priority {
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.priority-high {
|
||||||
|
background: var(--pf-global--danger-color--100);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.priority-medium {
|
||||||
|
background: var(--pf-global--warning-color--100);
|
||||||
|
color: var(--pf-global--Color--100);
|
||||||
|
}
|
||||||
|
|
||||||
|
.priority-low {
|
||||||
|
background: var(--pf-global--success-color--100);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-card-footer {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-card-footer .openshift-button {
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bulk Select Styles */
|
||||||
|
.bulk-select-toolbar {
|
||||||
|
background: var(--pf-global--BackgroundColor--200);
|
||||||
|
border-bottom: 1px solid var(--pf-global--BorderColor--200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-v6-c-dropdown__menu {
|
||||||
|
background: var(--pf-global--BackgroundColor--100);
|
||||||
|
border: 1px solid var(--pf-global--BorderColor--200);
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-v6-c-dropdown__menu-item {
|
||||||
|
padding: 8px 16px;
|
||||||
|
color: var(--pf-global--Color--100);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-v6-c-dropdown__menu-item:hover {
|
||||||
|
background: var(--pf-global--BackgroundColor--200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-v6-c-dropdown__toggle {
|
||||||
|
background: var(--pf-global--BackgroundColor--100);
|
||||||
|
border: 1px solid var(--pf-global--BorderColor--200);
|
||||||
|
color: var(--pf-global--Color--100);
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-v6-c-dropdown__toggle:hover {
|
||||||
|
border-color: var(--pf-global--primary-color--100);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-v6-c-dropdown__toggle-icon {
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pf-v6-c-dropdown__toggle[aria-expanded="true"] .pf-v6-c-dropdown__toggle-icon {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -1044,24 +1216,79 @@
|
|||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<h1 class="page-title">Smart Recommendations</h1>
|
<h1 class="page-title">Smart Recommendations</h1>
|
||||||
<p class="page-description">AI-powered recommendations for resource optimization and VPA activation</p>
|
<p class="page-description">AI-powered recommendations for resource optimization and VPA activation</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Smart Recommendations Content -->
|
<!-- Smart Recommendations Content -->
|
||||||
<div class="openshift-card">
|
<div class="openshift-card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h2 class="card-title">Recommendations</h2>
|
<h2 class="card-title">VPA Recommendations</h2>
|
||||||
<button class="openshift-button" onclick="loadSmartRecommendations()">
|
<div class="card-actions">
|
||||||
<i class="fas fa-sync-alt"></i>
|
<button class="openshift-button" onclick="loadSmartRecommendations()">
|
||||||
Refresh
|
<i class="fas fa-sync-alt"></i>
|
||||||
|
Refresh
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="table-content" id="smart-recommendations-container">
|
</div>
|
||||||
|
|
||||||
|
<!-- Bulk Select Toolbar -->
|
||||||
|
<div class="bulk-select-toolbar" style="padding: 16px; border-bottom: 1px solid var(--pf-global--BorderColor--200);">
|
||||||
|
<div class="pf-v6-c-toolbar">
|
||||||
|
<div class="pf-v6-c-toolbar__content">
|
||||||
|
<div class="pf-v6-c-toolbar__content-section">
|
||||||
|
<div class="pf-v6-c-toolbar__group pf-m-toggle-group">
|
||||||
|
<div class="pf-v6-c-toolbar__item">
|
||||||
|
<div class="pf-v6-c-dropdown">
|
||||||
|
<button class="pf-v6-c-dropdown__toggle" type="button" id="bulk-select-toggle" aria-expanded="false" onclick="toggleBulkSelect()">
|
||||||
|
<span class="pf-v6-c-dropdown__toggle-text">
|
||||||
|
<span id="bulk-select-text">0 selected</span>
|
||||||
|
</span>
|
||||||
|
<span class="pf-v6-c-dropdown__toggle-icon">
|
||||||
|
<i class="fas fa-chevron-down"></i>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<ul class="pf-v6-c-dropdown__menu" id="bulk-select-menu" style="display: none;">
|
||||||
|
<li>
|
||||||
|
<button class="pf-v6-c-dropdown__menu-item" onclick="selectAllRecommendations()">
|
||||||
|
Select all (<span id="total-recommendations">0</span>)
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button class="pf-v6-c-dropdown__menu-item" onclick="selectPageRecommendations()">
|
||||||
|
Select page (<span id="page-recommendations">0</span>)
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button class="pf-v6-c-dropdown__menu-item" onclick="deselectAllRecommendations()">
|
||||||
|
Select none (0)
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="pf-v6-c-toolbar__item" id="bulk-actions" style="display: none;">
|
||||||
|
<button class="openshift-button openshift-button-primary" onclick="applySelectedRecommendations()">
|
||||||
|
<i class="fas fa-check"></i>
|
||||||
|
Apply Selected (<span id="selected-count">0</span>)
|
||||||
|
</button>
|
||||||
|
<button class="openshift-button" onclick="previewSelectedRecommendations()">
|
||||||
|
<i class="fas fa-eye"></i>
|
||||||
|
Preview Selected
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Gallery of Service Cards -->
|
||||||
|
<div class="gallery-container" id="smart-recommendations-container" style="padding: 20px;">
|
||||||
<div class="loading-spinner">
|
<div class="loading-spinner">
|
||||||
<i class="fas fa-spinner fa-spin"></i>
|
<i class="fas fa-spinner fa-spin"></i>
|
||||||
Loading recommendations...
|
Loading recommendations...
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- VPA Management Section -->
|
<!-- VPA Management Section -->
|
||||||
@@ -1254,6 +1481,7 @@
|
|||||||
|
|
||||||
// Store recommendations globally for button functions
|
// Store recommendations globally for button functions
|
||||||
window.currentRecommendations = data.recommendations || [];
|
window.currentRecommendations = data.recommendations || [];
|
||||||
|
window.selectedRecommendations = new Set();
|
||||||
|
|
||||||
if (!data || !data.recommendations || data.recommendations.length === 0) {
|
if (!data || !data.recommendations || data.recommendations.length === 0) {
|
||||||
container.innerHTML = `
|
container.innerHTML = `
|
||||||
@@ -1261,77 +1489,245 @@
|
|||||||
<i class="fas fa-lightbulb" style="font-size: 48px; margin-bottom: 16px; color: var(--pf-global--Color--400);"></i>
|
<i class="fas fa-lightbulb" style="font-size: 48px; margin-bottom: 16px; color: var(--pf-global--Color--400);"></i>
|
||||||
<h3>No Recommendations Available</h3>
|
<h3>No Recommendations Available</h3>
|
||||||
<p>No smart recommendations found for the current cluster state.</p>
|
<p>No smart recommendations found for the current cluster state.</p>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
updateBulkSelectUI();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const recommendationsHtml = data.recommendations.map(rec => `
|
// Update bulk select counters
|
||||||
<div class="openshift-card" style="margin-bottom: 16px;">
|
document.getElementById('total-recommendations').textContent = data.recommendations.length;
|
||||||
<div class="card-header">
|
document.getElementById('page-recommendations').textContent = data.recommendations.length;
|
||||||
<h3 class="card-title">
|
|
||||||
<i class="fas fa-${getRecommendationIcon(rec.recommendation_type)}" style="margin-right: 8px; color: ${getPriorityColor(rec.priority)};"></i>
|
const recommendationsHtml = data.recommendations.map((rec, index) => `
|
||||||
${rec.title}
|
<div class="service-card" id="recommendation-${index}" data-recommendation-id="${index}">
|
||||||
</h3>
|
<div class="service-card-header">
|
||||||
<span class="priority-badge priority-${rec.priority}">${rec.priority.toUpperCase()}</span>
|
<div class="service-card-icon">
|
||||||
</div>
|
<i class="fas fa-${getRecommendationIcon(rec.recommendation_type)}"></i>
|
||||||
<div style="padding: 20px;">
|
|
||||||
<p style="margin-bottom: 16px; color: var(--pf-global--Color--200);">${rec.description}</p>
|
|
||||||
|
|
||||||
${rec.workload_list && rec.workload_list.length > 0 ? `
|
|
||||||
<div style="margin-bottom: 16px;">
|
|
||||||
<strong style="color: var(--pf-global--Color--100);">Affected Workloads:</strong>
|
|
||||||
<ul style="margin: 8px 0 0 20px; color: var(--pf-global--Color--200);">
|
|
||||||
${rec.workload_list.map(workload => {
|
|
||||||
// Extract priority from workload string (e.g., "example (shishika01) - HIGH")
|
|
||||||
const priorityMatch = workload.match(/\s-\s(\w+)$/);
|
|
||||||
const priority = priorityMatch ? priorityMatch[1].toLowerCase() : 'low';
|
|
||||||
const priorityColor = getPriorityColor(priority);
|
|
||||||
return `<li style="color: ${priorityColor}; font-weight: 500;">${workload}</li>`;
|
|
||||||
}).join('')}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
` : ''}
|
|
||||||
|
|
||||||
${rec.implementation_steps && rec.implementation_steps.length > 0 ? `
|
|
||||||
<div style="margin-bottom: 16px;">
|
|
||||||
<strong style="color: var(--pf-global--Color--100);">Implementation Steps:</strong>
|
|
||||||
<ol style="margin: 8px 0 0 20px; color: var(--pf-global--Color--200);">
|
|
||||||
${rec.implementation_steps.map(step => `<li>${step}</li>`).join('')}
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
` : ''}
|
|
||||||
|
|
||||||
<div style="display: flex; gap: 12px; margin-top: 20px; flex-wrap: wrap;">
|
|
||||||
${rec.vpa_yaml ? `
|
|
||||||
<button class="openshift-button openshift-button-primary" onclick="downloadVPAYAML('${rec.workload_name}', '${rec.namespace}')">
|
|
||||||
<i class="fas fa-download"></i>
|
|
||||||
Generate VPA YAML
|
|
||||||
</button>
|
|
||||||
` : ''}
|
|
||||||
|
|
||||||
${rec.kubectl_commands && rec.kubectl_commands.length > 0 ? `
|
|
||||||
<button class="openshift-button" onclick="showOpenShiftCommands('${rec.workload_name}', '${rec.namespace}')">
|
|
||||||
<i class="fas fa-terminal"></i>
|
|
||||||
OpenShift Commands
|
|
||||||
</button>
|
|
||||||
` : ''}
|
|
||||||
|
|
||||||
<button class="openshift-button openshift-button-success" onclick="applySmartRecommendation('${rec.workload_name}', '${rec.namespace}', '${rec.recommendation_type}', '${rec.priority}')">
|
|
||||||
<i class="fas fa-check"></i>
|
|
||||||
Apply Recommendation
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button class="openshift-button" onclick="previewSmartRecommendation('${rec.workload_name}', '${rec.namespace}', '${rec.recommendation_type}', '${rec.priority}')">
|
|
||||||
<i class="fas fa-eye"></i>
|
|
||||||
Preview Changes
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
<h3 class="service-card-title">${rec.title}</h3>
|
||||||
|
<div class="service-card-checkbox">
|
||||||
|
<input type="checkbox"
|
||||||
|
id="checkbox-${index}"
|
||||||
|
class="recommendation-checkbox"
|
||||||
|
onchange="toggleRecommendationSelection(${index})"
|
||||||
|
style="transform: scale(1.2);">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="service-card-body">
|
||||||
|
<p class="service-card-description">${rec.description}</p>
|
||||||
|
|
||||||
|
<div class="service-card-meta">
|
||||||
|
<div class="service-card-meta-item">
|
||||||
|
<i class="fas fa-cube"></i>
|
||||||
|
<span>${rec.workload_name || 'N/A'}</span>
|
||||||
|
</div>
|
||||||
|
<div class="service-card-meta-item">
|
||||||
|
<i class="fas fa-layer-group"></i>
|
||||||
|
<span>${rec.namespace || 'N/A'}</span>
|
||||||
|
</div>
|
||||||
|
<div class="service-card-meta-item">
|
||||||
|
<i class="fas fa-tag"></i>
|
||||||
|
<span>${rec.recommendation_type}</span>
|
||||||
|
</div>
|
||||||
|
<div class="service-card-priority priority-${rec.priority}">
|
||||||
|
${rec.priority.toUpperCase()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="service-card-footer">
|
||||||
|
${rec.vpa_yaml ? `
|
||||||
|
<button class="openshift-button openshift-button-primary" onclick="downloadVPAYAML('${rec.workload_name}', '${rec.namespace}')">
|
||||||
|
<i class="fas fa-download"></i>
|
||||||
|
VPA YAML
|
||||||
|
</button>
|
||||||
|
` : ''}
|
||||||
|
|
||||||
|
<button class="openshift-button openshift-button-success" onclick="applySmartRecommendation('${rec.workload_name}', '${rec.namespace}', '${rec.recommendation_type}', '${rec.priority}')">
|
||||||
|
<i class="fas fa-check"></i>
|
||||||
|
Apply
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button class="openshift-button" onclick="previewSmartRecommendation('${rec.workload_name}', '${rec.namespace}', '${rec.recommendation_type}', '${rec.priority}')">
|
||||||
|
<i class="fas fa-eye"></i>
|
||||||
|
Preview
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`).join('');
|
`).join('');
|
||||||
|
|
||||||
container.innerHTML = recommendationsHtml;
|
container.innerHTML = recommendationsHtml;
|
||||||
|
updateBulkSelectUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bulk Select Functions
|
||||||
|
function toggleBulkSelect() {
|
||||||
|
const menu = document.getElementById('bulk-select-menu');
|
||||||
|
const toggle = document.getElementById('bulk-select-toggle');
|
||||||
|
const isOpen = menu.style.display !== 'none';
|
||||||
|
|
||||||
|
menu.style.display = isOpen ? 'none' : 'block';
|
||||||
|
toggle.setAttribute('aria-expanded', !isOpen);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleRecommendationSelection(index) {
|
||||||
|
const checkbox = document.getElementById(`checkbox-${index}`);
|
||||||
|
const card = document.getElementById(`recommendation-${index}`);
|
||||||
|
|
||||||
|
if (checkbox.checked) {
|
||||||
|
window.selectedRecommendations.add(index);
|
||||||
|
card.classList.add('selected');
|
||||||
|
} else {
|
||||||
|
window.selectedRecommendations.delete(index);
|
||||||
|
card.classList.remove('selected');
|
||||||
|
}
|
||||||
|
|
||||||
|
updateBulkSelectUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectAllRecommendations() {
|
||||||
|
const checkboxes = document.querySelectorAll('.recommendation-checkbox');
|
||||||
|
checkboxes.forEach((checkbox, index) => {
|
||||||
|
checkbox.checked = true;
|
||||||
|
window.selectedRecommendations.add(index);
|
||||||
|
document.getElementById(`recommendation-${index}`).classList.add('selected');
|
||||||
|
});
|
||||||
|
updateBulkSelectUI();
|
||||||
|
toggleBulkSelect();
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectPageRecommendations() {
|
||||||
|
const checkboxes = document.querySelectorAll('.recommendation-checkbox');
|
||||||
|
checkboxes.forEach((checkbox, index) => {
|
||||||
|
checkbox.checked = true;
|
||||||
|
window.selectedRecommendations.add(index);
|
||||||
|
document.getElementById(`recommendation-${index}`).classList.add('selected');
|
||||||
|
});
|
||||||
|
updateBulkSelectUI();
|
||||||
|
toggleBulkSelect();
|
||||||
|
}
|
||||||
|
|
||||||
|
function deselectAllRecommendations() {
|
||||||
|
const checkboxes = document.querySelectorAll('.recommendation-checkbox');
|
||||||
|
checkboxes.forEach((checkbox, index) => {
|
||||||
|
checkbox.checked = false;
|
||||||
|
window.selectedRecommendations.delete(index);
|
||||||
|
document.getElementById(`recommendation-${index}`).classList.remove('selected');
|
||||||
|
});
|
||||||
|
updateBulkSelectUI();
|
||||||
|
toggleBulkSelect();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateBulkSelectUI() {
|
||||||
|
const selectedCount = window.selectedRecommendations ? window.selectedRecommendations.size : 0;
|
||||||
|
const totalCount = window.currentRecommendations ? window.currentRecommendations.length : 0;
|
||||||
|
|
||||||
|
document.getElementById('bulk-select-text').textContent = `${selectedCount} selected`;
|
||||||
|
document.getElementById('selected-count').textContent = selectedCount;
|
||||||
|
|
||||||
|
const bulkActions = document.getElementById('bulk-actions');
|
||||||
|
if (selectedCount > 0) {
|
||||||
|
bulkActions.style.display = 'flex';
|
||||||
|
} else {
|
||||||
|
bulkActions.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function applySelectedRecommendations() {
|
||||||
|
if (!window.selectedRecommendations || window.selectedRecommendations.size === 0) {
|
||||||
|
alert('No recommendations selected');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedIndices = Array.from(window.selectedRecommendations);
|
||||||
|
const selectedRecommendations = selectedIndices.map(index => window.currentRecommendations[index]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
showLoading('smart-recommendations-container');
|
||||||
|
|
||||||
|
const results = [];
|
||||||
|
for (const rec of selectedRecommendations) {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/v1/recommendations/apply', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
...rec,
|
||||||
|
dry_run: false
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const result = await response.json();
|
||||||
|
results.push({ success: true, recommendation: rec.title, result });
|
||||||
|
} else {
|
||||||
|
results.push({ success: false, recommendation: rec.title, error: 'Failed to apply' });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
results.push({ success: false, recommendation: rec.title, error: error.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show results
|
||||||
|
const successCount = results.filter(r => r.success).length;
|
||||||
|
const failCount = results.filter(r => !r.success).length;
|
||||||
|
|
||||||
|
alert(`Applied ${successCount} recommendations successfully. ${failCount} failed.`);
|
||||||
|
|
||||||
|
// Refresh recommendations
|
||||||
|
loadSmartRecommendations();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error applying selected recommendations:', error);
|
||||||
|
alert('Error applying recommendations: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function previewSelectedRecommendations() {
|
||||||
|
if (!window.selectedRecommendations || window.selectedRecommendations.size === 0) {
|
||||||
|
alert('No recommendations selected');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedIndices = Array.from(window.selectedRecommendations);
|
||||||
|
const selectedRecommendations = selectedIndices.map(index => window.currentRecommendations[index]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const results = [];
|
||||||
|
for (const rec of selectedRecommendations) {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/v1/recommendations/apply', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
...rec,
|
||||||
|
dry_run: true
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const result = await response.json();
|
||||||
|
results.push({ success: true, recommendation: rec, result });
|
||||||
|
} else {
|
||||||
|
results.push({ success: false, recommendation: rec, error: 'Failed to preview' });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
results.push({ success: false, recommendation: rec, error: error.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showRecommendationPreview(results);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error previewing selected recommendations:', error);
|
||||||
|
alert('Error previewing recommendations: ' + error.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load VPA management
|
// Load VPA management
|
||||||
|
|||||||
Reference in New Issue
Block a user