@@ -394,6 +394,7 @@ type FlavorInfos struct { | |||
type FlavorInfo struct { | |||
Id int `json:"id"` | |||
Value string `json:"value"` | |||
Desc string `json:"desc"` | |||
} | |||
type PoolInfos struct { | |||
@@ -19,6 +19,7 @@ type CreateModelArtsNotebookForm struct { | |||
JobName string `form:"job_name" binding:"Required"` | |||
Attachment string `form:"attachment"` | |||
Description string `form:"description"` | |||
Flavor string `form:"flavor"` | |||
} | |||
func (f *CreateModelArtsNotebookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { | |||
@@ -11,6 +11,7 @@ import ( | |||
"code.gitea.io/gitea/models" | |||
"code.gitea.io/gitea/modules/migrations" | |||
repository_service "code.gitea.io/gitea/modules/repository" | |||
api_repo "code.gitea.io/gitea/routers/api/v1/repo" | |||
"code.gitea.io/gitea/routers/repo" | |||
mirror_service "code.gitea.io/gitea/services/mirror" | |||
) | |||
@@ -195,6 +196,17 @@ func registerHandleUserStatistic() { | |||
}) | |||
} | |||
func registerHandleClearRepoStatisticFile() { | |||
RegisterTaskFatal("handle_repo_clear_statistic_file", &BaseConfig{ | |||
Enabled: true, | |||
RunAtStart: false, | |||
Schedule: "@daily", | |||
}, func(ctx context.Context, _ *models.User, _ Config) error { | |||
api_repo.ClearUnusedStatisticsFile() | |||
return nil | |||
}) | |||
} | |||
func initBasicTasks() { | |||
registerUpdateMirrorTask() | |||
registerRepoHealthCheck() | |||
@@ -134,7 +134,7 @@ type ResourcePool struct { | |||
} `json:"resource_pool"` | |||
} | |||
func GenerateTask(ctx *context.Context, jobName, uuid, description string) error { | |||
func GenerateTask(ctx *context.Context, jobName, uuid, description, flavor string) error { | |||
var dataActualPath string | |||
if uuid != "" { | |||
dataActualPath = setting.Bucket + "/" + setting.BasePath + path.Join(uuid[0:1], uuid[1:2]) + "/" + uuid + "/" | |||
@@ -163,7 +163,7 @@ func GenerateTask(ctx *context.Context, jobName, uuid, description string) error | |||
JobName: jobName, | |||
Description: description, | |||
ProfileID: setting.ProfileID, | |||
Flavor: setting.Flavor, | |||
Flavor: flavor, | |||
Pool: models.Pool{ | |||
ID: poolInfos.PoolInfo[0].PoolId, | |||
Name: poolInfos.PoolInfo[0].PoolName, | |||
@@ -163,6 +163,7 @@ var ( | |||
// UI settings | |||
UI = struct { | |||
ExplorePagingNum int | |||
ContributorPagingNum int | |||
IssuePagingNum int | |||
RepoSearchPagingNum int | |||
MembersPagingNum int | |||
@@ -203,19 +204,20 @@ var ( | |||
Keywords string | |||
} `ini:"ui.meta"` | |||
}{ | |||
ExplorePagingNum: 20, | |||
IssuePagingNum: 10, | |||
RepoSearchPagingNum: 10, | |||
MembersPagingNum: 20, | |||
FeedMaxCommitNum: 5, | |||
GraphMaxCommitNum: 100, | |||
CodeCommentLines: 4, | |||
ReactionMaxUserNum: 10, | |||
ThemeColorMetaTag: `#6cc644`, | |||
MaxDisplayFileSize: 8388608, | |||
DefaultTheme: `gitea`, | |||
Themes: []string{`gitea`, `arc-green`}, | |||
Reactions: []string{`+1`, `-1`, `laugh`, `hooray`, `confused`, `heart`, `rocket`, `eyes`}, | |||
ExplorePagingNum: 20, | |||
ContributorPagingNum: 50, | |||
IssuePagingNum: 10, | |||
RepoSearchPagingNum: 10, | |||
MembersPagingNum: 20, | |||
FeedMaxCommitNum: 5, | |||
GraphMaxCommitNum: 100, | |||
CodeCommentLines: 4, | |||
ReactionMaxUserNum: 10, | |||
ThemeColorMetaTag: `#6cc644`, | |||
MaxDisplayFileSize: 8388608, | |||
DefaultTheme: `gitea`, | |||
Themes: []string{`gitea`, `arc-green`}, | |||
Reactions: []string{`+1`, `-1`, `laugh`, `hooray`, `confused`, `heart`, `rocket`, `eyes`}, | |||
Notification: struct { | |||
MinTimeout time.Duration | |||
TimeoutStep time.Duration | |||
@@ -545,6 +547,7 @@ var ( | |||
GrowthCommit float64 | |||
GrowthComments float64 | |||
RecordBeginTime string | |||
Path string | |||
}{} | |||
) | |||
@@ -1326,6 +1329,7 @@ func SetRadarMapConfig() { | |||
RadarMap.GrowthCommit = sec.Key("growth_commit").MustFloat64(0.2) | |||
RadarMap.GrowthComments = sec.Key("growth_comments").MustFloat64(0.2) | |||
RadarMap.RecordBeginTime = sec.Key("record_beigin_time").MustString("2021-11-05") | |||
RadarMap.Path = sec.Key("PATH").MustString("data/projectborad") | |||
} | |||
@@ -11,11 +11,11 @@ import ( | |||
"strconv" | |||
"strings" | |||
"github.com/unknwon/com" | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/obs" | |||
"code.gitea.io/gitea/modules/setting" | |||
"github.com/unknwon/com" | |||
) | |||
type FileInfo struct { | |||
@@ -407,64 +407,9 @@ func GetObsListObject(jobName, parentDir string) ([]FileInfo, error) { | |||
isDir = false | |||
nextParentDir = parentDir | |||
} | |||
fileInfo := FileInfo{ | |||
ModTime: val.LastModified.Format("2006-01-02 15:04:05"), | |||
FileName: fileName, | |||
Size: val.Size, | |||
IsDir: isDir, | |||
ParenDir: nextParentDir, | |||
} | |||
fileInfos = append(fileInfos, fileInfo) | |||
} | |||
return fileInfos, err | |||
} else { | |||
if obsError, ok := err.(obs.ObsError); ok { | |||
log.Error("Code:%s, Message:%s", obsError.Code, obsError.Message) | |||
} | |||
return nil, err | |||
} | |||
} | |||
func GetVersionObsListObject(jobName, parentDir string) ([]FileInfo, error) { | |||
input := &obs.ListObjectsInput{} | |||
input.Bucket = setting.Bucket | |||
input.Prefix = strings.TrimPrefix(path.Join(setting.TrainJobModelPath, jobName, setting.OutPutPath, parentDir), "/") | |||
strPrefix := strings.Split(input.Prefix, "/") | |||
output, err := ObsCli.ListObjects(input) | |||
fileInfos := make([]FileInfo, 0) | |||
if err == nil { | |||
for _, val := range output.Contents { | |||
str1 := strings.Split(val.Key, "/") | |||
var isDir bool | |||
var fileName, nextParentDir string | |||
if strings.HasSuffix(val.Key, "/") { | |||
//dirs in next level dir | |||
if len(str1)-len(strPrefix) > 2 { | |||
continue | |||
} | |||
fileName = str1[len(str1)-2] | |||
isDir = true | |||
if parentDir == "" { | |||
nextParentDir = fileName | |||
} else { | |||
nextParentDir = parentDir + "/" + fileName | |||
} | |||
if fileName == strPrefix[len(strPrefix)-1] || (fileName+"/") == setting.OutPutPath { | |||
continue | |||
} | |||
} else { | |||
//files in next level dir | |||
if len(str1)-len(strPrefix) > 1 { | |||
continue | |||
} | |||
fileName = str1[len(str1)-1] | |||
isDir = false | |||
nextParentDir = parentDir | |||
} | |||
fileInfo := FileInfo{ | |||
ModTime: val.LastModified.Format("2006-01-02 15:04:05"), | |||
ModTime: val.LastModified.Local().Format("2006-01-02 15:04:05"), | |||
FileName: fileName, | |||
Size: val.Size, | |||
IsDir: isDir, | |||
@@ -218,6 +218,7 @@ show_only_private = Showing only private | |||
show_only_public = Showing only public | |||
issues.in_your_repos = In your repositories | |||
contributors = Contributors | |||
[explore] | |||
repos = Repositories | |||
@@ -2163,6 +2164,19 @@ repos.stars = Stars | |||
repos.forks = Forks | |||
repos.issues = Issues | |||
repos.size = Size | |||
repos.id=ID | |||
repos.projectName=Project Name | |||
repos.isPrivate=Private | |||
repos.openi=OpenI | |||
repos.visit=Visit | |||
repos.download=Code Download | |||
repos.pr=PR | |||
repos.commit=Commit | |||
repos.closedIssues=Closed Issue | |||
repos.contributor=Contributor | |||
repos.yes=Yes | |||
repos.no=No | |||
datasets.dataset_manage_panel= Dataset Manage | |||
datasets.owner=Owner | |||
@@ -220,6 +220,8 @@ show_only_public=只显示公开的 | |||
issues.in_your_repos=属于该用户项目的 | |||
contributors=贡献者 | |||
[explore] | |||
repos=项目 | |||
users=用户 | |||
@@ -227,6 +229,7 @@ organizations=组织 | |||
images = 云脑镜像 | |||
search=搜索 | |||
code=代码 | |||
data_analysis=数字看板 | |||
repo_no_results=未找到匹配的项目。 | |||
dataset_no_results = 未找到匹配的数据集。 | |||
user_no_results=未找到匹配的用户。 | |||
@@ -2166,6 +2169,19 @@ repos.stars=点赞数 | |||
repos.forks=派生数 | |||
repos.issues=任务数 | |||
repos.size=大小 | |||
repos.id=ID | |||
repos.projectName=项目名称 | |||
repos.isPrivate=私有 | |||
repos.openi=OpenI指数 | |||
repos.visit=浏览量 | |||
repos.download=代码下载量 | |||
repos.pr=PR数 | |||
repos.commit=Commit数 | |||
repos.closedIssues=已解决任务数 | |||
repos.contributor=贡献者数 | |||
repos.yes=是 | |||
repos.no=否 | |||
datasets.dataset_manage_panel=数据集管理 | |||
datasets.owner=所有者 | |||
@@ -19,11 +19,13 @@ | |||
"cssnano": "4.1.10", | |||
"domino": "2.1.5", | |||
"dropzone": "5.7.2", | |||
"echarts": "3.8.5", | |||
"element-ui": "2.15.5", | |||
"esdk-obs-browserjs": "3.20.7", | |||
"esdk-obs-nodejs": "3.20.11", | |||
"fast-glob": "3.2.2", | |||
"file-loader": "6.0.0", | |||
"file-saver": "2.0.5", | |||
"fomantic-ui": "2.8.4", | |||
"fs": "0.0.1-security", | |||
"highlight.js": "10.4.1", | |||
@@ -55,13 +57,15 @@ | |||
"webpack": "4.43.0", | |||
"webpack-cli": "3.3.11", | |||
"webpack-fix-style-only-entries": "0.4.0", | |||
"worker-loader": "2.0.0" | |||
"worker-loader": "2.0.0", | |||
"xlsx": "0.17.3" | |||
}, | |||
"devDependencies": { | |||
"eslint": "6.8.0", | |||
"eslint-config-airbnb-base": "14.1.0", | |||
"eslint-plugin-import": "2.20.2", | |||
"eslint-plugin-vue": "6.2.2", | |||
"script-loader": "0.7.2", | |||
"stylelint": "13.3.3", | |||
"stylelint-config-standard": "20.0.0", | |||
"updates": "10.2.11" | |||
@@ -0,0 +1 @@ | |||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1636355832057" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8359" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><path d="M1024 767.6928c0 7.168-2.8672 12.0832-8.4992 14.9504L571.2896 1021.8496 569.1392 1021.8496C567.7056 1023.2832 565.5552 1024 562.688 1024c-2.8672 0-5.0176-0.7168-6.4512-2.1504L554.1888 1021.8496 109.9776 782.6432C104.2432 779.776 101.376 774.8608 101.376 767.6928L101.376 255.1808 101.376 253.0304c0-1.3312 0.7168-2.8672 2.1504-4.3008L103.5264 246.5792l4.3008-4.3008 2.1504-2.1504L554.1888 1.024c5.7344-1.3312 11.3664-1.3312 17.1008 0l444.2112 239.2064 2.1504 2.1504 4.3008 4.3008 0 2.1504L1024 253.0304l0 2.1504L1024 767.6928zM135.5776 757.0432l410.0096 222.1056 0-474.112L135.5776 282.9312 135.5776 757.0432zM154.8288 255.1808 562.688 475.136l407.8592-219.9552L562.688 35.2256 154.8288 255.1808zM989.7984 757.0432l0-474.112L579.7888 505.0368l0 474.112L989.7984 757.0432z" p-id="8360"></path></svg> |
@@ -528,9 +528,10 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
m.Group("/projectboard", func() { | |||
m.Get("/restoreFork", adminReq, repo.RestoreForkNumber) | |||
m.Get("/downloadAll", adminReq, repo.ServeAllProjectsPeriodStatisticsFile) | |||
m.Group("/project", func() { | |||
m.Get("", adminReq, repo.GetAllProjectsPeriodStatistics) | |||
m.Group("/:id", func() { | |||
m.Get("", adminReq, repo.GetProjectLatestStatistics) | |||
m.Get("/period", adminReq, repo.GetProjectPeriodStatistics) | |||
@@ -1,8 +1,13 @@ | |||
package repo | |||
import ( | |||
"encoding/csv" | |||
"fmt" | |||
"io/ioutil" | |||
"net/http" | |||
"net/url" | |||
"os" | |||
"path" | |||
"strconv" | |||
"time" | |||
@@ -103,6 +108,7 @@ func GetAllProjectsPeriodStatistics(ctx *context.Context) { | |||
ctx.Error(http.StatusBadRequest, ctx.Tr("repo.total_count_get_error")) | |||
return | |||
} | |||
sql := generateSqlByType(ctx, beginTime, endTime, latestDate, q, orderBy, page, pageSize) | |||
projectsPeriodData := ProjectsPeriodData{ | |||
RecordBeginTime: recordBeginTime.Format(DATE_FORMAT), | |||
@@ -110,13 +116,146 @@ func GetAllProjectsPeriodStatistics(ctx *context.Context) { | |||
TotalPage: getTotalPage(total, pageSize), | |||
TotalCount: total, | |||
LastUpdatedTime: latestUpdatedTime, | |||
PageRecords: models.GetRepoStatisticByRawSql(generatePageSql(beginTime, endTime, latestDate, q, orderBy, page, pageSize)), | |||
PageRecords: models.GetRepoStatisticByRawSql(sql), | |||
} | |||
ctx.JSON(http.StatusOK, projectsPeriodData) | |||
} | |||
func generateSqlByType(ctx *context.Context, beginTime time.Time, endTime time.Time, latestDate string, q string, orderBy string, page int, pageSize int) string { | |||
sql := "" | |||
if ctx.QueryTrim("type") == "all" { | |||
sql = generateTypeAllSql(beginTime, endTime, latestDate, q, orderBy, page, pageSize) | |||
} else { | |||
sql = generatePageSql(beginTime, endTime, latestDate, q, orderBy, page, pageSize) | |||
} | |||
return sql | |||
} | |||
func ServeAllProjectsPeriodStatisticsFile(ctx *context.Context) { | |||
recordBeginTime, err := getRecordBeginTime() | |||
if err != nil { | |||
log.Error("Can not get record begin time", err) | |||
ctx.Error(http.StatusBadRequest, ctx.Tr("repo.record_begintime_get_err")) | |||
return | |||
} | |||
beginTime, endTime, err := getTimePeroid(ctx, recordBeginTime) | |||
if err != nil { | |||
log.Error("Parameter is wrong", err) | |||
ctx.Error(http.StatusBadRequest, ctx.Tr("repo.parameter_is_wrong")) | |||
return | |||
} | |||
q := ctx.QueryTrim("q") | |||
page := ctx.QueryInt("page") | |||
if page <= 0 { | |||
page = 1 | |||
} | |||
pageSize := 1000 | |||
orderBy := getOrderBy(ctx) | |||
_, latestDate, err := models.GetRepoStatLastUpdatedTime() | |||
if err != nil { | |||
log.Error("Can not query the last updated time.", err) | |||
ctx.Error(http.StatusBadRequest, ctx.Tr("repo.last_update_time_error")) | |||
return | |||
} | |||
countSql := generateCountSql(beginTime, endTime, latestDate, q) | |||
total, err := models.CountRepoStatByRawSql(countSql) | |||
if err != nil { | |||
log.Error("Can not query total count.", err) | |||
ctx.Error(http.StatusBadRequest, ctx.Tr("repo.total_count_get_error")) | |||
return | |||
} | |||
fileName, frontName := getFileName(ctx, beginTime, endTime) | |||
if err := os.MkdirAll(setting.RadarMap.Path, os.ModePerm); err != nil { | |||
ctx.Error(http.StatusBadRequest, fmt.Errorf("Failed to create dir %s: %v", setting.AvatarUploadPath, err).Error()) | |||
} | |||
totalPage := getTotalPage(total, pageSize) | |||
f, e := os.Create(fileName) | |||
defer f.Close() | |||
if e != nil { | |||
log.Warn("Failed to create file", e) | |||
} | |||
writer := csv.NewWriter(f) | |||
writer.Write(allProjectsPeroidHeader(ctx)) | |||
for i := 0; i <= totalPage; i++ { | |||
pageRecords := models.GetRepoStatisticByRawSql(generateSqlByType(ctx, beginTime, endTime, latestDate, q, orderBy, i+1, pageSize)) | |||
for _, record := range pageRecords { | |||
e = writer.Write(allProjectsPeroidValues(record, ctx)) | |||
if e != nil { | |||
log.Warn("Failed to write record", e) | |||
} | |||
} | |||
writer.Flush() | |||
} | |||
ctx.ServeFile(fileName, url.QueryEscape(frontName)) | |||
} | |||
func getFileName(ctx *context.Context, beginTime time.Time, endTime time.Time) (string, string) { | |||
baseName := setting.RadarMap.Path + "/项目分析_" | |||
if ctx.QueryTrim("q") != "" { | |||
baseName = baseName + ctx.QueryTrim("q") + "_" | |||
} | |||
if ctx.QueryTrim("type") == "all" { | |||
baseName = baseName + "所有" | |||
} else { | |||
baseName = baseName + beginTime.AddDate(0, 0, -1).Format(DATE_FORMAT) + "_" + endTime.AddDate(0, 0, -1).Format(DATE_FORMAT) | |||
} | |||
frontName := baseName + ".csv" | |||
localName := baseName + "_" + strconv.FormatInt(time.Now().Unix(), 10) + ".csv" | |||
return localName, path.Base(frontName) | |||
} | |||
func ClearUnusedStatisticsFile() { | |||
fileInfos, err := ioutil.ReadDir(setting.RadarMap.Path) | |||
if err != nil { | |||
log.Warn("can not read dir: "+setting.RadarMap.Path, err) | |||
return | |||
} | |||
for _, fileInfo := range fileInfos { | |||
if !fileInfo.IsDir() && fileInfo.ModTime().Before(time.Now().AddDate(0, 0, -1)) { | |||
os.Remove(path.Join(setting.RadarMap.Path, fileInfo.Name())) | |||
} | |||
} | |||
} | |||
func allProjectsPeroidHeader(ctx *context.Context) []string { | |||
return []string{ctx.Tr("admin.repos.id"), ctx.Tr("admin.repos.projectName"), ctx.Tr("admin.repos.isPrivate"), ctx.Tr("admin.repos.openi"), ctx.Tr("admin.repos.visit"), ctx.Tr("admin.repos.download"), ctx.Tr("admin.repos.pr"), ctx.Tr("admin.repos.commit"), | |||
ctx.Tr("admin.repos.watches"), ctx.Tr("admin.repos.stars"), ctx.Tr("admin.repos.forks"), ctx.Tr("admin.repos.issues"), ctx.Tr("admin.repos.closedIssues"), ctx.Tr("admin.repos.contributor")} | |||
} | |||
func allProjectsPeroidValues(rs *models.RepoStatistic, ctx *context.Context) []string { | |||
return []string{strconv.FormatInt(rs.RepoID, 10), rs.Name, getIsPrivateDisplay(rs.IsPrivate, ctx), strconv.FormatFloat(rs.RadarTotal, 'f', 2, 64), | |||
strconv.FormatInt(rs.NumVisits, 10), strconv.FormatInt(rs.NumDownloads, 10), strconv.FormatInt(rs.NumPulls, 10), strconv.FormatInt(rs.NumCommits, 10), | |||
strconv.FormatInt(rs.NumWatches, 10), strconv.FormatInt(rs.NumStars, 10), strconv.FormatInt(rs.NumForks, 10), strconv.FormatInt(rs.NumIssues, 10), | |||
strconv.FormatInt(rs.NumClosedIssues, 10), strconv.FormatInt(rs.NumContributor, 10), | |||
} | |||
} | |||
func getIsPrivateDisplay(private bool, ctx *context.Context) string { | |||
if private { | |||
return ctx.Tr("admin.repos.yes") | |||
} else { | |||
return ctx.Tr("admin.repos.no") | |||
} | |||
} | |||
func GetProjectLatestStatistics(ctx *context.Context) { | |||
repoId := ctx.Params(":id") | |||
recordBeginTime, err := getRecordBeginTime() | |||
@@ -235,11 +374,11 @@ func generateTargetSql(beginTime time.Time, endTime time.Time, repoId int64) str | |||
return sql | |||
} | |||
func generateCountSql(beginTime time.Time, endTime time.Time, yesterday string, q string) string { | |||
func generateCountSql(beginTime time.Time, endTime time.Time, latestDate string, q string) string { | |||
countSql := "SELECT count(*) FROM " + | |||
"(SELECT repo_id FROM repo_statistic where created_unix >=" + strconv.FormatInt(beginTime.Unix(), 10) + | |||
" and created_unix<" + strconv.FormatInt(endTime.Unix(), 10) + " group by repo_id) A," + | |||
"(SELECT repo_id,name,is_private,radar_total from public.repo_statistic where date='" + yesterday + "') B" + | |||
"(SELECT repo_id,name,is_private,radar_total from public.repo_statistic where date='" + latestDate + "') B" + | |||
" where A.repo_id=B.repo_id" | |||
if q != "" { | |||
countSql = countSql + " and B.name like '%" + q + "%'" | |||
@@ -247,18 +386,34 @@ func generateCountSql(beginTime time.Time, endTime time.Time, yesterday string, | |||
return countSql | |||
} | |||
func generatePageSql(beginTime time.Time, endTime time.Time, yesterday string, q string, orderBy string, page int, pageSize int) string { | |||
countSql := "SELECT A.repo_id,name,is_private,radar_total,num_watches,num_visits,num_downloads,num_pulls,num_commits,num_stars,num_forks,num_issues,num_closed_issues,num_contributor FROM " + | |||
func generateTypeAllSql(beginTime time.Time, endTime time.Time, latestDate string, q string, orderBy string, page int, pageSize int) string { | |||
sql := "SELECT A.repo_id,name,is_private,radar_total,num_watches,num_visits,num_downloads,num_pulls,num_commits,num_stars,num_forks,num_issues,num_closed_issues,num_contributor FROM " + | |||
"(SELECT repo_id,sum(num_visits) as num_visits " + | |||
" FROM repo_statistic where created_unix >=" + strconv.FormatInt(beginTime.Unix(), 10) + | |||
" and created_unix<" + strconv.FormatInt(endTime.Unix(), 10) + " group by repo_id) A," + | |||
"(SELECT repo_id,name,is_private,radar_total,num_watches,num_downloads,num_pulls,num_commits,num_stars,num_forks,num_issues,num_closed_issues,num_contributor from public.repo_statistic where date='" + latestDate + "') B" + | |||
" where A.repo_id=B.repo_id" | |||
if q != "" { | |||
sql = sql + " and name like '%" + q + "%'" | |||
} | |||
sql = sql + " order by " + orderBy + " desc,repo_id" + " limit " + strconv.Itoa(pageSize) + " offset " + strconv.Itoa((page-1)*pageSize) | |||
return sql | |||
} | |||
func generatePageSql(beginTime time.Time, endTime time.Time, latestDate string, q string, orderBy string, page int, pageSize int) string { | |||
sql := "SELECT A.repo_id,name,is_private,radar_total,num_watches,num_visits,num_downloads,num_pulls,num_commits,num_stars,num_forks,num_issues,num_closed_issues,num_contributor FROM " + | |||
"(SELECT repo_id,sum(num_watches_added) as num_watches,sum(num_visits) as num_visits, sum(num_downloads_added) as num_downloads,sum(num_pulls_added) as num_pulls,sum(num_commits_added) as num_commits,sum(num_stars_added) as num_stars,sum(num_forks_added) num_forks,sum(num_issues_added) as num_issues,sum(num_closed_issues_added) as num_closed_issues,sum(num_contributor_added) as num_contributor " + | |||
" FROM repo_statistic where created_unix >=" + strconv.FormatInt(beginTime.Unix(), 10) + | |||
" and created_unix<" + strconv.FormatInt(endTime.Unix(), 10) + " group by repo_id) A," + | |||
"(SELECT repo_id,name,is_private,radar_total from public.repo_statistic where date='" + yesterday + "') B" + | |||
"(SELECT repo_id,name,is_private,radar_total from public.repo_statistic where date='" + latestDate + "') B" + | |||
" where A.repo_id=B.repo_id" | |||
if q != "" { | |||
countSql = countSql + " and B.name like '%" + q + "%'" | |||
sql = sql + " and B.name like '%" + q + "%'" | |||
} | |||
countSql = countSql + " order by " + orderBy + " desc,A.repo_id" + " limit " + strconv.Itoa(pageSize) + " offset " + strconv.Itoa((page-1)*pageSize) | |||
return countSql | |||
sql = sql + " order by " + orderBy + " desc,A.repo_id" + " limit " + strconv.Itoa(pageSize) + " offset " + strconv.Itoa((page-1)*pageSize) | |||
return sql | |||
} | |||
func getOrderBy(ctx *context.Context) string { | |||
@@ -301,7 +456,7 @@ func getTimePeroid(ctx *context.Context, recordBeginTime time.Time) (time.Time, | |||
endTimeStr := ctx.QueryTrim("endTime") | |||
var beginTime time.Time | |||
var endTime time.Time | |||
var err error | |||
if queryType != "" { | |||
if queryType == "all" { | |||
@@ -346,12 +501,12 @@ func getTimePeroid(ctx *context.Context, recordBeginTime time.Time) (time.Time, | |||
} else { | |||
beginTime, err := time.Parse("2006-01-02", beginTimeStr) | |||
beginTime, err = time.Parse("2006-01-02", beginTimeStr) | |||
if err != nil { | |||
return now, now, err | |||
} | |||
endTime, err := time.Parse("2006-01-02", endTimeStr) | |||
endTime, err = time.Parse("2006-01-02", endTimeStr) | |||
if err != nil { | |||
return now, now, err | |||
} | |||
@@ -33,8 +33,9 @@ const ( | |||
// tplExploreOrganizations explore organizations page template | |||
tplExploreOrganizations base.TplName = "explore/organizations" | |||
// tplExploreCode explore code page template | |||
tplExploreCode base.TplName = "explore/code" | |||
tplExploreImages base.TplName = "explore/images" | |||
tplExploreCode base.TplName = "explore/code" | |||
tplExploreImages base.TplName = "explore/images" | |||
tplExploreExploreDataAnalysis base.TplName = "explore/data_analysis" | |||
) | |||
// Home render home page | |||
@@ -501,6 +502,10 @@ func ExploreImages(ctx *context.Context) { | |||
ctx.HTML(200, tplExploreImages) | |||
} | |||
func ExploreDataAnalysis(ctx *context.Context) { | |||
ctx.HTML(200, tplExploreExploreDataAnalysis) | |||
} | |||
// NotFound render 404 page | |||
func NotFound(ctx *context.Context) { | |||
ctx.Data["Title"] = "Page Not Found" | |||
@@ -204,7 +204,7 @@ func CloudBrainCreate(ctx *context.Context, form auth.CreateCloudBrainForm) { | |||
resourceSpecId := form.ResourceSpecId | |||
if !jobNamePattern.MatchString(jobName) { | |||
ctx.RenderWithErr(ctx.Tr("repo.cloudbrain_jobname_err"), tplModelArtsNew, &form) | |||
ctx.RenderWithErr(ctx.Tr("repo.cloudbrain_jobname_err"), tplCloudBrainNew, &form) | |||
return | |||
} | |||
@@ -27,15 +27,10 @@ import ( | |||
) | |||
const ( | |||
// tplModelArtsNotebookIndex base.TplName = "repo/modelarts/notebook/index" | |||
tplModelArtsNotebookIndex base.TplName = "repo/modelarts/notebook/index" | |||
tplModelArtsNotebookNew base.TplName = "repo/modelarts/notebook/new" | |||
tplModelArtsNotebookShow base.TplName = "repo/modelarts/notebook/show" | |||
tplModelArtsIndex base.TplName = "repo/modelarts/index" | |||
tplModelArtsNew base.TplName = "repo/modelarts/new" | |||
tplModelArtsShow base.TplName = "repo/modelarts/show" | |||
tplModelArtsTrainJobIndex base.TplName = "repo/modelarts/trainjob/index" | |||
tplModelArtsTrainJobNew base.TplName = "repo/modelarts/trainjob/new" | |||
tplModelArtsTrainJobShow base.TplName = "repo/modelarts/trainjob/show" | |||
@@ -51,229 +46,6 @@ func MustEnableModelArts(ctx *context.Context) { | |||
} | |||
} | |||
func ModelArtsIndex(ctx *context.Context) { | |||
MustEnableModelArts(ctx) | |||
repo := ctx.Repo.Repository | |||
page := ctx.QueryInt("page") | |||
if page <= 0 { | |||
page = 1 | |||
} | |||
ciTasks, count, err := models.Cloudbrains(&models.CloudbrainsOptions{ | |||
ListOptions: models.ListOptions{ | |||
Page: page, | |||
PageSize: setting.UI.IssuePagingNum, | |||
}, | |||
RepoID: repo.ID, | |||
Type: models.TypeCloudBrainTwo, | |||
}) | |||
if err != nil { | |||
ctx.ServerError("Cloudbrain", err) | |||
return | |||
} | |||
for i, task := range ciTasks { | |||
if task.Status == string(models.JobRunning) { | |||
ciTasks[i].CanDebug = true | |||
} else { | |||
ciTasks[i].CanDebug = false | |||
} | |||
ciTasks[i].CanDel = models.CanDelJob(ctx.IsSigned, ctx.User, task) | |||
} | |||
pager := context.NewPagination(int(count), setting.UI.IssuePagingNum, page, 5) | |||
pager.SetDefaultParams(ctx) | |||
ctx.Data["Page"] = pager | |||
ctx.Data["PageIsCloudBrain"] = true | |||
ctx.Data["Tasks"] = ciTasks | |||
ctx.HTML(200, tplModelArtsIndex) | |||
} | |||
func ModelArtsNew(ctx *context.Context) { | |||
ctx.Data["PageIsCloudBrain"] = true | |||
t := time.Now() | |||
var jobName = jobNamePrefixValid(cutString(ctx.User.Name, 5)) + t.Format("2006010215") + strconv.Itoa(int(t.Unix()))[5:] | |||
ctx.Data["job_name"] = jobName | |||
attachs, err := models.GetModelArtsUserAttachments(ctx.User.ID) | |||
if err != nil { | |||
ctx.ServerError("GetAllUserAttachments failed:", err) | |||
return | |||
} | |||
ctx.Data["attachments"] = attachs | |||
ctx.Data["dataset_path"] = modelarts.DataSetMountPath | |||
ctx.Data["env"] = modelarts.NotebookEnv | |||
ctx.Data["notebook_type"] = modelarts.NotebookType | |||
if modelarts.FlavorInfos == nil { | |||
json.Unmarshal([]byte(setting.FlavorInfos), &modelarts.FlavorInfos) | |||
} | |||
ctx.Data["flavors"] = modelarts.FlavorInfos.FlavorInfo | |||
ctx.HTML(200, tplModelArtsNew) | |||
} | |||
func ModelArtsCreate(ctx *context.Context, form auth.CreateModelArtsForm) { | |||
ctx.Data["PageIsCloudBrain"] = true | |||
jobName := form.JobName | |||
uuid := form.Attachment | |||
description := form.Description | |||
//repo := ctx.Repo.Repository | |||
if !jobNamePattern.MatchString(jobName) { | |||
ctx.RenderWithErr(ctx.Tr("repo.cloudbrain_jobname_err"), tplModelArtsNew, &form) | |||
return | |||
} | |||
err := modelarts.GenerateTask(ctx, jobName, uuid, description) | |||
if err != nil { | |||
ctx.RenderWithErr(err.Error(), tplModelArtsNew, &form) | |||
return | |||
} | |||
ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/modelarts") | |||
} | |||
func ModelArtsShow(ctx *context.Context) { | |||
ctx.Data["PageIsCloudBrain"] = true | |||
var jobID = ctx.Params(":jobid") | |||
task, err := models.GetCloudbrainByJobID(jobID) | |||
if err != nil { | |||
ctx.Data["error"] = err.Error() | |||
ctx.RenderWithErr(err.Error(), tplModelArtsIndex, nil) | |||
return | |||
} | |||
result, err := modelarts.GetJob(jobID) | |||
if err != nil { | |||
ctx.Data["error"] = err.Error() | |||
ctx.RenderWithErr(err.Error(), tplModelArtsIndex, nil) | |||
return | |||
} | |||
if result != nil { | |||
task.Status = result.Status | |||
err = models.UpdateJob(task) | |||
if err != nil { | |||
ctx.Data["error"] = err.Error() | |||
ctx.RenderWithErr(err.Error(), tplModelArtsIndex, nil) | |||
return | |||
} | |||
createTime, _ := com.StrTo(result.CreationTimestamp).Int64() | |||
result.CreateTime = time.Unix(int64(createTime/1000), 0).Format("2006-01-02 15:04:05") | |||
endTime, _ := com.StrTo(result.LatestUpdateTimestamp).Int64() | |||
result.LatestUpdateTime = time.Unix(int64(endTime/1000), 0).Format("2006-01-02 15:04:05") | |||
result.QueuingInfo.BeginTime = time.Unix(int64(result.QueuingInfo.BeginTimestamp/1000), 0).Format("2006-01-02 15:04:05") | |||
result.QueuingInfo.EndTime = time.Unix(int64(result.QueuingInfo.EndTimestamp/1000), 0).Format("2006-01-02 15:04:05") | |||
} | |||
ctx.Data["task"] = task | |||
ctx.Data["jobID"] = jobID | |||
ctx.Data["result"] = result | |||
ctx.HTML(200, tplModelArtsShow) | |||
} | |||
func ModelArtsDebug(ctx *context.Context) { | |||
var jobID = ctx.Params(":jobid") | |||
_, err := models.GetCloudbrainByJobID(jobID) | |||
if err != nil { | |||
ctx.ServerError("GetCloudbrainByJobID failed", err) | |||
return | |||
} | |||
result, err := modelarts.GetJob(jobID) | |||
if err != nil { | |||
ctx.RenderWithErr(err.Error(), tplModelArtsIndex, nil) | |||
return | |||
} | |||
res, err := modelarts.GetJobToken(jobID) | |||
if err != nil { | |||
ctx.RenderWithErr(err.Error(), tplModelArtsIndex, nil) | |||
return | |||
} | |||
urls := strings.Split(result.Spec.Annotations.Url, "/") | |||
urlPrefix := result.Spec.Annotations.TargetDomain | |||
for i, url := range urls { | |||
if i > 2 { | |||
urlPrefix += "/" + url | |||
} | |||
} | |||
//urlPrefix := result.Spec.Annotations.TargetDomain + "/modelarts/internal/hub/notebook/user/" + task.JobID | |||
log.Info(urlPrefix) | |||
debugUrl := urlPrefix + "?token=" + res.Token | |||
ctx.Redirect(debugUrl) | |||
} | |||
func ModelArtsStop(ctx *context.Context) { | |||
var jobID = ctx.Params(":jobid") | |||
log.Info(jobID) | |||
task, err := models.GetCloudbrainByJobID(jobID) | |||
if err != nil { | |||
ctx.ServerError("GetCloudbrainByJobID failed", err) | |||
return | |||
} | |||
if task.Status != string(models.JobRunning) { | |||
log.Error("the job(%s) is not running", task.JobName) | |||
ctx.ServerError("the job is not running", errors.New("the job is not running")) | |||
return | |||
} | |||
param := models.NotebookAction{ | |||
Action: models.ActionStop, | |||
} | |||
res, err := modelarts.StopJob(jobID, param) | |||
if err != nil { | |||
log.Error("StopJob(%s) failed:%v", task.JobName, err.Error()) | |||
ctx.ServerError("StopJob failed", err) | |||
return | |||
} | |||
task.Status = res.CurrentStatus | |||
err = models.UpdateJob(task) | |||
if err != nil { | |||
ctx.ServerError("UpdateJob failed", err) | |||
return | |||
} | |||
ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/modelarts") | |||
} | |||
func ModelArtsDel(ctx *context.Context) { | |||
var jobID = ctx.Params(":jobid") | |||
task, err := models.GetCloudbrainByJobID(jobID) | |||
if err != nil { | |||
ctx.ServerError("GetCloudbrainByJobID failed", err) | |||
return | |||
} | |||
if task.Status != string(models.ModelArtsCreateFailed) && task.Status != string(models.ModelArtsStartFailed) && task.Status != string(models.ModelArtsStopped) { | |||
log.Error("the job(%s) has not been stopped", task.JobName) | |||
ctx.ServerError("the job has not been stopped", errors.New("the job has not been stopped")) | |||
return | |||
} | |||
_, err = modelarts.DelJob(jobID) | |||
if err != nil { | |||
log.Error("DelJob(%s) failed:%v", task.JobName, err.Error()) | |||
ctx.ServerError("DelJob failed", err) | |||
return | |||
} | |||
err = models.DeleteJob(task) | |||
if err != nil { | |||
ctx.ServerError("DeleteJob failed", err) | |||
return | |||
} | |||
ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/modelarts") | |||
} | |||
func NotebookIndex(ctx *context.Context) { | |||
MustEnableModelArts(ctx) | |||
repo := ctx.Repo.Repository | |||
@@ -343,8 +115,9 @@ func NotebookCreate(ctx *context.Context, form auth.CreateModelArtsNotebookForm) | |||
jobName := form.JobName | |||
uuid := form.Attachment | |||
description := form.Description | |||
flavor := form.Flavor | |||
err := modelarts.GenerateTask(ctx, jobName, uuid, description) | |||
err := modelarts.GenerateTask(ctx, jobName, uuid, description, flavor) | |||
if err != nil { | |||
ctx.RenderWithErr(err.Error(), tplModelArtsNotebookNew, &form) | |||
return | |||
@@ -12,8 +12,10 @@ import ( | |||
"fmt" | |||
gotemplate "html/template" | |||
"io/ioutil" | |||
"net/http" | |||
"net/url" | |||
"path" | |||
"sort" | |||
"strings" | |||
"time" | |||
@@ -31,11 +33,12 @@ import ( | |||
) | |||
const ( | |||
tplRepoEMPTY base.TplName = "repo/empty" | |||
tplRepoHome base.TplName = "repo/home" | |||
tplWatchers base.TplName = "repo/watchers" | |||
tplForks base.TplName = "repo/forks" | |||
tplMigrating base.TplName = "repo/migrating" | |||
tplRepoEMPTY base.TplName = "repo/empty" | |||
tplRepoHome base.TplName = "repo/home" | |||
tplWatchers base.TplName = "repo/watchers" | |||
tplForks base.TplName = "repo/forks" | |||
tplMigrating base.TplName = "repo/migrating" | |||
tplContributors base.TplName = "repo/contributors" | |||
) | |||
type namedBlob struct { | |||
@@ -575,19 +578,29 @@ func safeURL(address string) string { | |||
} | |||
type ContributorInfo struct { | |||
UserInfo *models.User // nil for contributor who is not a registered user | |||
Email string | |||
CommitCnt int | |||
UserInfo *models.User // nil for contributor who is not a registered user | |||
RelAvatarLink string `json:"rel_avatar_link"` | |||
UserName string `json:"user_name"` | |||
Email string `json:"email"` | |||
CommitCnt int `json:"commit_cnt"` | |||
} | |||
func getContributorInfo(contributorInfos []*ContributorInfo, email string) *ContributorInfo{ | |||
type GetContributorsInfo struct { | |||
ErrorCode int `json:"error_code"` | |||
ErrorMsg string `json:"error_msg"` | |||
Count int `json:"count"` | |||
ContributorInfo []*ContributorInfo `json:"contributor_info"` | |||
} | |||
func getContributorInfo(contributorInfos []*ContributorInfo, email string) *ContributorInfo { | |||
for _, c := range contributorInfos { | |||
if strings.Compare(c.Email,email) == 0 { | |||
if strings.Compare(c.Email, email) == 0 { | |||
return c | |||
} | |||
} | |||
return nil | |||
} | |||
// Home render repository home page | |||
func Home(ctx *context.Context) { | |||
if len(ctx.Repo.Units) > 0 { | |||
@@ -596,35 +609,41 @@ func Home(ctx *context.Context) { | |||
if err == nil && contributors != nil { | |||
startTime := time.Now() | |||
var contributorInfos []*ContributorInfo | |||
contributorInfoHash:= make(map[string]*ContributorInfo) | |||
contributorInfoHash := make(map[string]*ContributorInfo) | |||
count := 0 | |||
for _, c := range contributors { | |||
if strings.Compare(c.Email,"") == 0 { | |||
if count >= 25 { | |||
continue | |||
} | |||
if strings.Compare(c.Email, "") == 0 { | |||
continue | |||
} | |||
// get user info from committer email | |||
user, err := models.GetUserByActivateEmail(c.Email) | |||
if err == nil { | |||
// committer is system user, get info through user's primary email | |||
if existedContributorInfo,ok:=contributorInfoHash[user.Email];ok { | |||
if existedContributorInfo, ok := contributorInfoHash[user.Email]; ok { | |||
// existed: same primary email, different committer name | |||
existedContributorInfo.CommitCnt += c.CommitCnt | |||
}else{ | |||
} else { | |||
// new committer info | |||
var newContributor = &ContributorInfo{ | |||
user, user.Email,c.CommitCnt, | |||
user, user.RelAvatarLink(), user.Name, user.Email, c.CommitCnt, | |||
} | |||
contributorInfos = append(contributorInfos, newContributor ) | |||
count++ | |||
contributorInfos = append(contributorInfos, newContributor) | |||
contributorInfoHash[user.Email] = newContributor | |||
} | |||
} else { | |||
// committer is not system user | |||
if existedContributorInfo,ok:=contributorInfoHash[c.Email];ok { | |||
if existedContributorInfo, ok := contributorInfoHash[c.Email]; ok { | |||
// existed: same primary email, different committer name | |||
existedContributorInfo.CommitCnt += c.CommitCnt | |||
}else{ | |||
} else { | |||
var newContributor = &ContributorInfo{ | |||
user, c.Email,c.CommitCnt, | |||
user, "", "",c.Email, c.CommitCnt, | |||
} | |||
count++ | |||
contributorInfos = append(contributorInfos, newContributor) | |||
contributorInfoHash[c.Email] = newContributor | |||
} | |||
@@ -632,7 +651,7 @@ func Home(ctx *context.Context) { | |||
} | |||
ctx.Data["ContributorInfo"] = contributorInfos | |||
var duration = time.Since(startTime) | |||
log.Info("getContributorInfo cost: %v seconds",duration.Seconds()) | |||
log.Info("getContributorInfo cost: %v seconds", duration.Seconds()) | |||
} | |||
if ctx.Repo.Repository.IsBeingCreated() { | |||
task, err := models.GetMigratingTask(ctx.Repo.Repository.ID) | |||
@@ -699,13 +718,13 @@ func renderLicense(ctx *context.Context) { | |||
log.Error("failed to get license content: %v, err:%v", f, err) | |||
continue | |||
} | |||
if bytes.Compare(buf,license) == 0 { | |||
log.Info("got matched license:%v",f) | |||
if bytes.Compare(buf, license) == 0 { | |||
log.Info("got matched license:%v", f) | |||
ctx.Data["LICENSE"] = f | |||
return | |||
} | |||
} | |||
log.Info("not found matched license,repo:%v",ctx.Repo.Repository.Name) | |||
log.Info("not found matched license,repo:%v", ctx.Repo.Repository.Name) | |||
} | |||
func renderLanguageStats(ctx *context.Context) { | |||
@@ -806,31 +825,31 @@ func renderCode(ctx *context.Context) { | |||
baseGitRepo, err := git.OpenRepository(ctx.Repo.Repository.BaseRepo.RepoPath()) | |||
defer baseGitRepo.Close() | |||
if err != nil { | |||
log.Error("error open baseRepo:%s",ctx.Repo.Repository.BaseRepo.RepoPath()) | |||
log.Error("error open baseRepo:%s", ctx.Repo.Repository.BaseRepo.RepoPath()) | |||
ctx.Data["FetchUpstreamCnt"] = -1 // minus value indicates error | |||
}else{ | |||
if _,error:= baseGitRepo.GetBranch(ctx.Repo.BranchName);error==nil{ | |||
} else { | |||
if _, error := baseGitRepo.GetBranch(ctx.Repo.BranchName); error == nil { | |||
//base repo has the same branch, then compare between current repo branch and base repo's branch | |||
compareUrl := ctx.Repo.BranchName + "..." + ctx.Repo.Repository.BaseRepo.OwnerName + "/" + ctx.Repo.Repository.BaseRepo.Name + ":" + ctx.Repo.BranchName | |||
ctx.SetParams("*",compareUrl) | |||
compareUrl := ctx.Repo.BranchName + "..." + ctx.Repo.Repository.BaseRepo.OwnerName + "/" + ctx.Repo.Repository.BaseRepo.Name + ":" + ctx.Repo.BranchName | |||
ctx.SetParams("*", compareUrl) | |||
ctx.Data["UpstreamSameBranchName"] = true | |||
}else{ | |||
} else { | |||
//else, compare between current repo branch and base repo's default branch | |||
compareUrl := ctx.Repo.BranchName + "..." + ctx.Repo.Repository.BaseRepo.OwnerName + "/" + ctx.Repo.Repository.BaseRepo.Name + ":" + ctx.Repo.Repository.BaseRepo.DefaultBranch | |||
ctx.SetParams("*",compareUrl) | |||
compareUrl := ctx.Repo.BranchName + "..." + ctx.Repo.Repository.BaseRepo.OwnerName + "/" + ctx.Repo.Repository.BaseRepo.Name + ":" + ctx.Repo.Repository.BaseRepo.DefaultBranch | |||
ctx.SetParams("*", compareUrl) | |||
ctx.Data["UpstreamSameBranchName"] = false | |||
} | |||
_, _, headGitRepo, compareInfo, _, _ := ParseCompareInfo(ctx) | |||
defer headGitRepo.Close() | |||
if compareInfo!= nil { | |||
if compareInfo.Commits!=nil { | |||
log.Info("compareInfoCommits数量:%d",compareInfo.Commits.Len()) | |||
if compareInfo != nil { | |||
if compareInfo.Commits != nil { | |||
log.Info("compareInfoCommits数量:%d", compareInfo.Commits.Len()) | |||
ctx.Data["FetchUpstreamCnt"] = compareInfo.Commits.Len() | |||
}else{ | |||
} else { | |||
log.Info("compareInfo nothing different") | |||
ctx.Data["FetchUpstreamCnt"] = 0 | |||
} | |||
}else{ | |||
} else { | |||
ctx.Data["FetchUpstreamCnt"] = -1 // minus value indicates error | |||
} | |||
} | |||
@@ -898,3 +917,68 @@ func Forks(ctx *context.Context) { | |||
ctx.HTML(200, tplForks) | |||
} | |||
func Contributors(ctx *context.Context) { | |||
ctx.Data["PageIsViewCode"] = true | |||
ctx.HTML(http.StatusOK, tplContributors) | |||
} | |||
func ContributorsAPI(ctx *context.Context) { | |||
count := 0 | |||
errorCode := 0 | |||
errorMsg := "" | |||
contributors, err := git.GetContributors(ctx.Repo.Repository.RepoPath()) | |||
var contributorInfos []*ContributorInfo | |||
if err == nil && contributors != nil { | |||
contributorInfoHash := make(map[string]*ContributorInfo) | |||
for _, c := range contributors { | |||
if strings.Compare(c.Email, "") == 0 { | |||
continue | |||
} | |||
// get user info from committer email | |||
user, err := models.GetUserByActivateEmail(c.Email) | |||
if err == nil { | |||
// committer is system user, get info through user's primary email | |||
if existedContributorInfo, ok := contributorInfoHash[user.Email]; ok { | |||
// existed: same primary email, different committer name | |||
existedContributorInfo.CommitCnt += c.CommitCnt | |||
} else { | |||
// new committer info | |||
var newContributor = &ContributorInfo{ | |||
user, user.RelAvatarLink(),user.Name, user.Email,c.CommitCnt, | |||
} | |||
count++ | |||
contributorInfos = append(contributorInfos, newContributor) | |||
contributorInfoHash[user.Email] = newContributor | |||
} | |||
} else { | |||
// committer is not system user | |||
if existedContributorInfo, ok := contributorInfoHash[c.Email]; ok { | |||
// existed: same primary email, different committer name | |||
existedContributorInfo.CommitCnt += c.CommitCnt | |||
} else { | |||
var newContributor = &ContributorInfo{ | |||
user, "", "",c.Email,c.CommitCnt, | |||
} | |||
count++ | |||
contributorInfos = append(contributorInfos, newContributor) | |||
contributorInfoHash[c.Email] = newContributor | |||
} | |||
} | |||
} | |||
sort.Slice(contributorInfos, func(i, j int) bool { | |||
return contributorInfos[i].CommitCnt > contributorInfos[j].CommitCnt | |||
}) | |||
} else { | |||
log.Error("GetContributors failed: %v", err) | |||
errorCode = -1 | |||
errorMsg = err.Error() | |||
} | |||
ctx.JSON(http.StatusOK, GetContributorsInfo{ | |||
ErrorCode: errorCode, | |||
ErrorMsg: errorMsg, | |||
Count: count, | |||
ContributorInfo: contributorInfos, | |||
}) | |||
} |
@@ -325,6 +325,7 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
m.Get("/organizations", routers.ExploreOrganizations) | |||
m.Get("/code", routers.ExploreCode) | |||
m.Get("/images", routers.ExploreImages) | |||
m.Get("/data_analysis", routers.ExploreDataAnalysis) | |||
}, ignSignIn) | |||
m.Combo("/install", routers.InstallInit).Get(routers.Install). | |||
Post(bindIgnErr(auth.InstallForm{}), routers.InstallPost) | |||
@@ -795,6 +796,8 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
m.Get("/tool/query_user_static_page", repo.QueryUserStaticDataPage) | |||
// Grouping for those endpoints not requiring authentication | |||
m.Group("/:username/:reponame", func() { | |||
m.Get("/contributors", repo.Contributors) | |||
m.Get("/contributors/list", repo.ContributorsAPI) | |||
m.Group("/milestone", func() { | |||
m.Get("/:id", repo.MilestoneIssuesAndPulls) | |||
}, reqRepoIssuesOrPullsReader, context.RepoRef()) | |||
@@ -37,6 +37,7 @@ | |||
<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a> | |||
<a class="item" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "explore.organizations"}}</a> | |||
<a class="item" href="{{AppSubUrl}}/explore/images">{{.i18n.Tr "explore.images"}}</a> | |||
<a class="item" href="{{AppSubUrl}}/explore/data_analysis">{{.i18n.Tr "explore.data_analysis"}}</a> | |||
</div> | |||
</div> | |||
{{else if .IsLandingPageHome}} | |||
@@ -29,6 +29,7 @@ | |||
<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a> | |||
<a class="item" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "explore.organizations"}}</a> | |||
<a class="item" href="{{AppSubUrl}}/explore/images">{{.i18n.Tr "explore.images"}}</a> | |||
<a class="item" href="{{AppSubUrl}}/explore/data_analysis">{{.i18n.Tr "explore.data_analysis"}}</a> | |||
</div> | |||
</div> | |||
{{else if .IsLandingPageHome}} | |||
@@ -0,0 +1,14 @@ | |||
{{template "base/head" .}} | |||
<div id="data_analysis" style="height: 100%;"> | |||
</div> | |||
{{template "base/footer" .}} | |||
<style> | |||
.full.height { | |||
flex-grow: 1; | |||
padding-bottom: 53px; | |||
} | |||
</style> |
@@ -7,7 +7,11 @@ | |||
</div> | |||
</h1> | |||
<p class="am-lh-18">免费私有代码仓库,免费计算资源,大容量数据存储,<br>多类型硬件环境(GPU、NPU),AI开发流水线(开发-调试-训练-迭代)</p> | |||
<a class="circular ui secondary button" href="{{AppSubUrl}}/user/sign_up">立即使用 <i class="right arrow icon"></i></a> | |||
{{if .IsSigned}} | |||
<a class="circular ui secondary button" href="{{AppSubUrl}}/dashboard">立即使用 <i class="right arrow icon"></i></a> | |||
{{else}} | |||
<a class="circular ui secondary button" href="{{AppSubUrl}}/user/login">立即使用 <i class="right arrow icon"></i></a> | |||
{{end}} | |||
<div class="bannerpic"><img class="ui fluid image" src="/img/gitopeni-index-01.svg"></div> | |||
</div> | |||
</div><!-- end segment --> | |||
@@ -33,7 +37,11 @@ | |||
<p class="am-lh-18">在这里为你和你的团队创建项目,基于Git工具,提交记录或者回滚代码修改。<br> | |||
不论是公开或者私有仓库,都可免费使用所有功能。<br> | |||
尽情将你喜欢的代码都放在这里,仓库数量、存储容量不受限</p> | |||
<a class="ui blue basic button am-mt-20" href="{{AppSubUrl}}/user/sign_up">立即使用</a> | |||
{{if .IsSigned}} | |||
<a class="ui blue basic button am-mt-20" href="{{AppSubUrl}}/dashboard">立即使用 </a> | |||
{{else}} | |||
<a class="ui blue basic button am-mt-20" href="{{AppSubUrl}}/user/login">立即使用 </a> | |||
{{end}} | |||
</div> | |||
<div class="ten wide column computer only i-code-pic am-pt-30"> | |||
<img class="ui fluid rounded image am-shadow-2 am-mt-10" src="/img/i-code-pic.jpg" style="position: absolute;"> | |||
@@ -184,7 +192,12 @@ | |||
开发者可以根据使用需求,自由选择相应计算资源,可以测试模型在不同硬件环境下的适配能力、性能、稳定性等<br> | |||
如果您的模型需要更多的计算资源,也可以单独申请<br> | |||
</p> | |||
<a class="ui blue basic button am-mt-20" href="{{AppSubUrl}}/user/sign_up">马上使用</a> <a class="ui grey basic button am-mt-20" href="mailto:aiforge@openi.org.cn">单独申请</a> | |||
{{if .IsSigned}} | |||
<a class="ui blue basic button am-mt-20" href="{{AppSubUrl}}/dashboard">立即使用 </a><a class="ui grey basic button am-mt-20" href="mailto:aiforge@openi.org.cn">单独申请</a> | |||
{{else}} | |||
<a class="ui blue basic button am-mt-20" href="{{AppSubUrl}}/user/login">立即使用 </a><a class="ui grey basic button am-mt-20" href="mailto:aiforge@openi.org.cn">单独申请</a> | |||
{{end}} | |||
</div> | |||
</div> | |||
</div> | |||
@@ -0,0 +1,9 @@ | |||
{{template "base/head" .}} | |||
<div class="repository watchers"> | |||
{{template "repo/header" .}} | |||
<div class="ui container" id="Contributors"> | |||
</div> | |||
</div> | |||
{{template "base/footer" .}} |
@@ -93,7 +93,7 @@ | |||
<div class="ui tabular stackable menu navbar"> | |||
{{if .Permission.CanRead $.UnitTypeCode}} | |||
<div class="dropdown-menu"> | |||
<a class="{{if or .PageIsViewCode .PageIsReleaseList .PageIsWiki .PageIsActivity}}active{{end}} item" href="{{.RepoLink}}{{if (ne .BranchName .Repository.DefaultBranch)}}/src/{{.BranchNameSubURL | EscapePound}}{{end}}"> | |||
<a class="{{if or .PageIsViewCode .PageIsReleaseList .PageIsWiki .PageIsActivity .PageIsViewCode}}active{{end}} item" href="{{.RepoLink}}{{if (ne .BranchName .Repository.DefaultBranch)}}/src/{{.BranchNameSubURL | EscapePound}}{{end}}"> | |||
<span>{{svg "octicon-code" 16}} {{.i18n.Tr "repo.code"}} <i class="dropdown icon"></i></span> | |||
</a> | |||
<div class="dropdown-content"> | |||
@@ -4,7 +4,7 @@ | |||
font-size: 1.0em; | |||
margin-bottom: 1.0rem; | |||
} | |||
#contributorInfo > a:nth-child(n+25){ | |||
#contributorInfo > a:nth-child(n+26){ | |||
display:none; | |||
} | |||
#contributorInfo > a{ | |||
@@ -329,9 +329,15 @@ | |||
<div> | |||
<h4 class="ui header"> | |||
{{$lenCon := len .ContributorInfo}} | |||
{{if lt $lenCon 25 }} | |||
<strong>贡献者 ({{len .ContributorInfo}})</strong> | |||
{{else}} | |||
<strong>贡献者 ({{len .ContributorInfo}}+)</strong> | |||
{{end}} | |||
<div class="ui right"> | |||
<a class="membersmore text grey" href="javascript:;">全部 {{svg "octicon-chevron-right" 16}}</a> | |||
<a class="membersmore text grey" href="{{.RepoLink}}/contributors">全部 {{svg "octicon-chevron-right" 16}}</a> | |||
</div> | |||
</h4> | |||
<div class="ui members" id="contributorInfo"> | |||
@@ -353,10 +359,10 @@ | |||
</div> | |||
<script type="text/javascript"> | |||
$(document).ready(function(){ | |||
$(".membersmore").click(function(){ | |||
$("#contributorInfo > a:nth-child(n+25)").show(); | |||
}); | |||
}); | |||
// $(document).ready(function(){ | |||
// $(".membersmore").click(function(){ | |||
// $("#contributorInfo > a:nth-child(n+25)").show(); | |||
// }); | |||
// }); | |||
</script> | |||
{{template "base/footer" .}} |
@@ -4,7 +4,7 @@ | |||
<div class="ui container"> | |||
<div class="ui two column stackable grid"> | |||
<div class="column" style="display: flex;align-items: center;"> | |||
<div class="ui large breadcrumb"> | |||
<div class="ui breadcrumb"> | |||
<a class="section" href="{{.RepoLink}}/issues">{{.i18n.Tr "repo.issues"}}</a> | |||
<div class="divider"> / </div> | |||
<div class="action section">{{.Title | RenderEmoji}}</div> | |||
@@ -4,7 +4,7 @@ | |||
<div class="ui container"> | |||
<div class="ui three column stackable grid"> | |||
<div class="column" style="display: flex;align-items: center;"> | |||
<div class="ui large breadcrumb"> | |||
<div class="ui breadcrumb"> | |||
<a class="section" href="{{.RepoLink}}/issues">{{.i18n.Tr "repo.issues"}}</a> | |||
<div class="divider"> / </div> | |||
<a class="section" href="{{.RepoLink}}/milestones">{{.i18n.Tr "repo.milestones"}}</a> | |||
@@ -4,7 +4,7 @@ | |||
<div class="ui container"> | |||
<div class="ui two column stackable grid"> | |||
<div class="column" style="display: flex;align-items: center;"> | |||
<div class="ui large breadcrumb"> | |||
<div class="ui breadcrumb"> | |||
<a class="section" href="{{.RepoLink}}/issues">{{.i18n.Tr "repo.issues"}}</a> | |||
<div class="divider"> / </div> | |||
<a class="section" href="{{.RepoLink}}/milestones">{{.i18n.Tr "repo.milestones"}}</a> | |||
@@ -4,7 +4,7 @@ | |||
<div class="ui container"> | |||
<div class="ui two column stackable grid"> | |||
<div class="column" style="display: flex;align-items: center;"> | |||
<div class="ui large breadcrumb"> | |||
<div class="ui breadcrumb"> | |||
<a class="section" href="{{.RepoLink}}/issues">{{.i18n.Tr "repo.issues"}}</a> | |||
<div class="divider"> / </div> | |||
<div class="action section">{{.Title | RenderEmoji}}</div> | |||
@@ -4,7 +4,7 @@ | |||
<div class="ui container"> | |||
<div class="ui two column stackable grid"> | |||
<div class="column" style="display: flex;align-items: center;"> | |||
<div class="ui large breadcrumb"> | |||
<div class="ui breadcrumb"> | |||
<a class="section" href="{{.RepoLink}}/issues">{{.i18n.Tr "repo.issues"}}</a> | |||
<div class="divider"> / </div> | |||
<div class="action section">{{.i18n.Tr "repo.issues.new"}}</div> | |||
@@ -5,13 +5,13 @@ | |||
<div class="ui two column stackable grid"> | |||
<div class="column" style="display: flex;align-items: center;"> | |||
{{if .PageIsIssueList}} | |||
<div class="ui large breadcrumb"> | |||
<div class="ui breadcrumb"> | |||
<a class="section" href="{{.RepoLink}}/issues">{{.i18n.Tr "repo.issues"}}</a> | |||
<div class="divider"> / </div> | |||
<div class="action section">{{.i18n.Tr "repo.issues_detail"}}</div> | |||
</div> | |||
{{else}} | |||
<div class="ui large breadcrumb"> | |||
<div class="ui breadcrumb"> | |||
<a class="section" href="{{.RepoLink}}/pulls">{{.i18n.Tr "repo.pulls"}}</a> | |||
<div class="divider"> / </div> | |||
<div class="action section">{{.i18n.Tr "repo.issues_detail"}}</div> | |||
@@ -1,485 +0,0 @@ | |||
<!-- 头部导航栏 --> | |||
{{template "base/head" .}} | |||
<style> | |||
.selectcloudbrain .active.item{ | |||
color: #0087f5 !important; | |||
border: 1px solid #0087f5; | |||
margin: -1px; | |||
background: #FFF !important; | |||
} | |||
#deletemodel { | |||
width: 100%; | |||
height: 100%; | |||
} | |||
/* 弹窗 */ | |||
#mask { | |||
position: fixed; | |||
top: 0px; | |||
left: 0px; | |||
right: 0px; | |||
bottom: 0px; | |||
filter: alpha(opacity=60); | |||
background-color: #777; | |||
z-index: 1000; | |||
display: none; | |||
opacity: 0.8; | |||
-moz-opacity: 0.5; | |||
padding-top: 100px; | |||
color: #000000 | |||
} | |||
#loadingPage { | |||
margin: 200px auto; | |||
width: 50px; | |||
height: 40px; | |||
text-align: center; | |||
font-size: 10px; | |||
display: block; | |||
} | |||
#loadingPage>div { | |||
background-color: green; | |||
height: 100%; | |||
width: 6px; | |||
display: inline-block; | |||
-webkit-animation: sk-stretchdelay 1.2s infinite ease-in-out; | |||
animation: sk-stretchdelay 1.2s infinite ease-in-out; | |||
} | |||
#loadingPage .rect2 { | |||
-webkit-animation-delay: -1.1s; | |||
animation-delay: -1.1s; | |||
} | |||
#loadingPage .rect3 { | |||
-webkit-animation-delay: -1.0s; | |||
animation-delay: -1.0s; | |||
} | |||
#loadingPage .rect4 { | |||
-webkit-animation-delay: -0.9s; | |||
animation-delay: -0.9s; | |||
} | |||
#loadingPage .rect5 { | |||
-webkit-animation-delay: -0.8s; | |||
animation-delay: -0.8s; | |||
} | |||
@-webkit-keyframes sk-stretchdelay { | |||
0%, | |||
40%, | |||
100% { | |||
-webkit-transform: scaleY(0.4) | |||
} | |||
20% { | |||
-webkit-transform: scaleY(1.0) | |||
} | |||
} | |||
@keyframes sk-stretchdelay { | |||
0%, | |||
40%, | |||
100% { | |||
transform: scaleY(0.4); | |||
-webkit-transform: scaleY(0.4); | |||
} | |||
20% { | |||
transform: scaleY(1.0); | |||
-webkit-transform: scaleY(1.0); | |||
} | |||
} | |||
/* 消息框 */ | |||
.alert { | |||
display: none; | |||
position: fixed; | |||
width: 100%; | |||
z-index: 1001; | |||
padding: 15px; | |||
border: 1px solid transparent; | |||
border-radius: 4px; | |||
text-align: center; | |||
font-weight: bold; | |||
} | |||
.alert-success { | |||
color: #3c763d; | |||
background-color: #dff0d8; | |||
border-color: #d6e9c6; | |||
} | |||
.alert-info { | |||
color: #31708f; | |||
background-color: #d9edf7; | |||
border-color: #bce8f1; | |||
} | |||
.alert-warning { | |||
color: #8a6d3b; | |||
background-color: #fcf8e3; | |||
border-color: #faebcc; | |||
} | |||
.alert-danger { | |||
color: #a94442; | |||
background-color: #f2dede; | |||
border-color: #ebccd1; | |||
} | |||
.pusher { | |||
width: calc(100% - 260px); | |||
box-sizing: border-box; | |||
} | |||
/* 弹窗 (background) */ | |||
#imageModal { | |||
display: none; | |||
position: fixed; | |||
z-index: 1; | |||
left: 0; | |||
top: 0; | |||
width: 100%; | |||
height: 100%; | |||
overflow: auto; | |||
background-color: rgb(0, 0, 0); | |||
background-color: rgba(0, 0, 0, 0.4); | |||
} | |||
/* 弹窗内容 */ | |||
.modal-content { | |||
background-color: #fefefe; | |||
margin: 15% auto; | |||
padding: 20px; | |||
border: 1px solid #888; | |||
width: 30%; | |||
} | |||
/* 关闭按钮 */ | |||
.close { | |||
color: #aaa; | |||
float: right; | |||
font-size: 28px; | |||
font-weight: bold; | |||
} | |||
.close:hover, | |||
.close:focus { | |||
color: black; | |||
text-decoration: none; | |||
cursor: pointer; | |||
} | |||
.dis { | |||
margin-bottom: 20px; | |||
} | |||
.disabled { | |||
cursor: pointer; | |||
pointer-events: none; | |||
} | |||
</style> | |||
<!-- 弹窗 --> | |||
<div id="mask"> | |||
<div id="loadingPage"> | |||
<div class="rect1"></div> | |||
<div class="rect2"></div> | |||
<div class="rect3"></div> | |||
<div class="rect4"></div> | |||
<div class="rect5"></div> | |||
</div> | |||
</div> | |||
<!-- 提示框 --> | |||
<div class="alert"></div> | |||
<div class="repository release dataset-list view"> | |||
{{template "repo/header" .}} | |||
<!-- 列表容器 --> | |||
<div class="ui container"> | |||
<!-- 中间云脑和新建任务按钮 --> | |||
<div class="ui two column stackable grid "> | |||
<div class="column"> | |||
<div class="ui blue small menu compact selectcloudbrain"> | |||
<a class="active item" href="{{.RepoLink}}/modelarts/notebook">调试任务</a> | |||
<a class="item" href="{{.RepoLink}}/modelarts/train-job">训练任务</a> | |||
</div> | |||
</div> | |||
<div class="column right aligned"> | |||
<div class="ui selection dropdown" style="min-width: 10em;min-height:2.6em;border-radius: .28571429rem;margin-right: 1em;padding: .67em 3.2em .7em 1em;"> | |||
{{svg "octicon-server" 16}} | |||
<div class="default text" style="color: rgba(0,0,0,.87);"> Ascend NPU</div> | |||
<i class="dropdown icon"></i> | |||
<div class="menu"> | |||
<a class="item" href="{{.RepoLink}}/cloudbrain" data-value="11">CPU / GPU</a> | |||
<a class="item" href="{{.RepoLink}}/modelarts" data-value="22">Ascend NPU</a> | |||
</div> | |||
</div> | |||
<a class="ui green button" href="{{.RepoLink}}/modelarts/create">新建调试任务</a> | |||
</div> | |||
</div> | |||
<!-- 中下列表展示区 --> | |||
<div class="ui grid"> | |||
<div class="row"> | |||
<div class="ui sixteen wide column"> | |||
<!-- 排序区 --> | |||
<!-- <div class="ui sixteen wide column"> | |||
<div class="ui two column stackable grid"> | |||
<div class="column"> | |||
</div> | |||
<div class="column right aligned"> | |||
<div class="ui right dropdown type jump item"> | |||
<span class="text"> | |||
{{.i18n.Tr "repo.issues.filter_sort"}}<i class="dropdown icon"></i> | |||
</span> | |||
</div> | |||
</div> | |||
</div> | |||
</div> --> | |||
<!-- 任务展示 --> | |||
<div class="dataset list"> | |||
<!-- 表头 --> | |||
<div class="ui grid stackable" style="background: #f0f0f0;;"> | |||
<div class="row"> | |||
<div class="five wide column"> | |||
<span style="margin:0 6px">{{$.i18n.Tr "repo.cloudbrain_task"}}</span> | |||
</div> | |||
<div class="three wide column"> | |||
<span>{{$.i18n.Tr "repo.cloudbrain_status_createtime"}}</span> | |||
</div> | |||
<div class="one wide column"> | |||
<span>{{$.i18n.Tr "repo.cloudbrain_creator"}}</span> | |||
</div> | |||
<div class="seven wide column text center"> | |||
<span style="margin-left: 10rem;">{{$.i18n.Tr "repo.cloudbrain_operate"}}</span> | |||
</div> | |||
</div> | |||
</div> | |||
{{range .Tasks}} | |||
<div class="ui grid stackable item"> | |||
<div class="row"> | |||
<!-- 任务名 --> | |||
<div class="five wide column"> | |||
<a class="title" href="{{$.Link}}/{{.JobID}}" title="{{.JobName}}" style="font-size: 15px;"> | |||
<span class="fitted" style="vertical-align: middle;">{{svg "octicon-tasklist" 16}}</span> | |||
<span class="fitted" style="width: 90%;vertical-align: middle;margin-left: 0.4rem;">{{.JobName}}</span> | |||
</a> | |||
</div> | |||
<div class="three wide column"> | |||
<!--任务状态 --> | |||
<!-- <span class="ui compact button job-status" id="{{.JobID}}" data-repopath="{{$.RepoRelPath}}" data-jobid="{{.JobID}}"> | |||
{{.Status}} | |||
</span> --> | |||
<span class="job-status" id="{{.JobID}}" data-repopath="{{$.RepoRelPath}}" data-jobid="{{.JobID}}"> | |||
<span><i style="vertical-align: middle;" class="{{.Status}}"></i><span style="margin-left: 0.4em;font-size: 12px;">{{.Status}}</span></span> | |||
</span> | |||
<!-- 任务创建时间 --> | |||
<span style="font-size: 12px;margin-left: 0.4rem;" class="">{{TimeSinceUnix .Cloudbrain.CreatedUnix $.Lang}}</span> | |||
</div> | |||
<div class="one wide column"> | |||
{{if .User.Name}} | |||
<a href="{{AppSubUrl}}/{{.User.Name}}" title="{{.User.Name}}"><img class="ui avatar image" src="{{.User.RelAvatarLink}}"></a> | |||
{{else}} | |||
<a title="Ghost"><img class="ui avatar image" src="{{AppSubUrl}}/user/avatar/Ghost/-1"></a> | |||
{{end}} | |||
</div> | |||
<div class="seven wide column text right"> | |||
<div class="ui compact buttons" style="margin-right:10px;"> | |||
<a class="ui basic blue button" href="{{$.Link}}/{{.JobID}}"> | |||
查看 | |||
</a> | |||
<a class="ui basic {{if not .CanDebug}}disabled {{else}}blue {{end}}button" href="{{$.Link}}/{{.JobID}}/debug" target="_blank"> | |||
调试 | |||
</a> | |||
<form id="stopForm-{{.JobID}}" action="{{if ne .Status "RUNNING"}}javascript:void(0){{else}}{{$.Link}}/{{.JobID}}/stop{{end}}" method="post" style="margin-left:-1px;"> | |||
{{$.CsrfTokenHtml}} | |||
<a class="ui basic {{if ne .Status "RUNNING"}}disabled {{else}}blue {{end}}button" onclick="document.getElementById('stopForm-{{.JobID}}').submit();"> | |||
停止 | |||
</a> | |||
</form> | |||
</div> | |||
<!-- 删除任务 --> | |||
<form class="ui compact buttons" id="delForm-{{.JobID}}" action="{{if not .CanDel}}javascript:void(0){{else}}{{$.Link}}/{{.JobID}}/del{{end}}" method="post"> | |||
{{$.CsrfTokenHtml}} | |||
<a class="ui compact {{if not .CanDel}}disabled {{else}}red {{end}}button" onclick="assertDelete(this)" style="border-radius: .28571429rem;"> | |||
删除 | |||
</a> | |||
</form> | |||
</div> | |||
</div> | |||
</div> | |||
{{end}} {{template "base/paginate" .}} | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
<!-- 确认模态框 --> | |||
<div id="deletemodel"> | |||
<div class="ui basic modal"> | |||
<div class="ui icon header"> | |||
<i class="trash icon"></i> 删除任务 | |||
</div> | |||
<div class="content"> | |||
<p>你确认删除该任务么?此任务一旦删除不可恢复。</p> | |||
</div> | |||
<div class="actions"> | |||
<div class="ui red basic inverted cancel button"> | |||
<i class="remove icon"></i> 取消操作 | |||
</div> | |||
<div class="ui green basic inverted ok button"> | |||
<i class="checkmark icon"></i> 确定操作 | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
{{template "base/footer" .}} | |||
<script> | |||
// 调试和评分新开窗口 | |||
function stop(obj) { | |||
if (obj.style.color != "rgb(204, 204, 204)") { | |||
obj.target = '_blank' | |||
} else { | |||
return | |||
} | |||
} | |||
// 删除时用户确认 | |||
function assertDelete(obj) { | |||
if (obj.style.color == "rgb(204, 204, 204)") { | |||
return | |||
} else { | |||
var delId = obj.parentNode.id | |||
flag = 1; | |||
$('.ui.basic.modal') | |||
.modal({ | |||
onDeny: function() { | |||
flag = false | |||
}, | |||
onApprove: function() { | |||
document.getElementById(delId).submit() | |||
flag = true | |||
}, | |||
onHidden: function() { | |||
if (flag == false) { | |||
$('.alert').html('您已取消操作').removeClass('alert-success').addClass('alert-danger').show().delay(1500).fadeOut(); | |||
} | |||
} | |||
}) | |||
.modal('show') | |||
} | |||
} | |||
// 加载任务状态 | |||
var timeid = window.setInterval(loadJobStatus, 15000); | |||
$(document).ready(loadJobStatus); | |||
function loadJobStatus() { | |||
$(".job-status").each((index, job) => { | |||
const jobID = job.dataset.jobid; | |||
const repoPath = job.dataset.repopath; | |||
if (job.textContent.trim() == 'STOPPED' || job.textContent.trim() == 'START_FAILED' || job.textContent.trim() == 'CREATE_FAILED') { | |||
return | |||
} | |||
$.get(`/api/v1/repos/${repoPath}/modelarts/${jobID}`, (data) => { | |||
const jobID = data.JobID | |||
const status = data.JobStatus | |||
if (status != job.textContent.trim()) { | |||
//$('#' + jobID).text(status) | |||
//if (status == 'STOPPED') { | |||
window.location.reload() | |||
//} | |||
} | |||
}).fail(function(err) { | |||
console.log(err); | |||
}); | |||
}); | |||
}; | |||
// 获取弹窗 | |||
var modal = document.getElementById('imageModal'); | |||
// 打开弹窗的按钮对象 | |||
var btns = document.getElementsByClassName("imageBtn"); | |||
// 获取 <span> 元素,用于关闭弹窗 | |||
var spans = document.getElementsByClassName('close'); | |||
// 点击按钮打开弹窗 | |||
for (i = 0; i < btns.length; i++) { | |||
btns[i].onclick = function() { | |||
modal.style.display = "block"; | |||
} | |||
} | |||
// 点击 <span> (x), 关闭弹窗 | |||
for (i = 0; i < spans.length; i++) { | |||
spans[i].onclick = function() { | |||
modal.style.display = "none"; | |||
} | |||
} | |||
// 在用户点击其他地方时,关闭弹窗 | |||
window.onclick = function(event) { | |||
if (event.target == modal) { | |||
modal.style.display = "none"; | |||
} | |||
} | |||
// 显示弹窗,弹出相应的信息 | |||
function showmask() { | |||
$('#imageModal').css('display', 'none') | |||
$('#mask').css('display', 'block') | |||
$("iframe[name=iframeContent]").on("load", function() { | |||
var responseText = $("iframe")[0].contentDocument.body.getElementsByTagName("pre")[0].innerHTML; | |||
var json1 = JSON.parse(responseText) | |||
$('#mask').css('display', 'none') | |||
parent.location.href | |||
if (json1.result_code === "0") { | |||
$('.alert').html('操作成功!').removeClass('alert-danger').addClass('alert-success').show().delay(1500).fadeOut(); | |||
} else { | |||
$('.alert').html(json1.error_msg).removeClass('alert-success').addClass('alert-danger').show().delay(5000).fadeOut(); | |||
} | |||
}) | |||
} | |||
</script> |
@@ -1,240 +0,0 @@ | |||
{{template "base/head" .}} | |||
<style> | |||
/* 遮罩层css效果图 */ | |||
#mask { | |||
position: fixed; | |||
top: 0px; | |||
left: 0px; | |||
right: 0px; | |||
bottom: 0px; | |||
filter: alpha(opacity=60); | |||
background-color: #777; | |||
z-index: 1000; | |||
display: none; | |||
opacity: 0.8; | |||
-moz-opacity: 0.5; | |||
padding-top: 100px; | |||
color: #000000 | |||
} | |||
/* 加载圈css效果图 */ | |||
#loadingPage { | |||
margin: 200px auto; | |||
width: 50px; | |||
height: 40px; | |||
text-align: center; | |||
font-size: 10px; | |||
display: block; | |||
} | |||
#loadingPage>div { | |||
background-color: green; | |||
height: 100%; | |||
width: 6px; | |||
display: inline-block; | |||
-webkit-animation: sk-stretchdelay 1.2s infinite ease-in-out; | |||
animation: sk-stretchdelay 1.2s infinite ease-in-out; | |||
} | |||
#loadingPage .rect2 { | |||
-webkit-animation-delay: -1.1s; | |||
animation-delay: -1.1s; | |||
} | |||
#loadingPage .rect3 { | |||
-webkit-animation-delay: -1.0s; | |||
animation-delay: -1.0s; | |||
} | |||
#loadingPage .rect4 { | |||
-webkit-animation-delay: -0.9s; | |||
animation-delay: -0.9s; | |||
} | |||
#loadingPage .rect5 { | |||
-webkit-animation-delay: -0.8s; | |||
animation-delay: -0.8s; | |||
} | |||
@-webkit-keyframes sk-stretchdelay { | |||
0%, | |||
40%, | |||
100% { | |||
-webkit-transform: scaleY(0.4) | |||
} | |||
20% { | |||
-webkit-transform: scaleY(1.0) | |||
} | |||
} | |||
@keyframes sk-stretchdelay { | |||
0%, | |||
40%, | |||
100% { | |||
transform: scaleY(0.4); | |||
-webkit-transform: scaleY(0.4); | |||
} | |||
20% { | |||
transform: scaleY(1.0); | |||
-webkit-transform: scaleY(1.0); | |||
} | |||
} | |||
.inline.required.field.cloudbrain_benchmark { | |||
display: none; | |||
} | |||
</style> | |||
<div id="mask"> | |||
<div id="loadingPage"> | |||
<div class="rect1"></div> | |||
<div class="rect2"></div> | |||
<div class="rect3"></div> | |||
<div class="rect4"></div> | |||
<div class="rect5"></div> | |||
</div> | |||
</div> | |||
<div class="repository"> | |||
{{template "repo/header" .}} | |||
<div class="repository new repo ui middle very relaxed page grid"> | |||
<div class="column"> | |||
{{template "base/alert" .}} | |||
<div class="ui negative message" id="messageInfo"> | |||
<p></p> | |||
</div> | |||
<form class="ui form" id="form_id" action="{{.Link}}" method="post"> | |||
{{.CsrfTokenHtml}} | |||
<h3 class="ui top attached header"> | |||
{{.i18n.Tr "repo.cloudbrain.new"}} | |||
</h3> | |||
<div class="ui attached segment"> | |||
<!-- <br> --> | |||
<div class="inline required field"> | |||
<label>任务名称</label> | |||
<input name="job_name" id="cloudbrain_job_name" placeholder="任务名称" value="{{.job_name}}" tabindex="3" autofocus required maxlength="255"> | |||
</div> | |||
<div class="inline field"> | |||
<label>数据集</label> | |||
<input type="text" list="cloudbrain_dataset" placeholder="选择数据集" name="" id="answerInput" autofocus maxlength="36"> | |||
<datalist id="cloudbrain_dataset" class="ui search" style='width:385px' name="attachment"> | |||
{{range .attachments}} | |||
<option name="attachment" data-value="{{.UUID}}">{{.Attachment.Name}}</option> | |||
{{end}} | |||
</datalist> | |||
<input type="hidden" name="attachment" id="answerInput-hidden"> | |||
</div> | |||
<div class="inline required field"> | |||
<label>工作环境</label> | |||
<input name="de" id="cloudbrain_de" value="{{.env}}" tabindex="3" disabled autofocus required maxlength="255" readonly="readonly"> | |||
</div> | |||
<div class="inline required field"> | |||
<label>类型</label> | |||
<input name="job_type" id="cloudbrain_job_type" value="{{.notebook_type}}" tabindex="3" disabled autofocus required maxlength="255" readonly="readonly"> | |||
</div> | |||
<div class="inline required field"> | |||
<label>规格</label> | |||
<select id="cloudbrain_flavor" class="ui search dropdown" placeholder="选择规格" style='width:385px' name="flavor"> | |||
{{range .flavors}} | |||
<option name="flavor" value="{{.Value}}">{{.Value}}</option> | |||
{{end}} | |||
</select> | |||
</div> | |||
<div class="inline required field"> | |||
<label>数据集存放路径</label> | |||
<input name="dataset_path" id="cloudbrain_dataset_path" value="{{.dataset_path}}" tabindex="3" disabled autofocus required maxlength="255" readonly="readonly"> | |||
</div> | |||
<div class="inline field"> | |||
<label>描述</label> | |||
<input name="description" id="cloudbrain_description" tabindex="3" autofocus maxlength="255"> | |||
</div> | |||
<div class="inline field"> | |||
<label></label> | |||
<button class="ui green button"> | |||
{{.i18n.Tr "repo.cloudbrain.new"}} | |||
</button> | |||
<a class="ui button" href="/">{{.i18n.Tr "repo.cloudbrain.cancel"}}</a> | |||
</div> | |||
</div> | |||
</form> | |||
</div> | |||
</div> | |||
</div> | |||
{{template "base/footer" .}} | |||
<script> | |||
// 取消创建跳转 | |||
let url_href = window.location.pathname.split('create')[0] | |||
$(".ui.button").attr('href',url_href) | |||
// 判断必填选项是否填写正确 | |||
let form = document.getElementById('form_id'); | |||
$('#messageInfo').css('display','none') | |||
form.onsubmit = function(e){ | |||
let value_task = $("input[name='job_name']").val() | |||
let re = /^[a-z0-9][a-z0-9-_]{1,34}[a-z0-9-]$/ | |||
let flag = re.test(value_task) | |||
if(!flag){ | |||
$('#messageInfo').css('display','block') | |||
let str = '只能以小写字母或数字开头且只包含小写字母、数字、_和-,不能以_结尾,最长36个字符。' | |||
$('#messageInfo p').text(str) | |||
return false | |||
} | |||
let min_value_task = value_task.toLowerCase() | |||
$("input[name='job_name']").attr("value",min_value_task) | |||
document.getElementById("mask").style.display = "block" | |||
} | |||
// 点击按钮后遮罩层显示 | |||
// function showmask() { | |||
// document.getElementById("mask").style.display = "block" | |||
// } | |||
// 页面加载完毕后遮罩层隐藏 | |||
document.onreadystatechange = function() { | |||
if (document.readyState === "complete") { | |||
document.getElementById("mask").style.display = "none" | |||
} | |||
} | |||
$('select.dropdown') | |||
.dropdown(); | |||
$(function() { | |||
$("#cloudbrain_job_type").change(function() { | |||
if ($(this).val() == 'BENCHMARK') { | |||
$(".cloudbrain_benchmark").show(); | |||
} else { | |||
$(".cloudbrain_benchmark").hide(); | |||
} | |||
}) | |||
}) | |||
document.querySelector('input[list]').addEventListener('input',function(e){ | |||
var input = e.target, | |||
list = input.getAttribute('list'), | |||
options = document.querySelectorAll('#'+list+' option'), | |||
hiddenInput = document.getElementById(input.getAttribute('id')+'-hidden'), | |||
inputValue = input.value; | |||
hiddenInput.value = inputValue; | |||
for (let i=0;i<options.length;i++){ | |||
var option = options[i] | |||
if(option.innerText===inputValue){ | |||
hiddenInput.value = option.getAttribute('data-value'); | |||
break | |||
} | |||
} | |||
}) | |||
</script> |
@@ -138,7 +138,7 @@ | |||
<label>规格</label> | |||
<select id="cloudbrain_flavor" class="ui search dropdown" placeholder="选择规格" style='width:385px' name="flavor"> | |||
{{range .flavors}} | |||
<option name="flavor" value="{{.Value}}">{{.Value}}</option> | |||
<option name="flavor" value="{{.Value}}">{{.Desc}}</option> | |||
{{end}} | |||
</select> | |||
@@ -1,122 +0,0 @@ | |||
{{template "base/head" .}} | |||
<div class="repository"> | |||
{{template "repo/header" .}} | |||
<div class="repository new repo ui middle very relaxed page grid"> | |||
<div class="column"> | |||
{{template "base/alert" .}} | |||
<h4 class="ui header" id="vertical-segment"> | |||
<a href="javascript:window.history.back();"><i class="arrow left icon"></i>返回</a> | |||
</h4> | |||
<div> | |||
<div class="ui yellow segment"> | |||
{{with .task}} | |||
<p>任务名称: {{.JobName}}</p> | |||
{{end}} | |||
</div> | |||
<div class="ui green segment"> | |||
<p>任务结果:</p> | |||
{{with .result}} | |||
<table class="ui celled striped table"> | |||
<tbody> | |||
<tr> | |||
<td class="four wide"> 状态 </td> | |||
<td> {{.Status}} </td> | |||
</tr> | |||
<tr> | |||
<td> 开始时间 </td> | |||
<td>{{.CreateTime}}</td> | |||
</tr> | |||
<tr> | |||
<td> 最后更新时间 </td> | |||
<td>{{.LatestUpdateTime}}</td> | |||
</tr> | |||
</tbody> | |||
</table> | |||
{{end}} | |||
</div> | |||
<div class="ui blue segment"> | |||
{{with .result}} | |||
<table class="ui celled striped table"> | |||
<thead> | |||
<tr> <th colspan="2"> 配置信息 </th> </tr> | |||
</thead> | |||
<tbody> | |||
<tr> | |||
<td class="four wide"> 开发环境类型 </td> | |||
<td>{{.Profile.DeType}}</td> | |||
</tr> | |||
<tr> | |||
<td> 硬件类型 </td> | |||
<td>{{.Profile.FlavorType}}</td> | |||
</tr> | |||
</tbody> | |||
</table> | |||
<table class="ui celled striped table"> | |||
<thead> | |||
<tr> <th colspan="2"> 机器规格详情 </th> </tr> | |||
</thead> | |||
<tbody> | |||
<tr> | |||
<td class="four wide"> 机器规格 </td> | |||
<td> {{.Flavor}} </td> | |||
</tr> | |||
<tr> | |||
<td> 规格名称 </td> | |||
<td>{{.FlavorDetails.Name}}</td> | |||
</tr> | |||
<tr> | |||
<td> 规格销售状态 </td> | |||
<td>{{.FlavorDetails.Status}}</td> | |||
</tr> | |||
<tr> | |||
<td> 排队个数 </td> | |||
<td>{{.FlavorDetails.QueuingNum}}</td> | |||
</tr> | |||
<tr> | |||
<td> 排到队的剩余时间(秒) </td> | |||
<td>{{.FlavorDetails.QueueLeftTime}}</td> | |||
</tr> | |||
<tr> | |||
<td> 自动停止时间(秒) </td> | |||
<td>{{.FlavorDetails.Duration}}</td> | |||
</tr> | |||
</tbody> | |||
</table> | |||
<table class="ui celled striped table" {{if eq .QueuingInfo.RemainTime 0}}hidden{{end}}> | |||
<thead> | |||
<tr> <th colspan="2"> 排队信息 </th> </tr> | |||
</thead> | |||
<tbody> | |||
<tr> | |||
<td> 实例状态 </td> | |||
<td>{{.QueuingInfo.Status}}</td> | |||
</tr> | |||
<tr> | |||
<td> 实例排队的开始时间 </td> | |||
<td>{{.QueuingInfo.BeginTime}}</td> | |||
</tr> | |||
<tr> | |||
<td> 排到队的剩余时间(秒) </td> | |||
<td>{{.QueuingInfo.RemainTime}}</td> | |||
</tr> | |||
<tr> | |||
<td> 实例排队的预计停止时间 </td> | |||
<td>{{.QueuingInfo.EndTime}}</td> | |||
</tr> | |||
<tr> | |||
<td> 实例在队列中的排位 </td> | |||
<td>{{.QueuingInfo.Rank}}</td> | |||
</tr> | |||
</tbody> | |||
</table> | |||
{{end}} | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
{{template "base/footer" .}} |
@@ -10,7 +10,7 @@ | |||
</div> --> | |||
<div class="ui two column stackable grid"> | |||
<div class="column" style="display: flex;align-items: center;"> | |||
<div class="ui large breadcrumb"> | |||
<div class="ui breadcrumb"> | |||
<a class="section" href="{{.RepoLink}}/pulls">{{.i18n.Tr "repo.pulls"}}</a> | |||
<div class="divider"> / </div> | |||
<div class="action section">{{.i18n.Tr "repo.issues_detail"}}</div> | |||
@@ -10,7 +10,7 @@ | |||
</div> --> | |||
<div class="ui two column stackable grid"> | |||
<div class="column" style="display: flex;align-items: center;"> | |||
<div class="ui large breadcrumb"> | |||
<div class="ui breadcrumb"> | |||
<a class="section" href="{{.RepoLink}}/pulls">{{.i18n.Tr "repo.pulls"}}</a> | |||
<div class="divider"> / </div> | |||
<div class="action section">{{.i18n.Tr "repo.issues_detail"}}</div> | |||
@@ -0,0 +1,112 @@ | |||
<template> | |||
<div class="ui container"> | |||
<div class="row git-user-content"> | |||
<h3 class="ui header"> | |||
<div class="ui breadcrumb"> | |||
<a class="section" :href="url_code">代码</a> | |||
<div class="divider"> / </div> | |||
<div class="active section" >贡献者 ({{totalNum}})</div> | |||
</div> | |||
</h3> | |||
<div class="ui horizontal relaxed list"> | |||
<div class="item user-list-item" v-for="(contributor,i) in contributors_list_page" > | |||
<a v-if="contributor.user_name" :href="AppSubUrl +'/'+ contributor.user_name"><img class="ui avatar s16 image js-popover-card" :src="contributor.rel_avatar_link"></a> | |||
<a v-else :href="'mailto:' + contributor.email "><img class="ui avatar s16 image js-popover-card" :avatar="contributor.email"></a> | |||
<div class="content"> | |||
<div class="header" > | |||
<a v-if="contributor.user_name" :href="AppSubUrl +'/'+ contributor.user_name">{{contributor.user_name}}</a> | |||
<a v-else :href="'mailto:' + contributor.email ">{{contributor.email}}</a> | |||
</div> | |||
<span class="commit-btn">Commits: {{contributor.commit_cnt}}</span> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="ui container" style="margin-top:50px;text-align:center"> | |||
<el-pagination | |||
background | |||
@current-change="handleCurrentChange" | |||
:current-page="currentPage" | |||
:page-size="pageSize" | |||
layout="total, prev, pager, next" | |||
:total="totalNum"> | |||
</el-pagination> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
const {AppSubUrl, StaticUrlPrefix, csrf} = window.config; | |||
export default { | |||
data() { | |||
return { | |||
url:'', | |||
contributors_list:[], | |||
contributors_list_page:[], | |||
currentPage:1, | |||
pageSize:50, | |||
totalNum:0, | |||
AppSubUrl:AppSubUrl | |||
}; | |||
}, | |||
methods: { | |||
getContributorsList(){ | |||
this.$axios.get(this.url+'/list').then((res)=>{ | |||
this.contributors_list = res.data.contributor_info | |||
this.totalNum = this.contributors_list.length | |||
this.contributors_list_page = this.contributors_list.slice(0,this.pageSize) | |||
}) | |||
}, | |||
handleCurrentChange(val){ | |||
this.contributors_list_page = this.contributors_list.slice((val-1)*this.pageSize,val*this.pageSize) | |||
}, | |||
}, | |||
computed:{ | |||
}, | |||
watch: { | |||
}, | |||
created(){ | |||
const url = window.location.pathname; | |||
this.url = url; | |||
let strIndex = this.url.indexOf("contributors") | |||
this.url_code = this.url.substr(0,strIndex) | |||
this.getContributorsList() | |||
}, | |||
updated(){ | |||
if(document.querySelectorAll('img[avatar]').length!==0){ | |||
window.LetterAvatar.transform() | |||
} | |||
} | |||
}; | |||
</script> | |||
<style scoped> | |||
/deep/ .el-pagination.is-background .el-pager li:not(.disabled).active { | |||
background-color: #5bb973; | |||
color: #FFF; | |||
} | |||
/deep/ .el-pagination.is-background .el-pager li.active { | |||
color: #fff; | |||
cursor: default; | |||
} | |||
/deep/ .el-pagination.is-background .el-pager li:hover { | |||
color: #5bb973; | |||
} | |||
/deep/ .el-pagination.is-background .el-pager li:not(.disabled):hover { | |||
color: #5bb973; | |||
} | |||
/deep/ .el-pagination.is-background .el-pager li:not(.disabled).active:hover { | |||
background-color: #5bb973; | |||
color: #FFF; | |||
} | |||
</style> |
@@ -0,0 +1,123 @@ | |||
<template> | |||
<div style="height:100%"> | |||
<el-tabs tab-position="left" v-model="activeName" style="height:100%" @tab-click="handleClick" > | |||
<el-tab-pane label="概览" name="first" > | |||
<span slot="label"> | |||
<el-image style="width: 13px; height: 13px" src="/img/overview.png"> | |||
</el-image> | |||
概览 | |||
</span> | |||
<div >概览.......</div> | |||
<div >概览.......</div> | |||
<div >概览.......</div><div >概览.......</div> | |||
<div >概览.......</div> | |||
</el-tab-pane> | |||
<el-tab-pane label="项目分析" name="second" id="second" > | |||
<ProAnalysis ref='ProAnalysis'id="pro" v-if="isRouterAlive"></ProAnalysis> | |||
<span slot="label"> | |||
<el-image style="width: 13px; height: 13px" src="/img/pro.svg"> | |||
</el-image> | |||
项目分析 | |||
</span> | |||
</el-tab-pane> | |||
<el-tab-pane name="third" > | |||
<span slot='label'> | |||
<el-image style="width: 13px; height: 13px" src="/img/name.png"> | |||
</el-image> | |||
用户分析 | |||
</span> | |||
<UserAnalysis ref='UserAnalysis' id ="usr"></UserAnalysis> | |||
</el-tab-pane> | |||
</el-tabs> | |||
</div> | |||
</template> | |||
<script> | |||
import ProAnalysis from './ProAnalysis.vue' | |||
import UserAnalysis from './UserAnalysis.vue' | |||
export default { | |||
components:{ | |||
'ProAnalysis':ProAnalysis, | |||
'UserAnalysis':UserAnalysis, | |||
}, | |||
data() { | |||
return { | |||
activeName:"second", | |||
loading:true, | |||
loading1:true, | |||
isRouterAlive: true, | |||
isSecond:true, | |||
isThird:false, | |||
} | |||
}, | |||
methods:{ | |||
handleClick(tab, event){ | |||
if(tab.name=="second"){ | |||
this.reload() | |||
//document.getElementById('usr').style.display="none" | |||
//document.getElementById("pro").style.display='block' | |||
//this.$refs.ProAnalysis.getAllProList("all",7) | |||
this.isSecond = true | |||
this.isThird = false | |||
this.$refs.ProAnalysis.getAllProList("all",7) | |||
} | |||
if(tab.name=="third"){ | |||
// document.getElementById('usr').style.display="block" | |||
// document.getElementById("pro").style.display='none' | |||
//this.$refs.UserAnalysis.getUserList("all_usr",7) | |||
this.isSecond = false | |||
this.isThird = true | |||
} | |||
}, | |||
reload () { | |||
this.isRouterAlive = false | |||
this.$nextTick(() => (this.isRouterAlive = true)) | |||
} | |||
}, | |||
} | |||
</script> | |||
<style scoped> | |||
/deep/ .is-active{ | |||
color: #238BFC ; | |||
background-color: #FFFF ; | |||
} | |||
/deep/ .ui-container{ | |||
background-color: #FFFF; | |||
} | |||
/deep/ .el-tabs--left .el-tabs__header.is-left{ | |||
background-color:#F5F5F6; | |||
width: 12.43%; | |||
} | |||
.el-tabs--left .el-tabs__header.is-left | |||
html, | |||
body, | |||
/deep/ .el-container { | |||
padding: 0px; | |||
margin: 0px; | |||
height: 100%; | |||
} | |||
/deep/ .el-tabs--left .el-tabs__item.is-left { | |||
text-align: left; | |||
} | |||
/deep/ .el-tabs__item { | |||
padding: 0px 20px 0px 20px; | |||
} | |||
</style> |
@@ -0,0 +1,923 @@ | |||
<template> | |||
<div style="width: 100%;"> | |||
<div id = "pro_main"> | |||
<div style="margin-top: 10px;"> | |||
<b class="pro_item">项目分析</b> <span class="update_time">数据更新时间:{{lastUpdatedTime}}/{{recordBeginTime}}</span> | |||
</div> | |||
<bar-label :width="'95%'" :height="'500px'"></bar-label> | |||
<div style="margin-top: 20px;"> | |||
<span class="sta_iterm">统计周期:</span> | |||
<button type="button" class='btn' id ="yesterday" v-bind:class="{colorChange:1==dynamic}" @click="getAllProList('yesterday',1)">昨天</button> | |||
<button type="button" class='btn' id = "current_week" v-bind:class="{colorChange:2==dynamic}" @click="getAllProList('current_week',2)">本周</button> | |||
<button type="button" class='btn' id = "current_month" v-bind:class="{colorChange:3==dynamic}" @click="getAllProList('current_month',3)">本月</button> | |||
<button type="button" class='btn' id = "last_month" v-bind:class="{colorChange:4==dynamic}" @click="getAllProList('last_month',4)">上月</button> | |||
<button type="button" class='btn' id = "monthly" v-bind:class="{colorChange:5==dynamic}" @click="getAllProList('monthly',5)">近30天</button> | |||
<button type="button" class='btn' id = "current_year" v-bind:class="{colorChange:6==dynamic}" @click="getAllProList('current_year',6)">今年</button> | |||
<button type="button" class='btn' id = "all" v-bind:class="{colorChange:7==dynamic}" @click="getAllProList('all',7)">所有</button> | |||
<span style="margin-left: 20px;"> | |||
<el-date-picker | |||
v-model="value_time" | |||
prefix-icon="el-icon-time" | |||
@change="getAllProList('',0)" | |||
type="daterange" | |||
size='small' | |||
range-separator="至" | |||
start-placeholder="开始日期" | |||
end-placeholder="结束日期"> | |||
</el-date-picker> | |||
</span> | |||
<span style="float:right; margin-right: 20px;"> | |||
<div style="display:inline-block;margin-left: 20px; "> | |||
<i class="el-icon-download"></i> | |||
<span ><a>下载报告</a> </span> | |||
</div> | |||
<span style="display:inline-block;margin-left: 20px; "> | |||
<el-input size="small" placeholder="输入项目关键字搜索" v-model="search" class="input-with-select" @keyup.enter.native="searchName() "><i slot="suffix" class="el-input__icon el-icon-search"></i> | |||
</el-input> | |||
</span> | |||
</span> | |||
</div> | |||
<div style="margin-top: 30px;"> | |||
<el-table | |||
:data="tableData" | |||
style="width: 100%" | |||
:header-cell-style="tableHeaderStyle" | |||
:cell-style='cellStyle'> | |||
<el-table-column | |||
label="ID" | |||
align="center" | |||
prop="repo_id" | |||
stripe | |||
> | |||
</el-table-column> | |||
<el-table-column | |||
label="项目名称" | |||
width="125px" | |||
align="center" | |||
prop="name" | |||
style="color:#0366D6 100%;" | |||
> | |||
<template slot-scope="scope"> | |||
<a @click=goToDetailPage(scope.row.repo_id,scope.row.name)>{{scope.row.name}} </a> | |||
</template> | |||
</el-table-column> | |||
<el-table-column | |||
prop="isPrivate" | |||
label="私有" | |||
align="center"> | |||
<template slot-scope="scope"> | |||
{{scope.row.isPrivate|changeType}} | |||
</template> | |||
</el-table-column> | |||
<el-table-column | |||
prop="openi" | |||
label="OpenI指数" | |||
align="center"> | |||
<template slot-scope="scope"> | |||
{{scope.row.openi | rounding}} | |||
</template> | |||
</el-table-column> | |||
<el-table-column | |||
prop="view" | |||
label="浏览量" | |||
align="center"> | |||
</el-table-column> | |||
<el-table-column | |||
prop="download" | |||
label="代码下载量" | |||
align="center"> | |||
</el-table-column> | |||
<el-table-column | |||
prop="pr" | |||
label="PR" | |||
align="center"> | |||
</el-table-column> | |||
<el-table-column | |||
prop="commit" | |||
label="Commit数" | |||
align="center"> | |||
</el-table-column> | |||
<el-table-column | |||
prop="watch" | |||
label="关注数" | |||
align="center"> | |||
</el-table-column> | |||
<el-table-column | |||
prop="star" | |||
label="点赞数" | |||
align="center"> | |||
</el-table-column> | |||
<el-table-column | |||
prop="fork" | |||
label="派生数" | |||
align="center"> | |||
</el-table-column> | |||
<el-table-column | |||
prop="issue" | |||
label="任务数" | |||
align="center"> | |||
</el-table-column> | |||
<el-table-column | |||
prop="issueClosed" | |||
label="已解决任务" | |||
align="center"> | |||
</el-table-column> | |||
<el-table-column | |||
prop="contributor" | |||
label="贡献者数" | |||
align="center"> | |||
</el-table-column> | |||
</el-table> | |||
</div> | |||
<div style="margin-top:50px;text-align:center"> | |||
<el-pagination | |||
background | |||
@current-change="handleCurrentChange" | |||
:current-page="page" | |||
:page-size="pageSize" | |||
layout="prev, pager, next" | |||
:total="totalNum"> | |||
</el-pagination> | |||
</div> | |||
</div> | |||
<div id ="pro_detail" style="display:none;width: 100%;"> | |||
<div style="margin-top: 10px;"> | |||
<b class="pro_item">OpenI / {{this.pro_name}}</b> <span class="update_time">数据更新时间:{{tableDataIDTotal.lastUpdatedTime}}/{{tableDataIDTotal.recordBeginTime}}</span> | |||
</div> | |||
<div style="margin-top: 10px;"> | |||
项目描述:{{tableDataIDTotal.description | discriptionFun}} | |||
</div> | |||
<div style="margin-top:20px"> | |||
<el-row > | |||
<el-col :span='4' class="items"> | |||
项目创建时间 </br> | |||
{{tableDataIDTotal.creatTime}} | |||
</el-col> | |||
<el-col :span='4' class="items"> | |||
OPenI指数 </br> | |||
{{tableDataIDTotal.openi | rounding}} | |||
</el-col> | |||
<el-col :span='4' class="items"> | |||
评论数 </br> | |||
{{tableDataIDTotal.comment}} | |||
</el-col> | |||
<el-col :span='4' class="items"> | |||
浏览数 </br> | |||
{{tableDataIDTotal.view}} | |||
</el-col> | |||
<el-col :span='4' class="items"> | |||
代码下载量 </br> | |||
{{tableDataIDTotal.download}} | |||
</el-col> | |||
<el-col :span='4' style="text-align: center;"> | |||
任务完成比例 </br> | |||
{{tableDataIDTotal.issueClosedRatio}} | |||
</el-col> | |||
</el-row> | |||
</div> | |||
<div style="margin-top:30px;"> | |||
<el-row :gutter="20"> | |||
<el-col :span=18 > | |||
<div class="item_l" id="charts"> | |||
<div style="font-size:14px;color:#409eff;margin:20px 5px;">OpenI指数:{{tableDataIDTotal.openi | rounding}}</div> | |||
<div > | |||
<el-col :span='8' id="radar_openi" :style="{width: '400px', height: '300px'}"></el-col> | |||
<el-col :span='16' id="line_openi" :style="{width: '600px', height: '300px',float:'right'}"></el-col> | |||
</div> | |||
</div> | |||
</el-col> | |||
<el-col :span=6 > | |||
<div class="item_r"> | |||
<div style="font-size:14px;color:rgb(0,0,0);margin:20px 5px;">贡献者TOP10</div> | |||
<div> | |||
<el-table | |||
:data="tableDataContTop10" | |||
style="width: 100%" | |||
stripe | |||
:header-cell-style="tableHeaderStyle" | |||
> | |||
<el-table-column | |||
label="用户名" | |||
align="center" | |||
prop="user"> | |||
</el-table-column> | |||
<el-table-column | |||
label="身份" | |||
align="center" | |||
prop="mode"> | |||
<template slot-scope="scope"> | |||
{{scope.row.mode | showMode}} | |||
</template> | |||
</el-table-column> | |||
<el-table-column | |||
prop="PR" | |||
label="pr" | |||
align="center"> | |||
</el-table-column> | |||
<el-table-column | |||
prop="commit" | |||
label="commit" | |||
align="center"> | |||
</el-table-column> | |||
</el-table> | |||
</div> | |||
</div> | |||
</el-col> | |||
</el-row> | |||
</div> | |||
<div style="margin-top: 20px;"> | |||
<span class="sta_iterm">统计周期:</span> | |||
<button type="button" class='btn' id ="yesterday_pro" v-bind:class="{colorChange:1==dynamic_pro}" @click="getOneProList(pro_id,'yesterday',true,1),getOneProList(pro_id,'yesterday',false,1)">昨天</button> | |||
<button type="button" class='btn' id = "current_week_pro" v-bind:class="{colorChange:2==dynamic_pro}" @click="getOneProList(pro_id,'current_week',true,2),getOneProList(pro_id,'current_week',false,2)">本周</button> | |||
<button type="button" class='btn' id = "current_month_pro" v-bind:class="{colorChange:3==dynamic_pro}" @click="getOneProList(pro_id,'current_month',true,3),getOneProList(pro_id,'current_month',false,3)">本月</button> | |||
<button type="button" class='btn' id = "last_month_pro" v-bind:class="{colorChange:4==dynamic_pro}" @click="getOneProList(pro_id,'last_month',true,4),getOneProList(pro_id,'last_month',false,4)">上月</button> | |||
<button type="button" class='btn' id = "monthly_pro" v-bind:class="{colorChange:5==dynamic_pro}" @click="getOneProList(pro_id,'monthly',true,5),getOneProList(pro_id,'monthly',false,5)">近30天</button> | |||
<button type="button" class='btn' id = "current_year_pro" v-bind:class="{colorChange:6==dynamic}" @click="getOneProList(pro_id,'current_year',true,6),getOneProList(pro_id,'current_year',false,6)">今年</button> | |||
<button type="button" class='btn' id = "all_pro" v-bind:class="{colorChange:7==dynamic_pro}" @click="getOneProList(pro_id,'all',true,7),getOneProList(pro_id,'all',false,7)">所有</button> | |||
<span style="margin-left: 20px;"> | |||
<el-date-picker | |||
v-model="create_time_pro" | |||
prefix-icon="el-icon-time" | |||
@change="getOneProList(pro_id,'',true,0),getOneProList(pro_id,'',false,0)" | |||
type="daterange" | |||
size='small' | |||
range-separator="至" | |||
start-placeholder="开始日期" | |||
end-placeholder="结束日期"> | |||
</el-date-picker> | |||
</span> | |||
<span style="float:right; margin-right: 20px;"> | |||
<div style="display:inline-block;margin-left: 20px; "> | |||
<i class="el-icon-download"></i> | |||
<span ><a>下载报告</a> </span> | |||
</div> | |||
</span> | |||
</div> | |||
<div class="item_echart" id ='linecharts'> | |||
<div style="margin-top:10px;margin-left: 5px;"> | |||
<label for="label" @change='clickCheckBox'> | |||
<input type="checkbox" class="checkboxchart" name="checkboxchart" checked="checked" value="浏览量"/>浏览量 | |||
<input type="checkbox" class="checkboxchart" name="checkboxchart" checked="checked" value="下载量"/>下载量 | |||
<input type="checkbox" class="checkboxchart" name="checkboxchart" checked="checked" value="commit"/>commit | |||
</label> | |||
</div> | |||
<div id ="selectData" style="width: 1280px;height: 300px;"> | |||
</div> | |||
</div> | |||
<div style="margin-top: 30px;"> | |||
<el-table | |||
:data="tableDataID.slice((currentPage-1)*pageSize,currentPage*pageSize)" | |||
style="width: 100%" | |||
:header-cell-style="tableHeaderStyle" | |||
:cell-style='cellStyle'> | |||
<el-table-column | |||
label="日期" | |||
align="center" | |||
prop="date" | |||
stripe | |||
> | |||
</el-table-column> | |||
<el-table-column | |||
label="浏览量" | |||
align="center" | |||
prop="view"> | |||
</el-table-column> | |||
<el-table-column | |||
prop="download" | |||
label="下载量" | |||
align="center"> | |||
</el-table-column> | |||
<el-table-column | |||
prop="commit" | |||
label="commit" | |||
align="center"> | |||
</el-table-column> | |||
</el-table> | |||
</div> | |||
<div style="margin-top:50px;text-align:center"> | |||
<el-pagination | |||
background | |||
@current-change="handleCurrentChangeID" | |||
:current-page="currentPage" | |||
:page-size="pageSize1" | |||
layout="prev, pager, next" | |||
:total="tableDataID.length"> | |||
</el-pagination> | |||
</div> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
// import barLabel from './basic/barLabel.vue'; | |||
export default{ | |||
name:'ProAnalysis', | |||
components: { | |||
// barLabel, | |||
}, | |||
data() { | |||
return { | |||
recordBeginTime:'', | |||
lastUpdatedTime:'', | |||
page:1, | |||
pageSize:10, | |||
params:{type:'all',page:1,pagesize:10,beginTime:'',endTime:'',q:'',sort:'openi'}, | |||
tableData: [], | |||
totalPage:0, | |||
totalNum:0, | |||
pickerOptions: { | |||
}, | |||
value_time: '', | |||
search:'', | |||
dynamic:7, | |||
//单个项目参数 | |||
currentPage:1, | |||
pageSize1:10, | |||
paramsID:{type:'all' ,beginTime:'',endTime:'',openi:'false'}, | |||
tableDataIDTotal: [], | |||
tableDataID: [], | |||
tableDataIDOpenI:[], | |||
tableDataContTop10:[], | |||
create_time_pro: '', | |||
dynamic_pro:7, | |||
pro_name:'', | |||
pro_id:'', | |||
radarOpenI:'', | |||
echartsOITd:'', | |||
echartsSelectData:'', | |||
option:'', | |||
}; | |||
}, | |||
methods: { | |||
handleCurrentChange(val){ | |||
console.log(val) | |||
this.params.page = val | |||
switch(this.params.type){ | |||
case "yesterday":{ | |||
this.value_time='' | |||
this.getAllProList(this.params.type,1) | |||
break | |||
} | |||
case "current_week":{ | |||
this.value_time='' | |||
this.getAllProList(this.params.type,2) | |||
break | |||
} | |||
case "current_month":{ | |||
this.value_time='' | |||
this.getAllProList(this.params.type,3) | |||
break | |||
} | |||
case "last_month":{ | |||
this.value_time='' | |||
this.getAllProList(this.params.type,4) | |||
break | |||
} | |||
case "monthly":{ | |||
this.value_time='' | |||
this.getAllProList(this.params.type,5) | |||
break | |||
} | |||
case "current_year":{ | |||
this.value_time='' | |||
this.getAllProList(this.params.type,6) | |||
break | |||
} | |||
case "all":{ | |||
this.value_time='' | |||
this.getAllProList(this.params.type,7) | |||
break | |||
} | |||
case "":{ | |||
// this.value_time='' | |||
this.getAllProList(this.params.type,0) | |||
break | |||
} | |||
} | |||
}, | |||
formatDate(myyear,mymonth,myweekday) { | |||
// var myyear = this.date.getFullYear(); | |||
// var mymonth = this.date.getMonth() + 1; | |||
// var myweekday = this.date.getDate(); | |||
if (mymonth < 10) { | |||
mymonth = "0" + mymonth; | |||
} | |||
if (myweekday < 10) { | |||
myweekday = "0" + myweekday; | |||
} | |||
return (myyear + "-" + mymonth + "-" + myweekday); | |||
}, | |||
getAllProList(type_val,index){ | |||
console.log("类型:"+type_val) | |||
this.dynamic = index | |||
if (typeof type_val=="undefined" || type_val=="null" || type_val==""){ | |||
this.params.type='' | |||
this.params.beginTime=this.formatDate(this.value_time[0].getFullYear(),this.value_time[0].getMonth() + 1,this.value_time[0].getDate()) | |||
this.params.endTime=this.formatDate(this.value_time[1].getFullYear(),this.value_time[1].getMonth() + 1,this.value_time[1].getDate()) | |||
}else{ | |||
this.params.type=type_val | |||
this.params.beginTime='' | |||
this.params.endTime='' | |||
this.value_time=[] | |||
} | |||
this.$axios.get('../api/v1/projectboard/project',{ | |||
params:this.params | |||
}).then((res)=>{ | |||
this.recordBeginTime=res.data.recordBeginTime | |||
this.lastUpdatedTime=res.data.lastUpdatedTime | |||
this.tableData = res.data.pageRecords | |||
this.totalPage=res.data.totalPage | |||
this.totalNum = this.totalPage*this.params.pagesize | |||
console.log("this.totalPage:"+this.totalPage) | |||
}) | |||
}, | |||
searchName(){ | |||
this.params.q = this.search | |||
this.params.page = 1 | |||
this.getAllProList("all",7) | |||
}, | |||
goToDetailPage(pro_id,pro_name){ | |||
document.getElementById("pro_main").style.display="none"; | |||
document.getElementById("pro_detail").style.display="block"; | |||
console.log(pro_id) | |||
console.log(pro_name) | |||
this.pro_name=pro_name; | |||
this.pro_id=pro_id; | |||
this.getOneProData(pro_id); | |||
this.getOneProList(pro_id,"monthly",true,5); | |||
this.getOneProList(pro_id,"monthly",false,5); | |||
}, | |||
tableHeaderStyle({row,column,rowIndex,columnIndex}){ | |||
if(rowIndex===0){ | |||
return 'background:#f5f5f6;color:#606266' | |||
} | |||
}, | |||
cellStyle({row,column,rowIndex,columnIndex}){ | |||
if(rowIndex%2 === 1){ | |||
return 'background:#f5f5f6;color:#606266' | |||
} | |||
}, | |||
handleCurrentChangeID(currentPage){ | |||
this.currentPage = currentPage; | |||
}, | |||
getOneProData(pro_id){ | |||
this.$axios.get('../api/v1/projectboard/project/'+pro_id,{ | |||
}).then((res)=>{ | |||
this.tableDataIDTotal = res.data | |||
this.tableDataContTop10=res.data.top10 | |||
// this.drawLine() | |||
this.drawRadarOpenI() | |||
}) | |||
}, | |||
getOneProList(pro_id,type_val,bool_val,index){ | |||
this.dynamic_pro=index | |||
console.log("日期类型:"+type_val) | |||
if (typeof type_val=="undefined" || type_val=="null" || type_val==""){ | |||
this.paramsID.type='' | |||
this.paramsID.beginTime= this.formatDate(this.create_time_pro[0].getFullYear(),this.create_time_pro[0].getMonth() + 1,this.create_time_pro[0].getDate()) | |||
this.paramsID.endTime=this.formatDate(this.create_time_pro[1].getFullYear(),this.create_time_pro[1].getMonth() + 1,this.create_time_pro[1].getDate()) | |||
}else{ | |||
this.create_time_pro=[] | |||
this.paramsID.type=type_val | |||
this.paramsID.beginTime='' | |||
this.paramsID.endTime='' | |||
} | |||
this.paramsID.openi=bool_val | |||
this.$axios.get('../api/v1/projectboard/project/'+pro_id+"/period",{ | |||
params:this.paramsID | |||
}).then((res)=>{ | |||
if (bool_val){ | |||
this.tableDataIDOpenI = res.data | |||
this.drawOpenItrend() | |||
}else{ | |||
this.tableDataID = res.data | |||
this.drawSelectData() | |||
} | |||
}) | |||
}, | |||
drawRadarOpenI(){ | |||
var ydata = [this.roundingF(this.tableDataIDTotal.impact),this.roundingF(this.tableDataIDTotal.completeness),this.roundingF(this.tableDataIDTotal.liveness),this.tableDataIDTotal.projectHealth,this.roundingF(this.tableDataIDTotal.teamHealth),this.roundingF(this.tableDataIDTotal.growth)] | |||
console.log("ydata:",ydata) | |||
var i = -1; | |||
var option = { | |||
titile:{ | |||
text:"" | |||
}, | |||
tooltip: { | |||
trigger: 'item', | |||
backgroundColor:'rgba(255,255,255,0.8)', | |||
color:'black', | |||
borderWidth:'1', | |||
borderColor:'gray', | |||
textStyle:{ | |||
color:'black' | |||
}, | |||
position: 'right' | |||
},//提示层 | |||
legend: { | |||
data: ['name1'] | |||
}, | |||
radar: { | |||
name: { | |||
textStyle: { | |||
color: 'rgb(0,0,0)', //字体颜色 | |||
borderRadius: 3, //圆角 | |||
padding: [3, 5] //padding | |||
} | |||
}, | |||
slpitNumber:5, | |||
center: ['50%', '50%'], | |||
indicator: [{ | |||
name: '社区影响力', | |||
max: 100 | |||
}, | |||
{ | |||
name: '项目成熟度', | |||
max: 100 | |||
}, | |||
{ | |||
name: '开发活跃度', | |||
max: 100 | |||
}, | |||
{ | |||
name: '项目健康度', | |||
max: 100 | |||
}, | |||
{ | |||
name: '团队健康度', | |||
max: 100 | |||
}, | |||
{ | |||
name: '项目发展趋势', | |||
max: 100 | |||
} | |||
], | |||
}, | |||
series: [{ | |||
type: 'radar', | |||
lineStyle:{ | |||
width:2, | |||
color: 'rgb(0,255,0)' | |||
}, | |||
data: [{ | |||
value: ydata, | |||
}] | |||
}] | |||
} | |||
this.radarOpenI.setOption(option) | |||
}, | |||
drawOpenItrend(){ | |||
var xdata_openI=[] | |||
var ydata_openI=[] | |||
for(var i =0;i<this.tableDataIDOpenI.length;i++){ | |||
xdata_openI.push(this.tableDataIDOpenI[this.tableDataIDOpenI.length-1-i].date); | |||
ydata_openI.push(this.roundingF(this.tableDataIDOpenI[i].openi)) | |||
} | |||
console.log("ydata_openI:"+ydata_openI) | |||
console.log(xdata_openI) | |||
var option = { | |||
title : { | |||
text: 'OpenI指数趋势', | |||
textStyle: { | |||
fontSize: 12, | |||
}, | |||
left:'center', | |||
top:'bottom', | |||
subtext: '', | |||
}, | |||
tooltip : { | |||
trigger: 'axis', | |||
backgroundColor:'rgba(255,255,255,0.8)', | |||
color:'black', | |||
borderWidth:'1', | |||
borderColor:'gray', | |||
textStyle:{ | |||
color:'black' | |||
}, | |||
}, | |||
legend: { | |||
orient: 'vertical', | |||
top:'top', | |||
}, | |||
// calculable : true, | |||
xAxis : [ | |||
{ | |||
type : 'category', | |||
boundaryGap: false, | |||
data : xdata_openI, | |||
} | |||
], | |||
yAxis : [ | |||
{ | |||
type : 'value', | |||
} | |||
], | |||
series : [ | |||
{ | |||
data: ydata_openI, | |||
type: 'line', | |||
areaStyle: { | |||
color:'red', | |||
opacity: 0.3, | |||
origin:"start" | |||
}, | |||
// lineStyle:{ | |||
// width:2, | |||
// color: '#409effd6' | |||
// }, | |||
} | |||
] | |||
}; | |||
this.echartsOITd.setOption(option) | |||
}, | |||
drawSelectData(){ | |||
var xdata=[] | |||
var ydata_view=[] | |||
var ydata_download=[] | |||
var ydata_commit=[] | |||
for(var i =0;i<this.tableDataIDOpenI.length;i++){ | |||
xdata.push(this.tableDataIDOpenI[this.tableDataID.length-1-i].date); | |||
ydata_view.push(this.roundingF(this.tableDataID[i].view)) | |||
ydata_download.push(this.roundingF(this.tableDataID[i].download)) | |||
ydata_commit.push(this.roundingF(this.tableDataID[i].commit)) | |||
} | |||
console.log("ydata_openI:"+ydata_download) | |||
console.log(xdata) | |||
this.option = { | |||
title : { | |||
text: '', | |||
textStyle: { | |||
fontSize: 12, | |||
}, | |||
left:'center', | |||
top:'bottom', | |||
subtext: '', | |||
}, | |||
tooltip : { | |||
trigger: 'axis', | |||
backgroundColor:'rgba(255,255,255,0.8)', | |||
color:'black', | |||
borderWidth:'1', | |||
borderColor:'gray', | |||
textStyle:{ | |||
color:'black' | |||
}, | |||
}, | |||
legend: { | |||
data:['浏览量','下载量','commit'], | |||
// orient: 'vertical', | |||
// top:'top', | |||
}, | |||
toolbox: { | |||
show : false, | |||
feature : { | |||
mark : {show: true}, | |||
dataView : {show: false, readOnly: false}, | |||
magicType : {show: true, type: ['line', 'bar']}, | |||
restore : {show: false}, | |||
saveAsImage : {show: true} | |||
} | |||
}, | |||
calculable : true, | |||
xAxis : [ | |||
{ | |||
type : 'category', | |||
data : xdata, | |||
} | |||
], | |||
yAxis : [ | |||
{ | |||
type : 'value', | |||
} | |||
], | |||
series : [ | |||
{ name:"浏览量", | |||
data: ydata_view, | |||
type: 'line', | |||
areaStyle: {}, | |||
}, | |||
{ | |||
name:"下载量", | |||
data: ydata_download, | |||
type: 'line', | |||
areaStyle: {}, | |||
}, | |||
{ | |||
name:"commit", | |||
data: ydata_commit, | |||
type: 'line', | |||
areaStyle: {}, | |||
}, | |||
] | |||
}; | |||
this.echartsSelectData.setOption(this.option) | |||
// // 使用刚指定的选择项数据显示图表。 | |||
// var selectArr = this.echartsSelectData.getOption().legend[0].data;//legend所有值 | |||
// var checkboxs=document.getElementsByName('checkboxchart'); | |||
// $(".checkboxchart").click(function(){ | |||
// var obj = {}; | |||
// for(var i=0; i<checkboxs.length; i++){ | |||
// if(checkboxs[i].checked){ | |||
// obj[selectArr[i]] = true; | |||
// }else{ | |||
// obj[selectArr[i]] = false; | |||
// } | |||
// } | |||
// option.legend.selected = obj; | |||
// this.echartsSelectData.setOption(option); | |||
// }); | |||
}, | |||
roundingF(value){ | |||
return Number(value).toFixed(2) | |||
}, | |||
clickCheckBox(){ | |||
// 使用刚指定的选择项数据显示图表。 | |||
var selectArr = this.echartsSelectData.getOption().legend[0].data;//legend所有值 | |||
var checkboxs=document.getElementsByName('checkboxchart'); | |||
// $(".checkboxchart").click(function(){ | |||
var obj = {}; | |||
for(var i=0; i<checkboxs.length; i++){ | |||
if(checkboxs[i].checked){ | |||
obj[selectArr[i]] = true; | |||
}else{ | |||
obj[selectArr[i]] = false; | |||
} | |||
} | |||
this.option.legend.selected = obj; | |||
this.echartsSelectData.setOption(this.option); | |||
// }); | |||
} | |||
}, | |||
filters:{ | |||
rounding (value) { | |||
return Number(value).toFixed(2) | |||
}, | |||
changeType(value){ | |||
if(value=='false'){ | |||
return "否" | |||
}else{ | |||
return "是" | |||
} | |||
}, | |||
discriptionFun(value){ | |||
if(value==''){ | |||
return "暂无描述" | |||
} | |||
}, | |||
showMode(value){ | |||
if(value==1){ | |||
return "可读权限" | |||
}else if(value==2){ | |||
return "可写权限" | |||
}else if(value==3){ | |||
return "管理员" | |||
}else if(value==4){ | |||
return "所有者" | |||
}else{ | |||
return "未定义" | |||
} | |||
} | |||
}, | |||
mounted() { | |||
// document.getElementById("all").style.outline="none" | |||
// document.getElementById("all").focus() | |||
this.getAllProList("all",7) | |||
console.log("id:"+this.pro_id); | |||
// document.getElementById('radar_openi').style.width = document.getElementById('charts').offsetWidth*0.4+'px' | |||
// document.getElementById('line_openi').style.width = document.getElementById('charts').offsetWidth*0.6+'px' | |||
// document.getElementById('selectData').style.width = document.getElementById('linecharts').offsetWidth+'px' | |||
this.radarOpenI = this.$echarts.init(document.getElementById('radar_openi')) | |||
this.echartsOITd = this.$echarts.init(document.getElementById('line_openi')) | |||
this.echartsSelectData = this.$echarts.init(document.getElementById('selectData')) | |||
// window.onresize=function(){ | |||
// this.radarOpenI.resize(); | |||
// this.echartsOITd.resize(); | |||
// this.echartsSelectData.resize(); | |||
// } | |||
// console.log("this.radarOpenI:"+this.radarOpenI) | |||
}, | |||
created() { | |||
} | |||
} | |||
</script> | |||
<style scoped> | |||
.pro_item{ | |||
font-size: 16px; | |||
color: rgba(16, 16, 16, 100); | |||
font-family: SourceHanSansSC-bold; | |||
} | |||
.sta_item{ | |||
font-size: 14px; | |||
color: rgb(0 0 0); | |||
font-family: SourceHanSansSC-bold; | |||
} | |||
.update_time{ | |||
line-height: 17px; | |||
font-size: 12px; | |||
color:rgba(187, 187, 187, 100); | |||
margin-left: 10px; | |||
} | |||
.btn{ | |||
line-height: 1.5; | |||
margin: -3px; | |||
border: 1px solid #409eff; | |||
background: #FFFF; | |||
color: #409eff; | |||
width: 60px; | |||
height: 30px; | |||
border-radius:4px ; | |||
} | |||
.btn:focus, | |||
.btn:active{ | |||
background-color:#409effd6 ; | |||
} | |||
/deep/ .el-date-picker { | |||
width: 200px; | |||
} | |||
.colorChange { | |||
background-color: #409effd6; | |||
} | |||
.items{ | |||
text-align: center; | |||
border-right:2px solid rgba(219, 219, 219, 100); | |||
} | |||
.item_l{ | |||
margin-right: 5px; | |||
border:1px solid rgba(219, 219, 219, 100); | |||
height: 370px; | |||
width: 100%; | |||
} | |||
.item_r{ | |||
margin-right:5px; | |||
border:1px solid rgba(219, 219, 219, 100); | |||
height: 370px; | |||
} | |||
.item_echart{ | |||
margin-top: 10px; | |||
margin-right: 5px; | |||
border:1px solid rgba(219, 219, 219, 100); | |||
height: 350px; | |||
width: 100%; | |||
} | |||
</style> |
@@ -0,0 +1,415 @@ | |||
<template> | |||
<div> | |||
<div style="margin-top: 10px;"> | |||
<b class="pro_item">用户分析</b> <span class="update_time">数据更新时间:{{lastUpdatedTime}}/{{recordBeginTime}}</span> | |||
</div> | |||
<div style="margin-top: 20px;"> | |||
<span class="sta_iterm">统计周期:</span> | |||
<button type="button" class='btn' id ="yesterday_usr" v-bind:class="{colorChange:1==dynamic}" @click="getUserList('yesterday_usr',1)">昨天</button> | |||
<button type="button" class='btn' id = "current_week_usr" v-bind:class="{colorChange:2==dynamic}" @click="getUserList('current_week_usr',2)">本周</button> | |||
<button type="button" class='btn' id = "current_month_usr" v-bind:class="{colorChange:3==dynamic}" @click="getUserList('current_month_usr',3)">本月</button> | |||
<button type="button" class='btn' id = "last_month_usr" v-bind:class="{colorChange:4==dynamic}" @click="getUserList('last_month_usr',4)">上月</button> | |||
<button type="button" class='btn' id = "monthly_usr" v-bind:class="{colorChange:5==dynamic}" @click="getUserList('monthly_usr',5)">近30天</button> | |||
<button type="button" class='btn' id = "current_year_usr" v-bind:class="{colorChange:6==dynamic}" @click="getUserList('current_year_usr',6)">今年</button> | |||
<button type="button" class='btn' id = "all_usr" v-bind:class="{colorChange:7==dynamic}" @click="getUserList('all_usr',7)">所有</button> | |||
<span style="margin-left: 20px;"> | |||
<el-date-picker | |||
v-model="value_time" | |||
prefix-icon="el-icon-time" | |||
@change="getUserList('',0)" | |||
type="daterange" | |||
size='small' | |||
unlink-panels | |||
range-separator="至" | |||
start-placeholder="开始日期" | |||
end-placeholder="结束日期"> | |||
</el-date-picker> | |||
</span> | |||
<span style="float:right; margin-right: 20px;"> | |||
<a style="display:inline-block;margin-left: 20px; " id = 'download'> | |||
<i class="el-icon-download"></i> | |||
<span ><a @click="exportData()">下载报告</a> </span> | |||
</a> | |||
<span style="display:inline-block;margin-left: 20px; "> | |||
<el-input size="small" placeholder="输入用户名搜索" v-model="search" class="input-with-select" @keyup.enter.native="searchName() "><i slot="suffix" class="el-input__icon el-icon-search"></i> | |||
</el-input> | |||
</span> | |||
</span> | |||
</div> | |||
<div style="margin-top: 30px;"> | |||
<el-table | |||
:data="tableData.slice((currentPage-1)*pageSize,currentPage*pageSize)" | |||
style="width: 100%" | |||
:header-cell-style="tableHeaderStyle" | |||
:cell-style='cellStyle'> | |||
<el-table-column | |||
label="ID" | |||
prop="ID" | |||
align="center" | |||
stripe | |||
> | |||
</el-table-column> | |||
<el-table-column | |||
label="用户名" | |||
align="center" | |||
prop="Name" | |||
width="100px"> | |||
</el-table-column> | |||
<el-table-column | |||
prop="CodeMergeCount" | |||
label="PR数" | |||
align="center"> | |||
</el-table-column> | |||
<el-table-column | |||
prop="CommitCount" | |||
label="commit数" | |||
align="center"> | |||
</el-table-column> | |||
<el-table-column | |||
prop="IssueCount" | |||
label="提出任务数" | |||
align="center"> | |||
</el-table-column> | |||
<el-table-column | |||
prop="CommentCount" | |||
label="评论数" | |||
align="center"> | |||
</el-table-column> | |||
<el-table-column | |||
prop="FocusRepoCount" | |||
label="关注项目数" | |||
align="center"> | |||
</el-table-column> | |||
<el-table-column | |||
prop="StarRepoCount" | |||
label="点赞项目数" | |||
align="center"> | |||
</el-table-column> | |||
<el-table-column | |||
prop="LoginCount" | |||
label="登录次数" | |||
align="center"> | |||
</el-table-column> | |||
<el-table-column | |||
prop="WatchedCount" | |||
label="关注者数" | |||
align="center"> | |||
</el-table-column> | |||
<el-table-column | |||
prop="CommitCodeSize" | |||
label="commit代码行数" | |||
width="115px" | |||
align="center"> | |||
</el-table-column> | |||
<el-table-column | |||
prop="SolveIssueCount" | |||
label="已解决任务数" | |||
align="center"> | |||
</el-table-column> | |||
<el-table-column | |||
prop="EncyclopediasCount" | |||
label="百科页面贡献次数" | |||
width="130px" | |||
align="center"> | |||
</el-table-column> | |||
<el-table-column | |||
prop="CreateRepoCount" | |||
label="创建项目" | |||
align="center"> | |||
</el-table-column> | |||
<el-table-column | |||
prop="RegistDate" | |||
label="用户注册时间" | |||
width="120px" | |||
align="center"> | |||
<template slot-scope="scope"> | |||
{{scope.row.RegistDate | transformTimestamp}} | |||
</template> | |||
</el-table-column> | |||
<el-table-column | |||
prop="CountDate" | |||
label="系统统计时间" | |||
width="120px" | |||
align="center"> | |||
<template slot-scope="scope"> | |||
{{scope.row.CountDate | transformTimestamp}} | |||
</template> | |||
</el-table-column> | |||
</el-table> | |||
</div> | |||
<div style="margin-top:50px;text-align:center"> | |||
<el-pagination | |||
background | |||
@current-change="handleCurrentChange" | |||
:current-page="currentPage" | |||
:page-size="pageSize" | |||
layout="prev, pager, next" | |||
:total="tableData.length"> | |||
</el-pagination> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import { export2Excel } from '../excel/util.js' | |||
export default{ | |||
name:'UserAnalysis', | |||
data() { | |||
return { | |||
type_val:'', | |||
recordBeginTime:'', | |||
lastUpdatedTime:'', | |||
currentPage:1, | |||
pageSize:10, | |||
params:{startDate:'',endDate:''}, | |||
tableData: [], | |||
pickerOptions: { | |||
}, | |||
value_time: '', | |||
search:'', | |||
data:'', | |||
columns: [{title: 'ID',key: 'ID'},{title: '用户名',key: 'Name'},{title: 'PR数',key: 'CommitCount'},{title: '提出任务数',key: 'IssueCount'},{title: '评论数',key: 'CommentCount'},{title: '关注项目数',key: 'FocusRepoCount'},{title: '点赞项目数',key: 'StarRepoCount'},{title: '登录次数',key: 'LoginCount'},{title:'关注者数',key:'WatchedCount'},{title:'commit代码行数',key:'CommitCodeSize'},{title:'已解决任务数',key:'SolveIssueCount'},{title:'百科页面贡献次数',key:'EncyclopediasCount'},{title:'创建项目',key:'CreateRepoCount'},{title:'加入时间',key:'RegistDate'}], | |||
blob:'', | |||
fileName:'', | |||
dynamic:7, | |||
params_pro:{type:'all',page:1,pagesize:10,beginTime:'',endTime:'',q:'',sort:'openi'}, | |||
}; | |||
}, | |||
methods: { | |||
exportData(){ | |||
export2Excel(this.columns,this.tableData,"测试下载excel") | |||
}, | |||
handleCurrentChange(currentPage){ | |||
this.currentPage = currentPage; | |||
}, | |||
formatDate(myyear,mymonth,myweekday) { | |||
// var myyear = this.date.getFullYear(); | |||
// var mymonth = this.date.getMonth() + 1; | |||
// var myweekday = this.date.getDate(); | |||
if (mymonth < 10) { | |||
mymonth = "0" + mymonth; | |||
} | |||
if (myweekday < 10) { | |||
myweekday = "0" + myweekday; | |||
} | |||
return (myyear + "-" + mymonth + "-" + myweekday); | |||
}, | |||
// 获得某月的天数 | |||
getMonthDays(nowYear,month){ | |||
let monthStartDate = new Date(nowYear, month, 1); | |||
let monthEndDate = new Date(nowYear, month + 1, 1); | |||
let days = (monthEndDate - monthStartDate)/(1000 * 60 * 60 * 24); | |||
return days; | |||
}, | |||
getUserList(type_val,index){ | |||
this.dynamic = index; | |||
console.log("dj:"+type_val) | |||
var now = new Date(); // 当前日期 | |||
var nowDayOfWeek = now.getDay(); // 今天本周的第几天 | |||
var nowDay = now.getDate(); // 当前日 | |||
var nowMonth = now.getMonth(); // 当前月 | |||
var nowYear = now.getFullYear(); // 当前年 | |||
var today = this.formatDate(nowYear,nowMonth+1,nowDay); | |||
let lastMonthDate = new Date(); // 上月日期 | |||
lastMonthDate.setDate(1); | |||
lastMonthDate.setMonth(lastMonthDate.getMonth()-1); | |||
let lastYear = lastMonthDate.getYear(); | |||
let lastMonth = lastMonthDate.getMonth(); | |||
if (typeof type_val=="undefined" || type_val=="null" || type_val==""){ | |||
this.params.startDate= this.formatDate(this.value_time[0].getFullYear(),this.value_time[0].getMonth() + 1,this.value_time[0].getDate()); | |||
this.params.endDate = this.formatDate(this.value_time[1].getFullYear(),this.value_time[1].getMonth() + 1,this.value_time[1].getDate()); | |||
}else{ | |||
switch(type_val){ | |||
case "yesterday_usr":{ | |||
var now = new Date(); | |||
var tmp = new Date(now.setTime(now.getTime()-24*60*60*1000)); | |||
var yesterday = this.formatDate(tmp.getFullYear(),tmp.getMonth()+1,tmp.getDate()); | |||
this.params.startDate = yesterday | |||
this.params.endDate = yesterday | |||
this.value_time=[] | |||
document.getElementById("yesterday_usr").style.backgroundColor="409effd6" | |||
document.getElementById("current_week_usr") | |||
break | |||
} | |||
case "current_week_usr":{ | |||
var now = new Date(); // 当前日期 | |||
var nowDayOfWeek = now.getDay(); // 今天本周的第几天 | |||
var day = nowDayOfWeek || 7; | |||
this.params.startDate = this.formatDate(now.getFullYear(), nowMonth+1, nowDay + 1 - day); | |||
this.params.endDate = today | |||
this.value_time=[] | |||
break | |||
} | |||
case "current_month_usr":{ | |||
this.params.startDate = this.formatDate(nowYear,nowMonth+1,1); | |||
this.params.endDate = today | |||
this.value_time=[] | |||
break | |||
} | |||
case "last_month_usr":{ | |||
this.params.startDate=this.formatDate(nowYear, lastMonth+1, 1); | |||
this.params.endDate=this.formatDate(nowYear, lastMonth+1, this.getMonthDays(nowYear,lastMonth)); | |||
this.value_time=[] | |||
break | |||
} | |||
case "monthly_usr":{ | |||
var temp=new Date(now - 1000 * 60 * 60 * 24 * 30) | |||
this.params.startDate = this.formatDate(temp.getFullYear(),temp.getMonth()+1,temp.getDate()); | |||
this.params.endDate = today | |||
this.value_time=[] | |||
break | |||
} | |||
case "current_year_usr":{ | |||
this.params.startDate = this.formatDate(now.getFullYear(), 1, 1); | |||
this.params.endDate = today | |||
this.value_time=[] | |||
break | |||
} | |||
case "all_usr":{ | |||
console.log("e:"+today) | |||
this.params.startDate = this.formatDate(2000, 1, 1); //this.recordBeginTime// | |||
this.params.endDate = today | |||
this.value_time=[] | |||
break | |||
} | |||
} | |||
}; | |||
this.$axios.get('../tool/query_user_static',{ | |||
params:this.params | |||
}).then((res)=>{ | |||
this.currentPage = 1 | |||
this.tableData = res.data | |||
}) | |||
this.$axios.get('../api/v1/projectboard/project',{ | |||
params:this.params_pro | |||
}).then((res)=>{ | |||
this.recordBeginTime=res.data.recordBeginTime | |||
this.lastUpdatedTime=res.data.lastUpdatedTime | |||
}) | |||
}, | |||
searchName(){ | |||
// this.params.q = this.search | |||
// this.params.page = 1 | |||
// this.getUserList("all_usr") | |||
var search = this.search; | |||
this.getUserList("all_usr",7) | |||
this.tableData = this.tableData.filter(data => !search || data.Name.toLowerCase().includes(search.toLowerCase())) | |||
}, | |||
goToDetailPage(pro_id,pro_name){ | |||
sessionStorage.setItem("pro_id",pro_id); | |||
sessionStorage.setItem("pro_name",pro_name); | |||
document.getElementById("pro_main").style.display="none"; | |||
document.getElementById("pro_detail").style.display="block"; | |||
}, | |||
tableHeaderStyle({row,column,rowIndex,columnIndex}){ | |||
if(rowIndex===0){ | |||
return 'background:#f5f5f6;color:#606266' | |||
} | |||
}, | |||
cellStyle({row,column,rowIndex,columnIndex}){ | |||
if(rowIndex%2 === 1){ | |||
return 'background:#f5f5f6;color:#606266' | |||
} | |||
}, | |||
}, | |||
filters:{ | |||
transformTimestamp(timestamp){ | |||
console.log("timestamp",timestamp) | |||
let a = new Date(timestamp*1000); | |||
const date = new Date(a); | |||
const Y = date.getFullYear() + '/'; | |||
const M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '/'; | |||
const D = (date.getDate() < 10 ? '0'+date.getDate() : date.getDate()) + ' '; | |||
const h = (date.getHours() < 10 ? '0'+date.getHours() : date.getHours()) + ':'; | |||
const m = (date.getMinutes() <10 ? '0'+date.getMinutes() : date.getMinutes());// + ':' ; | |||
// const s = (date.getSeconds() <10 ? '0'+date.getSeconds() : date.getSeconds()) ; // 秒 | |||
const dateString = Y + M + D + h + m ;//+ s; | |||
console.log('dateString', dateString); // > dateString 2021-07-06 14:23 | |||
return dateString; | |||
}, | |||
// transformTimestamp(timestamp){ | |||
// var dateString= new Date(timestamp); | |||
// return dateString.toLocaleDateString().replace(/\//g, "-") + " " + dateString.toTimeString().substr(0, 8); | |||
// }, | |||
}, | |||
mounted() { | |||
document.getElementById("all_usr").style.outline="none" | |||
document.getElementById("all_usr").focus() | |||
this.getUserList("all_usr",7) | |||
}, | |||
created() { | |||
}, | |||
watch:{ | |||
search(val){ | |||
if(!val){ | |||
this.getUserList("all_usr",7) | |||
} | |||
} | |||
}, | |||
} | |||
</script> | |||
<style scoped> | |||
.pro_item{ | |||
font-size: 16px; | |||
color: rgba(16, 16, 16, 100); | |||
font-family: SourceHanSansSC-bold; | |||
} | |||
.sta_item{ | |||
font-size: 14px; | |||
color: rgb(0 0 0); | |||
font-family: SourceHanSansSC-bold; | |||
} | |||
.update_time{ | |||
line-height: 17px; | |||
font-size: 12px; | |||
color:rgba(187, 187, 187, 100); | |||
margin-left: 10px; | |||
} | |||
.btn{ | |||
line-height: 1.5; | |||
margin: -3px; | |||
border: 1px solid #409effd6; | |||
background: #FFFF; | |||
color: #409eff; | |||
width: 60px; | |||
height: 30px; | |||
border-radius:4px ; | |||
} | |||
.btn:focus, | |||
.btn:active{ | |||
background-color:#409effd6 ; | |||
} | |||
/deep/ .el-date-picker { | |||
width: 200px; | |||
} | |||
/deep/ .el-table { | |||
font-size: 12px; | |||
} | |||
.colorChange { | |||
background-color: #409effd6; | |||
} | |||
</style> |
@@ -14,8 +14,11 @@ | |||
<slot name="content"></slot> | |||
<div slot="footer" class="dialog-footer"> | |||
<el-button size="small" @click="deleteDialog = false">{{"取消"}}</el-button> | |||
<el-button size="small" style="background-color: #21ba45;color: #fff;" @click="deleteCallback.call(vmContext,deleteParam)">{{"确定"}}</el-button> | |||
<button class="ui button" @click="deleteDialog = false">{{"取消"}}</button> | |||
<button class="ui green button" @click="deleteCallback.call(vmContext,deleteParam)">{{"确定"}}</button> | |||
<!-- <el-button size="small" style="font-size: 1rem;padding: .78571429em 1.5em .78571429em;border-radius: .28571429rem;" @click="deleteDialog = false">{{"取消"}}</el-button> | |||
<el-button size="small" style="background-color: #21ba45;color: #fff;font-size: 1rem;padding: .78571429em 1.5em .78571429em;border-radius: .28571429rem;" @click="deleteCallback.call(vmContext,deleteParam)">{{"确定"}}</el-button> --> | |||
</div> | |||
</el-dialog> | |||
</template> | |||
@@ -0,0 +1,179 @@ | |||
/* eslint-disable */ | |||
/* Blob.js | |||
* A Blob implementation. | |||
* 2014-05-27 | |||
* | |||
* By Eli Grey, http://eligrey.com | |||
* By Devin Samarin, https://github.com/eboyjr | |||
* License: X11/MIT | |||
* See LICENSE.md | |||
*/ | |||
/*global self, unescape */ | |||
/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true, | |||
plusplus: true */ | |||
/*! @source http://purl.eligrey.com/github/Blob.js/blob/master/Blob.js */ | |||
(function (view) { | |||
"use strict"; | |||
view.URL = view.URL || view.webkitURL; | |||
if (view.Blob && view.URL) { | |||
try { | |||
new Blob; | |||
return; | |||
} catch (e) {} | |||
} | |||
// Internally we use a BlobBuilder implementation to base Blob off of | |||
// in order to support older browsers that only have BlobBuilder | |||
var BlobBuilder = view.BlobBuilder || view.WebKitBlobBuilder || view.MozBlobBuilder || (function(view) { | |||
var | |||
get_class = function(object) { | |||
return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1]; | |||
} | |||
, FakeBlobBuilder = function BlobBuilder() { | |||
this.data = []; | |||
} | |||
, FakeBlob = function Blob(data, type, encoding) { | |||
this.data = data; | |||
this.size = data.length; | |||
this.type = type; | |||
this.encoding = encoding; | |||
} | |||
, FBB_proto = FakeBlobBuilder.prototype | |||
, FB_proto = FakeBlob.prototype | |||
, FileReaderSync = view.FileReaderSync | |||
, FileException = function(type) { | |||
this.code = this[this.name = type]; | |||
} | |||
, file_ex_codes = ( | |||
"NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR " | |||
+ "NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR" | |||
).split(" ") | |||
, file_ex_code = file_ex_codes.length | |||
, real_URL = view.URL || view.webkitURL || view | |||
, real_create_object_URL = real_URL.createObjectURL | |||
, real_revoke_object_URL = real_URL.revokeObjectURL | |||
, URL = real_URL | |||
, btoa = view.btoa | |||
, atob = view.atob | |||
, ArrayBuffer = view.ArrayBuffer | |||
, Uint8Array = view.Uint8Array | |||
; | |||
FakeBlob.fake = FB_proto.fake = true; | |||
while (file_ex_code--) { | |||
FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1; | |||
} | |||
if (!real_URL.createObjectURL) { | |||
URL = view.URL = {}; | |||
} | |||
URL.createObjectURL = function(blob) { | |||
var | |||
type = blob.type | |||
, data_URI_header | |||
; | |||
if (type === null) { | |||
type = "application/octet-stream"; | |||
} | |||
if (blob instanceof FakeBlob) { | |||
data_URI_header = "data:" + type; | |||
if (blob.encoding === "base64") { | |||
return data_URI_header + ";base64," + blob.data; | |||
} else if (blob.encoding === "URI") { | |||
return data_URI_header + "," + decodeURIComponent(blob.data); | |||
} if (btoa) { | |||
return data_URI_header + ";base64," + btoa(blob.data); | |||
} else { | |||
return data_URI_header + "," + encodeURIComponent(blob.data); | |||
} | |||
} else if (real_create_object_URL) { | |||
return real_create_object_URL.call(real_URL, blob); | |||
} | |||
}; | |||
URL.revokeObjectURL = function(object_URL) { | |||
if (object_URL.substring(0, 5) !== "data:" && real_revoke_object_URL) { | |||
real_revoke_object_URL.call(real_URL, object_URL); | |||
} | |||
}; | |||
FBB_proto.append = function(data/*, endings*/) { | |||
var bb = this.data; | |||
// decode data to a binary string | |||
if (Uint8Array && (data instanceof ArrayBuffer || data instanceof Uint8Array)) { | |||
var | |||
str = "" | |||
, buf = new Uint8Array(data) | |||
, i = 0 | |||
, buf_len = buf.length | |||
; | |||
for (; i < buf_len; i++) { | |||
str += String.fromCharCode(buf[i]); | |||
} | |||
bb.push(str); | |||
} else if (get_class(data) === "Blob" || get_class(data) === "File") { | |||
if (FileReaderSync) { | |||
var fr = new FileReaderSync; | |||
bb.push(fr.readAsBinaryString(data)); | |||
} else { | |||
// async FileReader won't work as BlobBuilder is sync | |||
throw new FileException("NOT_READABLE_ERR"); | |||
} | |||
} else if (data instanceof FakeBlob) { | |||
if (data.encoding === "base64" && atob) { | |||
bb.push(atob(data.data)); | |||
} else if (data.encoding === "URI") { | |||
bb.push(decodeURIComponent(data.data)); | |||
} else if (data.encoding === "raw") { | |||
bb.push(data.data); | |||
} | |||
} else { | |||
if (typeof data !== "string") { | |||
data += ""; // convert unsupported types to strings | |||
} | |||
// decode UTF-16 to binary string | |||
bb.push(unescape(encodeURIComponent(data))); | |||
} | |||
}; | |||
FBB_proto.getBlob = function(type) { | |||
if (!arguments.length) { | |||
type = null; | |||
} | |||
return new FakeBlob(this.data.join(""), type, "raw"); | |||
}; | |||
FBB_proto.toString = function() { | |||
return "[object BlobBuilder]"; | |||
}; | |||
FB_proto.slice = function(start, end, type) { | |||
var args = arguments.length; | |||
if (args < 3) { | |||
type = null; | |||
} | |||
return new FakeBlob( | |||
this.data.slice(start, args > 1 ? end : this.data.length) | |||
, type | |||
, this.encoding | |||
); | |||
}; | |||
FB_proto.toString = function() { | |||
return "[object Blob]"; | |||
}; | |||
FB_proto.close = function() { | |||
this.size = this.data.length = 0; | |||
}; | |||
return FakeBlobBuilder; | |||
}(view)); | |||
view.Blob = function Blob(blobParts, options) { | |||
var type = options ? (options.type || "") : ""; | |||
var builder = new BlobBuilder(); | |||
if (blobParts) { | |||
for (var i = 0, len = blobParts.length; i < len; i++) { | |||
builder.append(blobParts[i]); | |||
} | |||
} | |||
return builder.getBlob(type); | |||
}; | |||
}(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content || this)); |
@@ -0,0 +1,141 @@ | |||
/* eslint-disable */ | |||
require('script-loader!file-saver'); | |||
require('./Blob'); | |||
require('script-loader!xlsx/dist/xlsx.core.min'); | |||
function generateArray(table) { | |||
var out = []; | |||
var rows = table.querySelectorAll('tr'); | |||
var ranges = []; | |||
for (var R = 0; R < rows.length; ++R) { | |||
var outRow = []; | |||
var row = rows[R]; | |||
var columns = row.querySelectorAll('td'); | |||
for (var C = 0; C < columns.length; ++C) { | |||
var cell = columns[C]; | |||
var colspan = cell.getAttribute('colspan'); | |||
var rowspan = cell.getAttribute('rowspan'); | |||
var cellValue = cell.innerText; | |||
if (cellValue !== "" && cellValue == +cellValue) cellValue = +cellValue; | |||
//Skip ranges | |||
ranges.forEach(function (range) { | |||
if (R >= range.s.r && R <= range.e.r && outRow.length >= range.s.c && outRow.length <= range.e.c) { | |||
for (var i = 0; i <= range.e.c - range.s.c; ++i) outRow.push(null); | |||
} | |||
}); | |||
//Handle Row Span | |||
if (rowspan || colspan) { | |||
rowspan = rowspan || 1; | |||
colspan = colspan || 1; | |||
ranges.push({s: {r: R, c: outRow.length}, e: {r: R + rowspan - 1, c: outRow.length + colspan - 1}}); | |||
} | |||
; | |||
//Handle Value | |||
outRow.push(cellValue !== "" ? cellValue : null); | |||
//Handle Colspan | |||
if (colspan) for (var k = 0; k < colspan - 1; ++k) outRow.push(null); | |||
} | |||
out.push(outRow); | |||
} | |||
return [out, ranges]; | |||
}; | |||
function datenum(v, date1904) { | |||
if (date1904) v += 1462; | |||
var epoch = Date.parse(v); | |||
return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000); | |||
} | |||
function sheet_from_array_of_arrays(data, opts) { | |||
var ws = {}; | |||
var range = {s: {c: 10000000, r: 10000000}, e: {c: 0, r: 0}}; | |||
for (var R = 0; R != data.length; ++R) { | |||
for (var C = 0; C != data[R].length; ++C) { | |||
if (range.s.r > R) range.s.r = R; | |||
if (range.s.c > C) range.s.c = C; | |||
if (range.e.r < R) range.e.r = R; | |||
if (range.e.c < C) range.e.c = C; | |||
var cell = {v: data[R][C]}; | |||
if (cell.v == null) continue; | |||
var cell_ref = XLSX.utils.encode_cell({c: C, r: R}); | |||
if (typeof cell.v === 'number') cell.t = 'n'; | |||
else if (typeof cell.v === 'boolean') cell.t = 'b'; | |||
else if (cell.v instanceof Date) { | |||
cell.t = 'n'; | |||
cell.z = XLSX.SSF._table[14]; | |||
cell.v = datenum(cell.v); | |||
} | |||
else cell.t = 's'; | |||
ws[cell_ref] = cell; | |||
} | |||
} | |||
if (range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range); | |||
return ws; | |||
} | |||
function Workbook() { | |||
if (!(this instanceof Workbook)) return new Workbook(); | |||
this.SheetNames = []; | |||
this.Sheets = {}; | |||
} | |||
function s2ab(s) { | |||
var buf = new ArrayBuffer(s.length); | |||
var view = new Uint8Array(buf); | |||
for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF; | |||
return buf; | |||
} | |||
export function export_table_to_excel(id) { | |||
var theTable = document.getElementById(id); | |||
console.log('a') | |||
var oo = generateArray(theTable); | |||
var ranges = oo[1]; | |||
/* original data */ | |||
var data = oo[0]; | |||
var ws_name = "SheetJS"; | |||
console.log(data); | |||
var wb = new Workbook(), ws = sheet_from_array_of_arrays(data); | |||
/* add ranges to worksheet */ | |||
// ws['!cols'] = ['apple', 'banan']; | |||
ws['!merges'] = ranges; | |||
/* add worksheet to workbook */ | |||
wb.SheetNames.push(ws_name); | |||
wb.Sheets[ws_name] = ws; | |||
var wbout = XLSX.write(wb, {bookType: 'xlsx', bookSST: false, type: 'binary'}); | |||
saveAs(new Blob([s2ab(wbout)], {type: "application/octet-stream"}), "test.xlsx") | |||
} | |||
function formatJson(jsonData) { | |||
console.log(jsonData) | |||
} | |||
export function export_json_to_excel(th, jsonData, defaultTitle) { | |||
/* original data */ | |||
var data = jsonData; | |||
data.unshift(th); | |||
var ws_name = "SheetJS"; | |||
var wb = new Workbook(), ws = sheet_from_array_of_arrays(data); | |||
/* add worksheet to workbook */ | |||
wb.SheetNames.push(ws_name); | |||
wb.Sheets[ws_name] = ws; | |||
var wbout = XLSX.write(wb, {bookType: 'xlsx', bookSST: false, type: 'binary'}); | |||
var title = defaultTitle || '列表' | |||
saveAs(new Blob([s2ab(wbout)], {type: "application/octet-stream"}), title + ".xlsx") | |||
} |
@@ -0,0 +1,17 @@ | |||
export function export2Excel(columns,list,filename){ | |||
require.ensure([], () => { | |||
const { export_json_to_excel } = require('./Export2Excel'); | |||
let tHeader = [] | |||
let filterVal = [] | |||
console.log(columns) | |||
if(!columns){ | |||
return; | |||
} | |||
columns.forEach(item =>{ | |||
tHeader.push(item.title) | |||
filterVal.push(item.key) | |||
}) | |||
const data = list.map(v => filterVal.map(j => v[j])) | |||
export_json_to_excel(tHeader, data, filename); | |||
}) | |||
} |
@@ -0,0 +1,74 @@ | |||
/** | |||
* LetterAvatar | |||
* | |||
* Artur Heinze | |||
* Create Letter avatar based on Initials | |||
* based on https://gist.github.com/leecrossley/6027780 | |||
*/ | |||
(function(w, d){ | |||
function LetterAvatar (name, size, color) { | |||
name = name || ''; | |||
size = size || 60; | |||
var colours = [ | |||
"#1abc9c", "#2ecc71", "#3498db", "#9b59b6", "#34495e", "#16a085", "#27ae60", "#2980b9", "#8e44ad", "#2c3e50", | |||
"#f1c40f", "#e67e22", "#e74c3c", "#00bcd4", "#95a5a6", "#f39c12", "#d35400", "#c0392b", "#bdc3c7", "#7f8c8d" | |||
], | |||
nameSplit = String(name).split(' '), | |||
initials, charIndex, colourIndex, canvas, context, dataURI; | |||
if (nameSplit.length == 1) { | |||
initials = nameSplit[0] ? nameSplit[0].charAt(0):'?'; | |||
} else { | |||
initials = nameSplit[0].charAt(0) + nameSplit[1].charAt(0); | |||
} | |||
if (w.devicePixelRatio) { | |||
size = (size * w.devicePixelRatio); | |||
} | |||
charIndex = (initials == '?' ? 72 : initials.charCodeAt(0)) - 64; | |||
colourIndex = charIndex % 20; | |||
canvas = d.createElement('canvas'); | |||
canvas.width = size; | |||
canvas.height = size; | |||
context = canvas.getContext("2d"); | |||
context.fillStyle = color ? color : colours[colourIndex - 1]; | |||
context.fillRect (0, 0, canvas.width, canvas.height); | |||
context.font = Math.round(canvas.width/2)+"px 'Microsoft Yahei'"; | |||
context.textAlign = "center"; | |||
context.fillStyle = "#FFF"; | |||
context.fillText(initials, size / 2, size / 1.5); | |||
dataURI = canvas.toDataURL(); | |||
canvas = null; | |||
return dataURI; | |||
} | |||
LetterAvatar.transform = function() { | |||
Array.prototype.forEach.call(d.querySelectorAll('img[avatar]'), function(img, name, color) { | |||
name = img.getAttribute('avatar'); | |||
color = img.getAttribute('color'); | |||
img.src = LetterAvatar(name, img.getAttribute('width'), color); | |||
img.removeAttribute('avatar'); | |||
img.setAttribute('alt', name); | |||
}); | |||
}; | |||
// AMD support | |||
if (typeof define === 'function' && define.amd) { | |||
define(function () { return LetterAvatar; }); | |||
// CommonJS and Node.js module support. | |||
} else if (typeof exports !== 'undefined') { | |||
// Support Node.js specific `module.exports` (which can be a function) | |||
if (typeof module != 'undefined' && module.exports) { | |||
exports = module.exports = LetterAvatar; | |||
} | |||
// But always support CommonJS module 1.1.1 spec (`exports` cannot be a function) | |||
exports.LetterAvatar = LetterAvatar; | |||
} else { | |||
window.LetterAvatar = LetterAvatar; | |||
d.addEventListener('DOMContentLoaded', function(event) { | |||
LetterAvatar.transform(); | |||
}); | |||
} | |||
})(window, document); |
@@ -4,7 +4,7 @@ | |||
import './publicpath.js'; | |||
import './polyfills.js'; | |||
import './features/letteravatar.js' | |||
import Vue from 'vue'; | |||
import ElementUI from 'element-ui'; | |||
import 'element-ui/lib/theme-chalk/index.css'; | |||
@@ -13,7 +13,7 @@ import qs from 'qs'; | |||
import 'jquery.are-you-sure'; | |||
import './vendor/semanticdropdown.js'; | |||
import {svg} from './utils.js'; | |||
import echarts from 'echarts' | |||
import initContextPopups from './features/contextpopup.js'; | |||
import initGitGraph from './features/gitgraph.js'; | |||
import initClipboard from './features/clipboard.js'; | |||
@@ -35,14 +35,20 @@ import {createCodeEditor} from './features/codeeditor.js'; | |||
import MinioUploader from './components/MinioUploader.vue'; | |||
import ObsUploader from './components/ObsUploader.vue'; | |||
import EditAboutInfo from './components/EditAboutInfo.vue'; | |||
import Images from './components/Images.vue' | |||
import EditTopics from './components/EditTopics.vue' | |||
import Images from './components/Images.vue'; | |||
import EditTopics from './components/EditTopics.vue'; | |||
import DataAnalysis from './components/DataAnalysis.vue' | |||
import Contributors from './components/Contributors.vue' | |||
Vue.use(ElementUI); | |||
Vue.prototype.$axios = axios; | |||
Vue.prototype.qs = qs; | |||
const {AppSubUrl, StaticUrlPrefix, csrf} = window.config; | |||
Object.defineProperty(Vue.prototype, '$echarts', { | |||
value: echarts | |||
}) | |||
function htmlEncode(text) { | |||
return jQuery('<div />') | |||
.text(text) | |||
@@ -2969,7 +2975,9 @@ $(document).ready(async () => { | |||
initObsUploader(); | |||
initVueEditAbout(); | |||
initVueEditTopic(); | |||
initVueContributors(); | |||
initVueImages(); | |||
initVueDataAnalysis(); | |||
initTeamSettings(); | |||
initCtrlEnterSubmit(); | |||
initNavbarContentToggle(); | |||
@@ -3682,6 +3690,21 @@ function initVueEditTopic() { | |||
render:h=>h(EditTopics) | |||
}) | |||
} | |||
function initVueContributors() { | |||
const el = document.getElementById('Contributors'); | |||
if (!el) { | |||
return; | |||
} | |||
new Vue({ | |||
el:'#Contributors', | |||
render:h=>h(Contributors) | |||
}) | |||
} | |||
function initVueImages() { | |||
const el = document.getElementById('images'); | |||
console.log("el",el) | |||
@@ -3696,7 +3719,20 @@ function initVueImages() { | |||
render: h => h(Images) | |||
}); | |||
} | |||
function initVueDataAnalysis() { | |||
const el = document.getElementById('data_analysis'); | |||
console.log("el",el) | |||
if (!el) { | |||
return; | |||
} | |||
new Vue({ | |||
el: '#data_analysis', | |||
render: h => h(DataAnalysis) | |||
}); | |||
} | |||
// 新增 | |||
function initObsUploader() { | |||
const el = document.getElementById('obsUploader'); | |||
@@ -243,6 +243,48 @@ footer .column{margin-bottom:0!important; padding-bottom:0!important;} | |||
display: inline-block; | |||
width: 100%; | |||
} | |||
.git-user-content{ | |||
margin-bottom: 16px; | |||
word-break: break-all; | |||
} | |||
.row.git-user-content .ui.avatar.s16.image { | |||
width: 50px !important; | |||
height: 50px !important; | |||
vertical-align: middle; | |||
border-radius: 500rem;} | |||
.user-list-item { | |||
padding: 0.3em 0px !important; | |||
margin: 15px 0 !important; | |||
width: 220px !important; | |||
} | |||
.row.git-user-content .content { | |||
margin-left: 6px; | |||
display: inline-block; | |||
vertical-align: top !important; | |||
overflow: hidden; | |||
} | |||
.row.git-user-content .content .header { | |||
font-weight: bold; | |||
} | |||
.item.user-list-item .header { | |||
line-height: 23px !important; | |||
font-size: 16px !important; | |||
text-overflow: ellipsis; | |||
overflow: hidden; | |||
width: 145px; | |||
white-space:nowrap; | |||
} | |||
.item.user-list-item .header a { | |||
color: #587284 !important; | |||
} | |||
.row.git-user-content .content .commit-btn { | |||
margin-top: 6px; | |||
float: left; | |||
font-size: 11px; | |||
color: #40485b !important; | |||
} | |||
.dropdown-menu { | |||
position: relative; | |||