restyle log entry page
This commit is contained in:
@@ -43,3 +43,11 @@
|
||||
100% 10px;
|
||||
background-attachment: local, local, scroll, scroll;
|
||||
}
|
||||
|
||||
.log-entry-table-header-cell {
|
||||
@apply border-b border-slate-200 bg-slate-50 px-2 py-1 text-xs font-medium uppercase tracking-wider text-slate-500;
|
||||
}
|
||||
|
||||
.log-entry-table-row-cell {
|
||||
@apply flex items-center border-b border-slate-200 px-2 py-1 text-sm group-hover:bg-slate-50;
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ class LogEntriesController < ApplicationController
|
||||
HttpLogEntry.includes(
|
||||
:caused_by_entry,
|
||||
:triggered_entries,
|
||||
response: :base,
|
||||
:response,
|
||||
).find(params[:id])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -36,67 +36,68 @@
|
||||
<% end %>
|
||||
</div>
|
||||
<%= render partial: "shared/pagination_controls", locals: { collection: @log_entries } %>
|
||||
<div class="grid grid-cols-[auto_auto_auto_auto_1fr_auto_auto_auto] border-b border-slate-300 text-sm max-w-screen-lg mx-auto">
|
||||
<div class='grid-row contents'>
|
||||
<div class="grid-cell text-center font-semibold">ID</div>
|
||||
<div class="grid-cell text-right font-semibold">Size</div>
|
||||
<div class="grid-cell text-center font-semibold">Time</div>
|
||||
<div class="grid-cell text-center font-semibold">Status</div>
|
||||
<div class="grid-cell text-center font-semibold">URI</div>
|
||||
<div class="grid-cell text-center font-semibold"></div>
|
||||
<div class="grid-cell text-center font-semibold">Type</div>
|
||||
<div class="grid-cell text-center font-semibold">Response</div>
|
||||
<div class="grid grid-cols-[auto_auto_auto_auto_1fr_auto_auto_auto] max-w-screen-lg mx-auto overflow-hidden border border-slate-200 bg-white shadow mb-4 rounded-lg">
|
||||
<div class='contents'>
|
||||
<div class="log-entry-table-header-cell text-center rounded-tl">ID</div>
|
||||
<div class="log-entry-table-header-cell text-right">Size</div>
|
||||
<div class="log-entry-table-header-cell text-center">Time</div>
|
||||
<div class="log-entry-table-header-cell text-center">Status</div>
|
||||
<div class="log-entry-table-header-cell text-center">URI</div>
|
||||
<div class="log-entry-table-header-cell text-center"><!-- External link --></div>
|
||||
<div class="log-entry-table-header-cell text-left">Type</div>
|
||||
<div class="log-entry-table-header-cell text-right rounded-tr">Resp</div>
|
||||
</div>
|
||||
<div class="col-span-full border-b border-slate-300"></div>
|
||||
<% @log_entries.each do |hle| %>
|
||||
<div class="grid-row contents">
|
||||
<div class="grid-cell text-center">
|
||||
<%= link_to hle.id, log_entry_path(hle.id), class: "text-blue-600 hover:text-blue-800" %>
|
||||
<div class="contents group">
|
||||
<div class="log-entry-table-row-cell justify-end">
|
||||
<%= link_to hle.id, log_entry_path(hle.id), class: "text-blue-600 hover:text-blue-800 font-medium" %>
|
||||
</div>
|
||||
<div class="grid-cell text-right">
|
||||
<%= HexUtil.humansize(hle.response.size) %>
|
||||
<div class="log-entry-table-row-cell justify-end">
|
||||
<%= HexUtil.humansize(hle.response_size) %>
|
||||
</div>
|
||||
<div class="grid-cell text-right">
|
||||
<div class="log-entry-table-row-cell text-right">
|
||||
<%= time_ago_in_words(hle.created_at, include_seconds: true) %> ago
|
||||
</div>
|
||||
<div class="grid-cell text-center">
|
||||
<span class="<%= hle.status_code == 200 ? 'text-green-600' : 'text-red-600' %>">
|
||||
<div class="log-entry-table-row-cell text-center">
|
||||
<span class="<%= hle.status_code == 200 ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800' %> px-2 py-1 rounded-full text-xs font-medium">
|
||||
<%= hle.status_code %>
|
||||
</span>
|
||||
</div>
|
||||
<div class="grid-cell min-w-0">
|
||||
<div class="log-entry-table-row-cell text-right min-w-0">
|
||||
<% iterative_parts = path_iterative_parts(hle.uri_path) %>
|
||||
<div class="flex truncate whitespace-nowrap overflow-hidden ">
|
||||
<a class="bg-slate-100 rounded-l hover:bg-slate-200" href="/log_entries/filter/<%= hle.uri_host %>">
|
||||
<%= hle.uri_scheme %>://<%= hle.uri_host %>
|
||||
<div class="flex items-center px-2 space-x-0 bg-slate-100 rounded group-hover:border-slate-700 border truncate group-hover:overflow-visible group-hover:z-10 relative">
|
||||
<a class="py-1 bg-slate-100 rounded-l hover:bg-slate-200 transition-colors" href="/log_entries/filter/<%= hle.uri_host %>">
|
||||
<span class="text-slate-600"><%= hle.uri_scheme %>://</span><span class="font-medium"><%= hle.uri_host %></span>
|
||||
</a>
|
||||
<%- iterative_parts.each_with_index do |(part, up_to), index| -%>
|
||||
<% uri_and_up_to = hle.uri_host + up_to %>
|
||||
<a
|
||||
class="bg-slate-100 <%= index == iterative_parts.length-1 ? 'rounded-r' : '' %> hover:bg-slate-200"
|
||||
class="py-1 hover:bg-slate-200 transition-colors"
|
||||
href="/log_entries/filter/<%= uri_and_up_to %>"
|
||||
title="<%= hle.uri_scheme + "://" + uri_and_up_to %>"
|
||||
>/<%= part %></a>
|
||||
>/<span class="font-medium text-nowrap"><%= part %></span></a>
|
||||
<%- end -%>
|
||||
<%- if hle.uri_query -%>
|
||||
<% query_parsed = URI.decode_www_form(hle.uri_query).to_h %>
|
||||
<span class="text-slate-600 min-w-0" title="<%= query_parsed.pretty_inspect %>">
|
||||
<%= "?#{hle.uri_query}" %>
|
||||
</span><%- end -%>
|
||||
<span class="py-1 text-slate-500" title="<%= query_parsed.pretty_inspect %>">
|
||||
?<%= hle.uri_query %>
|
||||
</span>
|
||||
<%- end -%>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid-cell text-center">
|
||||
<%= link_to hle.uri.to_s, class: "text-blue-600 hover:text-blue-800", target: "_blank", rel: "noreferrer" do %>
|
||||
<%= render partial: "shared/icons/external_link", locals: { class_name: "w-4 h-4 inline" } %>
|
||||
<div class="log-entry-table-row-cell text-center">
|
||||
<%= link_to hle.uri.to_s, class: "text-blue-600 hover:text-blue-800 transition-colors", target: "_blank", rel: "noreferrer" do %>
|
||||
<%= render partial: "shared/icons/external_link", locals: { class_name: "w-4 h-4" } %>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="grid-cell max-w-24 truncate">
|
||||
<span title="<%= hle.content_type %>"> <%= hle.content_type %> </span>
|
||||
<div class="log-entry-table-row-cell">
|
||||
<span class="max-w-24 truncate inline-block" title="<%= hle.content_type %>">
|
||||
<%= hle.content_type %>
|
||||
</span>
|
||||
</div>
|
||||
<div class="grid-cell text-right">
|
||||
<div class="justify-end log-entry-table-row-cell">
|
||||
<%= hle.response_time_ms %>ms
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-full border-b border-slate-300"></div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -17,16 +17,43 @@
|
||||
</style>
|
||||
<% end %>
|
||||
<div class="bg-slate-50">
|
||||
<div class="mx-auto flex max-w-screen-md flex-row py-4">
|
||||
<div class="ml-4 flex min-w-[150px] items-center pr-4">
|
||||
<div class="mx-auto flex flex-col sm:flex-row max-w-screen-md py-4">
|
||||
<div class="ml-4 flex min-w-[150px] items-center pr-4 mb-2 sm:mb-0 justify-between">
|
||||
<%= link_to log_entries_path,
|
||||
class:
|
||||
"px-4 py-2 text-sm font-medium text-slate-700 bg-white border border-slate-300 rounded-md hover:bg-slate-50" do %>
|
||||
<span class="mr-1">←</span>
|
||||
<span>Log Entries</span>
|
||||
<% end %>
|
||||
<button type="button"
|
||||
onclick="toggleDetails(this)"
|
||||
class="sm:hidden ml-2 px-2 py-1 text-sm text-slate-700 bg-white border border-slate-300 rounded-md hover:bg-slate-50">
|
||||
<span id="expandIcon" class="hidden">▼</span>
|
||||
<span id="collapseIcon">▲</span>
|
||||
</button>
|
||||
<script>
|
||||
function toggleDetails(button) {
|
||||
const expandIcon = button.querySelector('#expandIcon');
|
||||
const collapseIcon = button.querySelector('#collapseIcon');
|
||||
const details = document.getElementById('log-entry-details');
|
||||
expandIcon.classList.toggle('hidden');
|
||||
collapseIcon.classList.toggle('hidden');
|
||||
details.classList.toggle('shadow-[inset_0_-5px_5px_-5px_rgba(0,0,0,0.2)]');
|
||||
|
||||
if (expandIcon.classList.contains('hidden')) {
|
||||
details.style.maxHeight = details.scrollHeight + 'px';
|
||||
} else {
|
||||
details.style.maxHeight = '100px';
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
const details = document.getElementById('log-entry-details');
|
||||
details.style.maxHeight = details.scrollHeight + 'px';
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
<div class="rounded-lg bg-white p-4 shadow">
|
||||
<div id="log-entry-details" class="sm:rounded-lg bg-white p-4 shadow transition-[max-height] duration-300 overflow-hidden">
|
||||
<div class="grid grid-cols-[auto_1fr] items-center gap-x-4 gap-y-2">
|
||||
<div class="text-right text-sm text-slate-500">URL</div>
|
||||
<div>
|
||||
@@ -61,11 +88,7 @@
|
||||
><%= rtms == -1 ? "Response time not recorded" : "#{rtms}ms" %></span
|
||||
>
|
||||
<span class="mx-2">•</span>
|
||||
<span
|
||||
><%= HexUtil.humansize(@log_entry.response.size) %>
|
||||
(<%= HexUtil.humansize(@log_entry.response.bytes_stored) %>
|
||||
stored)</span
|
||||
>
|
||||
<span><%= HexUtil.humansize(@log_entry.response.size_bytes) %></span>
|
||||
<span class="mx-2">•</span>
|
||||
<span>Performed by <%= @log_entry.performed_by %></span>
|
||||
<span class="mx-2">•</span>
|
||||
@@ -88,17 +111,6 @@
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% if @log_entry.response.base %>
|
||||
<div class="text-right text-sm text-slate-500">Base Entry</div>
|
||||
<div>
|
||||
<% base_hle = HttpLogEntry.find_by(response: @log_entry.response.base) %>
|
||||
<% if base_hle %>
|
||||
<%= render partial: "log_entry_table_row_mini", locals: { entry: base_hle } %>
|
||||
<% else %>
|
||||
<span class="italic text-slate-500">HLE not found...</span>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if @log_entry.caused_by_entry %>
|
||||
<div class="text-right text-sm text-slate-500">Caused By</div>
|
||||
<div class="overflow-hidden rounded border border-slate-200">
|
||||
|
||||
@@ -1,37 +1,65 @@
|
||||
<% content_for :head do %>
|
||||
<style type="text/css" data-turbolinks-track>
|
||||
.grid-cell {
|
||||
padding: 0.25rem;
|
||||
padding: 0.25rem 1rem;
|
||||
border-right: 1px solid #e2e8f0;
|
||||
}
|
||||
.grid-cell:last-child {
|
||||
padding-left: 0;
|
||||
padding-right: 1rem;
|
||||
border-right: none;
|
||||
}
|
||||
.grid-cell:first-child {
|
||||
padding-left: 1rem;
|
||||
}
|
||||
.grid-row:hover .grid-cell {
|
||||
background-color: #f1f5f9;
|
||||
transition: background-color 150ms ease-in-out;
|
||||
}
|
||||
.stats-card {
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid #e2e8f0;
|
||||
background-color: white;
|
||||
padding: 1rem;
|
||||
box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
||||
}
|
||||
.stats-grid {
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid #e2e8f0;
|
||||
background-color: white;
|
||||
box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
||||
overflow: hidden;
|
||||
}
|
||||
.stats-grid-header {
|
||||
background-color: #f8fafc;
|
||||
padding: 0.75rem 1rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: #334155;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
.stats-grid-header:hover {
|
||||
background-color: #f1f5f9;
|
||||
}
|
||||
.sort-indicator::after {
|
||||
content: "▼";
|
||||
font-size: 0.75rem;
|
||||
margin-left: 0.25rem;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.sort-indicator.asc::after {
|
||||
content: "▲";
|
||||
}
|
||||
</style>
|
||||
<% end %>
|
||||
|
||||
<div class="mx-auto mt-4 text-center sm:mt-6">
|
||||
<h1 class="text-2xl">Http Request Log Stats</h1>
|
||||
<div class="mt-2 text-lg">
|
||||
<div class="mx-auto max-w-5xl px-4 mt-6 sm:mt-8">
|
||||
<h1 class="text-2xl font-bold text-slate-900 text-center">HTTP Request Log Stats</h1>
|
||||
<div class="mt-2 text-center">
|
||||
<%= link_to "Back to Index",
|
||||
log_entries_path,
|
||||
class: "text-blue-600 hover:text-blue-800" %>
|
||||
class: "text-blue-600 hover:text-blue-800 transition-colors" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mx-auto mt-4 text-center">
|
||||
<div class="space-x-2 text-lg">
|
||||
<div class="mt-6 text-center">
|
||||
<div class="space-x-2">
|
||||
<% [10.seconds, 30.seconds, 1.minute, 5.minutes, 30.minutes].each do |time_window| %>
|
||||
<% if @time_window == time_window %>
|
||||
<span class="rounded bg-blue-100 px-2 py-1">
|
||||
<span class="rounded-full bg-blue-100 px-3 py-1 text-sm font-medium text-blue-800">
|
||||
<%= if time_window < 1.minute
|
||||
pluralize(time_window.seconds, "second")
|
||||
else
|
||||
@@ -48,74 +76,141 @@
|
||||
end
|
||||
),
|
||||
stats_log_entries_path(seconds: time_window.in_seconds),
|
||||
class: "text-blue-600 hover:text-blue-800 hover:bg-blue-50 px-2 py-1 rounded",
|
||||
class: "rounded-full px-3 py-1 text-sm text-blue-600 hover:text-blue-800 hover:bg-blue-50 transition-colors",
|
||||
) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-lg">
|
||||
<%= @last_window_count %> requests in last
|
||||
<div class="stats-card mt-6">
|
||||
<div class="text-xl font-bold text-slate-900">
|
||||
<%= @last_window_count %> requests
|
||||
<span class="text-base font-normal text-slate-600">
|
||||
in last
|
||||
<%= if @time_window < 1.minute
|
||||
pluralize(@time_window.seconds, "second")
|
||||
else
|
||||
time_ago_in_words(@time_window.ago)
|
||||
end %>
|
||||
<span class="text-slate-600">
|
||||
(<%= (@last_window_count.to_f / @time_window.in_seconds).round(1) %>/sec)
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="mt-2 text-slate-600">
|
||||
<%= HexUtil.humansize(@last_window_bytes) %> bytes transferred •
|
||||
<%= HexUtil.humansize(@last_window_bytes / @time_window.in_seconds) %>/sec
|
||||
<div class="mt-1 text-slate-600">
|
||||
<span class="font-medium"><%= (@last_window_count.to_f / @time_window.in_seconds).round(1) %></span> requests/sec
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mx-auto mt-6">
|
||||
<h2 class="mb-2 text-xl">By content type</h2>
|
||||
<div class="grid grid-cols-[1fr_auto_auto] border-b border-slate-300 text-sm">
|
||||
<div class="mt-1 text-sm text-slate-600">
|
||||
<span class="font-medium"><%= HexUtil.humansize(@last_window_bytes) %></span> transferred •
|
||||
<span class="font-medium"><%= HexUtil.humansize(@last_window_bytes / @time_window.in_seconds) %></span>/sec
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-8">
|
||||
<h2 class="text-xl font-bold text-slate-900 mb-3">By Content Type</h2>
|
||||
<div class="stats-grid">
|
||||
<div class="grid grid-cols-[1fr_auto_auto] sortable-table" id="content-type-table">
|
||||
<div class="grid-row contents">
|
||||
<div class="grid-cell font-semibold">Content Type</div>
|
||||
<div class="grid-cell text-right font-semibold">Requests</div>
|
||||
<div class="grid-cell text-right font-semibold">Transferred</div>
|
||||
<div class="stats-grid-header sort-indicator" data-column="content_type">Content Type</div>
|
||||
<div class="stats-grid-header text-right sort-indicator" data-column="count">Requests</div>
|
||||
<div class="stats-grid-header text-right sort-indicator" data-column="bytes">Transferred</div>
|
||||
</div>
|
||||
<div class="col-span-full border-b border-slate-300"></div>
|
||||
<% @content_type_counts
|
||||
.sort_by { |_ignore, stats| -stats[:count] }
|
||||
.each do |content_type, stats| %>
|
||||
<div class="grid-row contents">
|
||||
<div class="grid-cell"><%= content_type %></div>
|
||||
<div class="grid-cell text-right"><%= stats[:count] %></div>
|
||||
<div class="grid-cell text-right">
|
||||
<div class="grid-row contents" data-content-type="<%= content_type %>" data-count="<%= stats[:count] %>" data-bytes="<%= stats[:bytes] %>">
|
||||
<div class="grid-cell font-medium text-slate-900 text-sm"><%= content_type %></div>
|
||||
<div class="grid-cell text-right text-slate-600 text-sm"><%= stats[:count] %></div>
|
||||
<div class="grid-cell text-right text-slate-600 font-medium text-sm">
|
||||
<%= HexUtil.humansize(stats[:bytes]) %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-full border-b border-slate-300"></div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mx-auto mt-6">
|
||||
<h2 class="mb-2 text-xl">By domain</h2>
|
||||
<div class="grid grid-cols-[1fr_auto_auto] border-b border-slate-300 text-sm">
|
||||
<div class="grid-row contents">
|
||||
<div class="grid-cell font-semibold">Domain</div>
|
||||
<div class="grid-cell text-right font-semibold">Requests</div>
|
||||
<div class="grid-cell text-right font-semibold">Transferred</div>
|
||||
</div>
|
||||
<div class="col-span-full border-b border-slate-300"></div>
|
||||
</div>
|
||||
<div class="mt-8 mb-8">
|
||||
<h2 class="text-xl font-bold text-slate-900 mb-3">By Domain</h2>
|
||||
<div class="stats-grid">
|
||||
<div class="grid grid-cols-[1fr_auto_auto] sortable-table" id="domain-table">
|
||||
<div class="grid-row contents">
|
||||
<div class="stats-grid-header sort-indicator" data-column="domain">Domain</div>
|
||||
<div class="stats-grid-header text-right sort-indicator" data-column="count">Requests</div>
|
||||
<div class="stats-grid-header text-right sort-indicator" data-column="bytes">Transferred</div>
|
||||
</div>
|
||||
<% @by_domain_counts
|
||||
.sort_by { |_ignore, stats| -stats[:bytes] }
|
||||
.each do |domain, stats| %>
|
||||
<div class="grid-row contents">
|
||||
<div class="grid-cell"><%= domain %></div>
|
||||
<div class="grid-cell text-right"><%= stats[:count] %></div>
|
||||
<div class="grid-cell text-right">
|
||||
<div class="grid-row contents" data-domain="<%= domain %>" data-count="<%= stats[:count] %>" data-bytes="<%= stats[:bytes] %>">
|
||||
<div class="grid-cell font-medium text-slate-900 text-sm"><%= domain %></div>
|
||||
<div class="grid-cell text-right text-slate-600 text-sm"><%= stats[:count] %></div>
|
||||
<div class="grid-cell text-right text-slate-600 font-medium text-sm">
|
||||
<%= HexUtil.humansize(stats[:bytes]) %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-full border-b border-slate-300"></div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize sortable tables
|
||||
document.querySelectorAll('.sortable-table').forEach(table => {
|
||||
initSortableTable(table);
|
||||
});
|
||||
|
||||
function initSortableTable(table) {
|
||||
const headers = table.querySelectorAll('.stats-grid-header');
|
||||
|
||||
headers.forEach(header => {
|
||||
header.addEventListener('click', function() {
|
||||
const column = this.getAttribute('data-column');
|
||||
const isAsc = !this.classList.contains('asc');
|
||||
|
||||
// Reset all headers
|
||||
headers.forEach(h => h.classList.remove('asc'));
|
||||
|
||||
// Set current header state
|
||||
if (isAsc) {
|
||||
this.classList.add('asc');
|
||||
}
|
||||
|
||||
// Get all rows except the header row
|
||||
const rows = Array.from(table.querySelectorAll('.grid-row:not(:first-child)'));
|
||||
|
||||
// Sort the rows
|
||||
rows.sort((a, b) => {
|
||||
let aValue, bValue;
|
||||
|
||||
if (table.id === 'content-type-table') {
|
||||
if (column === 'content_type') {
|
||||
aValue = a.getAttribute('data-content-type');
|
||||
bValue = b.getAttribute('data-content-type');
|
||||
return isAsc ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue);
|
||||
} else {
|
||||
aValue = parseInt(a.getAttribute(`data-${column}`), 10);
|
||||
bValue = parseInt(b.getAttribute(`data-${column}`), 10);
|
||||
}
|
||||
} else if (table.id === 'domain-table') {
|
||||
if (column === 'domain') {
|
||||
aValue = a.getAttribute('data-domain');
|
||||
bValue = b.getAttribute('data-domain');
|
||||
return isAsc ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue);
|
||||
} else {
|
||||
aValue = parseInt(a.getAttribute(`data-${column}`), 10);
|
||||
bValue = parseInt(b.getAttribute(`data-${column}`), 10);
|
||||
}
|
||||
}
|
||||
|
||||
return isAsc ? aValue - bValue : bValue - aValue;
|
||||
});
|
||||
|
||||
// Remove existing rows
|
||||
rows.forEach(row => row.remove());
|
||||
|
||||
// Append sorted rows
|
||||
rows.forEach(row => {
|
||||
table.appendChild(row);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -10,7 +10,9 @@ START_PROMETHEUS_EXPORTER =
|
||||
# always start in sever mode
|
||||
Rails.const_defined?("Server") ||
|
||||
# always start in worker mode
|
||||
(Rails.env == "worker"),
|
||||
(Rails.env == "worker") ||
|
||||
# ran with args when in server mode, but no top level task
|
||||
(ARGV.any? && Rake.application.top_level_tasks.empty?),
|
||||
T::Boolean,
|
||||
)
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe LogEntriesController, type: :controller do
|
||||
render_views
|
||||
let(:user) { create(:user, :admin) }
|
||||
|
||||
context "when user is not signed in" do
|
||||
@@ -18,24 +19,59 @@ RSpec.describe LogEntriesController, type: :controller do
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET #show" do
|
||||
it "redirects to sign in" do
|
||||
get :show, params: { id: 1 }
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when user is signed in" do
|
||||
before { sign_in user }
|
||||
|
||||
describe "GET #index" do
|
||||
it "returns filtered log entries" do
|
||||
it "renders the index template with filtered log entries" do
|
||||
get :index, params: { filter: "example.com/test" }
|
||||
expect(response).to be_successful
|
||||
expect(response).to render_template(:index)
|
||||
end
|
||||
|
||||
it "shows existing log entries matching the filter" do
|
||||
log_entry =
|
||||
create(
|
||||
:http_log_entry,
|
||||
uri_host: "example.com",
|
||||
uri_path: "/test",
|
||||
uri_scheme: "https",
|
||||
)
|
||||
|
||||
get :index, params: { filter: "example.com/test" }
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(assigns(:log_entries)).to include(log_entry)
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET #stats" do
|
||||
it "returns statistics in the specified time window" do
|
||||
it "renders the stats template with time window data" do
|
||||
get :stats, params: { seconds: 3600 }
|
||||
expect(response).to be_successful
|
||||
expect(response).to render_template(:stats)
|
||||
expect(assigns(:time_window)).to eq 3600.seconds
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET #show" do
|
||||
let(:log_entry) { create(:http_log_entry) }
|
||||
|
||||
it "renders the show template with the requested log entry" do
|
||||
get :show, params: { id: log_entry.id }
|
||||
expect(response).to be_successful
|
||||
expect(response).to render_template(:show)
|
||||
expect(assigns(:log_entry)).to eq(log_entry)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user