Render actions runs pages
- SHA
a5f9b2e25298a158c52d1b1d6a032781dee11364- Parents
-
069a4d0 - Tree
108cb3d
a5f9b2e
a5f9b2e25298a158c52d1b1d6a032781dee11364069a4d0
108cb3d| Status | File | + | - |
|---|---|---|---|
| A |
internal/web/handlers/repo/actions.go
|
329 | 0 |
| M |
internal/web/handlers/repo/deferred_tabs.go
|
0 | 13 |
| M |
internal/web/handlers/repo/repo.go
|
1 | 0 |
| M |
internal/web/render/octicons.go
|
18 | 0 |
| M |
internal/web/static/css/shithub.css
|
372 | 0 |
| A |
internal/web/templates/repo/action_run.html
|
118 | 0 |
| A |
internal/web/templates/repo/actions.html
|
83 | 0 |
internal/web/handlers/repo/actions.goadded@@ -0,0 +1,329 @@ | ||
| 1 | +// SPDX-License-Identifier: AGPL-3.0-or-later | |
| 2 | + | |
| 3 | +package repo | |
| 4 | + | |
| 5 | +import ( | |
| 6 | + "errors" | |
| 7 | + "html/template" | |
| 8 | + "net/http" | |
| 9 | + "strconv" | |
| 10 | + "time" | |
| 11 | + | |
| 12 | + "github.com/go-chi/chi/v5" | |
| 13 | + "github.com/jackc/pgx/v5" | |
| 14 | + | |
| 15 | + "github.com/tenseleyFlow/shithub/internal/auth/policy" | |
| 16 | + checksdb "github.com/tenseleyFlow/shithub/internal/checks/sqlc" | |
| 17 | + "github.com/tenseleyFlow/shithub/internal/web/middleware" | |
| 18 | +) | |
| 19 | + | |
| 20 | +type actionsWorkflowView struct { | |
| 21 | + Name string | |
| 22 | + Count int | |
| 23 | +} | |
| 24 | + | |
| 25 | +type actionsSuiteView struct { | |
| 26 | + ID int64 | |
| 27 | + AppSlug string | |
| 28 | + Title string | |
| 29 | + HeadSha string | |
| 30 | + HeadShaShort string | |
| 31 | + PullNumber int64 | |
| 32 | + PullAuthorUsername string | |
| 33 | + HeadRef string | |
| 34 | + BaseRef string | |
| 35 | + RunCount int | |
| 36 | + StateText string | |
| 37 | + StateClass string | |
| 38 | + StateIcon string | |
| 39 | + CreatedAt time.Time | |
| 40 | + UpdatedAt time.Time | |
| 41 | + Duration string | |
| 42 | + Runs []actionsRunView | |
| 43 | + AnnotationCount int | |
| 44 | +} | |
| 45 | + | |
| 46 | +type actionsRunView struct { | |
| 47 | + ID int64 | |
| 48 | + Name string | |
| 49 | + StateText string | |
| 50 | + StateClass string | |
| 51 | + StateIcon string | |
| 52 | + Duration string | |
| 53 | + CompletedAt time.Time | |
| 54 | + DetailsURL string | |
| 55 | + SummaryHTML template.HTML | |
| 56 | +} | |
| 57 | + | |
| 58 | +func (h *Handlers) repoTabActions(w http.ResponseWriter, r *http.Request) { | |
| 59 | + row, owner, ok := h.loadRepoAndAuthorize(w, r, policy.ActionRepoRead) | |
| 60 | + if !ok { | |
| 61 | + return | |
| 62 | + } | |
| 63 | + suiteRows, err := h.cq.ListCheckSuitesForRepo(r.Context(), h.d.Pool, checksdb.ListCheckSuitesForRepoParams{ | |
| 64 | + RepoID: row.ID, | |
| 65 | + Limit: 50, | |
| 66 | + Offset: 0, | |
| 67 | + }) | |
| 68 | + if err != nil { | |
| 69 | + h.d.Logger.WarnContext(r.Context(), "repo actions: list suites", "error", err) | |
| 70 | + h.d.Render.HTTPError(w, r, http.StatusInternalServerError, "") | |
| 71 | + return | |
| 72 | + } | |
| 73 | + | |
| 74 | + suites := make([]actionsSuiteView, 0, len(suiteRows)) | |
| 75 | + workflowCounts := map[string]int{} | |
| 76 | + workflowOrder := []string{} | |
| 77 | + for _, suite := range suiteRows { | |
| 78 | + runs, err := h.cq.ListCheckRunsBySuite(r.Context(), h.d.Pool, suite.ID) | |
| 79 | + if err != nil { | |
| 80 | + h.d.Logger.WarnContext(r.Context(), "repo actions: list runs", "suite_id", suite.ID, "error", err) | |
| 81 | + continue | |
| 82 | + } | |
| 83 | + if _, ok := workflowCounts[suite.AppSlug]; !ok { | |
| 84 | + workflowOrder = append(workflowOrder, suite.AppSlug) | |
| 85 | + } | |
| 86 | + workflowCounts[suite.AppSlug]++ | |
| 87 | + suites = append(suites, actionsSuiteViewFromListRow(suite, runs)) | |
| 88 | + } | |
| 89 | + | |
| 90 | + workflows := make([]actionsWorkflowView, 0, len(workflowOrder)) | |
| 91 | + for _, name := range workflowOrder { | |
| 92 | + workflows = append(workflows, actionsWorkflowView{Name: name, Count: workflowCounts[name]}) | |
| 93 | + } | |
| 94 | + | |
| 95 | + data := h.repoHeaderData(r, row, owner.Username, "actions") | |
| 96 | + data["Title"] = "Actions · " + row.Name | |
| 97 | + data["Suites"] = suites | |
| 98 | + data["Workflows"] = workflows | |
| 99 | + data["RunCount"] = len(suites) | |
| 100 | + if err := h.d.Render.RenderPage(w, r, "repo/actions", data); err != nil { | |
| 101 | + h.d.Logger.ErrorContext(r.Context(), "repo actions render", "error", err) | |
| 102 | + } | |
| 103 | +} | |
| 104 | + | |
| 105 | +func (h *Handlers) repoActionRun(w http.ResponseWriter, r *http.Request) { | |
| 106 | + row, owner, ok := h.loadRepoAndAuthorize(w, r, policy.ActionRepoRead) | |
| 107 | + if !ok { | |
| 108 | + return | |
| 109 | + } | |
| 110 | + suiteID, err := strconv.ParseInt(chi.URLParam(r, "suiteID"), 10, 64) | |
| 111 | + if err != nil { | |
| 112 | + h.d.Render.HTTPError(w, r, http.StatusNotFound, "") | |
| 113 | + return | |
| 114 | + } | |
| 115 | + suite, err := h.cq.GetCheckSuiteForRepo(r.Context(), h.d.Pool, checksdb.GetCheckSuiteForRepoParams{ | |
| 116 | + RepoID: row.ID, | |
| 117 | + ID: suiteID, | |
| 118 | + }) | |
| 119 | + if err != nil { | |
| 120 | + if errors.Is(err, pgx.ErrNoRows) { | |
| 121 | + h.d.Render.HTTPError(w, r, http.StatusNotFound, "") | |
| 122 | + } else { | |
| 123 | + h.d.Logger.WarnContext(r.Context(), "repo actions: get suite", "suite_id", suiteID, "error", err) | |
| 124 | + h.d.Render.HTTPError(w, r, http.StatusInternalServerError, "") | |
| 125 | + } | |
| 126 | + return | |
| 127 | + } | |
| 128 | + runs, err := h.cq.ListCheckRunsBySuite(r.Context(), h.d.Pool, suite.ID) | |
| 129 | + if err != nil { | |
| 130 | + h.d.Logger.WarnContext(r.Context(), "repo actions: get suite runs", "suite_id", suiteID, "error", err) | |
| 131 | + h.d.Render.HTTPError(w, r, http.StatusInternalServerError, "") | |
| 132 | + return | |
| 133 | + } | |
| 134 | + | |
| 135 | + view := actionsSuiteViewFromGetRow(suite, runs) | |
| 136 | + data := h.repoHeaderData(r, row, owner.Username, "actions") | |
| 137 | + data["Title"] = view.Title + " · " + row.Name | |
| 138 | + data["Run"] = view | |
| 139 | + data["CSRFToken"] = middleware.CSRFTokenForRequest(r) | |
| 140 | + if err := h.d.Render.RenderPage(w, r, "repo/action_run", data); err != nil { | |
| 141 | + h.d.Logger.ErrorContext(r.Context(), "repo action run render", "suite_id", suiteID, "error", err) | |
| 142 | + } | |
| 143 | +} | |
| 144 | + | |
| 145 | +func actionsSuiteViewFromListRow(row checksdb.ListCheckSuitesForRepoRow, runs []checksdb.CheckRun) actionsSuiteView { | |
| 146 | + return actionsSuiteViewFromParts( | |
| 147 | + row.ID, | |
| 148 | + row.HeadSha, | |
| 149 | + row.AppSlug, | |
| 150 | + row.Status, | |
| 151 | + row.Conclusion, | |
| 152 | + row.CreatedAt.Time, | |
| 153 | + row.UpdatedAt.Time, | |
| 154 | + row.PullNumber, | |
| 155 | + row.PullTitle, | |
| 156 | + row.PullAuthorUsername, | |
| 157 | + row.HeadRef, | |
| 158 | + row.BaseRef, | |
| 159 | + runs, | |
| 160 | + ) | |
| 161 | +} | |
| 162 | + | |
| 163 | +func actionsSuiteViewFromGetRow(row checksdb.GetCheckSuiteForRepoRow, runs []checksdb.CheckRun) actionsSuiteView { | |
| 164 | + return actionsSuiteViewFromParts( | |
| 165 | + row.ID, | |
| 166 | + row.HeadSha, | |
| 167 | + row.AppSlug, | |
| 168 | + row.Status, | |
| 169 | + row.Conclusion, | |
| 170 | + row.CreatedAt.Time, | |
| 171 | + row.UpdatedAt.Time, | |
| 172 | + row.PullNumber, | |
| 173 | + row.PullTitle, | |
| 174 | + row.PullAuthorUsername, | |
| 175 | + row.HeadRef, | |
| 176 | + row.BaseRef, | |
| 177 | + runs, | |
| 178 | + ) | |
| 179 | +} | |
| 180 | + | |
| 181 | +func actionsSuiteViewFromParts( | |
| 182 | + id int64, | |
| 183 | + headSHA string, | |
| 184 | + appSlug string, | |
| 185 | + status checksdb.CheckStatus, | |
| 186 | + conclusion checksdb.NullCheckConclusion, | |
| 187 | + createdAt time.Time, | |
| 188 | + updatedAt time.Time, | |
| 189 | + pullNumber int64, | |
| 190 | + pullTitle string, | |
| 191 | + pullAuthorUsername string, | |
| 192 | + headRef string, | |
| 193 | + baseRef string, | |
| 194 | + runs []checksdb.CheckRun, | |
| 195 | +) actionsSuiteView { | |
| 196 | + title := pullTitle | |
| 197 | + if title == "" { | |
| 198 | + title = appSlug + " checks for " + shortSHA(headSHA) | |
| 199 | + } | |
| 200 | + stateText, stateClass, stateIcon := actionState(status, conclusion) | |
| 201 | + runViews := make([]actionsRunView, 0, len(runs)) | |
| 202 | + annotationCount := 0 | |
| 203 | + for _, run := range runs { | |
| 204 | + view := actionsRunViewFromRun(run) | |
| 205 | + if view.SummaryHTML != "" { | |
| 206 | + annotationCount++ | |
| 207 | + } | |
| 208 | + runViews = append(runViews, view) | |
| 209 | + } | |
| 210 | + return actionsSuiteView{ | |
| 211 | + ID: id, | |
| 212 | + AppSlug: appSlug, | |
| 213 | + Title: title, | |
| 214 | + HeadSha: headSHA, | |
| 215 | + HeadShaShort: shortSHA(headSHA), | |
| 216 | + PullNumber: pullNumber, | |
| 217 | + PullAuthorUsername: pullAuthorUsername, | |
| 218 | + HeadRef: headRef, | |
| 219 | + BaseRef: baseRef, | |
| 220 | + RunCount: len(runs), | |
| 221 | + StateText: stateText, | |
| 222 | + StateClass: stateClass, | |
| 223 | + StateIcon: stateIcon, | |
| 224 | + CreatedAt: createdAt, | |
| 225 | + UpdatedAt: updatedAt, | |
| 226 | + Duration: actionSuiteDuration(runs, createdAt, updatedAt), | |
| 227 | + Runs: runViews, | |
| 228 | + AnnotationCount: annotationCount, | |
| 229 | + } | |
| 230 | +} | |
| 231 | + | |
| 232 | +func actionsRunViewFromRun(run checksdb.CheckRun) actionsRunView { | |
| 233 | + stateText, stateClass, stateIcon := actionState(run.Status, run.Conclusion) | |
| 234 | + start := run.CreatedAt.Time | |
| 235 | + if run.StartedAt.Valid { | |
| 236 | + start = run.StartedAt.Time | |
| 237 | + } | |
| 238 | + end := run.UpdatedAt.Time | |
| 239 | + if run.CompletedAt.Valid { | |
| 240 | + end = run.CompletedAt.Time | |
| 241 | + } | |
| 242 | + return actionsRunView{ | |
| 243 | + ID: run.ID, | |
| 244 | + Name: run.Name, | |
| 245 | + StateText: stateText, | |
| 246 | + StateClass: stateClass, | |
| 247 | + StateIcon: stateIcon, | |
| 248 | + Duration: formatDuration(end.Sub(start)), | |
| 249 | + CompletedAt: end, | |
| 250 | + DetailsURL: run.DetailsUrl, | |
| 251 | + SummaryHTML: renderCheckSummary(run.Output), | |
| 252 | + } | |
| 253 | +} | |
| 254 | + | |
| 255 | +func actionState(status checksdb.CheckStatus, conclusion checksdb.NullCheckConclusion) (string, string, string) { | |
| 256 | + if !conclusion.Valid { | |
| 257 | + switch status { | |
| 258 | + case checksdb.CheckStatusCompleted: | |
| 259 | + return "Completed", "neutral", "check-circle" | |
| 260 | + case checksdb.CheckStatusInProgress: | |
| 261 | + return "In progress", "pending", "dot-fill" | |
| 262 | + case checksdb.CheckStatusQueued, checksdb.CheckStatusPending: | |
| 263 | + return "Queued", "pending", "dot-fill" | |
| 264 | + default: | |
| 265 | + return string(status), "neutral", "dot-fill" | |
| 266 | + } | |
| 267 | + } | |
| 268 | + switch conclusion.CheckConclusion { | |
| 269 | + case checksdb.CheckConclusionSuccess, checksdb.CheckConclusionSkipped, checksdb.CheckConclusionNeutral: | |
| 270 | + return "Success", "success", "check-circle-fill" | |
| 271 | + case checksdb.CheckConclusionFailure, checksdb.CheckConclusionTimedOut, checksdb.CheckConclusionActionRequired: | |
| 272 | + return "Failure", "failure", "x-circle-fill" | |
| 273 | + case checksdb.CheckConclusionCancelled, checksdb.CheckConclusionStale: | |
| 274 | + return "Cancelled", "neutral", "x-circle" | |
| 275 | + default: | |
| 276 | + return string(conclusion.CheckConclusion), "neutral", "dot-fill" | |
| 277 | + } | |
| 278 | +} | |
| 279 | + | |
| 280 | +func actionSuiteDuration(runs []checksdb.CheckRun, createdAt, updatedAt time.Time) string { | |
| 281 | + if len(runs) == 0 { | |
| 282 | + return formatDuration(updatedAt.Sub(createdAt)) | |
| 283 | + } | |
| 284 | + var start, end time.Time | |
| 285 | + for _, run := range runs { | |
| 286 | + runStart := run.CreatedAt.Time | |
| 287 | + if run.StartedAt.Valid { | |
| 288 | + runStart = run.StartedAt.Time | |
| 289 | + } | |
| 290 | + runEnd := run.UpdatedAt.Time | |
| 291 | + if run.CompletedAt.Valid { | |
| 292 | + runEnd = run.CompletedAt.Time | |
| 293 | + } | |
| 294 | + if start.IsZero() || runStart.Before(start) { | |
| 295 | + start = runStart | |
| 296 | + } | |
| 297 | + if end.IsZero() || runEnd.After(end) { | |
| 298 | + end = runEnd | |
| 299 | + } | |
| 300 | + } | |
| 301 | + return formatDuration(end.Sub(start)) | |
| 302 | +} | |
| 303 | + | |
| 304 | +func formatDuration(d time.Duration) string { | |
| 305 | + if d <= 0 { | |
| 306 | + return "—" | |
| 307 | + } | |
| 308 | + if d < time.Minute { | |
| 309 | + return strconv.Itoa(int(d.Seconds())) + "s" | |
| 310 | + } | |
| 311 | + if d < time.Hour { | |
| 312 | + mins := int(d / time.Minute) | |
| 313 | + secs := int((d % time.Minute) / time.Second) | |
| 314 | + if secs == 0 { | |
| 315 | + return strconv.Itoa(mins) + "m" | |
| 316 | + } | |
| 317 | + return strconv.Itoa(mins) + "m " + strconv.Itoa(secs) + "s" | |
| 318 | + } | |
| 319 | + hours := int(d / time.Hour) | |
| 320 | + mins := int((d % time.Hour) / time.Minute) | |
| 321 | + return strconv.Itoa(hours) + "h " + strconv.Itoa(mins) + "m" | |
| 322 | +} | |
| 323 | + | |
| 324 | +func shortSHA(sha string) string { | |
| 325 | + if len(sha) <= 7 { | |
| 326 | + return sha | |
| 327 | + } | |
| 328 | + return sha[:7] | |
| 329 | +} | |
internal/web/handlers/repo/deferred_tabs.gomodified@@ -24,19 +24,6 @@ type repoDeferredSection struct { | ||
| 24 | 24 | Body string |
| 25 | 25 | } |
| 26 | 26 | |
| 27 | -func (h *Handlers) repoTabActions(w http.ResponseWriter, r *http.Request) { | |
| 28 | - h.renderDeferredRepoTab(w, r, repoDeferredTab{ | |
| 29 | - Active: "actions", | |
| 30 | - Heading: "Actions", | |
| 31 | - Description: "Automate your development workflow with repository workflows and check runs.", | |
| 32 | - Icon: "play", | |
| 33 | - Sections: []repoDeferredSection{ | |
| 34 | - {Anchor: "all-workflows", Title: "All workflows", Body: "Workflow execution is parked for the Actions sprint. Check runs posted by external systems still appear on pull requests."}, | |
| 35 | - {Anchor: "workflows", Title: "Workflows", Body: "Add workflow files later under .shithub/workflows to run jobs on push and pull request events."}, | |
| 36 | - }, | |
| 37 | - }) | |
| 38 | -} | |
| 39 | - | |
| 40 | 27 | func (h *Handlers) repoTabProjects(w http.ResponseWriter, r *http.Request) { |
| 41 | 28 | h.renderDeferredRepoTab(w, r, repoDeferredTab{ |
| 42 | 29 | Active: "projects", |
internal/web/handlers/repo/repo.gomodified@@ -118,6 +118,7 @@ func (h *Handlers) MountNew(r chi.Router) { | ||
| 118 | 118 | // two-segment route doesn't collide with the /{username} catch-all from S09; |
| 119 | 119 | // caller is responsible for ordering this BEFORE /{username}. |
| 120 | 120 | func (h *Handlers) MountRepoHome(r chi.Router) { |
| 121 | + r.Get("/{owner}/{repo}/actions/runs/{suiteID}", h.repoActionRun) | |
| 121 | 122 | r.Get("/{owner}/{repo}/actions", h.repoTabActions) |
| 122 | 123 | r.Get("/{owner}/{repo}/projects", h.repoTabProjects) |
| 123 | 124 | r.Get("/{owner}/{repo}/wiki", h.repoTabWiki) |
internal/web/render/octicons.gomodified@@ -61,8 +61,12 @@ func BuiltinOcticons() OcticonResolver { | ||
| 61 | 61 | `><path d="M2.5 1.75v11.5c0 .138.112.25.25.25h3.17a.75.75 0 0 1 0 1.5H2.75A1.75 1.75 0 0 1 1 13.25V1.75C1 .784 1.784 0 2.75 0h8.5C12.216 0 13 .784 13 1.75v7.736a.75.75 0 0 1-1.5 0V1.75a.25.25 0 0 0-.25-.25h-8.5a.25.25 0 0 0-.25.25Zm13.274 9.537-4.557 4.45a.75.75 0 0 1-1.055-.008l-1.943-1.95a.75.75 0 0 1 1.062-1.058l1.419 1.425 4.026-3.932a.75.75 0 1 1 1.048 1.074ZM4.75 4h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1 0-1.5ZM4 7.75A.75.75 0 0 1 4.75 7h2a.75.75 0 0 1 0 1.5h-2A.75.75 0 0 1 4 7.75Z"/></svg>`), |
| 62 | 62 | "check-circle": trustedSVG(`<svg xmlns="http://www.w3.org/2000/svg" ` + cls + |
| 63 | 63 | `><path d="M8 1.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm11.03-1.78a.75.75 0 0 1 0 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.97 8.78a.75.75 0 0 1 1.06-1.06l1.22 1.22 2.72-2.72a.75.75 0 0 1 1.06 0Z"/></svg>`), |
| 64 | + "check-circle-fill": trustedSVG(`<svg xmlns="http://www.w3.org/2000/svg" ` + cls + | |
| 65 | + `><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16Zm3.78-9.72a.751.751 0 0 0-.018-1.042.751.751 0 0 0-1.042-.018L6.75 9.19 5.28 7.72a.751.751 0 0 0-1.042.018.751.751 0 0 0-.018 1.042l2 2a.75.75 0 0 0 1.06 0Z"/></svg>`), | |
| 64 | 66 | "x-circle": trustedSVG(`<svg xmlns="http://www.w3.org/2000/svg" ` + cls + |
| 65 | 67 | `><path d="M8 1.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm5.72-2.28a.75.75 0 0 1 1.06 0L8 6.94l1.22-1.22a.75.75 0 1 1 1.06 1.06L9.06 8l1.22 1.22a.75.75 0 1 1-1.06 1.06L8 9.06l-1.22 1.22a.75.75 0 1 1-1.06-1.06L6.94 8 5.72 6.78a.75.75 0 0 1 0-1.06Z"/></svg>`), |
| 68 | + "x-circle-fill": trustedSVG(`<svg xmlns="http://www.w3.org/2000/svg" ` + cls + | |
| 69 | + `><path d="M8 16A8 8 0 1 1 8 0a8 8 0 0 1 0 16ZM5.72 5.72a.75.75 0 0 0 0 1.06L6.94 8 5.72 9.22a.75.75 0 1 0 1.06 1.06L8 9.06l1.22 1.22a.75.75 0 1 0 1.06-1.06L9.06 8l1.22-1.22a.75.75 0 1 0-1.06-1.06L8 6.94 6.78 5.72a.75.75 0 0 0-1.06 0Z"/></svg>`), | |
| 66 | 70 | "rocket": trustedSVG(`<svg xmlns="http://www.w3.org/2000/svg" ` + cls + |
| 67 | 71 | `><path d="M14.064.66a.75.75 0 0 1 1.276.588c-.32 2.07-1.03 4.72-2.52 6.95-.72 1.08-1.65 2.08-2.84 2.8l.27 2.18a.75.75 0 0 1-.22.61l-1.5 1.5a.75.75 0 0 1-1.25-.34l-.65-2.61-2.97-.74-2.61-.65a.75.75 0 0 1-.34-1.25l1.5-1.5a.75.75 0 0 1 .61-.22l2.18.27c.72-1.19 1.72-2.12 2.8-2.84C10.03 3.92 12.68 3.21 14.75 2.89a.75.75 0 0 1-.686-2.23ZM9.5 7a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM1.5 14.25c0-.966.784-1.75 1.75-1.75h.5a.75.75 0 0 1 0 1.5h-.5a.25.25 0 0 0-.25.25v.5a.75.75 0 0 1-1.5 0Z"/></svg>`), |
| 68 | 72 | "git-branch": trustedSVG(`<svg xmlns="http://www.w3.org/2000/svg" ` + cls + |
@@ -85,6 +89,10 @@ func BuiltinOcticons() OcticonResolver { | ||
| 85 | 89 | `><path d="M1 2.75C1 1.784 1.784 1 2.75 1h10.5c.966 0 1.75.784 1.75 1.75v6.5A1.75 1.75 0 0 1 13.25 11H8.06l-3.31 2.48A.75.75 0 0 1 3.5 12.88V11h-.75A1.75 1.75 0 0 1 1 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v6.5c0 .138.112.25.25.25h1.5a.75.75 0 0 1 .75.75v1.13l2.36-1.77a.75.75 0 0 1 .45-.15h5.44a.25.25 0 0 0 .25-.25v-6.5a.25.25 0 0 0-.25-.25Z"/></svg>`), |
| 86 | 90 | "history": trustedSVG(`<svg xmlns="http://www.w3.org/2000/svg" ` + cls + |
| 87 | 91 | `><path d="M1.643 3.143.427 1.927A.25.25 0 0 0 0 2.104V5.75c0 .138.112.25.25.25h3.646a.25.25 0 0 0 .177-.427L2.715 4.215A6.5 6.5 0 1 1 8 14.5a.75.75 0 0 0 0 1.5 8 8 0 1 0-6.357-12.857ZM7.25 4.75a.75.75 0 0 1 1.5 0v3.19l2.03 2.03a.75.75 0 1 1-1.06 1.06L7.47 8.78a.75.75 0 0 1-.22-.53Z"/></svg>`), |
| 92 | + "calendar": trustedSVG(`<svg xmlns="http://www.w3.org/2000/svg" ` + cls + | |
| 93 | + `><path d="M4.75 0a.75.75 0 0 1 .75.75V2h5V.75a.75.75 0 0 1 1.5 0V2h1.25c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 13.25 16H2.75A1.75 1.75 0 0 1 1 14.25V3.75C1 2.784 1.784 2 2.75 2H4V.75A.75.75 0 0 1 4.75 0ZM2.5 7.5v6.75c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25V7.5Zm10.75-4H2.75a.25.25 0 0 0-.25.25V6h11V3.75a.25.25 0 0 0-.25-.25Z"/></svg>`), | |
| 94 | + "stopwatch": trustedSVG(`<svg xmlns="http://www.w3.org/2000/svg" ` + cls + | |
| 95 | + `><path d="M5.75.75A.75.75 0 0 1 6.5 0h3a.75.75 0 0 1 0 1.5h-.75v1l-.001.041a6.724 6.724 0 0 1 3.464 1.435l.007-.006.75-.75a.749.749 0 0 1 1.275.326.749.749 0 0 1-.215.734l-.75.75-.006.007a6.75 6.75 0 1 1-10.548 0L2.72 5.03l-.75-.75a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018l.75.75.007.006A6.72 6.72 0 0 1 7.25 2.541V1.5H6.5a.75.75 0 0 1-.75-.75ZM8 14.5a5.25 5.25 0 1 0-.001-10.501A5.25 5.25 0 0 0 8 14.5Zm.389-6.7 1.33-1.33a.75.75 0 1 1 1.061 1.06L9.45 8.861A1.503 1.503 0 0 1 8 10.75a1.499 1.499 0 1 1 .389-2.95Z"/></svg>`), | |
| 88 | 96 | "gear": trustedSVG(`<svg xmlns="http://www.w3.org/2000/svg" ` + cls + |
| 89 | 97 | `><path d="M8 0a1.5 1.5 0 0 1 1.45 1.13l.21.83c.23.08.45.18.66.3l.75-.44a1.5 1.5 0 0 1 1.93.3l.88.88a1.5 1.5 0 0 1 .3 1.93l-.44.75c.12.21.22.43.3.66l.83.21A1.5 1.5 0 0 1 16 8a1.5 1.5 0 0 1-1.13 1.45l-.83.21c-.08.23-.18.45-.3.66l.44.75a1.5 1.5 0 0 1-.3 1.93l-.88.88a1.5 1.5 0 0 1-1.93.3l-.75-.44c-.21.12-.43.22-.66.3l-.21.83A1.5 1.5 0 0 1 8 16a1.5 1.5 0 0 1-1.45-1.13l-.21-.83a5.36 5.36 0 0 1-.66-.3l-.75.44a1.5 1.5 0 0 1-1.93-.3L2.12 13a1.5 1.5 0 0 1-.3-1.93l.44-.75a5.36 5.36 0 0 1-.3-.66l-.83-.21A1.5 1.5 0 0 1 0 8c0-.69.47-1.29 1.13-1.45l.83-.21c.08-.23.18-.45.3-.66l-.44-.75a1.5 1.5 0 0 1 .3-1.93L3 2.12a1.5 1.5 0 0 1 1.93-.3l.75.44c.21-.12.43-.22.66-.3l.21-.83A1.5 1.5 0 0 1 8 0Zm0 5a3 3 0 1 0 0 6 3 3 0 0 0 0-6Z"/></svg>`), |
| 90 | 98 | "lock": trustedSVG(`<svg xmlns="http://www.w3.org/2000/svg" ` + cls + |
@@ -131,6 +139,16 @@ func BuiltinOcticons() OcticonResolver { | ||
| 131 | 139 | `><path d="M7.775 3.275a.75.75 0 0 1 0 1.06L4.53 7.58a2.25 2.25 0 1 0 3.182 3.182l1.47-1.47a.75.75 0 0 1 1.06 1.061l-1.47 1.47A3.75 3.75 0 1 1 3.47 6.52l3.245-3.245a.75.75 0 0 1 1.06 0Zm.45 9.45a.75.75 0 0 1 0-1.06l3.245-3.245a2.25 2.25 0 1 0-3.182-3.182l-1.47 1.47a.75.75 0 0 1-1.06-1.061l1.47-1.47A3.75 3.75 0 1 1 12.53 9.48l-3.245 3.245a.75.75 0 0 1-1.06 0Z"/></svg>`), |
| 132 | 140 | "dot-fill": trustedSVG(`<svg xmlns="http://www.w3.org/2000/svg" ` + cls + |
| 133 | 141 | `><path d="M8 4a4 4 0 1 1 0 8 4 4 0 0 1 0-8Z"/></svg>`), |
| 142 | + "kebab-horizontal": trustedSVG(`<svg xmlns="http://www.w3.org/2000/svg" ` + cls + | |
| 143 | + `><path d="M8 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM1.5 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Zm13 0a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"/></svg>`), | |
| 144 | + "workflow": trustedSVG(`<svg xmlns="http://www.w3.org/2000/svg" ` + cls + | |
| 145 | + `><path d="M0 2.75C0 1.784.784 1 1.75 1h2.5C5.216 1 6 1.784 6 2.75v2.5A1.75 1.75 0 0 1 4.25 7H3.5v2h6.75A1.75 1.75 0 0 1 12 10.75V12h.75a.75.75 0 0 1 0 1.5H12v.75A1.75 1.75 0 0 1 10.25 16h-2.5A1.75 1.75 0 0 1 6 14.25v-2.5C6 10.784 6.784 10 7.75 10h2.5a.25.25 0 0 1 .25.25V10.75a.25.25 0 0 0-.25-.25H3.5v3.75a.75.75 0 0 1-1.5 0V7h-.25A1.75 1.75 0 0 1 0 5.25Zm1.75-.25a.25.25 0 0 0-.25.25v2.5c0 .138.112.25.25.25h2.5a.25.25 0 0 0 .25-.25v-2.5a.25.25 0 0 0-.25-.25Zm6 9a.25.25 0 0 0-.25.25v2.5c0 .138.112.25.25.25h2.5a.25.25 0 0 0 .25-.25v-2.5a.25.25 0 0 0-.25-.25Z"/></svg>`), | |
| 146 | + "screen-full": trustedSVG(`<svg xmlns="http://www.w3.org/2000/svg" ` + cls + | |
| 147 | + `><path d="M1.75 10a.75.75 0 0 1 .75.75v2.5c0 .138.112.25.25.25h2.5a.75.75 0 0 1 0 1.5h-2.5A1.75 1.75 0 0 1 1 13.25v-2.5a.75.75 0 0 1 .75-.75Zm12.5 0a.75.75 0 0 1 .75.75v2.5A1.75 1.75 0 0 1 13.25 15h-2.5a.75.75 0 0 1 0-1.5h2.5a.25.25 0 0 0 .25-.25v-2.5a.75.75 0 0 1 .75-.75ZM2.75 2.5a.25.25 0 0 0-.25.25v2.5a.75.75 0 0 1-1.5 0v-2.5C1 1.784 1.784 1 2.75 1h2.5a.75.75 0 0 1 0 1.5ZM10 1.75a.75.75 0 0 1 .75-.75h2.5c.966 0 1.75.784 1.75 1.75v2.5a.75.75 0 0 1-1.5 0v-2.5a.25.25 0 0 0-.25-.25h-2.5a.75.75 0 0 1-.75-.75Z"/></svg>`), | |
| 148 | + "dash": trustedSVG(`<svg xmlns="http://www.w3.org/2000/svg" ` + cls + | |
| 149 | + `><path d="M2 7.75A.75.75 0 0 1 2.75 7h10a.75.75 0 0 1 0 1.5h-10A.75.75 0 0 1 2 7.75Z"/></svg>`), | |
| 150 | + "plus": trustedSVG(`<svg xmlns="http://www.w3.org/2000/svg" ` + cls + | |
| 151 | + `><path d="M7.75 2a.75.75 0 0 1 .75.75V7h4.25a.75.75 0 0 1 0 1.5H8.5v4.25a.75.75 0 0 1-1.5 0V8.5H2.75a.75.75 0 0 1 0-1.5H7V2.75A.75.75 0 0 1 7.75 2Z"/></svg>`), | |
| 134 | 152 | "milestone": trustedSVG(`<svg xmlns="http://www.w3.org/2000/svg" ` + cls + |
| 135 | 153 | `><path d="M8 0a.75.75 0 0 1 .75.75V2h3.5c.414 0 .75.336.75.75v4.5a.75.75 0 0 1-.75.75h-3.5v1h4.5c.414 0 .75.336.75.75v4.5a.75.75 0 0 1-.75.75h-5v.25a.75.75 0 0 1-1.5 0V.75A.75.75 0 0 1 8 0ZM8.75 3.5v3h2.75v-3Zm0 7v3h3.75v-3Z"/></svg>`), |
| 136 | 154 | // S29: notification bell for the top-bar inbox link. |
internal/web/static/css/shithub.cssmodified@@ -2158,6 +2158,378 @@ button.shithub-repo-action { | ||
| 2158 | 2158 | white-space: nowrap; |
| 2159 | 2159 | } |
| 2160 | 2160 | } |
| 2161 | + | |
| 2162 | +/* ========== Repository Actions ========== */ | |
| 2163 | +.shithub-actions-page, | |
| 2164 | +.shithub-actions-run-layout { | |
| 2165 | + display: grid; | |
| 2166 | + grid-template-columns: 18rem minmax(0, 1fr); | |
| 2167 | + gap: 1.5rem; | |
| 2168 | + max-width: 1280px; | |
| 2169 | + margin: 0 auto; | |
| 2170 | + padding: 1.5rem 1rem; | |
| 2171 | +} | |
| 2172 | +.shithub-actions-sidebar { | |
| 2173 | + align-self: start; | |
| 2174 | + display: flex; | |
| 2175 | + flex-direction: column; | |
| 2176 | + gap: 0.25rem; | |
| 2177 | +} | |
| 2178 | +.shithub-actions-sidebar-section { | |
| 2179 | + margin-top: 1rem; | |
| 2180 | + padding-top: 0.75rem; | |
| 2181 | + border-top: 1px solid var(--border-default); | |
| 2182 | +} | |
| 2183 | +.shithub-actions-sidebar-section h2 { | |
| 2184 | + margin: 0 0 0.4rem; | |
| 2185 | + padding: 0 0.55rem; | |
| 2186 | + color: var(--fg-muted); | |
| 2187 | + font-size: 0.78rem; | |
| 2188 | + font-weight: 600; | |
| 2189 | +} | |
| 2190 | +.shithub-actions-sidebar-section p { | |
| 2191 | + margin: 0; | |
| 2192 | + padding: 0.25rem 0.55rem; | |
| 2193 | + color: var(--fg-muted); | |
| 2194 | + font-size: 0.85rem; | |
| 2195 | +} | |
| 2196 | +.shithub-actions-nav-item { | |
| 2197 | + display: grid; | |
| 2198 | + grid-template-columns: 1rem minmax(0, 1fr) auto; | |
| 2199 | + align-items: center; | |
| 2200 | + gap: 0.5rem; | |
| 2201 | + min-height: 2rem; | |
| 2202 | + padding: 0.35rem 0.55rem; | |
| 2203 | + border-radius: 6px; | |
| 2204 | + color: var(--fg-default); | |
| 2205 | + font-size: 0.9rem; | |
| 2206 | + text-decoration: none; | |
| 2207 | +} | |
| 2208 | +.shithub-actions-nav-item:hover, | |
| 2209 | +.shithub-actions-nav-item.is-active { | |
| 2210 | + background: var(--canvas-subtle); | |
| 2211 | + text-decoration: none; | |
| 2212 | +} | |
| 2213 | +.shithub-actions-nav-item span:nth-child(2) { | |
| 2214 | + overflow: hidden; | |
| 2215 | + text-overflow: ellipsis; | |
| 2216 | + white-space: nowrap; | |
| 2217 | +} | |
| 2218 | +.shithub-actions-nav-count { | |
| 2219 | + color: var(--fg-muted); | |
| 2220 | + font-size: 0.78rem; | |
| 2221 | +} | |
| 2222 | +.shithub-actions-main, | |
| 2223 | +.shithub-actions-run-main { | |
| 2224 | + min-width: 0; | |
| 2225 | +} | |
| 2226 | +.shithub-actions-head { | |
| 2227 | + display: flex; | |
| 2228 | + align-items: center; | |
| 2229 | + justify-content: space-between; | |
| 2230 | + gap: 1rem; | |
| 2231 | + margin-bottom: 1rem; | |
| 2232 | +} | |
| 2233 | +.shithub-actions-head h1 { | |
| 2234 | + margin: 0; | |
| 2235 | + font-size: 1.5rem; | |
| 2236 | +} | |
| 2237 | +.shithub-actions-head p { | |
| 2238 | + margin: 0.2rem 0 0; | |
| 2239 | + color: var(--fg-muted); | |
| 2240 | +} | |
| 2241 | +.shithub-actions-filters { | |
| 2242 | + display: grid; | |
| 2243 | + grid-template-columns: minmax(16rem, 1fr) repeat(3, auto); | |
| 2244 | + gap: 0.5rem; | |
| 2245 | + margin-bottom: 1rem; | |
| 2246 | +} | |
| 2247 | +.shithub-actions-search { | |
| 2248 | + display: flex; | |
| 2249 | + align-items: center; | |
| 2250 | + gap: 0.45rem; | |
| 2251 | + min-width: 0; | |
| 2252 | + padding: 0.35rem 0.55rem; | |
| 2253 | + border: 1px solid var(--border-default); | |
| 2254 | + border-radius: 6px; | |
| 2255 | + background: var(--canvas-default); | |
| 2256 | + color: var(--fg-muted); | |
| 2257 | +} | |
| 2258 | +.shithub-actions-search input { | |
| 2259 | + width: 100%; | |
| 2260 | + min-width: 0; | |
| 2261 | + border: 0; | |
| 2262 | + outline: 0; | |
| 2263 | + background: transparent; | |
| 2264 | + color: var(--fg-muted); | |
| 2265 | +} | |
| 2266 | +.shithub-actions-filter-button { | |
| 2267 | + display: inline-flex; | |
| 2268 | + align-items: center; | |
| 2269 | + gap: 0.35rem; | |
| 2270 | + padding: 0.35rem 0.7rem; | |
| 2271 | + border: 1px solid var(--border-default); | |
| 2272 | + border-radius: 6px; | |
| 2273 | + background: var(--canvas-default); | |
| 2274 | + color: var(--fg-default); | |
| 2275 | + font-size: 0.88rem; | |
| 2276 | + font-weight: 600; | |
| 2277 | +} | |
| 2278 | +.shithub-actions-runs { | |
| 2279 | + overflow: hidden; | |
| 2280 | + border: 1px solid var(--border-default); | |
| 2281 | + border-radius: 6px; | |
| 2282 | + background: var(--canvas-default); | |
| 2283 | +} | |
| 2284 | +.shithub-actions-run-row { | |
| 2285 | + display: grid; | |
| 2286 | + grid-template-columns: minmax(0, 1.6fr) minmax(9rem, 0.7fr) minmax(13rem, 0.8fr); | |
| 2287 | + gap: 1rem; | |
| 2288 | + align-items: center; | |
| 2289 | + padding: 0.9rem 1rem; | |
| 2290 | + border-top: 1px solid var(--border-default); | |
| 2291 | +} | |
| 2292 | +.shithub-actions-run-row:first-child { border-top: 0; } | |
| 2293 | +.shithub-actions-run-primary, | |
| 2294 | +.shithub-actions-run-branch, | |
| 2295 | +.shithub-actions-run-meta { | |
| 2296 | + min-width: 0; | |
| 2297 | +} | |
| 2298 | +.shithub-actions-run-title { | |
| 2299 | + display: flex; | |
| 2300 | + align-items: flex-start; | |
| 2301 | + gap: 0.55rem; | |
| 2302 | + min-width: 0; | |
| 2303 | + color: var(--fg-default); | |
| 2304 | + font-size: 1rem; | |
| 2305 | + font-weight: 600; | |
| 2306 | + text-decoration: none; | |
| 2307 | +} | |
| 2308 | +.shithub-actions-run-title span:last-child { | |
| 2309 | + overflow: hidden; | |
| 2310 | + text-overflow: ellipsis; | |
| 2311 | + white-space: nowrap; | |
| 2312 | +} | |
| 2313 | +.shithub-actions-run-title:hover span:last-child { text-decoration: underline; } | |
| 2314 | +.shithub-actions-run-primary p { | |
| 2315 | + margin: 0.25rem 0 0 1.55rem; | |
| 2316 | + color: var(--fg-muted); | |
| 2317 | + font-size: 0.86rem; | |
| 2318 | +} | |
| 2319 | +.shithub-actions-run-meta { | |
| 2320 | + display: flex; | |
| 2321 | + align-items: center; | |
| 2322 | + justify-content: flex-end; | |
| 2323 | + gap: 0.75rem; | |
| 2324 | + color: var(--fg-muted); | |
| 2325 | + font-size: 0.82rem; | |
| 2326 | + white-space: nowrap; | |
| 2327 | +} | |
| 2328 | +.shithub-actions-run-meta span { | |
| 2329 | + display: inline-flex; | |
| 2330 | + align-items: center; | |
| 2331 | + gap: 0.25rem; | |
| 2332 | +} | |
| 2333 | +.shithub-actions-state { | |
| 2334 | + display: inline-flex; | |
| 2335 | + align-items: center; | |
| 2336 | + justify-content: center; | |
| 2337 | + color: var(--fg-muted); | |
| 2338 | +} | |
| 2339 | +.shithub-actions-state-success { color: #1a7f37; } | |
| 2340 | +.shithub-actions-state-failure { color: #cf222e; } | |
| 2341 | +.shithub-actions-state-pending { color: #9a6700; } | |
| 2342 | +.shithub-actions-state-neutral { color: var(--fg-muted); } | |
| 2343 | +.shithub-actions-empty { | |
| 2344 | + padding: 4rem 1rem; | |
| 2345 | + border: 1px solid var(--border-default); | |
| 2346 | + border-radius: 6px; | |
| 2347 | + background: var(--canvas-default); | |
| 2348 | + text-align: center; | |
| 2349 | +} | |
| 2350 | +.shithub-actions-empty-icon { | |
| 2351 | + display: inline-flex; | |
| 2352 | + align-items: center; | |
| 2353 | + justify-content: center; | |
| 2354 | + width: 3rem; | |
| 2355 | + height: 3rem; | |
| 2356 | + margin-bottom: 1rem; | |
| 2357 | + border: 1px solid var(--border-default); | |
| 2358 | + border-radius: 999px; | |
| 2359 | + color: var(--fg-muted); | |
| 2360 | +} | |
| 2361 | +.shithub-actions-empty h2 { | |
| 2362 | + margin: 0; | |
| 2363 | + font-size: 1.25rem; | |
| 2364 | +} | |
| 2365 | +.shithub-actions-empty p { | |
| 2366 | + margin: 0.5rem auto 0; | |
| 2367 | + max-width: 34rem; | |
| 2368 | + color: var(--fg-muted); | |
| 2369 | +} | |
| 2370 | +.shithub-actions-run-page { | |
| 2371 | + max-width: 1280px; | |
| 2372 | + margin: 0 auto; | |
| 2373 | + padding: 1.5rem 1rem; | |
| 2374 | +} | |
| 2375 | +.shithub-actions-run-head { | |
| 2376 | + display: flex; | |
| 2377 | + align-items: flex-start; | |
| 2378 | + justify-content: space-between; | |
| 2379 | + gap: 1rem; | |
| 2380 | + padding-bottom: 1rem; | |
| 2381 | + border-bottom: 1px solid var(--border-default); | |
| 2382 | +} | |
| 2383 | +.shithub-actions-back { | |
| 2384 | + display: inline-block; | |
| 2385 | + margin-bottom: 0.4rem; | |
| 2386 | + color: var(--fg-muted); | |
| 2387 | + font-size: 0.86rem; | |
| 2388 | +} | |
| 2389 | +.shithub-actions-run-head h1 { | |
| 2390 | + display: flex; | |
| 2391 | + align-items: center; | |
| 2392 | + gap: 0.45rem; | |
| 2393 | + margin: 0; | |
| 2394 | + font-size: 1.5rem; | |
| 2395 | + line-height: 1.25; | |
| 2396 | +} | |
| 2397 | +.shithub-actions-run-head h1 span:last-child { | |
| 2398 | + color: var(--fg-muted); | |
| 2399 | + font-weight: 400; | |
| 2400 | +} | |
| 2401 | +.shithub-actions-run-head p { | |
| 2402 | + margin: 0.35rem 0 0; | |
| 2403 | + color: var(--fg-muted); | |
| 2404 | +} | |
| 2405 | +.shithub-actions-run-head-actions { | |
| 2406 | + display: flex; | |
| 2407 | + align-items: center; | |
| 2408 | + gap: 0.5rem; | |
| 2409 | + flex-shrink: 0; | |
| 2410 | +} | |
| 2411 | +.shithub-actions-run-status { | |
| 2412 | + pointer-events: none; | |
| 2413 | +} | |
| 2414 | +.shithub-actions-run-layout { | |
| 2415 | + grid-template-columns: 14rem minmax(0, 1fr); | |
| 2416 | + padding: 1.5rem 0 0; | |
| 2417 | +} | |
| 2418 | +.shithub-actions-summary-strip { | |
| 2419 | + display: grid; | |
| 2420 | + grid-template-columns: minmax(0, 1.6fr) repeat(3, minmax(7rem, 1fr)); | |
| 2421 | + gap: 1rem; | |
| 2422 | + margin-bottom: 1rem; | |
| 2423 | + padding: 1rem; | |
| 2424 | + border: 1px solid var(--border-default); | |
| 2425 | + border-radius: 6px; | |
| 2426 | + background: var(--canvas-default); | |
| 2427 | +} | |
| 2428 | +.shithub-actions-summary-strip > div { | |
| 2429 | + display: flex; | |
| 2430 | + flex-direction: column; | |
| 2431 | + gap: 0.2rem; | |
| 2432 | +} | |
| 2433 | +.shithub-actions-summary-strip span { | |
| 2434 | + color: var(--fg-muted); | |
| 2435 | + font-size: 0.82rem; | |
| 2436 | +} | |
| 2437 | +.shithub-actions-workflow-card, | |
| 2438 | +.shithub-actions-annotations { | |
| 2439 | + border: 1px solid var(--border-default); | |
| 2440 | + border-radius: 6px; | |
| 2441 | + background: var(--canvas-default); | |
| 2442 | +} | |
| 2443 | +.shithub-actions-workflow-card > header, | |
| 2444 | +.shithub-actions-annotations > header { | |
| 2445 | + padding: 1rem; | |
| 2446 | + border-bottom: 1px solid var(--border-default); | |
| 2447 | +} | |
| 2448 | +.shithub-actions-workflow-card h2, | |
| 2449 | +.shithub-actions-annotations h2 { | |
| 2450 | + margin: 0; | |
| 2451 | + font-size: 1rem; | |
| 2452 | +} | |
| 2453 | +.shithub-actions-workflow-card p, | |
| 2454 | +.shithub-actions-annotations p { | |
| 2455 | + margin: 0.25rem 0 0; | |
| 2456 | + color: var(--fg-muted); | |
| 2457 | +} | |
| 2458 | +.shithub-actions-workflow-graph { | |
| 2459 | + position: relative; | |
| 2460 | + min-height: 13rem; | |
| 2461 | + padding: 2.5rem; | |
| 2462 | + background: var(--canvas-inset); | |
| 2463 | +} | |
| 2464 | +.shithub-actions-workflow-stage { | |
| 2465 | + display: flex; | |
| 2466 | + flex-direction: column; | |
| 2467 | + gap: 0.75rem; | |
| 2468 | + max-width: 20rem; | |
| 2469 | +} | |
| 2470 | +.shithub-actions-job-card { | |
| 2471 | + display: grid; | |
| 2472 | + grid-template-columns: 1rem minmax(0, 1fr) auto; | |
| 2473 | + align-items: center; | |
| 2474 | + gap: 0.55rem; | |
| 2475 | + padding: 0.7rem; | |
| 2476 | + border: 1px solid var(--border-default); | |
| 2477 | + border-radius: 6px; | |
| 2478 | + background: var(--canvas-default); | |
| 2479 | + color: var(--fg-default); | |
| 2480 | + text-decoration: none; | |
| 2481 | +} | |
| 2482 | +.shithub-actions-job-card:hover { text-decoration: none; } | |
| 2483 | +.shithub-actions-job-card strong { | |
| 2484 | + overflow: hidden; | |
| 2485 | + text-overflow: ellipsis; | |
| 2486 | + white-space: nowrap; | |
| 2487 | +} | |
| 2488 | +.shithub-actions-job-card span:last-child { | |
| 2489 | + color: var(--fg-muted); | |
| 2490 | + font-size: 0.82rem; | |
| 2491 | +} | |
| 2492 | +.shithub-actions-graph-controls { | |
| 2493 | + position: absolute; | |
| 2494 | + right: 0.75rem; | |
| 2495 | + bottom: 0.75rem; | |
| 2496 | + display: flex; | |
| 2497 | + gap: 0.35rem; | |
| 2498 | +} | |
| 2499 | +.shithub-actions-annotations { | |
| 2500 | + margin-top: 1rem; | |
| 2501 | +} | |
| 2502 | +.shithub-actions-annotation-list { | |
| 2503 | + display: flex; | |
| 2504 | + flex-direction: column; | |
| 2505 | +} | |
| 2506 | +.shithub-actions-annotation { | |
| 2507 | + display: grid; | |
| 2508 | + grid-template-columns: 1rem minmax(0, 1fr); | |
| 2509 | + gap: 0.6rem; | |
| 2510 | + padding: 0.9rem 1rem; | |
| 2511 | + border-top: 1px solid var(--border-default); | |
| 2512 | +} | |
| 2513 | +.shithub-actions-annotation > svg { | |
| 2514 | + margin-top: 0.15rem; | |
| 2515 | + color: #9a6700; | |
| 2516 | +} | |
| 2517 | +@media (max-width: 900px) { | |
| 2518 | + .shithub-actions-page, | |
| 2519 | + .shithub-actions-run-layout, | |
| 2520 | + .shithub-actions-filters, | |
| 2521 | + .shithub-actions-run-row, | |
| 2522 | + .shithub-actions-summary-strip { | |
| 2523 | + grid-template-columns: 1fr; | |
| 2524 | + } | |
| 2525 | + .shithub-actions-run-meta, | |
| 2526 | + .shithub-actions-run-head { | |
| 2527 | + justify-content: flex-start; | |
| 2528 | + } | |
| 2529 | + .shithub-actions-run-head { | |
| 2530 | + flex-direction: column; | |
| 2531 | + } | |
| 2532 | +} | |
| 2161 | 2533 | .shithub-tree-panel { |
| 2162 | 2534 | border: 1px solid var(--border-default); |
| 2163 | 2535 | border-radius: 6px; |
internal/web/templates/repo/action_run.htmladded@@ -0,0 +1,118 @@ | ||
| 1 | +{{ define "page" -}} | |
| 2 | +{{ template "repo-header" . }} | |
| 3 | +<section class="shithub-actions-run-page"> | |
| 4 | + <header class="shithub-actions-run-head"> | |
| 5 | + <div> | |
| 6 | + <a href="/{{ .Owner }}/{{ .Repo.Name }}/actions" class="shithub-actions-back">← {{ .Run.AppSlug }}</a> | |
| 7 | + <h1> | |
| 8 | + <span class="shithub-actions-state shithub-actions-state-{{ .Run.StateClass }}">{{ octicon .Run.StateIcon }}</span> | |
| 9 | + {{ .Run.Title }} <span>#{{ .Run.ID }}</span> | |
| 10 | + </h1> | |
| 11 | + <p> | |
| 12 | + {{ if .Run.PullNumber }} | |
| 13 | + {{ if .Run.PullAuthorUsername }}<a href="/{{ .Run.PullAuthorUsername }}">{{ .Run.PullAuthorUsername }}</a>{{ end }} | |
| 14 | + opened <a href="/{{ .Owner }}/{{ .Repo.Name }}/pulls/{{ .Run.PullNumber }}">#{{ .Run.PullNumber }}</a> | |
| 15 | + {{ if .Run.HeadRef }}from <a class="shithub-branch-name" href="/{{ .Owner }}/{{ .Repo.Name }}/tree/{{ .Run.HeadRef }}">{{ .Run.HeadRef }}</a>{{ end }} | |
| 16 | + {{ if .Run.BaseRef }}into <a class="shithub-branch-name" href="/{{ .Owner }}/{{ .Repo.Name }}/tree/{{ .Run.BaseRef }}">{{ .Run.BaseRef }}</a>{{ end }} | |
| 17 | + {{ else }} | |
| 18 | + Check suite for <a href="/{{ .Owner }}/{{ .Repo.Name }}/commit/{{ .Run.HeadSha }}"><code>{{ .Run.HeadShaShort }}</code></a> | |
| 19 | + {{ end }} | |
| 20 | + </p> | |
| 21 | + </div> | |
| 22 | + <div class="shithub-actions-run-head-actions"> | |
| 23 | + <span class="shithub-button shithub-actions-run-status shithub-actions-state-{{ .Run.StateClass }}">{{ octicon .Run.StateIcon }} {{ .Run.StateText }}</span> | |
| 24 | + <a class="shithub-button" href="/{{ .Owner }}/{{ .Repo.Name }}/tree/{{ if .Run.HeadRef }}{{ .Run.HeadRef }}{{ else }}{{ .Run.HeadSha }}{{ end }}">{{ octicon "code" }} Code</a> | |
| 25 | + </div> | |
| 26 | + </header> | |
| 27 | + | |
| 28 | + <div class="shithub-actions-run-layout"> | |
| 29 | + <aside class="shithub-actions-sidebar shithub-actions-run-sidebar" aria-label="Run navigation"> | |
| 30 | + <a href="#summary" class="shithub-actions-nav-item is-active" aria-current="page">{{ octicon "home" }} <span>Summary</span></a> | |
| 31 | + <div class="shithub-actions-sidebar-section"> | |
| 32 | + <h2>All jobs</h2> | |
| 33 | + {{ range .Run.Runs }} | |
| 34 | + <a href="#job-{{ .ID }}" class="shithub-actions-nav-item"> | |
| 35 | + <span class="shithub-actions-state shithub-actions-state-{{ .StateClass }}">{{ octicon .StateIcon }}</span> | |
| 36 | + <span>{{ .Name }}</span> | |
| 37 | + </a> | |
| 38 | + {{ end }} | |
| 39 | + </div> | |
| 40 | + <div class="shithub-actions-sidebar-section"> | |
| 41 | + <h2>Run details</h2> | |
| 42 | + <span class="shithub-actions-nav-item">{{ octicon "pulse" }} <span>Usage</span></span> | |
| 43 | + <span class="shithub-actions-nav-item">{{ octicon "file" }} <span>Workflow file</span></span> | |
| 44 | + </div> | |
| 45 | + </aside> | |
| 46 | + | |
| 47 | + <div class="shithub-actions-run-main"> | |
| 48 | + <section id="summary" class="shithub-actions-summary-strip"> | |
| 49 | + <div> | |
| 50 | + <span>Triggered via</span> | |
| 51 | + <strong>{{ if .Run.PullNumber }}pull request{{ else }}checks API{{ end }}</strong> | |
| 52 | + </div> | |
| 53 | + <div> | |
| 54 | + <span>Status</span> | |
| 55 | + <strong class="shithub-actions-state-{{ .Run.StateClass }}">{{ .Run.StateText }}</strong> | |
| 56 | + </div> | |
| 57 | + <div> | |
| 58 | + <span>Total duration</span> | |
| 59 | + <strong>{{ .Run.Duration }}</strong> | |
| 60 | + </div> | |
| 61 | + <div> | |
| 62 | + <span>Artifacts</span> | |
| 63 | + <strong>—</strong> | |
| 64 | + </div> | |
| 65 | + </section> | |
| 66 | + | |
| 67 | + <section class="shithub-actions-workflow-card"> | |
| 68 | + <header> | |
| 69 | + <div> | |
| 70 | + <h2><a href="/{{ .Owner }}/{{ .Repo.Name }}/actions">{{ .Run.AppSlug }}.yml</a></h2> | |
| 71 | + <p>on: {{ if .Run.PullNumber }}pull_request{{ else }}check_run{{ end }}</p> | |
| 72 | + </div> | |
| 73 | + </header> | |
| 74 | + <div class="shithub-actions-workflow-graph"> | |
| 75 | + <div class="shithub-actions-workflow-stage"> | |
| 76 | + {{ range .Run.Runs }} | |
| 77 | + <a id="job-{{ .ID }}" href="{{ if .DetailsURL }}{{ .DetailsURL }}{{ else }}#job-{{ .ID }}{{ end }}" class="shithub-actions-job-card"> | |
| 78 | + <span class="shithub-actions-state shithub-actions-state-{{ .StateClass }}">{{ octicon .StateIcon }}</span> | |
| 79 | + <strong>{{ .Name }}</strong> | |
| 80 | + <span>{{ .Duration }}</span> | |
| 81 | + </a> | |
| 82 | + {{ end }} | |
| 83 | + </div> | |
| 84 | + <div class="shithub-actions-graph-controls" aria-hidden="true"> | |
| 85 | + <button type="button" class="shithub-icon-button">{{ octicon "screen-full" }}</button> | |
| 86 | + <button type="button" class="shithub-icon-button">{{ octicon "dash" }}</button> | |
| 87 | + <button type="button" class="shithub-icon-button">{{ octicon "plus" }}</button> | |
| 88 | + </div> | |
| 89 | + </div> | |
| 90 | + </section> | |
| 91 | + | |
| 92 | + <section class="shithub-actions-annotations"> | |
| 93 | + <header> | |
| 94 | + <h2>Annotations</h2> | |
| 95 | + <p>{{ .Run.AnnotationCount }} warning{{ if ne .Run.AnnotationCount 1 }}s{{ end }}</p> | |
| 96 | + </header> | |
| 97 | + {{ if .Run.AnnotationCount }} | |
| 98 | + <div class="shithub-actions-annotation-list"> | |
| 99 | + {{ range .Run.Runs }} | |
| 100 | + {{ if .SummaryHTML }} | |
| 101 | + <article class="shithub-actions-annotation"> | |
| 102 | + {{ octicon "alert" }} | |
| 103 | + <div> | |
| 104 | + <strong>{{ .Name }}</strong> | |
| 105 | + <div class="markdown-body">{{ .SummaryHTML }}</div> | |
| 106 | + </div> | |
| 107 | + </article> | |
| 108 | + {{ end }} | |
| 109 | + {{ end }} | |
| 110 | + </div> | |
| 111 | + {{ else }} | |
| 112 | + <p class="shithub-muted">No annotations were reported for this run.</p> | |
| 113 | + {{ end }} | |
| 114 | + </section> | |
| 115 | + </div> | |
| 116 | + </div> | |
| 117 | +</section> | |
| 118 | +{{- end }} | |
internal/web/templates/repo/actions.htmladded@@ -0,0 +1,83 @@ | ||
| 1 | +{{ define "page" -}} | |
| 2 | +{{ template "repo-header" . }} | |
| 3 | +<section class="shithub-actions-page"> | |
| 4 | + <aside class="shithub-actions-sidebar" aria-label="Actions navigation"> | |
| 5 | + <a href="/{{ .Owner }}/{{ .Repo.Name }}/actions" class="shithub-actions-nav-item is-active" aria-current="page"> | |
| 6 | + {{ octicon "play" }} <span>All workflows</span> | |
| 7 | + <span class="shithub-actions-nav-count">{{ .RunCount }}</span> | |
| 8 | + </a> | |
| 9 | + <div class="shithub-actions-sidebar-section"> | |
| 10 | + <h2>Workflows</h2> | |
| 11 | + {{ if .Workflows }} | |
| 12 | + {{ range .Workflows }} | |
| 13 | + <a href="/{{ $.Owner }}/{{ $.Repo.Name }}/actions?workflow={{ .Name }}" class="shithub-actions-nav-item"> | |
| 14 | + {{ octicon "workflow" }} <span>{{ .Name }}</span> | |
| 15 | + <span class="shithub-actions-nav-count">{{ .Count }}</span> | |
| 16 | + </a> | |
| 17 | + {{ end }} | |
| 18 | + {{ else }} | |
| 19 | + <p>No workflows have run yet.</p> | |
| 20 | + {{ end }} | |
| 21 | + </div> | |
| 22 | + </aside> | |
| 23 | + | |
| 24 | + <div class="shithub-actions-main"> | |
| 25 | + <header class="shithub-actions-head"> | |
| 26 | + <div> | |
| 27 | + <h1>All workflows</h1> | |
| 28 | + <p>Showing runs from check suites reported for this repository.</p> | |
| 29 | + </div> | |
| 30 | + <a class="shithub-button" href="/{{ .Owner }}/{{ .Repo.Name }}/settings/branches">{{ octicon "gear" }} Status checks</a> | |
| 31 | + </header> | |
| 32 | + | |
| 33 | + <div class="shithub-actions-filters" aria-label="Workflow run filters"> | |
| 34 | + <div class="shithub-actions-search"> | |
| 35 | + {{ octicon "search" }} | |
| 36 | + <input type="search" placeholder="Filter workflow runs" aria-label="Filter workflow runs" disabled> | |
| 37 | + </div> | |
| 38 | + <span class="shithub-actions-filter-button">Event {{ octicon "triangle-down" }}</span> | |
| 39 | + <span class="shithub-actions-filter-button">Status {{ octicon "triangle-down" }}</span> | |
| 40 | + <span class="shithub-actions-filter-button">Branch {{ octicon "triangle-down" }}</span> | |
| 41 | + </div> | |
| 42 | + | |
| 43 | + {{ if .Suites }} | |
| 44 | + <div class="shithub-actions-runs" aria-label="Workflow runs"> | |
| 45 | + {{ range .Suites }} | |
| 46 | + <article class="shithub-actions-run-row"> | |
| 47 | + <div class="shithub-actions-run-primary"> | |
| 48 | + <a href="/{{ $.Owner }}/{{ $.Repo.Name }}/actions/runs/{{ .ID }}" class="shithub-actions-run-title" aria-label="{{ .StateText }}: {{ .Title }}"> | |
| 49 | + <span class="shithub-actions-state shithub-actions-state-{{ .StateClass }}">{{ octicon .StateIcon }}</span> | |
| 50 | + <span>{{ .Title }}</span> | |
| 51 | + </a> | |
| 52 | + <p> | |
| 53 | + <strong>{{ .AppSlug }}</strong> | |
| 54 | + #{{ .ID }}: | |
| 55 | + {{ if .PullNumber }} | |
| 56 | + Pull request <a href="/{{ $.Owner }}/{{ $.Repo.Name }}/pulls/{{ .PullNumber }}">#{{ .PullNumber }}</a> | |
| 57 | + {{ if .PullAuthorUsername }}opened by <a href="/{{ .PullAuthorUsername }}">{{ .PullAuthorUsername }}</a>{{ end }} | |
| 58 | + {{ else }} | |
| 59 | + check suite for <a href="/{{ $.Owner }}/{{ $.Repo.Name }}/commit/{{ .HeadSha }}"><code>{{ .HeadShaShort }}</code></a> | |
| 60 | + {{ end }} | |
| 61 | + </p> | |
| 62 | + </div> | |
| 63 | + <div class="shithub-actions-run-branch"> | |
| 64 | + {{ if .HeadRef }}<a class="shithub-branch-name" href="/{{ $.Owner }}/{{ $.Repo.Name }}/tree/{{ .HeadRef }}">{{ .HeadRef }}</a>{{ else }}<code>{{ .HeadShaShort }}</code>{{ end }} | |
| 65 | + </div> | |
| 66 | + <div class="shithub-actions-run-meta"> | |
| 67 | + <span>{{ octicon "calendar" }} <time datetime="{{ .UpdatedAt.Format "2006-01-02T15:04:05Z" }}">{{ relativeTime .UpdatedAt }}</time></span> | |
| 68 | + <span>{{ octicon "stopwatch" }} {{ .Duration }}</span> | |
| 69 | + <button type="button" class="shithub-icon-button" aria-label="Show run options">{{ octicon "kebab-horizontal" }}</button> | |
| 70 | + </div> | |
| 71 | + </article> | |
| 72 | + {{ end }} | |
| 73 | + </div> | |
| 74 | + {{ else }} | |
| 75 | + <section class="shithub-actions-empty"> | |
| 76 | + <div class="shithub-actions-empty-icon">{{ octicon "play" }}</div> | |
| 77 | + <h2>There are no workflow runs yet</h2> | |
| 78 | + <p>Check runs posted via the shithub checks API will appear here and on pull requests.</p> | |
| 79 | + </section> | |
| 80 | + {{ end }} | |
| 81 | + </div> | |
| 82 | +</section> | |
| 83 | +{{- end }} | |