From daffaafe7446500fc133d0046e223df4f1980553 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 17 Jun 2025 22:27:08 -0700 Subject: [PATCH 01/12] rework delete org and rename org --- options/locale/locale_en-US.ini | 21 ++++-- routers/web/org/setting.go | 108 +++++++++++++++------------- routers/web/web.go | 3 +- services/forms/org.go | 6 +- templates/org/settings/delete.tmpl | 35 --------- templates/org/settings/navbar.tmpl | 3 - templates/org/settings/options.tmpl | 103 +++++++++++++++++++++++--- 7 files changed, 177 insertions(+), 102 deletions(-) delete mode 100644 templates/org/settings/delete.tmpl diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 6d8aaef4cd85e..71d63df7d3014 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -2831,15 +2831,28 @@ settings.visibility.private_shortname = Private settings.update_settings = Update Settings settings.update_setting_success = Organization settings have been updated. -settings.change_orgname_prompt = Note: Changing the organization name will also change your organization's URL and free the old name. -settings.change_orgname_redirect_prompt = The old name will redirect until it is claimed. + +settings.rename = Rename Organization +settings.rename_desc = Changing the organization name will also change your organization's URL and free the old name. +settings.rename_success = Organization %s have been renamed to %s successfully. +settings.rename_no_change = Organization name is no change. +settings.rename_new_org_name = New Organization Name +settings.rename_failed = Rename Organization failed because of internal error +settings.rename_notices_1 = - This operation CANNOT be undone. +settings.rename_notices_2 = - The old name will redirect until it is claimed. + settings.update_avatar_success = The organization's avatar has been updated. settings.delete = Delete Organization settings.delete_account = Delete This Organization settings.delete_prompt = The organization will be permanently removed. This CANNOT be undone! +settings.name_confirm = Enter the organization name as confirmation: +settings.delete_notices_1 = - This operation CANNOT be undone. +settings.delete_notices_2 = - This operation will permanently delete all the repositories of %s including code, issues, comments, wiki data and collaborator settings. +settings.delete_notices_3 = - This operation will permanently delete all the packages of %s. +settings.delete_notices_4 = - This operation will permanently delete all the projects of %s. settings.confirm_delete_account = Confirm Deletion -settings.delete_org_title = Delete Organization -settings.delete_org_desc = This organization will be deleted permanently. Continue? +settings.delete_failed = Delete Organization failed because of internal error +settings.delete_successful = Organization %s has been deleted successfully. settings.hooks_desc = Add webhooks which will be triggered for all repositories under this organization. settings.labels_desc = Add labels which can be used on issues for all repositories under this organization. diff --git a/routers/web/org/setting.go b/routers/web/org/setting.go index 9dd0a98160e60..f1fbe96b8f93a 100644 --- a/routers/web/org/setting.go +++ b/routers/web/org/setting.go @@ -18,6 +18,7 @@ import ( repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/templates" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" shared_user "code.gitea.io/gitea/routers/web/shared/user" user_setting "code.gitea.io/gitea/routers/web/user/setting" @@ -31,8 +32,6 @@ import ( const ( // tplSettingsOptions template path for render settings tplSettingsOptions templates.TplName = "org/settings/options" - // tplSettingsDelete template path for render delete repository - tplSettingsDelete templates.TplName = "org/settings/delete" // tplSettingsHooks template path for render hook settings tplSettingsHooks templates.TplName = "org/settings/hooks" // tplSettingsLabels template path for render labels settings @@ -71,26 +70,6 @@ func SettingsPost(ctx *context.Context) { org := ctx.Org.Organization - if org.Name != form.Name { - if err := user_service.RenameUser(ctx, org.AsUser(), form.Name); err != nil { - if user_model.IsErrUserAlreadyExist(err) { - ctx.Data["Err_Name"] = true - ctx.RenderWithErr(ctx.Tr("form.username_been_taken"), tplSettingsOptions, &form) - } else if db.IsErrNameReserved(err) { - ctx.Data["Err_Name"] = true - ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(db.ErrNameReserved).Name), tplSettingsOptions, &form) - } else if db.IsErrNamePatternNotAllowed(err) { - ctx.Data["Err_Name"] = true - ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tplSettingsOptions, &form) - } else { - ctx.ServerError("RenameUser", err) - } - return - } - - ctx.Org.OrgLink = setting.AppSubURL + "/org/" + url.PathEscape(org.Name) - } - if form.Email != "" { if err := user_service.ReplacePrimaryEmailAddress(ctx, org.AsUser(), form.Email); err != nil { ctx.Data["Err_Email"] = true @@ -165,40 +144,28 @@ func SettingsDeleteAvatar(ctx *context.Context) { // SettingsDelete response for deleting an organization func SettingsDelete(ctx *context.Context) { - ctx.Data["Title"] = ctx.Tr("org.settings") - ctx.Data["PageIsOrgSettings"] = true - ctx.Data["PageIsSettingsDelete"] = true - - if ctx.Req.Method == http.MethodPost { - if ctx.Org.Organization.Name != ctx.FormString("org_name") { - ctx.Data["Err_OrgName"] = true - ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_org_name"), tplSettingsDelete, nil) - return - } - - if err := org_service.DeleteOrganization(ctx, ctx.Org.Organization, false); err != nil { - if repo_model.IsErrUserOwnRepos(err) { - ctx.Flash.Error(ctx.Tr("form.org_still_own_repo")) - ctx.Redirect(ctx.Org.OrgLink + "/settings/delete") - } else if packages_model.IsErrUserOwnPackages(err) { - ctx.Flash.Error(ctx.Tr("form.org_still_own_packages")) - ctx.Redirect(ctx.Org.OrgLink + "/settings/delete") - } else { - ctx.ServerError("DeleteOrganization", err) - } - } else { - log.Trace("Organization deleted: %s", ctx.Org.Organization.Name) - ctx.Redirect(setting.AppSubURL + "/") - } + if ctx.Org.Organization.Name != ctx.FormString("org_name") { + ctx.Flash.Error(ctx.Tr("form.enterred_invalid_org_name")) + ctx.JSONRedirect(ctx.Org.OrgLink + "/settings") return } - if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { - ctx.ServerError("RenderUserOrgHeader", err) + if err := org_service.DeleteOrganization(ctx, ctx.Org.Organization, false); err != nil { + if repo_model.IsErrUserOwnRepos(err) { + ctx.Flash.Error(ctx.Tr("form.org_still_own_repo")) + } else if packages_model.IsErrUserOwnPackages(err) { + ctx.Flash.Error(ctx.Tr("form.org_still_own_packages")) + } else { + log.Error("DeleteOrganization: %v", err) + ctx.Flash.Error(util.Iif(ctx.Doer.IsAdmin, err.Error(), string(ctx.Tr("org.settings.delete_failed")))) + } + ctx.JSONRedirect(ctx.Org.OrgLink + "/settings") return } - ctx.HTML(http.StatusOK, tplSettingsDelete) + log.Trace("Organization deleted: %s", ctx.Org.Organization.Name) + ctx.Flash.Success(ctx.Tr("org.settings.delete_successful", ctx.Org.Organization.Name)) + ctx.JSONRedirect(setting.AppSubURL + "/") } // Webhooks render webhook list page @@ -250,3 +217,44 @@ func Labels(ctx *context.Context) { ctx.HTML(http.StatusOK, tplSettingsLabels) } + +// SettingsRename response for renaming organization +func SettingsRename(ctx *context.Context) { + form := web.GetForm(ctx).(*forms.RenameOrgForm) + org := ctx.Org.Organization + + if org.Name != form.OrgName { + ctx.Flash.Error(ctx.Tr("form.enterred_invalid_org_name")) + ctx.JSONRedirect(ctx.Org.OrgLink + "/settings") + return + } + + if org.Name == form.NewOrgName { + ctx.Flash.Error(ctx.Tr("org.settings.rename_no_change")) + ctx.JSONRedirect(ctx.Org.OrgLink + "/settings") + return + } + + oldOrgName := org.Name + + if err := user_service.RenameUser(ctx, org.AsUser(), form.NewOrgName); err != nil { + if user_model.IsErrUserAlreadyExist(err) { + ctx.Flash.Error(ctx.Tr("form.username_been_taken")) + } else if db.IsErrNameReserved(err) { + ctx.Flash.Error(ctx.Tr("repo.form.name_reserved")) + } else if db.IsErrNamePatternNotAllowed(err) { + ctx.Flash.Error(ctx.Tr("repo.form.name_pattern_not_allowed")) + } else { + log.Error("RenameOrganization: %v", err) + ctx.Flash.Error(util.Iif(ctx.Doer.IsAdmin, err.Error(), string(ctx.Tr("org.settings.rename_failed")))) + } + ctx.JSONRedirect(ctx.Org.OrgLink + "/settings") + return + } + + ctx.Org.OrgLink = setting.AppSubURL + "/org/" + url.PathEscape(org.Name) + + log.Trace("Organization renamed to %s", org.Name) + ctx.Flash.Success(ctx.Tr("org.settings.rename_success", oldOrgName, org.Name)) + ctx.JSONRedirect(ctx.Org.OrgLink + "/settings") +} diff --git a/routers/web/web.go b/routers/web/web.go index 5eba29c601219..6362ad7122dc1 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -964,7 +964,8 @@ func registerWebRoutes(m *web.Router) { addSettingsVariablesRoutes() }, actions.MustEnableActions) - m.Methods("GET,POST", "/delete", org.SettingsDelete) + m.Post("/rename", web.Bind(forms.RenameOrgForm{}), org.SettingsRename) + m.Post("/delete", org.SettingsDelete) m.Group("/packages", func() { m.Get("", org.Packages) diff --git a/services/forms/org.go b/services/forms/org.go index db182f7e96b5b..cc39092dbe3e7 100644 --- a/services/forms/org.go +++ b/services/forms/org.go @@ -36,7 +36,6 @@ func (f *CreateOrgForm) Validate(req *http.Request, errs binding.Errors) binding // UpdateOrgSettingForm form for updating organization settings type UpdateOrgSettingForm struct { - Name string `binding:"Required;Username;MaxSize(40)" locale:"org.org_name_holder"` FullName string `binding:"MaxSize(100)"` Email string `binding:"MaxSize(255)"` Description string `binding:"MaxSize(255)"` @@ -53,6 +52,11 @@ func (f *UpdateOrgSettingForm) Validate(req *http.Request, errs binding.Errors) return middleware.Validate(errs, ctx.Data, f, ctx.Locale) } +type RenameOrgForm struct { + OrgName string `binding:"Required;Username;MaxSize(40)"` + NewOrgName string `binding:"Required;Username;MaxSize(40)"` +} + // ___________ // \__ ___/___ _____ _____ // | |_/ __ \\__ \ / \ diff --git a/templates/org/settings/delete.tmpl b/templates/org/settings/delete.tmpl deleted file mode 100644 index e1ef471e34124..0000000000000 --- a/templates/org/settings/delete.tmpl +++ /dev/null @@ -1,35 +0,0 @@ -{{template "org/settings/layout_head" (dict "ctxData" . "pageClass" "organization settings delete")}} - -
-

