freeleaps-ops/apps/gitea-webhook-ambassador-python/app/static/js/dashboard.js
Nicolas f6c515157c feat: 添加 Python 版本的 Gitea Webhook Ambassador
- 新增完整的 Python 实现,替代 Go 版本
- 添加 Web 登录界面和仪表板
- 实现 JWT 认证和 API 密钥管理
- 添加数据库存储功能
- 保持与 Go 版本一致的目录结构和启动脚本
- 包含完整的文档和测试脚本
2025-07-20 21:17:10 +08:00

373 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 全局变量存储 JWT 令牌
let authToken = localStorage.getItem('auth_token');
$(document).ready(function() {
// 检查认证状态
if (!authToken) {
window.location.href = '/login';
return;
}
// 设置 AJAX 默认配置
$.ajaxSetup({
beforeSend: function(xhr, settings) {
// 不为登录请求添加认证头
if (settings.url === '/api/auth/login') {
return;
}
if (authToken) {
xhr.setRequestHeader('Authorization', 'Bearer ' + authToken);
}
},
error: function(xhr, status, error) {
// 如果收到 401重定向到登录页
if (xhr.status === 401) {
localStorage.removeItem('auth_token');
window.location.href = '/login';
return;
}
handleAjaxError(xhr, status, error);
}
});
// 初始化工具提示
$('[data-bs-toggle="tooltip"]').tooltip();
// 加载初始数据
loadProjects();
loadAPIKeys();
loadLogs();
checkHealth();
loadHealthDetails();
loadStatsDetails();
// 设置定期健康检查
setInterval(checkHealth, 30000);
// 项目管理
$('#addProjectForm').on('submit', function(e) {
e.preventDefault();
const projectData = {
name: $('#projectName').val(),
jenkinsJob: $('#jenkinsJob').val(),
giteaRepo: $('#giteaRepo').val()
};
$.ajax({
url: '/api/projects/',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify(projectData),
success: function() {
$('#addProjectModal').modal('hide');
$('#addProjectForm')[0].reset();
loadProjects();
showSuccess('项目添加成功');
},
error: handleAjaxError
});
});
// API 密钥管理
$('#generateKeyForm').on('submit', function(e) {
e.preventDefault();
$.ajax({
url: '/api/keys',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify({ description: $('#keyDescription').val() }),
success: function(response) {
$('#generateKeyModal').modal('hide');
$('#generateKeyForm')[0].reset();
loadAPIKeys();
showSuccess('API 密钥生成成功');
// 显示新生成的密钥
showApiKeyModal(response.key);
},
error: handleAjaxError
});
});
// 日志查询
$('#logQueryForm').on('submit', function(e) {
e.preventDefault();
loadLogs({
startTime: $('#startTime').val(),
endTime: $('#endTime').val(),
level: $('#logLevel').val(),
query: $('#logQuery').val()
});
});
// 标签页切换
$('.nav-link').on('click', function() {
$('.nav-link').removeClass('active');
$(this).addClass('active');
});
});
function loadProjects() {
$.get('/api/projects/')
.done(function(data) {
const tbody = $('#projectsTable tbody');
tbody.empty();
data.projects.forEach(function(project) {
tbody.append(`
<tr>
<td>${escapeHtml(project.name)}</td>
<td>${escapeHtml(project.jenkinsJob)}</td>
<td>${escapeHtml(project.giteaRepo)}</td>
<td>
<button class="btn btn-sm btn-danger" onclick="deleteProject(${project.id})">
<i class="bi bi-trash"></i> 删除
</button>
</td>
</tr>
`);
});
})
.fail(handleAjaxError);
}
function loadAPIKeys() {
$.get('/api/keys')
.done(function(data) {
const tbody = $('#apiKeysTable tbody');
tbody.empty();
data.keys.forEach(function(key) {
tbody.append(`
<tr>
<td>${escapeHtml(key.description || '无描述')}</td>
<td><code class="api-key">${escapeHtml(key.key)}</code></td>
<td>${new Date(key.created_at).toLocaleString('zh-CN')}</td>
<td>
<button class="btn btn-sm btn-danger" onclick="revokeKey(${key.id})">
<i class="bi bi-trash"></i> 撤销
</button>
</td>
</tr>
`);
});
})
.fail(handleAjaxError);
}
function loadLogs(query = {}) {
$.get('/api/logs', query)
.done(function(data) {
const logContainer = $('#logEntries');
logContainer.empty();
if (data.logs && data.logs.length > 0) {
data.logs.forEach(function(log) {
const levelClass = {
'error': 'error',
'warn': 'warn',
'info': 'info',
'debug': 'debug'
}[log.level] || '';
logContainer.append(`
<div class="log-entry ${levelClass}">
<small>${new Date(log.timestamp).toLocaleString('zh-CN')}</small>
[${escapeHtml(log.level.toUpperCase())}] ${escapeHtml(log.message)}
</div>
`);
});
} else {
logContainer.append('<div class="text-muted">暂无日志记录</div>');
}
})
.fail(handleAjaxError);
}
function checkHealth() {
$.get('/health')
.done(function(data) {
const indicator = $('.health-indicator');
indicator.removeClass('healthy unhealthy')
.addClass(data.status === 'healthy' ? 'healthy' : 'unhealthy');
$('#healthStatus').text(data.status === 'healthy' ? '健康' : '异常');
})
.fail(function() {
const indicator = $('.health-indicator');
indicator.removeClass('healthy').addClass('unhealthy');
$('#healthStatus').text('异常');
});
}
function loadHealthDetails() {
$.get('/health')
.done(function(data) {
const healthDetails = $('#healthDetails');
healthDetails.html(`
<div class="mb-3">
<strong>状态:</strong>
<span class="badge ${data.status === 'healthy' ? 'bg-success' : 'bg-danger'}">
${data.status === 'healthy' ? '健康' : '异常'}
</span>
</div>
<div class="mb-3">
<strong>版本:</strong> ${data.version || '未知'}
</div>
<div class="mb-3">
<strong>启动时间:</strong> ${data.uptime || '未知'}
</div>
<div class="mb-3">
<strong>内存使用:</strong> ${data.memory || '未知'}
</div>
`);
})
.fail(function() {
$('#healthDetails').html('<div class="text-danger">无法获取健康状态</div>');
});
}
function loadStatsDetails() {
$.get('/api/stats')
.done(function(data) {
const statsDetails = $('#statsDetails');
statsDetails.html(`
<div class="mb-3">
<strong>总项目数:</strong> ${data.total_projects || 0}
</div>
<div class="mb-3">
<strong>API 密钥数:</strong> ${data.total_api_keys || 0}
</div>
<div class="mb-3">
<strong>今日触发次数:</strong> ${data.today_triggers || 0}
</div>
<div class="mb-3">
<strong>成功触发次数:</strong> ${data.successful_triggers || 0}
</div>
`);
})
.fail(function() {
$('#statsDetails').html('<div class="text-danger">无法获取统计信息</div>');
});
}
function deleteProject(id) {
if (!confirm('确定要删除这个项目吗?')) return;
$.ajax({
url: `/api/projects/${id}`,
method: 'DELETE',
success: function() {
loadProjects();
showSuccess('项目删除成功');
},
error: handleAjaxError
});
}
function revokeKey(id) {
if (!confirm('确定要撤销这个 API 密钥吗?')) return;
$.ajax({
url: `/api/keys/${id}`,
method: 'DELETE',
success: function() {
loadAPIKeys();
showSuccess('API 密钥撤销成功');
},
error: handleAjaxError
});
}
function showApiKeyModal(key) {
// 创建模态框显示新生成的密钥
const modal = $(`
<div class="modal fade" id="newApiKeyModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">新 API 密钥</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="alert alert-warning">
<strong>重要提示:</strong> 请保存这个密钥,因为它只会显示一次!
</div>
<div class="mb-3">
<label class="form-label">API 密钥:</label>
<input type="text" class="form-control" value="${key}" readonly>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
<button type="button" class="btn btn-primary" onclick="copyToClipboard('${key}')">
复制到剪贴板
</button>
</div>
</div>
</div>
</div>
`);
$('body').append(modal);
modal.modal('show');
modal.on('hidden.bs.modal', function() {
modal.remove();
});
}
function copyToClipboard(text) {
navigator.clipboard.writeText(text).then(function() {
showSuccess('已复制到剪贴板');
}, function() {
showError('复制失败');
});
}
function handleAjaxError(jqXHR, textStatus, errorThrown) {
const message = jqXHR.responseJSON?.detail || errorThrown || '发生错误';
showError(`错误: ${message}`);
}
function showSuccess(message) {
// 创建成功提示
const alert = $(`
<div class="alert alert-success alert-dismissible fade show" role="alert">
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
`);
$('.main-content').prepend(alert);
// 3秒后自动消失
setTimeout(function() {
alert.alert('close');
}, 3000);
}
function showError(message) {
// 创建错误提示
const alert = $(`
<div class="alert alert-danger alert-dismissible fade show" role="alert">
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
`);
$('.main-content').prepend(alert);
// 5秒后自动消失
setTimeout(function() {
alert.alert('close');
}, 5000);
}
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}