525 lines
19 KiB
JavaScript
525 lines
19 KiB
JavaScript
|
|
// http_server_management_console/frontend/js/app.js
|
|
new Vue({
|
|
el: '#app',
|
|
data() {
|
|
return {
|
|
serverConfig: JSON.parse(localStorage.getItem('serverConfig')) || {
|
|
sites: [
|
|
{
|
|
id: Date.now(),
|
|
name: '示例网站',
|
|
domain: 'example.com',
|
|
port: 8080,
|
|
ssl: false,
|
|
collapsed: false,
|
|
routes: [
|
|
{
|
|
path: '/static',
|
|
type: 'static',
|
|
target: '静态资源'
|
|
},
|
|
{
|
|
path: '/api',
|
|
type: 'proxy',
|
|
target: 'http://backend:3000'
|
|
}
|
|
]
|
|
}
|
|
]
|
|
},
|
|
logs: JSON.parse(localStorage.getItem('serverLogs')) || [
|
|
{ timestamp: '2025-05-06 23:13:22', message: '添加了路由配置 /api → http://backend:3000', type: 'success' },
|
|
{ timestamp: '2025-05-06 23:12:15', message: '修改了网站端口 8080 → 8443', type: 'warning' },
|
|
{ timestamp: '2025-05-06 23:10:07', message: '创建了网站配置 example.com', type: 'info' }
|
|
],
|
|
requestCount: 0,
|
|
goroutineCount: 10,
|
|
filterType: 'all',
|
|
searchQuery: '',
|
|
logLevels: [
|
|
{ value: 'all', label: '全部' },
|
|
{ value: 'info', label: '信息' },
|
|
{ value: 'success', label: '成功' },
|
|
{ value: 'warning', label: '警告' },
|
|
{ value: 'danger', label: '错误' }
|
|
],
|
|
siteDialog: {
|
|
visible: false,
|
|
title: '添加网站',
|
|
form: {
|
|
id: null,
|
|
name: '',
|
|
domain: '',
|
|
port: 80,
|
|
ssl: false,
|
|
collapsed: false
|
|
},
|
|
editIndex: null
|
|
},
|
|
routeDialog: {
|
|
visible: false,
|
|
title: '添加路由',
|
|
form: {
|
|
path: '',
|
|
type: 'static',
|
|
target: ''
|
|
},
|
|
siteIndex: null,
|
|
editIndex: null
|
|
},
|
|
requestChart: null,
|
|
goroutineChart: null,
|
|
chartInterval: null
|
|
}
|
|
},
|
|
computed: {
|
|
filteredLogs() {
|
|
let result = this.logs;
|
|
|
|
if (this.filterType !== 'all') {
|
|
result = result.filter(log => log.type === this.filterType);
|
|
}
|
|
|
|
if (this.searchQuery) {
|
|
const query = this.searchQuery.toLowerCase();
|
|
result = result.filter(log =>
|
|
log.message.toLowerCase().includes(query) ||
|
|
log.timestamp.includes(query)
|
|
);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
},
|
|
mounted() {
|
|
this.requestCount = Math.floor(Math.random() * 1000) + 500;
|
|
|
|
// 模拟数据更新
|
|
setInterval(() => {
|
|
this.requestCount += Math.floor(Math.random() * 10);
|
|
this.goroutineCount = Math.max(5, this.goroutineCount + Math.floor(Math.random() * 5) - 2);
|
|
}, 3000);
|
|
|
|
// 应用已保存的主题
|
|
const savedTheme = localStorage.getItem('theme');
|
|
if (savedTheme) {
|
|
document.body.className = savedTheme;
|
|
}
|
|
|
|
// 初始化图表
|
|
this.initCharts();
|
|
},
|
|
beforeDestroy() {
|
|
if (this.chartInterval) {
|
|
clearInterval(this.chartInterval);
|
|
}
|
|
},
|
|
methods: {
|
|
// 站点配置管理
|
|
toggleCollapse(index) {
|
|
this.serverConfig.sites[index].collapsed = !this.serverConfig.sites[index].collapsed;
|
|
this.saveConfig();
|
|
},
|
|
saveConfig() {
|
|
localStorage.setItem('serverConfig', JSON.stringify(this.serverConfig));
|
|
this.logAction('保存了服务器配置', 'success');
|
|
},
|
|
addSite() {
|
|
this.siteDialog = {
|
|
visible: true,
|
|
title: '添加网站',
|
|
form: {
|
|
id: Date.now(),
|
|
name: '新网站',
|
|
domain: 'new-site.com',
|
|
port: 80,
|
|
ssl: false,
|
|
collapsed: false
|
|
},
|
|
editIndex: null
|
|
};
|
|
},
|
|
editSite(index) {
|
|
const site = this.serverConfig.sites[index];
|
|
this.siteDialog = {
|
|
visible: true,
|
|
title: '编辑网站',
|
|
form: JSON.parse(JSON.stringify(site)),
|
|
editIndex: index
|
|
};
|
|
},
|
|
saveSite() {
|
|
if (this.siteDialog.editIndex !== null) {
|
|
this.serverConfig.sites.splice(this.siteDialog.editIndex, 1, this.siteDialog.form);
|
|
this.logAction(`更新了网站 ${this.siteDialog.form.name} 的配置`, 'success');
|
|
} else {
|
|
this.serverConfig.sites.push({
|
|
...this.siteDialog.form,
|
|
routes: []
|
|
});
|
|
this.logAction(`添加了网站 ${this.siteDialog.form.name}`, 'success');
|
|
}
|
|
|
|
this.saveConfig();
|
|
this.siteDialog.visible = false;
|
|
},
|
|
deleteSite(index) {
|
|
this.$confirm('确定要删除这个网站吗?', '提示', {
|
|
confirmButtonText: '确定',
|
|
cancelButtonText: '取消',
|
|
type: 'warning'
|
|
}).then(() => {
|
|
const site = this.serverConfig.sites[index];
|
|
this.serverConfig.sites.splice(index, 1);
|
|
this.saveConfig();
|
|
this.logAction(`删除了网站 ${site.name}`, 'warning');
|
|
this.$message({
|
|
type: 'success',
|
|
message: '删除成功!'
|
|
});
|
|
}).catch(() => {
|
|
this.$message({
|
|
type: 'info',
|
|
message: '已取消删除'
|
|
});
|
|
});
|
|
},
|
|
|
|
// 路由管理
|
|
addRoute(siteIndex) {
|
|
this.routeDialog = {
|
|
visible: true,
|
|
title: '添加路由',
|
|
form: {
|
|
path: '',
|
|
type: 'static',
|
|
target: ''
|
|
},
|
|
siteIndex,
|
|
editIndex: null
|
|
};
|
|
},
|
|
editRoute(siteIndex, routeIndex) {
|
|
const route = this.serverConfig.sites[siteIndex].routes[routeIndex];
|
|
this.routeDialog = {
|
|
visible: true,
|
|
title: '编辑路由',
|
|
form: JSON.parse(JSON.stringify(route)),
|
|
siteIndex,
|
|
editIndex: routeIndex
|
|
};
|
|
},
|
|
saveRoute() {
|
|
const site = this.serverConfig.sites[this.routeDialog.siteIndex];
|
|
|
|
if (this.routeDialog.editIndex !== null) {
|
|
site.routes.splice(this.routeDialog.editIndex, 1, this.routeDialog.form);
|
|
this.logAction(`更新了路由 ${this.routeDialog.form.path}`, 'success');
|
|
} else {
|
|
site.routes.push(this.routeDialog.form);
|
|
this.logAction(`添加了路由 ${this.routeDialog.form.path}`, 'success');
|
|
}
|
|
|
|
this.saveConfig();
|
|
this.routeDialog.visible = false;
|
|
},
|
|
deleteRoute(siteIndex, routeIndex) {
|
|
this.$confirm('确定要删除这个路由吗?', '提示', {
|
|
confirmButtonText: '确定',
|
|
cancelButtonText: '取消',
|
|
type: 'warning'
|
|
}).then(() => {
|
|
const route = this.serverConfig.sites[siteIndex].routes[routeIndex];
|
|
this.serverConfig.sites[siteIndex].routes.splice(routeIndex, 1);
|
|
this.saveConfig();
|
|
this.logAction(`删除了路由 ${route.path}`, 'warning');
|
|
this.$message({
|
|
type: 'success',
|
|
message: '删除成功!'
|
|
});
|
|
}).catch(() => {
|
|
this.$message({
|
|
type: 'info',
|
|
message: '已取消删除'
|
|
});
|
|
});
|
|
},
|
|
|
|
// 配置导入导出
|
|
exportConfig() {
|
|
const dataStr = JSON.stringify(this.serverConfig, null, 2);
|
|
const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr);
|
|
|
|
const exportFileDefaultName = `server-config-${new Date().toISOString().slice(0,10)}.json`;
|
|
|
|
const linkElement = document.createElement('a');
|
|
linkElement.setAttribute('href', dataUri);
|
|
linkElement.setAttribute('download', exportFileDefaultName);
|
|
linkElement.click();
|
|
|
|
this.logAction('导出了服务器配置', 'success');
|
|
},
|
|
importConfig() {
|
|
const input = document.createElement('input');
|
|
input.type = 'file';
|
|
input.accept = '.json';
|
|
|
|
input.onchange = e => {
|
|
const file = e.target.files[0];
|
|
const reader = new FileReader();
|
|
|
|
reader.onload = event => {
|
|
try {
|
|
const importedConfig = JSON.parse(event.target.result);
|
|
this.$confirm('确定要导入此配置吗?当前配置将被覆盖。', '提示', {
|
|
confirmButtonText: '确定',
|
|
cancelButtonText: '取消',
|
|
type: 'warning'
|
|
}).then(() => {
|
|
this.serverConfig = importedConfig;
|
|
this.saveConfig();
|
|
this.logAction('导入了服务器配置', 'success');
|
|
this.$message({
|
|
type: 'success',
|
|
message: '导入成功!'
|
|
});
|
|
}).catch(() => {
|
|
this.$message({
|
|
type: 'info',
|
|
message: '已取消导入'
|
|
});
|
|
});
|
|
} catch (error) {
|
|
this.$message.error('配置文件格式错误: ' + error.message);
|
|
}
|
|
};
|
|
|
|
reader.readAsText(file);
|
|
};
|
|
|
|
input.click();
|
|
},
|
|
|
|
// 日志管理
|
|
logAction(message, type = 'info') {
|
|
const timestamp = new Date().toISOString().replace('T', ' ').substring(0, 19);
|
|
this.logs.unshift({ timestamp, message, type });
|
|
|
|
if (this.logs.length > 100) {
|
|
this.logs = this.logs.slice(0, 100);
|
|
}
|
|
|
|
localStorage.setItem('serverLogs', JSON.stringify(this.logs));
|
|
},
|
|
clearLogs() {
|
|
this.$confirm('确定要清空所有操作日志吗?', '提示', {
|
|
confirmButtonText: '确定',
|
|
cancelButtonText: '取消',
|
|
type: 'warning'
|
|
}).then(() => {
|
|
this.logs = [];
|
|
localStorage.setItem('serverLogs', JSON.stringify(this.logs));
|
|
this.logAction('清空了所有操作日志', 'warning');
|
|
this.$message.success('日志已清空');
|
|
}).catch(() => {
|
|
this.$message.info('已取消清空操作');
|
|
});
|
|
},
|
|
exportLogs() {
|
|
const dataStr = JSON.stringify(this.logs, null, 2);
|
|
const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr);
|
|
|
|
const exportFileDefaultName = `server-logs-${new Date().toISOString().slice(0,10)}.json`;
|
|
|
|
const linkElement = document.createElement('a');
|
|
linkElement.setAttribute('href', dataUri);
|
|
linkElement.setAttribute('download', exportFileDefaultName);
|
|
linkElement.click();
|
|
|
|
this.logAction('导出了操作日志', 'info');
|
|
},
|
|
getTypeColor(type) {
|
|
const colors = {
|
|
info: 'text-blue-400',
|
|
success: 'text-green-400',
|
|
warning: 'text-yellow-400',
|
|
danger: 'text-red-400'
|
|
};
|
|
return colors[type] || 'text-gray-400';
|
|
},
|
|
|
|
// 主题管理
|
|
changeTheme(theme) {
|
|
document.body.className = theme;
|
|
localStorage.setItem('theme', theme);
|
|
|
|
// 更新图表颜色以匹配主题
|
|
this.updateChartColors(theme);
|
|
|
|
let themeName = '未知';
|
|
switch (theme) {
|
|
case 'theme-dark':
|
|
themeName = '暗色';
|
|
break;
|
|
case 'theme-light':
|
|
themeName = '亮色';
|
|
break;
|
|
case 'theme-tech':
|
|
themeName = '科技';
|
|
break;
|
|
}
|
|
|
|
this.logAction(`切换为${themeName}主题`, 'info');
|
|
},
|
|
|
|
// 服务器控制
|
|
startServer() {
|
|
this.logAction('启动了HTTP服务器', 'success');
|
|
this.$message.success('服务器已启动');
|
|
},
|
|
stopServer() {
|
|
this.logAction('停止了HTTP服务器', 'warning');
|
|
this.$message.warning('服务器已停止');
|
|
},
|
|
restartServer() {
|
|
this.logAction('重启了HTTP服务器', 'info');
|
|
this.$message.info('服务器正在重启');
|
|
},
|
|
showLogs() {
|
|
this.$message.info('正在显示日志');
|
|
},
|
|
|
|
// 图表功能
|
|
initCharts() {
|
|
// 初始化请求图表
|
|
const requestCtx = document.getElementById('request-chart').getContext('2d');
|
|
this.requestChart = new Chart(requestCtx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: Array.from({length: 12}, (_, i) => `${i*5}秒`),
|
|
datasets: [{
|
|
label: '访问量',
|
|
data: Array.from({length: 12}, () => Math.floor(Math.random() * 100) + 50),
|
|
borderColor: 'rgba(58, 134, 255, 0.8)',
|
|
backgroundColor: 'rgba(58, 134, 255, 0.1)',
|
|
borderWidth: 2,
|
|
tension: 0.4,
|
|
fill: true,
|
|
pointRadius: 0
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
display: false
|
|
}
|
|
},
|
|
scales: {
|
|
x: {
|
|
display: false,
|
|
grid: {
|
|
display: false
|
|
}
|
|
},
|
|
y: {
|
|
display: false,
|
|
grid: {
|
|
display: false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// 初始化Goroutine图表
|
|
const goroutineCtx = document.getElementById('goroutine-chart').getContext('2d');
|
|
this.goroutineChart = new Chart(goroutineCtx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: Array.from({length: 12}, (_, i) => `${i*5}秒`),
|
|
datasets: [{
|
|
label: 'Goroutines',
|
|
data: Array.from({length: 12}, () => Math.floor(Math.random() * 20) + 10),
|
|
borderColor: 'rgba(131, 56, 236, 0.8)',
|
|
backgroundColor: 'rgba(131, 56, 236, 0.1)',
|
|
borderWidth: 2,
|
|
tension: 0.4,
|
|
fill: true,
|
|
pointRadius: 0
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
display: false
|
|
}
|
|
},
|
|
scales: {
|
|
x: {
|
|
display: false,
|
|
grid: {
|
|
display: false
|
|
}
|
|
},
|
|
y: {
|
|
display: false,
|
|
grid: {
|
|
display: false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// 模拟实时数据更新
|
|
this.chartInterval = setInterval(() => {
|
|
// 更新请求图表数据
|
|
const requestData = this.requestChart.data.datasets[0].data;
|
|
requestData.shift();
|
|
requestData.push(Math.floor(Math.random() * 30) + requestData[requestData.length - 1] - 15);
|
|
this.requestChart.update();
|
|
|
|
// 更新Goroutine图表数据
|
|
const goroutineData = this.goroutineChart.data.datasets[0].data;
|
|
goroutineData.shift();
|
|
goroutineData.push(Math.floor(Math.random() * 5) + goroutineData[goroutineData.length - 1] - 2);
|
|
this.goroutineChart.update();
|
|
}, 5000);
|
|
},
|
|
updateChartColors(theme) {
|
|
let requestColor, goroutineColor;
|
|
|
|
switch(theme) {
|
|
case 'theme-tech':
|
|
requestColor = 'rgba(0, 247, 255, 0.8)';
|
|
goroutineColor = 'rgba(0, 200, 255, 0.8)';
|
|
break;
|
|
case 'theme-light':
|
|
requestColor = 'rgba(0, 102, 255, 0.8)';
|
|
goroutineColor = 'rgba(98, 0, 238, 0.8)';
|
|
break;
|
|
default: // dark theme
|
|
requestColor = 'rgba(58, 134, 255, 0.8)';
|
|
goroutineColor = 'rgba(131, 56, 236, 0.8)';
|
|
}
|
|
|
|
if (this.requestChart) {
|
|
this.requestChart.data.datasets[0].borderColor = requestColor;
|
|
this.requestChart.data.datasets[0].backgroundColor = requestColor.replace('0.8', '0.1');
|
|
this.requestChart.update();
|
|
}
|
|
|
|
if (this.goroutineChart) {
|
|
this.goroutineChart.data.datasets[0].borderColor = goroutineColor;
|
|
this.goroutineChart.data.datasets[0].backgroundColor = goroutineColor.replace('0.8', '0.1');
|
|
this.goroutineChart.update();
|
|
}
|
|
}
|
|
}
|
|
});
|