373 lines
12 KiB
JavaScript
373 lines
12 KiB
JavaScript
// Global variable to store JWT token
|
|
let authToken = localStorage.getItem('auth_token');
|
|
|
|
$(document).ready(function() {
|
|
// Check authentication status
|
|
if (!authToken) {
|
|
window.location.href = '/login';
|
|
return;
|
|
}
|
|
|
|
// Set AJAX default config
|
|
$.ajaxSetup({
|
|
beforeSend: function(xhr, settings) {
|
|
// Do not add auth header for login request
|
|
if (settings.url === '/api/auth/login') {
|
|
return;
|
|
}
|
|
if (authToken) {
|
|
xhr.setRequestHeader('Authorization', 'Bearer ' + authToken);
|
|
}
|
|
},
|
|
error: function(xhr, status, error) {
|
|
// If 401 received, redirect to login page
|
|
if (xhr.status === 401) {
|
|
localStorage.removeItem('auth_token');
|
|
window.location.href = '/login';
|
|
return;
|
|
}
|
|
handleAjaxError(xhr, status, error);
|
|
}
|
|
});
|
|
|
|
// Initialize tooltips
|
|
$('[data-bs-toggle="tooltip"]').tooltip();
|
|
|
|
// Load initial data
|
|
loadProjects();
|
|
loadAPIKeys();
|
|
loadLogs();
|
|
checkHealth();
|
|
loadHealthDetails();
|
|
loadStatsDetails();
|
|
|
|
// Set periodic health check
|
|
setInterval(checkHealth, 30000);
|
|
|
|
// Project management
|
|
$('#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('Project added successfully');
|
|
},
|
|
error: handleAjaxError
|
|
});
|
|
});
|
|
|
|
// API key management
|
|
$('#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 key generated successfully');
|
|
|
|
// Show newly generated key
|
|
showApiKeyModal(response.key);
|
|
},
|
|
error: handleAjaxError
|
|
});
|
|
});
|
|
|
|
// Log query
|
|
$('#logQueryForm').on('submit', function(e) {
|
|
e.preventDefault();
|
|
loadLogs({
|
|
startTime: $('#startTime').val(),
|
|
endTime: $('#endTime').val(),
|
|
level: $('#logLevel').val(),
|
|
query: $('#logQuery').val()
|
|
});
|
|
});
|
|
|
|
// Tab switching
|
|
$('.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> Delete
|
|
</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 || 'No 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> Revoke
|
|
</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">No log records</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' ? 'Healthy' : 'Unhealthy');
|
|
})
|
|
.fail(function() {
|
|
const indicator = $('.health-indicator');
|
|
indicator.removeClass('healthy').addClass('unhealthy');
|
|
$('#healthStatus').text('Unhealthy');
|
|
});
|
|
}
|
|
|
|
function loadHealthDetails() {
|
|
$.get('/health')
|
|
.done(function(data) {
|
|
const healthDetails = $('#healthDetails');
|
|
healthDetails.html(`
|
|
<div class="mb-3">
|
|
<strong>Status:</strong>
|
|
<span class="badge ${data.status === 'healthy' ? 'bg-success' : 'bg-danger'}">
|
|
${data.status === 'healthy' ? 'Healthy' : 'Unhealthy'}
|
|
</span>
|
|
</div>
|
|
<div class="mb-3">
|
|
<strong>Version:</strong> ${data.version || 'Unknown'}
|
|
</div>
|
|
<div class="mb-3">
|
|
<strong>Uptime:</strong> ${data.uptime || 'Unknown'}
|
|
</div>
|
|
<div class="mb-3">
|
|
<strong>Memory Usage:</strong> ${data.memory || 'Unknown'}
|
|
</div>
|
|
`);
|
|
})
|
|
.fail(function() {
|
|
$('#healthDetails').html('<div class="text-danger">Unable to get health status</div>');
|
|
});
|
|
}
|
|
|
|
function loadStatsDetails() {
|
|
$.get('/api/stats')
|
|
.done(function(data) {
|
|
const statsDetails = $('#statsDetails');
|
|
statsDetails.html(`
|
|
<div class="mb-3">
|
|
<strong>Total Projects:</strong> ${data.total_projects || 0}
|
|
</div>
|
|
<div class="mb-3">
|
|
<strong>API Keys:</strong> ${data.total_api_keys || 0}
|
|
</div>
|
|
<div class="mb-3">
|
|
<strong>Today's Triggers:</strong> ${data.today_triggers || 0}
|
|
</div>
|
|
<div class="mb-3">
|
|
<strong>Successful Triggers:</strong> ${data.successful_triggers || 0}
|
|
</div>
|
|
`);
|
|
})
|
|
.fail(function() {
|
|
$('#statsDetails').html('<div class="text-danger">Unable to get statistics</div>');
|
|
});
|
|
}
|
|
|
|
function deleteProject(id) {
|
|
if (!confirm('Are you sure you want to delete this project?')) return;
|
|
|
|
$.ajax({
|
|
url: `/api/projects/${id}`,
|
|
method: 'DELETE',
|
|
success: function() {
|
|
loadProjects();
|
|
showSuccess('Project deleted successfully');
|
|
},
|
|
error: handleAjaxError
|
|
});
|
|
}
|
|
|
|
function revokeKey(id) {
|
|
if (!confirm('Are you sure you want to revoke this API key?')) return;
|
|
|
|
$.ajax({
|
|
url: `/api/keys/${id}`,
|
|
method: 'DELETE',
|
|
success: function() {
|
|
loadAPIKeys();
|
|
showSuccess('API key revoked successfully');
|
|
},
|
|
error: handleAjaxError
|
|
});
|
|
}
|
|
|
|
function showApiKeyModal(key) {
|
|
// Create modal to show newly generated 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">New API Key</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="alert alert-warning">
|
|
<strong>Important:</strong> Please save this key, as it will only be shown once!
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">API Key:</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">Close</button>
|
|
<button type="button" class="btn btn-primary" onclick="copyToClipboard('${key}')">
|
|
Copy to Clipboard
|
|
</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('Copied to clipboard');
|
|
}, function() {
|
|
showError('Copy failed');
|
|
});
|
|
}
|
|
|
|
function handleAjaxError(jqXHR, textStatus, errorThrown) {
|
|
const message = jqXHR.responseJSON?.detail || errorThrown || 'An error occurred';
|
|
showError(`Error: ${message}`);
|
|
}
|
|
|
|
function showSuccess(message) {
|
|
// Create success alert
|
|
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);
|
|
|
|
// Auto dismiss after 3 seconds
|
|
setTimeout(function() {
|
|
alert.alert('close');
|
|
}, 3000);
|
|
}
|
|
|
|
function showError(message) {
|
|
// Create error alert
|
|
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);
|
|
|
|
// Auto dismiss after 5 seconds
|
|
setTimeout(function() {
|
|
alert.alert('close');
|
|
}, 5000);
|
|
}
|
|
|
|
function escapeHtml(unsafe) {
|
|
return unsafe
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, ''');
|
|
}
|