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
index dab4fc8..f987d88 100644
--- a/app/controllers/blobs_controller.rb
+++ b/app/controllers/blobs_controller.rb
@@ -17,6 +17,7 @@ class BlobsController < ApplicationController
@raw_content = content
@filename = File.basename(@file_path)
@extension = File.extname(@filename).delete_prefix(".")
+ @commit_count = GitRepositoryService.commit_count(@repository.disk_path, @branch)
@is_binary = content.encoding != Encoding::UTF_8 || !content.valid_encoding? || binary_content?(content)
@file_size = content.bytesize
app/controllers/commits_controller.rb
+1
index e3e80fc..509b43e 100644
--- a/app/controllers/commits_controller.rb
+++ b/app/controllers/commits_controller.rb
@@ -19,6 +19,7 @@ class CommitsController < ApplicationController
limit: @per_page, offset: @offset
)
@total = GitRepositoryService.commit_count(@repository.disk_path, @branch)
+ @commit_count = @total
@total_pages = (@total.to_f / @per_page).ceil
end
app/controllers/repositories_controller.rb
+36
-3
index 21538be..3f31efa 100644
--- a/app/controllers/repositories_controller.rb
+++ b/app/controllers/repositories_controller.rb
@@ -1,8 +1,8 @@
class RepositoriesController < ApplicationController
before_action :require_sign_in!, only: [ :new, :create ]
- before_action :load_owner, only: [ :show, :tree ]
- before_action :load_repository, only: [ :show, :tree ]
- before_action :ensure_can_read!, only: [ :show, :tree ]
+ before_action :load_owner, only: [ :show, :tree, :cicd ]
+ before_action :load_repository, only: [ :show, :tree, :cicd ]
+ before_action :ensure_can_read!, only: [ :show, :tree, :cicd ]
def new
@repository = Repository.new
@@ -64,6 +64,7 @@ class RepositoriesController < ApplicationController
@tree = GitRepositoryService.list_tree(@repository.disk_path, branch, tree_path)
@current_path = tree_path
@path_parts = tree_path.to_s.split("/").reject(&:blank?)
+ @commit_count = GitRepositoryService.commit_count(@repository.disk_path, branch)
# If it's actually a file, redirect to the blob view
if @tree.empty?
@@ -71,6 +72,38 @@ class RepositoriesController < ApplicationController
end
end
+ def cicd
+ @branch = @repository.default_branch
+ @commit_count = if @repository.initialized? && GitRepositoryService.branch_exists?(@repository.disk_path, @branch)
+ GitRepositoryService.commit_count(@repository.disk_path, @branch)
+ else
+ 0
+ end
+ @pipeline_summary = {
+ success_rate: "98.4%",
+ avg_duration: "6m 12s",
+ weekly_runs: 27,
+ last_deploy: "18 minutes ago"
+ }
+ @pipeline_runs = [
+ { name: "Deploy production", ref: "main", sha: "8f3c1b2", status: "passed", duration: "5m 48s", started_at: "18 minutes ago", actor: "sigit" },
+ { name: "PR validation", ref: "feature/repo-tabs", sha: "7bd91aa", status: "running", duration: "2m 14s", started_at: "now", actor: "naufal" },
+ { name: "Nightly security scan", ref: "main", sha: "4c72e16", status: "passed", duration: "9m 03s", started_at: "7 hours ago", actor: "automation" },
+ { name: "Deploy staging", ref: "release/apr", sha: "cb194ef", status: "failed", duration: "4m 51s", started_at: "yesterday", actor: "sigit" }
+ ]
+ @environments = [
+ { name: "Production", url: "app.sigitsi.com", status: "healthy", version: "v1.24.0", updated_at: "18 minutes ago" },
+ { name: "Staging", url: "staging.sigitsi.com", status: "degraded", version: "v1.25.0-rc.2", updated_at: "42 minutes ago" },
+ { name: "Preview", url: "preview-482.sigitsi.com", status: "building", version: "feature/repo-tabs", updated_at: "2 minutes ago" }
+ ]
+ @checks = [
+ { name: "RSpec", status: "passed", detail: "248 examples in 32s" },
+ { name: "Brakeman", status: "passed", detail: "0 warnings" },
+ { name: "Lint", status: "running", detail: "RuboCop on 84 files" },
+ { name: "Deploy smoke test", status: "failed", detail: "Health endpoint timed out once" }
+ ]
+ end
+
private
def repository_params
app/views/blobs/show.html.erb
+23
-1
index 55921e8..c449572 100644
--- a/app/views/blobs/show.html.erb
+++ b/app/views/blobs/show.html.erb
@@ -18,7 +18,29 @@
<span class="font-medium text-gray-100"><%= @filename %></span>
</nav>
<div class="flex items-center gap-0 -mb-px">
- <div class="tab-item active">Code</div>
+ <%= link_to repository_path(@owner.username, @repository.name), class: "tab-item active flex items-center gap-1.5" do %>
+ <svg class="w-4 h-4" viewBox="0 0 16 16" fill="currentColor">
+ <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"/>
+ </svg>
+ Code
+ <% end %>
+ <%= link_to repository_commits_path(@owner.username, @repository.name, @branch),
+ class: "tab-item flex items-center gap-1.5" do %>
+ <svg class="w-4 h-4" viewBox="0 0 16 16" fill="currentColor">
+ <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"/>
+ </svg>
+ Commits
+ <% end %>
+ <%= link_to repository_cicd_path(@owner.username, @repository.name),
+ class: "tab-item flex items-center gap-1.5" do %>
+ <svg class="w-4 h-4" viewBox="0 0 16 16" fill="currentColor">
+ <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"/>
+ </svg>
+ CI/CD
+ <% if @commit_count&.> 0 %>
+ <span class="badge-gray text-xs"><%= number_with_delimiter(@commit_count) %></span>
+ <% end %>
+ <% end %>
</div>
</div>
</div>
app/views/commits/index.html.erb
+32
-2
index 19e3e60..b89b98e 100644
--- a/app/views/commits/index.html.erb
+++ b/app/views/commits/index.html.erb
@@ -3,16 +3,46 @@
<div class="border-b border-surface-600 bg-surface-800">
<div class="max-w-6xl mx-auto px-4 sm:px-6 pt-6 pb-0">
<div class="flex items-center gap-2 text-sm mb-4">
+ <svg class="w-4 h-4 text-gray-500" viewBox="0 0 16 16" fill="currentColor">
+ <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"/>
+ </svg>
<%= link_to "@#{@owner.username}", user_profile_path(@owner.username), class: "text-brand-500 hover:underline font-medium" %>
<span class="text-gray-500">/</span>
<%= link_to @repository.name, repository_path(@owner.username, @repository.name), class: "text-brand-500 hover:underline font-semibold" %>
+ <% if @repository.is_private %>
+ <span class="badge-gray ml-1">Private</span>
+ <% end %>
</div>
+
+ <% if @repository.description.present? %>
+ <p class="text-sm text-gray-400 mb-4"><%= @repository.description %></p>
+ <% end %>
+
<div class="flex items-center gap-0 -mb-px">
- <%= link_to repository_path(@owner.username, @repository.name), class: "tab-item flex items-center gap-1.5" do %>Code<% end %>
+ <%= link_to repository_path(@owner.username, @repository.name),
+ class: "tab-item flex items-center gap-1.5" do %>
+ <svg class="w-4 h-4" viewBox="0 0 16 16" fill="currentColor">
+ <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"/>
+ </svg>
+ Code
+ <% end %>
<div class="tab-item active flex items-center gap-1.5">
+ <svg class="w-4 h-4" viewBox="0 0 16 16" fill="currentColor">
+ <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"/>
+ </svg>
Commits
- <span class="badge-gray"><%= number_with_delimiter(@total) %></span>
+ <span class="badge-gray text-xs"><%= number_with_delimiter(@total) %></span>
</div>
+ <%= link_to repository_cicd_path(@owner.username, @repository.name),
+ class: "tab-item flex items-center gap-1.5" do %>
+ <svg class="w-4 h-4" viewBox="0 0 16 16" fill="currentColor">
+ <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"/>
+ </svg>
+ CI/CD
+ <% if @commit_count&.> 0 %>
+ <span class="badge-gray text-xs"><%= number_with_delimiter(@commit_count) %></span>
+ <% end %>
+ <% end %>
</div>
</div>
</div>
app/views/repositories/cicd.html.erb
+185
new file mode 100644
index 0000000..24fde7c
--- /dev/null
+++ b/app/views/repositories/cicd.html.erb
@@ -0,0 +1,185 @@
+<% content_for :title, "CI/CD · #{@repository.full_name}" %>
+
+<div class="border-b border-surface-600 bg-surface-800">
+ <div class="max-w-6xl mx-auto px-4 sm:px-6 pt-6 pb-0">
+ <div class="flex items-center gap-2 text-sm mb-4">
+ <svg class="w-4 h-4 text-gray-500" viewBox="0 0 16 16" fill="currentColor">
+ <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"/>
+ </svg>
+ <%= link_to "@#{@owner.username}", user_profile_path(@owner.username),
+ class: "text-brand-500 hover:underline font-medium" %>
+ <span class="text-gray-500">/</span>
+ <%= link_to @repository.name, repository_path(@owner.username, @repository.name),
+ class: "text-brand-500 hover:underline font-semibold" %>
+ <% if @repository.is_private %>
+ <span class="badge-gray ml-1">Private</span>
+ <% end %>
+ </div>
+
+ <% if @repository.description.present? %>
+ <p class="text-sm text-gray-400 mb-4"><%= @repository.description %></p>
+ <% end %>
+
+ <div class="flex items-center gap-0 -mb-px">
+ <%= link_to repository_path(@owner.username, @repository.name),
+ class: "tab-item flex items-center gap-1.5" do %>
+ <svg class="w-4 h-4" viewBox="0 0 16 16" fill="currentColor">
+ <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"/>
+ </svg>
+ Code
+ <% end %>
+ <% if @repository.initialized? %>
+ <%= link_to repository_commits_path(@owner.username, @repository.name, @repository.default_branch),
+ class: "tab-item flex items-center gap-1.5" do %>
+ <svg class="w-4 h-4" viewBox="0 0 16 16" fill="currentColor">
+ <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"/>
+ </svg>
+ Commits
+ <% if @commit_count&.> 0 %>
+ <span class="badge-gray text-xs"><%= number_with_delimiter(@commit_count) %></span>
+ <% end %>
+ <% end %>
+ <% end %>
+ <div class="tab-item active flex items-center gap-1.5">
+ <svg class="w-4 h-4" viewBox="0 0 16 16" fill="currentColor">
+ <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"/>
+ </svg>
+ CI/CD
+ <% if @commit_count&.> 0 %>
+ <span class="badge-gray text-xs"><%= number_with_delimiter(@commit_count) %></span>
+ <% end %>
+ </div>
+ </div>
+ </div>
+</div>
+
+<div class="max-w-6xl mx-auto px-4 sm:px-6 py-6">
+ <div class="grid gap-6 lg:grid-cols-[1.35fr_0.95fr]">
+ <div class="space-y-6">
+ <div class="grid gap-4 sm:grid-cols-2 xl:grid-cols-4">
+ <div class="card p-5">
+ <p class="text-xs uppercase tracking-[0.18em] text-gray-500">Success Rate</p>
+ <p class="mt-3 text-2xl font-semibold text-gray-100"><%= @pipeline_summary[:success_rate] %></p>
+ <p class="mt-1 text-xs text-emerald-400">+1.2% in the last 7 days</p>
+ </div>
+ <div class="card p-5">
+ <p class="text-xs uppercase tracking-[0.18em] text-gray-500">Avg Duration</p>
+ <p class="mt-3 text-2xl font-semibold text-gray-100"><%= @pipeline_summary[:avg_duration] %></p>
+ <p class="mt-1 text-xs text-gray-400">From build to deploy</p>
+ </div>
+ <div class="card p-5">
+ <p class="text-xs uppercase tracking-[0.18em] text-gray-500">Runs This Week</p>
+ <p class="mt-3 text-2xl font-semibold text-gray-100"><%= @pipeline_summary[:weekly_runs] %></p>
+ <p class="mt-1 text-xs text-gray-400">Across all branches</p>
+ </div>
+ <div class="card p-5">
+ <p class="text-xs uppercase tracking-[0.18em] text-gray-500">Last Deploy</p>
+ <p class="mt-3 text-2xl font-semibold text-gray-100"><%= @pipeline_summary[:last_deploy] %></p>
+ <p class="mt-1 text-xs text-brand-400">Production is live</p>
+ </div>
+ </div>
+
+ <div class="card overflow-hidden">
+ <div class="flex items-center justify-between gap-4 border-b border-surface-600 px-5 py-4 bg-surface-600">
+ <div>
+ <h2 class="text-sm font-semibold text-gray-100">Recent Pipeline Runs</h2>
+ <p class="mt-1 text-xs text-gray-400">Latest activity for <span class="text-gray-200"><%= @repository.full_name %></span></p>
+ </div>
+ <span class="badge-gray text-xs"><%= @branch %></span>
+ </div>
+
+ <div>
+ <% @pipeline_runs.each do |run| %>
+ <% status_classes = case run[:status]
+ when "passed" then "bg-emerald-500/12 text-emerald-300 ring-1 ring-inset ring-emerald-500/20"
+ when "running" then "bg-sky-500/12 text-sky-300 ring-1 ring-inset ring-sky-500/20"
+ else "bg-rose-500/12 text-rose-300 ring-1 ring-inset ring-rose-500/20"
+ end %>
+ <div class="flex items-center justify-between gap-4 border-b border-surface-600/80 px-5 py-4 last:border-b-0">
+ <div class="min-w-0">
+ <div class="flex items-center gap-3">
+ <p class="text-sm font-medium text-gray-100"><%= run[:name] %></p>
+ <span class="rounded-full px-2 py-0.5 text-[11px] font-medium <%= status_classes %>"><%= run[:status].capitalize %></span>
+ </div>
+ <p class="mt-1 text-xs text-gray-400">
+ <span class="font-mono text-brand-400"><%= run[:sha] %></span>
+ on <span class="text-gray-200"><%= run[:ref] %></span>
+ by <span class="text-gray-200"><%= run[:actor] %></span>
+ </p>
+ </div>
+ <div class="shrink-0 text-right text-xs text-gray-400">
+ <p><%= run[:duration] %></p>
+ <p class="mt-1 text-gray-500"><%= run[:started_at] %></p>
+ </div>
+ </div>
+ <% end %>
+ </div>
+ </div>
+ </div>
+
+ <div class="space-y-6">
+ <div class="card overflow-hidden">
+ <div class="border-b border-surface-600 px-5 py-4 bg-surface-600">
+ <h2 class="text-sm font-semibold text-gray-100">Environments</h2>
+ </div>
+ <div class="space-y-0">
+ <% @environments.each do |environment| %>
+ <% env_classes = case environment[:status]
+ when "healthy" then "bg-emerald-400"
+ when "building" then "bg-sky-400"
+ else "bg-amber-400"
+ end %>
+ <div class="border-b border-surface-600/80 px-5 py-4 last:border-b-0">
+ <div class="flex items-center justify-between gap-3">
+ <div class="flex items-center gap-3">
+ <span class="h-2.5 w-2.5 rounded-full <%= env_classes %>"></span>
+ <div>
+ <p class="text-sm font-medium text-gray-100"><%= environment[:name] %></p>
+ <p class="text-xs text-gray-400"><%= environment[:url] %></p>
+ </div>
+ </div>
+ <span class="badge-gray text-xs"><%= environment[:version] %></span>
+ </div>
+ <p class="mt-3 text-xs text-gray-500">Updated <%= environment[:updated_at] %></p>
+ </div>
+ <% end %>
+ </div>
+ </div>
+
+ <div class="card overflow-hidden">
+ <div class="border-b border-surface-600 px-5 py-4 bg-surface-600">
+ <h2 class="text-sm font-semibold text-gray-100">Checks</h2>
+ </div>
+ <div class="space-y-0">
+ <% @checks.each do |check| %>
+ <% icon = case check[:status]
+ when "passed" then "text-emerald-400"
+ when "running" then "text-sky-400"
+ else "text-rose-400"
+ end %>
+ <div class="flex items-start gap-3 border-b border-surface-600/80 px-5 py-4 last:border-b-0">
+ <svg class="w-4 h-4 mt-0.5 shrink-0 <%= icon %>" viewBox="0 0 16 16" fill="currentColor">
+ <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"/>
+ </svg>
+ <div class="min-w-0">
+ <div class="flex items-center gap-2">
+ <p class="text-sm font-medium text-gray-100"><%= check[:name] %></p>
+ <span class="text-xs text-gray-500"><%= check[:status] %></span>
+ </div>
+ <p class="mt-1 text-xs text-gray-400"><%= check[:detail] %></p>
+ </div>
+ </div>
+ <% end %>
+ </div>
+ </div>
+
+ <div class="card p-5">
+ <h2 class="text-sm font-semibold text-gray-100">Deployment Policy</h2>
+ <p class="mt-2 text-sm text-gray-400">
+ Pushes to <span class="font-mono text-gray-200">main</span> deploy automatically after all required checks pass.
+ Release branches require one approval before staging promotion.
+ </p>
+ </div>
+ </div>
+ </div>
+</div>
app/views/repositories/show.html.erb
+10
index 18fd2b5..28e8340 100644
--- a/app/views/repositories/show.html.erb
+++ b/app/views/repositories/show.html.erb
@@ -42,6 +42,16 @@
<% end %>
<% end %>
<% end %>
+ <%= link_to repository_cicd_path(@owner.username, @repository.name),
+ class: "tab-item flex items-center gap-1.5" do %>
+ <svg class="w-4 h-4" viewBox="0 0 16 16" fill="currentColor">
+ <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"/>
+ </svg>
+ CI/CD
+ <% if @commit_count&.> 0 %>
+ <span class="badge-gray text-xs"><%= number_with_delimiter(@commit_count) %></span>
+ <% end %>
+ <% end %>
</div>
</div>
</div>
app/views/repositories/tree.html.erb
+17
index b34aa63..5f81fd8 100644
--- a/app/views/repositories/tree.html.erb
+++ b/app/views/repositories/tree.html.erb
@@ -36,6 +36,23 @@
</svg>
Code
<% end %>
+ <%= link_to repository_commits_path(@owner.username, @repository.name, @branch),
+ class: "tab-item flex items-center gap-1.5" do %>
+ <svg class="w-4 h-4" viewBox="0 0 16 16" fill="currentColor">
+ <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"/>
+ </svg>
+ Commits
+ <% end %>
+ <%= link_to repository_cicd_path(@owner.username, @repository.name),
+ class: "tab-item flex items-center gap-1.5" do %>
+ <svg class="w-4 h-4" viewBox="0 0 16 16" fill="currentColor">
+ <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"/>
+ </svg>
+ CI/CD
+ <% if @commit_count&.> 0 %>
+ <span class="badge-gray text-xs"><%= number_with_delimiter(@commit_count) %></span>
+ <% end %>
+ <% end %>
</div>
</div>
</div>
config/routes.rb
+1
index c700995..1eda314 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -33,6 +33,7 @@ Rails.application.routes.draw do
# Repository routes (in order of specificity)
get "/:username/:repository/commits/:branch", to: "commits#index", as: :repository_commits,
constraints: { branch: /[^\/]+/, repository: /[^\/.][^\/]*/ }
+ get "/:username/:repository/ci-cd", to: "repositories#cicd", as: :repository_cicd
get "/:username/:repository/commit/:sha", to: "commits#show", as: :repository_commit
get "/:username/:repository/blob/:branch/*path", to: "blobs#show", as: :repository_blob, format: false
get "/:username/:repository/raw/:branch/*path", to: "blobs#raw", as: :repository_blob_raw, format: false