- {{ctx.Locale.Tr "org.settings.delete_account"}} -

-
-
-

{{svg "octicon-alert"}} {{ctx.Locale.Tr "org.settings.delete_prompt"}}

-
-
- {{.CsrfTokenHtml}} -
- - -
- -
-
-
- - - -{{template "org/settings/layout_footer" .}} diff --git a/templates/org/settings/navbar.tmpl b/templates/org/settings/navbar.tmpl index ce792f667c4f9..58475de7e7a31 100644 --- a/templates/org/settings/navbar.tmpl +++ b/templates/org/settings/navbar.tmpl @@ -41,8 +41,5 @@ {{end}} - - {{ctx.Locale.Tr "org.settings.delete"}} - diff --git a/templates/org/settings/options.tmpl b/templates/org/settings/options.tmpl index f4583bbe366b6..aaec812a1b567 100644 --- a/templates/org/settings/options.tmpl +++ b/templates/org/settings/options.tmpl @@ -6,14 +6,6 @@
{{.CsrfTokenHtml}} -
- - -
@@ -97,5 +89,100 @@
+ +

+ {{ctx.Locale.Tr "repo.settings.danger_zone"}} +

+
+
+
+
+
{{ctx.Locale.Tr "org.settings.rename"}}
+
{{ctx.Locale.Tr "org.settings.rename_desc"}}
+
+
+ +
+
+ +
+
+
{{ctx.Locale.Tr "org.settings.delete_account"}}
+
{{ctx.Locale.Tr "org.settings.delete_prompt"}}
+
+
+ +
+
+
+
+ + + + + {{template "org/settings/layout_footer" .}} From ad09dba81c8621b3b04ee14be11c15b0c8633c32 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 20 Jun 2025 13:29:45 -0700 Subject: [PATCH 02/12] improves --- options/locale/locale_en-US.ini | 2 +- templates/org/settings/options.tmpl | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 71d63df7d3014..108f438bf9ddc 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -2834,7 +2834,7 @@ settings.update_setting_success = Organization settings have been updated. settings.rename = Rename Organization settings.rename_desc = Changing the organization name will also change your organization's URL and free the old name. -settings.rename_success = Organization %s have been renamed to %s successfully. +settings.rename_success = Organization %[1]s have been renamed to %[2]s successfully. settings.rename_no_change = Organization name is no change. settings.rename_new_org_name = New Organization Name settings.rename_failed = Rename Organization failed because of internal error diff --git a/templates/org/settings/options.tmpl b/templates/org/settings/options.tmpl index aaec812a1b567..e974844f37de5 100644 --- a/templates/org/settings/options.tmpl +++ b/templates/org/settings/options.tmpl @@ -141,8 +141,8 @@
- - + +
@@ -173,8 +173,8 @@
- - + +
From f2b5744e97ded7831ca1d9f38edc98eee570fa02 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 20 Jun 2025 21:29:24 -0700 Subject: [PATCH 03/12] Remove form validation for json request and fix some other bugs --- models/db/name.go | 19 +++++++++++++++++++ models/user/user.go | 5 +++++ options/locale/locale_en-US.ini | 2 ++ routers/web/org/setting.go | 19 ++++++++++--------- routers/web/web.go | 2 +- services/forms/org.go | 12 ------------ services/forms/user_form.go | 2 +- 7 files changed, 38 insertions(+), 23 deletions(-) diff --git a/models/db/name.go b/models/db/name.go index 48c7fdbce5fe4..0c0694727dc40 100644 --- a/models/db/name.go +++ b/models/db/name.go @@ -72,6 +72,25 @@ func (err ErrNameCharsNotAllowed) Unwrap() error { return util.ErrInvalidArgument } +type ErrNameTooLong struct { + Name string + MaxLength int +} + +func (err ErrNameTooLong) Error() string { + return fmt.Sprintf("name is too long [name: %s, max length: %d]", err.Name, err.MaxLength) +} + +// Unwrap unwraps this as a ErrInvalid err +func (err ErrNameTooLong) Unwrap() error { + return util.ErrInvalidArgument +} + +func IsErrNameTooLong(err error) bool { + _, ok := err.(ErrNameTooLong) + return ok +} + // IsUsableName checks if name is reserved or pattern of name is not allowed // based on given reserved names and patterns. // Names are exact match, patterns can be a prefix or suffix match with placeholder '*'. diff --git a/models/user/user.go b/models/user/user.go index 86a354934546d..042bafe0fd1b3 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -618,8 +618,13 @@ var ( reservedUserPatterns = []string{"*.keys", "*.gpg", "*.rss", "*.atom", "*.png"} ) +const MaxUsableUsernameLength = 40 + // IsUsableUsername returns an error when a username is reserved func IsUsableUsername(name string) error { + if len(name) > MaxUsableUsernameLength { + return db.ErrNameTooLong{Name: name, MaxLength: MaxUsableUsernameLength} + } // Validate username make sure it satisfies requirement. if !validation.IsValidUsername(name) { // Note: usually this error is normally caught up earlier in the UI diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 937d50ae68a00..20d47c2a163bf 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -2812,8 +2812,10 @@ team_permission_desc = Permission team_unit_desc = Allow Access to Repository Sections team_unit_disabled = (Disabled) +form.username_been_taken = The organization name "%s" has already been taken. form.name_reserved = The organization name "%s" is reserved. form.name_pattern_not_allowed = The pattern "%s" is not allowed in an organization name. +form.name_too_long = The organization name "%[1]s" is too long, max size is %[2]d form.create_org_not_allowed = You are not allowed to create an organization. settings = Settings diff --git a/routers/web/org/setting.go b/routers/web/org/setting.go index f1fbe96b8f93a..97c8ff5ccd3ec 100644 --- a/routers/web/org/setting.go +++ b/routers/web/org/setting.go @@ -220,30 +220,31 @@ func Labels(ctx *context.Context) { // SettingsRename response for renaming organization func SettingsRename(ctx *context.Context) { - form := web.GetForm(ctx).(*forms.RenameOrgForm) org := ctx.Org.Organization + oldOrgName := ctx.FormString("org_name") + newOrgName := ctx.FormString("new_org_name") - if org.Name != form.OrgName { + if org.Name != oldOrgName { ctx.Flash.Error(ctx.Tr("form.enterred_invalid_org_name")) ctx.JSONRedirect(ctx.Org.OrgLink + "/settings") return } - if org.Name == form.NewOrgName { + if org.Name == newOrgName { ctx.Flash.Error(ctx.Tr("org.settings.rename_no_change")) ctx.JSONRedirect(ctx.Org.OrgLink + "/settings") return } - oldOrgName := org.Name - - if err := user_service.RenameUser(ctx, org.AsUser(), form.NewOrgName); err != nil { + if err := user_service.RenameUser(ctx, org.AsUser(), newOrgName); err != nil { if user_model.IsErrUserAlreadyExist(err) { - ctx.Flash.Error(ctx.Tr("form.username_been_taken")) + ctx.Flash.Error(ctx.Tr("org.form.username_been_taken", newOrgName)) } else if db.IsErrNameReserved(err) { - ctx.Flash.Error(ctx.Tr("repo.form.name_reserved")) + ctx.Flash.Error(ctx.Tr("org.form.name_reserved", newOrgName)) } else if db.IsErrNamePatternNotAllowed(err) { - ctx.Flash.Error(ctx.Tr("repo.form.name_pattern_not_allowed")) + ctx.Flash.Error(ctx.Tr("org.form.name_pattern_not_allowed", newOrgName)) + } else if db.IsErrNameTooLong(err) { + ctx.Flash.Error(ctx.Tr("org.form.name_too_long", newOrgName, user_model.MaxUsableUsernameLength)) } else { log.Error("RenameOrganization: %v", err) ctx.Flash.Error(util.Iif(ctx.Doer.IsAdmin, err.Error(), string(ctx.Tr("org.settings.rename_failed")))) diff --git a/routers/web/web.go b/routers/web/web.go index d3db6d8bbe02d..ca7788a2d7344 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -964,7 +964,7 @@ func registerWebRoutes(m *web.Router) { addSettingsVariablesRoutes() }, actions.MustEnableActions) - m.Post("/rename", web.Bind(forms.RenameOrgForm{}), org.SettingsRename) + m.Post("/rename", org.SettingsRename) m.Post("/delete", org.SettingsDelete) m.Group("/packages", func() { diff --git a/services/forms/org.go b/services/forms/org.go index cc39092dbe3e7..e6cb39946d323 100644 --- a/services/forms/org.go +++ b/services/forms/org.go @@ -52,18 +52,6 @@ func (f *UpdateOrgSettingForm) Validate(req *http.Request, errs binding.Errors) return middleware.Validate(errs, ctx.Data, f, ctx.Locale) } -type RenameOrgForm struct { - OrgName string `binding:"Required;Username;MaxSize(40)"` - NewOrgName string `binding:"Required;Username;MaxSize(40)"` -} - -// ___________ -// \__ ___/___ _____ _____ -// | |_/ __ \\__ \ / \ -// | |\ ___/ / __ \| Y Y \ -// |____| \___ >____ /__|_| / -// \/ \/ \/ - // CreateTeamForm form for creating team type CreateTeamForm struct { TeamName string `binding:"Required;AlphaDashDot;MaxSize(255)"` diff --git a/services/forms/user_form.go b/services/forms/user_form.go index ddf2bd09b0bb6..7433e7d1f21c3 100644 --- a/services/forms/user_form.go +++ b/services/forms/user_form.go @@ -62,7 +62,7 @@ type InstallForm struct { PasswordAlgorithm string - AdminName string `binding:"OmitEmpty;Username;MaxSize(30)" locale:"install.admin_name"` + AdminName string `binding:"OmitEmpty;Username;MaxSize(40)" locale:"install.admin_name"` AdminPasswd string `binding:"OmitEmpty;MaxSize(255)" locale:"install.admin_password"` AdminConfirmPasswd string AdminEmail string `binding:"OmitEmpty;MinSize(3);MaxSize(254);Include(@)" locale:"install.admin_email"` From 57a9b99977abbdb21065efdaa0fb4c86fd11bb65 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sat, 21 Jun 2025 13:23:42 +0800 Subject: [PATCH 04/12] Revert "Remove form validation for json request and fix some other bugs" This reverts commit f2b5744e97ded7831ca1d9f38edc98eee570fa02. --- models/db/name.go | 19 ------------------- models/user/user.go | 5 ----- options/locale/locale_en-US.ini | 2 -- routers/web/org/setting.go | 19 +++++++++---------- routers/web/web.go | 2 +- services/forms/org.go | 12 ++++++++++++ services/forms/user_form.go | 2 +- 7 files changed, 23 insertions(+), 38 deletions(-) diff --git a/models/db/name.go b/models/db/name.go index 0c0694727dc40..48c7fdbce5fe4 100644 --- a/models/db/name.go +++ b/models/db/name.go @@ -72,25 +72,6 @@ func (err ErrNameCharsNotAllowed) Unwrap() error { return util.ErrInvalidArgument } -type ErrNameTooLong struct { - Name string - MaxLength int -} - -func (err ErrNameTooLong) Error() string { - return fmt.Sprintf("name is too long [name: %s, max length: %d]", err.Name, err.MaxLength) -} - -// Unwrap unwraps this as a ErrInvalid err -func (err ErrNameTooLong) Unwrap() error { - return util.ErrInvalidArgument -} - -func IsErrNameTooLong(err error) bool { - _, ok := err.(ErrNameTooLong) - return ok -} - // IsUsableName checks if name is reserved or pattern of name is not allowed // based on given reserved names and patterns. // Names are exact match, patterns can be a prefix or suffix match with placeholder '*'. diff --git a/models/user/user.go b/models/user/user.go index 042bafe0fd1b3..86a354934546d 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -618,13 +618,8 @@ var ( reservedUserPatterns = []string{"*.keys", "*.gpg", "*.rss", "*.atom", "*.png"} ) -const MaxUsableUsernameLength = 40 - // IsUsableUsername returns an error when a username is reserved func IsUsableUsername(name string) error { - if len(name) > MaxUsableUsernameLength { - return db.ErrNameTooLong{Name: name, MaxLength: MaxUsableUsernameLength} - } // Validate username make sure it satisfies requirement. if !validation.IsValidUsername(name) { // Note: usually this error is normally caught up earlier in the UI diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 20d47c2a163bf..937d50ae68a00 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -2812,10 +2812,8 @@ team_permission_desc = Permission team_unit_desc = Allow Access to Repository Sections team_unit_disabled = (Disabled) -form.username_been_taken = The organization name "%s" has already been taken. form.name_reserved = The organization name "%s" is reserved. form.name_pattern_not_allowed = The pattern "%s" is not allowed in an organization name. -form.name_too_long = The organization name "%[1]s" is too long, max size is %[2]d form.create_org_not_allowed = You are not allowed to create an organization. settings = Settings diff --git a/routers/web/org/setting.go b/routers/web/org/setting.go index 97c8ff5ccd3ec..f1fbe96b8f93a 100644 --- a/routers/web/org/setting.go +++ b/routers/web/org/setting.go @@ -220,31 +220,30 @@ func Labels(ctx *context.Context) { // SettingsRename response for renaming organization func SettingsRename(ctx *context.Context) { + form := web.GetForm(ctx).(*forms.RenameOrgForm) org := ctx.Org.Organization - oldOrgName := ctx.FormString("org_name") - newOrgName := ctx.FormString("new_org_name") - if org.Name != oldOrgName { + if org.Name != form.OrgName { ctx.Flash.Error(ctx.Tr("form.enterred_invalid_org_name")) ctx.JSONRedirect(ctx.Org.OrgLink + "/settings") return } - if org.Name == newOrgName { + if org.Name == form.NewOrgName { ctx.Flash.Error(ctx.Tr("org.settings.rename_no_change")) ctx.JSONRedirect(ctx.Org.OrgLink + "/settings") return } - if err := user_service.RenameUser(ctx, org.AsUser(), newOrgName); err != nil { + oldOrgName := org.Name + + if err := user_service.RenameUser(ctx, org.AsUser(), form.NewOrgName); err != nil { if user_model.IsErrUserAlreadyExist(err) { - ctx.Flash.Error(ctx.Tr("org.form.username_been_taken", newOrgName)) + ctx.Flash.Error(ctx.Tr("form.username_been_taken")) } else if db.IsErrNameReserved(err) { - ctx.Flash.Error(ctx.Tr("org.form.name_reserved", newOrgName)) + ctx.Flash.Error(ctx.Tr("repo.form.name_reserved")) } else if db.IsErrNamePatternNotAllowed(err) { - ctx.Flash.Error(ctx.Tr("org.form.name_pattern_not_allowed", newOrgName)) - } else if db.IsErrNameTooLong(err) { - ctx.Flash.Error(ctx.Tr("org.form.name_too_long", newOrgName, user_model.MaxUsableUsernameLength)) + ctx.Flash.Error(ctx.Tr("repo.form.name_pattern_not_allowed")) } else { log.Error("RenameOrganization: %v", err) ctx.Flash.Error(util.Iif(ctx.Doer.IsAdmin, err.Error(), string(ctx.Tr("org.settings.rename_failed")))) diff --git a/routers/web/web.go b/routers/web/web.go index ca7788a2d7344..d3db6d8bbe02d 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -964,7 +964,7 @@ func registerWebRoutes(m *web.Router) { addSettingsVariablesRoutes() }, actions.MustEnableActions) - m.Post("/rename", org.SettingsRename) + m.Post("/rename", web.Bind(forms.RenameOrgForm{}), org.SettingsRename) m.Post("/delete", org.SettingsDelete) m.Group("/packages", func() { diff --git a/services/forms/org.go b/services/forms/org.go index e6cb39946d323..cc39092dbe3e7 100644 --- a/services/forms/org.go +++ b/services/forms/org.go @@ -52,6 +52,18 @@ func (f *UpdateOrgSettingForm) Validate(req *http.Request, errs binding.Errors) return middleware.Validate(errs, ctx.Data, f, ctx.Locale) } +type RenameOrgForm struct { + OrgName string `binding:"Required;Username;MaxSize(40)"` + NewOrgName string `binding:"Required;Username;MaxSize(40)"` +} + +// ___________ +// \__ ___/___ _____ _____ +// | |_/ __ \\__ \ / \ +// | |\ ___/ / __ \| Y Y \ +// |____| \___ >____ /__|_| / +// \/ \/ \/ + // CreateTeamForm form for creating team type CreateTeamForm struct { TeamName string `binding:"Required;AlphaDashDot;MaxSize(255)"` diff --git a/services/forms/user_form.go b/services/forms/user_form.go index 7433e7d1f21c3..ddf2bd09b0bb6 100644 --- a/services/forms/user_form.go +++ b/services/forms/user_form.go @@ -62,7 +62,7 @@ type InstallForm struct { PasswordAlgorithm string - AdminName string `binding:"OmitEmpty;Username;MaxSize(40)" locale:"install.admin_name"` + AdminName string `binding:"OmitEmpty;Username;MaxSize(30)" locale:"install.admin_name"` AdminPasswd string `binding:"OmitEmpty;MaxSize(255)" locale:"install.admin_password"` AdminConfirmPasswd string AdminEmail string `binding:"OmitEmpty;MinSize(3);MaxSize(254);Include(@)" locale:"install.admin_email"` From 9f37cbfe3d666bad22f3a369ee22c1222cc14d62 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sat, 21 Jun 2025 13:29:10 +0800 Subject: [PATCH 05/12] fix incorrect json response --- routers/web/org/setting.go | 26 +++-- templates/org/settings/options.tmpl | 96 +------------------ .../org/settings/options_dangerzone.tmpl | 93 ++++++++++++++++++ 3 files changed, 106 insertions(+), 109 deletions(-) create mode 100644 templates/org/settings/options_dangerzone.tmpl diff --git a/routers/web/org/setting.go b/routers/web/org/setting.go index f1fbe96b8f93a..33734f0e7012e 100644 --- a/routers/web/org/setting.go +++ b/routers/web/org/setting.go @@ -221,17 +221,19 @@ func Labels(ctx *context.Context) { // SettingsRename response for renaming organization func SettingsRename(ctx *context.Context) { form := web.GetForm(ctx).(*forms.RenameOrgForm) - org := ctx.Org.Organization + if ctx.HasError() { + ctx.JSONError(ctx.GetErrMsg()) + return + } + org := ctx.Org.Organization if org.Name != form.OrgName { - ctx.Flash.Error(ctx.Tr("form.enterred_invalid_org_name")) - ctx.JSONRedirect(ctx.Org.OrgLink + "/settings") + ctx.JSONError(ctx.Tr("form.enterred_invalid_org_name")) return } if org.Name == form.NewOrgName { - ctx.Flash.Error(ctx.Tr("org.settings.rename_no_change")) - ctx.JSONRedirect(ctx.Org.OrgLink + "/settings") + ctx.JSONError(ctx.Tr("org.settings.rename_no_change")) return } @@ -239,22 +241,18 @@ func SettingsRename(ctx *context.Context) { if err := user_service.RenameUser(ctx, org.AsUser(), form.NewOrgName); err != nil { if user_model.IsErrUserAlreadyExist(err) { - ctx.Flash.Error(ctx.Tr("form.username_been_taken")) + ctx.JSONError(ctx.Tr("form.username_been_taken")) } else if db.IsErrNameReserved(err) { - ctx.Flash.Error(ctx.Tr("repo.form.name_reserved")) + ctx.JSONError(ctx.Tr("repo.form.name_reserved")) } else if db.IsErrNamePatternNotAllowed(err) { - ctx.Flash.Error(ctx.Tr("repo.form.name_pattern_not_allowed")) + ctx.JSONError(ctx.Tr("repo.form.name_pattern_not_allowed")) } else { log.Error("RenameOrganization: %v", err) - ctx.Flash.Error(util.Iif(ctx.Doer.IsAdmin, err.Error(), string(ctx.Tr("org.settings.rename_failed")))) + ctx.JSONError(util.Iif(ctx.Doer.IsAdmin, err.Error(), string(ctx.Tr("org.settings.rename_failed")))) } - ctx.JSONRedirect(ctx.Org.OrgLink + "/settings") return } - ctx.Org.OrgLink = setting.AppSubURL + "/org/" + url.PathEscape(org.Name) - - log.Trace("Organization renamed to %s", org.Name) ctx.Flash.Success(ctx.Tr("org.settings.rename_success", oldOrgName, org.Name)) - ctx.JSONRedirect(ctx.Org.OrgLink + "/settings") + ctx.JSONRedirect(setting.AppSubURL + "/org/" + url.PathEscape(org.Name) + "/settings") } diff --git a/templates/org/settings/options.tmpl b/templates/org/settings/options.tmpl index e974844f37de5..3bbd2eb086331 100644 --- a/templates/org/settings/options.tmpl +++ b/templates/org/settings/options.tmpl @@ -89,100 +89,6 @@
- -

