Skip to content

Commit e26f930

Browse files
committed
feat(front): project service accounts
1 parent a5e2f05 commit e26f930

File tree

11 files changed

+228
-125
lines changed

11 files changed

+228
-125
lines changed

front/assets/js/people/add_to_project.js

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export var AddToProject = {
2121
if(modal){
2222
var addPeopleToProjectBtn = document.getElementById("add_people_to_project");
2323
var addGroupsToProjectBtn = document.getElementById("add_group_to_project");
24+
var addServiceAccountsToProjectBtn = document.getElementById("add_service_accounts_to_project");
2425
var cancelModalBtn = document.getElementById("cancel_btn");
2526

2627
if(addPeopleToProjectBtn){
@@ -37,6 +38,13 @@ export var AddToProject = {
3738
}
3839
}
3940

41+
if(addServiceAccountsToProjectBtn){
42+
addServiceAccountsToProjectBtn.onclick = () => {
43+
modal.style.display = "block";
44+
this.initProjectNonMembersFilter("service_account")
45+
}
46+
}
47+
4048
cancelModalBtn.onclick = () => {
4149
this.selectedUserIds = []
4250
document.getElementById('users').innerHTML = ''
@@ -137,9 +145,11 @@ export var AddToProject = {
137145
let assets_path = document.querySelector("meta[name='assets-path']").getAttribute("content")
138146

139147
return `<span ${props}>
140-
${result.has_avatar
141-
? `<img src="${result.avatar}" class="ba b--black-50 br-100 mr2" width="32">`
142-
: `<img src="${assets_path}/images/org-${result.name.charAt(0).toLowerCase()}.svg" class="bg-washed-gray w2 h2 br-100 mr2 ba b--black-50"></div>`
148+
${result.subject_type === "service_account"
149+
? `<div class="dib w2 h2 br-100 mr2 ba b--black-50 tc bg-light-gray"><span class="material-symbols-outlined f6 gray" style="line-height: 2;">smart_toy</span></div>`
150+
: result.has_avatar
151+
? `<img src="${result.avatar}" class="ba b--black-50 br-100 mr2" width="32">`
152+
: `<img src="${assets_path}/images/org-${result.name.charAt(0).toLowerCase()}.svg" class="bg-washed-gray w2 h2 br-100 mr2 ba b--black-50"></div>`
143153
}
144154
<span>${escapeHtml(result.name)}</span>
145155
</span>`
@@ -180,9 +190,11 @@ export var AddToProject = {
180190
`
181191
<div id="${user.id}" class="flex items-center justify-between bg-white shadow-1 mv1 mh1 ph3 pv2 br3">
182192
<div class="flex items-center">
183-
${user.has_avatar
184-
? `<img src="${user.avatar}" class="w2 h2 br-100 mr2 ba b--black-50">`
185-
: `<img src="${assets_path}/images/org-${user.name.charAt(0).toLowerCase()}.svg" class="bg-washed-gray w2 h2 br-100 mr2 ba b--black-50">`
193+
${user.subject_type === "service_account"
194+
? `<div class="w2 h2 br-100 mr2 ba b--black-50 flex items-center justify-center bg-light-gray"><span class="material-symbols-outlined f6 gray">smart_toy</span></div>`
195+
: user.has_avatar
196+
? `<img src="${user.avatar}" class="w2 h2 br-100 mr2 ba b--black-50">`
197+
: `<img src="${assets_path}/images/org-${user.name.charAt(0).toLowerCase()}.svg" class="bg-washed-gray w2 h2 br-100 mr2 ba b--black-50">`
186198
}
187199
<div class="flex items-center">
188200
<div class="b">${escapeHtml(user.name)}</div>

front/lib/front/clients/service_account.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ defmodule Front.Clients.ServiceAccount do
33

44
alias InternalApi.ServiceAccount.{
55
CreateRequest,
6-
DeleteRequest,
6+
DestroyRequest,
77
DescribeRequest,
88
ListRequest,
99
RegenerateTokenRequest,
@@ -92,7 +92,7 @@ defmodule Front.Clients.ServiceAccount do
9292

9393
@impl Front.ServiceAccount.Behaviour
9494
def delete(service_account_id) do
95-
%DeleteRequest{
95+
%DestroyRequest{
9696
service_account_id: service_account_id
9797
}
9898
|> grpc_call(:delete)

front/lib/front/rbac/members.ex

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,13 @@ defmodule Front.RBAC.Members do
112112
alias InternalApi.RBAC.SubjectType, as: Type
113113

114114
member_type =
115-
if opts[:member_type] == "group", do: Type.value(:GROUP), else: Type.value(:USER)
115+
opts[:member_type]
116+
|> case do
117+
"group" -> Type.value(:GROUP)
118+
"service_account" -> Type.value(:SERVICE_ACCOUNT)
119+
# assume user if not specified
120+
_ -> Type.value(:USER)
121+
end
116122

117123
req =
118124
build_list_members_request(org_id, project_id,

front/lib/front/service_account.ex

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,32 @@ defmodule Front.ServiceAccount do
1313
page_token :: String.t() | nil
1414
) :: {:ok, {[Front.Models.ServiceAccount.t()], String.t() | nil}} | {:error, any}
1515

16+
@callback list_for_project(
17+
org_id :: String.t(),
18+
project_id :: String.t(),
19+
page_size :: integer(),
20+
page_token :: String.t() | nil
21+
) :: {:ok, {[Front.Models.ServiceAccount.t()], String.t() | nil}} | {:error, any}
22+
23+
@callback list_available_for_project(
24+
org_id :: String.t(),
25+
project_id :: String.t(),
26+
page_size :: integer(),
27+
page_token :: String.t() | nil
28+
) :: {:ok, {[Front.Models.ServiceAccount.t()], String.t() | nil}} | {:error, any}
29+
30+
@callback assign_to_project(
31+
service_account_id :: String.t(),
32+
project_id :: String.t(),
33+
creator_id :: String.t()
34+
) :: {:ok, Front.Models.ServiceAccount.t()} | {:error, any}
35+
36+
@callback unassign_from_project(
37+
service_account_id :: String.t(),
38+
project_id :: String.t(),
39+
creator_id :: String.t()
40+
) :: {:ok, Front.Models.ServiceAccount.t()} | {:error, any}
41+
1642
@callback describe(service_account_id :: String.t()) ::
1743
{:ok, Front.Models.ServiceAccount.t()} | {:error, any}
1844

front/lib/front_web/controllers/people_controller.ex

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,9 +356,18 @@ defmodule FrontWeb.PeopleController do
356356
member_type: "user"
357357
)
358358

359+
fetch_service_accounts =
360+
async_fetch_members(org_id, project_id,
361+
username: username,
362+
role_id: role_id,
363+
page_no: page_no,
364+
member_type: "service_account"
365+
)
366+
359367
conn = conn |> assign_permissions(project_id)
360368
{:ok, {:ok, {members, total_pages}}} = Async.await(fetch_members)
361369
{:ok, {:ok, {groups, _total_pages}}} = Async.await(fetch_groups)
370+
{:ok, {:ok, {service_accounts, _total_pages}}} = Async.await(fetch_service_accounts)
362371
{:ok, {:ok, all_roles}} = Async.await(fetch_roles)
363372

364373
conn
@@ -367,6 +376,7 @@ defmodule FrontWeb.PeopleController do
367376
|> render("members/members_list.html",
368377
members: members,
369378
groups: groups,
379+
service_accounts: service_accounts,
370380
roles: all_roles,
371381
groups: groups,
372382
org_scope?: project_id == "",
Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
11
<div class="f6 gray flex items-center">
2-
<%= if @member.github_login do %>
3-
<svg class="mh1" width="16" height="16" xmlns="http://www.w3.org/2000/svg">
4-
<path d="M8 0A8 8 0 005.47 15.59c.4.075.546-.172.546-.385 0-.19-.007-.693-.01-1.36-2.226.483-2.695-1.073-2.695-1.073-.364-.924-.889-1.17-.889-1.17-.726-.496.055-.486.055-.486.803.056 1.225.824 1.225.824.714 1.223 1.873.87 2.329.665.073-.517.28-.87.508-1.07-1.777-.201-3.644-.888-3.644-3.953 0-.873.311-1.588.823-2.147-.082-.202-.357-1.016.079-2.117 0 0 .671-.215 2.2.82A7.662 7.662 0 018 3.868a7.67 7.67 0 012.003.27c1.527-1.035 2.198-.82 2.198-.82.436 1.101.162 1.915.08 2.117.513.559.822 1.274.822 2.147 0 3.073-1.87 3.75-3.652 3.947.286.247.542.736.542 1.482 0 1.07-.01 1.932-.01 2.194 0 .215.145.463.55.385A8 8 0 008 0" fill-rule="evenodd"/>
5-
</svg>
6-
<a href="https://github.com/<%= @member.github_login%>/">@<%= @member.github_login%></a>
7-
<% end %>
8-
<%= if @member.bitbucket_login do %>
9-
<svg class="mh1" height="16" width="16" xmlns="http://www.w3.org/2000/svg">
10-
<path d="M.75 1.032a.5.5 0 00-.5.58l2.123 12.886a.68.68 0 00.664.567H13.22a.5.5 0 00.5-.42l2.122-13.03a.5.5 0 00-.5-.58zm8.938 9.313h-3.25l-.88-4.598h4.917z" fill-rule="evenodd"/>
11-
</svg>
12-
<a href="https://bitbucket.org/<%= @member.bitbucket_login%>/">@<%= @member.bitbucket_login%></a>
13-
<% end %>
14-
<%= if @member.gitlab_login do %>
15-
<svg width="16px" height="16px" xmlns="http://www.w3.org/2000/svg" fill="#000000" viewBox="0 0 32 32">
16-
<path d="M 8.382813 1.972656 L 4.078125 13.453125 L 3.835938 14.105469 L 1.796875 19.542969 L 16 29.875 L 30.203125 19.542969 L 28.164063 14.105469 L 23.613281 1.972656 L 19.882813 13.453125 L 12.117188 13.453125 Z M 8.25 8.027344 L 10.015625 13.453125 L 6.214844 13.453125 Z M 23.75 8.027344 L 25.785156 13.453125 L 21.984375 13.453125 Z M 5.464844 15.453125 L 10.664063 15.453125 L 14.09375 26.015625 L 4.203125 18.820313 Z M 12.765625 15.453125 L 19.234375 15.453125 L 16 25.402344 Z M 21.335938 15.453125 L 26.53125 15.453125 L 27.796875 18.820313 L 17.902344 26.015625 Z" />
17-
</svg>
18-
<a href="https://gitlab.com/<%= @member.gitlab_login%>/">@<%= @member.gitlab_login%></a>
2+
<%= if @member.subject_type == "service_account" do %>
3+
<span class="mh1"><%= @member.name %></span>
4+
<% else %>
5+
<%= if @member.github_login do %>
6+
<svg class="mh1" width="16" height="16" xmlns="http://www.w3.org/2000/svg">
7+
<path d="M8 0A8 8 0 005.47 15.59c.4.075.546-.172.546-.385 0-.19-.007-.693-.01-1.36-2.226.483-2.695-1.073-2.695-1.073-.364-.924-.889-1.17-.889-1.17-.726-.496.055-.486.055-.486.803.056 1.225.824 1.225.824.714 1.223 1.873.87 2.329.665.073-.517.28-.87.508-1.07-1.777-.201-3.644-.888-3.644-3.953 0-.873.311-1.588.823-2.147-.082-.202-.357-1.016.079-2.117 0 0 .671-.215 2.2.82A7.662 7.662 0 018 3.868a7.67 7.67 0 012.003.27c1.527-1.035 2.198-.82 2.198-.82.436 1.101.162 1.915.08 2.117.513.559.822 1.274.822 2.147 0 3.073-1.87 3.75-3.652 3.947.286.247.542.736.542 1.482 0 1.07-.01 1.932-.01 2.194 0 .215.145.463.55.385A8 8 0 008 0" fill-rule="evenodd"/>
8+
</svg>
9+
<a href="https://github.com/<%= @member.github_login%>/">@<%= @member.github_login%></a>
10+
<% end %>
11+
<%= if @member.bitbucket_login do %>
12+
<svg class="mh1" height="16" width="16" xmlns="http://www.w3.org/2000/svg">
13+
<path d="M.75 1.032a.5.5 0 00-.5.58l2.123 12.886a.68.68 0 00.664.567H13.22a.5.5 0 00.5-.42l2.122-13.03a.5.5 0 00-.5-.58zm8.938 9.313h-3.25l-.88-4.598h4.917z" fill-rule="evenodd"/>
14+
</svg>
15+
<a href="https://bitbucket.org/<%= @member.bitbucket_login%>/">@<%= @member.bitbucket_login%></a>
16+
<% end %>
17+
<%= if @member.gitlab_login do %>
18+
<svg width="16px" height="16px" xmlns="http://www.w3.org/2000/svg" fill="#000000" viewBox="0 0 32 32">
19+
<path d="M 8.382813 1.972656 L 4.078125 13.453125 L 3.835938 14.105469 L 1.796875 19.542969 L 16 29.875 L 30.203125 19.542969 L 28.164063 14.105469 L 23.613281 1.972656 L 19.882813 13.453125 L 12.117188 13.453125 Z M 8.25 8.027344 L 10.015625 13.453125 L 6.214844 13.453125 Z M 23.75 8.027344 L 25.785156 13.453125 L 21.984375 13.453125 Z M 5.464844 15.453125 L 10.664063 15.453125 L 14.09375 26.015625 L 4.203125 18.820313 Z M 12.765625 15.453125 L 19.234375 15.453125 L 16 25.402344 Z M 21.335938 15.453125 L 26.53125 15.453125 L 27.796875 18.820313 L 17.902344 26.015625 Z" />
20+
</svg>
21+
<a href="https://gitlab.com/<%= @member.gitlab_login%>/">@<%= @member.gitlab_login%></a>
22+
<% end %>
1923
<% end %>
2024
</div>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<%= if show_people_management_buttons?(@conn, @org_scope?, @permissions) do %>
2+
<div>
3+
<div class="flex-m">
4+
<div>
5+
<button id="add_service_accounts_to_project" class="btn btn-primary flex items-center">
6+
<span class="material-symbols-outlined mr2">smart_toy</span>
7+
Add service accounts
8+
</button>
9+
</div>
10+
</div>
11+
</div>
12+
<% end %>

front/lib/front_web/templates/people/members/_member.html.eex

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,17 @@
66
<div>
77
<div class="flex items-center">
88
<span class="ml1 b">
9-
<%= if !@is_group? do %>
10-
<%= link @member.name, to: people_path(@conn, :show, @member.id), class: "link black" %>
11-
<% else %>
12-
<%= if @permissions["organization.people.manage"] do %>
13-
<span class="black pointer" style="cursor: pointer;" name="modify-group-btn" group_id="<%= @member.id %>"><%= @member.name %></span>
14-
<% else %>
9+
<%= cond do %>
10+
<% Map.get(assigns, :is_service_account?) -> %>
1511
<span class="black"><%= @member.name %></span>
16-
<% end %>
12+
<% @is_group? -> %>
13+
<%= if @permissions["organization.people.manage"] do %>
14+
<span class="black pointer" style="cursor: pointer;" name="modify-group-btn" group_id="<%= @member.id %>"><%= @member.name %></span>
15+
<% else %>
16+
<span class="black"><%= @member.name %></span>
17+
<% end %>
18+
<% true -> %>
19+
<%= link @member.name, to: people_path(@conn, :show, @member.id), class: "link black" %>
1720
<% end %>
1821
</span>
1922
<%= render "members/__role_labels.html", role_bindings: @member.subject_role_bindings %>
@@ -34,13 +37,18 @@
3437
<%= render "members/__change_role_btn.html", member: @member, roles: @roles, permissions: @permissions %>
3538
<% end %>
3639
<% end %>
37-
<%= if @is_group? and @org_scope? do %>
38-
<%= render "members/__modify_group_button.html", group: @member, permissions: @permissions %>
39-
<%= render "members/__delete_group_button.html", group: @member, conn: @conn, permissions: @permissions %>
40-
<% else %>
41-
<%= if @org_scope? || !Front.ce_roles?() || "Member" in member_role_names do %>
42-
<%= render "members/__remove_member_btn.html", member: @member %>
43-
<% end %>
40+
<%= cond do %>
41+
<% @is_group? and @org_scope? -> %>
42+
<%= render "members/__modify_group_button.html", group: @member, permissions: @permissions %>
43+
<%= render "members/__delete_group_button.html", group: @member, conn: @conn, permissions: @permissions %>
44+
<% Map.get(assigns, :is_service_account?) -> %>
45+
<%= if @org_scope? || !Front.ce_roles?() || "Member" in member_role_names do %>
46+
<%= render "members/__remove_member_btn.html", member: @member %>
47+
<% end %>
48+
<% true -> %>
49+
<%= if @org_scope? || !Front.ce_roles?() || "Member" in member_role_names do %>
50+
<%= render "members/__remove_member_btn.html", member: @member %>
51+
<% end %>
4452
<% end %>
4553

4654
</div>

front/lib/front_web/templates/people/members/members_list.html.eex

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,27 @@
4343
<% end) %>
4444
</div>
4545
</div>
46+
47+
<%= if FeatureProvider.feature_enabled?(:service_accounts, param: @conn.assigns[:organization_id]) &&
48+
Map.has_key?(assigns, :service_accounts) do %>
49+
<div class="bb b--black-075 w-100-l mt4 br3 shadow-3 bg-white">
50+
<div class="flex items-center justify-between pa3 bb bw1 b--black-075 br3 br--top ">
51+
<div>
52+
<div class="flex items-center">
53+
<span class="material-symbols-outlined pr2">smart_toy</span>
54+
<div class="b">Service Accounts</div>
55+
</div>
56+
</div>
57+
<%= if !@org_scope? && show_people_management_buttons?(@conn, @org_scope?, @permissions) do %>
58+
<%= render "members/_add_service_account_button.html", conn: @conn, org_scope?: @org_scope?, permissions: @permissions %>
59+
<% end %>
60+
</div>
61+
62+
<div id="service_accounts">
63+
<%= @service_accounts |> Enum.map(fn (service_account) -> %>
64+
<%= render "members/_member.html", is_group?: false, is_service_account?: true, member: service_account, roles: @roles, org_scope?: @org_scope?, conn: @conn, permissions: @permissions %>
65+
<% end) %>
66+
</div>
67+
</div>
68+
<% end %>
4669
<% end %>

front/lib/front_web/views/people_view.ex

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -218,18 +218,30 @@ defmodule FrontWeb.PeopleView do
218218
defp map_role_to_colour(_), do: "cyan"
219219

220220
def construct_member_avatar(member) do
221-
avatar_url =
222-
if member.has_avatar do
223-
member.avatar
224-
else
225-
first_letter = member.name |> String.first() |> String.downcase()
226-
"#{assets_path()}/images/org-#{first_letter}.svg"
227-
end
228-
229-
"""
230-
<img src="#{avatar_url}" class="w2 h2 br-100 mr2 ba b--black-50">
231-
"""
232-
|> raw()
221+
# Check if this is a service account
222+
is_service_account = member.subject_type == "service_account"
223+
224+
if is_service_account do
225+
"""
226+
<div class="w2 h2 br-100 mr2 ba b--black-50 flex items-center justify-center bg-light-gray">
227+
<span class="material-symbols-outlined f6 gray">smart_toy</span>
228+
</div>
229+
"""
230+
|> raw()
231+
else
232+
avatar_url =
233+
if member.has_avatar do
234+
member.avatar
235+
else
236+
first_letter = member.name |> String.first() |> String.downcase()
237+
"#{assets_path()}/images/org-#{first_letter}.svg"
238+
end
239+
240+
"""
241+
<img src="#{avatar_url}" class="w2 h2 br-100 mr2 ba b--black-50">
242+
"""
243+
|> raw()
244+
end
233245
end
234246

235247
def build_roles(member, roles) do

0 commit comments

Comments
 (0)