diff --git a/backend/app/api/v1/file.go b/backend/app/api/v1/file.go index 339a48854..8131c6c2c 100644 --- a/backend/app/api/v1/file.go +++ b/backend/app/api/v1/file.go @@ -685,25 +685,21 @@ func (b *BaseApi) Keys(c *gin.Context) { // @Tags File // @Summary Read file by Line -// @Description 按行读取文件 +// @Description 按行读取日志文件 // @Param request body request.FileReadByLineReq true "request" // @Success 200 // @Security ApiKeyAuth -// @Router /files/read [post] +// @Router /files/log/read [post] func (b *BaseApi) ReadFileByLine(c *gin.Context) { var req request.FileReadByLineReq if err := helper.CheckBindAndValidate(&req, c); err != nil { return } - lines, end, err := files.ReadFileByLine(req.Path, req.Page, req.PageSize) + res, err := fileService.ReadLogByLine(req) if err != nil { helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return } - res := response.FileLineContent{ - End: end, - } - res.Path = req.Path - res.Content = strings.Join(lines, "\n") helper.SuccessWithData(c, res) } diff --git a/backend/app/dto/request/file.go b/backend/app/dto/request/file.go index 8c6ae201a..d2c4e9b6b 100644 --- a/backend/app/dto/request/file.go +++ b/backend/app/dto/request/file.go @@ -118,9 +118,11 @@ type FileRoleUpdate struct { } type FileReadByLineReq struct { - Path string `json:"path" validate:"required"` Page int `json:"page" validate:"required"` PageSize int `json:"pageSize" validate:"required"` + Type string `json:"type" validate:"required"` + ID uint `json:"ID"` + Name string `json:"name"` } type FileExistReq struct { diff --git a/backend/app/model/website_ssl.go b/backend/app/model/website_ssl.go index 7131ca0ef..b9c8e2524 100644 --- a/backend/app/model/website_ssl.go +++ b/backend/app/model/website_ssl.go @@ -1,6 +1,11 @@ package model -import "time" +import ( + "fmt" + "github.com/1Panel-dev/1Panel/backend/constant" + "path" + "time" +) type WebsiteSSL struct { BaseModel @@ -28,3 +33,7 @@ type WebsiteSSL struct { func (w WebsiteSSL) TableName() string { return "website_ssls" } + +func (w WebsiteSSL) GetLogPath() string { + return path.Join(constant.SSLLogDir, fmt.Sprintf("%s-ssl-%d.log", w.PrimaryDomain, w.ID)) +} diff --git a/backend/app/service/container.go b/backend/app/service/container.go index c9d188404..2ccf5707b 100644 --- a/backend/app/service/container.go +++ b/backend/app/service/container.go @@ -7,7 +7,6 @@ import ( "io" "os" "os/exec" - "path" "path/filepath" "sort" "strconv" @@ -699,18 +698,15 @@ func (u *ContainerService) ContainerStats(id string) (*dto.ContainerStats, error func (u *ContainerService) LoadContainerLogs(req dto.OperationWithNameAndType) string { filePath := "" - switch req.Type { - case "image-pull", "image-push", "image-build", "compose-create": - filePath = path.Join(global.CONF.System.TmpDir, fmt.Sprintf("docker_logs/%s", req.Name)) - case "compose-detail": - client, err := docker.NewDockerClient() + if req.Type == "compose-detail" { + cli, err := docker.NewDockerClient() if err != nil { return "" } options := types.ContainerListOptions{All: true} options.Filters = filters.NewArgs() options.Filters.Add("label", fmt.Sprintf("%s=%s", composeProjectLabel, req.Name)) - containers, err := client.ContainerList(context.Background(), options) + containers, err := cli.ContainerList(context.Background(), options) if err != nil { return "" } @@ -725,9 +721,6 @@ func (u *ContainerService) LoadContainerLogs(req dto.OperationWithNameAndType) s break } } - if req.Type == "compose-create" { - filePath = path.Join(path.Dir(filePath), "compose.log") - } } if _, err := os.Stat(filePath); err != nil { return "" diff --git a/backend/app/service/file.go b/backend/app/service/file.go index 37b69093a..b9e77d058 100644 --- a/backend/app/service/file.go +++ b/backend/app/service/file.go @@ -4,6 +4,7 @@ import ( "fmt" "io/fs" "os" + "path" "path/filepath" "strings" "time" @@ -41,6 +42,7 @@ type IFileService interface { ChangeOwner(req request.FileRoleUpdate) error ChangeMode(op request.FileCreate) error BatchChangeModeAndOwner(op request.FileRoleReq) error + ReadLogByLine(req request.FileReadByLineReq) (*response.FileLineContent, error) } func NewIFileService() IFileService { @@ -297,3 +299,62 @@ func (f *FileService) DirSize(req request.DirSizeReq) (response.DirSizeRes, erro } return response.DirSizeRes{Size: size}, nil } + +func (f *FileService) ReadLogByLine(req request.FileReadByLineReq) (*response.FileLineContent, error) { + logFilePath := "" + switch req.Type { + case constant.TypeWebsite: + website, err := websiteRepo.GetFirst(commonRepo.WithByID(req.ID)) + if err != nil { + return nil, err + } + nginx, err := getNginxFull(&website) + if err != nil { + return nil, err + } + sitePath := path.Join(nginx.SiteDir, "sites", website.Alias) + logFilePath = path.Join(sitePath, "log", req.Name) + case constant.TypePhp: + php, err := runtimeRepo.GetFirst(commonRepo.WithByID(req.ID)) + if err != nil { + return nil, err + } + logFilePath = php.GetLogPath() + case constant.TypeSSL: + ssl, err := websiteSSLRepo.GetFirst(commonRepo.WithByID(req.ID)) + if err != nil { + return nil, err + } + logFilePath = ssl.GetLogPath() + case constant.TypeSystem: + fileName := "" + if req.Name == time.Now().Format("2006-01-02") { + fileName = "1Panel.log" + } else { + fileName = "1Panel-" + req.Name + ".log" + } + logFilePath = path.Join(global.CONF.System.DataDir, "log", fileName) + if _, err := os.Stat(logFilePath); err != nil { + fileGzPath := path.Join(global.CONF.System.DataDir, "log", fileName+".gz") + if _, err := os.Stat(fileGzPath); err != nil { + return nil, buserr.New("ErrHttpReqNotFound") + } + if err := handleGunzip(fileGzPath); err != nil { + return nil, fmt.Errorf("handle ungzip file %s failed, err: %v", fileGzPath, err) + } + } + case "image-pull", "image-push", "image-build", "compose-create": + logFilePath = path.Join(global.CONF.System.TmpDir, fmt.Sprintf("docker_logs/%s", req.Name)) + } + + lines, isEndOfFile, err := files.ReadFileByLine(logFilePath, req.Page, req.PageSize) + if err != nil { + return nil, err + } + res := &response.FileLineContent{ + Content: strings.Join(lines, "\n"), + End: isEndOfFile, + Path: logFilePath, + } + return res, nil +} diff --git a/backend/app/service/logs.go b/backend/app/service/logs.go index ab382a3e0..c13d0a036 100644 --- a/backend/app/service/logs.go +++ b/backend/app/service/logs.go @@ -51,7 +51,7 @@ func (u *LogService) ListSystemLogFile() ([]string, error) { if err != nil { return err } - if !info.IsDir() { + if !info.IsDir() && strings.HasPrefix(info.Name(), "1Panel-") { if info.Name() == "1Panel.log" { files = append(files, time.Now().Format("2006-01-02")) return nil diff --git a/backend/constant/common.go b/backend/constant/common.go index 9b3e312fb..90574267c 100644 --- a/backend/constant/common.go +++ b/backend/constant/common.go @@ -6,4 +6,9 @@ const ( DB DBContext = "db" SystemRestart = "systemRestart" + + TypeWebsite = "website" + TypePhp = "php" + TypeSSL = "ssl" + TypeSystem = "system" ) diff --git a/backend/utils/files/utils.go b/backend/utils/files/utils.go index 899195eec..def018e1e 100644 --- a/backend/utils/files/utils.go +++ b/backend/utils/files/utils.go @@ -78,6 +78,9 @@ func IsHidden(path string) bool { } func ReadFileByLine(filename string, page, pageSize int) ([]string, bool, error) { + if !NewFileOp().Stat(filename) { + return nil, true, nil + } file, err := os.Open(filename) if err != nil { return nil, false, err diff --git a/backend/utils/ssl/client.go b/backend/utils/ssl/client.go index c602b6f82..d09b81dd2 100644 --- a/backend/utils/ssl/client.go +++ b/backend/utils/ssl/client.go @@ -95,47 +95,47 @@ func (c *AcmeClient) UseDns(dnsType DnsType, params string) error { case DnsPod: dnsPodConfig := dnspod.NewDefaultConfig() dnsPodConfig.LoginToken = param.ID + "," + param.Token - dnsPodConfig.PropagationTimeout = 30 * time.Minute - dnsPodConfig.PollingInterval = 30 * time.Second + dnsPodConfig.PropagationTimeout = 60 * time.Minute + dnsPodConfig.PollingInterval = 5 * time.Second dnsPodConfig.TTL = 3600 p, err = dnspod.NewDNSProviderConfig(dnsPodConfig) case AliYun: alidnsConfig := alidns.NewDefaultConfig() alidnsConfig.SecretKey = param.SecretKey alidnsConfig.APIKey = param.AccessKey - alidnsConfig.PropagationTimeout = 30 * time.Minute - alidnsConfig.PollingInterval = 30 * time.Second + alidnsConfig.PropagationTimeout = 60 * time.Minute + alidnsConfig.PollingInterval = 5 * time.Second alidnsConfig.TTL = 3600 p, err = alidns.NewDNSProviderConfig(alidnsConfig) case CloudFlare: cloudflareConfig := cloudflare.NewDefaultConfig() cloudflareConfig.AuthEmail = param.Email cloudflareConfig.AuthKey = param.APIkey - cloudflareConfig.PropagationTimeout = 30 * time.Minute - cloudflareConfig.PollingInterval = 30 * time.Second + cloudflareConfig.PropagationTimeout = 60 * time.Minute + cloudflareConfig.PollingInterval = 5 * time.Second cloudflareConfig.TTL = 3600 p, err = cloudflare.NewDNSProviderConfig(cloudflareConfig) case NameCheap: namecheapConfig := namecheap.NewDefaultConfig() namecheapConfig.APIKey = param.APIkey namecheapConfig.APIUser = param.APIUser - namecheapConfig.PropagationTimeout = 30 * time.Minute - namecheapConfig.PollingInterval = 30 * time.Second + namecheapConfig.PropagationTimeout = 60 * time.Minute + namecheapConfig.PollingInterval = 5 * time.Second namecheapConfig.TTL = 3600 p, err = namecheap.NewDNSProviderConfig(namecheapConfig) case NameSilo: nameSiloConfig := namesilo.NewDefaultConfig() nameSiloConfig.APIKey = param.APIkey - nameSiloConfig.PropagationTimeout = 30 * time.Minute - nameSiloConfig.PollingInterval = 30 * time.Second + nameSiloConfig.PropagationTimeout = 60 * time.Minute + nameSiloConfig.PollingInterval = 5 * time.Second nameSiloConfig.TTL = 3600 p, err = namesilo.NewDNSProviderConfig(nameSiloConfig) case Godaddy: godaddyConfig := godaddy.NewDefaultConfig() godaddyConfig.APIKey = param.APIkey godaddyConfig.APISecret = param.APISecret - godaddyConfig.PropagationTimeout = 30 * time.Minute - godaddyConfig.PollingInterval = 30 * time.Second + godaddyConfig.PropagationTimeout = 60 * time.Minute + godaddyConfig.PollingInterval = 5 * time.Second godaddyConfig.TTL = 3600 p, err = godaddy.NewDNSProviderConfig(godaddyConfig) case NameCom: diff --git a/cmd/server/docs/docs.go b/cmd/server/docs/docs.go index bb209bdb4..2e53c8099 100644 --- a/cmd/server/docs/docs.go +++ b/cmd/server/docs/docs.go @@ -5655,6 +5655,36 @@ const docTemplate = `{ } } }, + "/files/log/read": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "按行读取日志文件", + "tags": [ + "File" + ], + "summary": "Read file by Line", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.FileReadByLineReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/files/mode": { "post": { "security": [ @@ -5785,36 +5815,6 @@ const docTemplate = `{ } } }, - "/files/read": { - "post": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "按行读取文件", - "tags": [ - "File" - ], - "summary": "Read file by Line", - "parameters": [ - { - "description": "request", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/request.FileReadByLineReq" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, "/files/recycle/clear": { "post": { "security": [ @@ -17777,16 +17777,22 @@ const docTemplate = `{ "required": [ "page", "pageSize", - "path" + "type" ], "properties": { + "ID": { + "type": "integer" + }, + "name": { + "type": "string" + }, "page": { "type": "integer" }, "pageSize": { "type": "integer" }, - "path": { + "type": { "type": "string" } } diff --git a/cmd/server/docs/swagger.json b/cmd/server/docs/swagger.json index b338e63b3..73e057a8a 100644 --- a/cmd/server/docs/swagger.json +++ b/cmd/server/docs/swagger.json @@ -5648,6 +5648,36 @@ } } }, + "/files/log/read": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "按行读取日志文件", + "tags": [ + "File" + ], + "summary": "Read file by Line", + "parameters": [ + { + "description": "request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.FileReadByLineReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/files/mode": { "post": { "security": [ @@ -5778,36 +5808,6 @@ } } }, - "/files/read": { - "post": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "按行读取文件", - "tags": [ - "File" - ], - "summary": "Read file by Line", - "parameters": [ - { - "description": "request", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/request.FileReadByLineReq" - } - } - ], - "responses": { - "200": { - "description": "OK" - } - } - } - }, "/files/recycle/clear": { "post": { "security": [ @@ -17770,16 +17770,22 @@ "required": [ "page", "pageSize", - "path" + "type" ], "properties": { + "ID": { + "type": "integer" + }, + "name": { + "type": "string" + }, "page": { "type": "integer" }, "pageSize": { "type": "integer" }, - "path": { + "type": { "type": "string" } } diff --git a/cmd/server/docs/swagger.yaml b/cmd/server/docs/swagger.yaml index d91f58d85..0b2304c57 100644 --- a/cmd/server/docs/swagger.yaml +++ b/cmd/server/docs/swagger.yaml @@ -3137,16 +3137,20 @@ definitions: type: object request.FileReadByLineReq: properties: + ID: + type: integer + name: + type: string page: type: integer pageSize: type: integer - path: + type: type: string required: - page - pageSize - - path + - type type: object request.FileRename: properties: @@ -8145,6 +8149,24 @@ paths: summary: List favorites tags: - File + /files/log/read: + post: + description: 按行读取日志文件 + parameters: + - description: request + in: body + name: request + required: true + schema: + $ref: '#/definitions/request.FileReadByLineReq' + responses: + "200": + description: OK + security: + - ApiKeyAuth: [] + summary: Read file by Line + tags: + - File /files/mode: post: consumes: @@ -8230,24 +8252,6 @@ paths: formatEN: Change owner [paths] => [user]/[group] formatZH: 修改用户/组 [paths] => [user]/[group] paramKeys: [] - /files/read: - post: - description: 按行读取文件 - parameters: - - description: request - in: body - name: request - required: true - schema: - $ref: '#/definitions/request.FileReadByLineReq' - responses: - "200": - description: OK - security: - - ApiKeyAuth: [] - summary: Read file by Line - tags: - - File /files/recycle/clear: post: consumes: diff --git a/frontend/src/api/interface/file.ts b/frontend/src/api/interface/file.ts index 88ce43672..f716f9228 100644 --- a/frontend/src/api/interface/file.ts +++ b/frontend/src/api/interface/file.ts @@ -165,7 +165,9 @@ export namespace File { } export interface FileReadByLine { - path: string; + id?: number; + type: string; + name?: string; page: number; pageSize: number; } diff --git a/frontend/src/components/log-dialog/index.vue b/frontend/src/components/log-dialog/index.vue new file mode 100644 index 000000000..511e5cb67 --- /dev/null +++ b/frontend/src/components/log-dialog/index.vue @@ -0,0 +1,79 @@ + + + + diff --git a/frontend/src/components/log-file/index.vue b/frontend/src/components/log-file/index.vue new file mode 100644 index 000000000..2c1015d18 --- /dev/null +++ b/frontend/src/components/log-file/index.vue @@ -0,0 +1,250 @@ + + diff --git a/frontend/src/components/log/index.vue b/frontend/src/components/log/index.vue deleted file mode 100644 index 338532304..000000000 --- a/frontend/src/components/log/index.vue +++ /dev/null @@ -1,160 +0,0 @@ - - diff --git a/frontend/src/views/container/compose/create/index.vue b/frontend/src/views/container/compose/create/index.vue index 008db8db6..0e899f9d9 100644 --- a/frontend/src/views/container/compose/create/index.vue +++ b/frontend/src/views/container/compose/create/index.vue @@ -78,21 +78,12 @@ v-model="form.file" /> - @@ -113,40 +104,37 @@ diff --git a/frontend/src/views/website/runtime/php/index.vue b/frontend/src/views/website/runtime/php/index.vue index eadbc20f5..96ea781a6 100644 --- a/frontend/src/views/website/runtime/php/index.vue +++ b/frontend/src/views/website/runtime/php/index.vue @@ -87,7 +87,7 @@ import CreateRuntime from '@/views/website/runtime/php/create/index.vue'; import Status from '@/components/status/index.vue'; import i18n from '@/lang'; import RouterMenu from '../index.vue'; -import Log from '@/components/log/index.vue'; +import Log from '@/components/log-dialog/index.vue'; const paginationConfig = reactive({ cacheSizeKey: 'runtime-page-size', @@ -149,7 +149,7 @@ const openDetail = (row: Runtime.Runtime) => { }; const openLog = (row: Runtime.RuntimeDTO) => { - logRef.value.acceptParams({ path: row.path + '/' + 'build.log' }); + logRef.value.acceptParams({ id: row.id, type: 'php' }); }; const openDelete = async (row: Runtime.Runtime) => { diff --git a/frontend/src/views/website/ssl/index.vue b/frontend/src/views/website/ssl/index.vue index 0052499dd..7101b8f41 100644 --- a/frontend/src/views/website/ssl/index.vue +++ b/frontend/src/views/website/ssl/index.vue @@ -81,7 +81,7 @@ - + @@ -143,7 +143,7 @@ import { MsgSuccess } from '@/utils/message'; import { GlobalStore } from '@/store'; import SSLUpload from './upload/index.vue'; import Apply from './apply/index.vue'; -import Log from '@/components/log/index.vue'; +import Log from '@/components/log-dialog/index.vue'; const globalStore = GlobalStore(); const paginationConfig = reactive({ @@ -248,7 +248,7 @@ const openDetail = (id: number) => { detailRef.value.acceptParams(id); }; const openLog = (row: Website.SSLDTO) => { - logRef.value.acceptParams({ path: row.logPath }); + logRef.value.acceptParams({ id: row.id, type: 'ssl' }); }; const applySSL = (row: Website.SSLDTO) => { diff --git a/frontend/src/views/website/website/config/log/log-fiile/index.vue b/frontend/src/views/website/website/config/log/log-fiile/index.vue index 3d1f74cf3..70340ef95 100644 --- a/frontend/src/views/website/website/config/log/log-fiile/index.vue +++ b/frontend/src/views/website/website/config/log/log-fiile/index.vue @@ -4,48 +4,24 @@ -
- - {{ $t('commons.button.watch') }} - - - {{ $t('file.download') }} - - +
+ + + - + diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 2609104ba..bc39f4aab 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -84,7 +84,7 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => { outDir: '../cmd/server/web', minify: 'esbuild', rollupOptions: { - external: ['codemirror'], + external: ['codemirror'], output: { chunkFileNames: 'assets/js/[name]-[hash].js', entryFileNames: 'assets/js/[name]-[hash].js',