diff options
-rw-r--r-- | modules/actions/log.go | 4 | ||||
-rw-r--r-- | options/locale/locale_en-US.ini | 1 | ||||
-rw-r--r-- | routers/web/repo/actions/view.go | 50 | ||||
-rw-r--r-- | routers/web/web.go | 1 | ||||
-rw-r--r-- | templates/repo/actions/view.tmpl | 1 | ||||
-rw-r--r-- | web_src/js/components/RepoActionView.vue | 5 | ||||
-rw-r--r-- | web_src/js/svg.js | 2 |
7 files changed, 62 insertions, 2 deletions
diff --git a/modules/actions/log.go b/modules/actions/log.go index 36bed931fa..cdf18646aa 100644 --- a/modules/actions/log.go +++ b/modules/actions/log.go @@ -73,7 +73,7 @@ func WriteLogs(ctx context.Context, filename string, offset int64, rows []*runne } func ReadLogs(ctx context.Context, inStorage bool, filename string, offset, limit int64) ([]*runnerv1.LogRow, error) { - f, err := openLogs(ctx, inStorage, filename) + f, err := OpenLogs(ctx, inStorage, filename) if err != nil { return nil, err } @@ -141,7 +141,7 @@ func RemoveLogs(ctx context.Context, inStorage bool, filename string) error { return nil } -func openLogs(ctx context.Context, inStorage bool, filename string) (io.ReadSeekCloser, error) { +func OpenLogs(ctx context.Context, inStorage bool, filename string) (io.ReadSeekCloser, error) { if !inStorage { name := DBFSPrefix + filename f, err := dbfs.Open(ctx, name) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 6de0258051..be90e5366c 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -129,6 +129,7 @@ concept_user_organization = Organization show_timestamps = Show timestamps show_log_seconds = Show seconds show_full_screen = Show full screen +download_logs = Download logs confirm_delete_selected = Confirm to delete all selected items? diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go index 7c2e9d63d6..537bc61807 100644 --- a/routers/web/repo/actions/view.go +++ b/routers/web/repo/actions/view.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "net/http" + "strings" "time" actions_model "code.gitea.io/gitea/models/actions" @@ -310,6 +311,55 @@ func rerunJob(ctx *context_module.Context, job *actions_model.ActionRunJob) erro return nil } +func Logs(ctx *context_module.Context) { + runIndex := ctx.ParamsInt64("run") + jobIndex := ctx.ParamsInt64("job") + + job, _ := getRunJobs(ctx, runIndex, jobIndex) + if ctx.Written() { + return + } + if job.TaskID == 0 { + ctx.Error(http.StatusNotFound, "job is not started") + return + } + + err := job.LoadRun(ctx) + if err != nil { + ctx.Error(http.StatusInternalServerError, err.Error()) + return + } + + task, err := actions_model.GetTaskByID(ctx, job.TaskID) + if err != nil { + ctx.Error(http.StatusInternalServerError, err.Error()) + return + } + if task.LogExpired { + ctx.Error(http.StatusNotFound, "logs have been cleaned up") + return + } + + reader, err := actions.OpenLogs(ctx, task.LogInStorage, task.LogFilename) + if err != nil { + ctx.Error(http.StatusInternalServerError, err.Error()) + return + } + defer reader.Close() + + workflowName := job.Run.WorkflowID + if p := strings.Index(workflowName, "."); p > 0 { + workflowName = workflowName[0:p] + } + ctx.ServeContent(reader, &context_module.ServeHeaderOptions{ + Filename: fmt.Sprintf("%v-%v-%v.log", workflowName, job.Name, task.ID), + ContentLength: &task.LogSize, + ContentType: "text/plain", + ContentTypeCharset: "utf-8", + Disposition: "attachment", + }) +} + func Cancel(ctx *context_module.Context) { runIndex := ctx.ParamsInt64("run") diff --git a/routers/web/web.go b/routers/web/web.go index 26ad2d54c3..a5465eb041 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1207,6 +1207,7 @@ func registerRoutes(m *web.Route) { Get(actions.View). Post(web.Bind(actions.ViewRequest{}), actions.ViewPost) m.Post("/rerun", reqRepoActionsWriter, actions.RerunOne) + m.Get("/logs", actions.Logs) }) m.Post("/cancel", reqRepoActionsWriter, actions.Cancel) m.Post("/approve", reqRepoActionsWriter, actions.Approve) diff --git a/templates/repo/actions/view.tmpl b/templates/repo/actions/view.tmpl index 297232fca0..7b07aa155b 100644 --- a/templates/repo/actions/view.tmpl +++ b/templates/repo/actions/view.tmpl @@ -22,6 +22,7 @@ data-locale-show-timestamps="{{.locale.Tr "show_timestamps"}}" data-locale-show-log-seconds="{{.locale.Tr "show_log_seconds"}}" data-locale-show-full-screen="{{.locale.Tr "show_full_screen"}}" + data-locale-download-logs="{{.locale.Tr "download_logs"}}" > </div> </div> diff --git a/web_src/js/components/RepoActionView.vue b/web_src/js/components/RepoActionView.vue index dcbb598052..7c65d5a131 100644 --- a/web_src/js/components/RepoActionView.vue +++ b/web_src/js/components/RepoActionView.vue @@ -74,6 +74,10 @@ <SvgIcon name="octicon-gear" :size="18"/> </button> <div class="menu transition action-job-menu" :class="{visible: menuVisible}" v-if="menuVisible" v-cloak> + <a class="item" :href="run.link+'/jobs/'+jobIndex+'/logs'" target="_blank"> + <i class="icon"><SvgIcon name="octicon-download"/></i> + {{ locale.downloadLogs }} + </a> <a class="item" @click="toggleTimeDisplay('seconds')"> <i class="icon"><SvgIcon v-show="timeVisible['log-time-seconds']" name="octicon-check"/></i> {{ locale.showLogSeconds }} @@ -453,6 +457,7 @@ export function initRepositoryActionView() { showTimeStamps: el.getAttribute('data-locale-show-timestamps'), showLogSeconds: el.getAttribute('data-locale-show-log-seconds'), showFullScreen: el.getAttribute('data-locale-show-full-screen'), + downloadLogs: el.getAttribute('data-locale-download-logs'), status: { unknown: el.getAttribute('data-locale-status-unknown'), waiting: el.getAttribute('data-locale-status-waiting'), diff --git a/web_src/js/svg.js b/web_src/js/svg.js index 1ea0eb8347..0a6292850a 100644 --- a/web_src/js/svg.js +++ b/web_src/js/svg.js @@ -22,6 +22,7 @@ import octiconDiffModified from '../../public/img/svg/octicon-diff-modified.svg' import octiconDiffRemoved from '../../public/img/svg/octicon-diff-removed.svg'; import octiconDiffRenamed from '../../public/img/svg/octicon-diff-renamed.svg'; import octiconDotFill from '../../public/img/svg/octicon-dot-fill.svg'; +import octiconDownload from '../../public/img/svg/octicon-download.svg'; import octiconEye from '../../public/img/svg/octicon-eye.svg'; import octiconFile from '../../public/img/svg/octicon-file.svg'; import octiconFileDirectoryFill from '../../public/img/svg/octicon-file-directory-fill.svg'; @@ -91,6 +92,7 @@ const svgs = { 'octicon-diff-removed': octiconDiffRemoved, 'octicon-diff-renamed': octiconDiffRenamed, 'octicon-dot-fill': octiconDotFill, + 'octicon-download': octiconDownload, 'octicon-eye': octiconEye, 'octicon-file': octiconFile, 'octicon-file-directory-fill': octiconFileDirectoryFill, |