2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 14:57:16 +08:00

feat: website backup and restore

This commit is contained in:
耗子
2023-07-24 18:46:38 +08:00
parent 5d7ae8d632
commit bd5854cef3
7 changed files with 141 additions and 36 deletions

View File

@@ -28,6 +28,7 @@ func NewWebsiteController() *WebsiteController {
}
}
// List 网站列表
func (c *WebsiteController) List(ctx http.Context) {
limit := ctx.Request().QueryInt("limit")
page := ctx.Request().QueryInt("page")
@@ -45,6 +46,7 @@ func (c *WebsiteController) List(ctx http.Context) {
})
}
// Add 添加网站
func (c *WebsiteController) Add(ctx http.Context) {
if !Check(ctx, "openresty") {
return
@@ -88,6 +90,7 @@ func (c *WebsiteController) Add(ctx http.Context) {
Success(ctx, newSite)
}
// Delete 删除网站
func (c *WebsiteController) Delete(ctx http.Context) {
if !Check(ctx, "openresty") {
return
@@ -103,6 +106,7 @@ func (c *WebsiteController) Delete(ctx http.Context) {
Success(ctx, nil)
}
// GetDefaultConfig 获取默认配置
func (c *WebsiteController) GetDefaultConfig(ctx http.Context) {
if !Check(ctx, "openresty") {
return
@@ -116,6 +120,7 @@ func (c *WebsiteController) GetDefaultConfig(ctx http.Context) {
})
}
// SaveDefaultConfig 保存默认配置
func (c *WebsiteController) SaveDefaultConfig(ctx http.Context) {
if !Check(ctx, "openresty") {
return
@@ -138,6 +143,7 @@ func (c *WebsiteController) SaveDefaultConfig(ctx http.Context) {
Success(ctx, nil)
}
// GetConfig 获取配置
func (c *WebsiteController) GetConfig(ctx http.Context) {
if !Check(ctx, "openresty") {
return
@@ -158,6 +164,7 @@ func (c *WebsiteController) GetConfig(ctx http.Context) {
Success(ctx, config)
}
// SaveConfig 保存配置
func (c *WebsiteController) SaveConfig(ctx http.Context) {
if !Check(ctx, "openresty") {
return
@@ -393,6 +400,7 @@ func (c *WebsiteController) SaveConfig(ctx http.Context) {
Success(ctx, nil)
}
// ClearLog 清空日志
func (c *WebsiteController) ClearLog(ctx http.Context) {
if !Check(ctx, "openresty") {
return
@@ -416,6 +424,7 @@ func (c *WebsiteController) ClearLog(ctx http.Context) {
Success(ctx, nil)
}
// UpdateRemark 更新备注
func (c *WebsiteController) UpdateRemark(ctx http.Context) {
id := ctx.Request().InputInt("id")
if id == 0 {
@@ -442,6 +451,7 @@ func (c *WebsiteController) UpdateRemark(ctx http.Context) {
Success(ctx, nil)
}
// BackupList 备份列表
func (c *WebsiteController) BackupList(ctx http.Context) {
backupList, err := c.backup.WebsiteList()
if err != nil {
@@ -453,6 +463,7 @@ func (c *WebsiteController) BackupList(ctx http.Context) {
Success(ctx, backupList)
}
// CreateBackup 创建备份
func (c *WebsiteController) CreateBackup(ctx http.Context) {
id := ctx.Request().InputInt("id")
if id == 0 {
@@ -478,6 +489,82 @@ func (c *WebsiteController) CreateBackup(ctx http.Context) {
Success(ctx, nil)
}
// UploadBackup 上传备份
func (c *WebsiteController) UploadBackup(ctx http.Context) {
file, err := ctx.Request().File("file")
if err != nil {
Error(ctx, http.StatusBadRequest, "上传文件失败")
return
}
backupPath := c.setting.Get(models.SettingKeyBackupPath) + "/website"
if !tools.Exists(backupPath) {
tools.Mkdir(backupPath, 0644)
}
name := file.GetClientOriginalName()
_, err = file.StoreAs(backupPath, name)
if err != nil {
Error(ctx, http.StatusBadRequest, "上传文件失败")
return
}
Success(ctx, "上传文件成功")
}
// RestoreBackup 还原备份
func (c *WebsiteController) RestoreBackup(ctx http.Context) {
id := ctx.Request().InputInt("id")
if id == 0 {
Error(ctx, http.StatusBadRequest, "参数错误")
return
}
fileName := ctx.Request().Input("name")
if len(fileName) == 0 {
Error(ctx, http.StatusBadRequest, "参数错误")
return
}
website := models.Website{}
err := facades.Orm().Query().Where("id", id).Get(&website)
if err != nil {
facades.Log().Error("[面板][WebsiteController] 获取网站信息失败 ", err)
Error(ctx, http.StatusInternalServerError, "获取网站信息失败: "+err.Error())
return
}
err = c.backup.WebsiteRestore(website, fileName)
if err != nil {
facades.Log().Error("[面板][WebsiteController] 还原网站失败 ", err)
Error(ctx, http.StatusInternalServerError, "还原网站失败: "+err.Error())
return
}
Success(ctx, nil)
}
// DeleteBackup 删除备份
func (c *WebsiteController) DeleteBackup(ctx http.Context) {
fileName := ctx.Request().Input("name")
if len(fileName) == 0 {
Error(ctx, http.StatusBadRequest, "参数错误")
return
}
backupPath := c.setting.Get(models.SettingKeyBackupPath) + "/website"
if !tools.Exists(backupPath) {
tools.Mkdir(backupPath, 0644)
}
if !tools.RemoveFile(backupPath + "/" + fileName) {
Error(ctx, http.StatusInternalServerError, "删除备份失败")
return
}
Success(ctx, nil)
}
// ResetConfig 重置配置
func (c *WebsiteController) ResetConfig(ctx http.Context) {
if !Check(ctx, "openresty") {
return
@@ -569,6 +656,7 @@ server
Success(ctx, nil)
}
// Status 网站状态
func (c *WebsiteController) Status(ctx http.Context) {
if !Check(ctx, "openresty") {
return

View File

@@ -14,6 +14,7 @@ import (
type Backup interface {
WebsiteList() ([]BackupFile, error)
WebSiteBackup(website models.Website) error
WebsiteRestore(website models.Website, backupFile string) error
}
type BackupFile struct {
@@ -38,6 +39,9 @@ func (s *BackupImpl) WebsiteList() ([]BackupFile, error) {
}
path += "/website"
if !tools.Exists(path) {
tools.Mkdir(path, 0644)
}
files, err := os.ReadDir(path)
if err != nil {
@@ -69,8 +73,32 @@ func (s *BackupImpl) WebSiteBackup(website models.Website) error {
tools.Mkdir(backupPath, 0644)
}
backupFile := backupPath + "/" + website.Name + carbon.Now().ToShortDateTimeString() + ".zip"
tools.ExecShell("cd " + website.Path + " && zip -r " + backupFile + " .")
backupFile := backupPath + "/" + website.Name + "_" + carbon.Now().ToShortDateTimeString() + ".zip"
tools.ExecShell(`cd '` + website.Path + `' && zip -r '` + backupFile + `' .`)
return nil
}
func (s *BackupImpl) WebsiteRestore(website models.Website, backupFile string) error {
backupPath := s.setting.Get(models.SettingKeyBackupPath)
if len(backupPath) == 0 {
return errors.New("未正确配置备份路径")
}
backupPath += "/website"
if !tools.Exists(backupPath) {
tools.Mkdir(backupPath, 0644)
}
backupFile = backupPath + "/" + backupFile
if !tools.Exists(backupFile) {
return errors.New("备份文件不存在")
}
tools.ExecShell(`rm -rf '` + website.Path + `/*'`)
tools.ExecShell(`unzip -o '` + backupFile + `' -d '` + website.Path + `' 2>&1`)
tools.Chmod(website.Path, 0755)
tools.Chown(website.Path, "www", "www")
return nil
}

View File

@@ -348,7 +348,7 @@ func (r *WebsiteImpl) GetConfig(id int) (WebsiteSetting, error) {
}
setting.Rewrite = tools.ReadFile("/www/server/vhost/rewrite/" + website.Name + ".conf")
setting.Log = tools.ExecShell("tail -n 100 /www/wwwlogs/" + website.Name + ".log")
setting.Log = tools.ExecShell(`tail -n 100 '/www/wwwlogs/` + website.Name + `.log'`)
return setting, nil
}

View File

@@ -1,9 +1,8 @@
<!--
Name: 网站 - 备份
Author: 耗子
Date: 2023-07-21
Date: 2023-07-24
-->
<h1>这里正在装修,下个版本再来看看吧!</h1>
<script type="text/html" template lay-done="layui.data.sendParams(d.params)">
<div class="layui-row">
<div class="layui-col-xs12 layui-col-sm12 layui-col-md12">
@@ -26,8 +25,7 @@ Date: 2023-07-21
<script>
layui.data.sendParams = function (params) {
layui.use(['admin', 'form', 'laydate', 'code'], function () {
var $ = layui.$
, admin = layui.admin
var admin = layui.admin
, layer = layui.layer
, table = layui.table
, upload = layui.upload;
@@ -39,7 +37,7 @@ Date: 2023-07-21
, toolbar: '#website-backup-bar'
, title: '备份列表'
, cols: [[
{field: 'backup', title: '备份名称', width: 500}
{field: 'name', title: '备份名称', width: 500}
, {field: 'size', title: '文件大小'}
, {field: 'right', title: '操作', width: 150, toolbar: '#website-backup-control'}
]]
@@ -56,6 +54,7 @@ Date: 2023-07-21
index = layer.msg('正在上传备份文件,可能需要较长时间,请勿操作...', {
icon: 16
, time: 0
, shade: 0.3
});
}
, done: function (res) {
@@ -63,9 +62,6 @@ Date: 2023-07-21
layer.msg('上传成功!', {icon: 1});
table.reload('website-backup-list');
}
, error: function (res) {
layer.msg('上传失败:' + res.msg, {icon: 2});
}
});
}
});
@@ -75,26 +71,23 @@ Date: 2023-07-21
index = layer.msg('正在备份网站,请稍等...', {
icon: 16
, time: 0
, shade: 0.3
});
admin.req({
url: '/api/panel/website/createBackup'
, type: 'post'
, data: {
name: params.data.name
id: params.data.id
}
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
console.log('耗子Linux面板网站失败接口返回' + result);
layer.alert('备份失败!');
return false;
}
table.reload('website-backup-list');
layer.msg('备份成功!', {icon: 1});
}
, error: function (xhr, status, error) {
console.log('耗子Linux面板ajax请求出错错误' + error);
}
});
}
});
@@ -102,52 +95,46 @@ Date: 2023-07-21
table.on('tool(website-backup-list)', function (obj) {
let data = obj.data;
if (obj.event === 'del') {
layer.confirm('确定要删除网站备份 <b style="color: red;">' + data.backup + '</b> 吗?', function (index) {
layer.confirm('确定要删除网站备份 <b style="color: red;">' + data.name + '</b> 吗?', function (index) {
index = layer.msg('正在删除网站备份,请稍等...', {
icon: 16
, time: 0
, shade: 0.3
});
admin.req({
url: "/api/panel/website/deleteBackup"
, method: 'post'
, type: 'post'
, data: data
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
console.log('耗子Linux面板网站备份删除失败接口返回' + result);
layer.msg('网站备份删除失败,请刷新重试!')
return false;
}
obj.del();
layer.alert('网站备份' + data.backup + '删除成功!');
}
, error: function (xhr, status, error) {
console.log('耗子Linux面板ajax请求出错错误' + error);
layer.alert('网站备份' + data.name + '删除成功!');
}
});
});
} else if (obj.event === 'restore') {
layer.confirm('高风险操作,确定要恢复网站备份 <b style="color: red;">' + data.backup + '</b> 吗?', function (index) {
layer.confirm('高风险操作,确定要恢复网站备份 <b style="color: red;">' + data.name + '</b> 吗?', function (index) {
index = layer.msg('正在恢复网站备份,可能需要较长时间,请勿操作...', {
icon: 16
, time: 0
, shade: 0.3
});
data.name = params.data.name;
data.id = params.data.id;
admin.req({
url: "/api/panel/website/restoreBackup"
, method: 'post'
, type: 'post'
, data: data
, success: function (result) {
layer.close(index);
if (result.code !== 0) {
console.log('耗子Linux面板网站备份恢复失败接口返回' + result);
layer.msg('网站备份恢复失败,请刷新重试!')
return false;
}
layer.alert('网站备份' + data.backup + '恢复成功!');
}
, error: function (xhr, status, error) {
console.log('耗子Linux面板ajax请求出错错误' + error);
layer.alert('网站备份' + data.name + '恢复成功!');
}
});
});

View File

@@ -1,7 +1,7 @@
<!--
Name: 网站 - 编辑
Author: 耗子
Date: 2023-07-21
Date: 2023-07-24
-->
<script type="text/html" template lay-done="layui.data.sendParams(d.params)">
<div class="layui-tab" lay-filter="website-edit-tab">
@@ -321,7 +321,7 @@ Date: 2023-07-21
url: '/api/panel/website/config'
, type: 'post'
, data: {
id: params.id,
id: params.data.id,
domains: domains.join('\n'),
ports: ports.join('\n'),
ssl: $('input[name="ssl"]').prop('checked'),
@@ -365,7 +365,7 @@ Date: 2023-07-21
admin.req({
url: '/api/panel/website/resetConfig'
, type: 'post'
, data: {id: params.id}
, data: {id: params.data.id}
, success: function (res) {
layer.close(index)
if (res.code === 0) {

View File

@@ -1,7 +1,7 @@
<!--
Name: 网站 - 列表
Author: 耗子
Date: 2023-07-71
Date: 2023-07-74
-->
<title>网站</title>
<div class="layui-fluid">
@@ -167,7 +167,6 @@ Date: 2023-07-71
, id: 'LAY-popup-website-edit'
, success: function (layero, index) {
view(this.id).render('website/edit', {
id : data.id,
php: php,
mysql: mysql,
postgresql: postgresql,

View File

@@ -48,6 +48,9 @@ func Web() {
r.Post("updateRemark", websiteController.UpdateRemark)
r.Get("backupList", websiteController.BackupList)
r.Post("createBackup", websiteController.CreateBackup)
r.Post("uploadBackup", websiteController.UploadBackup)
r.Post("restoreBackup", websiteController.RestoreBackup)
r.Post("deleteBackup", websiteController.DeleteBackup)
r.Post("resetConfig", websiteController.ResetConfig)
r.Post("status", websiteController.Status)
})