mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 06:47:20 +08:00
feat: 备份优化
This commit is contained in:
@@ -90,7 +90,7 @@ func initWeb() (*app.Web, error) {
|
||||
databaseServerService := service.NewDatabaseServerService(databaseServerRepo)
|
||||
databaseUserService := service.NewDatabaseUserService(databaseUserRepo)
|
||||
backupService := service.NewBackupService(locale, backupRepo)
|
||||
backupAccountRepo := data.NewBackupAccountRepo(locale, db, logger)
|
||||
backupAccountRepo := data.NewBackupAccountRepo(locale, db, logger, settingRepo)
|
||||
backupAccountService := service.NewBackupAccountService(locale, backupAccountRepo)
|
||||
certService := service.NewCertService(locale, certRepo)
|
||||
certDNSRepo := data.NewCertDNSRepo(db, logger)
|
||||
|
||||
7
go.sum
7
go.sum
@@ -164,6 +164,8 @@ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXi
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE=
|
||||
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
||||
github.com/google/wire v0.7.0 h1:JxUKI6+CVBgCO2WToKy/nQk0sS+amI9z9EjVmdaocj4=
|
||||
github.com/google/wire v0.7.0/go.mod h1:n6YbUQD9cPKTnHXEBN2DXlOp/mVADhVErcMFb0v3J18=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
@@ -318,6 +320,7 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
||||
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||
github.com/pkg/sftp v1.13.10 h1:+5FbKNTe5Z9aspU88DPIKJ9z2KZoaGCu6Sr6kKR/5mU=
|
||||
github.com/pkg/sftp v1.13.10/go.mod h1:bJ1a7uDhrX/4OII+agvy28lzRvQrmIQuaHrcI1HbeGA=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
||||
@@ -430,6 +433,8 @@ golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
|
||||
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -503,6 +508,8 @@ golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
|
||||
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"log/slog"
|
||||
|
||||
"github.com/acepanel/panel/pkg/types"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"gorm.io/gorm"
|
||||
|
||||
@@ -12,24 +13,49 @@ import (
|
||||
)
|
||||
|
||||
type backupAccountRepo struct {
|
||||
t *gotext.Locale
|
||||
db *gorm.DB
|
||||
log *slog.Logger
|
||||
t *gotext.Locale
|
||||
db *gorm.DB
|
||||
log *slog.Logger
|
||||
setting biz.SettingRepo
|
||||
}
|
||||
|
||||
func NewBackupAccountRepo(t *gotext.Locale, db *gorm.DB, log *slog.Logger) biz.BackupAccountRepo {
|
||||
func NewBackupAccountRepo(t *gotext.Locale, db *gorm.DB, log *slog.Logger, setting biz.SettingRepo) biz.BackupAccountRepo {
|
||||
return &backupAccountRepo{
|
||||
t: t,
|
||||
db: db,
|
||||
log: log,
|
||||
t: t,
|
||||
db: db,
|
||||
log: log,
|
||||
setting: setting,
|
||||
}
|
||||
}
|
||||
|
||||
func (r backupAccountRepo) List(page, limit uint) ([]*biz.BackupAccount, int64, error) {
|
||||
accounts := make([]*biz.BackupAccount, 0)
|
||||
// 本地存储
|
||||
path, err := r.setting.Get(biz.SettingKeyBackupPath)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
localStorage := &biz.BackupAccount{
|
||||
ID: 0,
|
||||
Type: biz.BackupAccountTypeLocal,
|
||||
Name: r.t.Get("Local Storage"),
|
||||
Info: types.BackupAccountInfo{
|
||||
Path: path,
|
||||
},
|
||||
}
|
||||
|
||||
var dbAccounts []*biz.BackupAccount
|
||||
var total int64
|
||||
err := r.db.Model(&biz.BackupAccount{}).Order("id desc").Count(&total).Offset(int((page - 1) * limit)).Limit(int(limit)).Find(&accounts).Error
|
||||
return accounts, total, err
|
||||
if err = r.db.Model(&biz.BackupAccount{}).Order("id desc").Count(&total).Offset(int((page - 1) * limit)).Limit(int(limit)).Find(&dbAccounts).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
accounts := make([]*biz.BackupAccount, 0, len(dbAccounts)+1)
|
||||
if page == 1 {
|
||||
accounts = append(accounts, localStorage)
|
||||
}
|
||||
accounts = append(accounts, dbAccounts...)
|
||||
|
||||
return accounts, total + 1, nil
|
||||
}
|
||||
|
||||
func (r backupAccountRepo) Get(id uint) (*biz.BackupAccount, error) {
|
||||
|
||||
@@ -3,14 +3,14 @@ package request
|
||||
import "github.com/acepanel/panel/pkg/types"
|
||||
|
||||
type BackupAccountCreate struct {
|
||||
Type string `form:"type" json:"type" validate:"required|in:local,s3,sftp,webdav"`
|
||||
Type string `form:"type" json:"type" validate:"required|in:s3,sftp,webdav"`
|
||||
Name string `form:"name" json:"name" validate:"required"`
|
||||
Info types.BackupAccountInfo `form:"info" json:"info"`
|
||||
}
|
||||
|
||||
type BackupAccountUpdate struct {
|
||||
ID uint `form:"id" json:"id" validate:"required|exists:backup_accounts,id"`
|
||||
Type string `form:"type" json:"type" validate:"required|in:local,s3,sftp,webdav"`
|
||||
Type string `form:"type" json:"type" validate:"required|in:s3,sftp,webdav"`
|
||||
Name string `form:"name" json:"name" validate:"required"`
|
||||
Info types.BackupAccountInfo `form:"info" json:"info"`
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ export default {
|
||||
list: (type: string, page: number, limit: number): any =>
|
||||
http.Get(`/backup/${type}`, { params: { page, limit } }),
|
||||
// 创建备份
|
||||
create: (type: string, target: string, path: string): any =>
|
||||
http.Post(`/backup/${type}`, { target, path }),
|
||||
create: (type: string, target: string, account_id: number): any =>
|
||||
http.Post(`/backup/${type}`, { target, account_id }),
|
||||
// 上传备份
|
||||
upload: (type: string, formData: FormData): any => http.Post(`/backup/${type}/upload`, formData),
|
||||
// 删除备份
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import backupAccount from '@/api/panel/backupAccount'
|
||||
import { formatDateTime } from '@/utils'
|
||||
import { NButton, NDataTable, NPopconfirm } from 'naive-ui'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { formatDateTime } from '@/utils'
|
||||
|
||||
const { $gettext } = useGettext()
|
||||
|
||||
@@ -11,7 +11,6 @@ const editModal = ref(false)
|
||||
const editId = ref(0)
|
||||
|
||||
const typeOptions = [
|
||||
{ label: $gettext('Local'), value: 'local' },
|
||||
{ label: 'S3', value: 's3' },
|
||||
{ label: 'SFTP', value: 'sftp' },
|
||||
{ label: 'WebDAV', value: 'webdav' }
|
||||
@@ -23,7 +22,7 @@ const styleOptions = [
|
||||
]
|
||||
|
||||
const defaultModel = {
|
||||
type: 'local',
|
||||
type: 's3',
|
||||
name: '',
|
||||
info: {
|
||||
access_key: '',
|
||||
@@ -79,6 +78,7 @@ const columns: any = [
|
||||
width: 200,
|
||||
hideInExcel: true,
|
||||
render(row: any) {
|
||||
const isLocal = row.type === 'local'
|
||||
return [
|
||||
h(
|
||||
NButton,
|
||||
@@ -86,6 +86,7 @@ const columns: any = [
|
||||
size: 'small',
|
||||
type: 'primary',
|
||||
secondary: true,
|
||||
disabled: isLocal,
|
||||
onClick: () => handleEdit(row)
|
||||
},
|
||||
{
|
||||
@@ -105,6 +106,7 @@ const columns: any = [
|
||||
{
|
||||
size: 'small',
|
||||
type: 'error',
|
||||
disabled: isLocal,
|
||||
style: 'margin-left: 15px;'
|
||||
},
|
||||
{
|
||||
@@ -170,9 +172,7 @@ onMounted(() => {
|
||||
<template>
|
||||
<n-flex vertical :size="20">
|
||||
<n-flex>
|
||||
<n-button type="primary" @click="createModal = true">{{
|
||||
$gettext('Add Account')
|
||||
}}</n-button>
|
||||
<n-button type="primary" @click="createModal = true">{{ $gettext('Add Account') }}</n-button>
|
||||
</n-flex>
|
||||
<n-data-table
|
||||
striped
|
||||
@@ -209,25 +209,12 @@ onMounted(() => {
|
||||
>
|
||||
<n-form :model="createModel">
|
||||
<n-form-item :label="$gettext('Name')" required>
|
||||
<n-input
|
||||
v-model:value="createModel.name"
|
||||
:placeholder="$gettext('Enter account name')"
|
||||
/>
|
||||
<n-input v-model:value="createModel.name" :placeholder="$gettext('Enter account name')" />
|
||||
</n-form-item>
|
||||
<n-form-item :label="$gettext('Type')" required>
|
||||
<n-select v-model:value="createModel.type" :options="typeOptions" />
|
||||
</n-form-item>
|
||||
|
||||
<!-- Local Fields -->
|
||||
<template v-if="createModel.type === 'local'">
|
||||
<n-form-item :label="$gettext('Save Directory')" required>
|
||||
<n-input
|
||||
v-model:value="createModel.info.path"
|
||||
:placeholder="$gettext('Enter save directory path')"
|
||||
/>
|
||||
</n-form-item>
|
||||
</template>
|
||||
|
||||
<!-- S3 Fields -->
|
||||
<template v-if="createModel.type === 's3'">
|
||||
<n-form-item :label="$gettext('Access Key')" required>
|
||||
@@ -276,10 +263,7 @@ onMounted(() => {
|
||||
<!-- SFTP Fields -->
|
||||
<template v-if="createModel.type === 'sftp'">
|
||||
<n-form-item :label="$gettext('Host')" required>
|
||||
<n-input
|
||||
v-model:value="createModel.info.host"
|
||||
:placeholder="$gettext('Enter host')"
|
||||
/>
|
||||
<n-input v-model:value="createModel.info.host" :placeholder="$gettext('Enter host')" />
|
||||
</n-form-item>
|
||||
<n-form-item :label="$gettext('Port')" required>
|
||||
<n-input-number
|
||||
@@ -357,25 +341,12 @@ onMounted(() => {
|
||||
>
|
||||
<n-form :model="editModel">
|
||||
<n-form-item :label="$gettext('Name')" required>
|
||||
<n-input
|
||||
v-model:value="editModel.name"
|
||||
:placeholder="$gettext('Enter account name')"
|
||||
/>
|
||||
<n-input v-model:value="editModel.name" :placeholder="$gettext('Enter account name')" />
|
||||
</n-form-item>
|
||||
<n-form-item :label="$gettext('Type')" required>
|
||||
<n-select v-model:value="editModel.type" :options="typeOptions" />
|
||||
</n-form-item>
|
||||
|
||||
<!-- Local Fields -->
|
||||
<template v-if="editModel.type === 'local'">
|
||||
<n-form-item :label="$gettext('Save Directory')" required>
|
||||
<n-input
|
||||
v-model:value="editModel.info.path"
|
||||
:placeholder="$gettext('Enter save directory path')"
|
||||
/>
|
||||
</n-form-item>
|
||||
</template>
|
||||
|
||||
<!-- S3 Fields -->
|
||||
<template v-if="editModel.type === 's3'">
|
||||
<n-form-item :label="$gettext('Access Key')" required>
|
||||
@@ -424,10 +395,7 @@ onMounted(() => {
|
||||
<!-- SFTP Fields -->
|
||||
<template v-if="editModel.type === 'sftp'">
|
||||
<n-form-item :label="$gettext('Host')" required>
|
||||
<n-input
|
||||
v-model:value="editModel.info.host"
|
||||
:placeholder="$gettext('Enter host')"
|
||||
/>
|
||||
<n-input v-model:value="editModel.info.host" :placeholder="$gettext('Enter host')" />
|
||||
</n-form-item>
|
||||
<n-form-item :label="$gettext('Port')" required>
|
||||
<n-input-number
|
||||
@@ -438,10 +406,7 @@ onMounted(() => {
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item :label="$gettext('Username')" required>
|
||||
<n-input
|
||||
v-model:value="editModel.info.user"
|
||||
:placeholder="$gettext('Enter username')"
|
||||
/>
|
||||
<n-input v-model:value="editModel.info.user" :placeholder="$gettext('Enter username')" />
|
||||
</n-form-item>
|
||||
<n-form-item :label="$gettext('Password')" required>
|
||||
<n-input
|
||||
@@ -468,10 +433,7 @@ onMounted(() => {
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item :label="$gettext('Username')" required>
|
||||
<n-input
|
||||
v-model:value="editModel.info.user"
|
||||
:placeholder="$gettext('Enter username')"
|
||||
/>
|
||||
<n-input v-model:value="editModel.info.user" :placeholder="$gettext('Enter username')" />
|
||||
</n-form-item>
|
||||
<n-form-item :label="$gettext('Password')" required>
|
||||
<n-input
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import backup from '@/api/panel/backup'
|
||||
import backupAccount from '@/api/panel/backupAccount'
|
||||
import type { MessageReactive } from 'naive-ui'
|
||||
import { NButton, NDataTable, NFlex, NInput, NPopconfirm } from 'naive-ui'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
@@ -19,9 +20,11 @@ const uploadModal = ref(false)
|
||||
const createModal = ref(false)
|
||||
const createModel = ref({
|
||||
target: '',
|
||||
path: ''
|
||||
account_id: 0
|
||||
})
|
||||
|
||||
const accounts = ref<any[]>([])
|
||||
|
||||
const restoreModal = ref(false)
|
||||
const restoreModel = ref({
|
||||
file: '',
|
||||
@@ -115,7 +118,7 @@ const { loading, data, page, total, pageSize, pageCount, refresh } = usePaginati
|
||||
)
|
||||
|
||||
const handleCreate = () => {
|
||||
useRequest(backup.create(type.value, createModel.value.target, createModel.value.path)).onSuccess(
|
||||
useRequest(backup.create(type.value, createModel.value.target, createModel.value.account_id)).onSuccess(
|
||||
() => {
|
||||
createModal.value = false
|
||||
window.$bus.emit('backup:refresh')
|
||||
@@ -178,6 +181,15 @@ onMounted(() => {
|
||||
})
|
||||
}
|
||||
})
|
||||
useRequest(backupAccount.list(1, 10000)).onSuccess(({ data }: { data: any }) => {
|
||||
for (const item of data.items) {
|
||||
accounts.value.push({
|
||||
label: item.name,
|
||||
value: item.id
|
||||
})
|
||||
}
|
||||
createModel.value.account_id = accounts.value[0]?.value || 0
|
||||
})
|
||||
refresh()
|
||||
window.$bus.on('backup:refresh', refresh)
|
||||
})
|
||||
@@ -189,6 +201,9 @@ onUnmounted(() => {
|
||||
|
||||
<template>
|
||||
<n-flex vertical :size="20">
|
||||
<n-alert type="info">
|
||||
{{ $gettext('Only local backups are displayed here. Remote backups are stored in the corresponding backup account.') }}
|
||||
</n-alert>
|
||||
<n-flex>
|
||||
<n-button type="primary" @click="createModal = true">{{
|
||||
$gettext('Create Backup')
|
||||
@@ -244,12 +259,11 @@ onUnmounted(() => {
|
||||
:placeholder="$gettext('Enter database name')"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item path="path" :label="$gettext('Save Directory')">
|
||||
<n-input
|
||||
v-model:value="createModel.path"
|
||||
type="text"
|
||||
@keydown.enter.prevent
|
||||
:placeholder="$gettext('Leave empty to use default path')"
|
||||
<n-form-item path="account_id" :label="$gettext('Backup Account')">
|
||||
<n-select
|
||||
v-model:value="createModel.account_id"
|
||||
:options="accounts"
|
||||
:placeholder="$gettext('Select backup account')"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
|
||||
Reference in New Issue
Block a user