- {{ctx.Locale.Tr "repo.settings.danger_zone"}} -

-
-
-
-
-
{{ctx.Locale.Tr "org.settings.rename"}}
-
{{ctx.Locale.Tr "org.settings.rename_desc"}}
-
-
- -
-
- -
-
-
{{ctx.Locale.Tr "org.settings.delete_account"}}
-
{{ctx.Locale.Tr "org.settings.delete_prompt"}}
-
-
- -
-
-
-
- - - - - + {{template "org/settings/options_dangerzone" .}} {{template "org/settings/layout_footer" .}} diff --git a/templates/org/settings/options_dangerzone.tmpl b/templates/org/settings/options_dangerzone.tmpl new file mode 100644 index 0000000000000..91e2e1f099469 --- /dev/null +++ b/templates/org/settings/options_dangerzone.tmpl @@ -0,0 +1,93 @@ +

+ {{ctx.Locale.Tr "repo.settings.danger_zone"}} +

+
+
+
+
+
{{ctx.Locale.Tr "org.settings.rename"}}
+
{{ctx.Locale.Tr "org.settings.rename_desc"}}
+
+
+ +
+
+ +
+
+
{{ctx.Locale.Tr "org.settings.delete_account"}}
+
{{ctx.Locale.Tr "org.settings.delete_prompt"}}
+
+
+ +
+
+
+
+ + + + From 960b1b43bf350e94b84a07b767402c1be72b2a4e Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sat, 21 Jun 2025 14:12:46 +0800 Subject: [PATCH 06/12] fix ui --- templates/org/settings/options.tmpl | 171 +++++++++--------- web_src/css/base.css | 4 + web_src/css/modules/dimmer.css | 2 +- web_src/css/modules/toast.css | 2 +- web_src/fomantic/build/components/dropdown.js | 6 +- web_src/fomantic/build/components/modal.js | 8 +- web_src/js/modules/fomantic/dropdown.ts | 4 +- web_src/js/modules/fomantic/modal.ts | 10 + web_src/js/modules/toast.ts | 18 +- 9 files changed, 127 insertions(+), 98 deletions(-) diff --git a/templates/org/settings/options.tmpl b/templates/org/settings/options.tmpl index 3bbd2eb086331..d94bb4c62b2e5 100644 --- a/templates/org/settings/options.tmpl +++ b/templates/org/settings/options.tmpl @@ -1,94 +1,97 @@ {{template "org/settings/layout_head" (dict "ctxData" . "pageClass" "organization settings options")}} -
-

