mirror of
https://github.com/go-gitea/gitea.git
synced 2025-07-16 00:01:18 -04:00
Compare commits
5 Commits
7b5b739a2f
...
aa1d95300a
Author | SHA1 | Date | |
---|---|---|---|
|
aa1d95300a | ||
|
618c9118c1 | ||
|
71ee488098 | ||
|
e314c9e866 | ||
|
d306ab2b8f |
406
cmd/admin.go
406
cmd/admin.go
@ -5,7 +5,6 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
@ -16,20 +15,15 @@ import (
|
|||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/graceful"
|
"code.gitea.io/gitea/modules/graceful"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
pwd "code.gitea.io/gitea/modules/password"
|
|
||||||
repo_module "code.gitea.io/gitea/modules/repository"
|
repo_module "code.gitea.io/gitea/modules/repository"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
|
||||||
"code.gitea.io/gitea/modules/storage"
|
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
auth_service "code.gitea.io/gitea/services/auth"
|
auth_service "code.gitea.io/gitea/services/auth"
|
||||||
"code.gitea.io/gitea/services/auth/source/oauth2"
|
"code.gitea.io/gitea/services/auth/source/oauth2"
|
||||||
"code.gitea.io/gitea/services/auth/source/smtp"
|
"code.gitea.io/gitea/services/auth/source/smtp"
|
||||||
repo_service "code.gitea.io/gitea/services/repository"
|
repo_service "code.gitea.io/gitea/services/repository"
|
||||||
user_service "code.gitea.io/gitea/services/user"
|
|
||||||
|
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
@ -48,147 +42,6 @@ var (
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
subcmdUser = cli.Command{
|
|
||||||
Name: "user",
|
|
||||||
Usage: "Modify users",
|
|
||||||
Subcommands: []cli.Command{
|
|
||||||
microcmdUserCreate,
|
|
||||||
microcmdUserList,
|
|
||||||
microcmdUserChangePassword,
|
|
||||||
microcmdUserDelete,
|
|
||||||
microcmdUserGenerateAccessToken,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
microcmdUserList = cli.Command{
|
|
||||||
Name: "list",
|
|
||||||
Usage: "List users",
|
|
||||||
Action: runListUsers,
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
cli.BoolFlag{
|
|
||||||
Name: "admin",
|
|
||||||
Usage: "List only admin users",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
microcmdUserCreate = cli.Command{
|
|
||||||
Name: "create",
|
|
||||||
Usage: "Create a new user in database",
|
|
||||||
Action: runCreateUser,
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "name",
|
|
||||||
Usage: "Username. DEPRECATED: use username instead",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "username",
|
|
||||||
Usage: "Username",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "password",
|
|
||||||
Usage: "User password",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "email",
|
|
||||||
Usage: "User email address",
|
|
||||||
},
|
|
||||||
cli.BoolFlag{
|
|
||||||
Name: "admin",
|
|
||||||
Usage: "User is an admin",
|
|
||||||
},
|
|
||||||
cli.BoolFlag{
|
|
||||||
Name: "random-password",
|
|
||||||
Usage: "Generate a random password for the user",
|
|
||||||
},
|
|
||||||
cli.BoolFlag{
|
|
||||||
Name: "must-change-password",
|
|
||||||
Usage: "Set this option to false to prevent forcing the user to change their password after initial login, (Default: true)",
|
|
||||||
},
|
|
||||||
cli.IntFlag{
|
|
||||||
Name: "random-password-length",
|
|
||||||
Usage: "Length of the random password to be generated",
|
|
||||||
Value: 12,
|
|
||||||
},
|
|
||||||
cli.BoolFlag{
|
|
||||||
Name: "access-token",
|
|
||||||
Usage: "Generate access token for the user",
|
|
||||||
},
|
|
||||||
cli.BoolFlag{
|
|
||||||
Name: "restricted",
|
|
||||||
Usage: "Make a restricted user account",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
microcmdUserChangePassword = cli.Command{
|
|
||||||
Name: "change-password",
|
|
||||||
Usage: "Change a user's password",
|
|
||||||
Action: runChangePassword,
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "username,u",
|
|
||||||
Value: "",
|
|
||||||
Usage: "The user to change password for",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "password,p",
|
|
||||||
Value: "",
|
|
||||||
Usage: "New password to set for user",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
microcmdUserDelete = cli.Command{
|
|
||||||
Name: "delete",
|
|
||||||
Usage: "Delete specific user by id, name or email",
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
cli.Int64Flag{
|
|
||||||
Name: "id",
|
|
||||||
Usage: "ID of user of the user to delete",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "username,u",
|
|
||||||
Usage: "Username of the user to delete",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "email,e",
|
|
||||||
Usage: "Email of the user to delete",
|
|
||||||
},
|
|
||||||
cli.BoolFlag{
|
|
||||||
Name: "purge",
|
|
||||||
Usage: "Purge user, all their repositories, organizations and comments",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Action: runDeleteUser,
|
|
||||||
}
|
|
||||||
|
|
||||||
microcmdUserGenerateAccessToken = cli.Command{
|
|
||||||
Name: "generate-access-token",
|
|
||||||
Usage: "Generate a access token for a specific user",
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "username,u",
|
|
||||||
Usage: "Username",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "token-name,t",
|
|
||||||
Usage: "Token name",
|
|
||||||
Value: "gitea-admin",
|
|
||||||
},
|
|
||||||
cli.BoolFlag{
|
|
||||||
Name: "raw",
|
|
||||||
Usage: "Display only the token value",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "scopes",
|
|
||||||
Value: "",
|
|
||||||
Usage: "Comma separated list of scopes to apply to access token",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Action: runGenerateAccessToken,
|
|
||||||
}
|
|
||||||
|
|
||||||
subcmdRepoSyncReleases = cli.Command{
|
subcmdRepoSyncReleases = cli.Command{
|
||||||
Name: "repo-sync-releases",
|
Name: "repo-sync-releases",
|
||||||
Usage: "Synchronize repository releases with tags",
|
Usage: "Synchronize repository releases with tags",
|
||||||
@ -486,265 +339,6 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func runChangePassword(c *cli.Context) error {
|
|
||||||
if err := argsSet(c, "username", "password"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if err := initDB(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(c.String("password")) < setting.MinPasswordLength {
|
|
||||||
return fmt.Errorf("Password is not long enough. Needs to be at least %d", setting.MinPasswordLength)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !pwd.IsComplexEnough(c.String("password")) {
|
|
||||||
return errors.New("Password does not meet complexity requirements")
|
|
||||||
}
|
|
||||||
pwned, err := pwd.IsPwned(context.Background(), c.String("password"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if pwned {
|
|
||||||
return errors.New("The password you chose is on a list of stolen passwords previously exposed in public data breaches. Please try again with a different password.\nFor more details, see https://haveibeenpwned.com/Passwords")
|
|
||||||
}
|
|
||||||
uname := c.String("username")
|
|
||||||
user, err := user_model.GetUserByName(ctx, uname)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = user.SetPassword(c.String("password")); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = user_model.UpdateUserCols(ctx, user, "passwd", "passwd_hash_algo", "salt"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("%s's password has been successfully updated!\n", user.Name)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func runCreateUser(c *cli.Context) error {
|
|
||||||
if err := argsSet(c, "email"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.IsSet("name") && c.IsSet("username") {
|
|
||||||
return errors.New("Cannot set both --name and --username flags")
|
|
||||||
}
|
|
||||||
if !c.IsSet("name") && !c.IsSet("username") {
|
|
||||||
return errors.New("One of --name or --username flags must be set")
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.IsSet("password") && c.IsSet("random-password") {
|
|
||||||
return errors.New("cannot set both -random-password and -password flags")
|
|
||||||
}
|
|
||||||
|
|
||||||
var username string
|
|
||||||
if c.IsSet("username") {
|
|
||||||
username = c.String("username")
|
|
||||||
} else {
|
|
||||||
username = c.String("name")
|
|
||||||
fmt.Fprintf(os.Stderr, "--name flag is deprecated. Use --username instead.\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if err := initDB(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var password string
|
|
||||||
if c.IsSet("password") {
|
|
||||||
password = c.String("password")
|
|
||||||
} else if c.IsSet("random-password") {
|
|
||||||
var err error
|
|
||||||
password, err = pwd.Generate(c.Int("random-password-length"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Printf("generated random password is '%s'\n", password)
|
|
||||||
} else {
|
|
||||||
return errors.New("must set either password or random-password flag")
|
|
||||||
}
|
|
||||||
|
|
||||||
// always default to true
|
|
||||||
changePassword := true
|
|
||||||
|
|
||||||
// If this is the first user being created.
|
|
||||||
// Take it as the admin and don't force a password update.
|
|
||||||
if n := user_model.CountUsers(nil); n == 0 {
|
|
||||||
changePassword = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.IsSet("must-change-password") {
|
|
||||||
changePassword = c.Bool("must-change-password")
|
|
||||||
}
|
|
||||||
|
|
||||||
restricted := util.OptionalBoolNone
|
|
||||||
|
|
||||||
if c.IsSet("restricted") {
|
|
||||||
restricted = util.OptionalBoolOf(c.Bool("restricted"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// default user visibility in app.ini
|
|
||||||
visibility := setting.Service.DefaultUserVisibilityMode
|
|
||||||
|
|
||||||
u := &user_model.User{
|
|
||||||
Name: username,
|
|
||||||
Email: c.String("email"),
|
|
||||||
Passwd: password,
|
|
||||||
IsAdmin: c.Bool("admin"),
|
|
||||||
MustChangePassword: changePassword,
|
|
||||||
Visibility: visibility,
|
|
||||||
}
|
|
||||||
|
|
||||||
overwriteDefault := &user_model.CreateUserOverwriteOptions{
|
|
||||||
IsActive: util.OptionalBoolTrue,
|
|
||||||
IsRestricted: restricted,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := user_model.CreateUser(u, overwriteDefault); err != nil {
|
|
||||||
return fmt.Errorf("CreateUser: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Bool("access-token") {
|
|
||||||
t := &auth_model.AccessToken{
|
|
||||||
Name: "gitea-admin",
|
|
||||||
UID: u.ID,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := auth_model.NewAccessToken(t); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("Access token was successfully created... %s\n", t.Token)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("New user '%s' has been successfully created!\n", username)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func runListUsers(c *cli.Context) error {
|
|
||||||
ctx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if err := initDB(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
users, err := user_model.GetAllUsers()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
w := tabwriter.NewWriter(os.Stdout, 5, 0, 1, ' ', 0)
|
|
||||||
|
|
||||||
if c.IsSet("admin") {
|
|
||||||
fmt.Fprintf(w, "ID\tUsername\tEmail\tIsActive\n")
|
|
||||||
for _, u := range users {
|
|
||||||
if u.IsAdmin {
|
|
||||||
fmt.Fprintf(w, "%d\t%s\t%s\t%t\n", u.ID, u.Name, u.Email, u.IsActive)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
twofa := user_model.UserList(users).GetTwoFaStatus()
|
|
||||||
fmt.Fprintf(w, "ID\tUsername\tEmail\tIsActive\tIsAdmin\t2FA\n")
|
|
||||||
for _, u := range users {
|
|
||||||
fmt.Fprintf(w, "%d\t%s\t%s\t%t\t%t\t%t\n", u.ID, u.Name, u.Email, u.IsActive, u.IsAdmin, twofa[u.ID])
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Flush()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func runDeleteUser(c *cli.Context) error {
|
|
||||||
if !c.IsSet("id") && !c.IsSet("username") && !c.IsSet("email") {
|
|
||||||
return fmt.Errorf("You must provide the id, username or email of a user to delete")
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if err := initDB(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := storage.Init(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
var user *user_model.User
|
|
||||||
if c.IsSet("email") {
|
|
||||||
user, err = user_model.GetUserByEmail(c.String("email"))
|
|
||||||
} else if c.IsSet("username") {
|
|
||||||
user, err = user_model.GetUserByName(ctx, c.String("username"))
|
|
||||||
} else {
|
|
||||||
user, err = user_model.GetUserByID(ctx, c.Int64("id"))
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if c.IsSet("username") && user.LowerName != strings.ToLower(strings.TrimSpace(c.String("username"))) {
|
|
||||||
return fmt.Errorf("The user %s who has email %s does not match the provided username %s", user.Name, c.String("email"), c.String("username"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.IsSet("id") && user.ID != c.Int64("id") {
|
|
||||||
return fmt.Errorf("The user %s does not match the provided id %d", user.Name, c.Int64("id"))
|
|
||||||
}
|
|
||||||
|
|
||||||
return user_service.DeleteUser(ctx, user, c.Bool("purge"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func runGenerateAccessToken(c *cli.Context) error {
|
|
||||||
if !c.IsSet("username") {
|
|
||||||
return fmt.Errorf("You must provide the username to generate a token for them")
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := installSignals()
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if err := initDB(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
user, err := user_model.GetUserByName(ctx, c.String("username"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
accessTokenScope, err := auth_model.AccessTokenScope(c.String("scopes")).Normalize()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
t := &auth_model.AccessToken{
|
|
||||||
Name: c.String("token-name"),
|
|
||||||
UID: user.ID,
|
|
||||||
Scope: accessTokenScope,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := auth_model.NewAccessToken(t); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Bool("raw") {
|
|
||||||
fmt.Printf("%s\n", t.Token)
|
|
||||||
} else {
|
|
||||||
fmt.Printf("Access token was successfully created: %s\n", t.Token)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func runRepoSyncReleases(_ *cli.Context) error {
|
func runRepoSyncReleases(_ *cli.Context) error {
|
||||||
ctx, cancel := installSignals()
|
ctx, cancel := installSignals()
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
21
cmd/admin_user.go
Normal file
21
cmd/admin_user.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
var subcmdUser = cli.Command{
|
||||||
|
Name: "user",
|
||||||
|
Usage: "Modify users",
|
||||||
|
Subcommands: []cli.Command{
|
||||||
|
microcmdUserCreate,
|
||||||
|
microcmdUserList,
|
||||||
|
microcmdUserChangePassword,
|
||||||
|
microcmdUserDelete,
|
||||||
|
microcmdUserGenerateAccessToken,
|
||||||
|
microcmdUserMustChangePassword,
|
||||||
|
},
|
||||||
|
}
|
76
cmd/admin_user_change_password.go
Normal file
76
cmd/admin_user_change_password.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
pwd "code.gitea.io/gitea/modules/password"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
var microcmdUserChangePassword = cli.Command{
|
||||||
|
Name: "change-password",
|
||||||
|
Usage: "Change a user's password",
|
||||||
|
Action: runChangePassword,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "username,u",
|
||||||
|
Value: "",
|
||||||
|
Usage: "The user to change password for",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "password,p",
|
||||||
|
Value: "",
|
||||||
|
Usage: "New password to set for user",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func runChangePassword(c *cli.Context) error {
|
||||||
|
if err := argsSet(c, "username", "password"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := installSignals()
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := initDB(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(c.String("password")) < setting.MinPasswordLength {
|
||||||
|
return fmt.Errorf("Password is not long enough. Needs to be at least %d", setting.MinPasswordLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !pwd.IsComplexEnough(c.String("password")) {
|
||||||
|
return errors.New("Password does not meet complexity requirements")
|
||||||
|
}
|
||||||
|
pwned, err := pwd.IsPwned(context.Background(), c.String("password"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if pwned {
|
||||||
|
return errors.New("The password you chose is on a list of stolen passwords previously exposed in public data breaches. Please try again with a different password.\nFor more details, see https://haveibeenpwned.com/Passwords")
|
||||||
|
}
|
||||||
|
uname := c.String("username")
|
||||||
|
user, err := user_model.GetUserByName(ctx, uname)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = user.SetPassword(c.String("password")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = user_model.UpdateUserCols(ctx, user, "passwd", "passwd_hash_algo", "salt"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%s's password has been successfully updated!\n", user.Name)
|
||||||
|
return nil
|
||||||
|
}
|
169
cmd/admin_user_create.go
Normal file
169
cmd/admin_user_create.go
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
pwd "code.gitea.io/gitea/modules/password"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
var microcmdUserCreate = cli.Command{
|
||||||
|
Name: "create",
|
||||||
|
Usage: "Create a new user in database",
|
||||||
|
Action: runCreateUser,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "name",
|
||||||
|
Usage: "Username. DEPRECATED: use username instead",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "username",
|
||||||
|
Usage: "Username",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "password",
|
||||||
|
Usage: "User password",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "email",
|
||||||
|
Usage: "User email address",
|
||||||
|
},
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "admin",
|
||||||
|
Usage: "User is an admin",
|
||||||
|
},
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "random-password",
|
||||||
|
Usage: "Generate a random password for the user",
|
||||||
|
},
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "must-change-password",
|
||||||
|
Usage: "Set this option to false to prevent forcing the user to change their password after initial login, (Default: true)",
|
||||||
|
},
|
||||||
|
cli.IntFlag{
|
||||||
|
Name: "random-password-length",
|
||||||
|
Usage: "Length of the random password to be generated",
|
||||||
|
Value: 12,
|
||||||
|
},
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "access-token",
|
||||||
|
Usage: "Generate access token for the user",
|
||||||
|
},
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "restricted",
|
||||||
|
Usage: "Make a restricted user account",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func runCreateUser(c *cli.Context) error {
|
||||||
|
if err := argsSet(c, "email"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.IsSet("name") && c.IsSet("username") {
|
||||||
|
return errors.New("Cannot set both --name and --username flags")
|
||||||
|
}
|
||||||
|
if !c.IsSet("name") && !c.IsSet("username") {
|
||||||
|
return errors.New("One of --name or --username flags must be set")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.IsSet("password") && c.IsSet("random-password") {
|
||||||
|
return errors.New("cannot set both -random-password and -password flags")
|
||||||
|
}
|
||||||
|
|
||||||
|
var username string
|
||||||
|
if c.IsSet("username") {
|
||||||
|
username = c.String("username")
|
||||||
|
} else {
|
||||||
|
username = c.String("name")
|
||||||
|
fmt.Fprintf(os.Stderr, "--name flag is deprecated. Use --username instead.\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := installSignals()
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := initDB(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var password string
|
||||||
|
if c.IsSet("password") {
|
||||||
|
password = c.String("password")
|
||||||
|
} else if c.IsSet("random-password") {
|
||||||
|
var err error
|
||||||
|
password, err = pwd.Generate(c.Int("random-password-length"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Printf("generated random password is '%s'\n", password)
|
||||||
|
} else {
|
||||||
|
return errors.New("must set either password or random-password flag")
|
||||||
|
}
|
||||||
|
|
||||||
|
// always default to true
|
||||||
|
changePassword := true
|
||||||
|
|
||||||
|
// If this is the first user being created.
|
||||||
|
// Take it as the admin and don't force a password update.
|
||||||
|
if n := user_model.CountUsers(nil); n == 0 {
|
||||||
|
changePassword = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.IsSet("must-change-password") {
|
||||||
|
changePassword = c.Bool("must-change-password")
|
||||||
|
}
|
||||||
|
|
||||||
|
restricted := util.OptionalBoolNone
|
||||||
|
|
||||||
|
if c.IsSet("restricted") {
|
||||||
|
restricted = util.OptionalBoolOf(c.Bool("restricted"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// default user visibility in app.ini
|
||||||
|
visibility := setting.Service.DefaultUserVisibilityMode
|
||||||
|
|
||||||
|
u := &user_model.User{
|
||||||
|
Name: username,
|
||||||
|
Email: c.String("email"),
|
||||||
|
Passwd: password,
|
||||||
|
IsAdmin: c.Bool("admin"),
|
||||||
|
MustChangePassword: changePassword,
|
||||||
|
Visibility: visibility,
|
||||||
|
}
|
||||||
|
|
||||||
|
overwriteDefault := &user_model.CreateUserOverwriteOptions{
|
||||||
|
IsActive: util.OptionalBoolTrue,
|
||||||
|
IsRestricted: restricted,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := user_model.CreateUser(u, overwriteDefault); err != nil {
|
||||||
|
return fmt.Errorf("CreateUser: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Bool("access-token") {
|
||||||
|
t := &auth_model.AccessToken{
|
||||||
|
Name: "gitea-admin",
|
||||||
|
UID: u.ID,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := auth_model.NewAccessToken(t); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Access token was successfully created... %s\n", t.Token)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("New user '%s' has been successfully created!\n", username)
|
||||||
|
return nil
|
||||||
|
}
|
78
cmd/admin_user_delete.go
Normal file
78
cmd/admin_user_delete.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/storage"
|
||||||
|
user_service "code.gitea.io/gitea/services/user"
|
||||||
|
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
var microcmdUserDelete = cli.Command{
|
||||||
|
Name: "delete",
|
||||||
|
Usage: "Delete specific user by id, name or email",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.Int64Flag{
|
||||||
|
Name: "id",
|
||||||
|
Usage: "ID of user of the user to delete",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "username,u",
|
||||||
|
Usage: "Username of the user to delete",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "email,e",
|
||||||
|
Usage: "Email of the user to delete",
|
||||||
|
},
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "purge",
|
||||||
|
Usage: "Purge user, all their repositories, organizations and comments",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: runDeleteUser,
|
||||||
|
}
|
||||||
|
|
||||||
|
func runDeleteUser(c *cli.Context) error {
|
||||||
|
if !c.IsSet("id") && !c.IsSet("username") && !c.IsSet("email") {
|
||||||
|
return fmt.Errorf("You must provide the id, username or email of a user to delete")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := installSignals()
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := initDB(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := storage.Init(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var user *user_model.User
|
||||||
|
if c.IsSet("email") {
|
||||||
|
user, err = user_model.GetUserByEmail(c.String("email"))
|
||||||
|
} else if c.IsSet("username") {
|
||||||
|
user, err = user_model.GetUserByName(ctx, c.String("username"))
|
||||||
|
} else {
|
||||||
|
user, err = user_model.GetUserByID(ctx, c.Int64("id"))
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if c.IsSet("username") && user.LowerName != strings.ToLower(strings.TrimSpace(c.String("username"))) {
|
||||||
|
return fmt.Errorf("The user %s who has email %s does not match the provided username %s", user.Name, c.String("email"), c.String("username"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.IsSet("id") && user.ID != c.Int64("id") {
|
||||||
|
return fmt.Errorf("The user %s does not match the provided id %d", user.Name, c.Int64("id"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return user_service.DeleteUser(ctx, user, c.Bool("purge"))
|
||||||
|
}
|
80
cmd/admin_user_generate_access_token.go
Normal file
80
cmd/admin_user_generate_access_token.go
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
var microcmdUserGenerateAccessToken = cli.Command{
|
||||||
|
Name: "generate-access-token",
|
||||||
|
Usage: "Generate an access token for a specific user",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "username,u",
|
||||||
|
Usage: "Username",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "token-name,t",
|
||||||
|
Usage: "Token name",
|
||||||
|
Value: "gitea-admin",
|
||||||
|
},
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "raw",
|
||||||
|
Usage: "Display only the token value",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "scopes",
|
||||||
|
Value: "",
|
||||||
|
Usage: "Comma separated list of scopes to apply to access token",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: runGenerateAccessToken,
|
||||||
|
}
|
||||||
|
|
||||||
|
func runGenerateAccessToken(c *cli.Context) error {
|
||||||
|
if !c.IsSet("username") {
|
||||||
|
return fmt.Errorf("You must provide a username to generate a token for")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := installSignals()
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := initDB(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := user_model.GetUserByName(ctx, c.String("username"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
accessTokenScope, err := auth_model.AccessTokenScope(c.String("scopes")).Normalize()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
t := &auth_model.AccessToken{
|
||||||
|
Name: c.String("token-name"),
|
||||||
|
UID: user.ID,
|
||||||
|
Scope: accessTokenScope,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := auth_model.NewAccessToken(t); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Bool("raw") {
|
||||||
|
fmt.Printf("%s\n", t.Token)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Access token was successfully created: %s\n", t.Token)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
60
cmd/admin_user_list.go
Normal file
60
cmd/admin_user_list.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"text/tabwriter"
|
||||||
|
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
var microcmdUserList = cli.Command{
|
||||||
|
Name: "list",
|
||||||
|
Usage: "List users",
|
||||||
|
Action: runListUsers,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "admin",
|
||||||
|
Usage: "List only admin users",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func runListUsers(c *cli.Context) error {
|
||||||
|
ctx, cancel := installSignals()
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := initDB(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
users, err := user_model.GetAllUsers()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w := tabwriter.NewWriter(os.Stdout, 5, 0, 1, ' ', 0)
|
||||||
|
|
||||||
|
if c.IsSet("admin") {
|
||||||
|
fmt.Fprintf(w, "ID\tUsername\tEmail\tIsActive\n")
|
||||||
|
for _, u := range users {
|
||||||
|
if u.IsAdmin {
|
||||||
|
fmt.Fprintf(w, "%d\t%s\t%s\t%t\n", u.ID, u.Name, u.Email, u.IsActive)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
twofa := user_model.UserList(users).GetTwoFaStatus()
|
||||||
|
fmt.Fprintf(w, "ID\tUsername\tEmail\tIsActive\tIsAdmin\t2FA\n")
|
||||||
|
for _, u := range users {
|
||||||
|
fmt.Fprintf(w, "%d\t%s\t%s\t%t\t%t\t%t\n", u.ID, u.Name, u.Email, u.IsActive, u.IsAdmin, twofa[u.ID])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Flush()
|
||||||
|
return nil
|
||||||
|
}
|
58
cmd/admin_user_must_change_password.go
Normal file
58
cmd/admin_user_must_change_password.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
var microcmdUserMustChangePassword = cli.Command{
|
||||||
|
Name: "must-change-password",
|
||||||
|
Usage: "Set the must change password flag for the provided users or all users",
|
||||||
|
Action: runMustChangePassword,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "all,A",
|
||||||
|
Usage: "All users must change password, except those explicitly excluded with --exclude",
|
||||||
|
},
|
||||||
|
cli.StringSliceFlag{
|
||||||
|
Name: "exclude,e",
|
||||||
|
Usage: "Do not change the must-change-password flag for these users",
|
||||||
|
},
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "unset",
|
||||||
|
Usage: "Instead of setting the must-change-password flag, unset it",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func runMustChangePassword(c *cli.Context) error {
|
||||||
|
ctx, cancel := installSignals()
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if c.NArg() == 0 && !c.IsSet("all") {
|
||||||
|
return errors.New("either usernames or --all must be provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
mustChangePassword := !c.Bool("unset")
|
||||||
|
all := c.Bool("all")
|
||||||
|
exclude := c.StringSlice("exclude")
|
||||||
|
|
||||||
|
if err := initDB(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := user_model.SetMustChangePassword(ctx, all, mustChangePassword, c.Args(), exclude)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Updated %d users setting MustChangePassword to %t\n", n, mustChangePassword)
|
||||||
|
return nil
|
||||||
|
}
|
@ -99,6 +99,13 @@ Admin operations:
|
|||||||
- `--password value`, `-p value`: New password. Required.
|
- `--password value`, `-p value`: New password. Required.
|
||||||
- Examples:
|
- Examples:
|
||||||
- `gitea admin user change-password --username myname --password asecurepassword`
|
- `gitea admin user change-password --username myname --password asecurepassword`
|
||||||
|
- `must-change-password`:
|
||||||
|
- Args:
|
||||||
|
- `[username...]`: Users that must change their passwords
|
||||||
|
- Options:
|
||||||
|
- `--all`, `-A`: Force a password change for all users
|
||||||
|
- `--exclude username`, `-e username`: Exclude the given user. Can be set multiple times.
|
||||||
|
- `--unset`: Revoke forced password change for the given users
|
||||||
- `regenerate`
|
- `regenerate`
|
||||||
- Options:
|
- Options:
|
||||||
- `hooks`: Regenerate Git Hooks for all repositories
|
- `hooks`: Regenerate Git Hooks for all repositories
|
||||||
|
49
models/user/must_change_password.go
Normal file
49
models/user/must_change_password.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
|
"xorm.io/builder"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetMustChangePassword(ctx context.Context, all, mustChangePassword bool, include, exclude []string) (int64, error) {
|
||||||
|
sliceTrimSpaceDropEmpty := func(input []string) []string {
|
||||||
|
output := make([]string, 0, len(input))
|
||||||
|
for _, in := range input {
|
||||||
|
in = strings.ToLower(strings.TrimSpace(in))
|
||||||
|
if in == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
output = append(output, in)
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
var cond builder.Cond
|
||||||
|
|
||||||
|
// Only include the users where something changes to get an accurate count
|
||||||
|
cond = builder.Neq{"must_change_password": mustChangePassword}
|
||||||
|
|
||||||
|
if !all {
|
||||||
|
include = sliceTrimSpaceDropEmpty(include)
|
||||||
|
if len(include) == 0 {
|
||||||
|
return 0, util.NewSilentWrapErrorf(util.ErrInvalidArgument, "no users to include provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
cond = cond.And(builder.In("lower_name", include))
|
||||||
|
}
|
||||||
|
|
||||||
|
exclude = sliceTrimSpaceDropEmpty(exclude)
|
||||||
|
if len(exclude) > 0 {
|
||||||
|
cond = cond.And(builder.NotIn("lower_name", exclude))
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.GetEngine(ctx).Where(cond).MustCols("must_change_password").Update(&User{MustChangePassword: mustChangePassword})
|
||||||
|
}
|
@ -101,12 +101,13 @@ func (te *TreeEntry) FollowLinks() (*TreeEntry, error) {
|
|||||||
return entry, nil
|
return entry, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns the subtree, or nil if this is not a tree
|
// returns the Tree pointed to by this TreeEntry, or nil if this is not a tree
|
||||||
func (te *TreeEntry) Tree() *Tree {
|
func (te *TreeEntry) Tree() *Tree {
|
||||||
t, err := te.ptree.repo.getTree(te.ID)
|
t, err := te.ptree.repo.getTree(te.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
t.ptree = te.ptree
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<div class="ui dropdown selection tooltip{{if .CodeIndexerUnavailable}} disabled{{end}}" data-content="{{.locale.Tr "explore.search.type.tooltip"}}">
|
<div class="ui dropdown selection tooltip{{if .CodeIndexerUnavailable}} disabled{{end}}" data-content="{{.locale.Tr "explore.search.type.tooltip"}}">
|
||||||
<input name="t" type="hidden" value="{{.queryType}}"{{if .CodeIndexerUnavailable}} disabled{{end}}>{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
<input name="t" type="hidden" value="{{.queryType}}"{{if .CodeIndexerUnavailable}} disabled{{end}}>{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||||
<div class="text">{{.locale.Tr (printf "explore.search.%s" (or .queryType "fuzzy"))}}</div>
|
<div class="text">{{.locale.Tr (printf "explore.search.%s" (or .queryType "fuzzy"))}}</div>
|
||||||
<div class="menu transition hidden" tabindex="-1" style="display: block !important;">
|
<div class="menu">
|
||||||
<div class="item tooltip" data-value="" data-content="{{.locale.Tr "explore.search.fuzzy.tooltip"}}">{{.locale.Tr "explore.search.fuzzy"}}</div>
|
<div class="item tooltip" data-value="" data-content="{{.locale.Tr "explore.search.fuzzy.tooltip"}}">{{.locale.Tr "explore.search.fuzzy"}}</div>
|
||||||
<div class="item tooltip" data-value="match" data-content="{{.locale.Tr "explore.search.match.tooltip"}}">{{.locale.Tr "explore.search.match"}}</div>
|
<div class="item tooltip" data-value="match" data-content="{{.locale.Tr "explore.search.match.tooltip"}}">{{.locale.Tr "explore.search.match"}}</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -639,7 +639,7 @@
|
|||||||
|
|
||||||
<div class="default text"> </div>
|
<div class="default text"> </div>
|
||||||
|
|
||||||
<div class="menu transition hidden" tabindex="-1" style="display: block !important;">
|
<div class="menu">
|
||||||
{{range .LockReasons}}
|
{{range .LockReasons}}
|
||||||
<div class="item" data-value="{{.}}">{{.}}</div>
|
<div class="item" data-value="{{.}}">{{.}}</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<div class="ui dropdown selection tooltip{{if .CodeIndexerUnavailable}} disabled{{end}}" data-content="{{.locale.Tr "repo.search.type.tooltip"}}">
|
<div class="ui dropdown selection tooltip{{if .CodeIndexerUnavailable}} disabled{{end}}" data-content="{{.locale.Tr "repo.search.type.tooltip"}}">
|
||||||
<input name="t" type="hidden"{{if .CodeIndexerUnavailable}} disabled{{end}} value="{{.queryType}}">{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
<input name="t" type="hidden"{{if .CodeIndexerUnavailable}} disabled{{end}} value="{{.queryType}}">{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||||
<div class="text">{{.locale.Tr (printf "repo.search.%s" (or .queryType "fuzzy"))}}</div>
|
<div class="text">{{.locale.Tr (printf "repo.search.%s" (or .queryType "fuzzy"))}}</div>
|
||||||
<div class="menu transition hidden" tabindex="-1" style="display: block !important;">
|
<div class="menu">
|
||||||
<div class="item tooltip" data-value="" data-content="{{.locale.Tr "repo.search.fuzzy.tooltip"}}">{{.locale.Tr "repo.search.fuzzy"}}</div>
|
<div class="item tooltip" data-value="" data-content="{{.locale.Tr "repo.search.fuzzy.tooltip"}}">{{.locale.Tr "repo.search.fuzzy"}}</div>
|
||||||
<div class="item tooltip" data-value="match" data-content="{{.locale.Tr "repo.search.match.tooltip"}}">{{.locale.Tr "repo.search.match"}}</div>
|
<div class="item tooltip" data-value="match" data-content="{{.locale.Tr "repo.search.match.tooltip"}}">{{.locale.Tr "repo.search.match"}}</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
</select>{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
</select>{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||||
<div class="default text">{{.Repository.DefaultBranch}}</div>
|
<div class="default text">{{.Repository.DefaultBranch}}</div>
|
||||||
<div class="menu transition hidden" tabindex="-1" style="display: block !important;">
|
<div class="menu">
|
||||||
{{range .Branches}}
|
{{range .Branches}}
|
||||||
<div class="item" data-value="{{.}}">{{.}}</div>
|
<div class="item" data-value="{{.}}">{{.}}</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@ -538,7 +538,7 @@
|
|||||||
{{.locale.Tr "repo.pulls.squash_merge_pull_request"}}
|
{{.locale.Tr "repo.pulls.squash_merge_pull_request"}}
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
<div class="menu transition hidden" tabindex="-1" style="display: block !important;">
|
<div class="menu">
|
||||||
<div class="item" data-value="merge">{{.locale.Tr "repo.pulls.merge_pull_request"}}</div>
|
<div class="item" data-value="merge">{{.locale.Tr "repo.pulls.merge_pull_request"}}</div>
|
||||||
<div class="item" data-value="rebase">{{.locale.Tr "repo.pulls.rebase_merge_pull_request"}}</div>
|
<div class="item" data-value="rebase">{{.locale.Tr "repo.pulls.rebase_merge_pull_request"}}</div>
|
||||||
<div class="item" data-value="rebase-merge">{{.locale.Tr "repo.pulls.rebase_merge_commit_pull_request"}}</div>
|
<div class="item" data-value="rebase-merge">{{.locale.Tr "repo.pulls.rebase_merge_commit_pull_request"}}</div>
|
||||||
|
@ -268,6 +268,11 @@ export function initRepositoryActionView() {
|
|||||||
const el = document.getElementById('repo-action-view');
|
const el = document.getElementById('repo-action-view');
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
|
|
||||||
|
// TODO: the parent element's full height doesn't work well now,
|
||||||
|
// but we can not pollute the global style at the moment, only fix the height problem for pages with this component
|
||||||
|
const parentFullHeight = document.querySelector('body > div.full.height');
|
||||||
|
if (parentFullHeight) parentFullHeight.style.paddingBottom = '0';
|
||||||
|
|
||||||
const view = createApp(sfc, {
|
const view = createApp(sfc, {
|
||||||
runIndex: el.getAttribute('data-run-index'),
|
runIndex: el.getAttribute('data-run-index'),
|
||||||
jobIndex: el.getAttribute('data-job-index'),
|
jobIndex: el.getAttribute('data-job-index'),
|
||||||
@ -412,11 +417,6 @@ export function initRepositoryActionView() {
|
|||||||
<style lang="less">
|
<style lang="less">
|
||||||
// some elements are not managed by vue, so we need to use global style
|
// some elements are not managed by vue, so we need to use global style
|
||||||
|
|
||||||
// TODO: the parent element's full height doesn't work well now
|
|
||||||
body > div.full.height {
|
|
||||||
padding-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.job-status-rotate {
|
.job-status-rotate {
|
||||||
animation: job-status-rotate-keyframes 1s linear infinite;
|
animation: job-status-rotate-keyframes 1s linear infinite;
|
||||||
}
|
}
|
||||||
|
@ -651,6 +651,10 @@ a.commit-statuses-trigger {
|
|||||||
color: var(--color-text-dark);
|
color: var(--color-text-dark);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ui.search > .results .result .description {
|
||||||
|
color: var(--color-text-light-2);
|
||||||
|
}
|
||||||
|
|
||||||
.ui.search > .results .result .image {
|
.ui.search > .results .result .image {
|
||||||
width: auto;
|
width: auto;
|
||||||
height: auto;
|
height: auto;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user