Reviewed-by: 林嘉怡 <2441898885@qq.com> Reviewed-by: yuyuanshifu <747342561@qq.com>master
@@ -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> |