diff --git a/options/locale/locale_en-US.json b/options/locale/locale_en-US.json index 2ffa130751c..199637d76b6 100644 --- a/options/locale/locale_en-US.json +++ b/options/locale/locale_en-US.json @@ -2778,9 +2778,9 @@ "org.settings.labels_desc": "Add labels which can be used on issues for all repositories under this organization.", "org.members.membership_visibility": "Membership Visibility:", "org.members.public": "Visible", - "org.members.public_helper": "make hidden", + "org.members.public_helper": "Make hidden", "org.members.private": "Hidden", - "org.members.private_helper": "make visible", + "org.members.private_helper": "Make visible", "org.members.member_role": "Member Role:", "org.members.owner": "Owner", "org.members.member": "Member", @@ -2808,7 +2808,10 @@ "org.teams.no_desc": "This team has no description", "org.teams.settings": "Settings", "org.teams.owners_permission_desc": "Owners have full access to all repositories and have administrator access to the organization.", + "org.teams.owners_permission_suggestion": "You can create new teams for members to get fine-grained access control.", "org.teams.members": "Team Members", + "org.teams.manage_team_member": "Manage teams and members", + "org.teams.manage_team_member_prompt": "Members are managed through teams. Add users to a team to invite them to this organization.", "org.teams.update_settings": "Update Settings", "org.teams.delete_team": "Delete Team", "org.teams.add_team_member": "Add Team Member", diff --git a/routers/web/org/members.go b/routers/web/org/members.go index 6523bbf38d4..5d7a0a28cde 100644 --- a/routers/web/org/members.go +++ b/routers/web/org/members.go @@ -5,6 +5,7 @@ package org import ( + "errors" "net/http" "code.gitea.io/gitea/models/organization" @@ -12,6 +13,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/templates" + "code.gitea.io/gitea/modules/util" shared_user "code.gitea.io/gitea/routers/web/shared/user" "code.gitea.io/gitea/services/context" org_service "code.gitea.io/gitea/services/org" @@ -76,11 +78,11 @@ func Members(ctx *context.Context) { // MembersAction response for operation to a member of organization func MembersAction(ctx *context.Context) { member, err := user_model.GetUserByID(ctx, ctx.FormInt64("uid")) - if err != nil { - log.Error("GetUserByID: %v", err) - } - if member == nil { - ctx.Redirect(ctx.Org.OrgLink + "/members") + if errors.Is(err, util.ErrNotExist) { + ctx.HTTPError(http.StatusNotFound) + return + } else if err != nil { + ctx.ServerError("GetUserByID", err) return } @@ -105,40 +107,25 @@ func MembersAction(ctx *context.Context) { return } err = org_service.RemoveOrgUser(ctx, org, member) - if organization.IsErrLastOrgOwner(err) { - ctx.Flash.Error(ctx.Tr("form.last_org_owner")) - ctx.JSONRedirect(ctx.Org.OrgLink + "/members") - return - } case "leave": err = org_service.RemoveOrgUser(ctx, org, ctx.Doer) if err == nil { ctx.Flash.Success(ctx.Tr("form.organization_leave_success", org.DisplayName())) - ctx.JSON(http.StatusOK, map[string]any{ - "redirect": "", // keep the user stay on current page, in case they want to do other operations. - }) - } else if organization.IsErrLastOrgOwner(err) { - ctx.Flash.Error(ctx.Tr("form.last_org_owner")) - ctx.JSONRedirect(ctx.Org.OrgLink + "/members") - } else { - log.Error("RemoveOrgUser(%d,%d): %v", org.ID, ctx.Doer.ID, err) + ctx.JSONRedirect(setting.AppSubURL + "/") + return } + } + + if err == nil { + ctx.JSONOK() return } - if err != nil { - log.Error("Action(%s): %v", ctx.PathParam("action"), err) - ctx.JSON(http.StatusOK, map[string]any{ - "ok": false, - "err": err.Error(), - }) + if organization.IsErrLastOrgOwner(err) { + ctx.JSONError(ctx.Tr("form.last_org_owner")) return } - redirect := ctx.Org.OrgLink + "/members" - if ctx.PathParam("action") == "leave" { - redirect = setting.AppSubURL + "/" - } - - ctx.JSONRedirect(redirect) + log.Error("Action(%s): %v", ctx.PathParam("action"), err) + ctx.JSONError(err.Error()) // FIXME: legacy logic, errors are handled together, it's not right, need to distinguish between different errors } diff --git a/services/org/team.go b/services/org/team.go index f62e638514e..6c92ee4f447 100644 --- a/services/org/team.go +++ b/services/org/team.go @@ -5,6 +5,7 @@ package org import ( "context" + "errors" "fmt" "strings" @@ -306,19 +307,19 @@ func removeTeamMember(ctx context.Context, team *organization.Team, user *user_m return err } - // Delete access to team repositories. + // Delete access to team repositories. If any user or repo is missing, we can continue. for _, repo := range repos { - if err := access_model.RecalculateUserAccess(ctx, repo, user.ID); err != nil { + if err := access_model.RecalculateUserAccess(ctx, repo, user.ID); err != nil && !errors.Is(err, util.ErrNotExist) { return err } // Remove watches from now inaccessible - if err := repo_service.ReconsiderWatches(ctx, repo, user); err != nil { + if err := repo_service.ReconsiderWatches(ctx, repo, user); err != nil && !errors.Is(err, util.ErrNotExist) { return err } // Remove issue assignments from now inaccessible - if err := repo_service.ReconsiderRepoIssuesAssignee(ctx, repo, user); err != nil { + if err := repo_service.ReconsiderRepoIssuesAssignee(ctx, repo, user); err != nil && !errors.Is(err, util.ErrNotExist) { return err } } diff --git a/templates/org/member/members.tmpl b/templates/org/member/members.tmpl index 9b9f060770b..1cb50f785b3 100644 --- a/templates/org/member/members.tmpl +++ b/templates/org/member/members.tmpl @@ -4,6 +4,13 @@
{{template "base/alert" .}} + {{if .IsOrganizationOwner}} +
+
{{ctx.Locale.Tr "org.teams.manage_team_member_prompt"}}
+ {{ctx.Locale.Tr "org.teams.manage_team_member"}} +
+
+ {{end}}
{{range .Members}} {{$isPublic := index $.MembersIsPublicMember .ID}} @@ -15,27 +22,27 @@
{{template "shared/user/name" .}} {{if not $isPublic}} - {{ctx.Locale.Tr "org.members.private"}} + {{ctx.Locale.Tr "org.members.private"}} {{end}}
- {{if not $.PublicOnly}} -
+
+ {{if not $.PublicOnly}} +
{{ctx.Locale.Tr "org.members.member_role"}} {{if index $.MembersIsUserOrgOwner .ID}}{{svg "octicon-shield-lock"}} {{ctx.Locale.Tr "org.members.owner"}}{{else}}{{ctx.Locale.Tr "org.members.member"}}{{end}}
+ {{end}} {{if $.IsOrganizationOwner}} -
- {{ctx.Locale.Tr "admin.users.2fa"}} - - {{if index $.MembersTwoFaStatus .ID}} - {{svg "octicon-check"}} - {{else}} - {{svg "octicon-x"}} - {{end}} - +
+ {{ctx.Locale.Tr "admin.users.2fa"}}: + {{if index $.MembersTwoFaStatus .ID}} + {{svg "octicon-check"}} + {{else}} + {{svg "octicon-x"}} + {{end}}
{{end}} - {{end}} +
{{if or (eq $.SignedUser.ID .ID) $.IsOrganizationOwner}} @@ -46,45 +53,23 @@ {{end}} {{end}} {{if eq $.SignedUser.ID .ID}} -
- -
+ {{else if $.IsOrganizationOwner}} -
- -
+ {{end}}
{{end}}
- {{template "base/paginate" .}}
- - - {{template "base/footer" .}} diff --git a/templates/org/team/repositories.tmpl b/templates/org/team/repositories.tmpl index 7b576d85bcb..8e0a2885d76 100644 --- a/templates/org/team/repositories.tmpl +++ b/templates/org/team/repositories.tmpl @@ -7,9 +7,11 @@ {{template "org/team/sidebar" .}}
{{template "org/team/navbar" .}} + {{$hasTopAttachedSegment := false}} {{$canAddRemove := and $.IsOrganizationOwner (not $.Team.IncludesAllRepositories)}} {{if $canAddRemove}} -
+ {{$hasTopAttachedSegment = true}} +
{{end}} -
+ {{if $.Team.IncludesAllRepositories}} + {{$hasTopAttachedSegment = true}} +
{{ctx.Locale.Tr "org.teams.all_repositories"}}
+ {{end}} +
{{range $.TeamRepos}}
diff --git a/templates/org/team/sidebar.tmpl b/templates/org/team/sidebar.tmpl index 01047722cd8..8678ed74544 100644 --- a/templates/org/team/sidebar.tmpl +++ b/templates/org/team/sidebar.tmpl @@ -26,7 +26,8 @@
{{if eq .Team.LowerName "owners"}}
- {{ctx.Locale.Tr "org.teams.owners_permission_desc"}} +

{{ctx.Locale.Tr "org.teams.owners_permission_desc"}}

+

{{ctx.Locale.Tr "org.teams.owners_permission_suggestion"}}

{{else}}
diff --git a/templates/org/team/teams.tmpl b/templates/org/team/teams.tmpl index c27f34d09dd..5ea15068fe4 100644 --- a/templates/org/team/teams.tmpl +++ b/templates/org/team/teams.tmpl @@ -4,7 +4,8 @@
{{template "base/alert" .}} {{if .IsOrganizationOwner}} -
+
+
{{ctx.Locale.Tr "org.teams.manage_team_member_prompt"}}
{{svg "octicon-plus"}} {{ctx.Locale.Tr "org.create_new_team"}}
diff --git a/web_src/js/features/comp/ConfirmModal.ts b/web_src/js/features/comp/ConfirmModal.ts index 60dc13e27cb..3ead21b2ddc 100644 --- a/web_src/js/features/comp/ConfirmModal.ts +++ b/web_src/js/features/comp/ConfirmModal.ts @@ -2,6 +2,7 @@ import {svg} from '../../svg.ts'; import {html, htmlRaw} from '../../utils/html.ts'; import {createElementFromHTML} from '../../utils/dom.ts'; import {fomanticQuery} from '../../modules/fomantic/base.ts'; +import {hideToastsAll} from '../../modules/toast.ts'; const {i18n} = window.config; @@ -27,6 +28,9 @@ export function createConfirmModal({header = '', content = '', confirmButtonColo export function confirmModal(modal: HTMLElement | ConfirmModalOptions): Promise { if (!(modal instanceof HTMLElement)) modal = createConfirmModal(modal); + // hide existing toasts when we need to show a new modal, otherwise the toasts only interfere the UI + // it's fine to do so because the modal is triggered by user's explicit action, so the user should already have read the toast messages + hideToastsAll(); return new Promise((resolve) => { const $modal = fomanticQuery(modal); $modal.modal({