1387 lines
48 KiB
HTML
1387 lines
48 KiB
HTML
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<title>CGNCIO Task Report System</title>
|
||
<style>
|
||
body {
|
||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||
margin: 0;
|
||
padding: 20px;
|
||
background-color: #f5f5f5;
|
||
}
|
||
|
||
.container {
|
||
max-width: 1300px;
|
||
margin: 0 auto;
|
||
background-color: white;
|
||
padding: 20px;
|
||
border-radius: 10px;
|
||
box-shadow: 0 0 10px rgba(0,0,0,0.1);
|
||
position: relative;
|
||
}
|
||
|
||
h1 {
|
||
color: #333;
|
||
text-align: center;
|
||
margin-bottom: 30px;
|
||
font-size: 2.5em;
|
||
}
|
||
|
||
.table-container {
|
||
overflow-x: auto;
|
||
}
|
||
|
||
table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
margin-top: 20px;
|
||
background-color: white;
|
||
}
|
||
|
||
th, td {
|
||
padding: 12px 15px;
|
||
text-align: left;
|
||
border-bottom: 1px solid #ddd;
|
||
}
|
||
|
||
th {
|
||
background-color: #8f8f8f;
|
||
color: white;
|
||
font-weight: 500;
|
||
}
|
||
|
||
tr:nth-child(even) {
|
||
background-color: #f9f9f9;
|
||
}
|
||
|
||
tr:hover {
|
||
background-color: #f5f5f5;
|
||
}
|
||
|
||
.timestamp {
|
||
color: #666;
|
||
font-size: 0.9em;
|
||
}
|
||
|
||
.status-header {
|
||
text-align: center;
|
||
padding: 20px;
|
||
background-color: #fff2d7;
|
||
border-radius: 5px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.total-tasks {
|
||
color: #c48613;
|
||
font-weight: bold;
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.container {
|
||
padding: 10px;
|
||
}
|
||
|
||
th, td {
|
||
padding: 8px 10px;
|
||
}
|
||
}
|
||
|
||
/* 添加导出按钮样式 */
|
||
.export-btn {
|
||
background-color: #4CAF50;
|
||
color: white;
|
||
padding: 10px 20px;
|
||
border: none;
|
||
border-radius: 5px;
|
||
cursor: pointer;
|
||
font-size: 16px;
|
||
margin: 10px 0;
|
||
transition: background-color 0.3s;
|
||
}
|
||
|
||
.export-btn:hover {
|
||
background-color: #45a049;
|
||
}
|
||
|
||
.button-container {
|
||
text-align: right;
|
||
margin-bottom: 0px;
|
||
}
|
||
|
||
.filter-container {
|
||
margin: 20px 0;
|
||
padding: 15px;
|
||
background-color: #f8f9fa;
|
||
border-radius: 8px;
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 15px;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.filter-group {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
|
||
.filter-btn {
|
||
background-color: #4CAF50;
|
||
color: white;
|
||
padding: 8px 15px;
|
||
border: none;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.filter-btn:hover {
|
||
background-color: #45a049;
|
||
}
|
||
|
||
.filter-btn.clear {
|
||
background-color: #6c757d;
|
||
}
|
||
|
||
input[type="date"], input[type="text"] {
|
||
width: 120px;
|
||
padding: 6px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
/* 添加返回按钮样式 */
|
||
.back-btn {
|
||
position: absolute;
|
||
top: 20px;
|
||
left: 20px;
|
||
background: none; /* 无背景 */
|
||
border: none;
|
||
font-size: 16px;
|
||
color: #666;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
padding: 10px;
|
||
transition: color 0.3s;
|
||
}
|
||
|
||
.back-btn:hover {
|
||
color: #333;
|
||
}
|
||
|
||
/* 排序按钮样式 */
|
||
.sort-btn {
|
||
cursor: pointer;
|
||
padding: 0 0px;
|
||
color: #fff;
|
||
user-select: none;
|
||
}
|
||
|
||
.sort-btn:hover {
|
||
color: #ddd;
|
||
}
|
||
|
||
/* 排序图标(箭头)样式 */
|
||
.sort-icon {
|
||
position: relative;
|
||
display: inline-block;
|
||
width: 8px;
|
||
height: 10px;
|
||
margin-left: 5px;
|
||
vertical-align: middle;
|
||
}
|
||
|
||
/* 向上箭头 */
|
||
.sort-icon:before {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
border-left: 4px solid transparent;
|
||
border-right: 4px solid transparent;
|
||
border-bottom: 4px solid #fff;
|
||
}
|
||
|
||
/* 向下箭头 */
|
||
.sort-icon:after {
|
||
content: '';
|
||
position: absolute;
|
||
bottom: 0;
|
||
left: 0;
|
||
border-left: 4px solid transparent;
|
||
border-right: 4px solid transparent;
|
||
border-top: 4px solid #fff;
|
||
}
|
||
|
||
/* 升序时只显示向上箭头 */
|
||
.sort-asc .sort-icon:after {
|
||
display: none;
|
||
}
|
||
|
||
/* 降序时只显示向下箭头 */
|
||
.sort-desc .sort-icon:before {
|
||
display: none;
|
||
}
|
||
|
||
/* Tab 容器样式 */
|
||
.tab-container {
|
||
margin: 20px 0;
|
||
}
|
||
|
||
.tabs {
|
||
display: flex;
|
||
gap: 15px; /* 增加 tab 之间的间距 */
|
||
border-bottom: 2px solid #ff9900;
|
||
padding-bottom: 0; /* 移除底部内边距 */
|
||
margin-bottom: 20px;
|
||
position: relative; /* 用于定位伪元素 */
|
||
}
|
||
|
||
/* Tab 按钮样式 */
|
||
.tab {
|
||
padding: 12px 25px; /* 增加内边距使 tab 更大 */
|
||
cursor: pointer;
|
||
border: 2px solid #ff9900;
|
||
border-bottom: none; /* 移除底部边框 */
|
||
border-radius: 8px 8px 0 0; /* 增加圆角 */
|
||
background-color: #fff2d7; /* 浅色背景 */
|
||
color: #ff9900;
|
||
font-weight: 500;
|
||
font-size: 15px; /* 增加字体大小 */
|
||
position: relative;
|
||
bottom: -2px; /* 对齐底部边框 */
|
||
}
|
||
|
||
/* 鼠标悬停效果 */
|
||
.tab:hover {
|
||
background-color: #ffead1; /* 更浅的背景色 */
|
||
transform: translateY(-2px); /* 轻微上浮效果 */
|
||
}
|
||
|
||
/* 激活状态样式 */
|
||
.tab.active {
|
||
background-color: #ff9900;
|
||
color: white;
|
||
border-bottom: 2px solid #ff9900;
|
||
bottom: -2px;
|
||
transform: translateY(0); /* 保持位置 */
|
||
box-shadow: 0 -2px 5px rgba(0,0,0,0.1); /* 添加阴影 */
|
||
}
|
||
|
||
/* Tab 内容区域样式 */
|
||
.tab-content {
|
||
background-color: white;
|
||
border-radius: 0 0 8px 8px;
|
||
padding: 20px;
|
||
box-shadow: 0 2px 5px rgba(0,0,0,0.05);
|
||
}
|
||
|
||
/* 底部状态栏样式 */
|
||
.bottom-bar {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-top: 20px;
|
||
padding: 15px;
|
||
background-color: #fff2d7;
|
||
border-radius: 5px;
|
||
}
|
||
|
||
.status-info {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.total-tasks {
|
||
margin-right: 20px;
|
||
color: #c48613;
|
||
font-weight: bold;
|
||
}
|
||
|
||
/* 工具统计页面样式 */
|
||
.tools-container {
|
||
display: flex;
|
||
gap: 20px;
|
||
margin-top: 20px;
|
||
}
|
||
|
||
.tool-list, .task-details {
|
||
flex: 1;
|
||
background: white;
|
||
padding: 20px;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.tool-row {
|
||
cursor: pointer;
|
||
transition: background-color 0.3s;
|
||
}
|
||
|
||
.tool-row:hover {
|
||
background-color: #fff2d7;
|
||
}
|
||
|
||
.tool-row.selected {
|
||
background-color: #ffe4b3;
|
||
}
|
||
|
||
h3 {
|
||
color: #333;
|
||
margin-bottom: 5px;
|
||
padding-bottom: 5px;
|
||
}
|
||
|
||
/* 筛选器样式 */
|
||
.filter-group label {
|
||
font-weight: 500;
|
||
color: #666;
|
||
}
|
||
|
||
.filter-group input[type="date"],
|
||
.filter-group select {
|
||
padding: 5px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.button-group {
|
||
margin-left: auto;
|
||
display: flex;
|
||
gap: 10px;
|
||
}
|
||
|
||
.filter-btn:not(.clear) {
|
||
background-color: #00aa0e;
|
||
color: white;
|
||
}
|
||
|
||
.filter-btn:not(.clear):hover {
|
||
background-color: #01a701;
|
||
}
|
||
|
||
.filter-btn.clear {
|
||
background-color: #6c757d;
|
||
color: white;
|
||
}
|
||
|
||
.filter-btn.clear:hover {
|
||
background-color: #5a6268;
|
||
}
|
||
|
||
/* 统一容器样式 */
|
||
.statistics-container {
|
||
background: white;
|
||
padding: 30px;
|
||
border-radius: 10px;
|
||
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||
margin-top: 20px;
|
||
position: relative;
|
||
}
|
||
|
||
.statistics-content {
|
||
display: flex;
|
||
gap: 60px;
|
||
}
|
||
|
||
/* 统计区域样式 */
|
||
.statistics-section {
|
||
flex: 1;
|
||
}
|
||
|
||
/* 工具名称高亮样式 */
|
||
.tool-highlight {
|
||
background-color: #fff2d7; /* 淡橙色背景 */
|
||
padding: 2px 8px; /* 添加内边距使背景更明显 */
|
||
border-radius: 4px; /* 圆角 */
|
||
}
|
||
|
||
/* 标题样式 */
|
||
h3 {
|
||
color: #333;
|
||
margin-bottom: 20px;
|
||
font-size: 1.2em;
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* 表格样式 */
|
||
#toolsTable, #taskDetailsTable {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
}
|
||
|
||
#toolsTable th, #taskDetailsTable th,
|
||
#toolsTable td, #taskDetailsTable td {
|
||
padding: 12px;
|
||
text-align: left;
|
||
border-bottom: 1px solid #ddd;
|
||
}
|
||
|
||
#toolsTable th, #taskDetailsTable th {
|
||
background-color: #8f8f8f;
|
||
color: white;
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* 确保响应式布局 */
|
||
@media (max-width: 1200px) {
|
||
.statistics-content {
|
||
flex-direction: column;
|
||
}
|
||
|
||
.statistics-section {
|
||
width: 100%;
|
||
}
|
||
}
|
||
|
||
/* 选项框容器样式 */
|
||
.view-select-container {
|
||
position: absolute;
|
||
top: 10px;
|
||
right: 20px;
|
||
transform: none;
|
||
}
|
||
|
||
/* 选项框样式 */
|
||
#viewSelect {
|
||
padding: 6px 12px;
|
||
font-size: 12px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
background-color: white;
|
||
cursor: pointer;
|
||
outline: none;
|
||
min-width: 100px;
|
||
text-align: left;
|
||
color: #666;
|
||
}
|
||
|
||
#viewSelect:hover {
|
||
border-color: #ff9900;
|
||
}
|
||
|
||
#viewSelect:focus {
|
||
border-color: #ff9900;
|
||
box-shadow: 0 0 0 2px rgba(255, 153, 0, 0.1);
|
||
}
|
||
|
||
#chartView {
|
||
height: 500px;
|
||
width: 100%;
|
||
display: none; /* 初始隐藏 */
|
||
}
|
||
|
||
#chartView.active {
|
||
display: block;
|
||
}
|
||
|
||
#trendView {
|
||
height: 500px;
|
||
width: 100%;
|
||
}
|
||
|
||
#trendView.active {
|
||
display: block;
|
||
}
|
||
|
||
/* 复选框样式 */
|
||
.checkbox-label {
|
||
display: flex;
|
||
align-items: center;
|
||
cursor: pointer;
|
||
color: #666;
|
||
font-size: 15px;
|
||
}
|
||
|
||
.checkbox-label input[type="checkbox"] {
|
||
cursor: pointer;
|
||
}
|
||
|
||
.pagination {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
margin: 20px 0;
|
||
gap: 10px;
|
||
}
|
||
|
||
.page-link {
|
||
padding: 8px 12px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
color: #333;
|
||
text-decoration: none;
|
||
}
|
||
|
||
.page-link:hover {
|
||
background-color: #f5f5f5;
|
||
}
|
||
|
||
.current-page {
|
||
padding: 8px 12px;
|
||
background-color: #ff9900;
|
||
color: white;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.ellipsis {
|
||
color: #666;
|
||
}
|
||
</style>
|
||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns"></script>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<!-- 返回按钮 -->
|
||
<button class="back-btn" onclick="window.location.href='/'">
|
||
← Back
|
||
</button>
|
||
|
||
<h1>Task Report System</h1>
|
||
|
||
<!-- Tab 导航 -->
|
||
<div class="tab-container">
|
||
<div class="tabs">
|
||
<div class="tab {% if active_tab == 'data' %}active{% endif %}"
|
||
onclick="switchTab('data')">Data Details</div>
|
||
<div class="tab {% if active_tab == 'tools' %}active{% endif %}"
|
||
onclick="switchTab('tools')">Tool Statistics</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tab 内容 -->
|
||
<div id="dataTab" class="tab-content"
|
||
style="display: {% if active_tab == 'data' %}block{% else %}none{% endif %}">
|
||
<!-- 筛选器 -->
|
||
<div class="filter-container">
|
||
<!-- 日期范围筛选 -->
|
||
<div class="filter-group">
|
||
<label>Date Range: </label>
|
||
<input type="date" id="start-date" name="start-date"
|
||
value="{{ filters.start_date or '' }}">
|
||
<span>to</span>
|
||
<input type="date" id="end-date" name="end-date"
|
||
value="{{ filters.end_date or '' }}">
|
||
</div>
|
||
|
||
<!-- Tool Name 筛选 -->
|
||
<div class="filter-group">
|
||
<label>Tool Name:</label>
|
||
<input type="text" id="tool-name-filter"
|
||
value="{{ filters.tool_name or '' }}"
|
||
placeholder="Input Tool Name">
|
||
</div>
|
||
|
||
<!-- Task Name 筛选 -->
|
||
<div class="filter-group">
|
||
<label>Task Name:</label>
|
||
<input type="text" id="task-name-filter"
|
||
value="{{ filters.task_name or '' }}"
|
||
placeholder="Input Task Name">
|
||
</div>
|
||
|
||
<!-- 添加 Debug 数据显示控制 -->
|
||
<div class="filter-group">
|
||
<label class="checkbox-label">
|
||
<input type="checkbox" id="show-debug"
|
||
{% if filters.show_debug %}checked{% endif %}
|
||
onchange="applyFilters()">
|
||
Show Debug Data
|
||
</label>
|
||
</div>
|
||
|
||
<!-- 筛选按钮 -->
|
||
<div class="button-group">
|
||
<button class="filter-btn" onclick="applyFilters()">Apply</button>
|
||
<button class="filter-btn clear" onclick="clearFilters()">Clear</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 表格 -->
|
||
<div class="table-container">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th class="sortable" onclick="sortTable('timestamp')">
|
||
Timestamp
|
||
<span class="sort-btn" id="timestamp_sort">
|
||
<span class="sort-icon"></span>
|
||
</span>
|
||
</th>
|
||
<th>Username</th>
|
||
<th>Tool Name</th>
|
||
<th>Task Name</th>
|
||
<th class="sortable" onclick="sortTable('time_saved')">
|
||
Time Saved (s)
|
||
<span class="sort-btn" id="time_saved_sort">
|
||
<span class="sort-icon"></span>
|
||
</span>
|
||
</th>
|
||
<th class="sortable" onclick="sortTable('time_cost')">
|
||
Time Cost (s)
|
||
<span class="sort-btn" id="time_cost_sort">
|
||
<span class="sort-icon"></span>
|
||
</span>
|
||
</th>
|
||
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for task in tasks %}
|
||
<tr>
|
||
<td class="timestamp">{{ task[4] }}</td>
|
||
<td>{{ task[1] }}</td>
|
||
<td>{{ task[2] }}</td>
|
||
<td>{{ task[3] }}</td>
|
||
<td>{{ task[5] }}</td>
|
||
<td>{{ task[6] }}</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="pagination">
|
||
<!-- 上一页 -->
|
||
{% if pagination.has_prev %}
|
||
<a href="{{ url_for('query_task_data',
|
||
page=pagination.prev_num,
|
||
per_page=per_page,
|
||
tool_name=filters.tool_name,
|
||
task_name=filters.task_name,
|
||
start_date=filters.start_date,
|
||
end_date=filters.end_date,
|
||
sort_by=filters.sort_by,
|
||
sort_direction=filters.sort_direction,
|
||
show_debug=filters.show_debug,
|
||
tab=active_tab) }}"
|
||
class="page-link">上一页</a>
|
||
{% endif %}
|
||
|
||
<!-- 页码 -->
|
||
{% for page_num in pagination.iter_pages(left_edge=2, left_current=2, right_current=3, right_edge=2) %}
|
||
{% if page_num %}
|
||
{% if page_num == pagination.page %}
|
||
<span class="current-page">{{ page_num }}</span>
|
||
{% else %}
|
||
<a href="{{ url_for('query_task_data',
|
||
page=page_num,
|
||
per_page=per_page,
|
||
tool_name=filters.tool_name,
|
||
task_name=filters.task_name,
|
||
start_date=filters.start_date,
|
||
end_date=filters.end_date,
|
||
sort_by=filters.sort_by,
|
||
sort_direction=filters.sort_direction,
|
||
show_debug=filters.show_debug,
|
||
tab=active_tab) }}"
|
||
class="page-link">{{ page_num }}</a>
|
||
{% endif %}
|
||
{% else %}
|
||
<span class="ellipsis">...</span>
|
||
{% endif %}
|
||
{% endfor %}
|
||
|
||
<!-- 下一页 -->
|
||
{% if pagination.has_next %}
|
||
<a href="{{ url_for('query_task_data',
|
||
page=pagination.next_num,
|
||
per_page=per_page,
|
||
tool_name=filters.tool_name,
|
||
task_name=filters.task_name,
|
||
start_date=filters.start_date,
|
||
end_date=filters.end_date,
|
||
sort_by=filters.sort_by,
|
||
sort_direction=filters.sort_direction,
|
||
show_debug=filters.show_debug,
|
||
tab=active_tab) }}"
|
||
class="page-link">下一页</a>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<!-- 底部状态栏 -->
|
||
<div class="bottom-bar">
|
||
<div class="status-info">
|
||
<span class="total-tasks">Total Tasks: {{ tasks|length }}</span>
|
||
</div>
|
||
<div class="button-container">
|
||
<button class="export-btn" onclick="window.location.href='/export-csv'">
|
||
Export to CSV
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="toolsTab" class="tab-content"
|
||
style="display: {% if active_tab == 'tools' %}block{% else %}none{% endif %}">
|
||
<!-- 添加筛选器 -->
|
||
<div class="filter-container">
|
||
<!-- 日期范围筛选 -->
|
||
<div class="filter-group">
|
||
<label>Month:</label>
|
||
<select id="month-select" onchange="handleMonthSelect()">
|
||
<option value="">Select Month</option>
|
||
{% for month in range(1, 13) %}
|
||
<option value="{{ month }}"
|
||
{% if filters.month == month|string %}selected{% endif %}>
|
||
{{ month }}月
|
||
</option>
|
||
{% endfor %}
|
||
</select>
|
||
<select id="year-select" onchange="handleMonthSelect()">
|
||
{% for year in range(2023, 2026) %}
|
||
<option value="{{ year }}"
|
||
{% if filters.year == year|string %}selected{% endif %}
|
||
{% if not filters.year and year == 2025 %}selected{% endif %}>
|
||
{{ year }}
|
||
</option>
|
||
{% endfor %}
|
||
</select>
|
||
|
||
<label style="margin-left: 30px;">Date Range:</label>
|
||
<input type="date" id="tools-start-date" name="tools-start-date"
|
||
value="{{ filters.tools_start_date or '' }}">
|
||
<span>to</span>
|
||
<input type="date" id="tools-end-date" name="tools-end-date"
|
||
value="{{ filters.tools_end_date or '' }}">
|
||
</div>
|
||
|
||
<!-- 筛选按钮 -->
|
||
<div class="filter-buttons">
|
||
<button class="filter-btn" onclick="applyToolsFilter()">Apply</button>
|
||
<button class="filter-btn clear" onclick="clearToolsFilter()">Clear</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 统一的容器 -->
|
||
<div class="statistics-container">
|
||
<!-- 添加选项框 -->
|
||
<div class="view-select-container">
|
||
<select id="viewSelect" onchange="switchView(this.value)">
|
||
<option value="details" selected>Statistic Details</option>
|
||
<option value="chart">Statistic Chart</option>
|
||
<option value="trend">Trend Chart</option>
|
||
</select>
|
||
</div>
|
||
|
||
<!-- 表格视图 -->
|
||
<div id="tableView" class="statistics-content">
|
||
<!-- 左侧工具列表 -->
|
||
<div class="statistics-section">
|
||
<h3>Tool Statistics</h3>
|
||
<table id="toolsTable">
|
||
<thead>
|
||
<tr>
|
||
<th>Index</th>
|
||
<th>Tool Name</th>
|
||
<th class="sortable" onclick="sortToolTable('total_requests')">
|
||
Total Request
|
||
<span class="sort-btn" id="total_requests_sort">
|
||
<span class="sort-icon"></span>
|
||
</span>
|
||
</th>
|
||
<th>Time Saved (s)</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for tool in tool_stats %}
|
||
<tr onclick="showTaskDetails('{{ tool.tool_name }}')"
|
||
class="tool-row {% if loop.first %}selected{% endif %}">
|
||
<td>{{ loop.index }}</td>
|
||
<td>{{ tool.tool_name }}</td>
|
||
<td>{{ tool.total_requests }}</td>
|
||
<td>{{ tool.time_saved }}</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<!-- 右侧任务详情 -->
|
||
<div class="statistics-section">
|
||
<h3 id="selectedToolTitle">Task Details</h3>
|
||
<table id="taskDetailsTable">
|
||
<thead>
|
||
<tr>
|
||
<th>Index</th>
|
||
<th>Task Name</th>
|
||
<th>Total Request</th>
|
||
<th>Time Saved (s)</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="taskDetailsBody">
|
||
<!-- 动态填充内容 -->
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 图表视图 -->
|
||
<div id="chartView" class="statistics-content" style="display: none;">
|
||
<canvas id="toolStatsChart"></canvas>
|
||
</div>
|
||
|
||
<!-- 添加趋势图容器 -->
|
||
<div id="trendView" class="statistics-content" style="display: none;">
|
||
<canvas id="trendChart"></canvas>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<script>
|
||
function applyFilters() {
|
||
const startDate = document.getElementById('start-date').value;
|
||
const endDate = document.getElementById('end-date').value;
|
||
const taskName = document.getElementById('task-name-filter').value;
|
||
const toolName = document.getElementById('tool-name-filter').value;
|
||
const showDebug = document.getElementById('show-debug').checked;
|
||
|
||
// 构建查询参数
|
||
const params = new URLSearchParams();
|
||
if (startDate) params.append('start_date', startDate);
|
||
if (endDate) params.append('end_date', endDate);
|
||
if (taskName) params.append('task_name', taskName);
|
||
if (toolName) params.append('tool_name', toolName);
|
||
params.append('show_debug', showDebug);
|
||
|
||
// 发送请求
|
||
window.location.href = `/query_task_data?${params.toString()}`;
|
||
}
|
||
|
||
function clearFilters() {
|
||
document.getElementById('start-date').value = '';
|
||
document.getElementById('end-date').value = '';
|
||
document.getElementById('task-name-filter').value = '';
|
||
document.getElementById('tool-name-filter').value = '';
|
||
window.location.href = '/query_task_data';
|
||
}
|
||
|
||
let currentSort = {
|
||
column: null,
|
||
direction: null
|
||
};
|
||
|
||
function sortTable(column) {
|
||
// 更新排序状态
|
||
if (currentSort.column === column) {
|
||
if (currentSort.direction === 'asc') {
|
||
currentSort.direction = 'desc';
|
||
} else {
|
||
currentSort.direction = 'asc';
|
||
}
|
||
} else {
|
||
currentSort.column = column;
|
||
currentSort.direction = 'asc';
|
||
}
|
||
|
||
|
||
// 构建查询参数
|
||
const params = new URLSearchParams(window.location.search);
|
||
params.set('sort_by', column);
|
||
params.set('sort_direction', currentSort.direction);
|
||
|
||
// 保持现有的筛选条件
|
||
const startDate = document.getElementById('start-date').value;
|
||
const endDate = document.getElementById('end-date').value;
|
||
const taskName = document.getElementById('task-name-filter').value;
|
||
const toolName = document.getElementById('tool-name-filter').value;
|
||
|
||
if (startDate) params.set('start_date', startDate);
|
||
if (endDate) params.set('end_date', endDate);
|
||
if (taskName) params.set('task_name', taskName);
|
||
if (toolName) params.set('tool_name', toolName);
|
||
|
||
// 更新 URL 并刷新页面
|
||
window.location.href = `/query_task_data?${params.toString()}`;
|
||
}
|
||
|
||
// 页面加载时初始化
|
||
window.onload = function() {
|
||
const urlParams = new URLSearchParams(window.location.search);
|
||
// const sortBy = urlParams.get('sort_by');
|
||
// const sortDirection = urlParams.get('sort_direction');
|
||
currentSort.column = urlParams.get('sort_by') || null;
|
||
currentSort.direction = urlParams.get('sort_direction') || null;
|
||
|
||
// 初始化 tab
|
||
const activeTab = urlParams.get('tab') || 'data';
|
||
|
||
// 如果是工具统计页面,自动加载第一个工具的任务详情
|
||
if (activeTab === 'tools') {
|
||
const firstTool = document.querySelector('.tool-row');
|
||
if (firstTool) {
|
||
const toolName = firstTool.querySelector('td:nth-child(2)').textContent;
|
||
firstTool.classList.add('selected');
|
||
// 立即获取并显示第一个工具的任务详情
|
||
fetch(`/get_tool_tasks?tool_name=${encodeURIComponent(toolName)}`)
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
const tbody = document.getElementById('taskDetailsBody');
|
||
document.getElementById('selectedToolTitle').innerHTML =
|
||
`Task Details of <span class="tool-highlight">${toolName}</span>`;
|
||
tbody.innerHTML = data.tasks.map((task, index) => `
|
||
<tr>
|
||
<td>${index + 1}</td>
|
||
<td>${task.task_name}</td>
|
||
<td>${task.total_requests}</td>
|
||
<td>${task.time_saved}</td>
|
||
</tr>
|
||
`).join('');
|
||
})
|
||
.catch(error => console.error('Error:', error));
|
||
}
|
||
}
|
||
|
||
|
||
// 更新排序图标
|
||
if (currentSort.column) {
|
||
const sortBtn = document.getElementById(`${currentSort.column}_sort`);
|
||
if (sortBtn) {
|
||
sortBtn.parentElement.classList.add(`sort-${currentSort.direction}`);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 添加 Tab 切换功能
|
||
function switchTab(tabName) {
|
||
// 隐藏所有 tab 内容
|
||
document.querySelectorAll('.tab-content').forEach(tab => {
|
||
tab.style.display = 'none';
|
||
});
|
||
|
||
// 移除所有 tab 的 active 类
|
||
document.querySelectorAll('.tab').forEach(tab => {
|
||
tab.classList.remove('active');
|
||
});
|
||
|
||
// 显示选中的 tab 内容
|
||
document.getElementById(tabName + 'Tab').style.display = 'block';
|
||
|
||
// 添加 active 类到选中的 tab
|
||
event.target.classList.add('active');
|
||
|
||
// 如果切换到工具统计 tab,立即加载数据
|
||
if (tabName === 'tools') {
|
||
// 构建查询参数
|
||
const params = new URLSearchParams();
|
||
// const params = new URLSearchParams(window.location.search);
|
||
params.set('tab', 'tools');
|
||
|
||
// 使用 fetch 获取数据而不是刷新页面
|
||
fetch(`/query_task_data?${params.toString()}`)
|
||
.then(response => response.text())
|
||
.then(html => {
|
||
const parser = new DOMParser();
|
||
const doc = parser.parseFromString(html, 'text/html');
|
||
const toolsContent = doc.getElementById('toolsTab').innerHTML;
|
||
document.getElementById('toolsTab').innerHTML = toolsContent;
|
||
|
||
|
||
// 自动加载第一个工具的详情
|
||
const firstTool = document.querySelector('.tool-row');
|
||
if (firstTool) {
|
||
const toolName = firstTool.querySelector('td:nth-child(2)').textContent;
|
||
firstTool.classList.add('selected');
|
||
// 立即获取并显示第一个工具的任务详情
|
||
fetch(`/get_tool_tasks?tool_name=${encodeURIComponent(toolName)}`)
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
const tbody = document.getElementById('taskDetailsBody');
|
||
document.getElementById('selectedToolTitle').innerHTML =
|
||
`Task Details of <span class="tool-highlight">${toolName}</span>`;
|
||
tbody.innerHTML = data.tasks.map((task, index) => `
|
||
<tr>
|
||
<td>${index + 1}</td>
|
||
<td>${task.task_name}</td>
|
||
<td>${task.total_requests}</td>
|
||
<td>${task.time_saved}</td>
|
||
</tr>
|
||
`).join('');
|
||
})
|
||
.catch(error => console.error('Error:', error));
|
||
}
|
||
})
|
||
.catch(error => console.error('Error:', error));
|
||
}
|
||
|
||
// 更新 URL 参数
|
||
const params = new URLSearchParams(window.location.search);
|
||
params.set('tab', tabName);
|
||
window.history.replaceState({}, '', `?${params.toString()}`);
|
||
}
|
||
|
||
// 工具统计相关的 JavaScript
|
||
let selectedToolName = null;
|
||
|
||
function showTaskDetails(toolName) {
|
||
// 更新选中状态
|
||
document.querySelectorAll('.tool-row').forEach(row => {
|
||
row.classList.remove('selected');
|
||
});
|
||
event.target.closest('tr').classList.add('selected');
|
||
|
||
// 更新标题
|
||
document.getElementById('selectedToolTitle').innerHTML =
|
||
`Task Details of <span class="tool-highlight">${toolName}</span>`;
|
||
|
||
// 获取任务详情数据
|
||
fetch(`/get_tool_tasks?tool_name=${encodeURIComponent(toolName)}`)
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
const tbody = document.getElementById('taskDetailsBody');
|
||
tbody.innerHTML = data.tasks.map((task, index) => `
|
||
<tr>
|
||
<td>${index + 1}</td>
|
||
<td>${task.task_name}</td>
|
||
<td>${task.total_requests}</td>
|
||
<td>${task.time_saved}</td>
|
||
</tr>
|
||
`).join('');
|
||
})
|
||
.catch(error => console.error('Error:', error));
|
||
}
|
||
|
||
// 处理月份选择
|
||
function handleMonthSelect() {
|
||
const monthSelect = document.getElementById('month-select');
|
||
const yearSelect = document.getElementById('year-select');
|
||
|
||
if (monthSelect.value) {
|
||
const year = yearSelect.value;
|
||
const month = monthSelect.value.padStart(2, '0');
|
||
const lastDay = new Date(year, month, 0).getDate();
|
||
|
||
document.getElementById('tools-start-date').value = `${year}-${month}-01`;
|
||
document.getElementById('tools-end-date').value = `${year}-${month}-${lastDay}`;
|
||
}
|
||
}
|
||
|
||
// 应用筛选
|
||
function applyToolsFilter() {
|
||
const startDate = document.getElementById('tools-start-date').value;
|
||
const endDate = document.getElementById('tools-end-date').value;
|
||
const month = document.getElementById('month-select').value;
|
||
const year = document.getElementById('year-select').value;
|
||
|
||
const params = new URLSearchParams(window.location.search);
|
||
|
||
if (startDate) params.set('tools_start_date', startDate);
|
||
if (endDate) params.set('tools_end_date', endDate);
|
||
if (month) params.set('month', month);
|
||
if (year) params.set('year', year);
|
||
|
||
params.set('tab', 'tools'); // 保持在工具统计标签页
|
||
|
||
window.location.href = `/query_task_data?${params.toString()}`;
|
||
}
|
||
|
||
// 清除筛选
|
||
function clearToolsFilter() {
|
||
// 清空所有筛选条件
|
||
document.getElementById('tools-start-date').value = '';
|
||
document.getElementById('tools-end-date').value = '';
|
||
document.getElementById('month-select').value = '';
|
||
document.getElementById('year-select').value = '';
|
||
|
||
// 清空 URL 参数,只保留 tab 参数
|
||
const params = new URLSearchParams();
|
||
params.set('tab', 'tools'); // 保持在工具统计标签页
|
||
|
||
// 跳转到清空后的 URL
|
||
window.location.href = `/query_task_data?${params.toString()}`;
|
||
}
|
||
|
||
// 视图切换函数
|
||
function switchView(viewType) {
|
||
const tableView = document.getElementById('tableView');
|
||
const chartView = document.getElementById('chartView');
|
||
const trendView = document.getElementById('trendView');
|
||
|
||
// 隐藏所有视图
|
||
tableView.style.display = 'none';
|
||
chartView.style.display = 'none';
|
||
trendView.style.display = 'none';
|
||
|
||
// 显示选中的视图
|
||
if (viewType === 'trend') {
|
||
trendView.style.display = 'block';
|
||
// 使用 setTimeout 确保 DOM 更新后再更新图表
|
||
setTimeout(updateTrendChart, 0);
|
||
} else if (viewType === 'chart') {
|
||
chartView.style.display = 'block';
|
||
setTimeout(updateChart, 0);
|
||
} else {
|
||
tableView.style.display = 'flex';
|
||
}
|
||
}
|
||
|
||
function updateChart() {
|
||
const toolRows = document.querySelectorAll('#toolsTable tbody tr');
|
||
const labels = [];
|
||
const requestData = [];
|
||
const timeData = [];
|
||
|
||
toolRows.forEach(row => {
|
||
labels.push(row.cells[1].textContent);
|
||
requestData.push(parseInt(row.cells[2].textContent));
|
||
timeData.push(parseInt(row.cells[3].textContent));
|
||
});
|
||
|
||
const ctx = document.getElementById('toolStatsChart');
|
||
if (window.toolChart) {
|
||
window.toolChart.destroy();
|
||
}
|
||
|
||
window.toolChart = new Chart(ctx, {
|
||
type: 'bar',
|
||
data: {
|
||
labels: labels,
|
||
datasets: [
|
||
{
|
||
label: 'Total Requests',
|
||
data: requestData,
|
||
backgroundColor: 'rgba(255, 153, 0, 0.7)',
|
||
borderColor: 'rgba(255, 153, 0, 1)',
|
||
borderWidth: 1,
|
||
yAxisID: 'y-requests'
|
||
},
|
||
{
|
||
label: 'Time Saved (s)',
|
||
data: timeData,
|
||
backgroundColor: 'rgba(54, 162, 235, 0.7)',
|
||
borderColor: 'rgba(54, 162, 235, 1)',
|
||
borderWidth: 1,
|
||
yAxisID: 'y-time'
|
||
}
|
||
]
|
||
},
|
||
options: {
|
||
responsive: true,
|
||
maintainAspectRatio: false,
|
||
scales: {
|
||
'y-requests': {
|
||
type: 'linear',
|
||
position: 'left',
|
||
title: {
|
||
display: true,
|
||
text: 'Total Requests',
|
||
color: 'rgba(255, 153, 0, 1)'
|
||
},
|
||
grid: {
|
||
drawOnChartArea: false
|
||
},
|
||
ticks: {
|
||
color: 'rgba(255, 153, 0, 1)',
|
||
stepSize: 1,
|
||
precision: 0
|
||
}
|
||
},
|
||
'y-time': {
|
||
type: 'linear',
|
||
position: 'right',
|
||
title: {
|
||
display: true,
|
||
text: 'Time Saved (s)',
|
||
color: 'rgba(54, 162, 235, 1)'
|
||
},
|
||
ticks: {
|
||
color: 'rgba(54, 162, 235, 1)'
|
||
}
|
||
},
|
||
x: {
|
||
grid: {
|
||
display: false
|
||
}
|
||
}
|
||
},
|
||
plugins: {
|
||
title: {
|
||
display: true,
|
||
text: 'Tool Statistics Overview',
|
||
font: {
|
||
size: 16,
|
||
weight: 'bold'
|
||
},
|
||
padding: 20
|
||
},
|
||
legend: {
|
||
position: 'top',
|
||
labels: {
|
||
padding: 20,
|
||
font: {
|
||
size: 12
|
||
}
|
||
}
|
||
}
|
||
},
|
||
barPercentage: 0.7,
|
||
categoryPercentage: 0.4
|
||
}
|
||
});
|
||
}
|
||
|
||
let trendChart = null; // 声明全局变量
|
||
|
||
function formatDate(date) {
|
||
return date.toISOString().split('T')[0];
|
||
}
|
||
|
||
function getCurrentDate() {
|
||
const today = new Date();
|
||
return formatDate(today);
|
||
}
|
||
|
||
function getDateBefore(days) {
|
||
const date = new Date();
|
||
date.setDate(date.getDate() - days);
|
||
return formatDate(date);
|
||
}
|
||
|
||
function updateTrendChart() {
|
||
console.log('Updating trend chart...');
|
||
|
||
// 获取当前的日期范围
|
||
const startDate = document.querySelector('input[name="tools-start-date"]').value;
|
||
const endDate = document.querySelector('input[name="tools-end-date"]').value;
|
||
|
||
console.log('Date range:', startDate, endDate);
|
||
|
||
// 创建 URLSearchParams 实例
|
||
const params = new URLSearchParams();
|
||
|
||
// 如果有日期范围,则使用指定的日期范围
|
||
if (startDate && endDate) {
|
||
params.append('start_date', startDate);
|
||
params.append('end_date', endDate);
|
||
}
|
||
else{
|
||
params.append('start_date', getDateBefore(30));
|
||
params.append('end_date', getCurrentDate());
|
||
}
|
||
console.log('Final request params:', params.toString());
|
||
// 获取趋势数据
|
||
fetch(`/get_trend_data?${params.toString()}`)
|
||
.then(response => {
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP error! status: ${response.status}`);
|
||
}
|
||
return response.json();
|
||
})
|
||
.then(data => {
|
||
console.log('Trend data:', data);
|
||
const ctx = document.getElementById('trendChart');
|
||
|
||
if (!ctx) {
|
||
console.error('Trend chart canvas not found');
|
||
return;
|
||
}
|
||
|
||
// 销毁旧图表
|
||
if (trendChart instanceof Chart) {
|
||
trendChart.destroy();
|
||
}
|
||
|
||
// 创建新图表
|
||
trendChart = new Chart(ctx, {
|
||
type: 'line',
|
||
data: {
|
||
labels: data.dates,
|
||
datasets: [
|
||
{
|
||
label: 'Total Requests',
|
||
data: data.requests,
|
||
borderColor: 'rgba(255, 153, 0, 1)',
|
||
backgroundColor: 'rgba(255, 153, 0, 0.1)',
|
||
yAxisID: 'y-requests',
|
||
tension: 0.4,
|
||
fill: true
|
||
},
|
||
{
|
||
label: 'Time Saved (s)',
|
||
data: data.timeSaved,
|
||
borderColor: 'rgba(54, 162, 235, 1)',
|
||
backgroundColor: 'rgba(54, 162, 235, 0.1)',
|
||
yAxisID: 'y-time',
|
||
tension: 0.4,
|
||
fill: true
|
||
}
|
||
]
|
||
},
|
||
options: {
|
||
responsive: true,
|
||
maintainAspectRatio: false,
|
||
scales: {
|
||
'y-requests': {
|
||
type: 'linear',
|
||
position: 'left',
|
||
title: {
|
||
display: true,
|
||
text: 'Total Requests',
|
||
color: 'rgba(255, 153, 0, 1)'
|
||
},
|
||
ticks: {
|
||
stepSize: 1,
|
||
precision: 0,
|
||
color: 'rgba(255, 153, 0, 1)'
|
||
},
|
||
grid: {
|
||
drawOnChartArea: false
|
||
}
|
||
},
|
||
'y-time': {
|
||
type: 'linear',
|
||
position: 'right',
|
||
title: {
|
||
display: true,
|
||
text: 'Time Saved (s)',
|
||
color: 'rgba(54, 162, 235, 1)'
|
||
},
|
||
ticks: {
|
||
color: 'rgba(54, 162, 235, 1)'
|
||
}
|
||
},
|
||
x: {
|
||
type: 'time',
|
||
time: {
|
||
unit: 'day',
|
||
displayFormats: {
|
||
day: 'MM/dd'
|
||
}
|
||
},
|
||
title: {
|
||
display: true,
|
||
text: 'Date'
|
||
}
|
||
}
|
||
},
|
||
plugins: {
|
||
title: {
|
||
display: true,
|
||
text: 'Usage Trend Overview',
|
||
font: {
|
||
size: 16,
|
||
weight: 'bold'
|
||
},
|
||
padding: 20
|
||
},
|
||
legend: {
|
||
position: 'top'
|
||
}
|
||
}
|
||
}
|
||
});
|
||
})
|
||
.catch(error => {
|
||
console.error('Error fetching trend data:', error);
|
||
alert('Failed to load trend data. Please try again.');
|
||
});
|
||
}
|
||
</script>
|
||
</body>
|
||
</html> |