mirror of
https://github.com/muety/wakapi.git
synced 2023-08-10 21:12:56 +03:00
Merge branch 'notarock/62'
This commit is contained in:
commit
e19761337f
@ -1,9 +1,12 @@
|
|||||||
# Build Stage
|
# Build Stage
|
||||||
|
|
||||||
FROM golang:1.13 AS build-env
|
FROM golang:1.13 AS build-env
|
||||||
ADD . /src
|
WORKDIR /src
|
||||||
RUN cd /src && go build -o wakapi
|
ADD ./go.mod .
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
ADD . .
|
||||||
|
RUN go build -o wakapi
|
||||||
|
|
||||||
# Final Stage
|
# Final Stage
|
||||||
|
|
||||||
@ -45,4 +48,4 @@ ADD wait-for-it.sh .
|
|||||||
|
|
||||||
VOLUME /data
|
VOLUME /data
|
||||||
|
|
||||||
ENTRYPOINT ./wait-for-it.sh
|
ENTRYPOINT ./wait-for-it.sh
|
||||||
|
@ -99,6 +99,7 @@ func (c *Config) GetMigrationFunc(dbDialect string) models.MigrationFunc {
|
|||||||
db.AutoMigrate(&models.Heartbeat{}).AddForeignKey("user_id", "users(id)", "RESTRICT", "RESTRICT")
|
db.AutoMigrate(&models.Heartbeat{}).AddForeignKey("user_id", "users(id)", "RESTRICT", "RESTRICT")
|
||||||
db.AutoMigrate(&models.SummaryItem{}).AddForeignKey("summary_id", "summaries(id)", "CASCADE", "CASCADE")
|
db.AutoMigrate(&models.SummaryItem{}).AddForeignKey("summary_id", "summaries(id)", "CASCADE", "CASCADE")
|
||||||
db.AutoMigrate(&models.KeyStringValue{})
|
db.AutoMigrate(&models.KeyStringValue{})
|
||||||
|
db.AutoMigrate(&models.CustomRule{})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
23
docker-compose.yml
Normal file
23
docker-compose.yml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
version: '3.7'
|
||||||
|
|
||||||
|
services:
|
||||||
|
wakapi:
|
||||||
|
build: .
|
||||||
|
ports:
|
||||||
|
- 3000:3000
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
WAKAPI_DB_TYPE: "postgres"
|
||||||
|
WAKAPI_DB_NAME: "wakapi"
|
||||||
|
WAKAPI_DB_USER: "wakapi"
|
||||||
|
WAKAPI_DB_PASSWORD: "CHANGE_ME!!!"
|
||||||
|
WAKAPI_DB_HOST: "db"
|
||||||
|
WAKAPI_DB_PORT: "5432"
|
||||||
|
ENVIRONMENT: "prod"
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: postgres:12.3
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: "wakapi"
|
||||||
|
POSTGRES_PASSWORD: "CHANGE_ME!!!"
|
||||||
|
POSTGRES_DB: "wakapi"
|
10
main.go
10
main.go
@ -31,6 +31,7 @@ var (
|
|||||||
aliasService *services.AliasService
|
aliasService *services.AliasService
|
||||||
heartbeatService *services.HeartbeatService
|
heartbeatService *services.HeartbeatService
|
||||||
userService *services.UserService
|
userService *services.UserService
|
||||||
|
customRuleService *services.CustomRuleService
|
||||||
summaryService *services.SummaryService
|
summaryService *services.SummaryService
|
||||||
aggregationService *services.AggregationService
|
aggregationService *services.AggregationService
|
||||||
keyValueService *services.KeyValueService
|
keyValueService *services.KeyValueService
|
||||||
@ -74,7 +75,8 @@ func main() {
|
|||||||
aliasService = services.NewAliasService(db)
|
aliasService = services.NewAliasService(db)
|
||||||
heartbeatService = services.NewHeartbeatService(db)
|
heartbeatService = services.NewHeartbeatService(db)
|
||||||
userService = services.NewUserService(db)
|
userService = services.NewUserService(db)
|
||||||
summaryService = services.NewSummaryService(db, heartbeatService, aliasService)
|
customRuleService = services.NewCustomRuleService(db)
|
||||||
|
summaryService = services.NewSummaryService(db, heartbeatService, aliasService, customRuleService)
|
||||||
aggregationService = services.NewAggregationService(db, userService, summaryService, heartbeatService)
|
aggregationService = services.NewAggregationService(db, userService, summaryService, heartbeatService)
|
||||||
keyValueService = services.NewKeyValueService(db)
|
keyValueService = services.NewKeyValueService(db)
|
||||||
|
|
||||||
@ -88,10 +90,10 @@ func main() {
|
|||||||
// TODO: move endpoint registration to the respective routes files
|
// TODO: move endpoint registration to the respective routes files
|
||||||
|
|
||||||
// Handlers
|
// Handlers
|
||||||
heartbeatHandler := routes.NewHeartbeatHandler(heartbeatService)
|
|
||||||
summaryHandler := routes.NewSummaryHandler(summaryService)
|
summaryHandler := routes.NewSummaryHandler(summaryService)
|
||||||
healthHandler := routes.NewHealthHandler(db)
|
healthHandler := routes.NewHealthHandler(db)
|
||||||
settingsHandler := routes.NewSettingsHandler(userService)
|
heartbeatHandler := routes.NewHeartbeatHandler(heartbeatService, customRuleService)
|
||||||
|
settingsHandler := routes.NewSettingsHandler(userService, customRuleService)
|
||||||
publicHandler := routes.NewIndexHandler(userService, keyValueService)
|
publicHandler := routes.NewIndexHandler(userService, keyValueService)
|
||||||
wakatimeV1AllHandler := wtV1Routes.NewAllTimeHandler(summaryService)
|
wakatimeV1AllHandler := wtV1Routes.NewAllTimeHandler(summaryService)
|
||||||
wakatimeV1SummariesHandler := wtV1Routes.NewSummariesHandler(summaryService)
|
wakatimeV1SummariesHandler := wtV1Routes.NewSummariesHandler(summaryService)
|
||||||
@ -136,6 +138,8 @@ func main() {
|
|||||||
// Settings Routes
|
// Settings Routes
|
||||||
settingsRouter.Methods(http.MethodGet).HandlerFunc(settingsHandler.GetIndex)
|
settingsRouter.Methods(http.MethodGet).HandlerFunc(settingsHandler.GetIndex)
|
||||||
settingsRouter.Path("/credentials").Methods(http.MethodPost).HandlerFunc(settingsHandler.PostCredentials)
|
settingsRouter.Path("/credentials").Methods(http.MethodPost).HandlerFunc(settingsHandler.PostCredentials)
|
||||||
|
settingsRouter.Path("/customrules").Methods(http.MethodPost).HandlerFunc(settingsHandler.PostCreateCustomRule)
|
||||||
|
settingsRouter.Path("/customrules/delete").Methods(http.MethodPost).HandlerFunc(settingsHandler.DeleteCustomRule)
|
||||||
settingsRouter.Path("/reset").Methods(http.MethodPost).HandlerFunc(settingsHandler.PostResetApiKey)
|
settingsRouter.Path("/reset").Methods(http.MethodPost).HandlerFunc(settingsHandler.PostResetApiKey)
|
||||||
settingsRouter.Path("/badges").Methods(http.MethodPost).HandlerFunc(settingsHandler.PostToggleBadges)
|
settingsRouter.Path("/badges").Methods(http.MethodPost).HandlerFunc(settingsHandler.PostToggleBadges)
|
||||||
|
|
||||||
|
15
migrations/sqlite3/6_customrule_table.sql
Normal file
15
migrations/sqlite3/6_customrule_table.sql
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
-- +migrate Up
|
||||||
|
-- SQL in section 'Up' is executed when this migration is applied
|
||||||
|
|
||||||
|
create table custom_rules
|
||||||
|
(
|
||||||
|
id integer primary key autoincrement,
|
||||||
|
user_id varchar(255) not null REFERENCES users (id) ON DELETE RESTRICT ON UPDATE RESTRICT,
|
||||||
|
extension varchar(255),
|
||||||
|
language varchar(255)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- +migrate Down
|
||||||
|
-- SQL section 'Down' is executed when this migration is rolled back
|
||||||
|
|
||||||
|
DROP TABLE custom_rules;
|
17
models/customrule.go
Normal file
17
models/customrule.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
type CustomRule struct {
|
||||||
|
ID uint `json:"id" gorm:"primary_key"`
|
||||||
|
User *User `json:"-" gorm:"not null"`
|
||||||
|
UserID string `json:"-" gorm:"not null; index:idx_customrule_user"`
|
||||||
|
Extension string `json:"extension"`
|
||||||
|
Language string `json:"language"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateLanguage(language string) bool {
|
||||||
|
return len(language) >= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateExtension(extension string) bool {
|
||||||
|
return len(extension) >= 1
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -27,7 +28,7 @@ func (h *Heartbeat) Valid() bool {
|
|||||||
return h.User != nil && h.UserID != "" && h.Time != CustomTime(time.Time{})
|
return h.User != nil && h.UserID != "" && h.Time != CustomTime(time.Time{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Heartbeat) Augment(customLangs map[string]string) {
|
func (h *Heartbeat) AugmentWithConfigRules(customLangs map[string]string) {
|
||||||
if h.Language == "" {
|
if h.Language == "" {
|
||||||
if h.languageRegex == nil {
|
if h.languageRegex == nil {
|
||||||
h.languageRegex = regexp.MustCompile(`^.+\.(.+)$`)
|
h.languageRegex = regexp.MustCompile(`^.+\.(.+)$`)
|
||||||
@ -43,3 +44,14 @@ func (h *Heartbeat) Augment(customLangs map[string]string) {
|
|||||||
h.Language, _ = customLangs[ending]
|
h.Language, _ = customLangs[ending]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Heartbeat) AugmentWithUserRules(customRules []*CustomRule) {
|
||||||
|
for _, lang := range customRules {
|
||||||
|
reg := fmt.Sprintf(".*%s$", lang.Extension)
|
||||||
|
match, err := regexp.MatchString(reg, h.Entity)
|
||||||
|
if match && err == nil {
|
||||||
|
h.Language = lang.Language
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -13,14 +13,16 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type HeartbeatHandler struct {
|
type HeartbeatHandler struct {
|
||||||
config *config2.Config
|
config *config2.Config
|
||||||
heartbeatSrvc *services.HeartbeatService
|
heartbeatSrvc *services.HeartbeatService
|
||||||
|
customRuleSrvc *services.CustomRuleService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHeartbeatHandler(heartbeatService *services.HeartbeatService) *HeartbeatHandler {
|
func NewHeartbeatHandler(heartbeatService *services.HeartbeatService, customRuleService *services.CustomRuleService) *HeartbeatHandler {
|
||||||
return &HeartbeatHandler{
|
return &HeartbeatHandler{
|
||||||
config: config2.Get(),
|
config: config2.Get(),
|
||||||
heartbeatSrvc: heartbeatService,
|
heartbeatSrvc: heartbeatService,
|
||||||
|
customRuleSrvc: customRuleService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,13 +43,21 @@ func (h *HeartbeatHandler) ApiPost(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rules, err := h.customRuleSrvc.GetCustomRuleForUser(user.ID)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
w.Write([]byte(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
for _, hb := range heartbeats {
|
for _, hb := range heartbeats {
|
||||||
hb.OperatingSystem = opSys
|
hb.OperatingSystem = opSys
|
||||||
hb.Editor = editor
|
hb.Editor = editor
|
||||||
hb.Machine = machineName
|
hb.Machine = machineName
|
||||||
hb.User = user
|
hb.User = user
|
||||||
hb.UserID = user.ID
|
hb.UserID = user.ID
|
||||||
hb.Augment(h.config.App.CustomLanguages)
|
hb.AugmentWithConfigRules(h.config.App.CustomLanguages)
|
||||||
|
hb.AugmentWithUserRules(rules)
|
||||||
|
|
||||||
if !hb.Valid() {
|
if !hb.Valid() {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
@ -9,19 +9,22 @@ import (
|
|||||||
"github.com/muety/wakapi/utils"
|
"github.com/muety/wakapi/utils"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SettingsHandler struct {
|
type SettingsHandler struct {
|
||||||
config *conf.Config
|
config *conf.Config
|
||||||
userSrvc *services.UserService
|
userSrvc *services.UserService
|
||||||
|
customRuleSrvc *services.CustomRuleService
|
||||||
}
|
}
|
||||||
|
|
||||||
var credentialsDecoder = schema.NewDecoder()
|
var credentialsDecoder = schema.NewDecoder()
|
||||||
|
|
||||||
func NewSettingsHandler(userService *services.UserService) *SettingsHandler {
|
func NewSettingsHandler(userService *services.UserService, customRuleService *services.CustomRuleService) *SettingsHandler {
|
||||||
return &SettingsHandler{
|
return &SettingsHandler{
|
||||||
config: conf.Get(),
|
config: conf.Get(),
|
||||||
userSrvc: userService,
|
customRuleSrvc: customRuleService,
|
||||||
|
userSrvc: userService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,14 +34,14 @@ func (h *SettingsHandler) GetIndex(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
user := r.Context().Value(models.UserKey).(*models.User)
|
user := r.Context().Value(models.UserKey).(*models.User)
|
||||||
|
rules, _ := h.customRuleSrvc.GetCustomRuleForUser(user.ID)
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"User": user,
|
"User": user,
|
||||||
|
"Rules": rules,
|
||||||
|
"Success": r.FormValue("success"),
|
||||||
|
"Error": r.FormValue("error"),
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: when alerts are present, other data will not be passed to the template
|
|
||||||
if handleAlerts(w, r, conf.SettingsTemplate) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
templates[conf.SettingsTemplate].Execute(w, data)
|
templates[conf.SettingsTemplate].Execute(w, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,6 +108,56 @@ func (h *SettingsHandler) PostCredentials(w http.ResponseWriter, r *http.Request
|
|||||||
http.Redirect(w, r, fmt.Sprintf("%s/settings?success=%s", h.config.Server.BasePath, msg), http.StatusFound)
|
http.Redirect(w, r, fmt.Sprintf("%s/settings?success=%s", h.config.Server.BasePath, msg), http.StatusFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *SettingsHandler) DeleteCustomRule(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if h.config.IsDev() {
|
||||||
|
loadTemplates()
|
||||||
|
}
|
||||||
|
|
||||||
|
user := r.Context().Value(models.UserKey).(*models.User)
|
||||||
|
ruleId, err := strconv.Atoi(r.PostFormValue("ruleid"))
|
||||||
|
if err != nil {
|
||||||
|
respondAlert(w, "internal server error", "", conf.SettingsTemplate, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rule := &models.CustomRule{
|
||||||
|
ID: uint(ruleId),
|
||||||
|
UserID: user.ID,
|
||||||
|
}
|
||||||
|
h.customRuleSrvc.Delete(rule)
|
||||||
|
msg := url.QueryEscape("Custom rule deleted successfully.")
|
||||||
|
|
||||||
|
http.Redirect(w, r, fmt.Sprintf("%s/settings?success=%s", h.config.Server.BasePath, msg), http.StatusFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SettingsHandler) PostCreateCustomRule(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if h.config.IsDev() {
|
||||||
|
loadTemplates()
|
||||||
|
}
|
||||||
|
user := r.Context().Value(models.UserKey).(*models.User)
|
||||||
|
extension := r.PostFormValue("extension")
|
||||||
|
language := r.PostFormValue("language")
|
||||||
|
|
||||||
|
if extension[0] == '.' {
|
||||||
|
extension = extension[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
rule := &models.CustomRule{
|
||||||
|
UserID: user.ID,
|
||||||
|
Extension: extension,
|
||||||
|
Language: language,
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := h.customRuleSrvc.Create(rule); err != nil {
|
||||||
|
respondAlert(w, "internal server error", "", conf.SettingsTemplate, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := url.QueryEscape("Custom rule saved successfully.")
|
||||||
|
|
||||||
|
http.Redirect(w, r, fmt.Sprintf("%s/settings?success=%s", h.config.Server.BasePath, msg), http.StatusFound)
|
||||||
|
}
|
||||||
|
|
||||||
func (h *SettingsHandler) PostResetApiKey(w http.ResponseWriter, r *http.Request) {
|
func (h *SettingsHandler) PostResetApiKey(w http.ResponseWriter, r *http.Request) {
|
||||||
if h.config.IsDev() {
|
if h.config.IsDev() {
|
||||||
loadTemplates()
|
loadTemplates()
|
||||||
|
64
services/custom_rule.go
Normal file
64
services/custom_rule.go
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jinzhu/gorm"
|
||||||
|
"github.com/muety/wakapi/config"
|
||||||
|
"github.com/muety/wakapi/models"
|
||||||
|
"github.com/patrickmn/go-cache"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CustomRuleService struct {
|
||||||
|
Config *config.Config
|
||||||
|
Db *gorm.DB
|
||||||
|
cache *cache.Cache
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCustomRuleService(db *gorm.DB) *CustomRuleService {
|
||||||
|
return &CustomRuleService{
|
||||||
|
Config: config.Get(),
|
||||||
|
Db: db,
|
||||||
|
cache: cache.New(1*time.Hour, 2*time.Hour),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *CustomRuleService) GetCustomRuleById(customRuleId uint) (*models.CustomRule, error) {
|
||||||
|
r := &models.CustomRule{}
|
||||||
|
if err := srv.Db.Where(&models.CustomRule{ID: customRuleId}).First(r).Error; err != nil {
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *CustomRuleService) GetCustomRuleForUser(userId string) ([]*models.CustomRule, error) {
|
||||||
|
var rules []*models.CustomRule
|
||||||
|
if rules, found := srv.cache.Get(userId); found {
|
||||||
|
return rules.([]*models.CustomRule), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := srv.Db.
|
||||||
|
Where(&models.CustomRule{UserID: userId}).
|
||||||
|
Find(&rules).Error; err != nil {
|
||||||
|
return rules, err
|
||||||
|
}
|
||||||
|
srv.cache.Set(userId, rules, cache.DefaultExpiration)
|
||||||
|
return rules, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *CustomRuleService) Create(rule *models.CustomRule) (*models.CustomRule, error) {
|
||||||
|
result := srv.Db.Create(rule)
|
||||||
|
if err := result.Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
srv.cache.Delete(rule.UserID)
|
||||||
|
|
||||||
|
return rule, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *CustomRuleService) Delete(rule *models.CustomRule) {
|
||||||
|
srv.Db.
|
||||||
|
Where("id = ?", rule.ID).
|
||||||
|
Where("user_id = ?", rule.UserID).
|
||||||
|
Delete(models.CustomRule{})
|
||||||
|
srv.cache.Delete(rule.UserID)
|
||||||
|
}
|
@ -17,20 +17,22 @@ import (
|
|||||||
const HeartbeatDiffThreshold = 2 * time.Minute
|
const HeartbeatDiffThreshold = 2 * time.Minute
|
||||||
|
|
||||||
type SummaryService struct {
|
type SummaryService struct {
|
||||||
Config *config.Config
|
Config *config.Config
|
||||||
Cache *cache.Cache
|
Cache *cache.Cache
|
||||||
Db *gorm.DB
|
Db *gorm.DB
|
||||||
HeartbeatService *HeartbeatService
|
HeartbeatService *HeartbeatService
|
||||||
AliasService *AliasService
|
AliasService *AliasService
|
||||||
|
CustomRuleService *CustomRuleService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSummaryService(db *gorm.DB, heartbeatService *HeartbeatService, aliasService *AliasService) *SummaryService {
|
func NewSummaryService(db *gorm.DB, heartbeatService *HeartbeatService, aliasService *AliasService, customRuleService *CustomRuleService) *SummaryService {
|
||||||
return &SummaryService{
|
return &SummaryService{
|
||||||
Config: config.Get(),
|
Config: config.Get(),
|
||||||
Cache: cache.New(24*time.Hour, 24*time.Hour),
|
Cache: cache.New(24*time.Hour, 24*time.Hour),
|
||||||
Db: db,
|
Db: db,
|
||||||
HeartbeatService: heartbeatService,
|
HeartbeatService: heartbeatService,
|
||||||
AliasService: aliasService,
|
AliasService: aliasService,
|
||||||
|
CustomRuleService: customRuleService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,7 +203,7 @@ function togglePlaceholders(mask) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getPresentDataMask() {
|
function getPresentDataMask() {
|
||||||
return data.map(list => list.reduce((acc, e) => acc + e.total, 0) > 0)
|
return data.map(list => list ? list.reduce((acc, e) => acc + e.total, 0) : 0 > 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getContainer(chart) {
|
function getContainer(chart) {
|
||||||
@ -303,4 +303,5 @@ window.addEventListener('load', function () {
|
|||||||
setTopLabels()
|
setTopLabels()
|
||||||
togglePlaceholders(getPresentDataMask())
|
togglePlaceholders(getPresentDataMask())
|
||||||
draw()
|
draw()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -66,6 +66,59 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="w-full mt-4 mb-8 pb-8">
|
||||||
|
<div class="font-semibold text-lg text-white m-0 border-b-2 border-green-700 inline-block">
|
||||||
|
Custom Rules
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-gray-300 text-sm mb-4 mt-6">
|
||||||
|
Custom rules modify future coding activity, they’re useful for correcting languages displayed wrong on your dashboard.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ if .Rules }}
|
||||||
|
{{ range $i, $rule := .Rules }}
|
||||||
|
<div class="text-white border-1 w-full border-green-700 inline-block my-1 py-1 text-align">
|
||||||
|
<label class="inline-block text-sm mb-1 text-gray-500" >When filename ends in:</label>
|
||||||
|
{{ $rule.Extension }}
|
||||||
|
<label class="inline-block text-sm mb-1 text-gray-500" >Change the language to:</label>
|
||||||
|
{{ $rule.Language }}
|
||||||
|
|
||||||
|
<form class="float-right" action="settings/customrules/delete" method="post">
|
||||||
|
<input type="hidden" id="ruleid" name="ruleid" required value="{{ $rule.ID }}">
|
||||||
|
<button type="submit" class="py-1 px-3 rounded bg-red-500 hover:bg-red-600 text-white text-sm">
|
||||||
|
Remove
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{else}}
|
||||||
|
<div class="text-white border-1 w-full border-green-700 inline-block my-1 py-1">
|
||||||
|
No rules.
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
<form action="settings/customrules" method="post">
|
||||||
|
<div class="inline-block justify-around mt-4 w-full">
|
||||||
|
<label class="inline-block text-sm mb-1 text-gray-500" for="extension">When filename ends in:</label>
|
||||||
|
<input class="shadow appearance-nonshadow appearance-none bg-gray-800 focus:bg-gray-700 text-gray-300 border-green-700 focus:border-gray-500 border rounded w-full py-1 px-3"
|
||||||
|
type="text" id="extension"
|
||||||
|
name="extension" placeholder="py" minlength="1" required>
|
||||||
|
</div>
|
||||||
|
<div class="inline-block justify-around mt-4 w-full">
|
||||||
|
<label class="inline-block text-sm mb-1 text-gray-500" for="language">Change the language to:</label>
|
||||||
|
<input class="shadow appearance-nonshadow appearance-none bg-gray-800 focus:bg-gray-700 text-gray-300 border-green-700 focus:border-gray-500 border rounded w-full py-1 px-3"
|
||||||
|
type="text" id="language"
|
||||||
|
name="language" placeholder="Python" minlength="1" required>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between float-right">
|
||||||
|
<button type="submit" class="py-1 px-3 my-3 rounded bg-green-700 hover:bg-green-800 text-white text-sm">
|
||||||
|
Add rule
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="w-full mt-4 mb-8 pb-8">
|
<div class="w-full mt-4 mb-8 pb-8">
|
||||||
<div class="font-semibold text-lg text-white m-0 border-b-2 border-green-700 inline-block">
|
<div class="font-semibold text-lg text-white m-0 border-b-2 border-green-700 inline-block">
|
||||||
Badges
|
Badges
|
||||||
@ -136,4 +189,5 @@
|
|||||||
{{ template "foot.tpl.html" . }}
|
{{ template "foot.tpl.html" . }}
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user