- {{ctx.Locale.Tr "org.settings.options"}} -

-
-
- {{.CsrfTokenHtml}} -
- - -
-
- - -
-
- {{/* it is rendered as markdown, but the length is limited, so at the moment we do not use the markdown editor here */}} - - -
-
- - -
-
- - -
-
-
- -
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
+
+

+ {{ctx.Locale.Tr "org.settings.options"}} +

+
+ + {{.CsrfTokenHtml}} +
+ + +
+
+ + +
+
+ {{/* it is rendered as markdown, but the length is limited, so at the moment we do not use the markdown editor here */}} + + +
+
+ + +
+
+ + +
+ +
+
+ +
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
-
- -
-
- - -
-
-
+
+ +
+
+ + +
+
+
- {{if .SignedUser.IsAdmin}} -
+ {{if .SignedUser.IsAdmin}} +
-
- - -

{{ctx.Locale.Tr "admin.users.max_repo_creation_desc"}}

-
- {{end}} +
+ + +

{{ctx.Locale.Tr "admin.users.max_repo_creation_desc"}}

+
+ {{end}} -
- -
- +
+ +
+ -
+
-
- {{.CsrfTokenHtml}} -
- {{template "shared/avatar_upload_crop" dict "LabelText" (ctx.Locale.Tr "settings.choose_new_avatar")}} -
-
- - -
-
-
+
+ {{.CsrfTokenHtml}} +
+ {{template "shared/avatar_upload_crop" dict "LabelText" (ctx.Locale.Tr "settings.choose_new_avatar")}} +
+
+ +
- {{template "org/settings/options_dangerzone" .}} +
+
+
+ +{{template "org/settings/options_dangerzone" .}} + {{template "org/settings/layout_footer" .}} diff --git a/web_src/css/base.css b/web_src/css/base.css index b50abf79f1d2c..dc58fb850a134 100644 --- a/web_src/css/base.css +++ b/web_src/css/base.css @@ -30,6 +30,10 @@ --page-spacing: 16px; /* space between page elements */ --page-margin-x: 32px; /* minimum space on left and right side of page */ --page-space-bottom: 64px; /* space between last page element and footer */ + + /* z-index */ + --z-index-modal: 1001; /* modal dialog, hard-coded from Fomantic modal.css */ + --z-index-toast: 1002; /* should be larger than modal */ } @media (min-width: 768px) and (max-width: 1200px) { diff --git a/web_src/css/modules/dimmer.css b/web_src/css/modules/dimmer.css index 89248213707e3..7d1ca6171aa9e 100644 --- a/web_src/css/modules/dimmer.css +++ b/web_src/css/modules/dimmer.css @@ -20,7 +20,7 @@ opacity: 1; } -.ui.dimmer > * { +.ui.dimmer > .ui.modal { position: static; margin-top: auto !important; margin-bottom: auto !important; diff --git a/web_src/css/modules/toast.css b/web_src/css/modules/toast.css index 1145f3b1b58be..330d3b176eb39 100644 --- a/web_src/css/modules/toast.css +++ b/web_src/css/modules/toast.css @@ -3,7 +3,7 @@ position: fixed; opacity: 0; transition: all .2s ease; - z-index: 500; + z-index: var(--z-index-toast); border-radius: var(--border-radius); box-shadow: 0 8px 24px var(--color-shadow); display: flex; diff --git a/web_src/fomantic/build/components/dropdown.js b/web_src/fomantic/build/components/dropdown.js index 85530c79912ee..3ad098486516b 100644 --- a/web_src/fomantic/build/components/dropdown.js +++ b/web_src/fomantic/build/components/dropdown.js @@ -525,7 +525,7 @@ $.fn.dropdown = function(parameters) { return true; } if(settings.onShow.call(element) !== false) { - settings.onAfterFiltered.call(element); // GITEA-PATCH: callback to correctly handle the filtered items + $module.fomanticExt.onDropdownAfterFiltered.call(element); // GITEA-PATCH: callback to correctly handle the filtered items module.animate.show(function() { if( module.can.click() ) { module.bind.intent(); @@ -753,7 +753,7 @@ $.fn.dropdown = function(parameters) { if(module.is.searchSelection() && module.can.show() && module.is.focusedOnSearch() ) { module.show(); } - settings.onAfterFiltered.call(element); // GITEA-PATCH: callback to correctly handle the filtered items + $module.fomanticExt.onDropdownAfterFiltered.call(element); // GITEA-PATCH: callback to correctly handle the filtered items } ; if(settings.useLabels && module.has.maxSelections()) { @@ -3994,8 +3994,6 @@ $.fn.dropdown.settings = { onShow : function(){}, onHide : function(){}, - onAfterFiltered: function(){}, // GITEA-PATCH: callback to correctly handle the filtered items - /* Component */ name : 'Dropdown', namespace : 'dropdown', diff --git a/web_src/fomantic/build/components/modal.js b/web_src/fomantic/build/components/modal.js index 420ecc250baa1..3f578ccfccc7d 100644 --- a/web_src/fomantic/build/components/modal.js +++ b/web_src/fomantic/build/components/modal.js @@ -467,7 +467,7 @@ $.fn.modal = function(parameters) { ignoreRepeatedEvents = false; return false; } - + $module.fomanticExt.onModalBeforeHidden.call(element); // GITEA-PATCH: handle more UI updates before hidden if( module.is.animating() || module.is.active() ) { if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) { module.remove.active(); @@ -641,7 +641,7 @@ $.fn.modal = function(parameters) { $module .off('mousedown' + elementEventNamespace) ; - } + } $dimmer .off('mousedown' + elementEventNamespace) ; @@ -877,7 +877,7 @@ $.fn.modal = function(parameters) { ? $(document).scrollTop() + settings.padding : $(document).scrollTop() + (module.cache.contextHeight - module.cache.height - settings.padding), marginLeft: -(module.cache.width / 2) - }) + }) ; } else { $module @@ -886,7 +886,7 @@ $.fn.modal = function(parameters) { ? -(module.cache.height / 2) : settings.padding / 2, marginLeft: -(module.cache.width / 2) - }) + }) ; } module.verbose('Setting modal offset for legacy mode'); diff --git a/web_src/js/modules/fomantic/dropdown.ts b/web_src/js/modules/fomantic/dropdown.ts index 02fee5a267d85..ccc22073d7fe5 100644 --- a/web_src/js/modules/fomantic/dropdown.ts +++ b/web_src/js/modules/fomantic/dropdown.ts @@ -9,9 +9,9 @@ const fomanticDropdownFn = $.fn.dropdown; // use our own `$().dropdown` function to patch Fomantic's dropdown module export function initAriaDropdownPatch() { if ($.fn.dropdown === ariaDropdownFn) throw new Error('initAriaDropdownPatch could only be called once'); - $.fn.dropdown.settings.onAfterFiltered = onAfterFiltered; $.fn.dropdown = ariaDropdownFn; $.fn.fomanticExt.onResponseKeepSelectedItem = onResponseKeepSelectedItem; + $.fn.fomanticExt.onDropdownAfterFiltered = onDropdownAfterFiltered; (ariaDropdownFn as FomanticInitFunction).settings = fomanticDropdownFn.settings; } @@ -71,7 +71,7 @@ function updateSelectionLabel(label: HTMLElement) { } } -function onAfterFiltered(this: any) { +function onDropdownAfterFiltered(this: any) { const $dropdown = $(this).closest('.ui.dropdown'); // "this" can be the "ui dropdown" or "