修改网站托管模块
This commit is contained in:
15
app.go
15
app.go
@@ -405,3 +405,18 @@ func (a *App) BackupDatabase(backupPath string) error {
|
|||||||
}
|
}
|
||||||
return a.store.Backup(backupPath)
|
return a.store.Backup(backupPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SelectFolder 打开文件夹选择对话框
|
||||||
|
func (a *App) SelectFolder() (string, error) {
|
||||||
|
result, err := a.app.Dialog.OpenFileWithOptions(&application.OpenFileDialogOptions{
|
||||||
|
CanChooseDirectories: true,
|
||||||
|
CanChooseFiles: false,
|
||||||
|
Title: "选择网站文件夹",
|
||||||
|
ButtonText: "选择",
|
||||||
|
}).PromptForSingleSelection()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -136,38 +136,38 @@
|
|||||||
<template #bodyCell="{ column, record }">
|
<template #bodyCell="{ column, record }">
|
||||||
<template v-if="column.key === 'site'">
|
<template v-if="column.key === 'site'">
|
||||||
<div class="site-cell">
|
<div class="site-cell">
|
||||||
<a-avatar :style="{ backgroundColor: getAvatarColor(record.name) }" size="small">
|
<a-avatar :style="{ backgroundColor: getAvatarColor(record.Name || record.name) }" size="small">
|
||||||
{{ record.name.charAt(0).toUpperCase() }}
|
{{ (record.Name || record.name).charAt(0).toUpperCase() }}
|
||||||
</a-avatar>
|
</a-avatar>
|
||||||
<div class="site-details">
|
<div class="site-details">
|
||||||
<div class="site-name">{{ record.name }}</div>
|
<div class="site-name">{{ record.Name || record.name }}</div>
|
||||||
<div class="site-path">{{ record.path }}</div>
|
<div class="site-path">{{ record.Path || record.path }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-else-if="column.key === 'domain'">
|
<template v-else-if="column.key === 'domain'">
|
||||||
<div class="domain-cell">
|
<div class="domain-cell">
|
||||||
<a-tag color="blue" v-for="domain in record.domains" :key="domain">
|
<a-tag color="blue" v-for="domain in (record.Domains || record.domains)" :key="domain">
|
||||||
{{ domain }}
|
{{ domain }}
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-else-if="column.key === 'port'">
|
<template v-else-if="column.key === 'port'">
|
||||||
<a-tag color="cyan">{{ record.port }}</a-tag>
|
<a-tag color="cyan">{{ record.Port || record.port }}</a-tag>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-else-if="column.key === 'status'">
|
<template v-else-if="column.key === 'status'">
|
||||||
<a-badge
|
<a-badge
|
||||||
:status="record.enabled ? 'success' : 'default'"
|
:status="(record.Enabled !== undefined ? record.Enabled : record.enabled) ? 'success' : 'default'"
|
||||||
:text="record.enabled ? '已启用' : '已禁用'"
|
:text="(record.Enabled !== undefined ? record.Enabled : record.enabled) ? '已启用' : '已禁用'"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-else-if="column.key === 'action'">
|
<template v-else-if="column.key === 'action'">
|
||||||
<a-space size="small">
|
<a-space size="small">
|
||||||
<a-button type="link" size="small" @click="openSite(record)" :disabled="!nginxRunning || !record.enabled">
|
<a-button type="link" size="small" @click="openSite(record)" :disabled="!nginxRunning || !(record.Enabled !== undefined ? record.Enabled : record.enabled)">
|
||||||
<template #icon><eye-outlined /></template>
|
<template #icon><eye-outlined /></template>
|
||||||
访问
|
访问
|
||||||
</a-button>
|
</a-button>
|
||||||
@@ -180,9 +180,9 @@
|
|||||||
编辑
|
编辑
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-switch
|
<a-switch
|
||||||
v-model:checked="record.enabled"
|
:checked="record.Enabled !== undefined ? record.Enabled : record.enabled"
|
||||||
size="small"
|
size="small"
|
||||||
@change="toggleSiteStatus(record)"
|
@change="(val: boolean) => toggleSiteStatus(record, val)"
|
||||||
/>
|
/>
|
||||||
<a-popconfirm
|
<a-popconfirm
|
||||||
title="确定要删除此站点吗?"
|
title="确定要删除此站点吗?"
|
||||||
@@ -210,12 +210,12 @@
|
|||||||
@cancel="handleModalCancel"
|
@cancel="handleModalCancel"
|
||||||
>
|
>
|
||||||
<a-form :model="formData" layout="vertical" ref="formRef">
|
<a-form :model="formData" layout="vertical" ref="formRef">
|
||||||
<a-form-item label="选择网站" name="name" :rules="[{ required: true, message: '请选择网站' }]">
|
<a-form-item label="站点名称" name="name" :rules="[{ required: true, message: '请输入站点名称' }]">
|
||||||
<a-select v-model:value="formData.name" placeholder="请选择已下载的网站" @change="onSiteSelect">
|
<a-input v-model:value="formData.name" placeholder="请输入站点名称,如: example.com" />
|
||||||
<a-select-option v-for="site in availableSites" :key="site.name" :value="site.name">
|
<div class="form-tip">
|
||||||
{{ site.name }}
|
<info-circle-outlined />
|
||||||
</a-select-option>
|
<span>可以输入域名或自定义名称</span>
|
||||||
</a-select>
|
</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item label="域名配置" name="domains" :rules="[{ required: true, message: '请至少添加一个域名' }]">
|
<a-form-item label="域名配置" name="domains" :rules="[{ required: true, message: '请至少添加一个域名' }]">
|
||||||
@@ -252,10 +252,20 @@
|
|||||||
</a-row>
|
</a-row>
|
||||||
|
|
||||||
<a-form-item label="网站路径" name="path" :rules="[{ required: true, message: '请输入网站路径' }]">
|
<a-form-item label="网站路径" name="path" :rules="[{ required: true, message: '请输入网站路径' }]">
|
||||||
<a-input v-model:value="formData.path" placeholder="相对路径,如: www/example.com" />
|
<a-input-group compact style="display: flex;">
|
||||||
|
<a-input
|
||||||
|
v-model:value="formData.path"
|
||||||
|
placeholder="网站文件夹的绝对路径或相对路径"
|
||||||
|
style="flex: 1;"
|
||||||
|
/>
|
||||||
|
<a-button type="primary" @click="handleSelectFolder">
|
||||||
|
<template #icon><folder-open-outlined /></template>
|
||||||
|
选择文件夹
|
||||||
|
</a-button>
|
||||||
|
</a-input-group>
|
||||||
<div class="form-tip">
|
<div class="form-tip">
|
||||||
<info-circle-outlined />
|
<info-circle-outlined />
|
||||||
<span>相对于项目根目录的路径</span>
|
<span>支持绝对路径(如 D:/www/mysite)或相对路径(如 www/mysite)</span>
|
||||||
</div>
|
</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
@@ -304,7 +314,6 @@ const totalVisits = ref(0);
|
|||||||
|
|
||||||
// 托管站点列表
|
// 托管站点列表
|
||||||
const hostingSites = ref<any[]>([]);
|
const hostingSites = ref<any[]>([]);
|
||||||
const availableSites = ref<any[]>([]);
|
|
||||||
|
|
||||||
// 对话框
|
// 对话框
|
||||||
const modalVisible = ref(false);
|
const modalVisible = ref(false);
|
||||||
@@ -362,7 +371,6 @@ onMounted(() => {
|
|||||||
// 加载数据
|
// 加载数据
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
getAvailableSites(),
|
|
||||||
getHostingSites(),
|
getHostingSites(),
|
||||||
checkNginxStatus()
|
checkNginxStatus()
|
||||||
]);
|
]);
|
||||||
@@ -379,16 +387,6 @@ const checkNginxStatus = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取可用网站列表(已下载的)
|
|
||||||
const getAvailableSites = async () => {
|
|
||||||
try {
|
|
||||||
const res = await App.GetDownloadList();
|
|
||||||
availableSites.value = res || [];
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取网站列表失败:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 获取托管列表
|
// 获取托管列表
|
||||||
const getHostingSites = async () => {
|
const getHostingSites = async () => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
@@ -450,17 +448,31 @@ const showAddModal = () => {
|
|||||||
// 编辑站点
|
// 编辑站点
|
||||||
const editSite = (record: any) => {
|
const editSite = (record: any) => {
|
||||||
editingRecord.value = record;
|
editingRecord.value = record;
|
||||||
formData.value = { ...record };
|
// 将后端字段转换为前端字段
|
||||||
|
formData.value = {
|
||||||
|
name: record.Name || record.name,
|
||||||
|
domains: record.Domains || record.domains || [],
|
||||||
|
port: record.Port || record.port,
|
||||||
|
path: record.Path || record.path,
|
||||||
|
index: record.Index || record.index || 'index.html',
|
||||||
|
enabled: record.Enabled !== undefined ? record.Enabled : record.enabled
|
||||||
|
};
|
||||||
modalVisible.value = true;
|
modalVisible.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 网站选择改变
|
// 选择文件夹
|
||||||
const onSiteSelect = (value: string) => {
|
const handleSelectFolder = async () => {
|
||||||
const site = availableSites.value.find(s => s.name === value);
|
try {
|
||||||
if (site) {
|
const folderPath = await App.SelectFolder();
|
||||||
// 设置网站路径(从工作目录)
|
if (folderPath) {
|
||||||
formData.value.path = `www/${value}`;
|
formData.value.path = folderPath;
|
||||||
formData.value.domains = [value, '127.0.0.1'];
|
message.success('已选择文件夹');
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('选择文件夹失败:', error);
|
||||||
|
if (error.message && error.message !== 'User cancelled') {
|
||||||
|
message.error('选择文件夹失败');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -472,25 +484,25 @@ const handleModalOk = async () => {
|
|||||||
if (editingRecord.value) {
|
if (editingRecord.value) {
|
||||||
// 更新站点配置
|
// 更新站点配置
|
||||||
await App.UpdateNginxSite({
|
await App.UpdateNginxSite({
|
||||||
ID: "",
|
id: "",
|
||||||
Name: formData.value.name,
|
name: formData.value.name,
|
||||||
Domains: formData.value.domains,
|
domains: formData.value.domains,
|
||||||
Port: formData.value.port,
|
port: formData.value.port,
|
||||||
Path: formData.value.path,
|
path: formData.value.path,
|
||||||
Index: formData.value.index,
|
index: formData.value.index,
|
||||||
Enabled: formData.value.enabled
|
enabled: formData.value.enabled
|
||||||
});
|
});
|
||||||
message.success('更新成功');
|
message.success('更新成功');
|
||||||
} else {
|
} else {
|
||||||
// 添加站点配置
|
// 添加站点配置
|
||||||
await App.AddNginxSite({
|
await App.AddNginxSite({
|
||||||
ID: "",
|
id: "",
|
||||||
Name: formData.value.name,
|
name: formData.value.name,
|
||||||
Domains: formData.value.domains,
|
domains: formData.value.domains,
|
||||||
Port: formData.value.port,
|
port: formData.value.port,
|
||||||
Path: formData.value.path,
|
path: formData.value.path,
|
||||||
Index: formData.value.index,
|
index: formData.value.index,
|
||||||
Enabled: formData.value.enabled
|
enabled: formData.value.enabled
|
||||||
});
|
});
|
||||||
message.success('站点配置创建成功');
|
message.success('站点配置创建成功');
|
||||||
}
|
}
|
||||||
@@ -517,14 +529,27 @@ const handleModalCancel = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 切换站点状态
|
// 切换站点状态
|
||||||
const toggleSiteStatus = async (record: any) => {
|
const toggleSiteStatus = async (record: any, enabled: boolean) => {
|
||||||
try {
|
try {
|
||||||
if (record.enabled) {
|
const siteName = record.Name || record.name;
|
||||||
await App.EnableNginxSite(record.name);
|
if (enabled) {
|
||||||
|
await App.EnableNginxSite(siteName);
|
||||||
message.success('站点已启用');
|
message.success('站点已启用');
|
||||||
|
// 更新本地状态
|
||||||
|
if (record.Enabled !== undefined) {
|
||||||
|
record.Enabled = true;
|
||||||
|
} else {
|
||||||
|
record.enabled = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
await App.DisableNginxSite(record.name);
|
await App.DisableNginxSite(siteName);
|
||||||
message.success('站点已禁用');
|
message.success('站点已禁用');
|
||||||
|
// 更新本地状态
|
||||||
|
if (record.Enabled !== undefined) {
|
||||||
|
record.Enabled = false;
|
||||||
|
} else {
|
||||||
|
record.enabled = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果 nginx 正在运行,重载配置
|
// 如果 nginx 正在运行,重载配置
|
||||||
@@ -534,15 +559,16 @@ const toggleSiteStatus = async (record: any) => {
|
|||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('切换状态失败:', error);
|
console.error('切换状态失败:', error);
|
||||||
message.error(error.message || '操作失败');
|
message.error(error.message || '操作失败');
|
||||||
// 回滚状态
|
// 重新加载数据恢复状态
|
||||||
record.enabled = !record.enabled;
|
await getHostingSites();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 删除站点
|
// 删除站点
|
||||||
const deleteSite = async (record: any) => {
|
const deleteSite = async (record: any) => {
|
||||||
try {
|
try {
|
||||||
await App.DeleteNginxSite(record.name);
|
const siteName = record.Name || record.name;
|
||||||
|
await App.DeleteNginxSite(siteName);
|
||||||
message.success('站点配置已删除');
|
message.success('站点配置已删除');
|
||||||
|
|
||||||
// 重新加载站点列表
|
// 重新加载站点列表
|
||||||
@@ -560,14 +586,17 @@ const deleteSite = async (record: any) => {
|
|||||||
|
|
||||||
// 访问站点
|
// 访问站点
|
||||||
const openSite = (record: any) => {
|
const openSite = (record: any) => {
|
||||||
const url = `http://${record.domains[0]}:${record.port}`;
|
const domains = record.Domains || record.domains || [];
|
||||||
|
const port = record.Port || record.port;
|
||||||
|
const url = `http://${domains[0]}:${port}`;
|
||||||
window.open(url, '_blank');
|
window.open(url, '_blank');
|
||||||
};
|
};
|
||||||
|
|
||||||
// 打开文件夹
|
// 打开文件夹
|
||||||
const openFolder = async (record: any) => {
|
const openFolder = async (record: any) => {
|
||||||
try {
|
try {
|
||||||
await App.OpenSiteFileDir(record.name);
|
const siteName = record.Name || record.name;
|
||||||
|
await App.OpenSiteFileDir(siteName);
|
||||||
message.success('已打开文件夹');
|
message.success('已打开文件夹');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('打开文件夹失败:', error);
|
console.error('打开文件夹失败:', error);
|
||||||
|
|||||||
@@ -254,6 +254,14 @@ export function RestartNginx() {
|
|||||||
return $Call.ByID(2491282768);
|
return $Call.ByID(2491282768);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SelectFolder 打开文件夹选择对话框
|
||||||
|
* @returns {$CancellablePromise<string>}
|
||||||
|
*/
|
||||||
|
export function SelectFolder() {
|
||||||
|
return $Call.ByID(237181597);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 启动 Nginx
|
* 启动 Nginx
|
||||||
* @returns {$CancellablePromise<void>}
|
* @returns {$CancellablePromise<void>}
|
||||||
|
|||||||
Reference in New Issue
Block a user