Reviewed-by: 林嘉怡 <2441898885@qq.com> Reviewed-by: yuyuanshifu <747342561@qq.com>tags/v1.21.12.1
| @@ -0,0 +1,101 @@ | |||
| package models | |||
| import ( | |||
| "code.gitea.io/gitea/modules/timeutil" | |||
| "fmt" | |||
| "time" | |||
| ) | |||
| type BlockChainIssueStatus int | |||
| const ( | |||
| BlockChainIssueInit BlockChainIssueStatus = iota | |||
| BlockChainIssueSuccess | |||
| BlockChainIssueFailed | |||
| ) | |||
| type BlockChainIssue struct { | |||
| ID int64 `xorm:"pk autoincr"` | |||
| IssueID int64 `xorm:"INDEX NOT NULL unique"` | |||
| Contributor string `xorm:"INDEX NOT NULL"` | |||
| ContractAddress string `xorm:"INDEX NOT NULL"` | |||
| Status BlockChainIssueStatus `xorm:"INDEX NOT NULL DEFAULT 0"` | |||
| Amount int64 `xorm:"INDEX"` | |||
| UserID int64 `xorm:"INDEX"` | |||
| RepoID int64 `xorm:"INDEX"` | |||
| TransactionHash string `xorm:"INDEX"` | |||
| CreatedUnix timeutil.TimeStamp `xorm:"created"` | |||
| UpdatedUnix timeutil.TimeStamp `xorm:"updated"` | |||
| DeletedAt time.Time `xorm:"deleted"` | |||
| User *User `xorm:"-"` | |||
| Repo *Repository `xorm:"-"` | |||
| } | |||
| func getBlockChainIssueByID(e Engine, id int64) (*BlockChainIssue, error) { | |||
| blockChainIssue := new(BlockChainIssue) | |||
| has, err := e.ID(id).Get(blockChainIssue) | |||
| if err != nil { | |||
| return nil, err | |||
| } else if !has { | |||
| return nil, fmt.Errorf("get block_chain by id failed(%d)", id) | |||
| } | |||
| return blockChainIssue, nil | |||
| } | |||
| func GetBlockChainIssueByID(id int64) (*BlockChainIssue, error) { | |||
| return getBlockChainIssueByID(x, id) | |||
| } | |||
| func getBlockChainIssueByPrID(e Engine, prId int64) (*BlockChainIssue, error) { | |||
| blockChainIssue := new(BlockChainIssue) | |||
| has, err := e.Where("pr_id = ?", prId).Get(blockChainIssue) | |||
| if err != nil { | |||
| return nil, err | |||
| } else if !has { | |||
| return nil, fmt.Errorf("get block_chain by pr_id failed(%d)", prId) | |||
| } | |||
| return blockChainIssue, nil | |||
| } | |||
| func GetBlockChainIssueByPrID(prId int64) (*BlockChainIssue, error) { | |||
| return getBlockChainIssueByPrID(x, prId) | |||
| } | |||
| func getBlockChainIssueByCommitID(e Engine, commitID string) (*BlockChainIssue, error) { | |||
| blockChainIssue := new(BlockChainIssue) | |||
| has, err := e.Where("commit_id = ?", commitID).Get(blockChainIssue) | |||
| if err != nil { | |||
| return nil, err | |||
| } else if !has { | |||
| return nil, fmt.Errorf("get block_chain_issue by commitID failed(%s)", commitID) | |||
| } | |||
| return blockChainIssue, nil | |||
| } | |||
| func GetBlockChainIssueByCommitID(commitID string) (*BlockChainIssue, error) { | |||
| return getBlockChainIssueByCommitID(x, commitID) | |||
| } | |||
| func updateBlockChainIssueCols(e Engine, blockChainIssue *BlockChainIssue, cols ...string) error { | |||
| _, err := e.ID(blockChainIssue.ID).Cols(cols...).Update(blockChainIssue) | |||
| return err | |||
| } | |||
| func UpdateBlockChainIssueCols(blockChainIssue *BlockChainIssue, cols ...string) error { | |||
| return updateBlockChainIssueCols(x, blockChainIssue, cols...) | |||
| } | |||
| func GetBlockChainUnSuccessIssues() ([]*BlockChainIssue, error) { | |||
| blockChainIssues := make([]*BlockChainIssue, 0, 10) | |||
| return blockChainIssues, x. | |||
| Where("status != ?", BlockChainIssueSuccess). | |||
| Find(&blockChainIssues) | |||
| } | |||
| func InsertBlockChainIssue(blockChainIssue *BlockChainIssue) (_ *BlockChainIssue, err error) { | |||
| if _, err := x.Insert(blockChainIssue); err != nil { | |||
| return nil, err | |||
| } | |||
| return blockChainIssue, nil | |||
| } | |||
| @@ -15,18 +15,19 @@ const ( | |||
| ) | |||
| type BlockChain struct { | |||
| ID int64 `xorm:"pk autoincr"` | |||
| CommitID string `xorm:"INDEX NOT NULL"` | |||
| Contributor string `xorm:"INDEX NOT NULL"` | |||
| ContractAddress string `xorm:"INDEX NOT NULL"` | |||
| Status BlockChainCommitStatus `xorm:"INDEX NOT NULL DEFAULT 0"` | |||
| Amount int64 `xorm:"INDEX"` | |||
| UserID int64 `xorm:"INDEX"` | |||
| RepoID int64 `xorm:"INDEX"` | |||
| TransactionHash string `xorm:"INDEX"` | |||
| CreatedUnix timeutil.TimeStamp `xorm:"created"` | |||
| UpdatedUnix timeutil.TimeStamp `xorm:"updated"` | |||
| DeletedAt time.Time `xorm:"deleted"` | |||
| ID int64 `xorm:"pk autoincr"` | |||
| PrID int64 `xorm:"INDEX NOT NULL unique"` | |||
| CommitID string `xorm:"INDEX NOT NULL unique"` | |||
| Contributor string `xorm:"INDEX NOT NULL"` | |||
| ContractAddress string `xorm:"INDEX NOT NULL"` | |||
| Status BlockChainCommitStatus `xorm:"INDEX NOT NULL DEFAULT 0"` | |||
| Amount int64 `xorm:"INDEX"` | |||
| UserID int64 `xorm:"INDEX"` | |||
| RepoID int64 `xorm:"INDEX"` | |||
| TransactionHash string `xorm:"INDEX"` | |||
| CreatedUnix timeutil.TimeStamp `xorm:"created"` | |||
| UpdatedUnix timeutil.TimeStamp `xorm:"updated"` | |||
| DeletedAt time.Time `xorm:"deleted"` | |||
| User *User `xorm:"-"` | |||
| Repo *Repository `xorm:"-"` | |||
| @@ -47,6 +48,21 @@ func GetBlockChainByID(id int64) (*BlockChain, error) { | |||
| return getBlockChainByID(x, id) | |||
| } | |||
| func getBlockChainByPrID(e Engine, prId int64) (*BlockChain, error) { | |||
| blockChain := new(BlockChain) | |||
| has, err := e.Where("pr_id = ?", prId).Get(blockChain) | |||
| if err != nil { | |||
| return nil, err | |||
| } else if !has { | |||
| return nil, fmt.Errorf("get block_chain by pr_id failed(%d)", prId) | |||
| } | |||
| return blockChain, nil | |||
| } | |||
| func GetBlockChainByPrID(prId int64) (*BlockChain, error) { | |||
| return getBlockChainByPrID(x, prId) | |||
| } | |||
| func getBlockChainByCommitID(e Engine, commitID string) (*BlockChain, error) { | |||
| blockChain := new(BlockChain) | |||
| has, err := e.Where("commit_id = ?", commitID).Get(blockChain) | |||
| @@ -66,6 +66,10 @@ type Issue struct { | |||
| // IsLocked limits commenting abilities to users on an issue | |||
| // with write access | |||
| IsLocked bool `xorm:"NOT NULL DEFAULT false"` | |||
| //block_chain | |||
| Amount int64 | |||
| IsTransformed bool `xorm:"INDEX NOT NULL DEFAULT false"` | |||
| } | |||
| var ( | |||
| @@ -36,6 +36,13 @@ const ( | |||
| PullRequestStatusError | |||
| ) | |||
| const ( | |||
| PullRequestAmountZero int = 0 | |||
| PullRequestAmountOne int = 100 | |||
| PullRequestAmountTwo int = 200 | |||
| PullRequestAmountMax int = 300 | |||
| ) | |||
| // PullRequest represents relation between pull request and repositories. | |||
| type PullRequest struct { | |||
| ID int64 `xorm:"pk autoincr"` | |||
| @@ -65,6 +72,10 @@ type PullRequest struct { | |||
| MergedUnix timeutil.TimeStamp `xorm:"updated INDEX"` | |||
| isHeadRepoLoaded bool `xorm:"-"` | |||
| //block_chain | |||
| IsTransformed bool `xorm:"INDEX NOT NULL DEFAULT false"` | |||
| Amount int `xorm:"INDEX NOT NULL DEFAULT 0"` | |||
| } | |||
| // MustHeadUserName returns the HeadRepo's username if failed return blank | |||
| @@ -396,7 +407,7 @@ func (pr *PullRequest) SetMerged() (bool, error) { | |||
| return false, fmt.Errorf("Issue.changeStatus: %v", err) | |||
| } | |||
| if _, err := sess.Where("id = ?", pr.ID).Cols("has_merged, status, merged_commit_id, merger_id, merged_unix").Update(pr); err != nil { | |||
| if _, err := sess.Where("id = ?", pr.ID).Cols("has_merged, status, merged_commit_id, merger_id, merged_unix, amount").Update(pr); err != nil { | |||
| return false, fmt.Errorf("Failed to update pr[%d]: %v", pr.ID, err) | |||
| } | |||
| @@ -5,6 +5,7 @@ | |||
| package models | |||
| import ( | |||
| "code.gitea.io/gitea/modules/setting" | |||
| "fmt" | |||
| "code.gitea.io/gitea/modules/base" | |||
| @@ -170,3 +171,11 @@ func (prs PullRequestList) invalidateCodeComments(e Engine, doer *User, repo *gi | |||
| func (prs PullRequestList) InvalidateCodeComments(doer *User, repo *git.Repository, branch string) error { | |||
| return prs.invalidateCodeComments(x, doer, repo, branch) | |||
| } | |||
| func GetUnTransformedMergedPullRequests() ([]*PullRequest, error) { | |||
| prs := make([]*PullRequest, 0, 10) | |||
| return prs, x. | |||
| Where("has_merged = ? AND pull_request.is_transformed = ? AND to_timestamp(merged_unix) >= ?",true, false, setting.CommitValidDate). | |||
| Join("INNER", "issue", "issue.id = pull_request.issue_id"). | |||
| Find(&prs) | |||
| } | |||
| @@ -206,9 +206,9 @@ type Repository struct { | |||
| Avatar string `xorm:"VARCHAR(64)"` | |||
| //blockchain | |||
| ContractAddress string `xorm:"INDEX"` | |||
| Balance int64 `xorm:"NOT NULL DEFAULT 0"` | |||
| BlockChainStatus RepoBlockChainStatus `xorm:"NOT NULL DEFAULT 0"` | |||
| ContractAddress string `xorm:"INDEX"` | |||
| Balance string `xorm:"NOT NULL DEFAULT '0'"` | |||
| BlockChainStatus RepoBlockChainStatus `xorm:"NOT NULL DEFAULT 0"` | |||
| // git clone total count | |||
| CloneCnt int64 `xorm:"NOT NULL DEFAULT 0"` | |||
| @@ -1105,6 +1105,12 @@ func CreateRepository(ctx DBContext, doer, u *User, repo *Repository) (err error | |||
| Type: tp, | |||
| Config: &CloudBrainConfig{EnableCloudBrain: true}, | |||
| }) | |||
| } else if tp == UnitTypeBlockChain { | |||
| units = append(units, RepoUnit{ | |||
| RepoID: repo.ID, | |||
| Type: tp, | |||
| Config: &BlockChainConfig{EnableBlockChain: true}, | |||
| }) | |||
| } else { | |||
| units = append(units, RepoUnit{ | |||
| RepoID: repo.ID, | |||
| @@ -141,6 +141,20 @@ func (cfg *CloudBrainConfig) ToDB() ([]byte, error) { | |||
| return json.Marshal(cfg) | |||
| } | |||
| type BlockChainConfig struct { | |||
| EnableBlockChain bool | |||
| } | |||
| // FromDB fills up a CloudBrainConfig from serialized format. | |||
| func (cfg *BlockChainConfig) FromDB(bs []byte) error { | |||
| return json.Unmarshal(bs, &cfg) | |||
| } | |||
| // ToDB exports a CloudBrainConfig to a serialized format. | |||
| func (cfg *BlockChainConfig) ToDB() ([]byte, error) { | |||
| return json.Marshal(cfg) | |||
| } | |||
| // BeforeSet is invoked from XORM before setting the value of a field of this object. | |||
| func (r *RepoUnit) BeforeSet(colName string, val xorm.Cell) { | |||
| switch colName { | |||
| @@ -160,6 +174,8 @@ func (r *RepoUnit) BeforeSet(colName string, val xorm.Cell) { | |||
| r.Config = new(DatasetConfig) | |||
| case UnitTypeCloudBrain: | |||
| r.Config = new(CloudBrainConfig) | |||
| case UnitTypeBlockChain: | |||
| r.Config = new(BlockChainConfig) | |||
| default: | |||
| panic("unrecognized repo unit type: " + com.ToStr(*val)) | |||
| } | |||
| @@ -26,6 +26,7 @@ const ( | |||
| UnitTypeExternalTracker // 7 ExternalTracker | |||
| UnitTypeDatasets UnitType = 10 // 10 Dataset | |||
| UnitTypeCloudBrain UnitType = 11 // 11 CloudBrain | |||
| UnitTypeBlockChain UnitType = 12 // 12 BlockChain | |||
| ) | |||
| // Value returns integer value for unit type | |||
| @@ -53,6 +54,8 @@ func (u UnitType) String() string { | |||
| return "UnitTypeDataset" | |||
| case UnitTypeCloudBrain: | |||
| return "UnitTypeCloudBrain" | |||
| case UnitTypeBlockChain: | |||
| return "UnitTypeBlockChain" | |||
| } | |||
| return fmt.Sprintf("Unknown UnitType %d", u) | |||
| } | |||
| @@ -76,6 +79,7 @@ var ( | |||
| UnitTypeExternalTracker, | |||
| UnitTypeDatasets, | |||
| UnitTypeCloudBrain, | |||
| UnitTypeBlockChain, | |||
| } | |||
| // DefaultRepoUnits contains the default unit types | |||
| @@ -87,6 +91,7 @@ var ( | |||
| UnitTypeWiki, | |||
| UnitTypeDatasets, | |||
| UnitTypeCloudBrain, | |||
| UnitTypeBlockChain, | |||
| } | |||
| // NotAllowedDefaultRepoUnits contains units that can't be default | |||
| @@ -268,6 +273,14 @@ var ( | |||
| 6, | |||
| } | |||
| UnitBlockChain = Unit{ | |||
| UnitTypeBlockChain, | |||
| "repo.blockchains", | |||
| "/blockchains", | |||
| "repo.blockchains.desc", | |||
| 7, | |||
| } | |||
| // Units contains all the units | |||
| Units = map[UnitType]Unit{ | |||
| UnitTypeCode: UnitCode, | |||
| @@ -279,6 +292,7 @@ var ( | |||
| UnitTypeExternalWiki: UnitExternalWiki, | |||
| UnitTypeDatasets: UnitDataset, | |||
| UnitTypeCloudBrain: UnitCloudBrain, | |||
| UnitTypeBlockChain: UnitBlockChain, | |||
| } | |||
| ) | |||
| @@ -174,8 +174,8 @@ type User struct { | |||
| Token string `xorm:"VARCHAR(1024)"` | |||
| //BlockChain | |||
| PublicKey string `xorm` | |||
| PrivateKey string `xorm` | |||
| PublicKey string `xorm:"INDEX"` | |||
| PrivateKey string `xorm:"INDEX"` | |||
| } | |||
| // SearchOrganizationsOptions options to filter organizations | |||
| @@ -369,6 +369,7 @@ type CreateIssueForm struct { | |||
| AssigneeID int64 | |||
| Content string | |||
| Files []string | |||
| Rewards int64 | |||
| } | |||
| // Validate validates the fields | |||
| @@ -489,6 +490,7 @@ type MergePullRequestForm struct { | |||
| MergeTitleField string | |||
| MergeMessageField string | |||
| ForceMerge *bool `json:"force_merge,omitempty"` | |||
| BlockChainAmount int | |||
| } | |||
| // Validate validates the fields | |||
| @@ -2,6 +2,7 @@ package blockchain | |||
| import ( | |||
| "fmt" | |||
| "strconv" | |||
| "code.gitea.io/gitea/modules/setting" | |||
| "github.com/go-resty/resty/v2" | |||
| @@ -13,11 +14,10 @@ var ( | |||
| const ( | |||
| UrlCreateAccount = "createAccount" | |||
| UrlGetBalance = "getBalance" | |||
| UrlNewRepo = "newRepo" | |||
| UrlContribute = "contribute" | |||
| ActionCommit = "commit" | |||
| UrlGetBalance = "getBalance" | |||
| UrlNewRepo = "newRepo" | |||
| UrlContribute = "contribute" | |||
| UrlSetIssue = "setIssue" | |||
| Success = 0 | |||
| ) | |||
| @@ -31,7 +31,7 @@ type CreateAccountResult struct { | |||
| type GetBalanceResult struct { | |||
| Code int `json:"code"` | |||
| Msg string `json:"message"` | |||
| Payload map[string]interface{} `json:"data"` | |||
| Data string `json:"data"` | |||
| } | |||
| type NewRepoResult struct { | |||
| @@ -41,9 +41,15 @@ type NewRepoResult struct { | |||
| } | |||
| type ContributeResult struct { | |||
| Code int `json:"code"` | |||
| Msg string `json:"message"` | |||
| Payload map[string]interface{} `json:"data"` | |||
| Code int `json:"code"` | |||
| Msg string `json:"message"` | |||
| //Payload map[string]interface{} `json:"data"` | |||
| } | |||
| type SetIssueResult struct { | |||
| Code int `json:"code"` | |||
| Msg string `json:"message"` | |||
| //Data string `json:"data"` | |||
| } | |||
| func getRestyClient() *resty.Client { | |||
| @@ -122,18 +128,18 @@ func GetBalance(contractAddress, contributor string) (*GetBalanceResult, error) | |||
| return &result, nil | |||
| } | |||
| func Contribute(contractAddress, contributor, action, commitId string, codeLine int) (*ContributeResult, error) { | |||
| func Contribute(contractAddress, contributor, commitId string, amount int64) (*ContributeResult, error) { | |||
| client := getRestyClient() | |||
| var result ContributeResult | |||
| strAmount := strconv.FormatInt(amount, 10) | |||
| res, err := client.R(). | |||
| SetHeader("Accept", "application/json"). | |||
| SetQueryParams(map[string]string{ | |||
| "contractAddress": contractAddress, | |||
| "contributor": contributor, | |||
| "action": action, | |||
| "commitId": commitId, | |||
| "amount": string(codeLine), | |||
| "contractAddress" : contractAddress, | |||
| "contributor" : contributor, | |||
| "commitId": commitId, | |||
| "amount": strAmount, | |||
| }). | |||
| SetResult(&result). | |||
| Get(setting.BlockChainHost + UrlContribute) | |||
| @@ -148,3 +154,31 @@ func Contribute(contractAddress, contributor, action, commitId string, codeLine | |||
| return &result, nil | |||
| } | |||
| func SetIssue(contractAddress, contributor string, issueId int64, amount int64) (*SetIssueResult, error) { | |||
| client := getRestyClient() | |||
| var result SetIssueResult | |||
| strAmount := strconv.FormatInt(amount, 10) | |||
| strIssue := strconv.FormatInt(issueId, 10) | |||
| res, err := client.R(). | |||
| SetHeader("Accept", "application/json"). | |||
| SetQueryParams(map[string]string{ | |||
| "contractAddress" : contractAddress, | |||
| "contributor" : contributor, | |||
| "issueId": strIssue, | |||
| "amount": strAmount, | |||
| }). | |||
| SetResult(&result). | |||
| Get(setting.BlockChainHost + UrlSetIssue) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("resty SetIssue: %v", err) | |||
| } | |||
| if result.Code != Success { | |||
| return &result, fmt.Errorf("SetIssue err: %s", res.String()) | |||
| } | |||
| return &result, nil | |||
| } | |||
| @@ -820,5 +820,6 @@ func UnitTypes() macaron.Handler { | |||
| ctx.Data["UnitTypeWiki"] = models.UnitTypeWiki | |||
| ctx.Data["UnitTypeExternalWiki"] = models.UnitTypeExternalWiki | |||
| ctx.Data["UnitTypeExternalTracker"] = models.UnitTypeExternalTracker | |||
| ctx.Data["UnitTypeBlockChain"] = models.UnitTypeBlockChain | |||
| } | |||
| } | |||
| @@ -12,16 +12,16 @@ func init() { | |||
| spec := "*/10 * * * *" | |||
| c.AddFunc(spec, repo.HandleUnDecompressAttachment) | |||
| //specCheckBlockChainUserSuccess := "*/10 * * * *" | |||
| //c.AddFunc(specCheckBlockChainUserSuccess, repo.HandleBlockChainUnSuccessUsers) | |||
| specCheckBlockChainUserSuccess := "*/10 * * * *" | |||
| c.AddFunc(specCheckBlockChainUserSuccess, repo.HandleBlockChainUnSuccessUsers) | |||
| //specCheckRepoBlockChainSuccess := "*/5 * * * *" | |||
| //c.AddFunc(specCheckRepoBlockChainSuccess, repo.HandleBlockChainUnSuccessRepos) | |||
| specCheckRepoBlockChainSuccess := "*/1 * * * *" | |||
| c.AddFunc(specCheckRepoBlockChainSuccess, repo.HandleBlockChainUnSuccessRepos) | |||
| //specCheckUnTransformedActions := "*/1 * * * *" | |||
| //c.AddFunc(specCheckUnTransformedActions, repo.HandleUnTransformedActions) | |||
| specCheckUnTransformedPRs := "*/1 * * * *" | |||
| c.AddFunc(specCheckUnTransformedPRs, repo.HandleBlockChainMergedPulls) | |||
| //specCheckBlockChainCommitSuccess := "*/3 * * * *" | |||
| //c.AddFunc(specCheckBlockChainCommitSuccess, repo.HandleBlockChainUnSuccessCommits) | |||
| specCheckBlockChainCommitSuccess := "*/3 * * * *" | |||
| c.AddFunc(specCheckBlockChainCommitSuccess, repo.HandleBlockChainUnSuccessCommits) | |||
| c.Start() | |||
| } | |||
| @@ -268,7 +268,7 @@ twofa_passcode_incorrect=你的验证码不正确。如果你丢失了你的设 | |||
| twofa_scratch_token_incorrect=你的验证口令不正确。 | |||
| login_userpass=登录 | |||
| login_openid=OpenID | |||
| login_cloudbrain=云脑用户登录 | |||
| login_cloudbrain=用户登录 | |||
| oauth_signup_tab=注册帐号 | |||
| oauth_signup_title=添加电子邮件和密码 (用于帐号恢复) | |||
| oauth_signup_submit=完成账号 | |||
| @@ -753,7 +753,10 @@ cloudbrain.new=新建任务 | |||
| cloudbrain.desc=云脑功能 | |||
| cloudbrain.cancel=取消 | |||
| cloudbrain.commit_image=提交 | |||
| clone_cnt=次下载 | |||
| balance=余额 | |||
| balance.total_view=余额总览 | |||
| balance.available=可用余额: | |||
| balance.disable=不可用余额: | |||
| template.items=模板选项 | |||
| template.git_content=Git数据(默认分支) | |||
| @@ -936,6 +939,7 @@ issues.filter_labels=筛选标签 | |||
| issues.filter_reviewers=筛选审核者 | |||
| issues.new=创建任务 | |||
| issues.new.title_empty=标题不能为空 | |||
| issues.new.rewards_error=奖励金额错误:不能小于0,且不能大于自身余额 | |||
| issues.new.labels=标签 | |||
| issues.new.add_labels_title=添加标签 | |||
| issues.new.no_label=未选择标签 | |||
| @@ -1,14 +1,14 @@ | |||
| package repo | |||
| import ( | |||
| "code.gitea.io/gitea/modules/repository" | |||
| "encoding/json" | |||
| "strconv" | |||
| "code.gitea.io/gitea/models" | |||
| "code.gitea.io/gitea/modules/base" | |||
| "code.gitea.io/gitea/modules/blockchain" | |||
| "code.gitea.io/gitea/modules/context" | |||
| "code.gitea.io/gitea/modules/log" | |||
| "encoding/json" | |||
| "net/http" | |||
| "strconv" | |||
| ) | |||
| type BlockChainInitNotify struct { | |||
| @@ -20,6 +20,29 @@ type BlockChainCommitNotify struct { | |||
| CommitID string `json:"commitId"` | |||
| TransactionHash string `json:"txHash"` | |||
| } | |||
| const ( | |||
| tplBlockChainIndex base.TplName = "repo/blockchain/index" | |||
| ) | |||
| func BlockChainIndex(ctx *context.Context) { | |||
| repo := ctx.Repo.Repository | |||
| if repo.ContractAddress == "" || ctx.User.PublicKey == ""{ | |||
| log.Error("the repo(%d) or the user(%d) has not been initialized in block_chain", repo.RepoID, ctx.User.ID) | |||
| ctx.HTML(http.StatusInternalServerError, tplBlockChainIndex) | |||
| return | |||
| } | |||
| res, err := blockchain.GetBalance(repo.ContractAddress, ctx.User.PublicKey) | |||
| if err != nil { | |||
| log.Error("GetBalance(%s) failed:%v", ctx.User.PublicKey, err) | |||
| ctx.HTML(http.StatusInternalServerError, tplBlockChainIndex) | |||
| return | |||
| } | |||
| ctx.Data["balance"] = res.Data | |||
| ctx.Data["PageIsBlockChain"] = true | |||
| ctx.HTML(200, tplBlockChainIndex) | |||
| } | |||
| func HandleBlockChainInitNotify(ctx *context.Context) { | |||
| var req BlockChainInitNotify | |||
| @@ -130,7 +153,6 @@ func HandleBlockChainUnSuccessRepos() { | |||
| continue | |||
| } | |||
| strRepoID := strconv.FormatInt(repo.ID, 10) | |||
| log.Info(strRepoID) | |||
| _, err = blockchain.NewRepo(strRepoID, repo.Owner.PublicKey, repo.Name) | |||
| if err != nil { | |||
| log.Error("blockchain.NewRepo(%s) failed:%v", strRepoID, err) | |||
| @@ -148,7 +170,7 @@ func HandleBlockChainUnSuccessCommits() { | |||
| } | |||
| for _, block_chain := range blockChains { | |||
| _, err = blockchain.Contribute(block_chain.ContractAddress, block_chain.Contributor, blockchain.ActionCommit, block_chain.CommitID, int(block_chain.Amount)) | |||
| _, err = blockchain.Contribute(block_chain.ContractAddress, block_chain.Contributor, block_chain.CommitID, block_chain.Amount) | |||
| if err != nil { | |||
| log.Error("blockchain.Contribute(%s) failed:%v", block_chain.CommitID, err) | |||
| } | |||
| @@ -180,71 +202,89 @@ func HandleBlockChainUnSuccessUsers() { | |||
| return | |||
| } | |||
| func HandleUnTransformedActions() { | |||
| actions, err := models.GetUnTransformedActions() | |||
| func HandleBlockChainMergedPulls() { | |||
| prs, err := models.GetUnTransformedMergedPullRequests() | |||
| if err != nil { | |||
| log.Error("GetUnTransformedActions failed:", err.Error()) | |||
| log.Error("GetUnTransformedMergedPullRequests failed:", err.Error()) | |||
| return | |||
| } | |||
| isTransformed := true | |||
| for _, pr := range prs { | |||
| _, err = models.GetBlockChainByPrID(pr.ID) | |||
| if err == nil { | |||
| log.Info("the pr(%s) has been transformed", pr.MergedCommitID) | |||
| continue | |||
| } | |||
| for _, action := range actions { | |||
| var content repository.PushCommits | |||
| err = json.Unmarshal([]byte(action.Content), &content) | |||
| err = pr.LoadIssue() | |||
| if err != nil { | |||
| isTransformed = false | |||
| log.Error("json.Unmarshal action.Content(%s) failed:%v", action.Content, err) | |||
| break | |||
| log.Error("LoadIssue(%s) failed:%v", pr.MergedCommitID, err) | |||
| continue | |||
| } | |||
| repo, err := models.GetRepositoryByID(action.RepoID) | |||
| poster, err := models.GetUserByID(pr.Issue.PosterID) | |||
| if err != nil { | |||
| isTransformed = false | |||
| log.Error("GetRepositoryByID(%d) failed:%v", action.RepoID, err) | |||
| break | |||
| log.Error("GetUserByID(%s) failed:%v", pr.MergedCommitID, err) | |||
| continue | |||
| } | |||
| if len(poster.PrivateKey) == 0 || len(poster.PublicKey) == 0 { | |||
| log.Error("the user has not been init in block_chain:", poster.Name) | |||
| continue | |||
| } | |||
| if repo.ContractAddress == "" { | |||
| isTransformed = false | |||
| repo, err := models.GetRepositoryByID(pr.HeadRepoID) | |||
| if err != nil { | |||
| log.Error("GetUserByID(%s) failed:%v", pr.MergedCommitID, err) | |||
| continue | |||
| } | |||
| if len(repo.ContractAddress) == 0 { | |||
| log.Error("the repo(%s) has not been initialized in block_chain", repo.Name) | |||
| break | |||
| continue | |||
| } | |||
| for _, commit := range content.Commits { | |||
| _, err = models.GetBlockChainByCommitID(commit.Sha1) | |||
| if err == nil { | |||
| log.Info("the commit(%s) has been transformed", commit.Sha1) | |||
| continue | |||
| } | |||
| user, err := models.GetUserByName(commit.CommitterName) | |||
| if err != nil { | |||
| isTransformed = false | |||
| log.Error("GetUserByName(%s) failed:%v", commit.CommitterName, err) | |||
| break | |||
| } | |||
| blockChain := models.BlockChain{ | |||
| CommitID: commit.Sha1, | |||
| Contributor: user.PublicKey, | |||
| ContractAddress: repo.ContractAddress, | |||
| Status: models.BlockChainCommitInit, | |||
| Amount: 1, | |||
| UserID: action.UserID, | |||
| RepoID: action.RepoID, | |||
| } | |||
| _, err = models.InsertBlockChain(&blockChain) | |||
| if err != nil { | |||
| isTransformed = false | |||
| log.Error("InsertBlockChain(%s) failed:%v", commit.Sha1, err) | |||
| break | |||
| } | |||
| blockChain := models.BlockChain{ | |||
| Contributor : poster.PublicKey, | |||
| PrID : pr.ID, | |||
| CommitID : pr.MergedCommitID, | |||
| ContractAddress : repo.ContractAddress, | |||
| Status : models.BlockChainCommitInit, | |||
| Amount : int64(pr.Amount), | |||
| UserID : poster.ID, | |||
| RepoID : pr.HeadRepoID, | |||
| } | |||
| _, err = models.InsertBlockChain(&blockChain) | |||
| if err != nil { | |||
| log.Error("InsertBlockChain(%s) failed:%v", pr.MergedCommitID, err) | |||
| continue | |||
| } | |||
| pr.IsTransformed = true | |||
| pr.UpdateCols("is_transformed") | |||
| _, err = blockchain.Contribute(repo.ContractAddress, poster.PublicKey, pr.MergedCommitID, int64(pr.Amount)) | |||
| if err != nil { | |||
| log.Error("Contribute(%s) failed:%v", pr.MergedCommitID, err) | |||
| } | |||
| } | |||
| log.Info("", isTransformed) | |||
| return | |||
| } | |||
| func HandleBlockChainUnSuccessIssues() { | |||
| issues, err := models.GetBlockChainUnSuccessCommits() | |||
| if err != nil { | |||
| log.Error("GetBlockChainUnSuccessIssues failed:", err.Error()) | |||
| return | |||
| } | |||
| for _, issue := range issues { | |||
| _, err = blockchain.SetIssue(issue.ContractAddress, issue.Contributor, issue.ID, issue.Amount) | |||
| if err != nil { | |||
| log.Error("SetIssue(%s) failed:%v", issue.CommitID, err) | |||
| } | |||
| } | |||
| return | |||
| } | |||
| @@ -509,6 +509,21 @@ func NewIssue(ctx *context.Context) { | |||
| ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWrite(models.UnitTypeIssues) | |||
| /*if ctx.Repo.Repository.ContractAddress == "" || ctx.User.PublicKey == ""{ | |||
| log.Error("the repo(%d) or the user(%d) has not been initialized in block_chain", ctx.Repo.Repository.ID, ctx.User.ID) | |||
| ctx.HTML(http.StatusInternalServerError, tplIssueNew) | |||
| return | |||
| } | |||
| res, err := blockchain.GetBalance(ctx.Repo.Repository.ContractAddress, ctx.User.PublicKey) | |||
| if err != nil { | |||
| log.Error("GetBalance(%s) failed:%v", ctx.User.PublicKey, err) | |||
| ctx.HTML(http.StatusInternalServerError, tplIssueNew) | |||
| return | |||
| } | |||
| ctx.Data["balance"] = res.Data*/ | |||
| ctx.HTML(200, tplIssueNew) | |||
| } | |||
| @@ -637,6 +652,33 @@ func NewIssuePost(ctx *context.Context, form auth.CreateIssueForm) { | |||
| Ref: form.Ref, | |||
| } | |||
| /*if repo.ContractAddress == "" || ctx.User.PublicKey == ""{ | |||
| log.Error("the repo(%d) or the user(%d) has not been initialized in block_chain", issue.RepoID, ctx.User.ID) | |||
| ctx.HTML(http.StatusInternalServerError, tplIssueNew) | |||
| return | |||
| } | |||
| res, err := blockchain.GetBalance(repo.ContractAddress, ctx.User.PublicKey) | |||
| if err != nil { | |||
| log.Error("GetBalance(%s) failed:%v", ctx.User.PublicKey, err) | |||
| ctx.HTML(http.StatusInternalServerError, tplIssueNew) | |||
| return | |||
| } | |||
| balance, err := com.StrTo(res.Data).Int64() | |||
| if err != nil { | |||
| log.Error("balance(%s) convert failed:%v", res.Data, err) | |||
| ctx.HTML(http.StatusInternalServerError, tplIssueNew) | |||
| return | |||
| } | |||
| if form.Rewards < 0 || form.Rewards > balance { | |||
| ctx.RenderWithErr(ctx.Tr("repo.issues.new.rewards_error"), tplIssueNew, form) | |||
| return | |||
| } | |||
| issue.Amount = form.Rewards*/ | |||
| if err := issue_service.NewIssue(repo, issue, labelIDs, attachments, assigneeIDs); err != nil { | |||
| if models.IsErrUserDoesNotHaveAccessToRepo(err) { | |||
| ctx.Error(400, "UserDoesNotHaveAccessToRepo", err.Error()) | |||
| @@ -810,6 +810,14 @@ func MergePullRequest(ctx *context.Context, form auth.MergePullRequestForm) { | |||
| return | |||
| } | |||
| if form.BlockChainAmount < int(models.PullRequestAmountZero) || form.BlockChainAmount > int(models.PullRequestAmountMax) { | |||
| log.Error("amount set error(0-300)") | |||
| ctx.RenderWithErr("amount set error(0-300)", tplIssueView, form) | |||
| return | |||
| } | |||
| pr.Amount = form.BlockChainAmount | |||
| if err = pull_service.Merge(pr, ctx.User, ctx.Repo.GitRepo, models.MergeStyle(form.Do), message); err != nil { | |||
| if models.IsErrInvalidMergeStyle(err) { | |||
| ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option")) | |||
| @@ -568,6 +568,8 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
| reqRepoDatasetWriter := context.RequireRepoWriter(models.UnitTypeDatasets) | |||
| reqRepoCloudBrainReader := context.RequireRepoReader(models.UnitTypeCloudBrain) | |||
| reqRepoCloudBrainWriter := context.RequireRepoWriter(models.UnitTypeCloudBrain) | |||
| //reqRepoBlockChainReader := context.RequireRepoReader(models.UnitTypeBlockChain) | |||
| //reqRepoBlockChainWriter := context.RequireRepoWriter(models.UnitTypeBlockChain) | |||
| // ***** START: Organization ***** | |||
| m.Group("/org", func() { | |||
| @@ -911,6 +913,10 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
| m.Post("/create", reqRepoCloudBrainWriter, bindIgnErr(auth.CreateCloudBrainForm{}), repo.CloudBrainCreate) | |||
| }, context.RepoRef()) | |||
| m.Group("/blockchain", func() { | |||
| m.Get("", repo.BlockChainIndex) | |||
| }, context.RepoRef()) | |||
| m.Group("/wiki", func() { | |||
| m.Get("/?:page", repo.Wiki) | |||
| m.Get("/_pages", repo.WikiPages) | |||
| @@ -0,0 +1,18 @@ | |||
| {{template "base/head" .}} | |||
| <div class="repository balance view"> | |||
| {{template "repo/header" .}} | |||
| <div class="ui container"> | |||
| <h3 class="ui top attached header"> | |||
| {{.i18n.Tr "repo.balance.total_view"}} | |||
| </h3> | |||
| <div class="ui attached segment"> | |||
| <div class="inline field"> | |||
| <span class="fitted">{{.i18n.Tr "repo.balance.available"}}</span> | |||
| <span class="fitted">{{.balance}}</span> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| {{template "base/footer" .}} | |||
| @@ -145,6 +145,11 @@ | |||
| </a> | |||
| {{end}} | |||
| <a class="{{if .PageIsBlockChain}}active{{end}} item " href="{{.RepoLink}}/blockchain"> | |||
| {{svg "octicon-law" 16}} | |||
| {{.i18n.Tr "repo.balance"}} | |||
| </a> | |||
| {{template "custom/extra_tabs" .}} | |||
| {{if .Permission.IsAdmin}} | |||
| @@ -158,4 +163,4 @@ | |||
| {{end}} | |||
| </div> | |||
| <div class="ui tabs divider"></div> | |||
| </div> | |||
| </div> | |||
| @@ -188,7 +188,6 @@ | |||
| {{end}} | |||
| <div class="ui {{if .IsClosed}}{{if .IsPull}}{{if .PullRequest.HasMerged}}purple{{else}}red{{end}}{{else}}red{{end}}{{else}}{{if .IsRead}}white{{else}}green{{end}}{{end}} label">#{{.Index}}</div> | |||
| <a class="title" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Title | RenderEmoji}}</a> | |||
| {{if .IsPull }} | |||
| {{if (index $.CommitStatus .PullRequest.ID)}} | |||
| {{template "repo/commit_status" (index $.CommitStatus .PullRequest.ID)}} | |||
| @@ -1,182 +1,224 @@ | |||
| <form class="ui comment form stackable grid" action="{{.Link}}" method="post"> | |||
| {{.CsrfTokenHtml}} | |||
| {{if .Flash}} | |||
| <div class="sixteen wide column"> | |||
| {{template "base/alert" .}} | |||
| </div> | |||
| {{end}} | |||
| <div class="twelve wide column"> | |||
| <div class="ui comments"> | |||
| <div class="comment"> | |||
| <a class="avatar" href="{{.SignedUser.HomeLink}}"> | |||
| <img src="{{.SignedUser.RelAvatarLink}}"> | |||
| </a> | |||
| <div class="ui segment content"> | |||
| <div class="field"> | |||
| <input name="title" id="issue_title" placeholder="{{.i18n.Tr "repo.milestones.title"}}" value="{{.title}}" tabindex="3" autofocus required maxlength="255"> | |||
| {{if .PageIsComparePull}} | |||
| <div class="title_wip_desc">{{.i18n.Tr "repo.pulls.title_wip_desc" (index .PullRequestWorkInProgressPrefixes 0| Escape) | Safe}}</div> | |||
| {{end}} | |||
| </div> | |||
| {{template "repo/issue/comment_tab" .}} | |||
| <div class="text right"> | |||
| <button class="ui green button" tabindex="6"> | |||
| {{if .PageIsComparePull}} | |||
| {{.i18n.Tr "repo.pulls.create"}} | |||
| {{else}} | |||
| {{.i18n.Tr "repo.issues.create"}} | |||
| {{end}} | |||
| </button> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <!-- --> | |||
| <form class="ui comment form stackable grid" action="{{.Link}}" method="post"> | |||
| {{.CsrfTokenHtml}} | |||
| {{if .Flash}} | |||
| <div class="sixteen wide column"> | |||
| {{template "base/alert" .}} | |||
| </div> | |||
| {{end}} | |||
| <div class="twelve wide column"> | |||
| <div class="ui comments"> | |||
| <div class="comment"> | |||
| <a class="avatar" href="{{.SignedUser.HomeLink}}"> | |||
| <img src="{{.SignedUser.RelAvatarLink}}"> | |||
| </a> | |||
| <div class="ui segment content"> | |||
| <div class="field"> | |||
| <!-- --> | |||
| <input name="title" id="issue_title" placeholder="{{.i18n.Tr "repo.milestones.title"}}" value="{{.title}}" tabindex="3" autofocus required maxlength="255"> | |||
| {{if .PageIsComparePull}} | |||
| <div class="title_wip_desc">{{.i18n.Tr "repo.pulls.title_wip_desc" (index .PullRequestWorkInProgressPrefixes 0| Escape) | Safe}}</div> | |||
| {{end}} | |||
| </div> | |||
| <div class="four wide column"> | |||
| <div class="ui segment metas"> | |||
| {{template "repo/issue/branch_selector_field" .}} | |||
| <!-- 项目奖励输入框 --> | |||
| <!-- <div class="field"> | |||
| <!-- value="{{.dog}}" -> | |||
| <input name="dog" id="issue_reward" placeholder="项目奖励" value="asdfas" tabindex="3" autofocus> | |||
| </div> --> | |||
| <input id="label_ids" name="label_ids" type="hidden" value="{{.label_ids}}"> | |||
| <div class="ui {{if not .HasIssuesOrPullsWritePermission}}disabled{{end}} floating jump select-label dropdown"> | |||
| <span class="text"> | |||
| <strong>{{.i18n.Tr "repo.issues.new.labels"}}</strong> | |||
| {{if .HasIssuesOrPullsWritePermission}} | |||
| {{svg "octicon-gear" 16}} | |||
| {{end}} | |||
| </span> | |||
| <div class="filter menu" data-id="#label_ids"> | |||
| <div class="header" style="text-transform: none;font-size:16px;">{{.i18n.Tr "repo.issues.new.add_labels_title"}}</div> | |||
| {{if or .Labels .OrgLabels}} | |||
| <div class="ui icon search input"> | |||
| <i class="search icon"></i> | |||
| <input type="text" placeholder="{{.i18n.Tr "repo.issues.filter_labels"}}"> | |||
| </div> | |||
| {{end}} | |||
| <div class="no-select item">{{.i18n.Tr "repo.issues.new.clear_labels"}}</div> | |||
| {{if or .Labels .OrgLabels}} | |||
| {{range .Labels}} | |||
| <a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}"><span class="octicon-check {{if not .IsChecked}}invisible{{end}}">{{svg "octicon-check" 16}}</span><span class="label color" style="background-color: {{.Color}}"></span> {{.Name | RenderEmoji}} | |||
| {{if .Description }}<br><small class="desc">{{.Description | RenderEmoji}}</small>{{end}}</a> | |||
| {{end}} | |||
| <div class="ui divider"></div> | |||
| {{range .OrgLabels}} | |||
| <a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}"><span class="octicon-check {{if not .IsChecked}}invisible{{end}}">{{svg "octicon-check" 16}}</span><span class="label color" style="background-color: {{.Color}}"></span> {{.Name | RenderEmoji}} | |||
| {{if .Description }}<br><small class="desc">{{.Description | RenderEmoji}}</small>{{end}}</a> | |||
| {{end}} | |||
| {{else}} | |||
| <div class="header" style="text-transform: none;font-size:14px;">{{.i18n.Tr "repo.issues.new.no_items"}}</div> | |||
| {{end}} | |||
| </div> | |||
| </div> | |||
| <div class="ui labels list"> | |||
| <span class="no-select item {{if .HasSelectedLabel}}hide{{end}}">{{.i18n.Tr "repo.issues.new.no_label"}}</span> | |||
| {{range .Labels}} | |||
| <a class="{{if not .IsChecked}}hide{{end}} item" id="label_{{.ID}}" href="{{$.RepoLink}}/issues?labels={{.ID}}"><span class="label color" style="background-color: {{.Color}}"></span> <span class="text">{{.Name | RenderEmoji}}</span></a> | |||
| {{end}} | |||
| {{range .OrgLabels}} | |||
| <a class="{{if not .IsChecked}}hide{{end}} item" id="label_{{.ID}}" href="/issues?labels={{.ID}}"><span class="label color" style="background-color: {{.Color}}"></span> <span class="text">{{.Name | RenderEmoji}}</span></a> | |||
| {{end}} | |||
| </div> | |||
| {{template "repo/issue/comment_tab" .}} | |||
| <div class="text right"> | |||
| <button class="ui green button" tabindex="6"> | |||
| {{if .PageIsComparePull}} | |||
| {{.i18n.Tr "repo.pulls.create"}} | |||
| {{else}} | |||
| {{.i18n.Tr "repo.issues.create"}} | |||
| {{end}} | |||
| </button> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="ui divider"></div> | |||
| <div class="four wide column"> | |||
| <div class="ui segment metas"> | |||
| {{template "repo/issue/branch_selector_field" .}} | |||
| <input id="milestone_id" name="milestone_id" type="hidden" value="{{.milestone_id}}"> | |||
| <div class="ui {{if not .HasIssuesOrPullsWritePermission}}disabled{{end}} floating jump select-milestone dropdown"> | |||
| <span class="text"> | |||
| <strong>{{.i18n.Tr "repo.issues.new.milestone"}}</strong> | |||
| {{if .HasIssuesOrPullsWritePermission}} | |||
| {{svg "octicon-gear" 16}} | |||
| {{end}} | |||
| </span> | |||
| <div class="menu"> | |||
| <div class="header" style="text-transform: none;font-size:16px;">{{.i18n.Tr "repo.issues.new.add_milestone_title"}}</div> | |||
| {{if or .OpenMilestones .ClosedMilestones}} | |||
| <div class="ui icon search input"> | |||
| <i class="search icon"></i> | |||
| <input type="text" placeholder="{{.i18n.Tr "repo.issues.filter_milestones"}}"> | |||
| </div> | |||
| {{end}} | |||
| <div class="no-select item">{{.i18n.Tr "repo.issues.new.clear_milestone"}}</div> | |||
| {{if and (not .OpenMilestones) (not .ClosedMilestones)}} | |||
| <div class="header" style="text-transform: none;font-size:14px;"> | |||
| {{.i18n.Tr "repo.issues.new.no_items"}} | |||
| </div> | |||
| {{else}} | |||
| {{if .OpenMilestones}} | |||
| <div class="divider"></div> | |||
| <div class="header"> | |||
| {{svg "octicon-milestone" 16}} | |||
| {{.i18n.Tr "repo.issues.new.open_milestone"}} | |||
| </div> | |||
| {{range .OpenMilestones}} | |||
| <div class="item" data-id="{{.ID}}" data-href="{{$.RepoLink}}/issues?milestone={{.ID}}"> {{.Name}}</div> | |||
| {{end}} | |||
| {{end}} | |||
| {{if .ClosedMilestones}} | |||
| <div class="divider"></div> | |||
| <div class="header"> | |||
| {{svg "octicon-milestone" 16}} | |||
| {{.i18n.Tr "repo.issues.new.closed_milestone"}} | |||
| </div> | |||
| {{range .ClosedMilestones}} | |||
| <a class="item" data-id="{{.ID}}" data-href="{{$.RepoLink}}/issues?milestone={{.ID}}"> {{.Name}}</a> | |||
| {{end}} | |||
| {{end}} | |||
| {{end}} | |||
| </div> | |||
| </div> | |||
| <div class="ui select-milestone list"> | |||
| <span class="no-select item {{if .Milestone}}hide{{end}}">{{.i18n.Tr "repo.issues.new.no_milestone"}}</span> | |||
| <div class="selected"> | |||
| {{if .Milestone}} | |||
| <a class="item" href="{{.RepoLink}}/issues?milestone={{.Milestone.ID}}"> {{.Milestone.Name}}</a> | |||
| {{end}} | |||
| </div> | |||
| </div> | |||
| <input id="label_ids" name="label_ids" type="hidden" value="{{.label_ids}}"> | |||
| <div class="ui {{if not .HasIssuesOrPullsWritePermission}}disabled{{end}} floating jump select-label dropdown"> | |||
| <span class="text"> | |||
| <strong>{{.i18n.Tr "repo.issues.new.labels"}}</strong> | |||
| {{if .HasIssuesOrPullsWritePermission}} | |||
| {{svg "octicon-gear" 16}} | |||
| {{end}} | |||
| </span> | |||
| <div class="filter menu" data-id="#label_ids"> | |||
| <div class="header" style="text-transform: none;font-size:16px;">{{.i18n.Tr "repo.issues.new.add_labels_title"}}</div> | |||
| {{if or .Labels .OrgLabels}} | |||
| <div class="ui icon search input"> | |||
| <i class="search icon"></i> | |||
| <input type="text" placeholder="{{.i18n.Tr "repo.issues.filter_labels"}}"> | |||
| </div> | |||
| {{end}} | |||
| <div class="no-select item">{{.i18n.Tr "repo.issues.new.clear_labels"}}</div> | |||
| {{if or .Labels .OrgLabels}} | |||
| {{range .Labels}} | |||
| <a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}"><span class="octicon-check {{if not .IsChecked}}invisible{{end}}">{{svg "octicon-check" 16}}</span><span class="label color" style="background-color: {{.Color}}"></span> {{.Name | RenderEmoji}} | |||
| {{if .Description }}<br><small class="desc">{{.Description | RenderEmoji}}</small>{{end}}</a> | |||
| {{end}} | |||
| <div class="ui divider"></div> | |||
| {{range .OrgLabels}} | |||
| <a class="{{if .IsChecked}}checked{{end}} item" href="#" data-id="{{.ID}}" data-id-selector="#label_{{.ID}}"><span class="octicon-check {{if not .IsChecked}}invisible{{end}}">{{svg "octicon-check" 16}}</span><span class="label color" style="background-color: {{.Color}}"></span> {{.Name | RenderEmoji}} | |||
| {{if .Description }}<br><small class="desc">{{.Description | RenderEmoji}}</small>{{end}}</a> | |||
| {{end}} | |||
| {{else}} | |||
| <div class="header" style="text-transform: none;font-size:14px;">{{.i18n.Tr "repo.issues.new.no_items"}}</div> | |||
| {{end}} | |||
| </div> | |||
| </div> | |||
| <div class="ui labels list"> | |||
| <span class="no-select item {{if .HasSelectedLabel}}hide{{end}}">{{.i18n.Tr "repo.issues.new.no_label"}}</span> | |||
| {{range .Labels}} | |||
| <a class="{{if not .IsChecked}}hide{{end}} item" id="label_{{.ID}}" href="{{$.RepoLink}}/issues?labels={{.ID}}"><span class="label color" style="background-color: {{.Color}}"></span> <span class="text">{{.Name | RenderEmoji}}</span></a> | |||
| {{end}} | |||
| {{range .OrgLabels}} | |||
| <a class="{{if not .IsChecked}}hide{{end}} item" id="label_{{.ID}}" href="/issues?labels={{.ID}}"><span class="label color" style="background-color: {{.Color}}"></span> <span class="text">{{.Name | RenderEmoji}}</span></a> | |||
| {{end}} | |||
| </div> | |||
| <div class="ui divider"></div> | |||
| <div class="ui divider"></div> | |||
| <input id="assignee_ids" name="assignee_ids" type="hidden" value="{{.assignee_ids}}"> | |||
| <div class="ui {{if not .HasIssuesOrPullsWritePermission}}disabled{{end}} floating jump select-assignees dropdown"> | |||
| <span class="text"> | |||
| <strong>{{.i18n.Tr "repo.issues.new.assignees"}}</strong> | |||
| {{if .HasIssuesOrPullsWritePermission}} | |||
| {{svg "octicon-gear" 16}} | |||
| {{end}} | |||
| </span> | |||
| <div class="filter menu" data-id="#assignee_ids"> | |||
| <div class="header" style="text-transform: none;font-size:16px;">{{.i18n.Tr "repo.issues.new.add_assignees_title"}}</div> | |||
| <div class="ui icon search input"> | |||
| <i class="search icon"></i> | |||
| <input type="text" placeholder="{{.i18n.Tr "repo.issues.filter_assignees"}}"> | |||
| </div> | |||
| <div class="no-select item">{{.i18n.Tr "repo.issues.new.clear_assignees"}}</div> | |||
| {{range .Assignees}} | |||
| <a class="item" href="#" data-id="{{.ID}}" data-id-selector="#assignee_{{.ID}}"> | |||
| <span class="octicon-check invisible">{{svg "octicon-check" 16}}</span> | |||
| <span class="text"> | |||
| <img class="ui avatar image" src="{{.RelAvatarLink}}"> {{.GetDisplayName}} | |||
| </span> | |||
| </a> | |||
| {{end}} | |||
| </div> | |||
| </div> | |||
| <div class="ui assignees list"> | |||
| <span class="no-select item {{if .HasSelectedLabel}}hide{{end}}"> | |||
| {{.i18n.Tr "repo.issues.new.no_assignees"}} | |||
| </span> | |||
| {{range .Assignees}} | |||
| <a style="padding: 5px;color:rgba(0, 0, 0, 0.87);" class="hide item" id="assignee_{{.ID}}" href="{{$.RepoLink}}/issues?assignee={{.ID}}"> | |||
| <img class="ui avatar image" src="{{.RelAvatarLink}}" style="vertical-align: middle;"> {{.GetDisplayName}} | |||
| </a> | |||
| {{end}} | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <input id="milestone_id" name="milestone_id" type="hidden" value="{{.milestone_id}}"> | |||
| <div class="ui {{if not .HasIssuesOrPullsWritePermission}}disabled{{end}} floating jump select-milestone dropdown"> | |||
| <span class="text"> | |||
| <strong>{{.i18n.Tr "repo.issues.new.milestone"}}</strong> | |||
| {{if .HasIssuesOrPullsWritePermission}} | |||
| {{svg "octicon-gear" 16}} | |||
| {{end}} | |||
| </span> | |||
| <div class="menu"> | |||
| <div class="header" style="text-transform: none;font-size:16px;">{{.i18n.Tr "repo.issues.new.add_milestone_title"}}</div> | |||
| {{if or .OpenMilestones .ClosedMilestones}} | |||
| <div class="ui icon search input"> | |||
| <i class="search icon"></i> | |||
| <input type="text" placeholder="{{.i18n.Tr "repo.issues.filter_milestones"}}"> | |||
| </div> | |||
| {{end}} | |||
| <div class="no-select item">{{.i18n.Tr "repo.issues.new.clear_milestone"}}</div> | |||
| {{if and (not .OpenMilestones) (not .ClosedMilestones)}} | |||
| <div class="header" style="text-transform: none;font-size:14px;"> | |||
| {{.i18n.Tr "repo.issues.new.no_items"}} | |||
| </div> | |||
| {{else}} | |||
| {{if .OpenMilestones}} | |||
| <div class="divider"></div> | |||
| <div class="header"> | |||
| {{svg "octicon-milestone" 16}} | |||
| {{.i18n.Tr "repo.issues.new.open_milestone"}} | |||
| </div> | |||
| {{range .OpenMilestones}} | |||
| <div class="item" data-id="{{.ID}}" data-href="{{$.RepoLink}}/issues?milestone={{.ID}}"> {{.Name}}</div> | |||
| {{end}} | |||
| {{end}} | |||
| {{if .ClosedMilestones}} | |||
| <div class="divider"></div> | |||
| <div class="header"> | |||
| {{svg "octicon-milestone" 16}} | |||
| {{.i18n.Tr "repo.issues.new.closed_milestone"}} | |||
| </div> | |||
| {{range .ClosedMilestones}} | |||
| <a class="item" data-id="{{.ID}}" data-href="{{$.RepoLink}}/issues?milestone={{.ID}}"> {{.Name}}</a> | |||
| {{end}} | |||
| {{end}} | |||
| {{end}} | |||
| </div> | |||
| </div> | |||
| <div class="ui select-milestone list"> | |||
| <span class="no-select item {{if .Milestone}}hide{{end}}">{{.i18n.Tr "repo.issues.new.no_milestone"}}</span> | |||
| <div class="selected"> | |||
| {{if .Milestone}} | |||
| <a class="item" href="{{.RepoLink}}/issues?milestone={{.Milestone.ID}}"> {{.Milestone.Name}}</a> | |||
| {{end}} | |||
| </div> | |||
| </div> | |||
| <div class="ui divider"></div> | |||
| <input id="assignee_ids" name="assignee_ids" type="hidden" value="{{.assignee_ids}}"> | |||
| <div class="ui {{if not .HasIssuesOrPullsWritePermission}}disabled{{end}} floating jump select-assignees dropdown"> | |||
| <span class="text"> | |||
| <strong>{{.i18n.Tr "repo.issues.new.assignees"}}</strong> | |||
| {{if .HasIssuesOrPullsWritePermission}} | |||
| {{svg "octicon-gear" 16}} | |||
| {{end}} | |||
| </span> | |||
| <div class="filter menu" data-id="#assignee_ids"> | |||
| <div class="header" style="text-transform: none;font-size:16px;">{{.i18n.Tr "repo.issues.new.add_assignees_title"}}</div> | |||
| <div class="ui icon search input"> | |||
| <i class="search icon"></i> | |||
| <input type="text" placeholder="{{.i18n.Tr "repo.issues.filter_assignees"}}"> | |||
| </div> | |||
| <div class="no-select item">{{.i18n.Tr "repo.issues.new.clear_assignees"}}</div> | |||
| {{range .Assignees}} | |||
| <a class="item" href="#" data-id="{{.ID}}" data-id-selector="#assignee_{{.ID}}"> | |||
| <span class="octicon-check invisible">{{svg "octicon-check" 16}}</span> | |||
| <span class="text"> | |||
| <img class="ui avatar image" src="{{.RelAvatarLink}}"> {{.GetDisplayName}} | |||
| </span> | |||
| </a> | |||
| {{end}} | |||
| </div> | |||
| </div> | |||
| <div class="ui assignees list"> | |||
| <span class="no-select item {{if .HasSelectedLabel}}hide{{end}}"> | |||
| {{.i18n.Tr "repo.issues.new.no_assignees"}} | |||
| </span> | |||
| {{range .Assignees}} | |||
| <a style="padding: 5px;color:rgba(0, 0, 0, 0.87);" class="hide item" id="assignee_{{.ID}}" href="{{$.RepoLink}}/issues?assignee={{.ID}}"> | |||
| <img class="ui avatar image" src="{{.RelAvatarLink}}" style="vertical-align: middle;"> {{.GetDisplayName}} | |||
| </a> | |||
| {{end}} | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </form> | |||
| <script src="https://cdn.jsdelivr.net/npm/jquery@3.2.1/dist/jquery.min.js"></script> | |||
| <script> | |||
| // $(document).ready(function(){ | |||
| // var reward_value = $('.ui.form').form('get value', 'dog') | |||
| // var reward_value = $('form').form('get value', 'dog') | |||
| // console.log(reward_value) | |||
| // alert(reward_value) | |||
| // $('.ui.green.button').click(function(){ | |||
| // $('.ui.form') | |||
| // .form({ | |||
| // // on: 'blur', | |||
| // inline: true, | |||
| // fields: { | |||
| // dog: { | |||
| // identifier: 'dog', | |||
| // rules: [ | |||
| // { | |||
| // type: 'empty', | |||
| // prompt: '请您输入项目奖励' | |||
| // }, | |||
| // { | |||
| // type : 'integer[0..100]', | |||
| // prompt : '项目奖励必须为整数,请您输入有效奖励金额' | |||
| // } | |||
| // ] | |||
| // } | |||
| // }, | |||
| // onFailure: function(e){ | |||
| // return false; | |||
| // } | |||
| // }); | |||
| // }); | |||
| // </script> | |||
| {{if .PageIsComparePull}} | |||
| <script>window.wipPrefixes = {{.PullRequestWorkInProgressPrefixes}}</script> | |||
| <script>window.wipPrefixes = {{.PullRequestWorkInProgressPrefixes}}</script> | |||
| {{end}} | |||
| @@ -1,3 +1,9 @@ | |||
| <style> | |||
| .ui.selection.dropdown { | |||
| padding-top: 0em; | |||
| padding-bottom: 0em; | |||
| } | |||
| </style> | |||
| {{if gt (len .PullReviewers) 0}} | |||
| <div class="comment box"> | |||
| <div class="content"> | |||
| @@ -58,6 +64,8 @@ | |||
| </div> | |||
| </div> | |||
| {{end}} | |||
| <!-- 合并请求 --> | |||
| <div class="timeline-item comment merge box"> | |||
| <a class="timeline-avatar text {{if .Issue.PullRequest.HasMerged}}purple | |||
| {{- else if .Issue.IsClosed}}grey | |||
| @@ -188,6 +196,8 @@ | |||
| {{$.i18n.Tr (printf "repo.signing.wont_sign.%s" .WontSignReason) }} | |||
| </div> | |||
| {{end}} | |||
| <!-- 合并请求 --> | |||
| {{if .AllowMerge}} | |||
| {{$prUnit := .Repository.MustGetUnit $.UnitTypePullRequests}} | |||
| {{$approvers := .Issue.PullRequest.GetApprovers}} | |||
| @@ -197,6 +207,21 @@ | |||
| <div class="ui form merge-fields" style="display: none"> | |||
| <form action="{{.Link}}/merge" method="post"> | |||
| {{.CsrfTokenHtml}} | |||
| <!-- 下拉框 --> | |||
| <div class="field"> | |||
| <div class="ui dropdown selection"> | |||
| <input type="hidden" name="block_chain_amount"> | |||
| <div class="default text">项目奖励</div> | |||
| <i class="dropdown icon"></i> | |||
| <div class="menu" > | |||
| <div class="item" data-value=0>0</div> | |||
| <div class="item" data-value=100>100</div> | |||
| <div class="item" data-value=200>200</div> | |||
| <div class="item" data-value=300>300</div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="field"> | |||
| <input type="text" name="merge_title_field" value="{{.Issue.PullRequest.GetDefaultMergeMessage}}"> | |||
| </div> | |||
| @@ -206,7 +231,7 @@ | |||
| <button class="ui green button" type="submit" name="do" value="merge"> | |||
| {{$.i18n.Tr "repo.pulls.merge_pull_request"}} | |||
| </button> | |||
| <button class="ui button merge-cancel"> | |||
| <button class="ui button merge-cancel" onclick="$('.ui.form.merge-fields')[0].reset();"> | |||
| {{$.i18n.Tr "cancel"}} | |||
| </button> | |||
| </form> | |||
| @@ -216,6 +241,21 @@ | |||
| <div class="ui form rebase-fields" style="display: none"> | |||
| <form action="{{.Link}}/merge" method="post"> | |||
| {{.CsrfTokenHtml}} | |||
| <!-- 下拉框 --> | |||
| <div class="field"> | |||
| <div class="ui dropdown selection"> | |||
| <input type="hidden" name="block_chain_amount"> | |||
| <div class="default text">项目奖励</div> | |||
| <i class="dropdown icon"></i> | |||
| <div class="menu" > | |||
| <div class="item" data-value=0>0</div> | |||
| <div class="item" data-value=100>100</div> | |||
| <div class="item" data-value=200>200</div> | |||
| <div class="item" data-value=300>300</div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <button class="ui green button" type="submit" name="do" value="rebase"> | |||
| {{$.i18n.Tr "repo.pulls.rebase_merge_pull_request"}} | |||
| </button> | |||
| @@ -229,6 +269,21 @@ | |||
| <div class="ui form rebase-merge-fields" style="display: none"> | |||
| <form action="{{.Link}}/merge" method="post"> | |||
| {{.CsrfTokenHtml}} | |||
| <!-- 下拉框 --> | |||
| <div class="field"> | |||
| <div class="ui dropdown selection"> | |||
| <input type="hidden" name="block_chain_amount"> | |||
| <div class="default text">项目奖励</div> | |||
| <i class="dropdown icon"></i> | |||
| <div class="menu" > | |||
| <div class="item" data-value=0>0</div> | |||
| <div class="item" data-value=100>100</div> | |||
| <div class="item" data-value=200>200</div> | |||
| <div class="item" data-value=300>300</div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="field"> | |||
| <input type="text" name="merge_title_field" value="{{.Issue.PullRequest.GetDefaultMergeMessage}}"> | |||
| </div> | |||
| @@ -248,6 +303,21 @@ | |||
| <div class="ui form squash-fields" style="display: none"> | |||
| <form action="{{.Link}}/merge" method="post"> | |||
| {{.CsrfTokenHtml}} | |||
| <!-- 下拉框 --> | |||
| <div class="field"> | |||
| <div class="ui dropdown selection"> | |||
| <input type="hidden" name="block_chain_amount"> | |||
| <div class="default text">项目奖励</div> | |||
| <i class="dropdown icon"></i> | |||
| <div class="menu" > | |||
| <div class="item" data-value=0>0</div> | |||
| <div class="item" data-value=100>100</div> | |||
| <div class="item" data-value=200>200</div> | |||
| <div class="item" data-value=300>300</div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="field"> | |||
| <input type="text" name="merge_title_field" value="{{.Issue.PullRequest.GetDefaultSquashMessage}}"> | |||
| </div> | |||
| @@ -377,3 +447,34 @@ | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <script src="https://cdn.jsdelivr.net/npm/jquery@3.2.1/dist/jquery.min.js"></script> | |||
| <script> | |||
| $(document) | |||
| .ready(function() { | |||
| $('.ui.dropdown.selection').dropdown(); | |||
| $('.ui.green.button').click(function(){ | |||
| $('.ui.form') | |||
| .form({ | |||
| on: 'blur', | |||
| inline: true, | |||
| fields: { | |||
| block_chain_amount: { | |||
| identifier: 'block_chain_amount', | |||
| rules: [ | |||
| { | |||
| type : 'empty', | |||
| prompt : '项目奖励不能为空' | |||
| } | |||
| ] | |||
| } | |||
| }, | |||
| onFailure: function(e){ | |||
| return false; | |||
| } | |||
| }); | |||
| }); | |||
| }) | |||
| </script> | |||