diff --git a/backend/app/service/cronjob_helper.go b/backend/app/service/cronjob_helper.go index a9281ed95..d19446ce9 100644 --- a/backend/app/service/cronjob_helper.go +++ b/backend/app/service/cronjob_helper.go @@ -3,9 +3,11 @@ package service import ( "context" "fmt" + "github.com/1Panel-dev/1Panel/backend/utils/files" "os" "path" "strings" + "sync" "time" "github.com/1Panel-dev/1Panel/backend/app/model" @@ -54,6 +56,11 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) { } message = []byte(stdout) u.HandleRmExpired("LOCAL", "", cronjob, nil) + case "cutWebsiteLog": + record.File, err = u.handleCutWebsiteLog(cronjob, record.StartTime) + if err != nil { + global.LOG.Errorf("cut website log file failed, err: %v", err) + } } if err != nil { cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), string(message)) @@ -157,7 +164,7 @@ func handleTar(sourceDir, targetDir, name, exclusionRules string) error { if len(exclude) == 0 { continue } - excludeRules += (" --exclude " + exclude) + excludeRules += " --exclude " + exclude } path := "" if strings.Contains(sourceDir, "/") { @@ -262,6 +269,72 @@ func (u *CronjobService) handleDatabase(cronjob model.Cronjob, app *repo.RootInf return paths, nil } +func (u *CronjobService) handleCutWebsiteLog(cronjob *model.Cronjob, startTime time.Time) (string, error) { + var ( + websites []string + err error + filePaths []string + ) + if cronjob.Website == "all" { + websites, _ = NewIWebsiteService().GetWebsiteOptions() + if len(websites) == 0 { + return "", nil + } + } else { + websites = append(websites, cronjob.Website) + } + + nginx, err := getAppInstallByKey(constant.AppOpenresty) + if err != nil { + return "", nil + } + baseDir := path.Join(nginx.GetPath(), "www", "sites") + fileOp := files.NewFileOp() + var wg sync.WaitGroup + wg.Add(len(websites)) + for _, websiteName := range websites { + name := websiteName + go func() { + website, _ := websiteRepo.GetFirst(websiteRepo.WithDomain(name)) + if website.ID == 0 { + wg.Done() + return + } + websiteLogDir := path.Join(baseDir, website.PrimaryDomain, "log") + srcAccessLogPath := path.Join(websiteLogDir, "access.log") + srcErrorLogPath := path.Join(websiteLogDir, "error.log") + dstLogDir := path.Join(global.CONF.System.Backup, "log", "website", website.PrimaryDomain) + if !fileOp.Stat(dstLogDir) { + _ = os.MkdirAll(dstLogDir, 0755) + } + + dstName := fmt.Sprintf("%s_log_%s.gz", website.PrimaryDomain, startTime.Format("20060102150405")) + filePaths = append(filePaths, path.Join(dstLogDir, dstName)) + if err = fileOp.Compress([]string{srcAccessLogPath, srcErrorLogPath}, dstLogDir, dstName, files.Gz); err != nil { + global.LOG.Errorf("There was an error in compressing the website[%s] access.log", website.PrimaryDomain) + } else { + _ = fileOp.WriteFile(srcAccessLogPath, strings.NewReader(""), 0755) + _ = fileOp.WriteFile(srcErrorLogPath, strings.NewReader(""), 0755) + } + global.LOG.Infof("The website[%s] log file was successfully rotated in the directory [%s]", website.PrimaryDomain, dstLogDir) + var record model.BackupRecord + record.Type = "cutWebsiteLog" + record.Name = cronjob.Website + record.Source = "LOCAL" + record.BackupType = "LOCAL" + record.FileDir = dstLogDir + record.FileName = dstName + if err = backupRepo.CreateRecord(&record); err != nil { + global.LOG.Errorf("save backup record failed, err: %v", err) + } + wg.Done() + }() + } + wg.Wait() + u.HandleRmExpired("LOCAL", "", cronjob, nil) + return strings.Join(filePaths, ","), nil +} + func (u *CronjobService) handleWebsite(cronjob model.Cronjob, backup model.BackupAccount, startTime time.Time) ([]string, error) { var paths []string localDir, err := loadLocalDir() diff --git a/frontend/components.d.ts b/frontend/components.d.ts index b919c0cf1..182ad8cc9 100644 --- a/frontend/components.d.ts +++ b/frontend/components.d.ts @@ -5,7 +5,6 @@ declare module 'vue' { export interface GlobalComponents { 404: typeof import('./src/components/error-message/404.vue')['default'] - AppLayout: typeof import('./src/components/app-layout/index.vue')['default'] AppStatus: typeof import('./src/components/app-status/index.vue')['default'] BackButton: typeof import('./src/components/back-button/index.vue')['default'] Backup: typeof import('./src/components/backup/index.vue')['default'] @@ -13,13 +12,11 @@ declare module 'vue' { BreadCrumbsItem: typeof import('./src/components/bread-crumbs/bread-crumbs-item.vue')['default'] CardWithHeader: typeof import('./src/components/card-with-header/index.vue')['default'] Codemirror: typeof import('./src/components/codemirror-dialog/codemirror.vue')['default'] - Collapse: typeof import('./src/components/app-layout/menu/components/Collapse.vue')['default'] ComplexTable: typeof import('./src/components/complex-table/index.vue')['default'] ConfirmDialog: typeof import('./src/components/confirm-dialog/index.vue')['default'] ContainerLog: typeof import('./src/components/container-log/index.vue')['default'] DrawerHeader: typeof import('./src/components/drawer-header/index.vue')['default'] ElAlert: typeof import('element-plus/es')['ElAlert'] - ElAside: typeof import('element-plus/es')['ElAside'] ElAvatar: typeof import('element-plus/es')['ElAvatar'] ElBadge: typeof import('element-plus/es')['ElBadge'] ElButton: typeof import('element-plus/es')['ElButton'] @@ -29,7 +26,6 @@ declare module 'vue' { ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup'] ElCol: typeof import('element-plus/es')['ElCol'] ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider'] - ElContainer: typeof import('element-plus/es')['ElContainer'] ElDatePicker: typeof import('element-plus/es')['ElDatePicker'] ElDescriptions: typeof import('element-plus/es')['ElDescriptions'] ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem'] @@ -40,7 +36,6 @@ declare module 'vue' { ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem'] ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu'] ElEmpty: typeof import('element-plus/es')['ElEmpty'] - ElFooter: typeof import('element-plus/es')['ElFooter'] ElForm: typeof import('element-plus/es')['ElForm'] ElFormItem: typeof import('element-plus/es')['ElFormItem'] ElIcon: typeof import('element-plus/es')['ElIcon'] @@ -48,7 +43,6 @@ declare module 'vue' { ElInput: typeof import('element-plus/es')['ElInput'] ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] ElLink: typeof import('element-plus/es')['ElLink'] - ElMain: typeof import('element-plus/es')['ElMain'] ElMenu: typeof import('element-plus/es')['ElMenu'] ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] ElOption: typeof import('element-plus/es')['ElOption'] @@ -76,20 +70,16 @@ declare module 'vue' { Err_ip: typeof import('./src/components/error-message/err_ip.vue')['default'] FileList: typeof import('./src/components/file-list/index.vue')['default'] FileRole: typeof import('./src/components/file-role/index.vue')['default'] - Footer: typeof import('./src/components/app-layout/footer/index.vue')['default'] FormButton: typeof import('./src/components/layout-content/form-button.vue')['default'] Group: typeof import('./src/components/group/index.vue')['default'] InfiniteScroll: typeof import('element-plus/es')['ElInfiniteScroll'] LayoutContent: typeof import('./src/components/layout-content/index.vue')['default'] Line: typeof import('./src/components/v-charts/components/Line.vue')['default'] Loading: typeof import('element-plus/es')['ElLoadingDirective'] - Logo: typeof import('./src/components/app-layout/menu/components/Logo.vue')['default'] - Menu: typeof import('./src/components/app-layout/menu/index.vue')['default'] MsgInfo: typeof import('./src/components/msg-info/index.vue')['default'] Popover: typeof import('element-plus/es')['ElPopoverDirective'] RouterButton: typeof import('./src/components/router-button/index.vue')['default'] Status: typeof import('./src/components/status/index.vue')['default'] - SubItem: typeof import('./src/components/app-layout/menu/components/sub-item.vue')['default'] SvgIcon: typeof import('./src/components/svg-icon/svg-icon.vue')['default'] SystemUpgrade: typeof import('./src/components/system-upgrade/index.vue')['default'] TableSetting: typeof import('./src/components/table-setting/index.vue')['default'] diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 65f15caca..36ae8840b 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -656,6 +656,8 @@ const message = { cleanDataHelper: 'Delete the backup file generated during this task.', noLogs: 'No task output yet...', errPath: 'Backup path [{0}] error, cannot download!', + cutWebsiteLog: 'cut website log', + cutWebsiteLogHelper: 'The cut log files will be backed up to the backup directory of 1Panel', }, monitor: { monitor: 'Monitor', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index f1dba76ab..d40f54122 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -663,6 +663,8 @@ const message = { cleanDataHelper: '删除该任务执行过程中产生的备份文件', noLogs: '暂无任务输出...', errPath: '备份路径 [{0}] 错误,无法下载!', + cutWebsiteLog: '切割网站日志', + cutWebsiteLogHelper: '切割的日志文件会备份到 1Panel 的 backup 目录下', }, monitor: { monitor: '监控', diff --git a/frontend/src/views/cronjob/operate/index.vue b/frontend/src/views/cronjob/operate/index.vue index 01081bafc..9fe70cc0e 100644 --- a/frontend/src/views/cronjob/operate/index.vue +++ b/frontend/src/views/cronjob/operate/index.vue @@ -18,6 +18,7 @@ + {{ dialogData.rowData!.type }} @@ -82,14 +83,17 @@ + + {{ $t('cronjob.cutWebsiteLogHelper') }} +