Merge pull request #31 from LD-Reborn/26-create-a-front-end---mockup-and-basic-entities-view
26 create a front end mockup and basic entities view
This commit is contained in:
@@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Mvc;
|
|||||||
using Server.Helper;
|
using Server.Helper;
|
||||||
using Shared.Models;
|
using Shared.Models;
|
||||||
using Server.Exceptions;
|
using Server.Exceptions;
|
||||||
|
using Server.Models;
|
||||||
namespace Server.Controllers;
|
namespace Server.Controllers;
|
||||||
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
@@ -11,15 +12,22 @@ namespace Server.Controllers;
|
|||||||
public class HomeController : Controller
|
public class HomeController : Controller
|
||||||
{
|
{
|
||||||
private readonly ILogger<EntityController> _logger;
|
private readonly ILogger<EntityController> _logger;
|
||||||
|
private readonly SearchdomainManager _domainManager;
|
||||||
|
|
||||||
public HomeController(ILogger<EntityController> logger, IConfiguration config, SearchdomainManager domainManager, SearchdomainHelper searchdomainHelper, DatabaseHelper databaseHelper)
|
public HomeController(ILogger<EntityController> logger, IConfiguration config, SearchdomainManager domainManager, SearchdomainHelper searchdomainHelper, DatabaseHelper databaseHelper)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_domainManager = domainManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[HttpGet("/")]
|
[HttpGet("/")]
|
||||||
public IActionResult Index()
|
public IActionResult Index()
|
||||||
{
|
{
|
||||||
return View();
|
HomeIndexViewModel viewModel = new()
|
||||||
|
{
|
||||||
|
Searchdomains = _domainManager.ListSearchdomains()
|
||||||
|
};
|
||||||
|
return View(viewModel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
6
src/Server/Models/HomeViewModels.cs
Normal file
6
src/Server/Models/HomeViewModels.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Server.Models;
|
||||||
|
|
||||||
|
public class HomeIndexViewModel
|
||||||
|
{
|
||||||
|
public required List<string> Searchdomains { get; set; }
|
||||||
|
}
|
||||||
@@ -51,7 +51,7 @@ public class Searchdomain
|
|||||||
{
|
{
|
||||||
["id"] = this.id
|
["id"] = this.id
|
||||||
};
|
};
|
||||||
DbDataReader embeddingReader = helper.ExecuteSQLCommand("SELECT embedding.id, id_datapoint, model, embedding FROM embedding", parametersIDSearchdomain); // TODO fix: parametersIDSearchdomain defined, but not used
|
DbDataReader embeddingReader = helper.ExecuteSQLCommand("SELECT e.id, e.id_datapoint, e.model, e.embedding FROM embedding e JOIN datapoint d ON e.id_datapoint = d.id JOIN entity ent ON d.id_entity = ent.id JOIN searchdomain s ON ent.id_searchdomain = s.id WHERE s.id = @id", parametersIDSearchdomain);
|
||||||
Dictionary<int, Dictionary<string, float[]>> embedding_unassigned = [];
|
Dictionary<int, Dictionary<string, float[]>> embedding_unassigned = [];
|
||||||
while (embeddingReader.Read())
|
while (embeddingReader.Read())
|
||||||
{
|
{
|
||||||
@@ -83,7 +83,7 @@ public class Searchdomain
|
|||||||
}
|
}
|
||||||
embeddingReader.Close();
|
embeddingReader.Close();
|
||||||
|
|
||||||
DbDataReader datapointReader = helper.ExecuteSQLCommand("SELECT id, id_entity, name, probmethod_embedding, similaritymethod, hash FROM datapoint", parametersIDSearchdomain);
|
DbDataReader datapointReader = helper.ExecuteSQLCommand("SELECT d.id, d.id_entity, d.name, d.probmethod_embedding, d.similaritymethod, d.hash FROM datapoint d JOIN entity ent ON d.id_entity = ent.id JOIN searchdomain s ON ent.id_searchdomain = s.id WHERE s.id = @id", parametersIDSearchdomain);
|
||||||
Dictionary<int, List<Datapoint>> datapoint_unassigned = [];
|
Dictionary<int, List<Datapoint>> datapoint_unassigned = [];
|
||||||
while (datapointReader.Read())
|
while (datapointReader.Read())
|
||||||
{
|
{
|
||||||
@@ -107,7 +107,7 @@ public class Searchdomain
|
|||||||
}
|
}
|
||||||
datapointReader.Close();
|
datapointReader.Close();
|
||||||
|
|
||||||
DbDataReader attributeReader = helper.ExecuteSQLCommand("SELECT id, id_entity, attribute, value FROM attribute", parametersIDSearchdomain);
|
DbDataReader attributeReader = helper.ExecuteSQLCommand("SELECT a.id, a.id_entity, a.attribute, a.value FROM attribute a JOIN entity ent ON a.id_entity = ent.id JOIN searchdomain s ON ent.id_searchdomain = s.id WHERE s.id = @id", parametersIDSearchdomain);
|
||||||
Dictionary<int, Dictionary<string, string>> attributes_unassigned = [];
|
Dictionary<int, Dictionary<string, string>> attributes_unassigned = [];
|
||||||
while (attributeReader.Read())
|
while (attributeReader.Read())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,8 +1,217 @@
|
|||||||
@{
|
@using Server.Models
|
||||||
|
@using System.Web
|
||||||
|
@model HomeIndexViewModel
|
||||||
|
@{
|
||||||
ViewData["Title"] = "Home Page";
|
ViewData["Title"] = "Home Page";
|
||||||
|
int i = 0;
|
||||||
|
Dictionary<int, string> domains = [];
|
||||||
|
Model.Searchdomains.ForEach(domain =>
|
||||||
|
{
|
||||||
|
domains[i++] = domain;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="text-center">
|
<div class="container-fluid mt-4">
|
||||||
<h1 class="display-4">Welcome</h1>
|
<div class="row">
|
||||||
<p>Learn about <a href="https://learn.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
|
|
||||||
|
<!-- Sidebar -->
|
||||||
|
<div class="col-md-4 sidebar">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body p-2">
|
||||||
|
<ul class="list-group list-group-flush mb-2" style="max-height: 60vh; overflow-y: auto;">
|
||||||
|
@foreach (var domain in domains)
|
||||||
|
{
|
||||||
|
<li id="sidebar_domain_@(domain.Key)" class="domain-item list-group-item list-group-item-action text-nowrap" style="width: max-content" title="@domain.Value" onclick="selectDomain(@(domain.Key))">
|
||||||
|
@domain.Value
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
<button class="btn btn-primary w-100">Add</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<div class="col col-md-8">
|
||||||
|
|
||||||
|
<div class="card section-card">
|
||||||
|
<div class="card-body">
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
|
<h4 class="mb-0 text-nowrap overflow-auto">Searchdomain1</h4>
|
||||||
|
<div class="col-md-3 text-end w-auto">
|
||||||
|
<button class="btn btn-warning btn-sm me-2">Rename</button>
|
||||||
|
<button class="btn btn-danger btn-sm">Delete</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Settings -->
|
||||||
|
<div class="row align-items-center mb-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Settings</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
placeholder="JSON-string"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2 mt-3 mt-md-0">
|
||||||
|
<button class="btn btn-warning w-100">Update</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Cache -->
|
||||||
|
<div class="d-flex align-items-center mb-4">
|
||||||
|
<div class="me-3">
|
||||||
|
<strong>Cache utilization:</strong> 2.47MiB
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary btn-sm">Reset</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Recent Queries -->
|
||||||
|
<div class="card section-card mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
|
<strong>Recent queries</strong>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control form-control-sm w-25"
|
||||||
|
placeholder="filter"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@* <div class="list-row">
|
||||||
|
<span>Some test query</span>
|
||||||
|
<button class="btn btn-primary btn-sm">Details</button>
|
||||||
|
</div>
|
||||||
|
<div class="list-row">
|
||||||
|
<span>Some other test query</span>
|
||||||
|
<button class="btn btn-primary btn-sm">Details</button>
|
||||||
|
</div> *@
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Entities -->
|
||||||
|
<div class="card section-card">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
|
<strong>Entities</strong>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control form-control-sm w-25"
|
||||||
|
placeholder="filter"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="spinner d-none"></div>
|
||||||
|
<table id="entitiesTable" class="table table-striped" style="max-height: 60vh; overflow-y: auto; display: block;">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="visually-hidden">Name</th>
|
||||||
|
<th class="visually-hidden">Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
@* <div class="list-row">
|
||||||
|
<span>Someentity</span>
|
||||||
|
<button class="btn btn-primary btn-sm">Details</button>
|
||||||
|
</div>
|
||||||
|
<div class="list-row">
|
||||||
|
<span>Some other test query</span>
|
||||||
|
<button class="btn btn-primary btn-sm">Details</button>
|
||||||
|
</div> *@
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var domains = JSON.parse('@Html.Raw(System.Text.Json.JsonSerializer.Serialize(domains))');
|
||||||
|
var entities = null;
|
||||||
|
|
||||||
|
function selectDomain(domainKey) {
|
||||||
|
// Deselect all domain items
|
||||||
|
document.querySelectorAll('.domain-item').forEach(item => {
|
||||||
|
item.classList.remove('active');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Select the clicked domain item
|
||||||
|
var selectedItem = document.getElementById('sidebar_domain_' + domainKey);
|
||||||
|
selectedItem.classList.add('active');
|
||||||
|
|
||||||
|
// Update main content header
|
||||||
|
var domainName = domains[domainKey];
|
||||||
|
document.querySelector('.section-card h4').innerText = domainName;
|
||||||
|
|
||||||
|
// Request the entities from that searchdomain
|
||||||
|
let url = `/Entity/List?searchdomain=${encodeURIComponent(domainName)}&returnEmbeddings=false`;
|
||||||
|
let table = document.querySelector("#entitiesTable").parentElement;
|
||||||
|
clearEntitiesTable();
|
||||||
|
showEntitiesLoading(table);
|
||||||
|
fetch(url)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
entities = data.Results;
|
||||||
|
populateEntitiesTable();
|
||||||
|
hideEntitiesLoading(table);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error fetching entities:', error);
|
||||||
|
flagSearchdomainAsErroneous(domainKey);
|
||||||
|
hideEntitiesLoading(table);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearEntitiesTable() {
|
||||||
|
var tableBody = document.querySelector('#entitiesTable tbody');
|
||||||
|
tableBody.innerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function populateEntitiesTable() {
|
||||||
|
if (!entities) return;
|
||||||
|
|
||||||
|
var tableBody = document.querySelector('#entitiesTable tbody');
|
||||||
|
clearEntitiesTable();
|
||||||
|
|
||||||
|
entities.forEach(entity => {
|
||||||
|
var row = document.createElement('tr');
|
||||||
|
|
||||||
|
var nameCell = document.createElement('td');
|
||||||
|
nameCell.textContent = entity.Name;
|
||||||
|
row.appendChild(nameCell);
|
||||||
|
|
||||||
|
var actionCell = document.createElement('td');
|
||||||
|
var detailsButton = document.createElement('button');
|
||||||
|
detailsButton.className = 'btn btn-primary btn-sm';
|
||||||
|
detailsButton.textContent = 'Details';
|
||||||
|
actionCell.appendChild(detailsButton);
|
||||||
|
row.appendChild(actionCell);
|
||||||
|
|
||||||
|
tableBody.appendChild(row);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function flagSearchdomainAsErroneous(domainKey) {
|
||||||
|
var domainItem = document.getElementById('sidebar_domain_' + domainKey);
|
||||||
|
domainItem.classList.add('list-group-item-danger');
|
||||||
|
}
|
||||||
|
|
||||||
|
function showEntitiesLoading(element = null) {
|
||||||
|
if (element == null) element = document;
|
||||||
|
element.querySelector('.spinner').classList.remove('d-none');
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideEntitiesLoading(element = null) {
|
||||||
|
if (element == null) element = document;
|
||||||
|
element.querySelector('.spinner').classList.add('d-none');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -3,16 +3,15 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>@ViewData["Title"] - test</title>
|
<title>@ViewData["Title"] - embeddingsearch</title>
|
||||||
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
|
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
|
||||||
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
|
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
|
||||||
<link rel="stylesheet" href="~/test.styles.css" asp-append-version="true" />
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
|
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">test</a>
|
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">embeddingsearch</a>
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
|
||||||
aria-expanded="false" aria-label="Toggle navigation">
|
aria-expanded="false" aria-label="Toggle navigation">
|
||||||
<span class="navbar-toggler-icon"></span>
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
|||||||
@@ -19,4 +19,29 @@ html {
|
|||||||
|
|
||||||
body {
|
body {
|
||||||
margin-bottom: 60px;
|
margin-bottom: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Throbber */
|
||||||
|
.spinner-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border: 3px solid #ccc;
|
||||||
|
border-top: 3px solid #0d6efd;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 0.8s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.d-none {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user