Add CI/CD tab and pipeline summary page to repository UI
Seto Elkahfi committed
Apr 15, 2026 at 22:38 UTC
9e42334b54ba445c5186c626633b06b5ebfe6793
9 files changed
+306
-6
app/controllers/blobs_controller.rb
+1
@@ -17,6 +17,7 @@ class BlobsController < ApplicationController
17
@raw_content = content
18
@filename = File.basename(@file_path)
19
@extension = File.extname(@filename).delete_prefix(".")
20
+ @commit_count = GitRepositoryService.commit_count(@repository.disk_path, @branch)
21
@is_binary = content.encoding != Encoding::UTF_8 || !content.valid_encoding? || binary_content?(content)
22
@file_size = content.bytesize
23
app/controllers/commits_controller.rb
+1
@@ -19,6 +19,7 @@ class CommitsController < ApplicationController
19
limit: @per_page, offset: @offset
20
)
21
@total = GitRepositoryService.commit_count(@repository.disk_path, @branch)
22
+ @commit_count = @total
23
@total_pages = (@total.to_f / @per_page).ceil
24
end
25
app/controllers/repositories_controller.rb
+36
-3
@@ -1,8 +1,8 @@
1
class RepositoriesController < ApplicationController
2
before_action :require_sign_in!, only: [ :new, :create ]
3
- before_action :load_owner, only: [ :show, :tree ]
4
- before_action :load_repository, only: [ :show, :tree ]
5
- before_action :ensure_can_read!, only: [ :show, :tree ]
3
+ before_action :load_owner, only: [ :show, :tree, :cicd ]
4
+ before_action :load_repository, only: [ :show, :tree, :cicd ]
5
+ before_action :ensure_can_read!, only: [ :show, :tree, :cicd ]
6
7
def new
8
@repository = Repository.new
@@ -64,6 +64,7 @@ class RepositoriesController < ApplicationController
64
@tree = GitRepositoryService.list_tree(@repository.disk_path, branch, tree_path)
65
@current_path = tree_path
66
@path_parts = tree_path.to_s.split("/").reject(&:blank?)
67
+ @commit_count = GitRepositoryService.commit_count(@repository.disk_path, branch)
68
69
# If it's actually a file, redirect to the blob view
70
if @tree.empty?
@@ -71,6 +72,38 @@ class RepositoriesController < ApplicationController
72
end
73
end
74
75
+ def cicd
76
+ @branch = @repository.default_branch
77
+ @commit_count = if @repository.initialized? && GitRepositoryService.branch_exists?(@repository.disk_path, @branch)
78
+ GitRepositoryService.commit_count(@repository.disk_path, @branch)
79
+ else
80
+ 0
81
+ end
82
+ @pipeline_summary = {
83
+ success_rate: "98.4%",
84
+ avg_duration: "6m 12s",
85
+ weekly_runs: 27,
86
+ last_deploy: "18 minutes ago"
87
+ }
88
+ @pipeline_runs = [
89
+ { name: "Deploy production", ref: "main", sha: "8f3c1b2", status: "passed", duration: "5m 48s", started_at: "18 minutes ago", actor: "sigit" },
90
+ { name: "PR validation", ref: "feature/repo-tabs", sha: "7bd91aa", status: "running", duration: "2m 14s", started_at: "now", actor: "naufal" },
91
+ { name: "Nightly security scan", ref: "main", sha: "4c72e16", status: "passed", duration: "9m 03s", started_at: "7 hours ago", actor: "automation" },
92
+ { name: "Deploy staging", ref: "release/apr", sha: "cb194ef", status: "failed", duration: "4m 51s", started_at: "yesterday", actor: "sigit" }
93
+ ]
94
+ @environments = [
95
+ { name: "Production", url: "app.sigitsi.com", status: "healthy", version: "v1.24.0", updated_at: "18 minutes ago" },
96
+ { name: "Staging", url: "staging.sigitsi.com", status: "degraded", version: "v1.25.0-rc.2", updated_at: "42 minutes ago" },
97
+ { name: "Preview", url: "preview-482.sigitsi.com", status: "building", version: "feature/repo-tabs", updated_at: "2 minutes ago" }
98
+ ]
99
+ @checks = [
100
+ { name: "RSpec", status: "passed", detail: "248 examples in 32s" },
101
+ { name: "Brakeman", status: "passed", detail: "0 warnings" },
102
+ { name: "Lint", status: "running", detail: "RuboCop on 84 files" },
103
+ { name: "Deploy smoke test", status: "failed", detail: "Health endpoint timed out once" }
104
+ ]
105
+ end
106
+
107
private
108
109
def repository_params
app/views/blobs/show.html.erb
+23
-1
@@ -18,7 +18,29 @@
18
<span class="font-medium text-gray-100"><%= @filename %></span>
19
</nav>
20
<div class="flex items-center gap-0 -mb-px">
21
- <div class="tab-item active">Code</div>
21
+ <%= link_to repository_path(@owner.username, @repository.name), class: "tab-item active flex items-center gap-1.5" do %>
22
+ <svg class="w-4 h-4" viewBox="0 0 16 16" fill="currentColor">
23
+ <path d="M2 2.5A2.5 2.5 0 0 1 4.5 0h8.75a.75.75 0 0 1 .75.75v12.5a.75.75 0 0 1-.75.75h-2.5a.75.75 0 0 1 0-1.5h1.75v-2h-8a1 1 0 0 0-.714 1.7.75.75 0 1 1-1.072 1.05A2.495 2.495 0 0 1 2 11.5Zm10.5-1h-8a1 1 0 0 0-1 1v6.708A2.486 2.486 0 0 1 4.5 9h8Z"/>
24
+ </svg>
25
+ Code
26
+ <% end %>
27
+ <%= link_to repository_commits_path(@owner.username, @repository.name, @branch),
28
+ class: "tab-item flex items-center gap-1.5" do %>
29
+ <svg class="w-4 h-4" viewBox="0 0 16 16" fill="currentColor">
30
+ <path d="M11.93 8.5a4.002 4.002 0 0 1-7.86 0H.75a.75.75 0 0 1 0-1.5h3.32a4.002 4.002 0 0 1 7.86 0h3.32a.75.75 0 0 1 0 1.5Zm-1.43-.75a2.5 2.5 0 1 0-5 0 2.5 2.5 0 0 0 5 0Z"/>
31
+ </svg>
32
+ Commits
33
+ <% end %>
34
+ <%= link_to repository_cicd_path(@owner.username, @repository.name),
35
+ class: "tab-item flex items-center gap-1.5" do %>
36
+ <svg class="w-4 h-4" viewBox="0 0 16 16" fill="currentColor">
37
+ <path d="M4.75 4.5C2.679 4.5 1 5.958 1 7.75S2.679 11 4.75 11c1.028 0 1.948-.36 2.75-1.242l.652-.716.696.672C10.023 10.849 10.956 11 11.25 11 13.321 11 15 9.542 15 7.75S13.321 4.5 11.25 4.5c-1.028 0-1.948.36-2.75 1.242l-.652.716-.696-.672C5.977 4.651 5.044 4.5 4.75 4.5Zm0 1.5c.483 0 .943.152 1.36.556l.562.541-.562.618C5.693 8.119 5.233 8.5 4.75 8.5c-1.241 0-2.25-.672-2.25-1.5S3.509 6 4.75 6Zm6.5 0c1.241 0 2.25.672 2.25 1.5S12.491 9 11.25 9c-.483 0-.943-.152-1.36-.556l-.562-.541.562-.618c.417-.404.877-.785 1.36-.785Z"/>
38
+ </svg>
39
+ CI/CD
40
+ <% if @commit_count&.> 0 %>
41
+ <span class="badge-gray text-xs"><%= number_with_delimiter(@commit_count) %></span>
42
+ <% end %>
43
+ <% end %>
44
</div>
45
</div>
46
</div>
app/views/commits/index.html.erb
+32
-2
@@ -3,16 +3,46 @@
3
<div class="border-b border-surface-600 bg-surface-800">
4
<div class="max-w-6xl mx-auto px-4 sm:px-6 pt-6 pb-0">
5
<div class="flex items-center gap-2 text-sm mb-4">
6
+ <svg class="w-4 h-4 text-gray-500" viewBox="0 0 16 16" fill="currentColor">
7
+ <path d="M2 2.5A2.5 2.5 0 0 1 4.5 0h8.75a.75.75 0 0 1 .75.75v12.5a.75.75 0 0 1-.75.75h-2.5a.75.75 0 0 1 0-1.5h1.75v-2h-8a1 1 0 0 0-.714 1.7.75.75 0 1 1-1.072 1.05A2.495 2.495 0 0 1 2 11.5Zm10.5-1h-8a1 1 0 0 0-1 1v6.708A2.486 2.486 0 0 1 4.5 9h8Z"/>
8
+ </svg>
9
<%= link_to "@#{@owner.username}", user_profile_path(@owner.username), class: "text-brand-500 hover:underline font-medium" %>
10
<span class="text-gray-500">/</span>
11
<%= link_to @repository.name, repository_path(@owner.username, @repository.name), class: "text-brand-500 hover:underline font-semibold" %>
12
+ <% if @repository.is_private %>
13
+ <span class="badge-gray ml-1">Private</span>
14
+ <% end %>
15
</div>
16
+
17
+ <% if @repository.description.present? %>
18
+ <p class="text-sm text-gray-400 mb-4"><%= @repository.description %></p>
19
+ <% end %>
20
+
21
<div class="flex items-center gap-0 -mb-px">
11
- <%= link_to repository_path(@owner.username, @repository.name), class: "tab-item flex items-center gap-1.5" do %>Code<% end %>
22
+ <%= link_to repository_path(@owner.username, @repository.name),
23
+ class: "tab-item flex items-center gap-1.5" do %>
24
+ <svg class="w-4 h-4" viewBox="0 0 16 16" fill="currentColor">
25
+ <path d="M2 2.5A2.5 2.5 0 0 1 4.5 0h8.75a.75.75 0 0 1 .75.75v12.5a.75.75 0 0 1-.75.75h-2.5a.75.75 0 0 1 0-1.5h1.75v-2h-8a1 1 0 0 0-.714 1.7.75.75 0 1 1-1.072 1.05A2.495 2.495 0 0 1 2 11.5Zm10.5-1h-8a1 1 0 0 0-1 1v6.708A2.486 2.486 0 0 1 4.5 9h8Z"/>
26
+ </svg>
27
+ Code
28
+ <% end %>
29
<div class="tab-item active flex items-center gap-1.5">
30
+ <svg class="w-4 h-4" viewBox="0 0 16 16" fill="currentColor">
31
+ <path d="M11.93 8.5a4.002 4.002 0 0 1-7.86 0H.75a.75.75 0 0 1 0-1.5h3.32a4.002 4.002 0 0 1 7.86 0h3.32a.75.75 0 0 1 0 1.5Zm-1.43-.75a2.5 2.5 0 1 0-5 0 2.5 2.5 0 0 0 5 0Z"/>
32
+ </svg>
33
Commits
14
- <span class="badge-gray"><%= number_with_delimiter(@total) %></span>
34
+ <span class="badge-gray text-xs"><%= number_with_delimiter(@total) %></span>
35
</div>
36
+ <%= link_to repository_cicd_path(@owner.username, @repository.name),
37
+ class: "tab-item flex items-center gap-1.5" do %>
38
+ <svg class="w-4 h-4" viewBox="0 0 16 16" fill="currentColor">
39
+ <path d="M4.75 4.5C2.679 4.5 1 5.958 1 7.75S2.679 11 4.75 11c1.028 0 1.948-.36 2.75-1.242l.652-.716.696.672C10.023 10.849 10.956 11 11.25 11 13.321 11 15 9.542 15 7.75S13.321 4.5 11.25 4.5c-1.028 0-1.948.36-2.75 1.242l-.652.716-.696-.672C5.977 4.651 5.044 4.5 4.75 4.5Zm0 1.5c.483 0 .943.152 1.36.556l.562.541-.562.618C5.693 8.119 5.233 8.5 4.75 8.5c-1.241 0-2.25-.672-2.25-1.5S3.509 6 4.75 6Zm6.5 0c1.241 0 2.25.672 2.25 1.5S12.491 9 11.25 9c-.483 0-.943-.152-1.36-.556l-.562-.541.562-.618c.417-.404.877-.785 1.36-.785Z"/>
40
+ </svg>
41
+ CI/CD
42
+ <% if @commit_count&.> 0 %>
43
+ <span class="badge-gray text-xs"><%= number_with_delimiter(@commit_count) %></span>
44
+ <% end %>
45
+ <% end %>
46
</div>
47
</div>
48
</div>
app/views/repositories/cicd.html.erb
new
+185
@@ -0,0 +1,185 @@
1
+<% content_for :title, "CI/CD · #{@repository.full_name}" %>
2
+
3
+<div class="border-b border-surface-600 bg-surface-800">
4
+ <div class="max-w-6xl mx-auto px-4 sm:px-6 pt-6 pb-0">
5
+ <div class="flex items-center gap-2 text-sm mb-4">
6
+ <svg class="w-4 h-4 text-gray-500" viewBox="0 0 16 16" fill="currentColor">
7
+ <path d="M2 2.5A2.5 2.5 0 0 1 4.5 0h8.75a.75.75 0 0 1 .75.75v12.5a.75.75 0 0 1-.75.75h-2.5a.75.75 0 0 1 0-1.5h1.75v-2h-8a1 1 0 0 0-.714 1.7.75.75 0 1 1-1.072 1.05A2.495 2.495 0 0 1 2 11.5Zm10.5-1h-8a1 1 0 0 0-1 1v6.708A2.486 2.486 0 0 1 4.5 9h8Z"/>
8
+ </svg>
9
+ <%= link_to "@#{@owner.username}", user_profile_path(@owner.username),
10
+ class: "text-brand-500 hover:underline font-medium" %>
11
+ <span class="text-gray-500">/</span>
12
+ <%= link_to @repository.name, repository_path(@owner.username, @repository.name),
13
+ class: "text-brand-500 hover:underline font-semibold" %>
14
+ <% if @repository.is_private %>
15
+ <span class="badge-gray ml-1">Private</span>
16
+ <% end %>
17
+ </div>
18
+
19
+ <% if @repository.description.present? %>
20
+ <p class="text-sm text-gray-400 mb-4"><%= @repository.description %></p>
21
+ <% end %>
22
+
23
+ <div class="flex items-center gap-0 -mb-px">
24
+ <%= link_to repository_path(@owner.username, @repository.name),
25
+ class: "tab-item flex items-center gap-1.5" do %>
26
+ <svg class="w-4 h-4" viewBox="0 0 16 16" fill="currentColor">
27
+ <path d="M2 2.5A2.5 2.5 0 0 1 4.5 0h8.75a.75.75 0 0 1 .75.75v12.5a.75.75 0 0 1-.75.75h-2.5a.75.75 0 0 1 0-1.5h1.75v-2h-8a1 1 0 0 0-.714 1.7.75.75 0 1 1-1.072 1.05A2.495 2.495 0 0 1 2 11.5Zm10.5-1h-8a1 1 0 0 0-1 1v6.708A2.486 2.486 0 0 1 4.5 9h8Z"/>
28
+ </svg>
29
+ Code
30
+ <% end %>
31
+ <% if @repository.initialized? %>
32
+ <%= link_to repository_commits_path(@owner.username, @repository.name, @repository.default_branch),
33
+ class: "tab-item flex items-center gap-1.5" do %>
34
+ <svg class="w-4 h-4" viewBox="0 0 16 16" fill="currentColor">
35
+ <path d="M11.93 8.5a4.002 4.002 0 0 1-7.86 0H.75a.75.75 0 0 1 0-1.5h3.32a4.002 4.002 0 0 1 7.86 0h3.32a.75.75 0 0 1 0 1.5Zm-1.43-.75a2.5 2.5 0 1 0-5 0 2.5 2.5 0 0 0 5 0Z"/>
36
+ </svg>
37
+ Commits
38
+ <% if @commit_count&.> 0 %>
39
+ <span class="badge-gray text-xs"><%= number_with_delimiter(@commit_count) %></span>
40
+ <% end %>
41
+ <% end %>
42
+ <% end %>
43
+ <div class="tab-item active flex items-center gap-1.5">
44
+ <svg class="w-4 h-4" viewBox="0 0 16 16" fill="currentColor">
45
+ <path d="M4.75 4.5C2.679 4.5 1 5.958 1 7.75S2.679 11 4.75 11c1.028 0 1.948-.36 2.75-1.242l.652-.716.696.672C10.023 10.849 10.956 11 11.25 11 13.321 11 15 9.542 15 7.75S13.321 4.5 11.25 4.5c-1.028 0-1.948.36-2.75 1.242l-.652.716-.696-.672C5.977 4.651 5.044 4.5 4.75 4.5Zm0 1.5c.483 0 .943.152 1.36.556l.562.541-.562.618C5.693 8.119 5.233 8.5 4.75 8.5c-1.241 0-2.25-.672-2.25-1.5S3.509 6 4.75 6Zm6.5 0c1.241 0 2.25.672 2.25 1.5S12.491 9 11.25 9c-.483 0-.943-.152-1.36-.556l-.562-.541.562-.618c.417-.404.877-.785 1.36-.785Z"/>
46
+ </svg>
47
+ CI/CD
48
+ <% if @commit_count&.> 0 %>
49
+ <span class="badge-gray text-xs"><%= number_with_delimiter(@commit_count) %></span>
50
+ <% end %>
51
+ </div>
52
+ </div>
53
+ </div>
54
+</div>
55
+
56
+<div class="max-w-6xl mx-auto px-4 sm:px-6 py-6">
57
+ <div class="grid gap-6 lg:grid-cols-[1.35fr_0.95fr]">
58
+ <div class="space-y-6">
59
+ <div class="grid gap-4 sm:grid-cols-2 xl:grid-cols-4">
60
+ <div class="card p-5">
61
+ <p class="text-xs uppercase tracking-[0.18em] text-gray-500">Success Rate</p>
62
+ <p class="mt-3 text-2xl font-semibold text-gray-100"><%= @pipeline_summary[:success_rate] %></p>
63
+ <p class="mt-1 text-xs text-emerald-400">+1.2% in the last 7 days</p>
64
+ </div>
65
+ <div class="card p-5">
66
+ <p class="text-xs uppercase tracking-[0.18em] text-gray-500">Avg Duration</p>
67
+ <p class="mt-3 text-2xl font-semibold text-gray-100"><%= @pipeline_summary[:avg_duration] %></p>
68
+ <p class="mt-1 text-xs text-gray-400">From build to deploy</p>
69
+ </div>
70
+ <div class="card p-5">
71
+ <p class="text-xs uppercase tracking-[0.18em] text-gray-500">Runs This Week</p>
72
+ <p class="mt-3 text-2xl font-semibold text-gray-100"><%= @pipeline_summary[:weekly_runs] %></p>
73
+ <p class="mt-1 text-xs text-gray-400">Across all branches</p>
74
+ </div>
75
+ <div class="card p-5">
76
+ <p class="text-xs uppercase tracking-[0.18em] text-gray-500">Last Deploy</p>
77
+ <p class="mt-3 text-2xl font-semibold text-gray-100"><%= @pipeline_summary[:last_deploy] %></p>
78
+ <p class="mt-1 text-xs text-brand-400">Production is live</p>
79
+ </div>
80
+ </div>
81
+
82
+ <div class="card overflow-hidden">
83
+ <div class="flex items-center justify-between gap-4 border-b border-surface-600 px-5 py-4 bg-surface-600">
84
+ <div>
85
+ <h2 class="text-sm font-semibold text-gray-100">Recent Pipeline Runs</h2>
86
+ <p class="mt-1 text-xs text-gray-400">Latest activity for <span class="text-gray-200"><%= @repository.full_name %></span></p>
87
+ </div>
88
+ <span class="badge-gray text-xs"><%= @branch %></span>
89
+ </div>
90
+
91
+ <div>
92
+ <% @pipeline_runs.each do |run| %>
93
+ <% status_classes = case run[:status]
94
+ when "passed" then "bg-emerald-500/12 text-emerald-300 ring-1 ring-inset ring-emerald-500/20"
95
+ when "running" then "bg-sky-500/12 text-sky-300 ring-1 ring-inset ring-sky-500/20"
96
+ else "bg-rose-500/12 text-rose-300 ring-1 ring-inset ring-rose-500/20"
97
+ end %>
98
+ <div class="flex items-center justify-between gap-4 border-b border-surface-600/80 px-5 py-4 last:border-b-0">
99
+ <div class="min-w-0">
100
+ <div class="flex items-center gap-3">
101
+ <p class="text-sm font-medium text-gray-100"><%= run[:name] %></p>
102
+ <span class="rounded-full px-2 py-0.5 text-[11px] font-medium <%= status_classes %>"><%= run[:status].capitalize %></span>
103
+ </div>
104
+ <p class="mt-1 text-xs text-gray-400">
105
+ <span class="font-mono text-brand-400"><%= run[:sha] %></span>
106
+ on <span class="text-gray-200"><%= run[:ref] %></span>
107
+ by <span class="text-gray-200"><%= run[:actor] %></span>
108
+ </p>
109
+ </div>
110
+ <div class="shrink-0 text-right text-xs text-gray-400">
111
+ <p><%= run[:duration] %></p>
112
+ <p class="mt-1 text-gray-500"><%= run[:started_at] %></p>
113
+ </div>
114
+ </div>
115
+ <% end %>
116
+ </div>
117
+ </div>
118
+ </div>
119
+
120
+ <div class="space-y-6">
121
+ <div class="card overflow-hidden">
122
+ <div class="border-b border-surface-600 px-5 py-4 bg-surface-600">
123
+ <h2 class="text-sm font-semibold text-gray-100">Environments</h2>
124
+ </div>
125
+ <div class="space-y-0">
126
+ <% @environments.each do |environment| %>
127
+ <% env_classes = case environment[:status]
128
+ when "healthy" then "bg-emerald-400"
129
+ when "building" then "bg-sky-400"
130
+ else "bg-amber-400"
131
+ end %>
132
+ <div class="border-b border-surface-600/80 px-5 py-4 last:border-b-0">
133
+ <div class="flex items-center justify-between gap-3">
134
+ <div class="flex items-center gap-3">
135
+ <span class="h-2.5 w-2.5 rounded-full <%= env_classes %>"></span>
136
+ <div>
137
+ <p class="text-sm font-medium text-gray-100"><%= environment[:name] %></p>
138
+ <p class="text-xs text-gray-400"><%= environment[:url] %></p>
139
+ </div>
140
+ </div>
141
+ <span class="badge-gray text-xs"><%= environment[:version] %></span>
142
+ </div>
143
+ <p class="mt-3 text-xs text-gray-500">Updated <%= environment[:updated_at] %></p>
144
+ </div>
145
+ <% end %>
146
+ </div>
147
+ </div>
148
+
149
+ <div class="card overflow-hidden">
150
+ <div class="border-b border-surface-600 px-5 py-4 bg-surface-600">
151
+ <h2 class="text-sm font-semibold text-gray-100">Checks</h2>
152
+ </div>
153
+ <div class="space-y-0">
154
+ <% @checks.each do |check| %>
155
+ <% icon = case check[:status]
156
+ when "passed" then "text-emerald-400"
157
+ when "running" then "text-sky-400"
158
+ else "text-rose-400"
159
+ end %>
160
+ <div class="flex items-start gap-3 border-b border-surface-600/80 px-5 py-4 last:border-b-0">
161
+ <svg class="w-4 h-4 mt-0.5 shrink-0 <%= icon %>" viewBox="0 0 16 16" fill="currentColor">
162
+ <path d="M8 1.5a6.5 6.5 0 1 0 6.5 6.5A6.507 6.507 0 0 0 8 1.5Zm3.03 5.28-3.5 3.75a.75.75 0 0 1-1.08.02L4.72 8.81a.75.75 0 0 1 1.06-1.06l1.18 1.18 2.95-3.17a.75.75 0 1 1 1.12 1Z"/>
163
+ </svg>
164
+ <div class="min-w-0">
165
+ <div class="flex items-center gap-2">
166
+ <p class="text-sm font-medium text-gray-100"><%= check[:name] %></p>
167
+ <span class="text-xs text-gray-500"><%= check[:status] %></span>
168
+ </div>
169
+ <p class="mt-1 text-xs text-gray-400"><%= check[:detail] %></p>
170
+ </div>
171
+ </div>
172
+ <% end %>
173
+ </div>
174
+ </div>
175
+
176
+ <div class="card p-5">
177
+ <h2 class="text-sm font-semibold text-gray-100">Deployment Policy</h2>
178
+ <p class="mt-2 text-sm text-gray-400">
179
+ Pushes to <span class="font-mono text-gray-200">main</span> deploy automatically after all required checks pass.
180
+ Release branches require one approval before staging promotion.
181
+ </p>
182
+ </div>
183
+ </div>
184
+ </div>
185
+</div>
app/views/repositories/show.html.erb
+10
@@ -42,6 +42,16 @@
42
<% end %>
43
<% end %>
44
<% end %>
45
+ <%= link_to repository_cicd_path(@owner.username, @repository.name),
46
+ class: "tab-item flex items-center gap-1.5" do %>
47
+ <svg class="w-4 h-4" viewBox="0 0 16 16" fill="currentColor">
48
+ <path d="M4.75 4.5C2.679 4.5 1 5.958 1 7.75S2.679 11 4.75 11c1.028 0 1.948-.36 2.75-1.242l.652-.716.696.672C10.023 10.849 10.956 11 11.25 11 13.321 11 15 9.542 15 7.75S13.321 4.5 11.25 4.5c-1.028 0-1.948.36-2.75 1.242l-.652.716-.696-.672C5.977 4.651 5.044 4.5 4.75 4.5Zm0 1.5c.483 0 .943.152 1.36.556l.562.541-.562.618C5.693 8.119 5.233 8.5 4.75 8.5c-1.241 0-2.25-.672-2.25-1.5S3.509 6 4.75 6Zm6.5 0c1.241 0 2.25.672 2.25 1.5S12.491 9 11.25 9c-.483 0-.943-.152-1.36-.556l-.562-.541.562-.618c.417-.404.877-.785 1.36-.785Z"/>
49
+ </svg>
50
+ CI/CD
51
+ <% if @commit_count&.> 0 %>
52
+ <span class="badge-gray text-xs"><%= number_with_delimiter(@commit_count) %></span>
53
+ <% end %>
54
+ <% end %>
55
</div>
56
</div>
57
</div>
app/views/repositories/tree.html.erb
+17
@@ -36,6 +36,23 @@
36
</svg>
37
Code
38
<% end %>
39
+ <%= link_to repository_commits_path(@owner.username, @repository.name, @branch),
40
+ class: "tab-item flex items-center gap-1.5" do %>
41
+ <svg class="w-4 h-4" viewBox="0 0 16 16" fill="currentColor">
42
+ <path d="M11.93 8.5a4.002 4.002 0 0 1-7.86 0H.75a.75.75 0 0 1 0-1.5h3.32a4.002 4.002 0 0 1 7.86 0h3.32a.75.75 0 0 1 0 1.5Zm-1.43-.75a2.5 2.5 0 1 0-5 0 2.5 2.5 0 0 0 5 0Z"/>
43
+ </svg>
44
+ Commits
45
+ <% end %>
46
+ <%= link_to repository_cicd_path(@owner.username, @repository.name),
47
+ class: "tab-item flex items-center gap-1.5" do %>
48
+ <svg class="w-4 h-4" viewBox="0 0 16 16" fill="currentColor">
49
+ <path d="M4.75 4.5C2.679 4.5 1 5.958 1 7.75S2.679 11 4.75 11c1.028 0 1.948-.36 2.75-1.242l.652-.716.696.672C10.023 10.849 10.956 11 11.25 11 13.321 11 15 9.542 15 7.75S13.321 4.5 11.25 4.5c-1.028 0-1.948.36-2.75 1.242l-.652.716-.696-.672C5.977 4.651 5.044 4.5 4.75 4.5Zm0 1.5c.483 0 .943.152 1.36.556l.562.541-.562.618C5.693 8.119 5.233 8.5 4.75 8.5c-1.241 0-2.25-.672-2.25-1.5S3.509 6 4.75 6Zm6.5 0c1.241 0 2.25.672 2.25 1.5S12.491 9 11.25 9c-.483 0-.943-.152-1.36-.556l-.562-.541.562-.618c.417-.404.877-.785 1.36-.785Z"/>
50
+ </svg>
51
+ CI/CD
52
+ <% if @commit_count&.> 0 %>
53
+ <span class="badge-gray text-xs"><%= number_with_delimiter(@commit_count) %></span>
54
+ <% end %>
55
+ <% end %>
56
</div>
57
</div>
58
</div>
config/routes.rb
+1
@@ -33,6 +33,7 @@ Rails.application.routes.draw do
33
# Repository routes (in order of specificity)
34
get "/:username/:repository/commits/:branch", to: "commits#index", as: :repository_commits,
35
constraints: { branch: /[^\/]+/, repository: /[^\/.][^\/]*/ }
36
+ get "/:username/:repository/ci-cd", to: "repositories#cicd", as: :repository_cicd
37
get "/:username/:repository/commit/:sha", to: "commits#show", as: :repository_commit
38
get "/:username/:repository/blob/:branch/*path", to: "blobs#show", as: :repository_blob, format: false
39
get "/:username/:repository/raw/:branch/*path", to: "blobs#raw", as: :repository_blob_raw, format: false