diff --git a/src/Server/Views/Home/Index.cshtml b/src/Server/Views/Home/Index.cshtml index 6afa4eb..d2af315 100644 --- a/src/Server/Views/Home/Index.cshtml +++ b/src/Server/Views/Home/Index.cshtml @@ -85,7 +85,6 @@
@T["Database size"]: 0.00MiB
- @@ -744,7 +743,7 @@ "attributes": attributes, "datapoints": datapoints }]; - // TODO add throbber to indicate loading + showToast("@T["Creating entity"]", "primary"); fetch(`/Entity/Index`, { method: 'POST', headers: { @@ -754,14 +753,15 @@ }).then(async response => { result = await response.json(); if (response.ok && result.Success) { - // TODO add toast + showToast("@T["Entity was created successfully"]", "success"); console.log('Entity was created successfully'); selectDomain(getSelectedDomainKey()); } else { - // TODO add toast + showToast("@T["Failed to create entity"]", "danger"); console.error('Failed to create entity:', result.Message); } }).catch(error => { + showToast("@T["Failed to create entity"]", "danger"); console.error('Error creating entity:', error); }); @@ -781,15 +781,16 @@ method: 'GET' }).then(response => { if (response.ok) { - // TODO add toast + showToast("@T["Searchdomain was created successfully"]", "success"); console.log('Searchdomain created successfully'); // Reload the page to show the new searchdomain location.reload(); } else { - // TODO add toast + showToast("@T["Failed to create searchdomain"]", "danger"); console.error('Failed to create searchdomain'); } }).catch(error => { + showToast("@T["Failed to create searchdomain"]", "danger"); console.error('Error creating searchdomain:', error); }); }); @@ -802,15 +803,16 @@ method: 'GET' }).then(response => { if (response.ok) { - // TODO add toast + showToast("@T["Searchdomain cache was cleared successfully"]", "success"); console.log('Searchdomain cache cleared successfully'); // Update cache utilization display document.querySelector('#cacheUtilization').innerText = '0.00MiB'; } else { - // TODO add toast + showToast("@T["Failed to clear searchdomain cache"]", "danger"); console.error('Failed to clear searchdomain cache'); } }).catch(error => { + showToast("@T["Failed to clear searchdomain cache"]", "danger"); console.error('Error clearing searchdomain cache:', error); }); }); @@ -825,11 +827,11 @@ }).then(async response => { let result = await response.json(); if (response.ok && result.Success) { - // TODO add toast + showToast("@T["Entity was deleted successfully"]", "success"); console.log('Entity deleted successfully'); selectDomain(getSelectedDomainKey()); } else { - // TODO add toast + showToast("@T["Failed to delete entity"]", "danger"); console.error('Failed to delete entity:', result.Message); } }).catch(error => { @@ -865,8 +867,7 @@ "attributes": attributes, "datapoints": datapoints }]; - console.log(data); - // TODO add throbber to indicate loading + showToast("@T["Updating entity"]", "primary"); fetch(`/Entity/Index`, { method: 'POST', headers: { @@ -876,15 +877,16 @@ }).then(async response => { result = await response.json(); if (response.ok && result.Success) { - // TODO add toast - console.log('Entity was created successfully'); + showToast("@T["Entity was updated successfully"]", "success"); + console.log('Entity was updated successfully'); selectDomain(getSelectedDomainKey()); } else { - // TODO add toast - console.error('Failed to create entity:', result.Message); + showToast("@T["Failed to update entity"]", "danger"); + console.error('Failed to update entity:', result.Message); } }).catch(error => { - console.error('Error creating entity:', error); + showToast("@T["Failed to update entity"]", "danger"); + console.error('Error update entity:', error); }); }); @@ -901,15 +903,16 @@ }).then(async response => { result = await response.json(); if (response.ok && result.Success) { - // TODO add toast + showToast("@T["Search query was deleted successfully"]", "success"); console.log('Search query was deleted successfully'); selectDomain(getSelectedDomainKey()); } else { - // TODO add toast - console.error('Failed to delete query:', result.Message); + showToast("@T["Failed to delete search query"]", "danger"); + console.error('Failed to delete search query:', result.Message); } }).catch(error => { - console.error('Error creating entity:', error); + showToast("@T["Failed to delete search query"]", "danger"); + console.error('Failed to delete search query:', error); }); }); document @@ -928,15 +931,16 @@ }).then(async response => { result = await response.json(); if (response.ok && result.Success) { - // TODO add toast - console.log('Search query was deleted successfully'); + showToast("@T["Searchdomain was created successfully"]", "success"); + console.log('Search query was updated successfully'); selectDomain(getSelectedDomainKey()); } else { - // TODO add toast - console.error('Failed to delete query:', result.Message); + showToast("@T["Updating search query failed"]", "danger"); + console.error('Updating search query failed:', result.Message); } }).catch(error => { - console.error('Error creating entity:', error); + showToast("@T["Updating search query failed"]", "danger"); + console.error('Updating search query failed:', error); }); }); }); @@ -945,18 +949,20 @@ // Implement delete logic here fetch(`/Searchdomain/Delete?searchdomain=${encodeURI(domains[domainKey])}`, { method: 'GET' - }).then(response => { - if (response.ok) { - // TODO add toast + }).then(async response => { + var result = await response.json();; + if (response.ok && result.Success === true) { + showToast("@T["Searchdomain was deleted successfully"]", "success"); // Remove from sidebar var domainItem = document.getElementById('sidebar_domain_' + domainKey); domainItem.remove(); console.log('Searchdomain deleted successfully'); } else { - // TODO add toast - console.error('Failed to delete searchdomain'); + showToast("@T["Failed to delete searchdomain"]", "danger"); + console.error('Failed to delete searchdomain:', result.Message); } }).catch(error => { + showToast("@T["Failed to delete searchdomain"]", "danger"); console.error('Error deleting searchdomain:', error); }); } @@ -965,9 +971,10 @@ // Implement rename logic here fetch(`/Searchdomain/Update?searchdomain=${encodeURI(domains[domainKey])}&newName=${newName}`, { method: 'GET' - }).then(response => { - if (response.ok) { - // TODO add toast + }).then(async response => { + var result = await response.json(); + if (response.ok && result.Success === true) { + showToast("@T["Searchdomain was renamed successfully"]", "success"); // Update sidebar and header name var domainItem = document.getElementById('sidebar_domain_' + domainKey); domainItem.innerText = newName; @@ -976,11 +983,12 @@ console.log('Searchdomain renamed successfully'); } else { - // TODO add toast - console.error('Failed to rename searchdomain'); + showToast("@T["Failed to rename searchdomain"]", "danger"); + console.error('Failed to rename searchdomain:', result.Message); } }).catch(error => { - console.error('Error renaming searchdomain:', error); + showToast("@T["Failed to rename searchdomain"]", "danger"); + console.error('Failed to rename searchdomain:', error); }); } @@ -992,15 +1000,17 @@ 'Content-Type': 'application/json' }, body: JSON.stringify(newSettings) - }).then(response => { - if (response.ok) { - // TODO add toast + }).then(async response => { + var result = await response.json(); + if (response.ok && result.Success === true) { + showToast("@T["Searchdomain settings were updated successfully"]", "success"); console.log('Searchdomain settings updated successfully'); } else { - // TODO add toast + showToast("@T["Updating searchdomain settings failed"]", "danger"); console.error('Failed to update searchdomain settings'); } }).catch(error => { + showToast("@T["Updating searchdomain settings failed"]", "danger"); console.error('Error updating searchdomain settings:', error); }); } @@ -1045,37 +1055,37 @@ let entitiesUrl = `/Entity/List?searchdomain=${encodeURIComponent(domainName)}&returnEmbeddings=false&returnModels=true`; let entitiesCard = document.querySelector("#entitiesTable").parentElement; clearEntitiesTable(); - showEntitiesLoading(entitiesCard); + showThrobber(entitiesCard); fetch(entitiesUrl) .then(r => r.json()) .then(data => { entities = data.Results; populateEntitiesTable(); - hideEntitiesLoading(entitiesCard); + hideThrobber(entitiesCard); }) .catch(err => { console.error(err); flagSearchdomainAsErroneous(domainKey); - hideEntitiesLoading(entitiesCard); + hideThrobber(entitiesCard); }); /* ---------- QUERIES ---------- */ let queriesUrl = `/Searchdomain/GetSearches?searchdomain=${encodeURIComponent(domainName)}`; let queriesCard = document.querySelector("#queriesTable").parentElement; clearQueriesTable(); - showQueriesLoading(queriesCard); + showThrobber(queriesCard); fetch(queriesUrl) .then(r => r.json()) .then(data => { queries = Object.entries(data.Searches).map(key => ({"Name": key[0], "AccessDateTimes": key[1].AccessDateTimes, "Results": key[1].Results})); populateQueriesTable(); - hideQueriesLoading(queriesCard); + hideThrobber(queriesCard); }) .catch(err => { console.error('Error fetching queries:', err); - hideQueriesLoading(queriesCard); + hideThrobber(queriesCard); }); searchdomainConfigPromise.then(searchdomainConfig => { @@ -1085,9 +1095,8 @@ configElementCachereconciliation.checked = searchdomainConfig.Settings.CacheReconciliation; configElementCachereconciliation.disabled = false; } else { - //configElement.value = 'Error fetching searchdomain config'; configElementCachereconciliation.disabled = true; - // TODO add toast + showToast("@T["Unable to fetch searchdomain config"]", "danger"); console.error('Failed to fetch searchdomain config'); } }); @@ -1097,7 +1106,7 @@ document.querySelector('#cacheUtilization').innerText = `${NumberOfBytesAsHumanReadable(cacheUtilization.SearchCacheSizeBytes)}`; } else { - // TODO add toast + showToast("@T["Unable to fetch searchdomain cache utilization"]", "danger"); console.error('Failed to fetch searchdomain cache utilization'); } }); @@ -1108,7 +1117,7 @@ document.querySelector('#databaseUtilization').innerText = `${NumberOfBytesAsHumanReadable(databaseUtilization.SearchdomainDatabaseSizeBytes)}`; } else { - // TODO add toast + showToast("@T["Unable to fetch searchdomain database utilization"]", "danger"); console.error('Failed to fetch searchdomain database utilization'); } }); @@ -1275,26 +1284,16 @@ domainItem.classList.add('list-group-item-danger'); } - function showEntitiesLoading(element = null) { + function showThrobber(element = null) { if (element == null) element = document; element.querySelector('.spinner').classList.remove('d-none'); } - function hideEntitiesLoading(element = null) { + function hideThrobber(element = null) { if (element == null) element = document; element.querySelector('.spinner').classList.add('d-none'); } - function showQueriesLoading(element = null) { - if (!element) element = document; - element.querySelector('.spinner').classList.remove('d-none'); - } - - function hideQueriesLoading(element = null) { - if (!element) element = document; - element.querySelector('.spinner').classList.add('d-none'); - } - function showEntityDetails(entity) { // Title document.getElementById('entityDetailsTitle').innerText = entity.Name; diff --git a/src/Server/Views/Shared/_Layout.cshtml b/src/Server/Views/Shared/_Layout.cshtml index 7b398f8..5a7f940 100644 --- a/src/Server/Views/Shared/_Layout.cshtml +++ b/src/Server/Views/Shared/_Layout.cshtml @@ -1,4 +1,7 @@ - +@using Server.Services +@inject LocalizationService T + + @@ -6,6 +9,11 @@ @ViewData["Title"] - embeddingsearch +
diff --git a/src/Server/wwwroot/js/site.js b/src/Server/wwwroot/js/site.js index dcc7262..12425ec 100644 --- a/src/Server/wwwroot/js/site.js +++ b/src/Server/wwwroot/js/site.js @@ -2,3 +2,50 @@ // for details on configuring this project to bundle and minify static web assets. // Write your JavaScript code. +function createToastContainer() { + const container = document.createElement('div'); + container.id = 'toastContainer'; + container.className = 'toast-container position-fixed bottom-0 end-0 p-3'; + container.setAttribute("aria-live", "polite"); + container.setAttribute("aria-atomic", "true"); + + const liveRegion = document.createElement('div'); + liveRegion.id = 'toastLiveRegion'; + liveRegion.className = 'visually-hidden'; + liveRegion.setAttribute('aria-live', 'assertive'); + liveRegion.setAttribute('aria-atomic', 'true'); + container.appendChild(liveRegion); + + document.body.appendChild(container); + return container; +} + +// Simple toast helper +function showToast(message, type) { + const toastContainer = document.getElementById('toastContainer') || createToastContainer(); + const toast = document.createElement('div'); + toast.className = `toast align-items-center text-white bg-${type} border-0`; + toast.role = 'alert'; + var useDarkElements = type === "warning" + toast.innerHTML = ` +
+
${message}
+ +
+ `; + if (useDarkElements) { + toast.classList.remove("text-white"); + toast.classList.add("text-dark"); + } + toastContainer.appendChild(toast); + + const liveRegion = document.getElementById('toastLiveRegion'); + if (liveRegion) { + liveRegion.textContent = ''; + setTimeout(() => liveRegion.textContent = message, 500); + } + + const bsToast = new bootstrap.Toast(toast, { delay: 10000 }); + bsToast.show(); + toast.addEventListener('hidden.bs.toast', () => toast.remove()); +} \ No newline at end of file