diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 73041ceee5..aa01e8699a 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -3773,6 +3773,9 @@ variables.creation.success = The variable "%s" has been added.
variables.update.failed = Failed to edit variable.
variables.update.success = The variable has been edited.
+logs.always_auto_scroll = Always auto scroll logs
+logs.always_expand_running = Always expand running logs
+
[projects]
deleted.display_name = Deleted Project
type-1.display_name = Individual Project
diff --git a/routers/web/devtest/mock_actions.go b/routers/web/devtest/mock_actions.go
index 46e302d634..f29b8e4046 100644
--- a/routers/web/devtest/mock_actions.go
+++ b/routers/web/devtest/mock_actions.go
@@ -31,7 +31,11 @@ func generateMockStepsLog(logCur actions.LogCursor) (stepsLog []*actions.ViewSte
"##[endgroup]",
}
cur := logCur.Cursor // usually the cursor is the "file offset", but here we abuse it as "line number" to make the mock easier, intentionally
- for i := 0; i < util.Iif(logCur.Step == 0, 3, 1); i++ {
+ mockCount := util.Iif(logCur.Step == 0, 3, 1)
+ if logCur.Step == 1 && logCur.Cursor == 0 {
+ mockCount = 30 // for the first batch, return as many as possible to test the auto-expand and auto-scroll
+ }
+ for i := 0; i < mockCount; i++ {
logStr := mockedLogs[int(cur)%len(mockedLogs)]
cur++
logStr = strings.ReplaceAll(logStr, "{step}", fmt.Sprintf("%d", logCur.Step))
@@ -56,6 +60,21 @@ func MockActionsRunsJobs(ctx *context.Context) {
resp.State.Run.Status = actions_model.StatusRunning.String()
resp.State.Run.CanCancel = true
resp.State.Run.CanDeleteArtifact = true
+ resp.State.Run.WorkflowID = "workflow-id"
+ resp.State.Run.WorkflowLink = "./workflow-link"
+ resp.State.Run.Commit = actions.ViewCommit{
+ ShortSha: "ccccdddd",
+ Link: "./commit-link",
+ Pusher: actions.ViewUser{
+ DisplayName: "pusher user",
+ Link: "./pusher-link",
+ },
+ Branch: actions.ViewBranch{
+ Name: "commit-branch",
+ Link: "./branch-link",
+ IsDeleted: false,
+ },
+ }
resp.Artifacts = append(resp.Artifacts, &actions.ArtifactsViewItem{
Name: "artifact-a",
Size: 100 * 1024,
diff --git a/templates/devtest/repo-action-view.tmpl b/templates/devtest/repo-action-view.tmpl
index 1fa71c0e5f..9c6bdf13da 100644
--- a/templates/devtest/repo-action-view.tmpl
+++ b/templates/devtest/repo-action-view.tmpl
@@ -1,30 +1,9 @@
{{template "base/head" .}}
-
+ {{template "repo/actions/view_component" (dict
+ "RunIndex" 1
+ "JobIndex" 2
+ "ActionsURL" (print AppSubUrl "/devtest/actions-mock")
+ )}}
{{template "base/footer" .}}
diff --git a/templates/repo/actions/view.tmpl b/templates/repo/actions/view.tmpl
index f7b03608ee..bde579f882 100644
--- a/templates/repo/actions/view.tmpl
+++ b/templates/repo/actions/view.tmpl
@@ -2,33 +2,11 @@
{{template "repo/header" .}}
-
-
+ {{template "repo/actions/view_component" (dict
+ "RunIndex" .RunIndex
+ "JobIndex" .JobIndex
+ "ActionsURL" .ActionsURL
+ )}}
{{template "base/footer" .}}
diff --git a/templates/repo/actions/view_component.tmpl b/templates/repo/actions/view_component.tmpl
new file mode 100644
index 0000000000..8d1de41f70
--- /dev/null
+++ b/templates/repo/actions/view_component.tmpl
@@ -0,0 +1,30 @@
+
+
diff --git a/web_src/js/components/RepoActionView.vue b/web_src/js/components/RepoActionView.vue
index b083fb0b77..914c9e76de 100644
--- a/web_src/js/components/RepoActionView.vue
+++ b/web_src/js/components/RepoActionView.vue
@@ -43,6 +43,20 @@ function isLogElementInViewport(el: HTMLElement): boolean {
return rect.top >= 0 && rect.bottom <= window.innerHeight; // only check height but not width
}
+type LocaleStorageOptions = {
+ autoScroll: boolean;
+ expandRunning: boolean;
+};
+
+function getLocaleStorageOptions(): LocaleStorageOptions {
+ try {
+ const optsJson = localStorage.getItem('actions-view-options');
+ if (optsJson) return JSON.parse(optsJson);
+ } catch {}
+ // if no options in localStorage, or failed to parse, return default options
+ return {autoScroll: true, expandRunning: false};
+}
+
const sfc = {
name: 'RepoActionView',
components: {
@@ -56,7 +70,17 @@ const sfc = {
locale: Object,
},
+ watch: {
+ optionAlwaysAutoScroll() {
+ this.saveLocaleStorageOptions();
+ },
+ optionAlwaysExpandRunning() {
+ this.saveLocaleStorageOptions();
+ },
+ },
+
data() {
+ const {autoScroll, expandRunning} = getLocaleStorageOptions();
return {
// internal state
loadingAbortController: null,
@@ -70,6 +94,8 @@ const sfc = {
'log-time-stamp': false,
'log-time-seconds': false,
},
+ optionAlwaysAutoScroll: autoScroll ?? false,
+ optionAlwaysExpandRunning: expandRunning ?? false,
// provided by backend
run: {
@@ -147,6 +173,11 @@ const sfc = {
},
methods: {
+ saveLocaleStorageOptions() {
+ const opts: LocaleStorageOptions = {autoScroll: this.optionAlwaysAutoScroll, expandRunning: this.optionAlwaysExpandRunning};
+ localStorage.setItem('actions-view-options', JSON.stringify(opts));
+ },
+
// get the job step logs container ('.job-step-logs')
getJobStepLogsContainer(stepIndex: number): HTMLElement {
return this.$refs.logs[stepIndex];
@@ -228,8 +259,10 @@ const sfc = {
},
shouldAutoScroll(stepIndex: number): boolean {
+ if (!this.optionAlwaysAutoScroll) return false;
const el = this.getJobStepLogsContainer(stepIndex);
- if (!el.lastChild) return false;
+ // if the logs container is empty, then auto-scroll if the step is expanded
+ if (!el.lastChild) return this.currentJobStepsStates[stepIndex].expanded;
return isLogElementInViewport(el.lastChild);
},
@@ -280,6 +313,7 @@ const sfc = {
const abortController = new AbortController();
this.loadingAbortController = abortController;
try {
+ const isFirstLoad = !this.run.status;
const job = await this.fetchJobData(abortController);
if (this.loadingAbortController !== abortController) return;
@@ -289,9 +323,10 @@ const sfc = {
// sync the currentJobStepsStates to store the job step states
for (let i = 0; i < this.currentJob.steps.length; i++) {
+ const expanded = isFirstLoad && this.optionAlwaysExpandRunning && this.currentJob.steps[i].status === 'running';
if (!this.currentJobStepsStates[i]) {
// initial states for job steps
- this.currentJobStepsStates[i] = {cursor: null, expanded: false};
+ this.currentJobStepsStates[i] = {cursor: null, expanded};
}
}
@@ -426,6 +461,8 @@ export function initRepositoryActionView() {
skipped: el.getAttribute('data-locale-status-skipped'),
blocked: el.getAttribute('data-locale-status-blocked'),
},
+ logsAlwaysAutoScroll: el.getAttribute('data-locale-logs-always-auto-scroll'),
+ logsAlwaysExpandRunning: el.getAttribute('data-locale-logs-always-expand-running'),
},
});
view.mount(el);
@@ -528,6 +565,17 @@ export function initRepositoryActionView() {
{{ locale.showFullScreen }}
+
+
+
+
+ {{ locale.logsAlwaysAutoScroll }}
+
+
+
+ {{ locale.logsAlwaysExpandRunning }}
+
+