| @@ -23,18 +23,22 @@ func GetRepoKPIStats(repo *Repository) (*git.RepoKPIStats, error) { | |||
| if repo.HasWiki() { | |||
| wikiPath = repo.WikiPath() | |||
| } | |||
| return getRepoKPIStats(repo.RepoPath(), wikiPath) | |||
| repoCreated := time.Unix(int64(repo.CreatedUnix), 0) | |||
| return getRepoKPIStats(repo.RepoPath(), repoCreated, wikiPath) | |||
| } | |||
| func getRepoKPIStats(repoPath string, wikiPath string) (*git.RepoKPIStats, error) { | |||
| func getRepoKPIStats(repoPath string, repoCreated time.Time, wikiPath string) (*git.RepoKPIStats, error) { | |||
| stats := &git.RepoKPIStats{} | |||
| contributors, err := git.GetContributors(repoPath) | |||
| contributors, err := git.GetContributorsDetail(repoPath, repoCreated) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| timeUntil := time.Now() | |||
| fourMonthAgo := timeUntil.AddDate(0, -4, 0) | |||
| if fourMonthAgo.Before(repoCreated) { | |||
| fourMonthAgo = repoCreated | |||
| } | |||
| recentlyContributors, err := git.GetContributorsDetail(repoPath, fourMonthAgo) | |||
| newContributersDict := make(map[string]struct{}) | |||
| if err != nil { | |||
| @@ -44,7 +48,7 @@ func getRepoKPIStats(repoPath string, wikiPath string) (*git.RepoKPIStats, error | |||
| if contributors != nil { | |||
| contributorDistinctDict := make(map[string]int, 0) | |||
| keyContributorsDict := make(map[string]struct{}, 0) | |||
| var commitsCount int64 | |||
| for _, contributor := range contributors { | |||
| if strings.Compare(contributor.Email, "") == 0 { | |||
| continue | |||
| @@ -70,6 +74,8 @@ func getRepoKPIStats(repoPath string, wikiPath string) (*git.RepoKPIStats, error | |||
| setKeyContributerDict(contributorDistinctDict, contributor.Email, keyContributorsDict) | |||
| } | |||
| commitsCount += int64(contributor.CommitCnt) | |||
| } | |||
| if recentlyContributors != nil { | |||
| @@ -110,10 +116,10 @@ func getRepoKPIStats(repoPath string, wikiPath string) (*git.RepoKPIStats, error | |||
| stats.Contributors = int64(len(contributorDistinctDict)) | |||
| stats.KeyContributors = int64(len(keyContributorsDict)) | |||
| stats.Commits = int64(commitsCount) | |||
| } | |||
| err = git.SetDevelopAge(repoPath, stats) | |||
| err = git.SetDevelopAge(repoPath, stats, repoCreated) | |||
| if err != nil { | |||
| return nil, fmt.Errorf("FillFromGit: %v", err) | |||
| } | |||
| @@ -14,6 +14,7 @@ type RepoStatistic struct { | |||
| Name string `xorm:"INDEX" json:"name"` | |||
| OwnerName string `json:"ownerName"` | |||
| IsPrivate bool `json:"isPrivate"` | |||
| IsMirror bool `json:"isMirror"` | |||
| Date string `xorm:"unique(s) NOT NULL" json:"date"` | |||
| NumWatches int64 `xorm:"NOT NULL DEFAULT 0" json:"watch"` | |||
| NumWatchesAdded int64 `xorm:"NOT NULL DEFAULT 0" json:"-"` | |||
| @@ -18,6 +18,7 @@ type RepoKPIStats struct { | |||
| KeyContributors int64 | |||
| DevelopAge int64 | |||
| ContributorsAdded int64 | |||
| Commits int64 | |||
| CommitsAdded int64 | |||
| CommitLinesModified int64 | |||
| WikiPages int64 | |||
| @@ -35,8 +36,9 @@ type UserKPITypeStats struct { | |||
| isNewContributor bool //是否是4个月内的新增贡献者 | |||
| } | |||
| func SetDevelopAge(repoPath string, stats *RepoKPIStats) error { | |||
| args := []string{"log", "--no-merges", "--branches=*", "--format=%cd", "--date=short"} | |||
| func SetDevelopAge(repoPath string, stats *RepoKPIStats, fromTime time.Time) error { | |||
| since := fromTime.Format(time.RFC3339) | |||
| args := []string{"log", "--no-merges", "--branches=*", "--format=%cd", "--date=short", fmt.Sprintf("--since='%s'", since)} | |||
| stdout, err := NewCommand(args...).RunInDirBytes(repoPath) | |||
| if err != nil { | |||
| return err | |||
| @@ -547,6 +547,7 @@ var ( | |||
| GrowthCommit float64 | |||
| GrowthComments float64 | |||
| RecordBeginTime string | |||
| IgnoreMirrorRepo bool | |||
| }{} | |||
| Warn_Notify_Mails []string | |||
| @@ -1333,6 +1334,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.IgnoreMirrorRepo = sec.Key("ignore_mirror_repo").MustBool(true) | |||
| } | |||
| @@ -229,7 +229,7 @@ organizations=组织 | |||
| images = 云脑镜像 | |||
| search=搜索 | |||
| code=代码 | |||
| data_analysis=数字看板 | |||
| data_analysis=数字看板(内测) | |||
| repo_no_results=未找到匹配的项目。 | |||
| dataset_no_results = 未找到匹配的数据集。 | |||
| user_no_results=未找到匹配的用户。 | |||
| @@ -49,9 +49,11 @@ func RepoStatisticDaily(date string) { | |||
| var minRepoRadar models.RepoStatistic | |||
| var maxRepoRadar models.RepoStatistic | |||
| for i, repo := range repos { | |||
| isInitMinMaxRadar := false | |||
| for _, repo := range repos { | |||
| log.Info("start statistic: %s", getDistinctProjectName(repo)) | |||
| var numDevMonths, numWikiViews, numContributor, numKeyContributor, numCommitsGrowth, numCommitLinesGrowth, numContributorsGrowth int64 | |||
| var numDevMonths, numWikiViews, numContributor, numKeyContributor, numCommitsGrowth, numCommitLinesGrowth, numContributorsGrowth, numCommits int64 | |||
| repoGitStat, err := models.GetRepoKPIStats(repo) | |||
| if err != nil { | |||
| log.Error("GetRepoKPIStats failed: %s", getDistinctProjectName(repo)) | |||
| @@ -63,6 +65,8 @@ func RepoStatisticDaily(date string) { | |||
| numCommitsGrowth = repoGitStat.CommitsAdded | |||
| numCommitLinesGrowth = repoGitStat.CommitLinesModified | |||
| numContributorsGrowth = repoGitStat.ContributorsAdded | |||
| numCommits = repoGitStat.Commits | |||
| } | |||
| var issueFixedRate float32 | |||
| @@ -98,15 +102,15 @@ func RepoStatisticDaily(date string) { | |||
| } | |||
| repoStat := models.RepoStatistic{ | |||
| RepoID: repo.ID, | |||
| Date: date, | |||
| Name: repo.Name, | |||
| IsPrivate: repo.IsPrivate, | |||
| OwnerName: repo.OwnerName, | |||
| NumWatches: int64(repo.NumWatches), | |||
| NumStars: int64(repo.NumStars), | |||
| NumForks: int64(repo.NumForks), | |||
| RepoID: repo.ID, | |||
| Date: date, | |||
| Name: repo.Name, | |||
| IsPrivate: repo.IsPrivate, | |||
| IsMirror: repo.IsMirror, | |||
| OwnerName: repo.OwnerName, | |||
| NumWatches: int64(repo.NumWatches), | |||
| NumStars: int64(repo.NumStars), | |||
| NumForks: int64(repo.NumForks), | |||
| NumDownloads: repo.CloneCnt, | |||
| NumComments: numComments, | |||
| NumVisits: int64(numVisits), | |||
| @@ -117,7 +121,7 @@ func RepoStatisticDaily(date string) { | |||
| DatasetSize: datasetSize, | |||
| NumModels: 0, | |||
| NumWikiViews: numWikiViews, | |||
| NumCommits: repo.NumCommit, | |||
| NumCommits: numCommits, | |||
| NumIssues: int64(repo.NumIssues), | |||
| NumPulls: int64(repo.NumPulls), | |||
| IssueFixedRate: issueFixedRate, | |||
| @@ -167,6 +171,7 @@ func RepoStatisticDaily(date string) { | |||
| tempRepoStat := models.RepoStatistic{ | |||
| RepoID: repoStat.RepoID, | |||
| Date: repoStat.Date, | |||
| IsMirror: repoStat.IsMirror, | |||
| Impact: normalization.GetImpactInitValue(repoStat.NumWatches, repoStat.NumStars, repoStat.NumForks, repoStat.NumDownloads, repoStat.NumComments, repoStat.NumVisits), | |||
| Completeness: normalization.GetCompleteInitValue(repoStat.NumClosedIssues, repoStat.NumVersions, repoStat.NumDevMonths, repoStat.DatasetSize, repoStat.NumModels, repoStat.NumWikiViews), | |||
| Liveness: normalization.GetLivenessInitValue(repoStat.NumCommits, repoStat.NumIssues, repoStat.NumPulls, repoStat.NumVisits), | |||
| @@ -177,57 +182,64 @@ func RepoStatisticDaily(date string) { | |||
| reposRadar = append(reposRadar, &tempRepoStat) | |||
| if i == 0 { | |||
| minRepoRadar = tempRepoStat | |||
| maxRepoRadar = tempRepoStat | |||
| } else { | |||
| if !isInitMinMaxRadar { | |||
| if tempRepoStat.Impact < minRepoRadar.Impact { | |||
| minRepoRadar.Impact = tempRepoStat.Impact | |||
| if !setting.RadarMap.IgnoreMirrorRepo || (setting.RadarMap.IgnoreMirrorRepo && !tempRepoStat.IsMirror) { | |||
| minRepoRadar = tempRepoStat | |||
| maxRepoRadar = tempRepoStat | |||
| isInitMinMaxRadar = true | |||
| } | |||
| if tempRepoStat.Impact > maxRepoRadar.Impact { | |||
| maxRepoRadar.Impact = tempRepoStat.Impact | |||
| } | |||
| } else { | |||
| if !setting.RadarMap.IgnoreMirrorRepo || (setting.RadarMap.IgnoreMirrorRepo && !tempRepoStat.IsMirror) { | |||
| if tempRepoStat.Impact < minRepoRadar.Impact { | |||
| minRepoRadar.Impact = tempRepoStat.Impact | |||
| } | |||
| if tempRepoStat.Completeness < minRepoRadar.Completeness { | |||
| minRepoRadar.Completeness = tempRepoStat.Completeness | |||
| } | |||
| if tempRepoStat.Impact > maxRepoRadar.Impact { | |||
| maxRepoRadar.Impact = tempRepoStat.Impact | |||
| } | |||
| if tempRepoStat.Completeness > maxRepoRadar.Completeness { | |||
| maxRepoRadar.Completeness = tempRepoStat.Completeness | |||
| } | |||
| if tempRepoStat.Completeness < minRepoRadar.Completeness { | |||
| minRepoRadar.Completeness = tempRepoStat.Completeness | |||
| } | |||
| if tempRepoStat.Liveness < minRepoRadar.Completeness { | |||
| minRepoRadar.Liveness = tempRepoStat.Liveness | |||
| } | |||
| if tempRepoStat.Completeness > maxRepoRadar.Completeness { | |||
| maxRepoRadar.Completeness = tempRepoStat.Completeness | |||
| } | |||
| if tempRepoStat.Liveness > maxRepoRadar.Liveness { | |||
| maxRepoRadar.Liveness = tempRepoStat.Liveness | |||
| } | |||
| if tempRepoStat.Liveness < minRepoRadar.Completeness { | |||
| minRepoRadar.Liveness = tempRepoStat.Liveness | |||
| } | |||
| if tempRepoStat.ProjectHealth < minRepoRadar.ProjectHealth { | |||
| minRepoRadar.ProjectHealth = tempRepoStat.ProjectHealth | |||
| } | |||
| if tempRepoStat.Liveness > maxRepoRadar.Liveness { | |||
| maxRepoRadar.Liveness = tempRepoStat.Liveness | |||
| } | |||
| if tempRepoStat.ProjectHealth > maxRepoRadar.ProjectHealth { | |||
| maxRepoRadar.ProjectHealth = tempRepoStat.ProjectHealth | |||
| } | |||
| if tempRepoStat.ProjectHealth < minRepoRadar.ProjectHealth { | |||
| minRepoRadar.ProjectHealth = tempRepoStat.ProjectHealth | |||
| } | |||
| if tempRepoStat.TeamHealth < minRepoRadar.TeamHealth { | |||
| minRepoRadar.TeamHealth = tempRepoStat.TeamHealth | |||
| } | |||
| if tempRepoStat.ProjectHealth > maxRepoRadar.ProjectHealth { | |||
| maxRepoRadar.ProjectHealth = tempRepoStat.ProjectHealth | |||
| } | |||
| if tempRepoStat.TeamHealth > maxRepoRadar.TeamHealth { | |||
| maxRepoRadar.TeamHealth = tempRepoStat.TeamHealth | |||
| } | |||
| if tempRepoStat.TeamHealth < minRepoRadar.TeamHealth { | |||
| minRepoRadar.TeamHealth = tempRepoStat.TeamHealth | |||
| } | |||
| if tempRepoStat.Growth < minRepoRadar.Growth { | |||
| minRepoRadar.Growth = tempRepoStat.Growth | |||
| } | |||
| if tempRepoStat.TeamHealth > maxRepoRadar.TeamHealth { | |||
| maxRepoRadar.TeamHealth = tempRepoStat.TeamHealth | |||
| } | |||
| if tempRepoStat.Growth < minRepoRadar.Growth { | |||
| minRepoRadar.Growth = tempRepoStat.Growth | |||
| } | |||
| if tempRepoStat.Growth > maxRepoRadar.Growth { | |||
| maxRepoRadar.Growth = tempRepoStat.Growth | |||
| } | |||
| if tempRepoStat.Growth > maxRepoRadar.Growth { | |||
| maxRepoRadar.Growth = tempRepoStat.Growth | |||
| } | |||
| } | |||
| @@ -238,13 +250,23 @@ func RepoStatisticDaily(date string) { | |||
| //radar map | |||
| log.Info("begin statistic radar") | |||
| for _, radarInit := range reposRadar { | |||
| radarInit.Impact = normalization.Normalization(radarInit.Impact, minRepoRadar.Impact, maxRepoRadar.Impact) | |||
| radarInit.Completeness = normalization.Normalization(radarInit.Completeness, minRepoRadar.Completeness, maxRepoRadar.Completeness) | |||
| radarInit.Liveness = normalization.Normalization(radarInit.Liveness, minRepoRadar.Liveness, maxRepoRadar.Liveness) | |||
| radarInit.ProjectHealth = normalization.Normalization(radarInit.ProjectHealth, minRepoRadar.ProjectHealth, maxRepoRadar.ProjectHealth) | |||
| radarInit.TeamHealth = normalization.Normalization(radarInit.TeamHealth, minRepoRadar.TeamHealth, maxRepoRadar.TeamHealth) | |||
| radarInit.Growth = normalization.Normalization(radarInit.Growth, minRepoRadar.Growth, maxRepoRadar.Growth) | |||
| radarInit.RadarTotal = normalization.GetRadarValue(radarInit.Impact, radarInit.Completeness, radarInit.Liveness, radarInit.ProjectHealth, radarInit.TeamHealth, radarInit.Growth) | |||
| if radarInit.IsMirror && setting.RadarMap.IgnoreMirrorRepo { | |||
| radarInit.Impact = 0 | |||
| radarInit.Completeness = 0 | |||
| radarInit.Liveness = 0 | |||
| radarInit.ProjectHealth = 0 | |||
| radarInit.TeamHealth = 0 | |||
| radarInit.Growth = 0 | |||
| radarInit.RadarTotal = 0 | |||
| } else { | |||
| radarInit.Impact = normalization.Normalization(radarInit.Impact, minRepoRadar.Impact, maxRepoRadar.Impact) | |||
| radarInit.Completeness = normalization.Normalization(radarInit.Completeness, minRepoRadar.Completeness, maxRepoRadar.Completeness) | |||
| radarInit.Liveness = normalization.Normalization(radarInit.Liveness, minRepoRadar.Liveness, maxRepoRadar.Liveness) | |||
| radarInit.ProjectHealth = normalization.Normalization(radarInit.ProjectHealth, minRepoRadar.ProjectHealth, maxRepoRadar.ProjectHealth) | |||
| radarInit.TeamHealth = normalization.Normalization(radarInit.TeamHealth, minRepoRadar.TeamHealth, maxRepoRadar.TeamHealth) | |||
| radarInit.Growth = normalization.Normalization(radarInit.Growth, minRepoRadar.Growth, maxRepoRadar.Growth) | |||
| radarInit.RadarTotal = normalization.GetRadarValue(radarInit.Impact, radarInit.Completeness, radarInit.Liveness, radarInit.ProjectHealth, radarInit.TeamHealth, radarInit.Growth) | |||
| } | |||
| models.UpdateRepoStat(radarInit) | |||
| } | |||
| @@ -53,7 +53,7 @@ | |||
| :cell-style='cellStyle'> | |||
| <el-table-column | |||
| label="ID" | |||
| align="center" | |||
| align="left" | |||
| prop="repo_id" | |||
| stripe | |||
| > | |||
| @@ -61,9 +61,9 @@ | |||
| <el-table-column | |||
| label="项目名称" | |||
| width="125px" | |||
| align="center" | |||
| align="left" | |||
| prop="name" | |||
| style="color:#0366D6 100%;" | |||
| style="color:#0366D6;font-family: Roboto" | |||
| > | |||
| <template slot-scope="scope"> | |||
| <a @click=goToDetailPage(scope.row.repo_id,scope.row.name,scope.row.ownerName)>{{scope.row.name}} </a> | |||
| @@ -197,7 +197,7 @@ | |||
| <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 style="font-size:14px;color:#0366D6;margin:20px 5px;">OpenI指数:{{tableDataIDTotal.openi | rounding}}</div> | |||
| <div > | |||
| <el-col :span='8' id="radar_openi" :style="{ height: '300px'}"></el-col> | |||
| @@ -208,7 +208,7 @@ | |||
| <el-col :span=6 > | |||
| <div class="item_r"> | |||
| <div style="margin:0 5px;"> | |||
| <div style="font-size:14px;color:rgb(0,0,0);margin:10px 0px">贡献者TOP10</div> | |||
| <div style="font-size:14px;color:rgb(0,0,0);margin:10px 5px">贡献者TOP10</div> | |||
| <div> | |||
| <el-table | |||
| :data="tableDataContTop10" | |||
| @@ -237,6 +237,7 @@ | |||
| <el-table-column | |||
| prop="pr" | |||
| label="PR" | |||
| width="50px" | |||
| align="center"> | |||
| </el-table-column> | |||
| <el-table-column | |||
| @@ -825,14 +826,14 @@ | |||
| type: 'radar', | |||
| lineStyle:{ | |||
| width:2, | |||
| color: '#409effd6', | |||
| color: '#0366D6', | |||
| normal:{ | |||
| color:'#409effd6 ' | |||
| color:'#0366D6 ' | |||
| } | |||
| }, | |||
| itemStyle : { | |||
| normal : { | |||
| color:'#409effd6' | |||
| color:'#0366D6' | |||
| } | |||
| }, | |||
| data: [{ | |||
| @@ -914,12 +915,12 @@ | |||
| lineStyle:{ | |||
| width:1, | |||
| normal:{ | |||
| color:'#409effd6' | |||
| color:'#0366D6' | |||
| } | |||
| }, | |||
| itemStyle : { | |||
| normal : { | |||
| color:'#409effd6' | |||
| color:'#0366D6' | |||
| } | |||
| }, | |||
| } | |||
| @@ -1023,6 +1024,7 @@ | |||
| ] | |||
| }; | |||
| // this.echartsSelectData.resize() | |||
| this.echartsSelectData.setOption(this.option) | |||
| // setTimeout(function (){ | |||
| // window.onresize = function () { | |||
| @@ -1189,10 +1191,10 @@ | |||
| .btnFirst{ | |||
| line-height: 1.5; | |||
| margin: -3.5px; | |||
| border: 1px solid #409eff; | |||
| border: 1px solid rgba(22, 132, 252, 100); | |||
| border-right: none; | |||
| background: #FFFF; | |||
| color: #409eff; | |||
| color: #1684FC; | |||
| width: 60px; | |||
| height: 30px; | |||
| border-radius:4px 0px 0px 4px; | |||
| @@ -1200,20 +1202,20 @@ | |||
| .btn{ | |||
| line-height: 1.5; | |||
| margin: -3.5px; | |||
| border: 1px solid #409eff; | |||
| border: 1px solid rgba(22, 132, 252, 100); | |||
| border-right: none; | |||
| background: #FFFF; | |||
| color: #409eff; | |||
| color: #1684FC; | |||
| width: 60px; | |||
| height: 30px; | |||
| } | |||
| .btnLast{ | |||
| line-height: 1.5; | |||
| margin: -3.5px; | |||
| border: 1px solid #409eff; | |||
| border: 1px solid rgba(22, 132, 252, 100); | |||
| /* border-right: none; */ | |||
| background: #FFFF; | |||
| color: #409eff; | |||
| color: #1684FC; | |||
| width: 60px; | |||
| height: 30px; | |||
| border-radius:0px 4px 4px 0px; | |||
| @@ -1244,7 +1246,7 @@ | |||
| } | |||
| .colorChange { | |||
| background-color: #409effd6; | |||
| background-color: #1684FC; | |||
| color: #FFFF; | |||
| cursor: default; | |||
| } | |||
| @@ -1272,7 +1274,7 @@ | |||
| width: 100%; | |||
| } | |||
| .item_content{ | |||
| color: #409eff; | |||
| color:#0366D6; | |||
| margin-top: 10px; | |||
| font-weight:bold; | |||
| } | |||
| @@ -49,15 +49,18 @@ | |||
| <el-table-column | |||
| label="ID" | |||
| prop="ID" | |||
| align="center" | |||
| align="left" | |||
| stripe | |||
| > | |||
| </el-table-column> | |||
| <el-table-column | |||
| label="用户名" | |||
| align="center" | |||
| align="left" | |||
| prop="Name" | |||
| width="100px"> | |||
| <template slot-scope="scope"> | |||
| <a :href="AppSubUrl +'../../../'+ scope.row.Name">{{scope.row.Name}} </a> | |||
| </template> | |||
| </el-table-column> | |||
| <el-table-column | |||
| prop="CodeMergeCount" | |||
| @@ -475,10 +478,10 @@ | |||
| .btnFirst{ | |||
| line-height: 1.5; | |||
| margin: -3.5px; | |||
| border: 1px solid #409eff; | |||
| border: 1px solid rgba(22, 132, 252, 100); | |||
| border-right: none; | |||
| background: #FFFF; | |||
| color: #409eff; | |||
| color: #1684FC; | |||
| width: 60px; | |||
| height: 30px; | |||
| border-radius:4px 0px 0px 4px; | |||
| @@ -486,20 +489,20 @@ | |||
| .btn{ | |||
| line-height: 1.5; | |||
| margin: -3.5px; | |||
| border: 1px solid #409eff; | |||
| border: 1px solid rgba(22, 132, 252, 100); | |||
| border-right: none; | |||
| background: #FFFF; | |||
| color: #409eff; | |||
| color: #1684FC; | |||
| width: 60px; | |||
| height: 30px; | |||
| } | |||
| .btnLast{ | |||
| line-height: 1.5; | |||
| margin: -3.5px; | |||
| border: 1px solid #409eff; | |||
| border: 1px solid rgba(22, 132, 252, 100); | |||
| /* border-right: none; */ | |||
| background: #FFFF; | |||
| color: #409eff; | |||
| color: #1684FC; | |||
| width: 60px; | |||
| height: 30px; | |||
| border-radius:0px 4px 4px 0px; | |||
| @@ -528,7 +531,7 @@ | |||
| } | |||
| .colorChange { | |||
| background-color: #409effd6; | |||
| background-color: #1684FC; | |||
| color: #FFFF; | |||
| cursor: default; | |||
| } | |||