查看历史记录-history-viewer

This commit is contained in:
gongwenxin
2025-06-26 10:13:39 +08:00
parent ff71f2aed6
commit 3d8b83e992
20 changed files with 42100 additions and 6219 deletions
+93
View File
@@ -0,0 +1,93 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>测试历史记录</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='history_style.css') }}">
</head>
<body>
<div class="container">
<div class="header">
<h1>测试历史记录</h1>
<div class="user-info">
<a href="{{ url_for('llm_config') }}">LLM 标准配置</a> |
<span>欢迎, {{ g.user['username'] }}</span> |
<a href="{{ url_for('logout') }}">登出</a>
</div>
</div>
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul class=flashes>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
<table class="history-table">
<thead>
<tr>
<th>测试时间</th>
<th>状态</th>
<th>总计</th>
<th>通过</th>
<th>失败</th>
<th>错误</th>
<th>跳过</th>
<th>耗时 (秒)</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% if history %}
{% for run in history %}
{% set summary = run.summary %}
{% if summary and 'error' not in summary and 'overall_summary' in summary %}
{% set overall = summary.overall_summary %}
{# 根据成功率计算渐变色 (HSL: 0=红, 120=绿) #}
{% set success_rate_str = overall.test_case_success_rate | default('0%') %}
{% set success_rate = success_rate_str.replace('%', '')|float %}
{% set hue = success_rate * 1.2 %}
{% set status_text = 'PASSED' if overall.test_cases_failed == 0 and overall.test_cases_error == 0 and overall.stages_failed == 0 and overall.stages_error == 0 else 'FAILED' %}
{# 如果通过,行背景为淡绿色,否则为淡红色 #}
<tr class="status-row-{{ 'passed' if status_text == 'PASSED' else 'failed' }}">
<td>{{ run.id.replace('_', ' ') }}</td>
<td>
{# 状态标签使用动态计算的HSL颜色 #}
<span class="status-badge" style="background-color: hsl({{ hue }}, 85%, 45%);">
{{ "%.1f"|format(success_rate) }}%
</span>
</td>
<td>{{ overall.total_test_cases_executed }}</td>
<td>{{ overall.test_cases_passed }}</td>
<td>{{ overall.test_cases_failed }}</td>
<td>{{ overall.test_cases_error }}</td>
<td>{{ overall.test_cases_skipped_in_endpoint }}</td>
<td>{{ "%.2f"|format(summary.duration_seconds|float) }}</td>
<td>
<a href="{{ url_for('show_details', run_id=run.id) }}" class="button">查看详情</a>
</td>
</tr>
{% else %}
<tr>
<td>{{ run.id.replace('_', ' ') }}</td>
<td colspan="8" class="error-row">无法加载此运行的摘要信息 (未运行结束或报告格式不正确)。</td>
</tr>
{% endif %}
{% endfor %}
{% else %}
<tr>
<td colspan="9" style="text-align: center;">没有找到任何测试记录。</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
</body>
</html>
+43
View File
@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>测试详情 - {{ run_id }}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='history_style.css') }}">
</head>
<body>
<div class="container">
<div class="header">
<h1>测试详情: {{ run_id }}</h1>
<div class="header-controls">
<div class="user-info">
<a href="{{ url_for('list_history') }}">返回列表</a> |
<a href="{{ url_for('llm_config') }}">LLM 标准配置</a> |
<span>欢迎, {{ g.user['username'] }}</span> |
<a href="{{ url_for('logout') }}">登出</a>
</div>
<div class="download-actions">
<a href="{{ url_for('download_report', run_id=run_id, filename='summary.json') }}" class="button download-btn">下载JSON报告</a>
<a href="{{ url_for('download_report', run_id=run_id, filename='api_call_details.md') }}" class="button download-btn">下载MD报告</a>
</div>
</div>
</div>
<div class="details-container">
<h2 id="summary-section">测试摘要 (JSON)</h2>
<pre class="json-output">{{ summary_content }}</pre>
<h2 id="details-section">API 调用详情</h2>
<div class="markdown-body">
{{ details_content|safe }}
</div>
</div>
<div class="floating-nav">
<a href="#summary-section" class="button nav-btn" title="跳转到摘要">测试摘要</a>
<a href="#details-section" class="button nav-btn" title="跳转到详情">API调用详情</a>
</div>
</div>
</body>
</html>
+117
View File
@@ -0,0 +1,117 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>LLM 合规性标准配置</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='history_style.css') }}">
<style>
.editor-section { margin-bottom: 20px; }
.info-box { background-color: #f8f9fa; border: 1px solid #dee2e6; padding: 15px; border-radius: 5px; margin-top: 10px; }
.info-box h3 { margin-top: 0; }
.criteria-list-container { margin-bottom: 15px; }
.criteria-item { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.criteria-item input[type="text"] { flex-grow: 1; padding: 8px; border: 1px solid #ccc; border-radius: 4px; }
.delete-btn { background-color: #dc3545; color: white; border: none; padding: 8px 12px; border-radius: 4px; cursor: pointer; transition: background-color 0.2s; }
.delete-btn:hover { background-color: #c82333; }
.add-btn { background-color: #28a745; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>LLM 合规性标准配置</h1>
<div class="user-info">
<a href="{{ url_for('list_history') }}">返回列表</a> |
<span>欢迎, {{ g.user['username'] }}</span> |
<a href="{{ url_for('logout') }}">登出</a>
</div>
</div>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="flash-{{ category }}">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
<div class="config-container">
<div class="editor-section">
<h2>编辑合规性标准</h2>
<p>在这里,您可以直接增删改用于 <code>TC-LLM-COMPLIANCE-001</code> 测试用例的合规性标准规则。</p>
{% if file_exists %}
<form method="post">
<div id="criteria-list-container">
{% for rule in criteria %}
<div class="criteria-item">
<input type="text" name="criteria" value="{{ rule }}" class="form-control">
<button type="button" class="delete-btn">删除</button>
</div>
{% endfor %}
</div>
<button type="button" id="add-rule-btn" class="button add-btn">添加规则</button>
<hr style="margin: 20px 0;">
<input type="submit" value="保存所有标准" class="button">
</form>
{% else %}
<div class="flash-error">
错误:无法找到配置文件 <code>custom_testcases/llm/compliance_criteria.json</code>。请确保该文件存在于正确的位置。
</div>
{% endif %}
</div>
<div class="info-section">
<div class="info-box">
<h3>工作原理说明</h3>
<p>
此测试用例 (<code>TC-LLM-COMPLIANCE-001</code>) 利用大语言模型(LLM)自动评估API是否符合您在左侧定义的合规标准。
</p>
<p>
在测试运行时,系统会收集API的**所有可用信息**(包括URL、请求方法、描述、参数定义、请求体Schema、示例请求和实际响应等),连同您定义的合规标准列表,一同发送给大模型。
</p>
<p>
<strong>重要提示:</strong>大模型仅能基于其接收到的信息进行判断。因此,它非常适合检查命名规范、结构约定、数据格式等问题,但无法验证需要外部知识或业务上下文的规则(例如"此接口必须与XX系统同步")。
</p>
</div>
<div class="info-box">
<h3>发送给LLM的API信息示例</h3>
<p>为了做出判断,系统会向LLM提供类似如下结构的API信息:</p>
<pre class="json-output">{{ example_api_info }}</pre>
</div>
</div>
</div>
</div>
<template id="criteria-item-template">
<div class="criteria-item">
<input type="text" name="criteria" value="" class="form-control" placeholder="输入新的合规标准...">
<button type="button" class="delete-btn">删除</button>
</div>
</template>
<script>
document.addEventListener('DOMContentLoaded', function () {
const listContainer = document.getElementById('criteria-list-container');
const addBtn = document.getElementById('add-rule-btn');
const template = document.getElementById('criteria-item-template');
if (addBtn) {
addBtn.addEventListener('click', function () {
const clone = template.content.cloneNode(true);
listContainer.appendChild(clone);
});
}
if (listContainer) {
listContainer.addEventListener('click', function (e) {
if (e.target && e.target.classList.contains('delete-btn')) {
e.target.closest('.criteria-item').remove();
}
});
}
});
</script>
</body>
</html>
+33
View File
@@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>登录 - DDMS合规性测试</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<div class="container">
<h2>请登录</h2>
{% with messages = get_flashed_messages() %}
{% if messages %}
<div class="flashes">
{% for message in messages %}
<p class="flash-error">{{ message }}</p>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<form method="post" class="form-group">
<label for="username">用户名:</label>
<input type="text" id="username" name="username" required>
<label for="password">密码:</label>
<input type="password" id="password" name="password" required>
<input type="submit" value="登录">
</form>
</div>
</body>
</html>