mirror of
https://github.com/muety/wakapi.git
synced 2023-08-10 21:12:56 +03:00
Merge branch 'master' into stable
This commit is contained in:
commit
a059c637a7
@ -80,6 +80,7 @@ INSERT INTO aliases (`type`, `user_id`, `key`, `value`) VALUES (0, 'your_usernam
|
|||||||
* Language ~ type **1**
|
* Language ~ type **1**
|
||||||
* Editor ~ type **2**
|
* Editor ~ type **2**
|
||||||
* OS ~ type **3**
|
* OS ~ type **3**
|
||||||
|
* Machine ~ type **4**
|
||||||
|
|
||||||
**NOTE:** In order for the aliases to take effect for non-live statistics, you would either have to wait 24 hours for the cache to be invalidated or restart Wakapi.
|
**NOTE:** In order for the aliases to take effect for non-live statistics, you would either have to wait 24 hours for the cache to be invalidated or restart Wakapi.
|
||||||
|
|
||||||
|
2
go.mod
2
go.mod
@ -14,7 +14,7 @@ require (
|
|||||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
github.com/rubenv/sql-migrate v0.0.0-20200402132117-435005d389bc
|
github.com/rubenv/sql-migrate v0.0.0-20200402132117-435005d389bc
|
||||||
github.com/satori/go.uuid v1.2.0
|
github.com/satori/go.uuid v1.2.0
|
||||||
github.com/t-tiger/gorm-bulk-insert v0.0.0-20191014134946-beb77b81825f
|
github.com/t-tiger/gorm-bulk-insert v1.3.0
|
||||||
golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c
|
golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c
|
||||||
gopkg.in/ini.v1 v1.50.0
|
gopkg.in/ini.v1 v1.50.0
|
||||||
)
|
)
|
||||||
|
2
go.sum
2
go.sum
@ -318,6 +318,8 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy
|
|||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/t-tiger/gorm-bulk-insert v0.0.0-20191014134946-beb77b81825f h1:Op5lFYUNE7tPxu6gJfwkgY8HMIWpLqiLApBJfGs71U8=
|
github.com/t-tiger/gorm-bulk-insert v0.0.0-20191014134946-beb77b81825f h1:Op5lFYUNE7tPxu6gJfwkgY8HMIWpLqiLApBJfGs71U8=
|
||||||
github.com/t-tiger/gorm-bulk-insert v0.0.0-20191014134946-beb77b81825f/go.mod h1:SK1RZT4TR1aMUNGtbk6YxTPgx2D/gfbxB571QGnAV+c=
|
github.com/t-tiger/gorm-bulk-insert v0.0.0-20191014134946-beb77b81825f/go.mod h1:SK1RZT4TR1aMUNGtbk6YxTPgx2D/gfbxB571QGnAV+c=
|
||||||
|
github.com/t-tiger/gorm-bulk-insert v1.3.0 h1:9k7BaVEhw/3fsvh6GTOBwJ2RXk3asc5xs5m6hwozq20=
|
||||||
|
github.com/t-tiger/gorm-bulk-insert v1.3.0/go.mod h1:ruDlk8xDl+8sX4bA7PQuYly9YEb3pbp1eP2LCyeRrFY=
|
||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
-- +migrate Up
|
-- +migrate Up
|
||||||
-- SQL in section 'Up' is executed when this migration is applied
|
-- SQL in section 'Up' is executed when this migration is applied
|
||||||
|
|
||||||
alter table users
|
alter table heartbeats
|
||||||
add `machine` varchar(255);
|
add column `machine` varchar(255);
|
||||||
|
|
||||||
-- +migrate Down
|
-- +migrate Down
|
||||||
-- SQL section 'Down' is executed when this migration is rolled back
|
-- SQL section 'Down' is executed when this migration is rolled back
|
||||||
|
|
||||||
alter table users
|
alter table heartbeats
|
||||||
drop column `machine`;
|
drop column `machine`;
|
@ -5,8 +5,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CustomTime time.Time
|
|
||||||
|
|
||||||
type Heartbeat struct {
|
type Heartbeat struct {
|
||||||
ID uint `gorm:"primary_key"`
|
ID uint `gorm:"primary_key"`
|
||||||
User *User `json:"-" gorm:"not null"`
|
User *User `json:"-" gorm:"not null"`
|
||||||
@ -21,7 +19,7 @@ type Heartbeat struct {
|
|||||||
Editor string `json:"editor"`
|
Editor string `json:"editor"`
|
||||||
OperatingSystem string `json:"operating_system"`
|
OperatingSystem string `json:"operating_system"`
|
||||||
Machine string `json:"machine"`
|
Machine string `json:"machine"`
|
||||||
Time CustomTime `json:"time" gorm:"type:timestamp; default:CURRENT_TIMESTAMP; index:idx_time,idx_time_user"`
|
Time CustomTime `json:"time" gorm:"type:timestamp(3); default:CURRENT_TIMESTAMP(3); index:idx_time,idx_time_user"`
|
||||||
languageRegex *regexp.Regexp
|
languageRegex *regexp.Regexp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/jinzhu/gorm"
|
"github.com/jinzhu/gorm"
|
||||||
|
"math"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -23,13 +24,15 @@ type KeyStringValue struct {
|
|||||||
Value string `gorm:"type:text"`
|
Value string `gorm:"type:text"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CustomTime time.Time
|
||||||
|
|
||||||
func (j *CustomTime) UnmarshalJSON(b []byte) error {
|
func (j *CustomTime) UnmarshalJSON(b []byte) error {
|
||||||
s := strings.Split(strings.Trim(string(b), "\""), ".")[0]
|
s := strings.Replace(strings.Trim(string(b), "\""), ".", "", 1) // TODO: not always three decimal points!
|
||||||
i, err := strconv.ParseInt(s, 10, 64)
|
i, err := strconv.ParseInt(s, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
t := time.Unix(i, 0)
|
t := time.Unix(0, i*int64(math.Pow10(19-len(s))))
|
||||||
*j = CustomTime(t)
|
*j = CustomTime(t)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -60,7 +63,7 @@ func (j CustomTime) Value() (driver.Value, error) {
|
|||||||
|
|
||||||
func (j CustomTime) String() string {
|
func (j CustomTime) String() string {
|
||||||
t := time.Time(j)
|
t := time.Time(j)
|
||||||
return t.Format("2006-01-02 15:04:05")
|
return t.Format("2006-01-02 15:04:05.000")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j CustomTime) Time() time.Time {
|
func (j CustomTime) Time() time.Time {
|
||||||
|
@ -5,22 +5,26 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
NSummaryTypes uint8 = 4
|
NSummaryTypes uint8 = 99
|
||||||
SummaryProject uint8 = 0
|
SummaryProject uint8 = 0
|
||||||
SummaryLanguage uint8 = 1
|
SummaryLanguage uint8 = 1
|
||||||
SummaryEditor uint8 = 2
|
SummaryEditor uint8 = 2
|
||||||
SummaryOS uint8 = 3
|
SummaryOS uint8 = 3
|
||||||
|
SummaryMachine uint8 = 4
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const UnknownSummaryKey = "unknown"
|
||||||
|
|
||||||
type Summary struct {
|
type Summary struct {
|
||||||
ID uint `json:"-" gorm:"primary_key"`
|
ID uint `json:"-" gorm:"primary_key"`
|
||||||
UserID string `json:"user_id" gorm:"not null; index:idx_time_summary_user"`
|
UserID string `json:"user_id" gorm:"not null; index:idx_time_summary_user"`
|
||||||
FromTime time.Time `json:"from" gorm:"not null; type:timestamp; default:CURRENT_TIMESTAMP; index:idx_time_summary_user"`
|
FromTime time.Time `json:"from" gorm:"not null; type:timestamp(3); default:CURRENT_TIMESTAMP(3); index:idx_time_summary_user"`
|
||||||
ToTime time.Time `json:"to" gorm:"not null; type:timestamp; default:CURRENT_TIMESTAMP; index:idx_time_summary_user"`
|
ToTime time.Time `json:"to" gorm:"not null; type:timestamp(3); default:CURRENT_TIMESTAMP(3); index:idx_time_summary_user"`
|
||||||
Projects []*SummaryItem `json:"projects"`
|
Projects []*SummaryItem `json:"projects"`
|
||||||
Languages []*SummaryItem `json:"languages"`
|
Languages []*SummaryItem `json:"languages"`
|
||||||
Editors []*SummaryItem `json:"editors"`
|
Editors []*SummaryItem `json:"editors"`
|
||||||
OperatingSystems []*SummaryItem `json:"operating_systems"`
|
OperatingSystems []*SummaryItem `json:"operating_systems"`
|
||||||
|
Machines []*SummaryItem `json:"machines"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SummaryItem struct {
|
type SummaryItem struct {
|
||||||
@ -43,3 +47,55 @@ type SummaryViewModel struct {
|
|||||||
Success string
|
Success string
|
||||||
ApiKey string
|
ApiKey string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Augments the summary in a way that at least one item is present for every type.
|
||||||
|
If a summary has zero items for a given type, but one or more for any of the other types,
|
||||||
|
the total summary duration can be derived from those and inserted as a dummy-item with key "unknown"
|
||||||
|
for the missing type.
|
||||||
|
For instance, the machine type was introduced post hoc. Accordingly, no "machine"-information is present in
|
||||||
|
the data for old heartbeats and summaries. If a user has two years of data without machine information and
|
||||||
|
one day with such, a "machine"-chart plotted from that data will reference a way smaller absolute total amount
|
||||||
|
of time than the other ones.
|
||||||
|
To avoid having to modify persisted data retrospectively, i.e. inserting a dummy SummaryItem for the new type,
|
||||||
|
such is generated dynamically here, considering the "machine" for all old heartbeats "unknown".
|
||||||
|
*/
|
||||||
|
func (s *Summary) FillUnknown() {
|
||||||
|
types := []uint8{SummaryProject, SummaryLanguage, SummaryEditor, SummaryOS, SummaryMachine}
|
||||||
|
missingTypes := make([]uint8, 0)
|
||||||
|
typeItems := map[uint8]*[]*SummaryItem{
|
||||||
|
SummaryProject: &s.Projects,
|
||||||
|
SummaryLanguage: &s.Languages,
|
||||||
|
SummaryEditor: &s.Editors,
|
||||||
|
SummaryOS: &s.OperatingSystems,
|
||||||
|
SummaryMachine: &s.Machines,
|
||||||
|
}
|
||||||
|
var somePresentType uint8
|
||||||
|
|
||||||
|
for _, t := range types {
|
||||||
|
if len(*typeItems[t]) == 0 {
|
||||||
|
missingTypes = append(missingTypes, t)
|
||||||
|
} else {
|
||||||
|
somePresentType = t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// can't proceed if entire summary is empty
|
||||||
|
if len(missingTypes) == len(types) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate total duration from any of the present sets of items
|
||||||
|
var timeSum time.Duration
|
||||||
|
for _, item := range *typeItems[somePresentType] {
|
||||||
|
timeSum += item.Total
|
||||||
|
}
|
||||||
|
|
||||||
|
// construct dummy item for all missing types
|
||||||
|
for _, t := range missingTypes {
|
||||||
|
*typeItems[t] = append(*typeItems[t], &SummaryItem{
|
||||||
|
Type: t,
|
||||||
|
Key: UnknownSummaryKey,
|
||||||
|
Total: timeSum,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -37,7 +37,7 @@ func (s *Signup) IsValid() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func validateUsername(username string) bool {
|
func validateUsername(username string) bool {
|
||||||
return len(username) >= 3
|
return len(username) >= 3 && username != "current"
|
||||||
}
|
}
|
||||||
|
|
||||||
func validatePassword(password string) bool {
|
func validatePassword(password string) bool {
|
||||||
|
@ -65,12 +65,13 @@ func (srv *SummaryService) Construct(from, to time.Time, user *models.User, reco
|
|||||||
heartbeats = append(heartbeats, hb...)
|
heartbeats = append(heartbeats, hb...)
|
||||||
}
|
}
|
||||||
|
|
||||||
types := []uint8{models.SummaryProject, models.SummaryLanguage, models.SummaryEditor, models.SummaryOS}
|
types := []uint8{models.SummaryProject, models.SummaryLanguage, models.SummaryEditor, models.SummaryOS, models.SummaryMachine}
|
||||||
|
|
||||||
var projectItems []*models.SummaryItem
|
var projectItems []*models.SummaryItem
|
||||||
var languageItems []*models.SummaryItem
|
var languageItems []*models.SummaryItem
|
||||||
var editorItems []*models.SummaryItem
|
var editorItems []*models.SummaryItem
|
||||||
var osItems []*models.SummaryItem
|
var osItems []*models.SummaryItem
|
||||||
|
var machineItems []*models.SummaryItem
|
||||||
|
|
||||||
if err := srv.AliasService.LoadUserAliases(user.ID); err != nil {
|
if err := srv.AliasService.LoadUserAliases(user.ID); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -92,6 +93,8 @@ func (srv *SummaryService) Construct(from, to time.Time, user *models.User, reco
|
|||||||
editorItems = item.Items
|
editorItems = item.Items
|
||||||
case models.SummaryOS:
|
case models.SummaryOS:
|
||||||
osItems = item.Items
|
osItems = item.Items
|
||||||
|
case models.SummaryMachine:
|
||||||
|
machineItems = item.Items
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
close(c)
|
close(c)
|
||||||
@ -100,6 +103,10 @@ func (srv *SummaryService) Construct(from, to time.Time, user *models.User, reco
|
|||||||
if len(existingSummaries) > 0 {
|
if len(existingSummaries) > 0 {
|
||||||
realFrom = existingSummaries[0].FromTime
|
realFrom = existingSummaries[0].FromTime
|
||||||
realTo = existingSummaries[len(existingSummaries)-1].ToTime
|
realTo = existingSummaries[len(existingSummaries)-1].ToTime
|
||||||
|
|
||||||
|
for _, summary := range existingSummaries {
|
||||||
|
summary.FillUnknown()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if len(heartbeats) > 0 {
|
if len(heartbeats) > 0 {
|
||||||
t1, t2 := time.Time(heartbeats[0].Time), time.Time(heartbeats[len(heartbeats)-1].Time)
|
t1, t2 := time.Time(heartbeats[0].Time), time.Time(heartbeats[len(heartbeats)-1].Time)
|
||||||
@ -119,6 +126,7 @@ func (srv *SummaryService) Construct(from, to time.Time, user *models.User, reco
|
|||||||
Languages: languageItems,
|
Languages: languageItems,
|
||||||
Editors: editorItems,
|
Editors: editorItems,
|
||||||
OperatingSystems: osItems,
|
OperatingSystems: osItems,
|
||||||
|
Machines: machineItems,
|
||||||
}
|
}
|
||||||
|
|
||||||
allSummaries := []*models.Summary{aggregatedSummary}
|
allSummaries := []*models.Summary{aggregatedSummary}
|
||||||
@ -154,6 +162,7 @@ func (srv *SummaryService) GetByUserWithin(user *models.User, from, to time.Time
|
|||||||
Preload("Languages", "type = ?", models.SummaryLanguage).
|
Preload("Languages", "type = ?", models.SummaryLanguage).
|
||||||
Preload("Editors", "type = ?", models.SummaryEditor).
|
Preload("Editors", "type = ?", models.SummaryEditor).
|
||||||
Preload("OperatingSystems", "type = ?", models.SummaryOS).
|
Preload("OperatingSystems", "type = ?", models.SummaryOS).
|
||||||
|
Preload("Machines", "type = ?", models.SummaryMachine).
|
||||||
Find(&summaries).Error; err != nil {
|
Find(&summaries).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -187,10 +196,12 @@ func (srv *SummaryService) aggregateBy(heartbeats []*models.Heartbeat, summaryTy
|
|||||||
key = h.Language
|
key = h.Language
|
||||||
case models.SummaryOS:
|
case models.SummaryOS:
|
||||||
key = h.OperatingSystem
|
key = h.OperatingSystem
|
||||||
|
case models.SummaryMachine:
|
||||||
|
key = h.Machine
|
||||||
}
|
}
|
||||||
|
|
||||||
if key == "" {
|
if key == "" {
|
||||||
key = "unknown"
|
key = models.UnknownSummaryKey
|
||||||
}
|
}
|
||||||
|
|
||||||
if aliasedKey, err := srv.AliasService.GetAliasOrDefault(user.ID, summaryType, key); err == nil {
|
if aliasedKey, err := srv.AliasService.GetAliasOrDefault(user.ID, summaryType, key); err == nil {
|
||||||
@ -276,6 +287,7 @@ func mergeSummaries(summaries []*models.Summary) (*models.Summary, error) {
|
|||||||
Languages: make([]*models.SummaryItem, 0),
|
Languages: make([]*models.SummaryItem, 0),
|
||||||
Editors: make([]*models.SummaryItem, 0),
|
Editors: make([]*models.SummaryItem, 0),
|
||||||
OperatingSystems: make([]*models.SummaryItem, 0),
|
OperatingSystems: make([]*models.SummaryItem, 0),
|
||||||
|
Machines: make([]*models.SummaryItem, 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, s := range summaries {
|
for _, s := range summaries {
|
||||||
@ -295,6 +307,7 @@ func mergeSummaries(summaries []*models.Summary) (*models.Summary, error) {
|
|||||||
finalSummary.Languages = mergeSummaryItems(finalSummary.Languages, s.Languages)
|
finalSummary.Languages = mergeSummaryItems(finalSummary.Languages, s.Languages)
|
||||||
finalSummary.Editors = mergeSummaryItems(finalSummary.Editors, s.Editors)
|
finalSummary.Editors = mergeSummaryItems(finalSummary.Editors, s.Editors)
|
||||||
finalSummary.OperatingSystems = mergeSummaryItems(finalSummary.OperatingSystems, s.OperatingSystems)
|
finalSummary.OperatingSystems = mergeSummaryItems(finalSummary.OperatingSystems, s.OperatingSystems)
|
||||||
|
finalSummary.Machines = mergeSummaryItems(finalSummary.Machines, s.Machines)
|
||||||
}
|
}
|
||||||
|
|
||||||
finalSummary.FromTime = minTime
|
finalSummary.FromTime = minTime
|
||||||
|
@ -5,6 +5,7 @@ const projectsCanvas = document.getElementById('chart-projects')
|
|||||||
const osCanvas = document.getElementById('chart-os')
|
const osCanvas = document.getElementById('chart-os')
|
||||||
const editorsCanvas = document.getElementById('chart-editor')
|
const editorsCanvas = document.getElementById('chart-editor')
|
||||||
const languagesCanvas = document.getElementById('chart-language')
|
const languagesCanvas = document.getElementById('chart-language')
|
||||||
|
const machinesCanvas = document.getElementById('chart-machine')
|
||||||
|
|
||||||
let charts = []
|
let charts = []
|
||||||
let resizeCount = 0
|
let resizeCount = 0
|
||||||
@ -56,7 +57,7 @@ function draw() {
|
|||||||
.map(p => {
|
.map(p => {
|
||||||
return {
|
return {
|
||||||
label: p.key,
|
label: p.key,
|
||||||
data: [parseInt(p.total)],
|
data: [parseInt(p.total) / 60],
|
||||||
backgroundColor: getRandomColor(p.key)
|
backgroundColor: getRandomColor(p.key)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -67,6 +68,14 @@ function draw() {
|
|||||||
legend: {
|
legend: {
|
||||||
display: false
|
display: false
|
||||||
},
|
},
|
||||||
|
scales: {
|
||||||
|
xAxes: [{
|
||||||
|
scaleLabel: {
|
||||||
|
display: true,
|
||||||
|
labelString: 'Minutes'
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
},
|
||||||
maintainAspectRatio: false,
|
maintainAspectRatio: false,
|
||||||
onResize: onChartResize
|
onResize: onChartResize
|
||||||
}
|
}
|
||||||
@ -135,10 +144,31 @@ function draw() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let machineChart = new Chart(machinesCanvas.getContext('2d'), {
|
||||||
|
type: 'pie',
|
||||||
|
data: {
|
||||||
|
datasets: [{
|
||||||
|
data: wakapiData.machines
|
||||||
|
.slice(0, Math.min(SHOW_TOP_N, wakapiData.machines.length))
|
||||||
|
.map(p => parseInt(p.total)),
|
||||||
|
backgroundColor: wakapiData.machines.map(p => getRandomColor(p.key))
|
||||||
|
}],
|
||||||
|
labels: wakapiData.machines
|
||||||
|
.slice(0, Math.min(SHOW_TOP_N, wakapiData.machines.length))
|
||||||
|
.map(p => p.key)
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
title: Object.assign(titleOptions, {text: `Machines (top ${SHOW_TOP_N})`}),
|
||||||
|
tooltips: getTooltipOptions('machines', 'pie'),
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
onResize: onChartResize
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
getTotal(wakapiData.operatingSystems)
|
getTotal(wakapiData.operatingSystems)
|
||||||
document.getElementById('grid-container').style.visibility = 'visible'
|
document.getElementById('grid-container').style.visibility = 'visible'
|
||||||
|
|
||||||
charts = [projectChart, osChart, editorChart, languageChart]
|
charts = [projectChart, osChart, editorChart, languageChart, machineChart]
|
||||||
|
|
||||||
charts.forEach(c => c.options.onResize(c.chart))
|
charts.forEach(c => c.options.onResize(c.chart))
|
||||||
equalizeHeights()
|
equalizeHeights()
|
||||||
|
@ -1 +1 @@
|
|||||||
1.7.6
|
1.8.3
|
@ -2,11 +2,13 @@
|
|||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.3/Chart.bundle.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.3/Chart.bundle.min.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
const languageColors = {{ .LanguageColors | json }}
|
||||||
|
|
||||||
let wakapiData = {}
|
let wakapiData = {}
|
||||||
let languageColors = {{ .LanguageColors | json }}
|
wakapiData.projects = {{ .Projects | json }}
|
||||||
wakapiData.projects = {{ .Projects | json }}
|
wakapiData.operatingSystems = {{ .OperatingSystems | json }}
|
||||||
wakapiData.operatingSystems = {{ .OperatingSystems | json }}
|
wakapiData.editors = {{ .Editors | json }}
|
||||||
wakapiData.editors = {{ .Editors | json }}
|
wakapiData.languages = {{ .Languages | json }}
|
||||||
wakapiData.languages = {{ .Languages | json }}
|
wakapiData.machines = {{ .Machines | json }}
|
||||||
</script>
|
</script>
|
||||||
<script src="assets/app.js"></script>
|
<script src="assets/app.js"></script>
|
@ -78,6 +78,11 @@
|
|||||||
<canvas id="chart-editor"></canvas>
|
<canvas id="chart-editor"></canvas>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="w-full lg:w-1/2 p-1">
|
||||||
|
<div class="p-4 bg-white rounded shadow m-2" id="machine-container" style="height: 300px">
|
||||||
|
<canvas id="chart-machine"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user