Add anlytics info and search to logs page
This commit is contained in:
3
app.py
3
app.py
@@ -187,7 +187,8 @@ async def execute_http_function(user_id, function):
|
|||||||
request_data if log_request else {},
|
request_data if log_request else {},
|
||||||
response_data['result'] if (log_response or response_data['status'] != 'SUCCESS') else {},
|
response_data['result'] if (log_response or response_data['status'] != 'SUCCESS') else {},
|
||||||
response_data['logs'],
|
response_data['logs'],
|
||||||
version_number)
|
version_number,
|
||||||
|
response_data.get('execution_time'))
|
||||||
|
|
||||||
if response_data['status'] != 'SUCCESS':
|
if response_data['status'] != 'SUCCESS':
|
||||||
return render_template("function_error.html", function_name=function_name ,error=response_data['result'], logs=response_data['logs'])
|
return render_template("function_error.html", function_name=function_name ,error=response_data['result'], logs=response_data['logs'])
|
||||||
|
|||||||
6
db.py
6
db.py
@@ -95,13 +95,13 @@ class DataBase():
|
|||||||
self.execute(
|
self.execute(
|
||||||
'DELETE FROM http_functions WHERE user_id=%s AND id=%s', [user_id, function_id], commit=True)
|
'DELETE FROM http_functions WHERE user_id=%s AND id=%s', [user_id, function_id], commit=True)
|
||||||
|
|
||||||
def add_http_function_invocation(self, http_function_id, status, request_data, response_data, logs, version_number):
|
def add_http_function_invocation(self, http_function_id, status, request_data, response_data, logs, version_number, execution_time):
|
||||||
self.execute(
|
self.execute(
|
||||||
'INSERT INTO http_function_invocations (http_function_id, status, request_data, response_data, logs, version_number) VALUES (%s, %s, %s, %s, %s, %s)', [http_function_id, status, json.dumps(request_data), json.dumps(response_data), json.dumps(logs), version_number], commit=True)
|
'INSERT INTO http_function_invocations (http_function_id, status, request_data, response_data, logs, version_number, execution_time) VALUES (%s, %s, %s, %s, %s, %s, %s)', [http_function_id, status, json.dumps(request_data), json.dumps(response_data), json.dumps(logs), version_number, execution_time], commit=True)
|
||||||
|
|
||||||
def get_http_function_invocations(self, http_function_id):
|
def get_http_function_invocations(self, http_function_id):
|
||||||
http_function_invocations = self.execute(
|
http_function_invocations = self.execute(
|
||||||
"""SELECT id, http_function_id, STATUS, invocation_time, request_data, response_data, LOGS, version_number
|
"""SELECT id, http_function_id, STATUS, invocation_time, request_data, response_data, LOGS, version_number, execution_time
|
||||||
FROM http_function_invocations
|
FROM http_function_invocations
|
||||||
WHERE http_function_id=%s
|
WHERE http_function_id=%s
|
||||||
ORDER BY invocation_time DESC""", [http_function_id])
|
ORDER BY invocation_time DESC""", [http_function_id])
|
||||||
|
|||||||
@@ -190,11 +190,7 @@ def logs(function_id):
|
|||||||
if not http_function:
|
if not http_function:
|
||||||
return jsonify({'error': 'Function not found'}), 404
|
return jsonify({'error': 'Function not found'}), 404
|
||||||
name = http_function['name']
|
name = http_function['name']
|
||||||
http_function_invocations = db.execute("""
|
http_function_invocations = db.get_http_function_invocations(function_id)
|
||||||
SELECT id, http_function_id, STATUS, invocation_time, request_data, response_data, LOGS, version_number
|
|
||||||
FROM http_function_invocations
|
|
||||||
WHERE http_function_id=%s
|
|
||||||
ORDER BY invocation_time DESC""", [function_id])
|
|
||||||
if htmx:
|
if htmx:
|
||||||
return render_block(environment, 'dashboard/http_functions/logs.html', 'page', user_id=user_id, function_id=function_id, name=name, http_function_invocations=http_function_invocations)
|
return render_block(environment, 'dashboard/http_functions/logs.html', 'page', user_id=user_id, function_id=function_id, name=name, http_function_invocations=http_function_invocations)
|
||||||
return render_template("dashboard/http_functions/logs.html", user_id=user_id, name=name, function_id=function_id, http_function_invocations=http_function_invocations)
|
return render_template("dashboard/http_functions/logs.html", user_id=user_id, name=name, function_id=function_id, http_function_invocations=http_function_invocations)
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ CREATE TABLE timer_function_invocations (
|
|||||||
invocation_time TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
invocation_time TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
logs JSONB,
|
logs JSONB,
|
||||||
version_number INT NOT NULL,
|
version_number INT NOT NULL,
|
||||||
|
execution_time FLOAT,
|
||||||
|
|
||||||
CONSTRAINT fk_timer_function_invocations
|
CONSTRAINT fk_timer_function_invocations
|
||||||
FOREIGN KEY (timer_function_id)
|
FOREIGN KEY (timer_function_id)
|
||||||
@@ -402,9 +403,9 @@ def logs(function_id):
|
|||||||
|
|
||||||
# Fetch the invocation logs
|
# Fetch the invocation logs
|
||||||
timer_function_invocations = db.execute("""
|
timer_function_invocations = db.execute("""
|
||||||
SELECT id, timer_function_id, status, invocation_time,
|
SELECT id, timer_function_id, status, invocation_time,
|
||||||
logs, version_number
|
logs, version_number, execution_time
|
||||||
FROM timer_function_invocations
|
FROM timer_function_invocations
|
||||||
WHERE timer_function_id = %s
|
WHERE timer_function_id = %s
|
||||||
ORDER BY invocation_time DESC
|
ORDER BY invocation_time DESC
|
||||||
LIMIT 100
|
LIMIT 100
|
||||||
|
|||||||
97
templates/dashboard/analytics.html
Normal file
97
templates/dashboard/analytics.html
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
<div class="mb-6">
|
||||||
|
<!-- Search and Filters -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<input type="text" id="logSearch" onkeyup="filterLogs()" placeholder="Search logs..."
|
||||||
|
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Analytics Cards -->
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
||||||
|
<div class="bg-white p-4 rounded-lg shadow-sm border border-gray-200">
|
||||||
|
<h3 class="text-sm font-medium text-gray-500">Total Invocations</h3>
|
||||||
|
<p class="text-2xl font-semibold text-gray-800">{{ invocations | length }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white p-4 rounded-lg shadow-sm border border-gray-200">
|
||||||
|
<h3 class="text-sm font-medium text-gray-500">Success Rate</h3>
|
||||||
|
{% set successful_invocations = invocations | selectattr('status', 'equalto', 'SUCCESS') | list %}
|
||||||
|
<p class="text-2xl font-semibold text-gray-800">
|
||||||
|
{% if invocations %}
|
||||||
|
{{ "%.2f"|format((successful_invocations|length / invocations|length) * 100) }}%
|
||||||
|
{% else %}
|
||||||
|
N/A
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white p-4 rounded-lg shadow-sm border border-gray-200">
|
||||||
|
<h3 class="text-sm font-medium text-gray-500">Avg. Execution Time</h3>
|
||||||
|
{% set total_execution_time = invocations | sum(attribute='execution_time') %}
|
||||||
|
<p class="text-2xl font-semibold text-gray-800">
|
||||||
|
{% if invocations %}
|
||||||
|
{{ "%.2f"|format(total_execution_time / invocations|length) }}ms
|
||||||
|
{% else %}
|
||||||
|
N/A
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Time-series Chart -->
|
||||||
|
<div class="bg-white p-4 rounded-lg shadow-sm border border-gray-200">
|
||||||
|
<h3 class="text-base sm:text-lg font-medium text-gray-900 mb-2 sm:mb-4">Invocation History</h3>
|
||||||
|
<div class="relative h-48 sm:h-64">
|
||||||
|
<svg class="w-full h-full" viewBox="0 0 400 200" preserveAspectRatio="none">
|
||||||
|
<!-- Y-axis -->
|
||||||
|
<line x1="40" y1="20" x2="40" y2="180" stroke="#E5E7EB" stroke-width="1" />
|
||||||
|
<!-- X-axis -->
|
||||||
|
<line x1="40" y1="180" x2="380" y2="180" stroke="#E5E7EB" stroke-width="1" />
|
||||||
|
|
||||||
|
{% if invocations %}
|
||||||
|
<!-- Execution time line -->
|
||||||
|
{% set max_execution_time = [1] %}
|
||||||
|
{% for inv in invocations %}
|
||||||
|
{% if inv.execution_time > max_execution_time[0] %}{% set _ = max_execution_time.pop() %}{{
|
||||||
|
max_execution_time.append(inv.execution_time) }}{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% set path = namespace(d='M') %}
|
||||||
|
{% for inv in invocations | reverse %}
|
||||||
|
{% set x = 40 + (loop.index0 * (340 / (invocations|length - 1 if invocations|length > 1 else 1))) %}
|
||||||
|
{% set y = 180 - ((inv.execution_time / max_execution_time[0]) * 160) %}
|
||||||
|
{% if loop.first %}
|
||||||
|
{% set path.d = path.d ~ x ~ "," ~ y %}
|
||||||
|
{% else %}
|
||||||
|
{% set path.d = path.d ~ " L" ~ x ~ "," ~ y %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<path d="{{ path.d }}" stroke="#10B981" stroke-width="2" fill="none" />
|
||||||
|
|
||||||
|
<!-- Data points -->
|
||||||
|
{% for inv in invocations | reverse %}
|
||||||
|
{% set x = 40 + (loop.index0 * (340 / (invocations|length - 1 if invocations|length > 1 else 1))) %}
|
||||||
|
{% set y = 180 - ((inv.execution_time / max_execution_time[0]) * 160) %}
|
||||||
|
<circle cx="{{ x }}" cy="{{ y }}" r="4" fill="#10B981">
|
||||||
|
<title>{{ inv.invocation_time.strftime('%Y-%m-%d %H:%M:%S') }}: {{ "%.2f"|format(inv.execution_time)
|
||||||
|
}}ms</title>
|
||||||
|
</circle>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function filterLogs() {
|
||||||
|
const searchInput = document.getElementById('logSearch').value.toLowerCase();
|
||||||
|
const logRows = document.querySelectorAll('.log-row');
|
||||||
|
logRows.forEach(row => {
|
||||||
|
const logContent = row.textContent.toLowerCase();
|
||||||
|
if (logContent.includes(searchInput)) {
|
||||||
|
row.style.display = '';
|
||||||
|
} else {
|
||||||
|
row.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -14,6 +14,7 @@ logs_url=url_for('http.logs', function_id=function_id),
|
|||||||
history_url=url_for('http.history', function_id=function_id)) }}
|
history_url=url_for('http.history', function_id=function_id)) }}
|
||||||
|
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
|
{{ render_partial('dashboard/analytics.html', invocations=http_function_invocations) }}
|
||||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
|
<div class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
|
||||||
<!-- Headers -->
|
<!-- Headers -->
|
||||||
<div class="grid md:grid-cols-4 gap-4 bg-gray-50 p-4 border-b border-gray-200">
|
<div class="grid md:grid-cols-4 gap-4 bg-gray-50 p-4 border-b border-gray-200">
|
||||||
@@ -26,7 +27,7 @@ history_url=url_for('http.history', function_id=function_id)) }}
|
|||||||
<!-- Data Rows -->
|
<!-- Data Rows -->
|
||||||
{% for invocation in http_function_invocations %}
|
{% for invocation in http_function_invocations %}
|
||||||
<div
|
<div
|
||||||
class="grid md:grid-cols-4 gap-4 p-4 hover:bg-gray-50 transition-colors duration-150 border-b border-gray-200">
|
class="log-row grid md:grid-cols-4 gap-4 p-4 hover:bg-gray-50 transition-colors duration-150 border-b border-gray-200">
|
||||||
<!-- Timestamp and Status -->
|
<!-- Timestamp and Status -->
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ logs_url=url_for('timer.logs', function_id=function_id),
|
|||||||
history_url=url_for('timer.history', function_id=function_id)) }}
|
history_url=url_for('timer.history', function_id=function_id)) }}
|
||||||
|
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
|
{{ render_partial('dashboard/analytics.html', invocations=timer_function_invocations) }}
|
||||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
|
<div class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
|
||||||
<!-- Headers -->
|
<!-- Headers -->
|
||||||
<div class="grid md:grid-cols-3 gap-4 bg-gray-50 p-4 border-b border-gray-200">
|
<div class="grid md:grid-cols-3 gap-4 bg-gray-50 p-4 border-b border-gray-200">
|
||||||
@@ -24,7 +25,7 @@ history_url=url_for('timer.history', function_id=function_id)) }}
|
|||||||
<!-- Data Rows -->
|
<!-- Data Rows -->
|
||||||
{% for invocation in timer_function_invocations %}
|
{% for invocation in timer_function_invocations %}
|
||||||
<div
|
<div
|
||||||
class="grid md:grid-cols-3 gap-4 p-4 hover:bg-gray-50 transition-colors duration-150 border-b border-gray-200">
|
class="log-row grid md:grid-cols-3 gap-4 p-4 hover:bg-gray-50 transition-colors duration-150 border-b border-gray-200">
|
||||||
<!-- Timestamp -->
|
<!-- Timestamp -->
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
|
|||||||
@@ -55,14 +55,15 @@ async def execute_timer_function_async(timer_function):
|
|||||||
|
|
||||||
# Record the invocation
|
# Record the invocation
|
||||||
db.execute("""
|
db.execute("""
|
||||||
INSERT INTO timer_function_invocations
|
INSERT INTO timer_function_invocations
|
||||||
(timer_function_id, status, logs, version_number)
|
(timer_function_id, status, logs, version_number, execution_time)
|
||||||
VALUES (%s, %s, %s, %s)
|
VALUES (%s, %s, %s, %s, %s)
|
||||||
""", [
|
""", [
|
||||||
timer_function['id'],
|
timer_function['id'],
|
||||||
response_data['status'],
|
response_data['status'],
|
||||||
json.dumps(response_data['logs']),
|
json.dumps(response_data['logs']),
|
||||||
version_number
|
version_number,
|
||||||
|
response_data.get('execution_time')
|
||||||
], commit=True)
|
], commit=True)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
Reference in New Issue
Block a user