From 379d4c30e709636b5404133443e475697c4b7244 Mon Sep 17 00:00:00 2001 From: dawn Date: Tue, 07 Jul 2026 11:34:05 +0000 Subject: [PATCH] appview/pages,appview/pipelines: refresh workflow UI Signed-off-by: dawn --- docker-compose.yml | 3 +++ types/pipeline.go | 2 ++ appview/pages/pages.go | 10 +++++----- appview/pipelines/pipelines.go | 67 ++++++++++++++++++++++++++++++++++++++++++++++--------------------- appview/pages/templates/repo/pipelines/workflow.html | 210 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------------------------------------------------------------------- appview/pages/templates/repo/pipelines/fragments/logBlock.html | 21 ++++++++++++++------- appview/pages/templates/repo/pipelines/fragments/logBlockEnd.html | 2 +- appview/pages/templates/repo/pipelines/fragments/logLine.html | 2 +- appview/pages/templates/repo/pipelines/fragments/workflowSymbol.html | 6 +++--- 9 file(s) changed, 196 insertion(s)(+), 127 deletion(s)(-) diff --git a/docker-compose.yml b/docker-compose.yml --- a/docker-compose.yml +++ b/docker-compose.yml @@ -340,8 +340,11 @@ TANGLED_REDIS_ADDR: redis:6379 TANGLED_KNOTMIRROR_URL: https://mirror.tngl.boltless.dev TANGLED_CODESEARCH_ZOEKT_URL: https://zoekt.tngl.boltless.dev + TANGLED_SSH_ENABLED: "true" + TANGLED_SSH_LISTEN_ADDR: "0.0.0.0:3333" ports: - "3000:3000" + - "3333:3333" volumes: - .:/src:cached - go-cache:/go/cache diff --git a/types/pipeline.go b/types/pipeline.go --- a/types/pipeline.go +++ b/types/pipeline.go @@ -103,6 +103,8 @@ return t } + + type Trigger struct { *tangled.CiPipeline_Trigger } diff --git a/appview/pages/pages.go b/appview/pages/pages.go --- a/appview/pages/pages.go +++ b/appview/pages/pages.go @@ -1713,11 +1713,11 @@ type WorkflowParams struct { BaseParams - RepoInfo repoinfo.RepoInfo - Pipeline types.Pipeline - Workflow string - LogUrl string - Active string + RepoInfo repoinfo.RepoInfo + Pipeline types.Pipeline + Workflow string + SSHLogCommand string + Active string } func (p *Pages) Workflow(w io.Writer, params WorkflowParams) error { diff --git a/appview/pipelines/pipelines.go b/appview/pipelines/pipelines.go --- a/appview/pipelines/pipelines.go +++ b/appview/pipelines/pipelines.go @@ -5,6 +5,7 @@ "context" "fmt" "log/slog" + "net" "net/http" "sync" "time" @@ -46,6 +47,7 @@ r.Get("/{pipeline}/workflow/{workflow}/logs", p.Logs) r.Group(func(r chi.Router) { r.Use(mw.RepoPermissionMiddleware("repo:push")) + r.Post("/{pipeline}/cancel", p.CancelPipeline) r.Post("/{pipeline}/workflow/{workflow}/cancel", p.CancelWorkflow) r.Post("/{pipeline}/retry", p.RetryPipeline) r.Post("/{pipeline}/workflow/{workflow}/retry", p.RetryWorkflow) @@ -223,11 +225,23 @@ } p.pages.Workflow(w, pages.WorkflowParams{ - BaseParams: pages.BaseParamsFromContext(r.Context()), - RepoInfo: p.repoResolver.GetRepoInfo(r, user), - Pipeline: types.Pipeline{CiPipeline: out}, - Workflow: workflowName, + BaseParams: pages.BaseParamsFromContext(r.Context()), + RepoInfo: p.repoResolver.GetRepoInfo(r, user), + Pipeline: types.Pipeline{CiPipeline: out}, + Workflow: workflowName, + SSHLogCommand: p.sshLogCommand(f.RepoDid, out.Commit), }) +} + +func (p *Pipelines) sshLogCommand(repoDid, sha string) string { + if p.config == nil || !p.config.SSH.Enabled || sha == "" { + return "" + } + _, port, err := net.SplitHostPort(p.config.SSH.ListenAddr) + if err != nil || port == "" { + return "" + } + return fmt.Sprintf("ssh -t -p %s %s %s %s", port, p.config.Core.AppviewHost, repoDid, sha) } var upgrader = websocket.Upgrader{ @@ -479,21 +493,40 @@ } } +func (p *Pipelines) CancelPipeline(w http.ResponseWriter, r *http.Request) { + p.cancel(w, r, nil) +} + func (p *Pipelines) CancelWorkflow(w http.ResponseWriter, r *http.Request) { - l := p.logger.With("handler", "CancelWorkflow") + workflowName := chi.URLParam(r, "workflow") + if workflowName == "" { + p.logger.With("handler", "CancelWorkflow").Debug("empty workflow name") + p.pages.Error404(w) + return + } + p.cancel(w, r, []string{workflowName}) +} + +func (p *Pipelines) cancel(w http.ResponseWriter, r *http.Request, workflows []string) { + l := p.logger.With("handler", "cancel", "workflows", workflows) errorId := "workflow-error" + target := "pipeline" + if len(workflows) == 1 { + target = "workflow" + } + fail := "Failed to cancel " + target f, err := p.repoResolver.Resolve(r) if err != nil { l.Error("failed to get repo and knot", "err", err) - p.pages.Notice(w, errorId, "Failed to cancel workflow") + p.pages.Notice(w, errorId, fail) return } l = l.With("repo", f.RepoDid) if f.Spindle == "" { l.Debug("spindle is empty") - p.pages.Notice(w, errorId, "Failed to cancel workflow") + p.pages.Notice(w, errorId, fail) return } @@ -503,20 +536,12 @@ p.pages.Error404(w) return } - - workflowName := chi.URLParam(r, "workflow") - if workflowName == "" { - l.Debug("empty workflow name") - p.pages.Error404(w) - return - } - - l = l.With("pipeline", pipelineId, "workflow", workflowName) + l = l.With("pipeline", pipelineId) spindleClient, err := p.oauth.SpindleServiceClient(r, f.Spindle, tangled.CiCancelPipelineNSID) if err != nil { l.Error("failed to prepare spindle client", "err", err) - p.pages.Notice(w, errorId, "Failed to cancel workflow") + p.pages.Notice(w, errorId, fail) return } @@ -526,14 +551,14 @@ &tangled.CiCancelPipeline_Input{ Repo: f.RepoDid, Pipeline: pipelineId.String(), - Workflows: []string{workflowName}, + Workflows: workflows, }, ); err != nil { - l.Error("failed to cancel workflow", "err", err) - p.pages.Notice(w, errorId, "Failed to cancel workflow") + l.Error("failed to cancel pipeline", "err", err) + p.pages.Notice(w, errorId, fail) return } - l.Debug("canceled workflow") + l.Debug("canceled pipeline") } // RetryPipeline retries all workflows in a pipeline diff --git a/appview/pages/templates/repo/pipelines/workflow.html b/appview/pages/templates/repo/pipelines/workflow.html --- a/appview/pages/templates/repo/pipelines/workflow.html +++ b/appview/pages/templates/repo/pipelines/workflow.html @@ -6,112 +6,144 @@ {{ template "repo/fragments/og" (dict "RepoInfo" .RepoInfo "Title" $title "Url" $url) }} {{ end }} -{{ define "repoContent" }} -
-
- {{ block "sidebar" . }} {{ end }} -
-
- {{ if $.RepoInfo.Roles.IsPushAllowed }} - {{ $status := (index .Pipeline.Statuses .Workflow).Latest.Status }} -
- -
-
- - -
+{{ define "repoContentLayout" }} +
+
+
+ {{ template "sidebar" . }}
- {{ end }} - {{ with (index .Pipeline.Statuses .Workflow).Latest }} - {{ if .Error }} -
- {{ i "triangle-alert" "size-4 shrink-0 mt-0.5" }} -
-
{{- .ErrorMessage -}}
- {{- with .ErrorDetails }} -
{{- . -}}
- {{- end }} -
-
- {{ end }} - {{ end }} - {{ block "logs" . }} {{ end }} +
+ {{ template "repoContent" . }} +
{{ template "fragments/workflow-timers" }} {{ end }} +{{ define "repoContent" }} + {{ with (index .Pipeline.Statuses .Workflow).Latest }} + {{ if .Error }} +
+ {{ i "triangle-alert" "size-4 shrink-0 mt-0.5" }} +
+
{{- .ErrorMessage -}}
+ {{- with .ErrorDetails }} +
{{- . -}}
+ {{- end }} +
+
+ {{ end }} + {{ end }} + {{ template "logs" . }} +{{ end }} + {{ define "sidebar" }} {{ $active := .Workflow }} - {{ $activeTab := "bg-white dark:bg-gray-700 drop-shadow-sm" }} - {{ $inactiveTab := "bg-gray-100 dark:bg-gray-800" }} - {{ with .Pipeline }} {{ $id := .Id }} -
- {{ range $name, $all := .Statuses }} - + {{ $pipeline := . }} +
+
+ {{ range $name, $all := .Statuses }} + {{ $lastStatus := $all.Latest }} + {{ $status := $lastStatus.Status }} + {{ $kind := $status.String }}
- {{ $lastStatus := $all.Latest }} - {{ $kind := $lastStatus.Status.String }} - -
-
- {{ template "repo/pipelines/fragments/workflowSymbol" $all }} + class="flex flex-col gap-1.5 rounded px-3 py-2.5 transition-colors {{ if eq $name $active }}border border-transparent bg-gray-100 dark:bg-gray-700{{ else }}border border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-800{{ end }}"> +
+ + {{ $name }} + {{ i "arrow-up-right" "size-4 shrink-0 text-gray-900 dark:text-gray-100" }} + +
+ {{ template "repo/fragments/shortTimeAgo" $lastStatus.Created }}
- - {{ $name }} -
- - + {{ end }} +
+ {{ template "sidebarActions" $ }} +
+ {{ end }} +{{ end }} + +{{ define "workflowActions" }} + {{ $root := .Root }} + {{ $status := .Status }} + {{ $kind := .Kind }} + {{ if and $root.RepoInfo.Roles.IsPushAllowed (ne $kind "success") }} +
+ + {{ if not $status.IsFinish }} + {{ end }}
{{ end }} +{{ end }} + +{{ define "sidebarActions" }} +
+ {{ with .SSHLogCommand }} + + {{ end }} + {{ if .RepoInfo.Roles.IsPushAllowed }} +
+
+ + {{ if .Pipeline.InProgress }} + + {{ end }} +
+ {{ end }} +
{{ end }} {{ define "logs" }} @@ -119,8 +151,8 @@ class="text-sm" hx-ext="ws" ws-connect="/{{ $.RepoInfo.FullName }}/pipelines/{{ .Pipeline.Id }}/workflow/{{ .Workflow }}/logs"> -
-