add:stage
This commit is contained in:
Vendored
+227
@@ -0,0 +1,227 @@
|
||||
/* history_style.css */
|
||||
|
||||
/* Header styles */
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header-controls {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
font-size: 0.9em;
|
||||
color: #555;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.user-info a {
|
||||
text-decoration: none;
|
||||
color: #007bff;
|
||||
}
|
||||
|
||||
.user-info a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.download-btn {
|
||||
background-color: #6c757d;
|
||||
color: white !important;
|
||||
margin-left: 5px;
|
||||
}
|
||||
.download-btn:hover {
|
||||
background-color: #5a6268;
|
||||
}
|
||||
|
||||
/* 悬浮导航按钮 */
|
||||
.floating-nav {
|
||||
position: fixed;
|
||||
bottom: 30px;
|
||||
right: 30px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px; /* 按钮之间的间距 */
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.floating-nav .nav-btn {
|
||||
background-color: #007bff;
|
||||
color: white !important;
|
||||
padding: 10px 15px;
|
||||
border-radius: 5px;
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
|
||||
transition: background-color 0.2s, transform 0.2s;
|
||||
}
|
||||
|
||||
.floating-nav .nav-btn:hover {
|
||||
background-color: #0056b3;
|
||||
transform: scale(1.05); /* 轻微放大效果 */
|
||||
}
|
||||
|
||||
/* Table styles */
|
||||
.history-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 20px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.history-table th, .history-table td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 12px 15px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.history-table th {
|
||||
background-color: #f8f9fa;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.history-table tbody tr:nth-child(even) {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
.history-table tbody tr:hover {
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
|
||||
/* 根据最终状态为行添加柔和的背景色 */
|
||||
.status-row-passed:nth-child(even) {
|
||||
background-color: #e6f7ec;
|
||||
}
|
||||
.status-row-passed:nth-child(odd) {
|
||||
background-color: #f2fbf5;
|
||||
}
|
||||
.status-row-failed:nth-child(even) {
|
||||
background-color: #fbeae5;
|
||||
}
|
||||
.status-row-failed:nth-child(odd) {
|
||||
background-color: #fef4f1;
|
||||
}
|
||||
|
||||
.error-row {
|
||||
color: #dc3545;
|
||||
font-style: italic;
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
/* Status badge styles */
|
||||
.status-badge {
|
||||
padding: 5px 10px;
|
||||
border-radius: 12px;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
font-size: 0.85em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.status-passed {
|
||||
background-color: #28a745; /* Green */
|
||||
}
|
||||
|
||||
.status-failed {
|
||||
background-color: #dc3545; /* Red */
|
||||
}
|
||||
|
||||
/* Button styles */
|
||||
.button {
|
||||
display: inline-block;
|
||||
padding: 8px 12px;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
/* Detail page styles */
|
||||
.details-container {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.json-output {
|
||||
background-color: #f8f9fa;
|
||||
border: 1px solid #dee2e6;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
}
|
||||
|
||||
/* Markdown body styles */
|
||||
.markdown-body {
|
||||
margin-top: 20px;
|
||||
padding: 20px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
background-color: #fff;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.markdown-body h1, .markdown-body h2, .markdown-body h3 {
|
||||
border-bottom: 1px solid #eee;
|
||||
padding-bottom: 0.3em;
|
||||
margin-top: 24px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.markdown-body table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.markdown-body th, .markdown-body td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.markdown-body th {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
.markdown-body pre {
|
||||
background-color: #f6f8fa;
|
||||
padding: 16px;
|
||||
border-radius: 3px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.markdown-body code {
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
background-color: rgba(27,31,35,0.05);
|
||||
padding: 0.2em 0.4em;
|
||||
margin: 0;
|
||||
font-size: 85%;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.markdown-body pre code {
|
||||
background: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
/* PDF container styles */
|
||||
.pdf-container {
|
||||
margin-bottom: 2rem;
|
||||
border: 1px solid #ddd;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.pdf-container iframe {
|
||||
display: block;
|
||||
border: none;
|
||||
}
|
||||
Vendored
+108
@@ -0,0 +1,108 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>API 测试工具</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>API 合规性测试</h1>
|
||||
|
||||
<div class="configuration-section">
|
||||
<h2>测试配置</h2>
|
||||
<form id="test-form">
|
||||
<div class="form-group">
|
||||
<label for="base_url">API 基础URL (必填):</label>
|
||||
<input type="text" id="base_url" name="base_url" required placeholder="例如:http://localhost:8080/api/v1">
|
||||
</div>
|
||||
|
||||
<fieldset>
|
||||
<legend>API 定义源 (选择一个)</legend>
|
||||
<div class="form-group">
|
||||
<label for="api_spec_type">API 规范类型:</label>
|
||||
<select id="api_spec_type" name="api_spec_type">
|
||||
<option value="YAPI">YAPI (.json)</option>
|
||||
<option value="Swagger">Swagger/OpenAPI (.json, .yaml)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="api_spec_file">上传 API 规范文件:</label>
|
||||
<input type="file" id="api_spec_file" name="api_spec_file" accept=".json,.yaml,.yml" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button type="button" id="load-spec-btn">加载分类/标签</button>
|
||||
</div>
|
||||
<div id="yapi-categories-container" class="checkbox-container"></div>
|
||||
<div id="swagger-tags-container" class="checkbox-container"></div>
|
||||
</fieldset>
|
||||
|
||||
<details>
|
||||
<summary>高级配置 (点击展开)</summary>
|
||||
<div class="form-group">
|
||||
<label for="custom_test_cases_dir">自定义测试用例目录:</label>
|
||||
<input type="text" id="custom_test_cases_dir" name="custom_test_cases_dir" placeholder="例如:./custom_testcases" value="./custom_testcases">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="stages_dir">自定义阶段目录:</label>
|
||||
<input type="text" id="stages_dir" name="stages_dir" placeholder="例如:./custom_stages" value="./custom_stages">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="output_dir">报告输出目录:</label>
|
||||
<input type="text" id="output_dir" name="output_dir" placeholder="例如:./test_reports" value="./test_reports">
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>LLM 配置 (可选, 点击展开)</summary>
|
||||
<fieldset>
|
||||
<legend>LLM 配置 (可选)</legend>
|
||||
<div class="form-group">
|
||||
<label for="llm_api_key">LLM API Key:</label>
|
||||
<input type="password" id="llm_api_key" name="llm_api_key" placeholder="留空则尝试读取环境变量">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="llm_base_url">LLM Base URL:</label>
|
||||
<input type="text" id="llm_base_url" name="llm_base_url" placeholder="例如:https://dashscope.aliyuncs.com/compatible-mode/v1">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="llm_model_name">LLM 模型名称:</label>
|
||||
<input type="text" id="llm_model_name" name="llm_model_name" placeholder="例如:qwen-plus">
|
||||
</div>
|
||||
<div class="form-group checkbox-group">
|
||||
<input type="checkbox" id="use_llm_for_request_body" name="use_llm_for_request_body">
|
||||
<label for="use_llm_for_request_body">使用LLM生成请求体</label>
|
||||
</div>
|
||||
<div class="form-group checkbox-group">
|
||||
<input type="checkbox" id="use_llm_for_path_params" name="use_llm_for_path_params">
|
||||
<label for="use_llm_for_path_params">使用LLM生成路径参数</label>
|
||||
</div>
|
||||
<div class="form-group checkbox-group">
|
||||
<input type="checkbox" id="use_llm_for_query_params" name="use_llm_for_query_params">
|
||||
<label for="use_llm_for_query_params">使用LLM生成查询参数</label>
|
||||
</div>
|
||||
<div class="form-group checkbox-group">
|
||||
<input type="checkbox" id="use_llm_for_headers" name="use_llm_for_headers">
|
||||
<label for="use_llm_for_headers">使用LLM生成头部参数</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
</details>
|
||||
|
||||
<button type="submit" class="submit-button">运行测试</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="results-section">
|
||||
<h2>测试日志与结果</h2>
|
||||
<label for="log-output">实时日志:</label>
|
||||
<textarea id="log-output" readonly style="width:100%"></textarea>
|
||||
<div id="results-container">
|
||||
<!-- 测试结果将在此处动态生成 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Vendored
+139
@@ -0,0 +1,139 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const form = document.getElementById('test-form');
|
||||
const logOutput = document.getElementById('log-output');
|
||||
const resultsContainer = document.getElementById('results-container');
|
||||
const loadSpecBtn = document.getElementById('load-spec-btn');
|
||||
const apiSpecFileInput = document.getElementById('api_spec_file');
|
||||
const apiSpecTypeSelect = document.getElementById('api_spec_type');
|
||||
const yapiCategoriesContainer = document.getElementById('yapi-categories-container');
|
||||
const swaggerTagsContainer = document.getElementById('swagger-tags-container');
|
||||
|
||||
// Make the log output area larger as per user request
|
||||
if (logOutput) {
|
||||
logOutput.rows = 25;
|
||||
}
|
||||
|
||||
// Event listener for the new "Load Categories/Tags" button
|
||||
if (loadSpecBtn) {
|
||||
loadSpecBtn.addEventListener('click', async () => {
|
||||
const specType = apiSpecTypeSelect.value;
|
||||
const file = apiSpecFileInput.files[0];
|
||||
|
||||
if (!file) {
|
||||
alert('请先选择一个 API 规范文件。');
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('api_spec_file', file);
|
||||
|
||||
let url = '';
|
||||
let container = null;
|
||||
|
||||
if (specType === 'YAPI') {
|
||||
url = '/list-yapi-categories';
|
||||
container = yapiCategoriesContainer;
|
||||
swaggerTagsContainer.style.display = 'none';
|
||||
yapiCategoriesContainer.style.display = 'block';
|
||||
} else { // Swagger
|
||||
url = '/list-swagger-tags';
|
||||
container = swaggerTagsContainer;
|
||||
yapiCategoriesContainer.style.display = 'none';
|
||||
swaggerTagsContainer.style.display = 'block';
|
||||
}
|
||||
|
||||
container.innerHTML = '<p>正在加载...</p>';
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
body: formData, // Send FormData directly
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({ error: '无法解析错误响应' }));
|
||||
throw new Error(errorData.error || `请求 ${specType} 分类/标签时出错`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
renderItemsList(container, data, specType); // Using a simplified renderer
|
||||
} catch (error) {
|
||||
console.error(`请求${specType}分类时出错:`, error);
|
||||
container.innerHTML = `<p class="error">加载失败: ${error.message}</p>`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
form.addEventListener('submit', async (event) => {
|
||||
event.preventDefault();
|
||||
logOutput.value = '正在开始测试...\n';
|
||||
resultsContainer.innerHTML = '';
|
||||
|
||||
// FormData will correctly handle all form fields, including the file upload
|
||||
const formData = new FormData(form);
|
||||
|
||||
try {
|
||||
const response = await fetch('/run-tests', {
|
||||
method: 'POST',
|
||||
// For FormData, the browser sets the Content-Type to multipart/form-data with the correct boundary.
|
||||
// Do not set the 'Content-Type' header manually.
|
||||
body: formData,
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
// Try to parse the error, provide a fallback message.
|
||||
const errorMessage = result.error || '运行测试时发生未知错误';
|
||||
logOutput.value += `\n错误: ${errorMessage}`;
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
// Restore summary to the log output
|
||||
logOutput.value += '\n测试执行完成。\n\n';
|
||||
logOutput.value += '--- 测试摘要 ---\n';
|
||||
logOutput.value += JSON.stringify(result.summary, null, 2);
|
||||
|
||||
displayResults(result);
|
||||
|
||||
} catch (error) {
|
||||
console.error('运行测试时捕获到错误:', error);
|
||||
logOutput.value += `\n\n发生严重错误: ${error.message}`;
|
||||
resultsContainer.innerHTML = `<p class="error">测试运行失败: ${error.message}</p>`;
|
||||
}
|
||||
});
|
||||
|
||||
// A simplified function to render categories/tags as a list
|
||||
function renderItemsList(container, items, type) {
|
||||
if (!items || items.length === 0) {
|
||||
container.innerHTML = '<p>未找到任何项。</p>';
|
||||
return;
|
||||
}
|
||||
let html = `<h4>${type} ${type === 'YAPI' ? '分类' : '标签'}:</h4><ul>`;
|
||||
items.forEach(item => {
|
||||
html += `<li><strong>${item.name}</strong>: ${item.description || '无描述'}</li>`;
|
||||
});
|
||||
html += '</ul>';
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
function displayResults(result) {
|
||||
// Per user request, only show download links and remove the summary view.
|
||||
let linksHtml = '<h3>下载报告</h3>';
|
||||
if (result.summary_report_path) {
|
||||
linksHtml += `<p><a href="${result.summary_report_path}" target="_blank" class="report-link">摘要报告 (JSON)</a></p>`;
|
||||
}
|
||||
if (result.details_report_path) {
|
||||
linksHtml += `<p><a href="${result.details_report_path}" target="_blank" class="report-link">API 调用详情 (Markdown)</a></p>`;
|
||||
}
|
||||
|
||||
if (!result.summary_report_path && !result.details_report_path) {
|
||||
linksHtml += '<p>没有可用的报告文件。</p>';
|
||||
}
|
||||
|
||||
resultsContainer.innerHTML = linksHtml;
|
||||
}
|
||||
});
|
||||
|
||||
// The old functions fetchYapiCategories, fetchSwaggerTags, and renderCheckboxes are no longer needed
|
||||
// and should be removed if they exist elsewhere in this file.
|
||||
Vendored
+161
@@ -0,0 +1,161 @@
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background-color: #f4f7f6;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
background-color: #fff;
|
||||
padding: 25px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #2c3e50;
|
||||
text-align: center;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #34495e;
|
||||
border-bottom: 2px solid #ecf0f1;
|
||||
padding-bottom: 10px;
|
||||
margin-top: 30px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
font-weight: bold;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.form-group input[type="text"],
|
||||
.form-group input[type="password"],
|
||||
.form-group input[type="url"] {
|
||||
width: calc(100% - 22px);
|
||||
padding: 10px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-group input[type="text"]:focus,
|
||||
.form-group input[type="password"]:focus,
|
||||
.form-group input[type="url"]:focus {
|
||||
border-color: #3498db;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.checkbox-group label {
|
||||
font-weight: normal;
|
||||
display: inline-block;
|
||||
margin-left: 5px;
|
||||
}
|
||||
.checkbox-group input[type="checkbox"] {
|
||||
margin-right: 5px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
border: 1px solid #ddd;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
legend {
|
||||
padding: 0 10px;
|
||||
font-weight: bold;
|
||||
color: #3498db;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
padding: 8px 12px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9em;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.action-button:hover {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
|
||||
.submit-button {
|
||||
background-color: #2ecc71;
|
||||
color: white;
|
||||
padding: 12px 20px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 1.1em;
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.submit-button:hover {
|
||||
background-color: #27ae60;
|
||||
}
|
||||
|
||||
.results-section {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
#status-area {
|
||||
font-weight: bold;
|
||||
margin-bottom: 15px;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
background-color: #ecf0f1;
|
||||
border: 1px solid #bdc3c7;
|
||||
}
|
||||
|
||||
#results-output {
|
||||
background-color: #2c3e50;
|
||||
color: #ecf0f1;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
white-space: pre-wrap; /* Allows wrapping and preserves whitespace */
|
||||
word-wrap: break-word; /* Breaks long words to prevent overflow */
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
font-family: "Courier New", Courier, monospace;
|
||||
}
|
||||
|
||||
#report-link-area p {
|
||||
margin-top: 10px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.categories-tags-container {
|
||||
margin-top: 10px;
|
||||
padding: 10px;
|
||||
background-color: #f9f9f9;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 4px;
|
||||
max-height: 150px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.categories-tags-container div {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.categories-tags-container label {
|
||||
font-weight: normal;
|
||||
}
|
||||
Reference in New Issue
Block a user