Urls web visualization, cleaning obsolete code

This commit is contained in:
Luciano Gervasoni
2025-03-25 02:51:16 +01:00
parent 0c6b5f1ea4
commit 24b4614049
52 changed files with 371 additions and 3293 deletions

View File

@@ -9,7 +9,7 @@
<script>
function getQueryString(pageNumber, itemsNumber, sources, statuses){
function getQueryString(pageNumber, itemsNumber, sources, searches, statuses){
// Query parameters. If input is null, get most recent value
let queryParams = new URLSearchParams(window.location.search);
// page
@@ -21,6 +21,9 @@
// sources
if (sources == null) sources = queryParams.get("sources") ?? "all";
queryParams.set("sources", sources);
// searches
if (searches == null) searches = queryParams.get("searches") ?? "all";
queryParams.set("searches", searches);
// status
if (statuses == null) statuses = queryParams.get("status") ?? "all";
queryParams.set("status", statuses);
@@ -33,11 +36,11 @@
return queryParamsString;
}
function loadPage(pageNumber, itemsNumber, sources, statuses) {
function loadPage(pageNumber, itemsNumber, sources, searches, statuses) {
$("#item-list").fadeTo(100, 0.5); // Smooth fade effect
$("#loading").show();
queryParamsString = getQueryString(pageNumber, itemsNumber, sources, statuses);
queryParamsString = getQueryString(pageNumber, itemsNumber, sources, searches, statuses);
$.ajax({
url: "?" + queryParamsString,
@@ -58,7 +61,7 @@
$(document).on("click", ".pagination a", function (event) {
event.preventDefault();
let page = $(this).attr("data-page");
loadPage(pageNumber=page, itemsNumber=null, sources=null, statuses=null);
loadPage(pageNumber=page, itemsNumber=null, sources=null, searches=null, statuses=null);
});
$(document).ready(function () {
@@ -68,25 +71,63 @@
////////////////////////////////////////////////////////////////////////////
const sourcesToggleAll = $("#toggle-all-sources");
const sourcesCheckboxes = $(".source-checkbox");
const searchesToggleAll = $("#toggle-all-searches");
const searchesCheckboxes = $(".search-checkbox");
const statusesToggleAll = $("#toggle-all-status");
const statusCheckboxes = $(".status-checkbox");
function updateFilters() {
// Get selected sources
let selectedSources = sourcesCheckboxes.filter(":checked").map(function () {
return $(this).val();
}).get().join(",");
// Get selected sources
if (sourcesToggleAll.prop("checked")) {
selectedSources = "all";
}
else {
if (sourcesCheckboxes.filter(":checked").length > 0 ){
selectedSources = sourcesCheckboxes.filter(":checked").map(function () {
return $(this).val();
}).get().join(",");
}
else {
selectedSources = "none";
}
}
// Get selected searches
if (searchesToggleAll.prop("checked")) {
selectedSearches = "all";
}
else {
if (searchesCheckboxes.filter(":checked").length > 0 ){
selectedSearches = searchesCheckboxes.filter(":checked").map(function () {
return $(this).val();
}).get().join(",");
}
else {
selectedSearches = "none";
}
}
// Get selected URL statuses
let selectedStatuses = statusCheckboxes.filter(":checked").map(function () {
return $(this).val();
}).get().join(",");
if (statusesToggleAll.prop("checked")) {
selectedStatuses = "all";
}
else {
if (statusCheckboxes.filter(":checked").length > 0 ){
selectedStatuses = statusCheckboxes.filter(":checked").map(function () {
return $(this).val();
}).get().join(",");
}
else {
selectedStatuses = "none";
}
}
// Get selected items per page
let selectedItems = $("input[name='items']:checked").val();
// Update pagination and reload data
loadPage(1, selectedItems, selectedSources, selectedStatuses);
loadPage(1, selectedItems, selectedSources, selectedSearches, selectedStatuses);
}
////////////////////////////////////////////////////////////////////////////
@@ -101,6 +142,15 @@
sourcesToggleAll.prop("checked", sourcesCheckboxes.length === sourcesCheckboxes.filter(":checked").length);
updateFilters();
});
// Searches
searchesToggleAll.on("change", function () {
searchesCheckboxes.prop("checked", searchesToggleAll.prop("checked"));
updateFilters();
});
searchesCheckboxes.on("change", function () {
searchesToggleAll.prop("checked", searchesCheckboxes.length === searchesCheckboxes.filter(":checked").length);
updateFilters();
});
// Status
statusesToggleAll.on("change", function () {
statusCheckboxes.prop("checked", statusesToggleAll.prop("checked"));
@@ -121,11 +171,15 @@
// Sources
sourcesCheckboxes.each(function () { $(this).prop("checked", true); });
sourcesToggleAll.prop("checked", true);
// Searches
searchesCheckboxes.each(function () { $(this).prop("checked", true); });
searchesToggleAll.prop("checked", true);
// Statuses
statusCheckboxes.each(function () { $(this).prop("checked", true); });
statusesToggleAll.prop("checked", true);
// Items
$("input[name='items'][value='" + 15 + "']").prop("checked", true);
// $("input[name='items'][value='" + 15 + "']").prop("checked", true);
// loadPage(pageNumber=page, itemsNumber=null, sources=null, searches=null, statuses=null);
});
////////////////////////////////////////////////////////////////////////////
@@ -148,6 +202,23 @@
let savedTheme = localStorage.getItem("theme") ||
(window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light");
setTheme(savedTheme);
// Local browser timestamp aware for ts_fetch print
document.querySelectorAll(".timestamp").forEach(function (el) {
const ts = el.getAttribute("data-ts");
if (ts) {
const options = {
day: "2-digit",
month: "2-digit",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false // Use 24-hour format
}; // "en-GB" for DD-MM-YYYY
const localDate = new Date(ts).toLocaleString("en-GB", options); // Adjust to browser's timezone
el.innerHTML = `${localDate}`;
}
});
});
////////////////////////////////////////////////////////////////////////////
</script>
@@ -174,6 +245,9 @@
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1);
padding: 15px;
transition: width 0.3s ease;
/* Enable scrolling */
overflow-y: auto;
max-height: 100vh;
}
#sidebar .nav-link {
@@ -313,10 +387,10 @@
}
th:nth-child(1), td:nth-child(1) { width: 50%; } /* URL column */
th:nth-child(2), td:nth-child(2) { width: 20%; } /* Fetch Date */
th:nth-child(3), td:nth-child(3) { width: 20%; } /* Sources */
th:nth-child(4), td:nth-child(4) { width: 5%; } /* Status */
th:nth-child(5), td:nth-child(5) { width: 5%; } /* Action */
th:nth-child(2), td:nth-child(2) { width: 27.5%; } /* Fetch Date */
th:nth-child(3), td:nth-child(3) { width: 10%; } /* Sources */
th:nth-child(4), td:nth-child(4) { width: 10%; } /* Searches */
th:nth-child(5), td:nth-child(5) { width: 2.5%; } /* Status */
/* ============================= */
/* Pagination Styling */
@@ -407,33 +481,23 @@
<span id="theme-icon">🌙</span>
</button>
</div>
<!-- Sources -->
<div class="nav-item mt-3">
<strong>Select sources</strong>
<form id="source-filter-form">
<!-- Toggle All Checkbox -->
<div class="form-check">
<input class="form-check-input" type="checkbox" id="toggle-all-sources">
<label class="form-check-label fw-bold" for="toggle-all-sources">
Toggle all
</label>
</div>
<!-- Individual Source Checkboxes -->
{% for source in sources %}
<div class="form-check">
<input class="form-check-input source-checkbox" type="checkbox" value="{{ source.id }}" id="source-{{ source.id }}">
<label class="form-check-label" for="source-{{ source.id }}">
{{ source.source }}
</label>
<!-- URLs per page -->
<div class="nav-item mt-3">
<strong>URLs per page</strong>
<div class="card-body">
<!-- Individual Status Checkboxes -->
{% for url_per_page in list_urls_per_page %}
<div class="items-form-check">
<input class="form-check-input items" type="radio" name="items" id="value-{{ url_per_page }}" value="{{ url_per_page }}">
<label class="form-check-label" for="value-{{ url_per_page }}">{{ url_per_page }}</label>
</div>
{% empty %}
<tr>
<td colspan="2" class="text-center">No sources available.</td>
<td colspan="2" class="text-center">No options available.</td>
</tr>
{% endfor %}
</form>
</div>
</div>
<!-- Status -->
@@ -457,6 +521,33 @@
</label>
</div>
{% empty %}
<tr>
<td colspan="2" class="text-center">No statuses available.</td>
</tr>
{% endfor %}
</form>
</div>
<!-- Sources -->
<div class="nav-item mt-3">
<strong>Select sources</strong>
<form id="source-filter-form">
<!-- Toggle All Checkbox -->
<div class="form-check">
<input class="form-check-input" type="checkbox" id="toggle-all-sources">
<label class="form-check-label fw-bold" for="toggle-all-sources">
Toggle all
</label>
</div>
<!-- Individual Source Checkboxes -->
{% for source in sources %}
<div class="form-check">
<input class="form-check-input source-checkbox" type="checkbox" value="{{ source.id }}" id="source-{{ source.id }}">
<label class="form-check-label" for="source-{{ source.id }}">
{{ source.source }}
</label>
</div>
{% empty %}
<tr>
<td colspan="2" class="text-center">No sources available.</td>
</tr>
@@ -464,26 +555,34 @@
</form>
</div>
<!-- URLs per page -->
<!-- Searches -->
<div class="nav-item mt-3">
<strong>URLs per page</strong>
<div class="card-body">
<!-- Individual Status Checkboxes -->
{% for url_per_page in list_urls_per_page %}
<div class="items-form-check">
<input class="form-check-input items" type="radio" name="items" id="value-{{ url_per_page }}" value="{{ url_per_page }}">
<label class="form-check-label" for="value-{{ url_per_page }}">{{ url_per_page }}</label>
<strong>Select searches</strong>
<form id="search-filter-form">
<!-- Toggle All Checkbox -->
<div class="form-check">
<input class="form-check-input" type="checkbox" id="toggle-all-searches">
<label class="form-check-label fw-bold" for="toggle-all-searches">
Toggle all
</label>
</div>
<!-- Individual Search Checkboxes -->
{% for search in searches %}
<div class="form-check">
<input class="form-check-input search-checkbox" type="checkbox" value="{{ search.id }}" id="search-{{ search.id }}">
<label class="form-check-label" for="search-{{ search.id }}">
[{{ search.type }}] {{ search.search }}
</label>
</div>
{% empty %}
<tr>
<td colspan="2" class="text-center">No options available.</td>
<td colspan="2" class="text-center">No search available.</td>
</tr>
{% endfor %}
</div>
</form>
</div>
</ul>
</div>

View File

@@ -7,15 +7,18 @@
<th scope="col"><strong>URL</strong></th>
<th scope="col"><strong>Fetch date</strong></th>
<th scope="col"><strong>Sources</strong></th>
<th scope="col"><strong>Search</strong></th>
<th scope="col"><strong>Status</strong></th>
<th scope="col"><strong>Action</strong></th>
</tr>
</thead>
<tbody>
{% for item in page_obj %}
<tr>
<td><a href="{{ item.url }}/" target="_blank">{{ item.url }}</a></td>
<td>{{ item.ts_fetch }}</td>
<td>
<a href="./{{ item.id }}" class="btn btn-primary btn-sm" target="_blank"></a>
<a href="{{ item.url }}/" target="_blank">{{ item.url }}</a>
</td>
<td class="timestamp" data-ts="{{ item.ts_fetch|date:'c' }}">{{ item.ts_fetch }}</td>
<td>
{% with sources_map|dict_get:item.id as sources %}
{% if sources %}
@@ -27,6 +30,17 @@
{% endif %}
{% endwith %}
</td>
<td>
{% with searches_map|dict_get:item.id as searches %}
{% if searches %}
{% for search in searches %}
<span class="badge bg-secondary">{{ search }}</span>
{% endfor %}
{% else %}
<span class="text-muted">No searches</span>
{% endif %}
{% endwith %}
</td>
<td>
{% if item.status == 'raw' %}
<span class="badge bg-secondary">{{ item.status|capfirst }}</span>
@@ -43,11 +57,7 @@
{% else %}
<span class="badge bg-light">Unknown</span>
{% endif %}
</td>
<td>
<a href="url/{{ item.id }}" class="btn btn-primary btn-sm" target="_blank">Details</a>
</td>
</td>
</tr>
{% empty %}
<tr>

View File

@@ -54,7 +54,7 @@
}
// Fetch URL
let fetchUrl = `/news/url/${urlId}/fetch/?url=${encodeURIComponent(url)}&model=${encodeURIComponent(selectedModel)}&text=${encodeURIComponent(inputText)}`;
let fetchUrl = `/api/url/${urlId}/fetch/?url=${encodeURIComponent(url)}&model=${encodeURIComponent(selectedModel)}&text=${encodeURIComponent(inputText)}`;
let resultContainer = $("#chat-output");
resultContainer.html(""); // Clear previous content before fetching
@@ -99,12 +99,6 @@
// Render Markdown progressively (but safely)
messageContainer.html(marked.parse(accumulatedText));
//////////////////////////////////////
//////////////////////////////////////
// ORIGINAL:
//let text = decoder.decode(value).replace(/\n/g, "<br>");
//resultContainer.append(text); // Append streamed text
//////////////////////////////////////
resultContainer.scrollTop(resultContainer[0].scrollHeight); // Auto-scroll to bottom
return read();
@@ -135,12 +129,16 @@
</tr>
<tr>
<th>Fetch Date</th>
<td>{{ url_item.ts_fetch }}</td>
<td>{{ url_item.ts_fetch }} UTC</td>
</tr>
<tr>
<th>Sources</th>
<th>Source</th>
<td>{{ sources|join:", " }}</td>
</tr>
<tr>
<th>Search</th>
<td>{{ searches|join:", " }}</td>
</tr>
<tr>
<th>Status</th>
<td>{{ url_item.status }}</td>
@@ -175,7 +173,6 @@
<form onsubmit="fetchDetailsWithSelection(event, {{ url_item.id }}, '{{ url_item.url }}')">
<label for="options-{{ url_item.id }}">Model:</label>
<select id="options-{{ url_item.id }}" class="form-control mb-2">
<!-- <option value="">-- Select an option --</option> -->
{% for model in models %}
<option value="{{ model }}">{{ model }}</option>
{% endfor %}
@@ -185,21 +182,23 @@
<!-- Input field with a default value -->
<label for="custom-input-{{ url_item.id }}">Prompt:</label>
<textarea id="custom-input-{{ url_item.id }}" class="form-control mb-2" rows="3">{{ prompt }} {{ url_item.url }}</textarea>
<!-- Fetch details button -->
<button class="btn btn-primary" onclick="fetchDetails({{ url_item.id }}, '{{ url_item.url }}')">
Fetch Details
</button>
<div class="d-flex align-items-center">
<!-- Fetch details button -->
<button class="btn btn-primary" onclick="fetchDetails({{ url_item.id }}, '{{ url_item.url }}')">
Fetch Details
</button>
<!-- Loading Spinner (Hidden by Default) -->
<div id="loading-spinner" class="spinner-border text-primary ms-2" role="status" style="display: none;">
<span class="visually-hidden">Loading...</span>
</div>
</div>
<!-- Chatbot-style response box -->
<div class="chat-box mt-3 p-3 border rounded">
<div id="chat-output"></div>
</div>
<!-- Loading Spinner (Hidden by Default) -->
<div id="loading-spinner" class="spinner-border text-primary mt-3" role="status" style="display: none;">
<span class="visually-hidden">Loading...</span>
</div>
</div>