add delete profile
This commit is contained in:
@@ -669,7 +669,7 @@ async function refresh() {
|
||||
{label:'Common denominators', render:r => esc((r.common_fields || []).slice(0,5).map(item => `${item.field} ${(item.coverage*100).toFixed(0)}%`).join(', ') || '-')},
|
||||
{label:'Shared fields', render:r => esc((r.shared_fields || []).slice(0,4).map(sharedFieldLabel).join(', ') || '-')},
|
||||
{label:'Discovery', render:r => profileDiscoveryDetails(r)},
|
||||
{label:'Action', render:r => `<button type="button" class="apply-suggested-profile" data-stream-id="${esc(r.stream_id)}">${r.profile_exists ? 'Update profile' : 'Apply profile'}</button>`}
|
||||
{label:'Action', render:r => `<button type="button" class="apply-suggested-profile" data-stream-id="${esc(r.stream_id)}">${r.profile_exists ? 'Update profile' : 'Apply profile'}</button>${r.profile_exists ? ` <button type="button" class="delete-stream-profile" data-stream-id="${esc(r.stream_id)}" data-stream-name="${esc(r.stream_name || r.stream_id)}">Delete profile</button>` : ''}`}
|
||||
], 'profile-suggestions') : '<p class="muted">No missing stream profiles. Enable "show existing profiles" to inspect already configured profiles.</p>';
|
||||
const llmText = llm.text ? esc(llm.text).replace(/\\n/g, '<br>') : esc(llm.error || 'LLM assessment disabled or waiting for first run.');
|
||||
document.getElementById('llmAssessment').innerHTML = `<div class="ai-box"><details><summary>Status: ${esc(llm.status || (llmEnabled ? 'starting' : 'disabled'))}</summary><p>${llmText}</p></details></div>`;
|
||||
@@ -843,7 +843,6 @@ async function refresh() {
|
||||
'<h3>Local-in Failures</h3>' + table(d.local_in_failures || [], [{label:'Source', key:'src_ip', render:r => entityCell(r, 'src_ip')}, {label:'Service', key:'service'}, {label:'Policy', key:'policy'}, {label:'Count', key:'count'}]);
|
||||
document.querySelectorAll('details[data-detail-id]').forEach(item => { if (openDetails.has(item.dataset.detailId)) item.open = true; });
|
||||
document.querySelectorAll('[data-sort-table]').forEach(button => button.addEventListener('click', () => { const current=tableSort[button.dataset.sortTable]; tableSort[button.dataset.sortTable]={key:button.dataset.sortKey,direction:current && current.key===button.dataset.sortKey ? -current.direction : 1}; refresh(); }));
|
||||
document.querySelectorAll('.apply-suggested-profile').forEach(button => button.addEventListener('click', () => applySuggestedProfile(button.dataset.streamId)));
|
||||
} catch (error) {
|
||||
showRefreshError(error);
|
||||
}
|
||||
@@ -945,7 +944,8 @@ function renderStreamPicker(errorText='') {
|
||||
<div class="stream-counts">${esc(enabledCount)} enabled, ${esc(streams.length)} visible, ${esc(window.availableStreams.length)} total</div>
|
||||
<div class="stream-list">${streams.map(stream => {
|
||||
const selected = window.streamSelection[stream.id] || {id:stream.id, title:stream.title, enabled:false};
|
||||
return `<div class="stream-row" data-stream-row="${esc(stream.id)}"><label title="${esc(stream.title || stream.id)}"><input type="checkbox" class="graylog-stream" data-id="${esc(stream.id)}" data-title="${esc(stream.title)}" ${selected.enabled ? 'checked' : ''}> <span class="stream-title">${esc(stream.title || stream.id)}</span></label><button type="button" class="edit-profile" data-id="${esc(stream.id)}" data-title="${esc(stream.title)}">Edit profile</button></div>`;
|
||||
const hasProfile = (window.streamProfiles || []).some(profile => String(profile.stream_id) === String(stream.id));
|
||||
return `<div class="stream-row" data-stream-row="${esc(stream.id)}"><label title="${esc(stream.title || stream.id)}"><input type="checkbox" class="graylog-stream" data-id="${esc(stream.id)}" data-title="${esc(stream.title)}" ${selected.enabled ? 'checked' : ''}> <span class="stream-title">${esc(stream.title || stream.id)}</span></label><button type="button" class="edit-profile" data-id="${esc(stream.id)}" data-title="${esc(stream.title)}">Edit profile</button>${hasProfile ? `<button type="button" class="delete-stream-profile" data-stream-id="${esc(stream.id)}" data-stream-name="${esc(stream.title || stream.id)}">Delete profile</button>` : ''}</div>`;
|
||||
}).join('')}</div>`;
|
||||
document.getElementById('streamFilter').addEventListener('input', () => renderStreamPicker());
|
||||
document.getElementById('streamEnabledOnly').addEventListener('change', () => renderStreamPicker());
|
||||
@@ -1043,6 +1043,32 @@ async function applySuggestedProfile(streamId) {
|
||||
if (button) button.disabled = false;
|
||||
}
|
||||
}
|
||||
async function deleteStreamProfile(streamId, streamName='') {
|
||||
const notice = document.getElementById('profileApplyResult') || document.getElementById('settingsResult');
|
||||
const title = streamName || streamId;
|
||||
if (!confirm(`Delete profile for ${title}? Baseline data is not deleted, but this stream will stop using the profile until you apply or create a new one.`)) return;
|
||||
try {
|
||||
if (notice) notice.textContent = `Deleting profile for ${title}...`;
|
||||
const configResponse = await fetch('/api/config', {cache: 'no-store'});
|
||||
const config = await configResponse.json();
|
||||
const profiles = (config.graylog_stream_profiles || []).filter(item => String(item.stream_id) !== String(streamId));
|
||||
const response = await fetch('/api/config', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({graylog_stream_profiles: profiles})});
|
||||
let result = {};
|
||||
try { result = await response.json(); } catch {}
|
||||
if (!response.ok) throw new Error(result.error || response.statusText || response.status);
|
||||
window.streamProfiles = result.graylog_stream_profiles || profiles;
|
||||
if (window.activeProfileStream === streamId) {
|
||||
window.activeProfileStream = '';
|
||||
window.activeProfileTitle = '';
|
||||
document.getElementById('profileEditorStatus').textContent = 'Profile deleted. Select a stream to edit or apply a recommended profile.';
|
||||
document.getElementById('fieldPicker').textContent = 'No profile selected for editing.';
|
||||
}
|
||||
if (notice) notice.textContent = `Deleted profile for ${title}. Monitor will stop using it on the next cycle.`;
|
||||
loadSettings();
|
||||
} catch (error) {
|
||||
if (notice) notice.textContent = `Could not delete profile: ${error}`;
|
||||
}
|
||||
}
|
||||
async function editStreamProfile(streamId, title) {
|
||||
document.getElementById('profileEditorStatus').innerHTML = `Editing profile for <code>${esc(title || streamId)}</code>`;
|
||||
document.getElementById('fieldPicker').textContent = 'Loading stream fields...';
|
||||
@@ -1059,10 +1085,25 @@ async function editStreamProfile(streamId, title) {
|
||||
window.activeProfileTitle = title || streamId;
|
||||
}
|
||||
document.getElementById('streamPicker').addEventListener('click', event => {
|
||||
const deleteButton = event.target.closest('.delete-stream-profile');
|
||||
if (deleteButton) {
|
||||
deleteStreamProfile(deleteButton.dataset.streamId, deleteButton.dataset.streamName);
|
||||
return;
|
||||
}
|
||||
const button = event.target.closest('.edit-profile');
|
||||
if (!button) return;
|
||||
editStreamProfile(button.dataset.id, button.dataset.title);
|
||||
});
|
||||
document.getElementById('profileSuggestions').addEventListener('click', event => {
|
||||
const deleteButton = event.target.closest('.delete-stream-profile');
|
||||
if (deleteButton) {
|
||||
deleteStreamProfile(deleteButton.dataset.streamId, deleteButton.dataset.streamName);
|
||||
return;
|
||||
}
|
||||
const applyButton = event.target.closest('.apply-suggested-profile');
|
||||
if (!applyButton) return;
|
||||
applySuggestedProfile(applyButton.dataset.streamId);
|
||||
});
|
||||
document.getElementById('streamPicker').addEventListener('change', event => {
|
||||
const checkbox = event.target.closest('.graylog-stream');
|
||||
if (!checkbox || !window.streamSelection[checkbox.dataset.id]) return;
|
||||
|
||||
@@ -34,6 +34,11 @@ class DashboardTests(unittest.TestCase):
|
||||
self.assertIn("Update profile", HTML)
|
||||
self.assertIn("function mergeProfile", HTML)
|
||||
|
||||
def test_dashboard_can_delete_stream_profiles(self):
|
||||
self.assertIn("Delete profile", HTML)
|
||||
self.assertIn("function deleteStreamProfile", HTML)
|
||||
self.assertIn("delete-stream-profile", HTML)
|
||||
|
||||
def test_dashboard_has_direct_stream_selection_save(self):
|
||||
self.assertIn("Save streams", HTML)
|
||||
self.assertIn("function saveStreamSelection", HTML)
|
||||
|
||||
Reference in New Issue
Block a user