mirror of
https://github.com/muety/wakapi.git
synced 2023-08-10 21:12:56 +03:00
Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
7d36c4e111 | |||
1510bcaa91 | |||
b858a02ecd | |||
0e61870568 | |||
0664861f74 | |||
bcb56a02ef | |||
fd2e0d145d |
@ -1,4 +1,5 @@
|
||||
ENV=prod
|
||||
WAKAPI_DB_TYPE=mysql
|
||||
WAKAPI_DB_USER=myuser
|
||||
WAKAPI_DB_PASSWORD=mysecretpassword
|
||||
WAKAPI_DB_HOST=localhost
|
||||
|
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@ -1,2 +1,2 @@
|
||||
github: n1try
|
||||
github: muety
|
||||
custom: ['https://paypal.me/ferdinandmuetsch', 'https://www.buymeacoffee.com/n1try']
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -2,4 +2,5 @@ launch.json
|
||||
.vscode
|
||||
.env
|
||||
wakapi
|
||||
.idea
|
||||
.idea
|
||||
build
|
17
README.md
17
README.md
@ -8,16 +8,16 @@
|
||||
## Prerequisites
|
||||
### Server
|
||||
* Go >= 1.13 (with `$GOPATH` properly set)
|
||||
* A MySQL database
|
||||
* An SQL database (MySQL or Postgres)
|
||||
|
||||
### Client
|
||||
* [WakaTime plugin](https://wakatime.com/plugins) for your editor / IDE
|
||||
|
||||
## Usage
|
||||
* Create an empty MySQL database
|
||||
* Create an empty database
|
||||
* Enable Go module support: `export GO111MODULE=on`
|
||||
* Get code: `go get github.com/n1try/wakapi`
|
||||
* Go to project root: `cd "$GOPATH/src/github.com/n1try/wakapi"`
|
||||
* Get code: `go get github.com/muety/wakapi`
|
||||
* Go to project root: `cd "$GOPATH/src/github.com/muety/wakapi"`
|
||||
* Copy `.env.example` to `.env` and set database credentials
|
||||
* Set target port in `config.ini`
|
||||
* Build executable: `go build`
|
||||
@ -27,6 +27,8 @@
|
||||
* `api_key = the_api_key_printed_to_the_console_after_starting_the_server`
|
||||
* Open [http://localhost:3000](http://localhost:3000) in your browser
|
||||
|
||||
**As an alternative** to building from source or using `go get` you can also download one of the existing [pre-compiled binaries](https://github.com/muety/wakapi/releases).
|
||||
|
||||
### Run with Docker
|
||||
* Edit `docker-compose.yml` file and change passwords for the DB
|
||||
* Build the container `docker-compose build`
|
||||
@ -34,7 +36,6 @@
|
||||
* To get the api key look in the logs `docker-compose logs | grep "API key"`
|
||||
* The application should now be running on `localhost:3000`
|
||||
|
||||
|
||||
### User Accounts
|
||||
* When starting wakapi for the first time, a default user _**admin**_ with password _**admin**_ is created. The corresponding API key is printed to the console.
|
||||
* Additional users, at the moment, can be added only via SQL statements on your database, like this:
|
||||
@ -62,12 +63,6 @@ For the above example, you would need to add two aliases, like this:
|
||||
It is recommended to use wakapi behind a **reverse proxy**, like [Caddy](https://caddyserver.com) or _nginx_ to enable **TLS encryption** (HTTPS).
|
||||
However, if you want to expose your wakapi instance to the public anyway, you need to set `listen = 0.0.0.0` in `config.ini`
|
||||
|
||||
## Todo
|
||||
* User sign up and log in
|
||||
* Additional endpoints for retrieving statistics data
|
||||
* Support for SQLite database
|
||||
* Unit tests
|
||||
|
||||
## Important Note
|
||||
**This is not an alternative to using WakaTime.** It is just a custom, non-commercial, self-hosted application to collect coding statistics using the already existing editor plugins provided by the WakaTime community. It was created for personal use only and with the purpose of keeping the sovereignity of your own data. However, if you like the official product, **please support the authors and buy an official WakaTime subscription!**
|
||||
|
||||
|
16
build.sh
Normal file
16
build.sh
Normal file
@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
|
||||
OSLIST=( darwin linux windows )
|
||||
ARCHLIST=( amd64 )
|
||||
VERSION=$(cat version.txt)
|
||||
|
||||
for os in ${OSLIST[*]}
|
||||
do
|
||||
for arch in ${ARCHLIST[*]}
|
||||
do
|
||||
GOOS=$os
|
||||
GOARCH=$arch
|
||||
echo "Building $GOOS / $GOARCH"
|
||||
GOOS=$GOOS GOARCH=$GOARCH go build -o "build/wakapi_${VERSION}_${GOOS}_${GOARCH}" "github.com/muety/wakapi"
|
||||
done
|
||||
done
|
1482
data/colors.json
Normal file
1482
data/colors.json
Normal file
File diff suppressed because it is too large
Load Diff
2
go.mod
2
go.mod
@ -1,4 +1,4 @@
|
||||
module github.com/n1try/wakapi
|
||||
module github.com/muety/wakapi
|
||||
|
||||
go 1.13
|
||||
|
||||
|
19
go.sum
19
go.sum
@ -1,6 +1,5 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.37.4 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU=
|
||||
cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||
@ -13,14 +12,11 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
|
||||
github.com/codegangsta/negroni v1.0.0 h1:+aYywywx4bnKXWvoWtRfJ91vC59NbEhEY03sZjQhbVY=
|
||||
github.com/codegangsta/negroni v1.0.0/go.mod h1:v0y3T5G7Y1UlFfyxFn/QLRU4a2EuNau2iZY63YTKWo0=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3 h1:tkum0XDgfR0jcVVXuTsYv/erY2NnEDqwRojbxR1rBYA=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
|
||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
@ -38,12 +34,10 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
@ -57,25 +51,20 @@ github.com/jinzhu/gorm v1.9.11 h1:gaHGvE+UnWGlbWG4Y3FUwY1EcZ5n6S9WtqBA/uySMLE=
|
||||
github.com/jinzhu/gorm v1.9.11/go.mod h1:bu/pK8szGZ2puuErfU0RwyeNdsf3e6nCX/noXaVxkfw=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M=
|
||||
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
|
||||
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q=
|
||||
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
@ -89,7 +78,6 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
||||
@ -104,21 +92,17 @@ github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
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/go.mod h1:SK1RZT4TR1aMUNGtbk6YxTPgx2D/gfbxB571QGnAV+c=
|
||||
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI=
|
||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
@ -157,7 +141,6 @@ golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
@ -168,14 +151,12 @@ google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/ini.v1 v1.50.0 h1:c/4YI/GUgB7d2yOkxdsQyYDhW67nWrTl6Zyd9vagYmg=
|
||||
gopkg.in/ini.v1 v1.50.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
51
main.go
51
main.go
@ -3,10 +3,13 @@ package main
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/codegangsta/negroni"
|
||||
@ -17,21 +20,26 @@ import (
|
||||
uuid "github.com/satori/go.uuid"
|
||||
ini "gopkg.in/ini.v1"
|
||||
|
||||
"github.com/n1try/wakapi/middlewares"
|
||||
"github.com/n1try/wakapi/models"
|
||||
"github.com/n1try/wakapi/routes"
|
||||
"github.com/n1try/wakapi/services"
|
||||
"github.com/n1try/wakapi/utils"
|
||||
"github.com/muety/wakapi/middlewares"
|
||||
"github.com/muety/wakapi/models"
|
||||
"github.com/muety/wakapi/routes"
|
||||
"github.com/muety/wakapi/services"
|
||||
"github.com/muety/wakapi/utils"
|
||||
|
||||
_ "github.com/jinzhu/gorm/dialects/mysql"
|
||||
_ "github.com/jinzhu/gorm/dialects/postgres"
|
||||
)
|
||||
|
||||
// TODO: Refactor entire project to be structured after business domains
|
||||
|
||||
func readConfig() *models.Config {
|
||||
if err := godotenv.Load(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// TODO: Use jinzhu/configor or so
|
||||
env, _ := os.LookupEnv("ENV")
|
||||
dbType, valid := os.LookupEnv("WAKAPI_DB_TYPE")
|
||||
dbUser, valid := os.LookupEnv("WAKAPI_DB_USER")
|
||||
dbPassword, valid := os.LookupEnv("WAKAPI_DB_PASSWORD")
|
||||
dbHost, valid := os.LookupEnv("WAKAPI_DB_HOST")
|
||||
@ -48,6 +56,10 @@ func readConfig() *models.Config {
|
||||
log.Fatalf("Fail to read file: %v", err)
|
||||
}
|
||||
|
||||
if dbType == "" {
|
||||
dbType = "mysql"
|
||||
}
|
||||
|
||||
dbMaxConn := cfg.Section("database").Key("max_connections").MustUint(1)
|
||||
addr := cfg.Section("server").Key("listen").MustString("127.0.0.1")
|
||||
port, err := strconv.Atoi(os.Getenv("PORT"))
|
||||
@ -64,6 +76,27 @@ func readConfig() *models.Config {
|
||||
customLangs[k.Name()] = k.MustString("unknown")
|
||||
}
|
||||
|
||||
// Read language colors
|
||||
// Source: https://raw.githubusercontent.com/ozh/github-colors/master/colors.json
|
||||
var colors = make(map[string]string)
|
||||
var rawColors map[string]struct {
|
||||
Color string `json:"color"`
|
||||
Url string `json:"url"`
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadFile("data/colors.json")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(data, &rawColors); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for k, v := range rawColors {
|
||||
colors[strings.ToLower(k)] = v.Color
|
||||
}
|
||||
|
||||
return &models.Config{
|
||||
Env: env,
|
||||
Port: port,
|
||||
@ -73,10 +106,11 @@ func readConfig() *models.Config {
|
||||
DbUser: dbUser,
|
||||
DbPassword: dbPassword,
|
||||
DbName: dbName,
|
||||
DbDialect: "mysql",
|
||||
DbDialect: dbType,
|
||||
DbMaxConn: dbMaxConn,
|
||||
CleanUp: cleanUp,
|
||||
CustomLanguages: customLangs,
|
||||
LanguageColors: colors,
|
||||
}
|
||||
}
|
||||
|
||||
@ -92,6 +126,7 @@ func main() {
|
||||
if err != nil {
|
||||
log.Fatal("Could not connect to database.")
|
||||
}
|
||||
// TODO: Graceful shutdown
|
||||
defer db.Close()
|
||||
|
||||
// Migrate database schema
|
||||
@ -147,8 +182,8 @@ func main() {
|
||||
mainRouter.Path("/").Methods(http.MethodGet).HandlerFunc(summaryHandler.Index)
|
||||
|
||||
// API Routes
|
||||
apiRouter.Path("/heartbeat").Methods(http.MethodPost).HandlerFunc(heartbeatHandler.Post)
|
||||
apiRouter.Path("/summary").Methods(http.MethodGet).HandlerFunc(summaryHandler.Get)
|
||||
apiRouter.Path("/heartbeat").Methods(http.MethodPost).HandlerFunc(heartbeatHandler.ApiPost)
|
||||
apiRouter.Path("/summary").Methods(http.MethodGet).HandlerFunc(summaryHandler.ApiGet)
|
||||
|
||||
// Static Routes
|
||||
router.PathPrefix("/assets").Handler(negroni.Classic().With(negroni.Wrap(http.FileServer(http.Dir("./static")))))
|
||||
|
@ -13,8 +13,8 @@ import (
|
||||
|
||||
"github.com/patrickmn/go-cache"
|
||||
|
||||
"github.com/n1try/wakapi/models"
|
||||
"github.com/n1try/wakapi/services"
|
||||
"github.com/muety/wakapi/models"
|
||||
"github.com/muety/wakapi/services"
|
||||
)
|
||||
|
||||
type AuthenticateMiddleware struct {
|
||||
|
@ -13,6 +13,7 @@ type Config struct {
|
||||
DbMaxConn uint
|
||||
CleanUp bool
|
||||
CustomLanguages map[string]string
|
||||
LanguageColors map[string]string
|
||||
}
|
||||
|
||||
func (c *Config) IsDev() bool {
|
||||
|
@ -35,3 +35,8 @@ type SummaryItemContainer struct {
|
||||
Type uint8
|
||||
Items []*SummaryItem
|
||||
}
|
||||
|
||||
type SummaryViewModel struct {
|
||||
*Summary
|
||||
LanguageColors map[string]string
|
||||
}
|
||||
|
@ -5,17 +5,17 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/n1try/wakapi/services"
|
||||
"github.com/n1try/wakapi/utils"
|
||||
"github.com/muety/wakapi/services"
|
||||
"github.com/muety/wakapi/utils"
|
||||
|
||||
"github.com/n1try/wakapi/models"
|
||||
"github.com/muety/wakapi/models"
|
||||
)
|
||||
|
||||
type HeartbeatHandler struct {
|
||||
HeartbeatSrvc *services.HeartbeatService
|
||||
}
|
||||
|
||||
func (h *HeartbeatHandler) Post(w http.ResponseWriter, r *http.Request) {
|
||||
func (h *HeartbeatHandler) ApiPost(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
|
@ -7,9 +7,9 @@ import (
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/n1try/wakapi/models"
|
||||
"github.com/n1try/wakapi/services"
|
||||
"github.com/n1try/wakapi/utils"
|
||||
"github.com/muety/wakapi/models"
|
||||
"github.com/muety/wakapi/services"
|
||||
"github.com/muety/wakapi/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -44,7 +44,7 @@ func (m *SummaryHandler) loadTemplates() {
|
||||
m.indexTemplate = indexTpl
|
||||
}
|
||||
|
||||
func (h *SummaryHandler) Get(w http.ResponseWriter, r *http.Request) {
|
||||
func (h *SummaryHandler) ApiGet(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
@ -91,7 +91,12 @@ func (h *SummaryHandler) Index(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
h.indexTemplate.Execute(w, summary)
|
||||
vm := models.SummaryViewModel{
|
||||
Summary: summary,
|
||||
LanguageColors: utils.FilterLanguageColors(h.SummarySrvc.Config.LanguageColors, summary),
|
||||
}
|
||||
|
||||
h.indexTemplate.Execute(w, vm)
|
||||
}
|
||||
|
||||
func loadUserSummary(r *http.Request, summaryService *services.SummaryService) (*models.Summary, error, int) {
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
|
||||
"github.com/jasonlvhit/gocron"
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/n1try/wakapi/models"
|
||||
"github.com/muety/wakapi/models"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/n1try/wakapi/models"
|
||||
"github.com/muety/wakapi/models"
|
||||
)
|
||||
|
||||
type AliasService struct {
|
||||
|
@ -2,12 +2,12 @@ package services
|
||||
|
||||
import (
|
||||
"github.com/jasonlvhit/gocron"
|
||||
"github.com/n1try/wakapi/utils"
|
||||
"github.com/muety/wakapi/utils"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/n1try/wakapi/models"
|
||||
"github.com/muety/wakapi/models"
|
||||
gormbulk "github.com/t-tiger/gorm-bulk-insert"
|
||||
)
|
||||
|
||||
|
@ -10,7 +10,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/n1try/wakapi/models"
|
||||
"github.com/muety/wakapi/models"
|
||||
)
|
||||
|
||||
type SummaryService struct {
|
||||
|
@ -2,7 +2,7 @@ package services
|
||||
|
||||
import (
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/n1try/wakapi/models"
|
||||
"github.com/muety/wakapi/models"
|
||||
)
|
||||
|
||||
type UserService struct {
|
||||
|
@ -1,11 +1,13 @@
|
||||
const SHOW_TOP_N = 10
|
||||
const CHART_TARGET_SIZE = 170
|
||||
|
||||
const projectsCanvas = document.getElementById("chart-projects")
|
||||
const osCanvas = document.getElementById("chart-os")
|
||||
const editorsCanvas = document.getElementById("chart-editor")
|
||||
const languagesCanvas = document.getElementById("chart-language")
|
||||
const projectsCanvas = document.getElementById('chart-projects')
|
||||
const osCanvas = document.getElementById('chart-os')
|
||||
const editorsCanvas = document.getElementById('chart-editor')
|
||||
const languagesCanvas = document.getElementById('chart-language')
|
||||
|
||||
let charts = []
|
||||
let resizeCount = 0
|
||||
|
||||
String.prototype.toHHMMSS = function () {
|
||||
var sec_num = parseInt(this, 10)
|
||||
@ -64,7 +66,9 @@ function draw() {
|
||||
tooltips: getTooltipOptions('projects', 'bar'),
|
||||
legend: {
|
||||
display: false
|
||||
}
|
||||
},
|
||||
maintainAspectRatio: false,
|
||||
onResize: onChartResize
|
||||
}
|
||||
})
|
||||
|
||||
@ -83,7 +87,9 @@ function draw() {
|
||||
},
|
||||
options: {
|
||||
title: Object.assign(titleOptions, {text: `Operating Systems (top ${SHOW_TOP_N})`}),
|
||||
tooltips: getTooltipOptions('operatingSystems', 'pie')
|
||||
tooltips: getTooltipOptions('operatingSystems', 'pie'),
|
||||
maintainAspectRatio: false,
|
||||
onResize: onChartResize
|
||||
}
|
||||
})
|
||||
|
||||
@ -102,7 +108,9 @@ function draw() {
|
||||
},
|
||||
options: {
|
||||
title: Object.assign(titleOptions, {text: `Editors (top ${SHOW_TOP_N})`}),
|
||||
tooltips: getTooltipOptions('editors', 'pie')
|
||||
tooltips: getTooltipOptions('editors', 'pie'),
|
||||
maintainAspectRatio: false,
|
||||
onResize: onChartResize
|
||||
}
|
||||
})
|
||||
|
||||
@ -113,7 +121,7 @@ function draw() {
|
||||
data: wakapiData.languages
|
||||
.slice(0, Math.min(SHOW_TOP_N, wakapiData.languages.length))
|
||||
.map(p => parseInt(p.total)),
|
||||
backgroundColor: wakapiData.languages.map(p => getRandomColor(p.key))
|
||||
backgroundColor: wakapiData.languages.map(p => languageColors[p.key.toLowerCase()] || getRandomColor(p.key))
|
||||
}],
|
||||
labels: wakapiData.languages
|
||||
.slice(0, Math.min(SHOW_TOP_N, wakapiData.languages.length))
|
||||
@ -121,7 +129,9 @@ function draw() {
|
||||
},
|
||||
options: {
|
||||
title: Object.assign(titleOptions, {text: `Languages (top ${SHOW_TOP_N})`}),
|
||||
tooltips: getTooltipOptions('languages', 'pie')
|
||||
tooltips: getTooltipOptions('languages', 'pie'),
|
||||
maintainAspectRatio: false,
|
||||
onResize: onChartResize
|
||||
}
|
||||
})
|
||||
|
||||
@ -129,39 +139,78 @@ function draw() {
|
||||
document.getElementById('grid-container').style.visibility = 'visible'
|
||||
|
||||
charts = [projectChart, osChart, editorChart, languageChart]
|
||||
|
||||
charts.forEach(c => c.options.onResize(c.chart))
|
||||
equalizeHeights()
|
||||
}
|
||||
|
||||
function getContainer(chart) {
|
||||
return chart.canvas.parentNode
|
||||
}
|
||||
|
||||
function onChartResize(chart) {
|
||||
let container = getContainer(chart)
|
||||
let targetHeight = Math.min(chart.width, CHART_TARGET_SIZE)
|
||||
let actualHeight = chart.height - chart.chartArea.top
|
||||
let containerTargetHeight = container.clientHeight += (targetHeight - actualHeight)
|
||||
container.style.height = parseInt(containerTargetHeight) + 'px'
|
||||
|
||||
resizeCount++
|
||||
watchEqualize()
|
||||
}
|
||||
|
||||
function watchEqualize() {
|
||||
if (resizeCount === charts.length) {
|
||||
equalizeHeights()
|
||||
resizeCount = 0
|
||||
}
|
||||
}
|
||||
|
||||
function equalizeHeights() {
|
||||
let maxHeight = 0
|
||||
charts.forEach(c => {
|
||||
let container = getContainer(c)
|
||||
if (maxHeight < container.clientHeight) {
|
||||
maxHeight = container.clientHeight
|
||||
}
|
||||
})
|
||||
charts.forEach(c => {
|
||||
let container = getContainer(c)
|
||||
container.style.height = parseInt(maxHeight) + 'px'
|
||||
})
|
||||
}
|
||||
|
||||
function getTotal(data) {
|
||||
let total = data.reduce((acc, d) => acc + d.total, 0)
|
||||
document.getElementById("total-span").innerText = total.toString().toHHMMSS()
|
||||
document.getElementById('total-span').innerText = total.toString().toHHMMSS()
|
||||
}
|
||||
|
||||
function getRandomColor(seed) {
|
||||
seed = seed ? seed : '1234567';
|
||||
Math.seedrandom(seed);
|
||||
var letters = '0123456789ABCDEF'.split('');
|
||||
var color = '#';
|
||||
seed = seed ? seed : '1234567'
|
||||
Math.seedrandom(seed)
|
||||
var letters = '0123456789ABCDEF'.split('')
|
||||
var color = '#'
|
||||
for (var i = 0; i < 6; i++) {
|
||||
color += letters[Math.floor(Math.random() * 16)];
|
||||
color += letters[Math.floor(Math.random() * 16)]
|
||||
}
|
||||
return color;
|
||||
return color
|
||||
}
|
||||
|
||||
// https://koddsson.com/posts/emoji-favicon/
|
||||
const favicon = document.querySelector("link[rel=icon]");
|
||||
const favicon = document.querySelector('link[rel=icon]')
|
||||
if (favicon) {
|
||||
const emoji = favicon.getAttribute("data-emoji");
|
||||
const emoji = favicon.getAttribute('data-emoji')
|
||||
if (emoji) {
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.height = 64;
|
||||
canvas.width = 64;
|
||||
const ctx = canvas.getContext("2d");
|
||||
ctx.font = "64px serif";
|
||||
ctx.fillText(emoji, 0, 64);
|
||||
favicon.href = canvas.toDataURL();
|
||||
const canvas = document.createElement('canvas')
|
||||
canvas.height = 64
|
||||
canvas.width = 64
|
||||
const ctx = canvas.getContext('2d')
|
||||
ctx.font = '64px serif'
|
||||
ctx.fillText(emoji, 0, 64)
|
||||
favicon.href = canvas.toDataURL()
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('load', function() {
|
||||
window.addEventListener('load', function () {
|
||||
draw()
|
||||
})
|
16
utils/color.go
Normal file
16
utils/color.go
Normal file
@ -0,0 +1,16 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"github.com/muety/wakapi/models"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func FilterLanguageColors(all map[string]string, summary *models.Summary) map[string]string {
|
||||
subset := make(map[string]string)
|
||||
for _, item := range summary.Languages {
|
||||
if c, ok := all[strings.ToLower(item.Key)]; ok {
|
||||
subset[strings.ToLower(item.Key)] = c
|
||||
}
|
||||
}
|
||||
return subset
|
||||
}
|
@ -2,12 +2,11 @@ package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/n1try/wakapi/models"
|
||||
"github.com/muety/wakapi/models"
|
||||
)
|
||||
|
||||
func ParseDate(date string) (time.Time, error) {
|
||||
@ -32,18 +31,34 @@ func ParseUserAgent(ua string) (string, string, error) {
|
||||
}
|
||||
|
||||
func MakeConnectionString(config *models.Config) string {
|
||||
location, _ := time.LoadLocation("Local")
|
||||
str := strings.Builder{}
|
||||
str.WriteString(config.DbUser)
|
||||
str.WriteString(":")
|
||||
str.WriteString(config.DbPassword)
|
||||
str.WriteString("@tcp(")
|
||||
str.WriteString(config.DbHost)
|
||||
str.WriteString(":")
|
||||
str.WriteString(strconv.Itoa(int(config.DbPort)))
|
||||
str.WriteString(")/")
|
||||
str.WriteString(config.DbName)
|
||||
str.WriteString("?charset=utf8&parseTime=true&loc=")
|
||||
str.WriteString(location.String())
|
||||
return str.String()
|
||||
switch config.DbDialect {
|
||||
case "mysql":
|
||||
return mySqlConnectionString(config)
|
||||
case "postgres":
|
||||
return postgresConnectionString(config)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func mySqlConnectionString(config *models.Config) string {
|
||||
location, _ := time.LoadLocation("Local")
|
||||
return fmt.Sprintf(
|
||||
"%s:%s@tcp(%s:%d)/%s?charset=utf8&parseTime=true&loc=%s",
|
||||
config.DbUser,
|
||||
config.DbPassword,
|
||||
config.DbHost,
|
||||
config.DbPort,
|
||||
config.DbName,
|
||||
location.String(),
|
||||
)
|
||||
}
|
||||
|
||||
func postgresConnectionString(config *models.Config) string {
|
||||
return fmt.Sprintf("host=%s port=%d user=%s dbname=%s password=%s sslmode=disable",
|
||||
config.DbHost,
|
||||
config.DbPort,
|
||||
config.DbUser,
|
||||
config.DbName,
|
||||
config.DbPassword,
|
||||
)
|
||||
}
|
||||
|
1
version.txt
Normal file
1
version.txt
Normal file
@ -0,0 +1 @@
|
||||
1.1.0
|
@ -10,7 +10,7 @@
|
||||
<link href="assets/app.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body class="bg-gray-800 text-gray-700 p-4 pt-12 flex flex-col min-h-screen max-w-screen-xl mx-auto justify-center">
|
||||
<body class="bg-gray-800 text-gray-700 p-4 pt-10 flex flex-col min-h-screen max-w-screen-xl mx-auto justify-center">
|
||||
<div class="flex items-center justify-center">
|
||||
<h1 class="font-semibold text-2xl text-white m-0 border-b-4 border-green-700">Your Coding Statistics 🤓</h1>
|
||||
</div>
|
||||
@ -22,7 +22,7 @@
|
||||
<a href="?interval=year" class="m-1 border-b border-green-700">This Year</a>
|
||||
<a href="?interval=any" class="m-1 border-b border-green-700">All Time</a>
|
||||
</div>
|
||||
<main class="mt-12 flex-grow" id="grid-container">
|
||||
<main class="mt-10 flex-grow" id="grid-container">
|
||||
<div class="flex justify-center">
|
||||
<div class="p-1">
|
||||
<div class="flex justify-center p-4 bg-white rounded shadow">
|
||||
@ -34,29 +34,29 @@
|
||||
</div>
|
||||
<div class="flex flex-wrap justify-center">
|
||||
<div class="w-full lg:w-1/2 p-1">
|
||||
<div class="p-4 bg-white rounded shadow m-2" id="projects-container">
|
||||
<div class="p-4 bg-white rounded shadow m-2" id="projects-container" style="height: 300px">
|
||||
<canvas id="chart-projects"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full lg:w-1/2 p-1">
|
||||
<div class="p-4 bg-white rounded shadow m-2" id="os-container">
|
||||
<div class="p-4 bg-white rounded shadow m-2" id="os-container" style="height: 300px">
|
||||
<canvas id="chart-os"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full lg:w-1/2 p-1">
|
||||
<div class="p-4 bg-white rounded shadow m-2" id="editor-container">
|
||||
<canvas id="chart-editor"></canvas>
|
||||
<div class="p-4 bg-white rounded shadow m-2 relative" id="language-container" style="height: 300px">
|
||||
<canvas id="chart-language"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full lg:w-1/2 p-1">
|
||||
<div class="p-4 bg-white rounded shadow m-2" id="language-container">
|
||||
<canvas id="chart-language"></canvas>
|
||||
<div class="p-4 bg-white rounded shadow m-2" id="editor-container" style="height: 300px">
|
||||
<canvas id="chart-editor"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<footer class="w-full text-center text-gray-300 text-xs mt-12">
|
||||
Made by <a href="https://muetsch.io" class="border-b border-green-700">Ferdinand Mütsch</a> as <a href="https://github.com/n1try/wakapi" class="border-b border-green-700">open-source</a>.
|
||||
Made by <a href="https://muetsch.io" class="border-b border-green-700">Ferdinand Mütsch</a> as <a href="https://github.com/muety/wakapi" class="border-b border-green-700">open-source</a>.
|
||||
</footer>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/seedrandom/2.4.4/seedrandom.min.js"></script>
|
||||
@ -64,7 +64,8 @@
|
||||
|
||||
<script>
|
||||
let wakapiData = {}
|
||||
wakapiData.projects = {{ json .Projects }}
|
||||
let languageColors = {{ .LanguageColors | json }}
|
||||
wakapiData.projects = {{ .Projects | json }}
|
||||
wakapiData.operatingSystems = {{ .OperatingSystems | json }}
|
||||
wakapiData.editors = {{ .Editors | json }}
|
||||
wakapiData.languages = {{ .Languages | json }}
|
||||
|
Reference in New Issue
Block a user