Merge pull request #39 from LD-Reborn/28-create-a-front-end---recent-queries
28 create a front end recent queries
This commit is contained in:
@@ -1,6 +1,8 @@
|
|||||||
|
using System.Text.Json;
|
||||||
using ElmahCore;
|
using ElmahCore;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Server.Exceptions;
|
using Server.Exceptions;
|
||||||
|
using Server.Helper;
|
||||||
using Shared.Models;
|
using Shared.Models;
|
||||||
|
|
||||||
namespace Server.Controllers;
|
namespace Server.Controllers;
|
||||||
@@ -98,14 +100,39 @@ public class SearchdomainController : ControllerBase
|
|||||||
{
|
{
|
||||||
_logger.LogError("Unable to update searchdomain {searchdomain} - not found", [searchdomain]);
|
_logger.LogError("Unable to update searchdomain {searchdomain} - not found", [searchdomain]);
|
||||||
return Ok(new SearchdomainUpdateResults() { Success = false, Message = $"Unable to update searchdomain {searchdomain} - not found" });
|
return Ok(new SearchdomainUpdateResults() { Success = false, Message = $"Unable to update searchdomain {searchdomain} - not found" });
|
||||||
} catch (Exception)
|
} catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError("Unable to update searchdomain {searchdomain}", [searchdomain]);
|
_logger.LogError("Unable to update searchdomain {searchdomain} - Exception: {ex.Message} - {ex.StackTrace}", [searchdomain, ex.Message, ex.StackTrace]);
|
||||||
return Ok(new SearchdomainUpdateResults() { Success = false, Message = $"Unable to update searchdomain {searchdomain}" });
|
return Ok(new SearchdomainUpdateResults() { Success = false, Message = $"Unable to update searchdomain {searchdomain}" });
|
||||||
}
|
}
|
||||||
return Ok(new SearchdomainUpdateResults(){Success = true});
|
return Ok(new SearchdomainUpdateResults(){Success = true});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("UpdateSettings")]
|
||||||
|
public ActionResult<SearchdomainUpdateResults> UpdateSettings(string searchdomain, [FromBody] SearchdomainSettings request)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Searchdomain searchdomain_ = _domainManager.GetSearchdomain(searchdomain);
|
||||||
|
Dictionary<string, dynamic> parameters = new()
|
||||||
|
{
|
||||||
|
{"settings", JsonSerializer.Serialize(request)},
|
||||||
|
{"id", searchdomain_.id}
|
||||||
|
};
|
||||||
|
searchdomain_.helper.ExecuteSQLNonQuery("UPDATE searchdomain set settings = @settings WHERE id = @id", parameters);
|
||||||
|
searchdomain_.settings = request;
|
||||||
|
} catch (SearchdomainNotFoundException)
|
||||||
|
{
|
||||||
|
_logger.LogError("Unable to update settings for searchdomain {searchdomain} - not found", [searchdomain]);
|
||||||
|
return Ok(new SearchdomainUpdateResults() { Success = false, Message = $"Unable to update settings for searchdomain {searchdomain} - not found" });
|
||||||
|
} catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError("Unable to update settings for searchdomain {searchdomain} - Exception: {ex.Message} - {ex.StackTrace}", [searchdomain, ex.Message, ex.StackTrace]);
|
||||||
|
return Ok(new SearchdomainUpdateResults() { Success = false, Message = $"Unable to update settings for searchdomain {searchdomain}" });
|
||||||
|
}
|
||||||
|
return Ok(new SearchdomainUpdateResults(){Success = true});
|
||||||
|
}
|
||||||
|
|
||||||
[HttpGet("GetSearches")]
|
[HttpGet("GetSearches")]
|
||||||
public ActionResult<SearchdomainSearchesResults> GetSearches(string searchdomain)
|
public ActionResult<SearchdomainSearchesResults> GetSearches(string searchdomain)
|
||||||
{
|
{
|
||||||
@@ -128,4 +155,96 @@ public class SearchdomainController : ControllerBase
|
|||||||
|
|
||||||
return Ok(new SearchdomainSearchesResults() { Searches = searchCache, Success = true });
|
return Ok(new SearchdomainSearchesResults() { Searches = searchCache, Success = true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("GetSettings")]
|
||||||
|
public ActionResult<SearchdomainSettingsResults> GetSettings(string searchdomain)
|
||||||
|
{
|
||||||
|
Searchdomain searchdomain_;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
searchdomain_ = _domainManager.GetSearchdomain(searchdomain);
|
||||||
|
}
|
||||||
|
catch (SearchdomainNotFoundException)
|
||||||
|
{
|
||||||
|
_logger.LogError("Unable to retrieve the searchdomain {searchdomain} - it likely does not exist yet", [searchdomain]);
|
||||||
|
return Ok(new SearchdomainSettingsResults() { Settings = null, Success = false, Message = "Searchdomain not found" });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError("Unable to retrieve the searchdomain {searchdomain} - {ex.Message} - {ex.StackTrace}", [searchdomain, ex.Message, ex.StackTrace]);
|
||||||
|
return Ok(new SearchdomainSettingsResults() { Settings = null, Success = false, Message = ex.Message });
|
||||||
|
}
|
||||||
|
SearchdomainSettings settings = searchdomain_.settings;
|
||||||
|
return Ok(new SearchdomainSettingsResults() { Settings = settings, Success = true });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("GetSearchCacheSize")]
|
||||||
|
public ActionResult<SearchdomainSearchCacheSizeResults> GetSearchCacheSize(string searchdomain)
|
||||||
|
{
|
||||||
|
Searchdomain searchdomain_;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
searchdomain_ = _domainManager.GetSearchdomain(searchdomain);
|
||||||
|
}
|
||||||
|
catch (SearchdomainNotFoundException)
|
||||||
|
{
|
||||||
|
_logger.LogError("Unable to retrieve the searchdomain {searchdomain} - it likely does not exist yet", [searchdomain]);
|
||||||
|
return Ok(new SearchdomainSearchCacheSizeResults() { SearchCacheSizeBytes = null, Success = false, Message = "Searchdomain not found" });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError("Unable to retrieve the searchdomain {searchdomain} - {ex.Message} - {ex.StackTrace}", [searchdomain, ex.Message, ex.StackTrace]);
|
||||||
|
return Ok(new SearchdomainSearchCacheSizeResults() { SearchCacheSizeBytes = null, Success = false, Message = ex.Message });
|
||||||
|
}
|
||||||
|
Dictionary<string, DateTimedSearchResult> searchCache = searchdomain_.searchCache;
|
||||||
|
long sizeInBytes = 0;
|
||||||
|
foreach (var entry in searchCache)
|
||||||
|
{
|
||||||
|
sizeInBytes += sizeof(int); // string length prefix
|
||||||
|
sizeInBytes += entry.Key.Length * sizeof(char); // string characters
|
||||||
|
sizeInBytes += entry.Value.EstimateSize();
|
||||||
|
}
|
||||||
|
return Ok(new SearchdomainSearchCacheSizeResults() { SearchCacheSizeBytes = sizeInBytes, Success = true });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("ClearSearchCache")]
|
||||||
|
public ActionResult<SearchdomainInvalidateCacheResults> InvalidateSearchCache(string searchdomain)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Searchdomain searchdomain_ = _domainManager.GetSearchdomain(searchdomain);
|
||||||
|
searchdomain_.InvalidateSearchCache();
|
||||||
|
} catch (SearchdomainNotFoundException)
|
||||||
|
{
|
||||||
|
_logger.LogError("Unable to invalidate search cache for searchdomain {searchdomain} - not found", [searchdomain]);
|
||||||
|
return Ok(new SearchdomainInvalidateCacheResults() { Success = false, Message = $"Unable to invalidate search cache for searchdomain {searchdomain} - not found" });
|
||||||
|
} catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError("Unable to invalidate search cache for searchdomain {searchdomain} - Exception: {ex.Message} - {ex.StackTrace}", [searchdomain, ex.Message, ex.StackTrace]);
|
||||||
|
return Ok(new SearchdomainInvalidateCacheResults() { Success = false, Message = $"Unable to invalidate search cache for searchdomain {searchdomain}" });
|
||||||
|
}
|
||||||
|
return Ok(new SearchdomainInvalidateCacheResults(){Success = true});
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("GetDatabaseSize")]
|
||||||
|
public ActionResult<SearchdomainGetDatabaseSizeResult> GetDatabaseSize(string searchdomain)
|
||||||
|
{
|
||||||
|
Searchdomain searchdomain_;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
searchdomain_ = _domainManager.GetSearchdomain(searchdomain);
|
||||||
|
}
|
||||||
|
catch (SearchdomainNotFoundException)
|
||||||
|
{
|
||||||
|
_logger.LogError("Unable to retrieve the searchdomain {searchdomain} - it likely does not exist yet", [searchdomain]);
|
||||||
|
return Ok(new SearchdomainGetDatabaseSizeResult() { SearchdomainDatabaseSizeBytes = null, Success = false, Message = "Searchdomain not found" });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError("Unable to retrieve the searchdomain {searchdomain} - {ex.Message} - {ex.StackTrace}", [searchdomain, ex.Message, ex.StackTrace]);
|
||||||
|
return Ok(new SearchdomainGetDatabaseSizeResult() { SearchdomainDatabaseSizeBytes = null, Success = false, Message = ex.Message });
|
||||||
|
}
|
||||||
|
long sizeInBytes = DatabaseHelper.GetSearchdomainDatabaseSize(searchdomain_.helper, searchdomain);
|
||||||
|
return Ok(new SearchdomainGetDatabaseSizeResult() { SearchdomainDatabaseSizeBytes = sizeInBytes, Success = true });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Data.Common;
|
using System.Data.Common;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Server.Exceptions;
|
using Server.Exceptions;
|
||||||
|
using Shared.Models;
|
||||||
|
|
||||||
namespace Server.Helper;
|
namespace Server.Helper;
|
||||||
|
|
||||||
@@ -27,12 +28,12 @@ public class DatabaseHelper(ILogger<DatabaseHelper> logger)
|
|||||||
helper.ExecuteSQLNonQuery(query.ToString(), parameters);
|
helper.ExecuteSQLNonQuery(query.ToString(), parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int DatabaseInsertSearchdomain(SQLHelper helper, string name)
|
public static int DatabaseInsertSearchdomain(SQLHelper helper, string name, SearchdomainSettings settings = new())
|
||||||
{
|
{
|
||||||
Dictionary<string, dynamic> parameters = new()
|
Dictionary<string, dynamic> parameters = new()
|
||||||
{
|
{
|
||||||
{ "name", name },
|
{ "name", name },
|
||||||
{ "settings", "{}"} // TODO add settings. It's not used yet, but maybe it's needed someday...
|
{ "settings", settings}
|
||||||
};
|
};
|
||||||
return helper.ExecuteSQLCommandGetInsertedID("INSERT INTO searchdomain (name, settings) VALUES (@name, @settings)", parameters);
|
return helper.ExecuteSQLCommandGetInsertedID("INSERT INTO searchdomain (name, settings) VALUES (@name, @settings)", parameters);
|
||||||
}
|
}
|
||||||
@@ -176,4 +177,38 @@ public class DatabaseHelper(ILogger<DatabaseHelper> logger)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static long GetSearchdomainDatabaseSize(SQLHelper helper, string searchdomain)
|
||||||
|
{
|
||||||
|
Dictionary<string, dynamic> parameters = new()
|
||||||
|
{
|
||||||
|
{ "searchdomain", searchdomain}
|
||||||
|
};
|
||||||
|
DbDataReader searchdomainSumReader = helper.ExecuteSQLCommand("SELECT SUM(LENGTH(id) + LENGTH(name) + LENGTH(settings)) AS total_bytes FROM embeddingsearch.searchdomain WHERE name=@searchdomain", parameters);
|
||||||
|
bool success = searchdomainSumReader.Read();
|
||||||
|
long result = success && !searchdomainSumReader.IsDBNull(0) ? searchdomainSumReader.GetInt64(0) : 0;
|
||||||
|
searchdomainSumReader.Close();
|
||||||
|
|
||||||
|
DbDataReader entitySumReader = helper.ExecuteSQLCommand("SELECT SUM(LENGTH(e.id) + LENGTH(e.name) + LENGTH(e.probmethod) + LENGTH(e.id_searchdomain)) AS total_bytes FROM embeddingsearch.entity e JOIN embeddingsearch.searchdomain s ON e.id_searchdomain = s.id WHERE s.name=@searchdomain", parameters);
|
||||||
|
success = entitySumReader.Read();
|
||||||
|
result += success && !entitySumReader.IsDBNull(0) ? entitySumReader.GetInt64(0) : 0;
|
||||||
|
entitySumReader.Close();
|
||||||
|
|
||||||
|
DbDataReader datapointSumReader = helper.ExecuteSQLCommand("SELECT SUM(LENGTH(d.id) + LENGTH(d.name) + LENGTH(d.probmethod_embedding) + LENGTH(d.similaritymethod) + LENGTH(d.id_entity) + LENGTH(d.hash)) AS total_bytes FROM embeddingsearch.datapoint d JOIN embeddingsearch.entity e ON d.id_entity = e.id JOIN embeddingsearch.searchdomain s ON e.id_searchdomain = s.id WHERE s.name=@searchdomain", parameters);
|
||||||
|
success = datapointSumReader.Read();
|
||||||
|
result += success && !datapointSumReader.IsDBNull(0) ? datapointSumReader.GetInt64(0) : 0;
|
||||||
|
datapointSumReader.Close();
|
||||||
|
|
||||||
|
DbDataReader embeddingSumReader = helper.ExecuteSQLCommand("SELECT SUM(LENGTH(em.id) + LENGTH(em.id_datapoint) + LENGTH(em.model) + LENGTH(em.embedding)) AS total_bytes FROM embeddingsearch.embedding em JOIN embeddingsearch.datapoint d ON em.id_datapoint = d.id JOIN embeddingsearch.entity e ON d.id_entity = e.id JOIN embeddingsearch.searchdomain s ON e.id_searchdomain = s.id WHERE s.name=@searchdomain", parameters);
|
||||||
|
success = embeddingSumReader.Read();
|
||||||
|
result += success && !embeddingSumReader.IsDBNull(0) ? embeddingSumReader.GetInt64(0) : 0;
|
||||||
|
embeddingSumReader.Close();
|
||||||
|
|
||||||
|
DbDataReader attributeSumReader = helper.ExecuteSQLCommand("SELECT SUM(LENGTH(a.id) + LENGTH(a.id_entity) + LENGTH(a.attribute) + LENGTH(a.value)) AS total_bytes FROM embeddingsearch.attribute a JOIN embeddingsearch.entity e ON a.id_entity = e.id JOIN embeddingsearch.searchdomain s ON e.id_searchdomain = s.id WHERE s.name=@searchdomain", parameters);
|
||||||
|
success = attributeSumReader.Read();
|
||||||
|
result += success && !attributeSumReader.IsDBNull(0) ? attributeSumReader.GetInt64(0) : 0;
|
||||||
|
attributeSumReader.Close();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Data.Common;
|
using System.Data.Common;
|
||||||
|
using System.Text.Json;
|
||||||
using ElmahCore.Mvc.Logger;
|
using ElmahCore.Mvc.Logger;
|
||||||
using MySql.Data.MySqlClient;
|
using MySql.Data.MySqlClient;
|
||||||
using Server.Helper;
|
using Server.Helper;
|
||||||
@@ -14,6 +15,7 @@ public class Searchdomain
|
|||||||
public AIProvider aIProvider;
|
public AIProvider aIProvider;
|
||||||
public string searchdomain;
|
public string searchdomain;
|
||||||
public int id;
|
public int id;
|
||||||
|
public SearchdomainSettings settings;
|
||||||
public Dictionary<string, DateTimedSearchResult> searchCache; // Key: query, Value: Search results for that query (with timestamp)
|
public Dictionary<string, DateTimedSearchResult> searchCache; // Key: query, Value: Search results for that query (with timestamp)
|
||||||
public List<Entity> entityCache;
|
public List<Entity> entityCache;
|
||||||
public List<string> modelsInUse;
|
public List<string> modelsInUse;
|
||||||
@@ -36,6 +38,7 @@ public class Searchdomain
|
|||||||
connection = new MySqlConnection(connectionString);
|
connection = new MySqlConnection(connectionString);
|
||||||
connection.Open();
|
connection.Open();
|
||||||
helper = new SQLHelper(connection, connectionString);
|
helper = new SQLHelper(connection, connectionString);
|
||||||
|
settings = GetSettings();
|
||||||
modelsInUse = []; // To make the compiler shut up - it is set in UpdateSearchDomain() don't worry // yeah, about that...
|
modelsInUse = []; // To make the compiler shut up - it is set in UpdateSearchDomain() don't worry // yeah, about that...
|
||||||
if (!runEmpty)
|
if (!runEmpty)
|
||||||
{
|
{
|
||||||
@@ -229,6 +232,19 @@ public class Searchdomain
|
|||||||
return this.id;
|
return this.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SearchdomainSettings GetSettings()
|
||||||
|
{
|
||||||
|
Dictionary<string, dynamic> parameters = new()
|
||||||
|
{
|
||||||
|
["name"] = searchdomain
|
||||||
|
};
|
||||||
|
DbDataReader reader = helper.ExecuteSQLCommand("SELECT settings from searchdomain WHERE name = @name", parameters);
|
||||||
|
reader.Read();
|
||||||
|
string settingsString = reader.GetString(0);
|
||||||
|
reader.Close();
|
||||||
|
return JsonSerializer.Deserialize<SearchdomainSettings>(settingsString);
|
||||||
|
}
|
||||||
|
|
||||||
public void InvalidateSearchCache()
|
public void InvalidateSearchCache()
|
||||||
{
|
{
|
||||||
searchCache = [];
|
searchCache = [];
|
||||||
|
|||||||
@@ -14,12 +14,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
<div class="container-fluid mt-4">
|
<div class="container-fluid mt-4">
|
||||||
|
<h1 class="visually-hidden">embeddingsearch</h1>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
||||||
<!-- Sidebar -->
|
<!-- Sidebar -->
|
||||||
<div class="col-md-4 sidebar">
|
<div class="col-md-4 sidebar" role="complementary">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body p-2">
|
<div class="card-body p-2">
|
||||||
|
<h2 class="visually-hidden">@T["Searchdomain selection"]</h2>
|
||||||
<ul class="list-group list-group-flush mb-2" style="max-height: 60vh; overflow-y: auto;">
|
<ul class="list-group list-group-flush mb-2" style="max-height: 60vh; overflow-y: auto;">
|
||||||
@foreach (var domain in domains)
|
@foreach (var domain in domains)
|
||||||
{
|
{
|
||||||
@@ -28,55 +30,63 @@
|
|||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
<button class="btn btn-primary w-100">Add</button>
|
<button id="searchdomainCreate" class="btn btn-primary w-100">@T["Create"]</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
<div class="col col-md-8">
|
<div class="col col-md-8" role="main">
|
||||||
|
|
||||||
<div class="card section-card">
|
<div class="card section-card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
<h2 class="visually-hidden">@T["Searchdomain information and settings"]</h2>
|
||||||
|
|
||||||
|
<h3 class="visually-hidden">@T["Actions"]</h3>
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
<h4 class="mb-0 text-nowrap overflow-auto">Searchdomain1</h4>
|
<p id="searchdomainName" class="mb-0 text-nowrap overflow-auto fs-3">Searchdomain</p>
|
||||||
<div class="col-md-3 text-end w-auto">
|
<div class="col-md-3 text-end w-auto">
|
||||||
<button class="btn btn-warning btn-sm me-2">Rename</button>
|
<button id="searchdomainRename" class="btn btn-warning btn-sm me-2">@T["Rename"]</button>
|
||||||
<button class="btn btn-danger btn-sm">Delete</button>
|
<button id="searchdomainDelete" class="btn btn-danger btn-sm">@T["Delete"]</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Settings -->
|
<!-- Settings -->
|
||||||
<div class="row align-items-center mb-3">
|
<div class="row align-items-center mb-3">
|
||||||
|
<h3>@T["Settings"]</h3>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label">Settings</label>
|
<input type="checkbox" class="form-check-input" id="searchdomainConfigCacheReconciliation" />
|
||||||
<input
|
<label class="form-check-label" for="searchdomainConfigCacheReconciliation">@T["Cache reconsiliation"]</label>
|
||||||
type="text"
|
|
||||||
class="form-control"
|
|
||||||
placeholder="JSON-string"
|
|
||||||
disabled
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-2 mt-3 mt-md-0">
|
<div class="col-md-2 mt-3 mt-md-0">
|
||||||
<button class="btn btn-warning w-100">Update</button>
|
<button class="btn btn-warning w-100" id="searchdomainConfigUpdate">@T["Update"]</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<h3 class="visually-hidden">@T["Search cache"]</h3>
|
||||||
<!-- Cache -->
|
<!-- Cache -->
|
||||||
<div class="d-flex align-items-center mb-4">
|
<div class="d-flex align-items-center mb-4">
|
||||||
<div class="me-3">
|
<div class="me-3">
|
||||||
<strong>Cache utilization:</strong> 2.47MiB
|
<strong>@T["Search cache utilization"]:</strong> <span id="cacheUtilization">0.00MiB</span>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-primary btn-sm">Reset</button>
|
<button id="cacheClear" class="btn btn-warning btn-sm">@T["Clear"]</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 class="visually-hidden">@T["Database size"]</h3>
|
||||||
|
<!-- Database size -->
|
||||||
|
<div class="d-flex align-items-center mb-4">
|
||||||
|
<div class="me-3">
|
||||||
|
<strong>@T["Database size"]:</strong> <span id="databaseUtilization">0.00MiB</span>
|
||||||
|
</div>
|
||||||
|
<button id="cacheClear" class="btn btn-warning btn-sm">@T["Clear"]</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Recent Queries -->
|
<!-- Recent Queries -->
|
||||||
<div class="card section-card mb-4">
|
<div class="card section-card mb-4">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
<strong>Recent queries</strong>
|
<h3>Recent queries</h3>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
class="form-control form-control-sm w-25"
|
class="form-control form-control-sm w-25"
|
||||||
@@ -101,7 +111,7 @@
|
|||||||
<div class="card section-card">
|
<div class="card section-card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
<strong>Entities</strong>
|
<h3>Entities</h3>
|
||||||
<input
|
<input
|
||||||
id="entitiesFilter"
|
id="entitiesFilter"
|
||||||
type="text"
|
type="text"
|
||||||
@@ -134,19 +144,19 @@
|
|||||||
<div class="modal fade" id="entityDetailsModal" tabindex="-1" aria-hidden="true">
|
<div class="modal fade" id="entityDetailsModal" tabindex="-1" aria-hidden="true">
|
||||||
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header bg-info">
|
||||||
<h5 class="modal-title" id="entityDetailsTitle">Entity Details</h5>
|
<h2 class="modal-title" id="entityDetailsTitle">@T["Entity Details"]</h2>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<!-- Attributes -->
|
<!-- Attributes -->
|
||||||
<h6>Attributes</h6>
|
<h3 class="fs-4">@T["Attributes"]</h3>
|
||||||
<table class="table table-sm table-bordered mb-4">
|
<table class="table table-sm table-bordered mb-4">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Key</th>
|
<th>@T["Key"]</th>
|
||||||
<th>Value</th>
|
<th>@T["Value"]</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="entityAttributesBody">
|
<tbody id="entityAttributesBody">
|
||||||
@@ -154,13 +164,13 @@
|
|||||||
</table>
|
</table>
|
||||||
|
|
||||||
<!-- Datapoints -->
|
<!-- Datapoints -->
|
||||||
<h6>Datapoints</h6>
|
<h3 class="fs-4">@T["Datapoints"]</h3>
|
||||||
<table class="table table-sm table-striped">
|
<table class="table table-sm table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>@T["Name"]</th>
|
||||||
<th>ProbMethod</th>
|
<th>@T["ProbMethod"]</th>
|
||||||
<th>SimilarityMethod</th>
|
<th>@T["SimilarityMethod"]</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="entityDatapointsBody">
|
<tbody id="entityDatapointsBody">
|
||||||
@@ -170,7 +180,7 @@
|
|||||||
|
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||||
Close
|
@T["Close"]
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -182,19 +192,19 @@
|
|||||||
<div class="modal-dialog modal-xl modal-dialog-scrollable">
|
<div class="modal-dialog modal-xl modal-dialog-scrollable">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
|
|
||||||
<div class="modal-header">
|
<div class="modal-header bg-info">
|
||||||
<h5 class="modal-title" id="queryDetailsTitle">Query Details</h5>
|
<h2 class="modal-title" id="queryDetailsTitle">@T["Query Details"] - <span id="queryDetailsQueryName"></span></h2>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
|
||||||
<!-- Access times -->
|
<!-- Access times -->
|
||||||
<h6>Access times</h6>
|
<h3>Access times</h3>
|
||||||
<ul id="queryAccessTimes" class="list-group mb-4"></ul>
|
<ul id="queryAccessTimes" class="list-group mb-4"></ul>
|
||||||
|
|
||||||
<!-- Results -->
|
<!-- Results -->
|
||||||
<h6>Results</h6>
|
<h3>Results</h3>
|
||||||
<table class="table table-sm table-striped">
|
<table class="table table-sm table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -217,6 +227,92 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Rename searchdomain Modal -->
|
||||||
|
<div class="modal fade" id="renameSearchdomainModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-m modal-dialog-scrollable">
|
||||||
|
<div class="modal-content">
|
||||||
|
|
||||||
|
<div class="modal-header bg-warning">
|
||||||
|
<h2 class="modal-title" id="renameSearchdomainTitle">@T["Rename searchdomain"]</h2>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
<!-- New name -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="renameSearchdomainNewName" class="form-label">New name</label>
|
||||||
|
<input type="text" class="form-control" id="renameSearchdomainNewName" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-warning" onclick="renameSearchdomain(getSelectedDomainKey(), document.getElementById('renameSearchdomainNewName').value)" data-bs-dismiss="modal">
|
||||||
|
Rename
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Delete searchdomain Modal -->
|
||||||
|
<div class="modal fade" id="deleteSearchdomainModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-m modal-dialog-scrollable">
|
||||||
|
<div class="modal-content">
|
||||||
|
|
||||||
|
<div class="modal-header bg-danger text-white">
|
||||||
|
<h2 class="modal-title" id="deleteSearchdomainTitle">@T["Delete searchdomain"]</h2>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>@T["Are you sure you want to delete this searchdomain? This action cannot be undone."]</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" id="searchdomainConfirmDelete" class="btn btn-danger" data-bs-dismiss="modal">
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Create searchdomain Modal -->
|
||||||
|
<div class="modal fade" id="createSearchdomainModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-m modal-dialog-scrollable">
|
||||||
|
<div class="modal-content">
|
||||||
|
|
||||||
|
<div class="modal-header bg-primary text-white">
|
||||||
|
<h2 class="modal-title" id="createSearchdomainTitle">@T["Create searchdomain"]</h2>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
<label for="createSearchdomainName" class="form-label">@T["Searchdomain name"]</label>
|
||||||
|
<input type="text" class="form-control mb-3" id="createSearchdomainName" placeholder="@T["Searchdomain name"]" />
|
||||||
|
<input type="checkbox" class="form-check-input" id="createSearchdomainWithCacheReconciliation" />
|
||||||
|
<label class="form-check-label" for="createSearchdomainWithCacheReconciliation">@T["Enable cache reconsiliation"]</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" id="searchdomainConfirmCreate" class="btn btn-primary" data-bs-dismiss="modal">
|
||||||
|
@T["Create"]
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||||
|
@T["Close"]
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -245,8 +341,185 @@
|
|||||||
queriesFilter.addEventListener('input', () => {
|
queriesFilter.addEventListener('input', () => {
|
||||||
populateQueriesTable(queriesFilter.value);
|
populateQueriesTable(queriesFilter.value);
|
||||||
});
|
});
|
||||||
|
selectDomain(0);
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById('searchdomainRename')
|
||||||
|
.addEventListener('click', () => {
|
||||||
|
const modal = new bootstrap.Modal(
|
||||||
|
document.getElementById('renameSearchdomainModal')
|
||||||
|
);
|
||||||
|
// Fill in searchdomain current name
|
||||||
|
const domainKey = getSelectedDomainKey();
|
||||||
|
document.getElementById(
|
||||||
|
'renameSearchdomainNewName'
|
||||||
|
).value = domains[domainKey];
|
||||||
|
modal.show();
|
||||||
|
});
|
||||||
|
document
|
||||||
|
.getElementById('searchdomainDelete')
|
||||||
|
.addEventListener('click', () => {
|
||||||
|
const modal = new bootstrap.Modal(
|
||||||
|
document.getElementById('deleteSearchdomainModal')
|
||||||
|
);
|
||||||
|
modal.show();
|
||||||
|
});
|
||||||
|
document
|
||||||
|
.getElementById('searchdomainConfirmDelete')
|
||||||
|
.addEventListener('click', () => {
|
||||||
|
const domainKey = getSelectedDomainKey();
|
||||||
|
deleteSearchdomain(domainKey);
|
||||||
|
selectDomain(0);
|
||||||
|
});
|
||||||
|
document
|
||||||
|
.getElementById('searchdomainConfigUpdate')
|
||||||
|
.addEventListener('click', () => {
|
||||||
|
const domainKey = getSelectedDomainKey();
|
||||||
|
const cacheReconciliation = document.getElementById('searchdomainConfigCacheReconciliation').checked;
|
||||||
|
updateSearchdomainConfig(domainKey, { CacheReconciliation: cacheReconciliation});
|
||||||
|
});
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById('searchdomainCreate')
|
||||||
|
.addEventListener('click', () => {
|
||||||
|
const modal = new bootstrap.Modal(
|
||||||
|
document.getElementById('createSearchdomainModal')
|
||||||
|
);
|
||||||
|
modal.show();
|
||||||
|
});
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById('searchdomainConfirmCreate')
|
||||||
|
.addEventListener('click', () => {
|
||||||
|
const modal = new bootstrap.Modal(
|
||||||
|
document.getElementById('createSearchdomainModal')
|
||||||
|
);
|
||||||
|
const name = document.getElementById('createSearchdomainName').value;
|
||||||
|
const cacheReconciliation = document.getElementById('createSearchdomainWithCacheReconciliation').checked;
|
||||||
|
const settings = { CacheReconciliation: cacheReconciliation };
|
||||||
|
// Implement create logic here
|
||||||
|
fetch(`/Searchdomain/Create?searchdomain=${encodeURIComponent(name)}&settings=${JSON.stringify(settings)}`, {
|
||||||
|
method: 'GET'
|
||||||
|
}).then(response => {
|
||||||
|
if (response.ok) {
|
||||||
|
// TODO add toast
|
||||||
|
console.log('Searchdomain created successfully');
|
||||||
|
// Reload the page to show the new searchdomain
|
||||||
|
location.reload();
|
||||||
|
} else {
|
||||||
|
// TODO add toast
|
||||||
|
console.error('Failed to create searchdomain');
|
||||||
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
console.error('Error creating searchdomain:', error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById('cacheClear')
|
||||||
|
.addEventListener('click', () => {
|
||||||
|
const domainKey = getSelectedDomainKey();
|
||||||
|
fetch(`/Searchdomain/ClearSearchCache?searchdomain=${encodeURIComponent(domains[domainKey])}`, {
|
||||||
|
method: 'GET'
|
||||||
|
}).then(response => {
|
||||||
|
if (response.ok) {
|
||||||
|
// TODO add toast
|
||||||
|
console.log('Searchdomain cache cleared successfully');
|
||||||
|
// Update cache utilization display
|
||||||
|
document.querySelector('#cacheUtilization').innerText = '0.00MiB';
|
||||||
|
} else {
|
||||||
|
// TODO add toast
|
||||||
|
console.error('Failed to clear searchdomain cache');
|
||||||
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
console.error('Error clearing searchdomain cache:', error);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function deleteSearchdomain(domainKey) {
|
||||||
|
// Implement delete logic here
|
||||||
|
fetch(`/Searchdomain/Delete?searchdomain=${encodeURI(domains[domainKey])}`, {
|
||||||
|
method: 'GET'
|
||||||
|
}).then(response => {
|
||||||
|
if (response.ok) {
|
||||||
|
// TODO add toast
|
||||||
|
// 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');
|
||||||
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
console.error('Error deleting searchdomain:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renameSearchdomain(domainKey, newName) {
|
||||||
|
// Implement rename logic here
|
||||||
|
fetch(`/Searchdomain/Update?searchdomain=${encodeURI(domains[domainKey])}&newName=${newName}`, {
|
||||||
|
method: 'GET'
|
||||||
|
}).then(response => {
|
||||||
|
if (response.ok) {
|
||||||
|
// TODO add toast
|
||||||
|
// Update sidebar and header name
|
||||||
|
var domainItem = document.getElementById('sidebar_domain_' + domainKey);
|
||||||
|
domainItem.innerText = newName;
|
||||||
|
document.querySelector('.section-card h3').innerText = newName;
|
||||||
|
domains[domainKey] = newName;
|
||||||
|
|
||||||
|
console.log('Searchdomain renamed successfully');
|
||||||
|
} else {
|
||||||
|
// TODO add toast
|
||||||
|
console.error('Failed to rename searchdomain');
|
||||||
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
console.error('Error renaming searchdomain:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSearchdomainConfig(domainKey, newSettings) {
|
||||||
|
// Implement update logic here
|
||||||
|
fetch(`/Searchdomain/UpdateSettings?searchdomain=${encodeURIComponent(domains[domainKey])}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(newSettings)
|
||||||
|
}).then(response => {
|
||||||
|
if (response.ok) {
|
||||||
|
// TODO add toast
|
||||||
|
console.log('Searchdomain settings updated successfully');
|
||||||
|
} else {
|
||||||
|
// TODO add toast
|
||||||
|
console.error('Failed to update searchdomain settings');
|
||||||
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
console.error('Error updating searchdomain settings:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSelectedDomainKey() {
|
||||||
|
return document.querySelector('.domain-item.active').id.split("_")[2] - 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSearchdomainConfig(domainKey) {
|
||||||
|
return fetch(`/Searchdomain/GetSettings?searchdomain=${encodeURIComponent(domains[domainKey])}`)
|
||||||
|
.then(r => r.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSearchdomainCacheUtilization(domainKey) {
|
||||||
|
return fetch(`/Searchdomain/GetSearchCacheSize?searchdomain=${encodeURIComponent(domains[domainKey])}`)
|
||||||
|
.then(r => r.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSearchdomainDatabaseUtilization(domainKey) {
|
||||||
|
return fetch(`/Searchdomain/GetDatabaseSize?searchdomain=${encodeURIComponent(domains[domainKey])}`)
|
||||||
|
.then(r => r.json());
|
||||||
|
}
|
||||||
|
|
||||||
function selectDomain(domainKey) {
|
function selectDomain(domainKey) {
|
||||||
document.querySelectorAll('.domain-item').forEach(item => {
|
document.querySelectorAll('.domain-item').forEach(item => {
|
||||||
item.classList.remove('active');
|
item.classList.remove('active');
|
||||||
@@ -256,7 +529,13 @@
|
|||||||
selectedItem.classList.add('active');
|
selectedItem.classList.add('active');
|
||||||
|
|
||||||
var domainName = domains[domainKey];
|
var domainName = domains[domainKey];
|
||||||
document.querySelector('.section-card h4').innerText = domainName;
|
document.querySelector('#searchdomainName').innerText = domainName;
|
||||||
|
|
||||||
|
let searchdomainConfigPromise = getSearchdomainConfig(getSelectedDomainKey());
|
||||||
|
let configElementCacheReconsiliation = document.getElementById('searchdomainConfigCacheReconciliation');
|
||||||
|
|
||||||
|
let cacheUtilizationPromise = getSearchdomainCacheUtilization(getSelectedDomainKey());
|
||||||
|
let databaseUtilizationPromise = getSearchdomainDatabaseUtilization(getSelectedDomainKey());
|
||||||
|
|
||||||
/* ---------- ENTITIES ---------- */
|
/* ---------- ENTITIES ---------- */
|
||||||
let entitiesUrl = `/Entity/List?searchdomain=${encodeURIComponent(domainName)}&returnEmbeddings=false`;
|
let entitiesUrl = `/Entity/List?searchdomain=${encodeURIComponent(domainName)}&returnEmbeddings=false`;
|
||||||
@@ -294,6 +573,41 @@
|
|||||||
console.error('Error fetching queries:', err);
|
console.error('Error fetching queries:', err);
|
||||||
hideQueriesLoading(queriesCard);
|
hideQueriesLoading(queriesCard);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
searchdomainConfigPromise.then(searchdomainConfig => {
|
||||||
|
if (searchdomainConfig != null && searchdomainConfig.Settings != null)
|
||||||
|
{
|
||||||
|
console.log(searchdomainConfig);
|
||||||
|
configElementCacheReconsiliation.checked = searchdomainConfig.Settings.CacheReconciliation;
|
||||||
|
configElementCacheReconsiliation.disabled = false;
|
||||||
|
} else {
|
||||||
|
//configElement.value = 'Error fetching searchdomain config';
|
||||||
|
configElementCacheReconsiliation.disabled = true;
|
||||||
|
// TODO add toast
|
||||||
|
console.error('Failed to fetch searchdomain config');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
cacheUtilizationPromise.then(cacheUtilization => {
|
||||||
|
if (cacheUtilization != null && cacheUtilization.SearchCacheSizeBytes != null)
|
||||||
|
{
|
||||||
|
document.querySelector('#cacheUtilization').innerText =
|
||||||
|
`${(cacheUtilization.SearchCacheSizeBytes / (1024 * 1024)).toFixed(2)}MiB`;
|
||||||
|
} else {
|
||||||
|
// TODO add toast
|
||||||
|
console.error('Failed to fetch searchdomain cache utilization');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
databaseUtilizationPromise.then(databaseUtilization => {
|
||||||
|
if (databaseUtilization != null && databaseUtilization.SearchdomainDatabaseSizeBytes != null)
|
||||||
|
{
|
||||||
|
document.querySelector('#databaseUtilization').innerText =
|
||||||
|
`${(databaseUtilization.SearchdomainDatabaseSizeBytes / (1024 * 1024)).toFixed(2)}MiB`;
|
||||||
|
} else {
|
||||||
|
// TODO add toast
|
||||||
|
console.error('Failed to fetch searchdomain database utilization');
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearEntitiesTable() {
|
function clearEntitiesTable() {
|
||||||
@@ -313,6 +627,7 @@
|
|||||||
|
|
||||||
const normalizedFilter = filterText.toLowerCase();
|
const normalizedFilter = filterText.toLowerCase();
|
||||||
|
|
||||||
|
let isFirstEntity = true;
|
||||||
entities
|
entities
|
||||||
.filter(e => e.Name.toLowerCase().includes(normalizedFilter))
|
.filter(e => e.Name.toLowerCase().includes(normalizedFilter))
|
||||||
.forEach(entity => {
|
.forEach(entity => {
|
||||||
@@ -320,11 +635,15 @@
|
|||||||
|
|
||||||
var nameCell = document.createElement('td');
|
var nameCell = document.createElement('td');
|
||||||
nameCell.textContent = entity.Name;
|
nameCell.textContent = entity.Name;
|
||||||
|
if (isFirstEntity) {
|
||||||
|
nameCell.classList.add('w-100'); // Otherwise the table doesn't use the full width
|
||||||
|
isFirstEntity = false;
|
||||||
|
}
|
||||||
row.appendChild(nameCell);
|
row.appendChild(nameCell);
|
||||||
|
|
||||||
var actionCell = document.createElement('td');
|
var actionCell = document.createElement('td');
|
||||||
var detailsButton = document.createElement('button');
|
var detailsButton = document.createElement('button');
|
||||||
detailsButton.className = 'btn btn-primary btn-sm';
|
detailsButton.className = 'btn btn-info btn-sm';
|
||||||
detailsButton.textContent = 'Details';
|
detailsButton.textContent = 'Details';
|
||||||
detailsButton.setAttribute("data-index", entities.findIndex(en => en == entity));
|
detailsButton.setAttribute("data-index", entities.findIndex(en => en == entity));
|
||||||
detailsButton.addEventListener('click', () => {
|
detailsButton.addEventListener('click', () => {
|
||||||
@@ -341,6 +660,7 @@
|
|||||||
function populateQueriesTable(filterText = '') {
|
function populateQueriesTable(filterText = '') {
|
||||||
if (!queries) return;
|
if (!queries) return;
|
||||||
|
|
||||||
|
|
||||||
const tbody = document.querySelector('#queriesTable tbody');
|
const tbody = document.querySelector('#queriesTable tbody');
|
||||||
tbody.innerHTML = '';
|
tbody.innerHTML = '';
|
||||||
|
|
||||||
@@ -358,7 +678,7 @@
|
|||||||
|
|
||||||
const actionCell = document.createElement('td');
|
const actionCell = document.createElement('td');
|
||||||
const btn = document.createElement('button');
|
const btn = document.createElement('button');
|
||||||
btn.className = 'btn btn-sm btn-primary';
|
btn.className = 'btn btn-sm btn-info';
|
||||||
btn.textContent = '@T["Details"]';
|
btn.textContent = '@T["Details"]';
|
||||||
btn.addEventListener('click', () => {
|
btn.addEventListener('click', () => {
|
||||||
showQueryDetails(query);
|
showQueryDetails(query);
|
||||||
@@ -369,6 +689,9 @@
|
|||||||
|
|
||||||
tbody.appendChild(row);
|
tbody.appendChild(row);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function flagSearchdomainAsErroneous(domainKey) {
|
function flagSearchdomainAsErroneous(domainKey) {
|
||||||
@@ -447,8 +770,7 @@
|
|||||||
|
|
||||||
function showQueryDetails(query) {
|
function showQueryDetails(query) {
|
||||||
// Title
|
// Title
|
||||||
document.getElementById('queryDetailsTitle').innerText =
|
document.getElementById('queryDetailsQueryName').innerText = query.Name;
|
||||||
`Query: ${query.Name}`;
|
|
||||||
|
|
||||||
/* ---------- Access times ---------- */
|
/* ---------- Access times ---------- */
|
||||||
const accessList = document.getElementById('queryAccessTimes');
|
const accessList = document.getElementById('queryAccessTimes');
|
||||||
|
|||||||
@@ -44,4 +44,8 @@ body {
|
|||||||
|
|
||||||
.d-none {
|
.d-none {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,6 +8,25 @@ public readonly struct ResultItem(float score, string name)
|
|||||||
public readonly float Score { get; } = score;
|
public readonly float Score { get; } = score;
|
||||||
[JsonPropertyName("Name")]
|
[JsonPropertyName("Name")]
|
||||||
public readonly string Name { get; } = name;
|
public readonly string Name { get; } = name;
|
||||||
|
|
||||||
|
public static long EstimateSize(ResultItem item)
|
||||||
|
{
|
||||||
|
long size = 0;
|
||||||
|
|
||||||
|
// string object
|
||||||
|
if (item.Name != null)
|
||||||
|
{
|
||||||
|
size += MemorySizes.ObjectHeader;
|
||||||
|
size += sizeof(int); // string length
|
||||||
|
size += item.Name.Length * sizeof(char);
|
||||||
|
size = Align(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long Align(long size)
|
||||||
|
=> (size + 7) & ~7; // 8-byte alignment
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct DateTimedSearchResult(DateTime dateTime, List<ResultItem> results)
|
public struct DateTimedSearchResult(DateTime dateTime, List<ResultItem> results)
|
||||||
@@ -16,4 +35,72 @@ public struct DateTimedSearchResult(DateTime dateTime, List<ResultItem> results)
|
|||||||
public List<DateTime> AccessDateTimes { get; set; } = [dateTime];
|
public List<DateTime> AccessDateTimes { get; set; } = [dateTime];
|
||||||
[JsonPropertyName("Results")]
|
[JsonPropertyName("Results")]
|
||||||
public List<ResultItem> Results { get; set; } = results;
|
public List<ResultItem> Results { get; set; } = results;
|
||||||
|
|
||||||
|
public long EstimateSize()
|
||||||
|
{
|
||||||
|
long size = 0;
|
||||||
|
|
||||||
|
size += EstimateDateTimeList(AccessDateTimes);
|
||||||
|
size += EstimateResultItemList(Results);
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long EstimateDateTimeList(List<DateTime>? list)
|
||||||
|
{
|
||||||
|
if (list == null)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
long size = 0;
|
||||||
|
|
||||||
|
// List object
|
||||||
|
size += MemorySizes.ObjectHeader;
|
||||||
|
size += MemorySizes.Reference; // reference to array
|
||||||
|
|
||||||
|
// Internal array
|
||||||
|
size += MemorySizes.ArrayHeader;
|
||||||
|
size += list.Capacity * sizeof(long); // DateTime = 8 bytes
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long EstimateResultItemList(List<ResultItem>? list)
|
||||||
|
{
|
||||||
|
if (list == null)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
long size = 0;
|
||||||
|
|
||||||
|
// List object
|
||||||
|
size += MemorySizes.ObjectHeader;
|
||||||
|
size += MemorySizes.Reference;
|
||||||
|
|
||||||
|
// Internal array of structs
|
||||||
|
size += MemorySizes.ArrayHeader;
|
||||||
|
int resultItemInlineSize = sizeof(float) + IntPtr.Size; // float + string reference
|
||||||
|
size += list.Capacity * resultItemInlineSize;
|
||||||
|
|
||||||
|
// Heap allocations referenced by ResultItem
|
||||||
|
foreach (var item in list)
|
||||||
|
size += ResultItem.EstimateSize(item);
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct SearchdomainSettings(bool cacheReconciliation = false)
|
||||||
|
{
|
||||||
|
[JsonPropertyName("CacheReconciliation")]
|
||||||
|
public bool CacheReconciliation { get; set; } = cacheReconciliation;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static class MemorySizes
|
||||||
|
{
|
||||||
|
public static readonly int PointerSize = IntPtr.Size;
|
||||||
|
public static readonly int ObjectHeader = PointerSize * 2;
|
||||||
|
public static readonly int Reference = PointerSize;
|
||||||
|
public static readonly int ArrayHeader = Align(ObjectHeader + sizeof(int));
|
||||||
|
|
||||||
|
public static int Align(int size)
|
||||||
|
=> (size + PointerSize - 1) & ~(PointerSize - 1);
|
||||||
}
|
}
|
||||||
@@ -54,3 +54,49 @@ public class SearchdomainSearchesResults
|
|||||||
[JsonPropertyName("Searches")]
|
[JsonPropertyName("Searches")]
|
||||||
public required Dictionary<string, DateTimedSearchResult> Searches { get; set; }
|
public required Dictionary<string, DateTimedSearchResult> Searches { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class SearchdomainSettingsResults
|
||||||
|
{
|
||||||
|
[JsonPropertyName("Success")]
|
||||||
|
public required bool Success { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("Message")]
|
||||||
|
public string? Message { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("Settings")]
|
||||||
|
public required SearchdomainSettings? Settings { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SearchdomainSearchCacheSizeResults
|
||||||
|
{
|
||||||
|
[JsonPropertyName("Success")]
|
||||||
|
public required bool Success { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("Message")]
|
||||||
|
public string? Message { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("SearchCacheSizeBytes")]
|
||||||
|
public required long? SearchCacheSizeBytes { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SearchdomainInvalidateCacheResults
|
||||||
|
{
|
||||||
|
[JsonPropertyName("Success")]
|
||||||
|
public required bool Success { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("Message")]
|
||||||
|
public string? Message { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SearchdomainGetDatabaseSizeResult
|
||||||
|
{
|
||||||
|
[JsonPropertyName("Success")]
|
||||||
|
public required bool Success { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("Message")]
|
||||||
|
public string? Message { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("SearchdomainDatabaseSizeBytes")]
|
||||||
|
public required long? SearchdomainDatabaseSizeBytes { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user