1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-01-31 14:08:06 +08:00

feat: 完成网站上传与恢复功能

This commit is contained in:
ssongliu 2022-12-01 16:21:49 +08:00 committed by ssongliu
parent e66ce1a9f2
commit 0d95cee924
13 changed files with 298 additions and 196 deletions

View File

@ -63,6 +63,24 @@ func (b *BaseApi) BackupWebsite(c *gin.Context) {
helper.SuccessWithData(c, nil) helper.SuccessWithData(c, nil)
} }
func (b *BaseApi) RecoverWebsiteByUpload(c *gin.Context) {
var req dto.WebSiteRecoverByFile
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := global.VALID.Struct(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := websiteService.RecoverByUpload(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
func (b *BaseApi) RecoverWebsite(c *gin.Context) { func (b *BaseApi) RecoverWebsite(c *gin.Context) {
var req dto.WebSiteRecover var req dto.WebSiteRecover
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&req); err != nil {

View File

@ -57,6 +57,13 @@ type WebSiteRecover struct {
BackupName string `json:"backupName" validate:"required"` BackupName string `json:"backupName" validate:"required"`
} }
type WebSiteRecoverByFile struct {
WebsiteName string `json:"websiteName" validate:"required"`
Type string `json:"type" validate:"required"`
FileDir string `json:"fileDir" validate:"required"`
FileName string `json:"fileName" validate:"required"`
}
type WebSiteDTO struct { type WebSiteDTO struct {
model.WebSite model.WebSite
} }

View File

@ -152,7 +152,7 @@ func (u *BackupService) BatchDeleteRecord(ids []uint) error {
} }
for _, record := range records { for _, record := range records {
if record.Source == "LOCAL" { if record.Source == "LOCAL" {
if err := os.Remove(record.FileDir + record.FileName); err != nil { if err := os.Remove(record.FileDir + "/" + record.FileName); err != nil {
global.LOG.Errorf("remove file %s failed, err: %v", record.FileDir+record.FileName, err) global.LOG.Errorf("remove file %s failed, err: %v", record.FileDir+record.FileName, err)
} }
} else { } else {

View File

@ -34,6 +34,7 @@ type IWebsiteService interface {
GetWebsiteOptions() ([]string, error) GetWebsiteOptions() ([]string, error)
Backup(domain string) error Backup(domain string) error
Recover(req dto.WebSiteRecover) error Recover(req dto.WebSiteRecover) error
RecoverByUpload(req dto.WebSiteRecoverByFile) error
UpdateWebsite(req dto.WebSiteUpdate) error UpdateWebsite(req dto.WebSiteUpdate) error
DeleteWebSite(req dto.WebSiteDel) error DeleteWebSite(req dto.WebSiteDel) error
} }
@ -149,15 +150,40 @@ func (w WebsiteService) Backup(domain string) error {
return nil return nil
} }
func (w WebsiteService) RecoverByUpload(req dto.WebSiteRecoverByFile) error {
if err := handleUnTar(fmt.Sprintf("%s/%s", req.FileDir, req.FileName), req.FileDir); err != nil {
return err
}
tmpDir := fmt.Sprintf("%s/%s", req.FileDir, strings.ReplaceAll(req.FileName, ".tar.gz", ""))
webJson, err := os.ReadFile(fmt.Sprintf("%s/website.json", tmpDir))
if err != nil {
return err
}
var websiteInfo WebSiteInfo
if err := json.Unmarshal(webJson, &websiteInfo); err != nil {
return err
}
if websiteInfo.WebsiteName != req.WebsiteName || websiteInfo.WebsiteType != req.Type {
return errors.New("上传文件与选中网站不匹配,无法恢复")
}
website, err := websiteRepo.GetFirst(websiteRepo.WithByDomain(req.WebsiteName))
if err != nil {
return err
}
if err := handleWebsiteRecover(&website, tmpDir); err != nil {
return err
}
return nil
}
func (w WebsiteService) Recover(req dto.WebSiteRecover) error { func (w WebsiteService) Recover(req dto.WebSiteRecover) error {
website, err := websiteRepo.GetFirst(websiteRepo.WithByDomain(req.WebsiteName)) website, err := websiteRepo.GetFirst(websiteRepo.WithByDomain(req.WebsiteName))
if err != nil { if err != nil {
return err return err
} }
app, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID))
if err != nil {
return err
}
if !strings.Contains(req.BackupName, "/") { if !strings.Contains(req.BackupName, "/") {
return errors.New("error path of request") return errors.New("error path of request")
} }
@ -167,71 +193,9 @@ func (w WebsiteService) Recover(req dto.WebSiteRecover) error {
return err return err
} }
fileDir = fileDir + "/" + fileName fileDir = fileDir + "/" + fileName
resource, err := appInstallResourceRepo.GetFirst(appInstallResourceRepo.WithAppInstallId(website.AppInstallID))
if err != nil {
return err
}
nginxInfo, err := appInstallRepo.LoadBaseInfoByKey("nginx")
if err != nil {
return err
}
mysqlInfo, err := appInstallRepo.LoadBaseInfoByKey("mysql")
if err != nil {
return err
}
db, err := mysqlRepo.Get(commonRepo.WithByID(resource.ResourceId))
if err != nil {
return err
}
src, err := os.OpenFile(fmt.Sprintf("%s/%s.conf", fileDir, website.PrimaryDomain), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775)
if err != nil {
return err
}
defer src.Close()
var out *os.File
nginxConfDir := fmt.Sprintf("%s/nginx/%s/conf/conf.d/%s.conf", constant.AppInstallDir, nginxInfo.Name, website.PrimaryDomain)
if _, err := os.Stat(nginxConfDir); err != nil {
out, err = os.Create(fmt.Sprintf("%s/%s.conf", nginxConfDir, website.PrimaryDomain))
if err != nil {
return err
}
} else {
out, err = os.OpenFile(nginxConfDir, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
return err
}
}
defer out.Close()
_, _ = io.Copy(out, src)
if website.Type == "deployment" {
cmd := exec.Command("docker", "exec", "-i", mysqlInfo.ContainerName, "mysql", "-uroot", "-p"+mysqlInfo.Password, db.Name)
sql, err := os.OpenFile(fmt.Sprintf("%s/%s.sql", fileDir, website.PrimaryDomain), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
return err
}
cmd.Stdin = sql
stdout, err := cmd.CombinedOutput()
if err != nil {
return errors.New(string(stdout))
}
appDir := fmt.Sprintf("%s/%s", constant.AppInstallDir, app.App.Key) if err := handleWebsiteRecover(&website, fileDir); err != nil {
if err := handleUnTar(fmt.Sprintf("%s/%s.web.tar.gz", fileDir, website.PrimaryDomain), appDir); err != nil { return err
return err
}
if _, err := compose.Restart(fmt.Sprintf("%s/%s/docker-compose.yml", appDir, app.Name)); err != nil {
return err
}
} else {
appDir := fmt.Sprintf("%s/nginx/%s/www", constant.AppInstallDir, nginxInfo.Name)
if err := handleUnTar(fmt.Sprintf("%s/%s.web.tar.gz", fileDir, website.PrimaryDomain), appDir); err != nil {
return err
}
}
cmd := exec.Command("docker", "exec", "-i", nginxInfo.ContainerName, "nginx", "-s", "reload")
stdout, err := cmd.CombinedOutput()
if err != nil {
return errors.New(string(stdout))
} }
return nil return nil
} }
@ -534,26 +498,6 @@ func handleWebsiteBackup(backupType, baseDir, backupDir, domain, backupName stri
if err != nil { if err != nil {
return err return err
} }
app, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID))
if err != nil {
return err
}
resource, err := appInstallResourceRepo.GetFirst(appInstallResourceRepo.WithAppInstallId(website.AppInstallID))
if err != nil {
return err
}
mysqlInfo, err := appInstallRepo.LoadBaseInfoByKey(resource.Key)
if err != nil {
return err
}
nginxInfo, err := appInstallRepo.LoadBaseInfoByKey("nginx")
if err != nil {
return err
}
db, err := mysqlRepo.Get(commonRepo.WithByID(resource.ResourceId))
if err != nil {
return err
}
tmpDir := fmt.Sprintf("%s/%s/%s", baseDir, backupDir, backupName) tmpDir := fmt.Sprintf("%s/%s/%s", baseDir, backupDir, backupName)
if _, err := os.Stat(tmpDir); err != nil && os.IsNotExist(err) { if _, err := os.Stat(tmpDir); err != nil && os.IsNotExist(err) {
@ -563,21 +507,28 @@ func handleWebsiteBackup(backupType, baseDir, backupDir, domain, backupName stri
} }
} }
} }
if err := saveNginxConf(nginxInfo.Name, website.PrimaryDomain, tmpDir); err != nil {
return err
}
if err := saveWebsiteJson(&website, tmpDir); err != nil { if err := saveWebsiteJson(&website, tmpDir); err != nil {
return err return err
} }
if website.Type == "deployment" { nginxInfo, err := appInstallRepo.LoadBaseInfoByKey("nginx")
dbFile := fmt.Sprintf("%s/%s.sql", tmpDir, website.PrimaryDomain) if err != nil {
outfile, _ := os.OpenFile(dbFile, os.O_RDWR|os.O_CREATE, 0755) return err
cmd := exec.Command("docker", "exec", mysqlInfo.ContainerName, "mysqldump", "-uroot", "-p"+mysqlInfo.Password, db.Name) }
cmd.Stdout = outfile nginxConfFile := fmt.Sprintf("%s/nginx/%s/conf/conf.d/%s.conf", constant.AppInstallDir, nginxInfo.Name, website.PrimaryDomain)
_ = cmd.Run() if err := copyConf(nginxConfFile, fmt.Sprintf("%s/%s.conf", tmpDir, website.PrimaryDomain)); err != nil {
_ = cmd.Wait() return err
}
if website.Type == "deployment" {
if err := mysqlOpration(&website, "backup", tmpDir); err != nil {
return err
}
app, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID))
if err != nil {
return err
}
websiteDir := fmt.Sprintf("%s/%s/%s", constant.AppInstallDir, app.App.Key, app.Name) websiteDir := fmt.Sprintf("%s/%s/%s", constant.AppInstallDir, app.App.Key, app.Name)
if err := handleTar(websiteDir, tmpDir, fmt.Sprintf("%s.web.tar.gz", website.PrimaryDomain), ""); err != nil { if err := handleTar(websiteDir, tmpDir, fmt.Sprintf("%s.web.tar.gz", website.PrimaryDomain), ""); err != nil {
return err return err
@ -612,6 +563,85 @@ func handleWebsiteBackup(backupType, baseDir, backupDir, domain, backupName stri
return nil return nil
} }
func handleWebsiteRecover(website *model.WebSite, fileDir string) error {
nginxInfo, err := appInstallRepo.LoadBaseInfoByKey("nginx")
if err != nil {
return err
}
nginxConfFile := fmt.Sprintf("%s/nginx/%s/conf/conf.d/%s.conf", constant.AppInstallDir, nginxInfo.Name, website.PrimaryDomain)
if err := copyConf(fmt.Sprintf("%s/%s.conf", fileDir, website.PrimaryDomain), nginxConfFile); err != nil {
return err
}
if website.Type == "deployment" {
if err := mysqlOpration(website, "recover", fileDir); err != nil {
return err
}
app, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID))
if err != nil {
return err
}
appDir := fmt.Sprintf("%s/%s", constant.AppInstallDir, app.App.Key)
if err := handleUnTar(fmt.Sprintf("%s/%s.web.tar.gz", fileDir, website.PrimaryDomain), appDir); err != nil {
return err
}
if _, err := compose.Restart(fmt.Sprintf("%s/%s/docker-compose.yml", appDir, app.Name)); err != nil {
return err
}
} else {
appDir := fmt.Sprintf("%s/nginx/%s/www", constant.AppInstallDir, nginxInfo.Name)
if err := handleUnTar(fmt.Sprintf("%s/%s.web.tar.gz", fileDir, website.PrimaryDomain), appDir); err != nil {
return err
}
}
cmd := exec.Command("docker", "exec", "-i", nginxInfo.ContainerName, "nginx", "-s", "reload")
stdout, err := cmd.CombinedOutput()
if err != nil {
return errors.New(string(stdout))
}
_ = os.RemoveAll(fileDir)
return nil
}
func mysqlOpration(website *model.WebSite, operation, filePath string) error {
mysqlInfo, err := appInstallRepo.LoadBaseInfoByKey("mysql")
if err != nil {
return err
}
resource, err := appInstallResourceRepo.GetFirst(appInstallResourceRepo.WithAppInstallId(website.AppInstallID))
if err != nil {
return err
}
db, err := mysqlRepo.Get(commonRepo.WithByID(resource.ResourceId))
if err != nil {
return err
}
if operation == "backup" {
dbFile := fmt.Sprintf("%s/%s.sql", filePath, website.PrimaryDomain)
outfile, _ := os.OpenFile(dbFile, os.O_RDWR|os.O_CREATE, 0755)
defer outfile.Close()
cmd := exec.Command("docker", "exec", mysqlInfo.ContainerName, "mysqldump", "-uroot", "-p"+mysqlInfo.Password, db.Name)
cmd.Stdout = outfile
_ = cmd.Run()
_ = cmd.Wait()
return nil
}
cmd := exec.Command("docker", "exec", "-i", mysqlInfo.ContainerName, "mysql", "-uroot", "-p"+mysqlInfo.Password, db.Name)
sqlfile, err := os.Open(fmt.Sprintf("%s/%s.sql", filePath, website.PrimaryDomain))
if err != nil {
return err
}
defer sqlfile.Close()
cmd.Stdin = sqlfile
stdout, err := cmd.CombinedOutput()
if err != nil {
return errors.New(string(stdout))
}
return nil
}
func saveWebsiteJson(website *model.WebSite, tmpDir string) error { func saveWebsiteJson(website *model.WebSite, tmpDir string) error {
var WebSiteInfo WebSiteInfo var WebSiteInfo WebSiteInfo
WebSiteInfo.WebsiteType = website.Type WebSiteInfo.WebsiteType = website.Type
@ -629,19 +659,20 @@ func saveWebsiteJson(website *model.WebSite, tmpDir string) error {
return nil return nil
} }
func saveNginxConf(nginxName, websiteDomain, tmpDir string) error { func copyConf(srcPath, dstPath string) error {
nginxConfFile := fmt.Sprintf("%s/nginx/%s/conf/conf.d/%s.conf", constant.AppInstallDir, nginxName, websiteDomain) if _, err := os.Stat(srcPath); err != nil {
src, err := os.OpenFile(nginxConfFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775) return err
}
src, err := os.OpenFile(srcPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775)
if err != nil { if err != nil {
return err return err
} }
defer src.Close() defer src.Close()
out, err := os.Create(fmt.Sprintf("%s/%s.conf", tmpDir, websiteDomain)) out, err := os.Create(dstPath)
if err != nil { if err != nil {
return err return err
} }
defer out.Close() defer out.Close()
_, _ = io.Copy(out, src) _, _ = io.Copy(out, src)
return nil return nil
} }

View File

@ -131,7 +131,7 @@ var AddTableBackupAccount = &gormigrate.Migration{
} }
item := &model.BackupAccount{ item := &model.BackupAccount{
Type: "LOCAL", Type: "LOCAL",
Vars: "{\"dir\":\"/opt/1Panel/backup\"}", Vars: "{\"dir\":\"/opt/1Panel/data/backup\"}",
} }
if err := tx.Create(item).Error; err != nil { if err := tx.Create(item).Error; err != nil {
return err return err

View File

@ -19,6 +19,7 @@ func (a *WebsiteRouter) InitWebsiteRouter(Router *gin.RouterGroup) {
groupRouter.GET("/options", baseApi.GetWebsiteOptions) groupRouter.GET("/options", baseApi.GetWebsiteOptions)
groupRouter.POST("/backup/:domain", baseApi.BackupWebsite) groupRouter.POST("/backup/:domain", baseApi.BackupWebsite)
groupRouter.POST("/recover", baseApi.RecoverWebsite) groupRouter.POST("/recover", baseApi.RecoverWebsite)
groupRouter.POST("/recover/byupload", baseApi.RecoverWebsiteByUpload)
groupRouter.POST("/update", baseApi.UpdateWebSite) groupRouter.POST("/update", baseApi.UpdateWebSite)
groupRouter.GET("/:id", baseApi.GetWebSite) groupRouter.GET("/:id", baseApi.GetWebSite)
groupRouter.GET("/:id/nginx", baseApi.GetWebSiteNginx) groupRouter.GET("/:id/nginx", baseApi.GetWebSiteNginx)

View File

@ -43,6 +43,12 @@ export namespace WebSite {
type: string; type: string;
backupName: string; backupName: string;
} }
export interface WebsiteRecoverByUpload {
websiteName: string;
type: string;
fileDir: string;
fileName: string;
}
export interface WebSiteDel { export interface WebSiteDel {
id: number; id: number;

View File

@ -17,6 +17,9 @@ export const BackupWebsite = (id: number) => {
export const RecoverWebsite = (req: WebSite.WebSiteRecover) => { export const RecoverWebsite = (req: WebSite.WebSiteRecover) => {
return http.post(`/websites/recover`, req); return http.post(`/websites/recover`, req);
}; };
export const RecoverWebsiteByUpload = (req: WebSite.WebsiteRecoverByUpload) => {
return http.post(`/websites/recover/byupload`, req);
};
export const UpdateWebsite = (req: WebSite.WebSiteUpdateReq) => { export const UpdateWebsite = (req: WebSite.WebSiteUpdateReq) => {
return http.post<any>(`/websites/update`, req); return http.post<any>(`/websites/update`, req);

View File

@ -695,6 +695,9 @@ export default {
author: 'author', author: 'author',
source: 'source', source: 'source',
sync: 'sync', sync: 'sync',
supportUpType: '.tar.gz files are supported only',
zipFormat:
'.tar.gz compressed package structure: test.tar.gz compressed package must contain the website.json file',
appName: 'App Name', appName: 'App Name',
status: 'status', status: 'status',
container: 'Container', container: 'Container',

View File

@ -739,6 +739,8 @@ export default {
type: '类型', type: '类型',
static: '静态网站', static: '静态网站',
deployment: '反向代理', deployment: '反向代理',
supportUpType: '仅支持 tar.gz 文件',
zipFormat: 'tar.gz 压缩包结构test.tar.gz 压缩包内必需包含 website.json 文件',
proxy: '反向代理', proxy: '反向代理',
alias: '代号', alias: '代号',
remark: '备注', remark: '备注',

View File

@ -6,7 +6,13 @@
<span>{{ $t('database.backup') }} - {{ websiteName }}</span> <span>{{ $t('database.backup') }} - {{ websiteName }}</span>
</div> </div>
</template> </template>
<ComplexTable :pagination-config="paginationConfig" v-model:selects="selects" @search="search" :data="data"> <ComplexTable
v-loading="loading"
:pagination-config="paginationConfig"
v-model:selects="selects"
@search="search"
:data="data"
>
<template #toolbar> <template #toolbar>
<el-button type="primary" @click="onBackup()"> <el-button type="primary" @click="onBackup()">
{{ $t('database.backup') }} {{ $t('database.backup') }}
@ -43,6 +49,7 @@ import { Backup } from '@/api/interface/backup';
import { BackupWebsite, RecoverWebsite } from '@/api/modules/website'; import { BackupWebsite, RecoverWebsite } from '@/api/modules/website';
const selects = ref<any>([]); const selects = ref<any>([]);
const loading = ref(false);
const data = ref(); const data = ref();
const paginationConfig = reactive({ const paginationConfig = reactive({
@ -88,14 +95,28 @@ const onRecover = async (row: Backup.RecordInfo) => {
type: websiteType.value, type: websiteType.value,
backupName: row.fileDir + '/' + row.fileName, backupName: row.fileDir + '/' + row.fileName,
}; };
await RecoverWebsite(params); loading.value = true;
ElMessage.success(i18n.global.t('commons.msg.operationSuccess')); await RecoverWebsite(params)
.then(() => {
loading.value = false;
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
})
.finally(() => {
loading.value = false;
});
}; };
const onBackup = async () => { const onBackup = async () => {
await BackupWebsite(websiteName.value); loading.value = true;
ElMessage.success(i18n.global.t('commons.msg.operationSuccess')); await BackupWebsite(websiteName.value)
search(); .then(() => {
loading.value = false;
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
search();
})
.finally(() => {
loading.value = false;
});
}; };
const onDownload = async (row: Backup.RecordInfo) => { const onDownload = async (row: Backup.RecordInfo) => {

View File

@ -138,8 +138,8 @@ const buttons = [
label: i18n.global.t('database.loadBackup'), label: i18n.global.t('database.loadBackup'),
click: (row: WebSite.WebSite) => { click: (row: WebSite.WebSite) => {
let params = { let params = {
mysqlName: 'test', websiteName: row.primaryDomain,
dbName: row.primaryDomain, websiteType: row.type,
}; };
uploadRef.value!.acceptParams(params); uploadRef.value!.acceptParams(params);
}, },

View File

@ -1,64 +1,66 @@
<template> <template>
<div> <div :v-loading="loading">
<el-dialog v-model="upVisiable" :destroy-on-close="true" :close-on-click-modal="false" width="70%"> <el-dialog v-model="upVisiable" :destroy-on-close="true" :close-on-click-modal="false" width="70%">
<template #header> <template #header>
<div class="card-header"> <div class="card-header">
<span>{{ $t('commons.button.import') }}</span> <span>{{ $t('commons.button.import') }}</span>
</div> </div>
</template> </template>
<el-upload <div v-loading="loading">
ref="uploadRef" <el-upload
:on-change="fileOnChange" ref="uploadRef"
:before-upload="beforeAvatarUpload" :on-change="fileOnChange"
class="upload-demo" :before-upload="beforeAvatarUpload"
:auto-upload="false" class="upload-demo"
> :auto-upload="false"
<template #trigger> >
<el-button type="primary" plain>{{ $t('database.selectFile') }}</el-button> <template #trigger>
</template> <el-button type="primary" plain>{{ $t('database.selectFile') }}</el-button>
<el-button style="margin-left: 10px" icon="Upload" @click="onSubmit"> </template>
{{ $t('commons.button.upload') }} <el-button style="margin-left: 10px" icon="Upload" @click="onSubmit">
</el-button> {{ $t('commons.button.upload') }}
</el-upload>
<div style="margin-left: 10px">
<span class="input-help">{{ $t('database.supportUpType') }}</span>
<span class="input-help">
{{ $t('database.zipFormat') }}
</span>
</div>
<el-divider />
<ComplexTable :pagination-config="paginationConfig" v-model:selects="selects" :data="data">
<template #toolbar>
<el-button
style="margin-left: 10px"
type="danger"
plain
:disabled="selects.length === 0"
@click="onBatchDelete(null)"
>
{{ $t('commons.button.delete') }}
</el-button> </el-button>
</template> </el-upload>
<el-table-column type="selection" fix /> <div style="margin-left: 10px">
<el-table-column :label="$t('commons.table.name')" show-overflow-tooltip prop="name" /> <span class="input-help">{{ $t('website.supportUpType') }}</span>
<el-table-column :label="$t('file.size')" prop="size"> <span class="input-help">
<template #default="{ row }"> {{ $t('website.zipFormat') }}
{{ computeSize(row.size) }} </span>
</div>
<el-divider />
<ComplexTable :pagination-config="paginationConfig" v-model:selects="selects" :data="data">
<template #toolbar>
<el-button
style="margin-left: 10px"
type="danger"
plain
:disabled="selects.length === 0"
@click="onBatchDelete(null)"
>
{{ $t('commons.button.delete') }}
</el-button>
</template> </template>
</el-table-column> <el-table-column type="selection" fix />
<el-table-column :label="$t('commons.table.createdAt')" min-width="80" fix> <el-table-column :label="$t('commons.table.name')" show-overflow-tooltip prop="name" />
<template #default="{ row }"> <el-table-column :label="$t('file.size')" prop="size">
{{ dateFromat(0, 0, row.modTime) }} <template #default="{ row }">
</template> {{ computeSize(row.size) }}
</el-table-column> </template>
<fu-table-operations </el-table-column>
width="300px" <el-table-column :label="$t('commons.table.createdAt')" min-width="80" fix>
:buttons="buttons" <template #default="{ row }">
:ellipsis="10" {{ dateFromat(0, 0, row.modTime) }}
:label="$t('commons.table.operate')" </template>
fix </el-table-column>
/> <fu-table-operations
</ComplexTable> width="300px"
:buttons="buttons"
:ellipsis="10"
:label="$t('commons.table.operate')"
fix
/>
</ComplexTable>
</div>
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
@ -68,14 +70,15 @@ import ComplexTable from '@/components/complex-table/index.vue';
import { reactive, ref } from 'vue'; import { reactive, ref } from 'vue';
import { computeSize, dateFromat } from '@/utils/util'; import { computeSize, dateFromat } from '@/utils/util';
import { useDeleteData } from '@/hooks/use-delete-data'; import { useDeleteData } from '@/hooks/use-delete-data';
import { recoverByUpload } from '@/api/modules/database';
import i18n from '@/lang'; import i18n from '@/lang';
import { ElMessage, UploadFile, UploadFiles, UploadInstance, UploadProps } from 'element-plus'; import { ElMessage, UploadFile, UploadFiles, UploadInstance, UploadProps } from 'element-plus';
import { File } from '@/api/interface/file'; import { File } from '@/api/interface/file';
import { BatchDeleteFile, GetFilesList, UploadFileData } from '@/api/modules/files'; import { BatchDeleteFile, GetFilesList, UploadFileData } from '@/api/modules/files';
import { RecoverWebsiteByUpload } from '@/api/modules/website';
const selects = ref<any>([]); const selects = ref<any>([]);
const baseDir = '/opt/1Panel/data/uploads/website/'; const baseDir = ref();
const loading = ref(false);
const data = ref(); const data = ref();
const paginationConfig = reactive({ const paginationConfig = reactive({
@ -85,16 +88,18 @@ const paginationConfig = reactive({
}); });
const upVisiable = ref(false); const upVisiable = ref(false);
const mysqlName = ref(); const websiteName = ref();
const dbName = ref(); const websiteType = ref();
interface DialogProps { interface DialogProps {
mysqlName: string; websiteName: string;
dbName: string; websiteType: string;
} }
const acceptParams = (params: DialogProps): void => { const acceptParams = (params: DialogProps): void => {
mysqlName.value = params.mysqlName; websiteName.value = params.websiteName;
dbName.value = params.dbName; websiteType.value = params.websiteType;
upVisiable.value = true; upVisiable.value = true;
baseDir.value = '/opt/1Panel/data/uploads/website/' + websiteName.value;
search(); search();
}; };
@ -102,7 +107,7 @@ const search = async () => {
let params = { let params = {
page: paginationConfig.currentPage, page: paginationConfig.currentPage,
pageSize: paginationConfig.pageSize, pageSize: paginationConfig.pageSize,
path: baseDir, path: baseDir.value,
expand: true, expand: true,
}; };
const res = await GetFilesList(params); const res = await GetFilesList(params);
@ -112,30 +117,29 @@ const search = async () => {
const onRecover = async (row: File.File) => { const onRecover = async (row: File.File) => {
let params = { let params = {
mysqlName: mysqlName.value, websiteName: websiteName.value,
dbName: dbName.value, type: websiteType.value,
fileDir: baseDir, fileDir: baseDir.value,
fileName: row.name, fileName: row.name,
}; };
await recoverByUpload(params); loading.value = true;
ElMessage.success(i18n.global.t('commons.msg.operationSuccess')); await RecoverWebsiteByUpload(params)
.then(() => {
loading.value = false;
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
})
.finally(() => {
loading.value = false;
});
}; };
const uploaderFiles = ref<UploadFiles>([]); const uploaderFiles = ref<UploadFiles>([]);
const uploadRef = ref<UploadInstance>(); const uploadRef = ref<UploadInstance>();
const beforeAvatarUpload: UploadProps['beforeUpload'] = (rawFile) => { const beforeAvatarUpload: UploadProps['beforeUpload'] = (rawFile) => {
if ( if (rawFile.name.endsWith('.tar.gz')) {
rawFile.name.endsWith('.sql') ||
rawFile.name.endsWith('.gz') ||
rawFile.name.endsWith('.zip') ||
rawFile.name.endsWith('.tgz')
) {
ElMessage.error(i18n.global.t('database.unSupportType')); ElMessage.error(i18n.global.t('database.unSupportType'));
return false; return false;
} else if (rawFile.size / 1024 / 1024 > 10) {
ElMessage.error(i18n.global.t('database.unSupportSize'));
return false;
} }
return true; return true;
}; };
@ -156,21 +160,27 @@ const onSubmit = () => {
if (uploaderFiles.value[0]!.raw != undefined) { if (uploaderFiles.value[0]!.raw != undefined) {
formData.append('file', uploaderFiles.value[0]!.raw); formData.append('file', uploaderFiles.value[0]!.raw);
} }
formData.append('path', baseDir); formData.append('path', baseDir.value + '/');
UploadFileData(formData, {}).then(() => { loading.value = true;
ElMessage.success(i18n.global.t('file.uploadSuccess')); UploadFileData(formData, {})
handleClose(); .then(() => {
search(); loading.value = false;
}); ElMessage.success(i18n.global.t('file.uploadSuccess'));
handleClose();
search();
})
.finally(() => {
loading.value = false;
});
}; };
const onBatchDelete = async (row: File.File | null) => { const onBatchDelete = async (row: File.File | null) => {
let files: Array<string> = []; let files: Array<string> = [];
if (row) { if (row) {
files.push(baseDir + row.name); files.push(baseDir.value + '/' + row.name);
} else { } else {
selects.value.forEach((item: File.File) => { selects.value.forEach((item: File.File) => {
files.push(baseDir + item.name); files.push(baseDir.value + '/' + item.name);
}); });
} }
await useDeleteData(BatchDeleteFile, { isDir: false, paths: files }, 'commons.msg.delete', true); await useDeleteData(BatchDeleteFile, { isDir: false, paths: files }, 'commons.msg.delete', true);