523 lines
20 KiB
JavaScript
523 lines
20 KiB
JavaScript
const { createApp } = Vue;
|
|
const { ElMessage, ElMessageBox } = ElementPlus;
|
|
|
|
const API_BASE = '/api/v1';
|
|
|
|
createApp({
|
|
data() {
|
|
return {
|
|
activeTab: 'repos',
|
|
repos: [],
|
|
reposLoading: false,
|
|
statsLoading: false,
|
|
addRepoVisible: false,
|
|
switchBranchVisible: false,
|
|
addRepoForm: {
|
|
urls: '',
|
|
branch: 'main',
|
|
username: '',
|
|
password: ''
|
|
},
|
|
switchBranchForm: {
|
|
branch: '',
|
|
repoId: null,
|
|
repoUrl: '',
|
|
currentBranch: ''
|
|
},
|
|
branches: [],
|
|
branchesLoading: false,
|
|
statsForm: {
|
|
repo_id: null,
|
|
branch: 'main',
|
|
constraint_type: 'commit_limit',
|
|
from: '',
|
|
to: '',
|
|
limit: 100
|
|
},
|
|
statsDateRange: null,
|
|
statsFormBranches: [],
|
|
statsFormBranchesLoading: false,
|
|
selectedStatsResult: null,
|
|
tasks: [],
|
|
tasksLoading: false,
|
|
caches: [],
|
|
cachesLoading: false
|
|
};
|
|
},
|
|
mounted() {
|
|
this.loadRepos();
|
|
this.loadCaches();
|
|
},
|
|
watch: {
|
|
activeTab(newTab) {
|
|
if (newTab === 'tasks') {
|
|
this.loadTasks();
|
|
} else if (newTab === 'caches') {
|
|
this.loadCaches();
|
|
}
|
|
}
|
|
},
|
|
methods: {
|
|
async loadRepos() {
|
|
this.reposLoading = true;
|
|
try {
|
|
const response = await axios.get(`${API_BASE}/repos`);
|
|
if (response.data.code === 0) {
|
|
this.repos = response.data.data.repositories || [];
|
|
} else {
|
|
ElMessage.error(response.data.message || '加载仓库列表失败');
|
|
}
|
|
} catch (error) {
|
|
ElMessage.error('网络请求失败: ' + error.message);
|
|
} finally {
|
|
this.reposLoading = false;
|
|
}
|
|
},
|
|
showAddRepoDialog() {
|
|
this.addRepoForm = {
|
|
urls: '',
|
|
branch: 'main',
|
|
username: '',
|
|
password: ''
|
|
};
|
|
this.addRepoVisible = true;
|
|
},
|
|
async onRepoChange() {
|
|
this.statsForm.branch = 'main';
|
|
this.statsFormBranches = [];
|
|
if (this.statsForm.repo_id) {
|
|
await this.loadStatsFormBranches();
|
|
}
|
|
},
|
|
async loadStatsFormBranches() {
|
|
if (!this.statsForm.repo_id) return;
|
|
|
|
this.statsFormBranchesLoading = true;
|
|
try {
|
|
const response = await axios.get(`${API_BASE}/repos/${this.statsForm.repo_id}/branches`);
|
|
if (response.data.code === 0) {
|
|
this.statsFormBranches = response.data.data.branches || ['main'];
|
|
// 如果当前分支不在列表中,添加进去
|
|
if (this.statsForm.branch && !this.statsFormBranches.includes(this.statsForm.branch)) {
|
|
this.statsFormBranches.unshift(this.statsForm.branch);
|
|
}
|
|
} else {
|
|
this.statsFormBranches = ['main', 'master', 'develop'];
|
|
}
|
|
} catch (error) {
|
|
this.statsFormBranches = ['main', 'master', 'develop'];
|
|
} finally {
|
|
this.statsFormBranchesLoading = false;
|
|
}
|
|
},
|
|
getRepoDisplayName(repo) {
|
|
const url = repo.url;
|
|
const parts = url.split('/');
|
|
return parts[parts.length - 1].replace('.git', '');
|
|
},
|
|
async addRepos() {
|
|
const urls = this.addRepoForm.urls.split('\n')
|
|
.map(u => u.trim())
|
|
.filter(u => u);
|
|
|
|
if (urls.length === 0) {
|
|
ElMessage.warning('请输入至少一个仓库URL');
|
|
return;
|
|
}
|
|
|
|
const repos = urls.map(url => ({
|
|
url,
|
|
branch: this.addRepoForm.branch || 'main'
|
|
}));
|
|
|
|
const requestData = { repos };
|
|
|
|
// 如果提供了认证信息
|
|
if (this.addRepoForm.username && this.addRepoForm.password) {
|
|
requestData.username = this.addRepoForm.username;
|
|
requestData.password = this.addRepoForm.password;
|
|
}
|
|
|
|
try {
|
|
const response = await axios.post(`${API_BASE}/repos/batch`, requestData);
|
|
if (response.data.code === 0) {
|
|
const result = response.data.data;
|
|
ElMessage.success(`成功添加 ${result.success_count} 个仓库`);
|
|
if (result.failure_count > 0) {
|
|
ElMessage.warning(`${result.failure_count} 个仓库添加失败`);
|
|
}
|
|
this.addRepoVisible = false;
|
|
this.loadRepos();
|
|
} else {
|
|
ElMessage.error(response.data.message || '添加仓库失败');
|
|
}
|
|
} catch (error) {
|
|
ElMessage.error('网络请求失败: ' + error.message);
|
|
}
|
|
},
|
|
async switchBranch(repo) {
|
|
this.switchBranchForm = {
|
|
branch: '',
|
|
repoId: repo.id,
|
|
repoUrl: repo.url,
|
|
currentBranch: repo.current_branch || '未知'
|
|
};
|
|
this.branches = [];
|
|
this.switchBranchVisible = true;
|
|
|
|
// 获取分支列表
|
|
await this.loadBranches(repo.id);
|
|
},
|
|
async loadBranches(repoId) {
|
|
this.branchesLoading = true;
|
|
try {
|
|
const response = await axios.get(`${API_BASE}/repos/${repoId}/branches`);
|
|
if (response.data.code === 0) {
|
|
this.branches = response.data.data.branches || [];
|
|
} else {
|
|
ElMessage.warning('获取分支列表失败: ' + (response.data.message || ''));
|
|
this.branches = [];
|
|
}
|
|
} catch (error) {
|
|
console.warn('无法获取分支列表:', error.message);
|
|
this.branches = [];
|
|
} finally {
|
|
this.branchesLoading = false;
|
|
}
|
|
},
|
|
async confirmSwitchBranch() {
|
|
if (!this.switchBranchForm.branch) {
|
|
ElMessage.warning('请输入分支名称');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await axios.post(
|
|
`${API_BASE}/repos/${this.switchBranchForm.repoId}/switch-branch`,
|
|
{ branch: this.switchBranchForm.branch }
|
|
);
|
|
if (response.data.code === 0) {
|
|
ElMessage.success('切换分支任务已提交');
|
|
this.switchBranchVisible = false;
|
|
this.loadRepos();
|
|
} else {
|
|
ElMessage.error(response.data.message || '切换分支失败');
|
|
}
|
|
} catch (error) {
|
|
ElMessage.error('网络请求失败: ' + error.message);
|
|
}
|
|
},
|
|
async updateRepo(repoId) {
|
|
try {
|
|
const response = await axios.post(`${API_BASE}/repos/${repoId}/update`);
|
|
if (response.data.code === 0) {
|
|
ElMessage.success('更新任务已提交');
|
|
this.loadRepos();
|
|
} else {
|
|
ElMessage.error(response.data.message || '更新仓库失败');
|
|
}
|
|
} catch (error) {
|
|
ElMessage.error('网络请求失败: ' + error.message);
|
|
}
|
|
},
|
|
async resetRepo(repoId) {
|
|
try {
|
|
await ElMessageBox.confirm('确定要重置该仓库吗?', '警告', {
|
|
confirmButtonText: '确定',
|
|
cancelButtonText: '取消',
|
|
type: 'warning'
|
|
});
|
|
|
|
const response = await axios.post(`${API_BASE}/repos/${repoId}/reset`);
|
|
if (response.data.code === 0) {
|
|
ElMessage.success('重置任务已提交');
|
|
this.loadRepos();
|
|
} else {
|
|
ElMessage.error(response.data.message || '重置仓库失败');
|
|
}
|
|
} catch (error) {
|
|
if (error !== 'cancel') {
|
|
ElMessage.error('网络请求失败: ' + error.message);
|
|
}
|
|
}
|
|
},
|
|
async deleteRepo(repoId) {
|
|
try {
|
|
await ElMessageBox.confirm('确定要删除该仓库吗?此操作不可恢复!', '警告', {
|
|
confirmButtonText: '确定',
|
|
cancelButtonText: '取消',
|
|
type: 'warning'
|
|
});
|
|
|
|
const response = await axios.delete(`${API_BASE}/repos/${repoId}`);
|
|
if (response.data.code === 0) {
|
|
ElMessage.success('删除成功');
|
|
this.loadRepos();
|
|
} else {
|
|
ElMessage.error(response.data.message || '删除仓库失败');
|
|
}
|
|
} catch (error) {
|
|
if (error !== 'cancel') {
|
|
ElMessage.error('网络请求失败: ' + error.message);
|
|
}
|
|
}
|
|
},
|
|
async calculateStats() {
|
|
if (!this.statsForm.repo_id) {
|
|
ElMessage.warning('请选择仓库');
|
|
return;
|
|
}
|
|
|
|
if (!this.statsForm.branch) {
|
|
ElMessage.warning('请输入分支名称');
|
|
return;
|
|
}
|
|
|
|
const constraint = {
|
|
type: this.statsForm.constraint_type
|
|
};
|
|
|
|
if (this.statsForm.constraint_type === 'date_range') {
|
|
if (!this.statsDateRange || this.statsDateRange.length !== 2) {
|
|
ElMessage.warning('请选择日期范围');
|
|
return;
|
|
}
|
|
constraint.from = this.statsDateRange[0];
|
|
constraint.to = this.statsDateRange[1];
|
|
} else {
|
|
constraint.limit = this.statsForm.limit;
|
|
}
|
|
|
|
try {
|
|
const response = await axios.post(`${API_BASE}/stats/calculate`, {
|
|
repo_id: this.statsForm.repo_id,
|
|
branch: this.statsForm.branch,
|
|
constraint
|
|
});
|
|
|
|
if (response.data.code === 0) {
|
|
ElMessage.success('统计任务已提交,请稍后查看结果');
|
|
// 3秒后刷新缓存列表
|
|
setTimeout(() => {
|
|
this.loadCaches();
|
|
}, 3000);
|
|
} else {
|
|
ElMessage.error(response.data.message || '提交统计任务失败');
|
|
}
|
|
} catch (error) {
|
|
ElMessage.error('网络请求失败: ' + error.message);
|
|
}
|
|
},
|
|
async loadTasks() {
|
|
this.tasksLoading = true;
|
|
try {
|
|
const response = await axios.get(`${API_BASE}/tasks`);
|
|
if (response.data.code === 0) {
|
|
this.tasks = response.data.data.tasks || [];
|
|
} else {
|
|
ElMessage.error(response.data.message || '加载任务列表失败');
|
|
}
|
|
} catch (error) {
|
|
ElMessage.error('网络请求失败: ' + error.message);
|
|
} finally {
|
|
this.tasksLoading = false;
|
|
}
|
|
},
|
|
async loadCaches() {
|
|
this.cachesLoading = true;
|
|
try {
|
|
const response = await axios.get(`${API_BASE}/stats/caches`);
|
|
if (response.data.code === 0) {
|
|
this.caches = response.data.data.caches || [];
|
|
} else {
|
|
ElMessage.error(response.data.message || '加载统计缓存列表失败');
|
|
}
|
|
} catch (error) {
|
|
ElMessage.error('网络请求失败: ' + error.message);
|
|
} finally {
|
|
this.cachesLoading = false;
|
|
}
|
|
},
|
|
async clearAllCaches() {
|
|
try {
|
|
await ElMessageBox.confirm('确定要清空所有统计缓存吗?此操作不可恢复!', '警告', {
|
|
confirmButtonText: '确定',
|
|
cancelButtonText: '取消',
|
|
type: 'warning'
|
|
});
|
|
|
|
const response = await axios.delete(`${API_BASE}/stats/caches/clear`);
|
|
if (response.data.code === 0) {
|
|
ElMessage.success('所有统计缓存已清除');
|
|
this.loadCaches();
|
|
} else {
|
|
ElMessage.error(response.data.message || '清除缓存失败');
|
|
}
|
|
} catch (error) {
|
|
if (error !== 'cancel') {
|
|
ElMessage.error('网络请求失败: ' + error.message);
|
|
}
|
|
}
|
|
},
|
|
async clearAllTasks() {
|
|
try {
|
|
await ElMessageBox.confirm('确定要清空所有任务记录吗?包括正在执行的任务!', '警告', {
|
|
confirmButtonText: '确定',
|
|
cancelButtonText: '取消',
|
|
type: 'warning'
|
|
});
|
|
|
|
const response = await axios.delete(`${API_BASE}/tasks/clear`);
|
|
if (response.data.code === 0) {
|
|
ElMessage.success('所有任务记录已清除');
|
|
this.loadTasks();
|
|
} else {
|
|
ElMessage.error(response.data.message || '清除任务失败');
|
|
}
|
|
} catch (error) {
|
|
if (error !== 'cancel') {
|
|
ElMessage.error('网络请求失败: ' + error.message);
|
|
}
|
|
}
|
|
},
|
|
async clearCompletedTasks() {
|
|
try {
|
|
await ElMessageBox.confirm('确定要清除所有已完成的任务记录吗?', '提示', {
|
|
confirmButtonText: '确定',
|
|
cancelButtonText: '取消',
|
|
type: 'info'
|
|
});
|
|
|
|
const response = await axios.delete(`${API_BASE}/tasks/clear-completed`);
|
|
if (response.data.code === 0) {
|
|
ElMessage.success('已完成的任务记录已清除');
|
|
this.loadTasks();
|
|
} else {
|
|
ElMessage.error(response.data.message || '清除已完成任务失败');
|
|
}
|
|
} catch (error) {
|
|
if (error !== 'cancel') {
|
|
ElMessage.error('网络请求失败: ' + error.message);
|
|
}
|
|
}
|
|
},
|
|
async viewStatsCache(cache) {
|
|
this.statsLoading = true;
|
|
try {
|
|
const params = {
|
|
repo_id: cache.repo_id,
|
|
branch: cache.branch,
|
|
constraint_type: cache.constraint_type
|
|
};
|
|
|
|
// 根据缓存的constraint_value添加参数
|
|
if (cache.constraint_type === 'date_range' && cache.constraint_value) {
|
|
try {
|
|
const constraint = JSON.parse(cache.constraint_value);
|
|
if (constraint.from) params.from = constraint.from;
|
|
if (constraint.to) params.to = constraint.to;
|
|
} catch (e) {
|
|
console.error('Failed to parse constraint_value:', e);
|
|
}
|
|
} else if (cache.constraint_type === 'commit_limit' && cache.constraint_value) {
|
|
try {
|
|
const constraint = JSON.parse(cache.constraint_value);
|
|
if (constraint.limit) params.limit = constraint.limit;
|
|
} catch (e) {
|
|
params.limit = 100; // 默认值
|
|
}
|
|
}
|
|
|
|
const response = await axios.get(`${API_BASE}/stats/result`, { params });
|
|
|
|
if (response.data.code === 0) {
|
|
// 适配后端返回的数据结构
|
|
const data = response.data.data;
|
|
const stats = data.statistics;
|
|
|
|
this.selectedStatsResult = {
|
|
summary: {
|
|
total_commits: stats.summary.total_commits || 0,
|
|
total_contributors: stats.summary.total_contributors || 0,
|
|
total_additions: stats.by_contributor.reduce((sum, c) => sum + (c.additions || 0), 0),
|
|
total_deletions: stats.by_contributor.reduce((sum, c) => sum + (c.deletions || 0), 0)
|
|
},
|
|
date_range: stats.summary.date_range || { from: '未指定', to: '未指定' },
|
|
contributors: stats.by_contributor.map(c => ({
|
|
name: c.author,
|
|
email: c.email,
|
|
commit_count: c.commits,
|
|
additions: c.additions,
|
|
deletions: c.deletions,
|
|
first_commit_date: c.first_commit_date || '-',
|
|
last_commit_date: c.last_commit_date || '-'
|
|
}))
|
|
};
|
|
ElMessage.success('查看统计结果成功');
|
|
} else {
|
|
ElMessage.error(response.data.message || '查询统计结果失败');
|
|
this.selectedStatsResult = null;
|
|
}
|
|
} catch (error) {
|
|
ElMessage.error('网络请求失败: ' + error.message);
|
|
this.selectedStatsResult = null;
|
|
} finally {
|
|
this.statsLoading = false;
|
|
}
|
|
},
|
|
getRepoStatusType(status) {
|
|
const statusMap = {
|
|
'pending': 'info',
|
|
'cloning': 'warning',
|
|
'ready': 'success',
|
|
'error': 'danger'
|
|
};
|
|
return statusMap[status] || 'info';
|
|
},
|
|
getTaskStatusType(status) {
|
|
const statusMap = {
|
|
'pending': 'info',
|
|
'running': 'warning',
|
|
'completed': 'success',
|
|
'failed': 'danger'
|
|
};
|
|
return statusMap[status] || 'info';
|
|
},
|
|
getConstraintText(cache) {
|
|
if (!cache.constraint_value) return '未指定';
|
|
try {
|
|
const constraint = JSON.parse(cache.constraint_value);
|
|
if (cache.constraint_type === 'date_range') {
|
|
return `${constraint.from || ''} ~ ${constraint.to || ''}`;
|
|
} else if (cache.constraint_type === 'commit_limit') {
|
|
return `最近 ${constraint.limit || 100} 次提交`;
|
|
}
|
|
} catch (e) {
|
|
return '解析失败';
|
|
}
|
|
return '未知';
|
|
},
|
|
getRepoName(repoId) {
|
|
const repo = this.repos.find(r => r.id === repoId);
|
|
if (repo) {
|
|
const url = repo.url;
|
|
const parts = url.split('/');
|
|
return parts[parts.length - 1].replace('.git', '');
|
|
}
|
|
return `仓库 #${repoId}`;
|
|
},
|
|
formatDate(dateStr) {
|
|
if (!dateStr) return '-';
|
|
const date = new Date(dateStr);
|
|
return date.toLocaleString('zh-CN');
|
|
},
|
|
formatFileSize(bytes) {
|
|
if (!bytes) return '0 B';
|
|
const k = 1024;
|
|
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
|
|
}
|
|
}
|
|
}).use(ElementPlus).mount('#app');
|