mirror of
https://github.com/muety/wakapi.git
synced 2023-08-10 21:12:56 +03:00
Compare commits
18 Commits
Author | SHA1 | Date | |
---|---|---|---|
cec2a84e2d | |||
ffb0b84d78 | |||
8a7333b899 | |||
dd3b9c9b9c | |||
d2b62e21c5 | |||
9505773165 | |||
4bfc8a9e9f | |||
df5fe6e623 | |||
037ad7b9b1 | |||
ec10cc922c | |||
acb76e1ab1 | |||
252a304ba8 | |||
c863cf6dc5 | |||
373d969734 | |||
99a3e8f5da | |||
4302cfcbd6 | |||
7cca0055fe | |||
20993a1182 |
10
.github/workflows/docker.yml
vendored
10
.github/workflows/docker.yml
vendored
@ -11,6 +11,15 @@ jobs:
|
|||||||
name: 'Build and publish Docker image'
|
name: 'Build and publish Docker image'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Set version
|
||||||
|
run: |
|
||||||
|
(git describe --tags --exact-match \
|
||||||
|
|| git symbolic-ref -q --short HEAD \
|
||||||
|
|| git rev-parse --short HEAD) > version.txt 2> /dev/null
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v1
|
uses: docker/setup-qemu-action@v1
|
||||||
|
|
||||||
@ -47,6 +56,7 @@ jobs:
|
|||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v2
|
||||||
with:
|
with:
|
||||||
|
context: .
|
||||||
file: Dockerfile
|
file: Dockerfile
|
||||||
push: true
|
push: true
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||||
|
44
.github/workflows/release.yml
vendored
44
.github/workflows/release.yml
vendored
@ -8,20 +8,13 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
name: 'Build, package and release to GitHub'
|
name: 'Build, package and release to GitHub'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
platform: [ubuntu-latest, macos-latest, windows-latest]
|
GOOS: [darwin, windows, linux]
|
||||||
include:
|
GOARCH: [amd64, arm64]
|
||||||
- platform: ubuntu-latest
|
|
||||||
alias: linux
|
|
||||||
- platform: macos-latest
|
|
||||||
alias: mac
|
|
||||||
- platform: windows-latest
|
|
||||||
alias: win
|
|
||||||
|
|
||||||
runs-on: ${{ matrix.platform }}
|
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Set up Go 1.x
|
- name: Set up Go 1.x
|
||||||
@ -33,24 +26,29 @@ jobs:
|
|||||||
- name: Check out code into the Go module directory
|
- name: Check out code into the Go module directory
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Get dependencies
|
- name: Set version
|
||||||
run: go get
|
shell: bash
|
||||||
|
run: |
|
||||||
|
(git describe --tags --exact-match \
|
||||||
|
|| git symbolic-ref -q --short HEAD \
|
||||||
|
|| git rev-parse --short HEAD) > version.txt 2> /dev/null
|
||||||
|
|
||||||
|
- name: Prepare
|
||||||
|
run: |
|
||||||
|
mkdir -p dist/ && cd dist/
|
||||||
|
cp ../config.default.yml config.yml
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: go build -v .
|
working-directory: ./dist
|
||||||
|
run: |
|
||||||
|
GOOS=${{ matrix.GOOS }} GOARCH=${{ matrix.GOARCH }} go build -v ../
|
||||||
|
|
||||||
- name: Compress working folder on Windows
|
- name: Compress working folder
|
||||||
if: runner.os == 'Windows'
|
working-directory: ./dist
|
||||||
run: |
|
run: |
|
||||||
cp .\config.default.yml .\config.yml
|
zip -9 wakapi_${{ matrix.GOOS }}_${{ matrix.GOARCH }}.zip *
|
||||||
Compress-Archive -Path .\wakapi.exe, .\config.yml -DestinationPath wakapi_${{ matrix.alias }}_amd64.zip
|
|
||||||
- name: Compress working folder on Unix
|
|
||||||
if: runner.os != 'Windows'
|
|
||||||
run: |
|
|
||||||
cp config.default.yml config.yml
|
|
||||||
zip -9 wakapi_${{ matrix.alias }}_amd64.zip wakapi config.yml
|
|
||||||
|
|
||||||
- name: Upload built executable to Release
|
- name: Upload built executable to Release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
files: wakapi_${{ matrix.alias }}_amd64.zip
|
files: ./dist/*.zip
|
||||||
|
60
Dockerfile
60
Dockerfile
@ -1,33 +1,25 @@
|
|||||||
# To build locally: docker buildx build . -t wakapi --load
|
|
||||||
|
|
||||||
# Preparation to save some time
|
|
||||||
FROM --platform=$BUILDPLATFORM golang:1.18-alpine AS prep-env
|
|
||||||
WORKDIR /src
|
|
||||||
|
|
||||||
ADD ./go.mod .
|
|
||||||
RUN go mod download
|
|
||||||
ADD . .
|
|
||||||
|
|
||||||
RUN wget "https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh" -O wait-for-it.sh && \
|
|
||||||
chmod +x wait-for-it.sh
|
|
||||||
|
|
||||||
# Build Stage
|
|
||||||
FROM golang:1.18-alpine AS build-env
|
FROM golang:1.18-alpine AS build-env
|
||||||
|
WORKDIR /src
|
||||||
|
|
||||||
# Required for go-sqlite3
|
# Required for go-sqlite3
|
||||||
RUN apk add --no-cache gcc musl-dev
|
RUN apk add --no-cache gcc musl-dev
|
||||||
|
|
||||||
WORKDIR /src
|
RUN wget "https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh" -O wait-for-it.sh && \
|
||||||
COPY --from=prep-env /src .
|
chmod +x wait-for-it.sh
|
||||||
|
|
||||||
RUN go build -v -o wakapi
|
ADD ./go.mod ./go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
ADD . .
|
||||||
|
|
||||||
WORKDIR /app
|
RUN go build -ldflags "-s -w" -v -o wakapi main.go
|
||||||
RUN cp /src/wakapi . && \
|
|
||||||
cp /src/config.default.yml config.yml && \
|
WORKDIR /staging
|
||||||
sed -i 's/listen_ipv6: ::1/listen_ipv6: /g' config.yml && \
|
RUN mkdir ./data ./app && \
|
||||||
cp /src/wait-for-it.sh . && \
|
cp /src/wakapi app/ && \
|
||||||
cp /src/entrypoint.sh .
|
cp /src/config.default.yml app/config.yml && \
|
||||||
|
sed -i 's/listen_ipv6: ::1/listen_ipv6: /g' app/config.yml && \
|
||||||
|
cp /src/wait-for-it.sh app/ && \
|
||||||
|
cp /src/entrypoint.sh app/
|
||||||
|
|
||||||
# Run Stage
|
# Run Stage
|
||||||
|
|
||||||
@ -41,18 +33,18 @@ WORKDIR /app
|
|||||||
RUN apk add --no-cache bash ca-certificates tzdata
|
RUN apk add --no-cache bash ca-certificates tzdata
|
||||||
|
|
||||||
# See README.md and config.default.yml for all config options
|
# See README.md and config.default.yml for all config options
|
||||||
ENV ENVIRONMENT prod
|
ENV ENVIRONMENT=prod \
|
||||||
ENV WAKAPI_DB_TYPE sqlite3
|
WAKAPI_DB_TYPE=sqlite3 \
|
||||||
ENV WAKAPI_DB_USER ''
|
WAKAPI_DB_USER='' \
|
||||||
ENV WAKAPI_DB_PASSWORD ''
|
WAKAPI_DB_PASSWORD='' \
|
||||||
ENV WAKAPI_DB_HOST ''
|
WAKAPI_DB_HOST='' \
|
||||||
ENV WAKAPI_DB_NAME=/data/wakapi.db
|
WAKAPI_DB_NAME=/data/wakapi.db \
|
||||||
ENV WAKAPI_PASSWORD_SALT ''
|
WAKAPI_PASSWORD_SALT='' \
|
||||||
ENV WAKAPI_LISTEN_IPV4 '0.0.0.0'
|
WAKAPI_LISTEN_IPV4='0.0.0.0' \
|
||||||
ENV WAKAPI_INSECURE_COOKIES 'true'
|
WAKAPI_INSECURE_COOKIES='true' \
|
||||||
ENV WAKAPI_ALLOW_SIGNUP 'true'
|
WAKAPI_ALLOW_SIGNUP='true'
|
||||||
|
|
||||||
COPY --from=build-env /app .
|
COPY --from=build-env /staging /
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
|
@ -4,10 +4,10 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
uuid "github.com/satori/go.uuid"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/jinzhu/configor"
|
"github.com/jinzhu/configor"
|
||||||
"github.com/muety/wakapi/data"
|
"github.com/muety/wakapi/data"
|
||||||
"github.com/muety/wakapi/models"
|
"github.com/muety/wakapi/models"
|
||||||
|
uuid "github.com/satori/go.uuid"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -29,6 +30,7 @@ const (
|
|||||||
KeyLatestTotalTime = "latest_total_time"
|
KeyLatestTotalTime = "latest_total_time"
|
||||||
KeyLatestTotalUsers = "latest_total_users"
|
KeyLatestTotalUsers = "latest_total_users"
|
||||||
KeyLastImportImport = "last_import"
|
KeyLastImportImport = "last_import"
|
||||||
|
KeyNewsbox = "newsbox"
|
||||||
|
|
||||||
SimpleDateFormat = "2006-01-02"
|
SimpleDateFormat = "2006-01-02"
|
||||||
SimpleDateTimeFormat = "2006-01-02 15:04:05"
|
SimpleDateTimeFormat = "2006-01-02 15:04:05"
|
||||||
@ -363,7 +365,13 @@ func Load(version string) *Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
env = config.Env
|
env = config.Env
|
||||||
|
|
||||||
config.Version = strings.TrimSpace(version)
|
config.Version = strings.TrimSpace(version)
|
||||||
|
tagVersionMatch, _ := regexp.MatchString(`\d+\.\d+\.\d+`, version)
|
||||||
|
if tagVersionMatch {
|
||||||
|
config.Version = "v" + config.Version
|
||||||
|
}
|
||||||
|
|
||||||
config.InstanceId = uuid.NewV4().String()
|
config.InstanceId = uuid.NewV4().String()
|
||||||
config.App.Colors = readColors()
|
config.App.Colors = readColors()
|
||||||
config.Db.Dialect = resolveDbDialect(config.Db.Type)
|
config.Db.Dialect = resolveDbDialect(config.Db.Type)
|
||||||
|
26
main.go
26
main.go
@ -2,7 +2,6 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
"github.com/muety/wakapi/migrations"
|
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
@ -11,28 +10,29 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/lpar/gzipped/v2"
|
|
||||||
"github.com/muety/wakapi/routes/relay"
|
|
||||||
|
|
||||||
"github.com/emvi/logbuch"
|
"github.com/emvi/logbuch"
|
||||||
"github.com/gorilla/handlers"
|
"github.com/gorilla/handlers"
|
||||||
conf "github.com/muety/wakapi/config"
|
|
||||||
"github.com/muety/wakapi/repositories"
|
|
||||||
"github.com/muety/wakapi/routes/api"
|
|
||||||
"github.com/muety/wakapi/services/mail"
|
|
||||||
fsutils "github.com/muety/wakapi/utils/fs"
|
|
||||||
"gorm.io/gorm/logger"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/lpar/gzipped/v2"
|
||||||
|
|
||||||
|
conf "github.com/muety/wakapi/config"
|
||||||
"github.com/muety/wakapi/middlewares"
|
"github.com/muety/wakapi/middlewares"
|
||||||
|
"github.com/muety/wakapi/migrations"
|
||||||
|
"github.com/muety/wakapi/repositories"
|
||||||
"github.com/muety/wakapi/routes"
|
"github.com/muety/wakapi/routes"
|
||||||
|
"github.com/muety/wakapi/routes/api"
|
||||||
shieldsV1Routes "github.com/muety/wakapi/routes/compat/shields/v1"
|
shieldsV1Routes "github.com/muety/wakapi/routes/compat/shields/v1"
|
||||||
wtV1Routes "github.com/muety/wakapi/routes/compat/wakatime/v1"
|
wtV1Routes "github.com/muety/wakapi/routes/compat/wakatime/v1"
|
||||||
|
"github.com/muety/wakapi/routes/relay"
|
||||||
"github.com/muety/wakapi/services"
|
"github.com/muety/wakapi/services"
|
||||||
|
"github.com/muety/wakapi/services/mail"
|
||||||
|
fsutils "github.com/muety/wakapi/utils/fs"
|
||||||
|
|
||||||
_ "gorm.io/driver/mysql"
|
_ "gorm.io/driver/mysql"
|
||||||
_ "gorm.io/driver/postgres"
|
_ "gorm.io/driver/postgres"
|
||||||
_ "gorm.io/driver/sqlite"
|
_ "gorm.io/driver/sqlite"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Embed version.txt
|
// Embed version.txt
|
||||||
@ -121,6 +121,10 @@ func main() {
|
|||||||
// Connect to database
|
// Connect to database
|
||||||
var err error
|
var err error
|
||||||
db, err = gorm.Open(config.Db.GetDialector(), &gorm.Config{Logger: gormLogger})
|
db, err = gorm.Open(config.Db.GetDialector(), &gorm.Config{Logger: gormLogger})
|
||||||
|
if err != nil {
|
||||||
|
logbuch.Error(err.Error())
|
||||||
|
logbuch.Fatal("could not open database")
|
||||||
|
}
|
||||||
if config.Db.IsSQLite() {
|
if config.Db.IsSQLite() {
|
||||||
db.Exec("PRAGMA foreign_keys = ON;")
|
db.Exec("PRAGMA foreign_keys = ON;")
|
||||||
}
|
}
|
||||||
|
@ -14,8 +14,9 @@ var (
|
|||||||
IntervalPast7DaysYesterday = &IntervalKey{"Last 7 Days from Yesterday"}
|
IntervalPast7DaysYesterday = &IntervalKey{"Last 7 Days from Yesterday"}
|
||||||
IntervalPast14Days = &IntervalKey{"Last 14 Days"}
|
IntervalPast14Days = &IntervalKey{"Last 14 Days"}
|
||||||
IntervalPast30Days = &IntervalKey{"30_days", "last_30_days", "Last 30 Days"}
|
IntervalPast30Days = &IntervalKey{"30_days", "last_30_days", "Last 30 Days"}
|
||||||
IntervalPast12Months = &IntervalKey{"12_months", "last_12_months"}
|
IntervalPast6Months = &IntervalKey{"6_months", "last_6_months"}
|
||||||
IntervalAny = &IntervalKey{"any"}
|
IntervalPast12Months = &IntervalKey{"12_months", "last_12_months", "last_year"}
|
||||||
|
IntervalAny = &IntervalKey{"any", "all_time"}
|
||||||
)
|
)
|
||||||
|
|
||||||
var AllIntervals = []*IntervalKey{
|
var AllIntervals = []*IntervalKey{
|
||||||
@ -30,6 +31,7 @@ var AllIntervals = []*IntervalKey{
|
|||||||
IntervalPast7DaysYesterday,
|
IntervalPast7DaysYesterday,
|
||||||
IntervalPast14Days,
|
IntervalPast14Days,
|
||||||
IntervalPast30Days,
|
IntervalPast30Days,
|
||||||
|
IntervalPast6Months,
|
||||||
IntervalPast12Months,
|
IntervalPast12Months,
|
||||||
IntervalAny,
|
IntervalAny,
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,16 @@
|
|||||||
package view
|
package view
|
||||||
|
|
||||||
|
type Newsbox struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
}
|
||||||
|
|
||||||
type HomeViewModel struct {
|
type HomeViewModel struct {
|
||||||
Success string
|
Success string
|
||||||
Error string
|
Error string
|
||||||
TotalHours int
|
TotalHours int
|
||||||
TotalUsers int
|
TotalUsers int
|
||||||
|
Newsbox *Newsbox
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HomeViewModel) WithSuccess(m string) *HomeViewModel {
|
func (s *HomeViewModel) WithSuccess(m string) *HomeViewModel {
|
||||||
|
@ -79,6 +79,12 @@ func (h *HeartbeatApiHandler) Post(w http.ResponseWriter, r *http.Request) {
|
|||||||
machineName := r.Header.Get("X-Machine-Name")
|
machineName := r.Header.Get("X-Machine-Name")
|
||||||
|
|
||||||
for _, hb := range heartbeats {
|
for _, hb := range heartbeats {
|
||||||
|
if hb == nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
w.Write([]byte("invalid heartbeat object"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
hb.OperatingSystem = opSys
|
hb.OperatingSystem = opSys
|
||||||
hb.Editor = editor
|
hb.Editor = editor
|
||||||
hb.Machine = machineName
|
hb.Machine = machineName
|
||||||
|
@ -37,7 +37,7 @@ func (h *SummaryApiHandler) RegisterRoutes(router *mux.Router) {
|
|||||||
// @ID get-summary
|
// @ID get-summary
|
||||||
// @Tags summary
|
// @Tags summary
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param interval query string false "Interval identifier" Enums(today, yesterday, week, month, year, 7_days, last_7_days, 30_days, last_30_days, 12_months, last_12_months, any)
|
// @Param interval query string false "Interval identifier" Enums(today, yesterday, week, month, year, 7_days, last_7_days, 30_days, last_30_days, 6_months, last_6_months, 12_months, last_12_months, last_year, any, all_time)
|
||||||
// @Param from query string false "Start date (e.g. '2021-02-07')"
|
// @Param from query string false "Start date (e.g. '2021-02-07')"
|
||||||
// @Param to query string false "End date (e.g. '2021-02-08')"
|
// @Param to query string false "End date (e.g. '2021-02-08')"
|
||||||
// @Param recompute query bool false "Whether to recompute the summary from raw heartbeat or use cache"
|
// @Param recompute query bool false "Whether to recompute the summary from raw heartbeat or use cache"
|
||||||
|
@ -43,7 +43,7 @@ func (h *BadgeHandler) RegisterRoutes(router *mux.Router) {
|
|||||||
// @Tags badges
|
// @Tags badges
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param user path string true "User ID to fetch data for"
|
// @Param user path string true "User ID to fetch data for"
|
||||||
// @Param interval path string true "Interval to aggregate data for" Enums(today, yesterday, week, month, year, 7_days, last_7_days, 30_days, last_30_days, 12_months, last_12_months, any)
|
// @Param interval path string true "Interval to aggregate data for" Enums(today, yesterday, week, month, year, 7_days, last_7_days, 30_days, last_30_days, 6_months, last_6_months, 12_months, last_12_months, last_year, any, all_time)
|
||||||
// @Param filter path string true "Filter to apply (e.g. 'project:wakapi' or 'language:Go')"
|
// @Param filter path string true "Filter to apply (e.g. 'project:wakapi' or 'language:Go')"
|
||||||
// @Success 200 {object} v1.BadgeData
|
// @Success 200 {object} v1.BadgeData
|
||||||
// @Router /compat/shields/v1/{user}/{interval}/{filter} [get]
|
// @Router /compat/shields/v1/{user}/{interval}/{filter} [get]
|
||||||
|
@ -48,7 +48,7 @@ func (h *StatsHandler) RegisterRoutes(router *mux.Router) {
|
|||||||
// @Tags wakatime
|
// @Tags wakatime
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param user path string true "User ID to fetch data for (or 'current')"
|
// @Param user path string true "User ID to fetch data for (or 'current')"
|
||||||
// @Param range path string false "Range interval identifier" Enums(today, yesterday, week, month, year, 7_days, last_7_days, 30_days, last_30_days, 12_months, last_12_months, any)
|
// @Param range path string false "Range interval identifier" Enums(today, yesterday, week, month, year, 7_days, last_7_days, 30_days, last_30_days, 6_months, last_6_months, 12_months, last_12_months, last_year, any, all_time)
|
||||||
// @Param project query string false "Project to filter by"
|
// @Param project query string false "Project to filter by"
|
||||||
// @Param language query string false "Language to filter by"
|
// @Param language query string false "Language to filter by"
|
||||||
// @Param editor query string false "Editor to filter by"
|
// @Param editor query string false "Editor to filter by"
|
||||||
|
@ -50,7 +50,7 @@ func (h *SummariesHandler) RegisterRoutes(router *mux.Router) {
|
|||||||
// @Tags wakatime
|
// @Tags wakatime
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param user path string true "User ID to fetch data for (or 'current')"
|
// @Param user path string true "User ID to fetch data for (or 'current')"
|
||||||
// @Param range query string false "Range interval identifier" Enums(today, yesterday, week, month, year, 7_days, last_7_days, 30_days, last_30_days, 12_months, last_12_months, any)
|
// @Param range query string false "Range interval identifier" Enums(today, yesterday, week, month, year, 7_days, last_7_days, 30_days, last_30_days, 6_months, last_6_months, 12_months, last_12_months, last_year, any, all_time)
|
||||||
// @Param start query string false "Start date (e.g. '2021-02-07')"
|
// @Param start query string false "Start date (e.g. '2021-02-07')"
|
||||||
// @Param end query string false "End date (e.g. '2021-02-08')"
|
// @Param end query string false "End date (e.g. '2021-02-08')"
|
||||||
// @Param project query string false "Project to filter by"
|
// @Param project query string false "Project to filter by"
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package routes
|
package routes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/gorilla/schema"
|
"github.com/gorilla/schema"
|
||||||
@ -10,6 +11,7 @@ import (
|
|||||||
"github.com/muety/wakapi/services"
|
"github.com/muety/wakapi/services"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -49,23 +51,29 @@ func (h *HomeHandler) GetIndex(w http.ResponseWriter, r *http.Request) {
|
|||||||
func (h *HomeHandler) buildViewModel(r *http.Request) *view.HomeViewModel {
|
func (h *HomeHandler) buildViewModel(r *http.Request) *view.HomeViewModel {
|
||||||
var totalHours int
|
var totalHours int
|
||||||
var totalUsers int
|
var totalUsers int
|
||||||
|
var newsbox view.Newsbox
|
||||||
|
|
||||||
if t, err := h.keyValueSrvc.GetString(conf.KeyLatestTotalTime); err == nil && t != nil && t.Value != "" {
|
if kv, err := h.keyValueSrvc.GetString(conf.KeyLatestTotalTime); err == nil && kv != nil && kv.Value != "" {
|
||||||
if d, err := time.ParseDuration(t.Value); err == nil {
|
if d, err := time.ParseDuration(kv.Value); err == nil {
|
||||||
totalHours = int(d.Hours())
|
totalHours = int(d.Hours())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if t, err := h.keyValueSrvc.GetString(conf.KeyLatestTotalUsers); err == nil && t != nil && t.Value != "" {
|
if kv, err := h.keyValueSrvc.GetString(conf.KeyLatestTotalUsers); err == nil && kv != nil && kv.Value != "" {
|
||||||
if d, err := strconv.Atoi(t.Value); err == nil {
|
if d, err := strconv.Atoi(kv.Value); err == nil {
|
||||||
totalUsers = d
|
totalUsers = d
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if kv, err := h.keyValueSrvc.GetString(conf.KeyNewsbox); err == nil && kv != nil && kv.Value != "" {
|
||||||
|
json.NewDecoder(strings.NewReader(kv.Value)).Decode(&newsbox)
|
||||||
|
}
|
||||||
|
|
||||||
return &view.HomeViewModel{
|
return &view.HomeViewModel{
|
||||||
Success: r.URL.Query().Get("success"),
|
Success: r.URL.Query().Get("success"),
|
||||||
Error: r.URL.Query().Get("error"),
|
Error: r.URL.Query().Get("error"),
|
||||||
TotalHours: totalHours,
|
TotalHours: totalHours,
|
||||||
TotalUsers: totalUsers,
|
TotalUsers: totalUsers,
|
||||||
|
Newsbox: &newsbox,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,15 +2,15 @@ package routes
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/duke-git/lancet/v2/datetime"
|
|
||||||
"github.com/muety/wakapi/views"
|
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/duke-git/lancet/v2/datetime"
|
||||||
"github.com/muety/wakapi/config"
|
"github.com/muety/wakapi/config"
|
||||||
"github.com/muety/wakapi/models"
|
"github.com/muety/wakapi/models"
|
||||||
"github.com/muety/wakapi/utils"
|
"github.com/muety/wakapi/utils"
|
||||||
|
"github.com/muety/wakapi/views"
|
||||||
)
|
)
|
||||||
|
|
||||||
type action func(w http.ResponseWriter, r *http.Request) (int, string, string)
|
type action func(w http.ResponseWriter, r *http.Request) (int, string, string)
|
||||||
|
@ -93,6 +93,22 @@ body {
|
|||||||
@apply font-semibold text-gray-400 hover:text-gray-300;
|
@apply font-semibold text-gray-400 hover:text-gray-300;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.newsbox {
|
||||||
|
@apply px-4 py-2 border-2 border-red-700 bg-gray-850 rounded-md text-white border-green-700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.newsbox-default {
|
||||||
|
@apply border-green-700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.newsbox-warning {
|
||||||
|
@apply border-yellow-600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.newsbox-danger {
|
||||||
|
@apply border-red-700;
|
||||||
|
}
|
||||||
|
|
||||||
::-webkit-calendar-picker-indicator {
|
::-webkit-calendar-picker-indicator {
|
||||||
filter: invert(1);
|
filter: invert(1);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -54,9 +54,13 @@ const docTemplate = `{
|
|||||||
"last_7_days",
|
"last_7_days",
|
||||||
"30_days",
|
"30_days",
|
||||||
"last_30_days",
|
"last_30_days",
|
||||||
|
"6_months",
|
||||||
|
"last_6_months",
|
||||||
"12_months",
|
"12_months",
|
||||||
"last_12_months",
|
"last_12_months",
|
||||||
"any"
|
"last_year",
|
||||||
|
"any",
|
||||||
|
"all_time"
|
||||||
],
|
],
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Interval to aggregate data for",
|
"description": "Interval to aggregate data for",
|
||||||
@ -354,9 +358,13 @@ const docTemplate = `{
|
|||||||
"last_7_days",
|
"last_7_days",
|
||||||
"30_days",
|
"30_days",
|
||||||
"last_30_days",
|
"last_30_days",
|
||||||
|
"6_months",
|
||||||
|
"last_6_months",
|
||||||
"12_months",
|
"12_months",
|
||||||
"last_12_months",
|
"last_12_months",
|
||||||
"any"
|
"last_year",
|
||||||
|
"any",
|
||||||
|
"all_time"
|
||||||
],
|
],
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Range interval identifier",
|
"description": "Range interval identifier",
|
||||||
@ -445,9 +453,13 @@ const docTemplate = `{
|
|||||||
"last_7_days",
|
"last_7_days",
|
||||||
"30_days",
|
"30_days",
|
||||||
"last_30_days",
|
"last_30_days",
|
||||||
|
"6_months",
|
||||||
|
"last_6_months",
|
||||||
"12_months",
|
"12_months",
|
||||||
"last_12_months",
|
"last_12_months",
|
||||||
"any"
|
"last_year",
|
||||||
|
"any",
|
||||||
|
"all_time"
|
||||||
],
|
],
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Range interval identifier",
|
"description": "Range interval identifier",
|
||||||
@ -809,9 +821,13 @@ const docTemplate = `{
|
|||||||
"last_7_days",
|
"last_7_days",
|
||||||
"30_days",
|
"30_days",
|
||||||
"last_30_days",
|
"last_30_days",
|
||||||
|
"6_months",
|
||||||
|
"last_6_months",
|
||||||
"12_months",
|
"12_months",
|
||||||
"last_12_months",
|
"last_12_months",
|
||||||
"any"
|
"last_year",
|
||||||
|
"any",
|
||||||
|
"all_time"
|
||||||
],
|
],
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Interval identifier",
|
"description": "Interval identifier",
|
||||||
|
@ -46,9 +46,13 @@
|
|||||||
"last_7_days",
|
"last_7_days",
|
||||||
"30_days",
|
"30_days",
|
||||||
"last_30_days",
|
"last_30_days",
|
||||||
|
"6_months",
|
||||||
|
"last_6_months",
|
||||||
"12_months",
|
"12_months",
|
||||||
"last_12_months",
|
"last_12_months",
|
||||||
"any"
|
"last_year",
|
||||||
|
"any",
|
||||||
|
"all_time"
|
||||||
],
|
],
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Interval to aggregate data for",
|
"description": "Interval to aggregate data for",
|
||||||
@ -346,9 +350,13 @@
|
|||||||
"last_7_days",
|
"last_7_days",
|
||||||
"30_days",
|
"30_days",
|
||||||
"last_30_days",
|
"last_30_days",
|
||||||
|
"6_months",
|
||||||
|
"last_6_months",
|
||||||
"12_months",
|
"12_months",
|
||||||
"last_12_months",
|
"last_12_months",
|
||||||
"any"
|
"last_year",
|
||||||
|
"any",
|
||||||
|
"all_time"
|
||||||
],
|
],
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Range interval identifier",
|
"description": "Range interval identifier",
|
||||||
@ -437,9 +445,13 @@
|
|||||||
"last_7_days",
|
"last_7_days",
|
||||||
"30_days",
|
"30_days",
|
||||||
"last_30_days",
|
"last_30_days",
|
||||||
|
"6_months",
|
||||||
|
"last_6_months",
|
||||||
"12_months",
|
"12_months",
|
||||||
"last_12_months",
|
"last_12_months",
|
||||||
"any"
|
"last_year",
|
||||||
|
"any",
|
||||||
|
"all_time"
|
||||||
],
|
],
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Range interval identifier",
|
"description": "Range interval identifier",
|
||||||
@ -801,9 +813,13 @@
|
|||||||
"last_7_days",
|
"last_7_days",
|
||||||
"30_days",
|
"30_days",
|
||||||
"last_30_days",
|
"last_30_days",
|
||||||
|
"6_months",
|
||||||
|
"last_6_months",
|
||||||
"12_months",
|
"12_months",
|
||||||
"last_12_months",
|
"last_12_months",
|
||||||
"any"
|
"last_year",
|
||||||
|
"any",
|
||||||
|
"all_time"
|
||||||
],
|
],
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Interval identifier",
|
"description": "Interval identifier",
|
||||||
|
@ -430,9 +430,13 @@ paths:
|
|||||||
- last_7_days
|
- last_7_days
|
||||||
- 30_days
|
- 30_days
|
||||||
- last_30_days
|
- last_30_days
|
||||||
|
- 6_months
|
||||||
|
- last_6_months
|
||||||
- 12_months
|
- 12_months
|
||||||
- last_12_months
|
- last_12_months
|
||||||
|
- last_year
|
||||||
- any
|
- any
|
||||||
|
- all_time
|
||||||
in: path
|
in: path
|
||||||
name: interval
|
name: interval
|
||||||
required: true
|
required: true
|
||||||
@ -623,9 +627,13 @@ paths:
|
|||||||
- last_7_days
|
- last_7_days
|
||||||
- 30_days
|
- 30_days
|
||||||
- last_30_days
|
- last_30_days
|
||||||
|
- 6_months
|
||||||
|
- last_6_months
|
||||||
- 12_months
|
- 12_months
|
||||||
- last_12_months
|
- last_12_months
|
||||||
|
- last_year
|
||||||
- any
|
- any
|
||||||
|
- all_time
|
||||||
in: path
|
in: path
|
||||||
name: range
|
name: range
|
||||||
type: string
|
type: string
|
||||||
@ -686,9 +694,13 @@ paths:
|
|||||||
- last_7_days
|
- last_7_days
|
||||||
- 30_days
|
- 30_days
|
||||||
- last_30_days
|
- last_30_days
|
||||||
|
- 6_months
|
||||||
|
- last_6_months
|
||||||
- 12_months
|
- 12_months
|
||||||
- last_12_months
|
- last_12_months
|
||||||
|
- last_year
|
||||||
- any
|
- any
|
||||||
|
- all_time
|
||||||
in: query
|
in: query
|
||||||
name: range
|
name: range
|
||||||
type: string
|
type: string
|
||||||
@ -925,9 +937,13 @@ paths:
|
|||||||
- last_7_days
|
- last_7_days
|
||||||
- 30_days
|
- 30_days
|
||||||
- last_30_days
|
- last_30_days
|
||||||
|
- 6_months
|
||||||
|
- last_6_months
|
||||||
- 12_months
|
- 12_months
|
||||||
- last_12_months
|
- last_12_months
|
||||||
|
- last_year
|
||||||
- any
|
- any
|
||||||
|
- all_time
|
||||||
in: query
|
in: query
|
||||||
name: interval
|
name: interval
|
||||||
type: string
|
type: string
|
||||||
|
@ -2,6 +2,11 @@ module.exports = {
|
|||||||
purge: {
|
purge: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
mode: 'all',
|
mode: 'all',
|
||||||
content: ['./views/*.tpl.html']
|
content: ['./views/*.tpl.html'],
|
||||||
}
|
safelist: [
|
||||||
|
'newsbox-default',
|
||||||
|
'newsbox-warning',
|
||||||
|
'newsbox-danger',
|
||||||
|
]
|
||||||
|
},
|
||||||
}
|
}
|
@ -60,6 +60,8 @@ func ResolveIntervalTZ(interval *models.IntervalKey, tz *time.Location) (err err
|
|||||||
from = now.AddDate(0, 0, -14)
|
from = now.AddDate(0, 0, -14)
|
||||||
case models.IntervalPast30Days:
|
case models.IntervalPast30Days:
|
||||||
from = now.AddDate(0, 0, -30)
|
from = now.AddDate(0, 0, -30)
|
||||||
|
case models.IntervalPast6Months:
|
||||||
|
from = now.AddDate(0, -6, 0)
|
||||||
case models.IntervalPast12Months:
|
case models.IntervalPast12Months:
|
||||||
from = now.AddDate(0, -12, 0)
|
from = now.AddDate(0, -12, 0)
|
||||||
case models.IntervalAny:
|
case models.IntervalAny:
|
||||||
|
@ -1 +1 @@
|
|||||||
2.3.5
|
dev
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<footer class="flex justify-between w-full text-center text-gray-500 text-xs mt-20">
|
<footer class="flex justify-between w-full text-center text-gray-500 text-xs mt-20">
|
||||||
<div class="text-xs font-mono font-semibold">
|
<div class="text-xs font-mono font-semibold">
|
||||||
v{{ getVersion }} @ {{ getDbType }}
|
{{ getVersion }} @ {{ getDbType }}
|
||||||
</div>
|
</div>
|
||||||
<div class="font-semibold text-sm hidden sm:inline-block">
|
<div class="font-semibold text-sm hidden sm:inline-block">
|
||||||
Made with <span class="iconify inline" data-icon="bi:heart-fill"></span> by <a href="https://muetsch.io" class="text-gray-400 hover:text-gray-300">Ferdinand Mütsch</a> as <a
|
Made with <span class="iconify inline" data-icon="bi:heart-fill"></span> by <a href="https://muetsch.io" class="text-gray-400 hover:text-gray-300">Ferdinand Mütsch</a> as <a
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
{{ template "alerts.tpl.html" . }}
|
{{ template "alerts.tpl.html" . }}
|
||||||
|
|
||||||
<div class="absolute flex top-0 right-0 mr-8 mt-10 py-2">
|
<div class="absolute flex top-0 right-0 mr-4 mt-10 py-2">
|
||||||
<div class="mx-1">
|
<div class="mx-1">
|
||||||
<a href="login" class="btn-primary">
|
<a href="login" class="btn-primary">
|
||||||
<span class="iconify inline" data-icon="fluent:key-24-filled"></span> Login</a>
|
<span class="iconify inline" data-icon="fluent:key-24-filled"></span> Login</a>
|
||||||
@ -18,6 +18,12 @@
|
|||||||
|
|
||||||
<main class="mt-10 px-4 md:px-10 lg:px-24 flex-grow flex justify-center w-full">
|
<main class="mt-10 px-4 md:px-10 lg:px-24 flex-grow flex justify-center w-full">
|
||||||
<div class="flex flex-col text-white">
|
<div class="flex flex-col text-white">
|
||||||
|
{{ if and .Newsbox .Newsbox.Text }}
|
||||||
|
<div class="mb-14 -mt-4 newsbox newsbox-{{ .Newsbox.Type }}">
|
||||||
|
{{ .Newsbox.Text | htmlSafe }}
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
<h1 class="text-8xl font-semibold antialiased text-center mb-10 leading-snug">Keep Track of<br><span
|
<h1 class="text-8xl font-semibold antialiased text-center mb-10 leading-snug">Keep Track of<br><span
|
||||||
class="text-green-700">Your</span> Coding Time</h1>
|
class="text-green-700">Your</span> Coding Time</h1>
|
||||||
<p class="text-center text-gray-500 text-xl my-2">Wakapi is an open-source tool that helps you keep track of the
|
<p class="text-center text-gray-500 text-xl my-2">Wakapi is an open-source tool that helps you keep track of the
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
<a id="time-option-year" class="submenu-item hover:bg-gray-800 rounded p-1 text-right w-full text-gray-300 px-2 font-semibold text-sm" :href="intervalLink('year')" @click="state.showDropdownTimepicker = !state.showDropdownTimepicker" data-trigger-for="showDropdownTimepicker">This Year</a>
|
<a id="time-option-year" class="submenu-item hover:bg-gray-800 rounded p-1 text-right w-full text-gray-300 px-2 font-semibold text-sm" :href="intervalLink('year')" @click="state.showDropdownTimepicker = !state.showDropdownTimepicker" data-trigger-for="showDropdownTimepicker">This Year</a>
|
||||||
<a id="time-option-last_7_days" class="submenu-item hover:bg-gray-800 rounded p-1 text-right w-full text-gray-300 px-2 font-semibold text-sm" :href="intervalLink('last_7_days')" @click="state.showDropdownTimepicker = !state.showDropdownTimepicker" data-trigger-for="showDropdownTimepicker">Past 7 Days</a>
|
<a id="time-option-last_7_days" class="submenu-item hover:bg-gray-800 rounded p-1 text-right w-full text-gray-300 px-2 font-semibold text-sm" :href="intervalLink('last_7_days')" @click="state.showDropdownTimepicker = !state.showDropdownTimepicker" data-trigger-for="showDropdownTimepicker">Past 7 Days</a>
|
||||||
<a id="time-option-last_30_days" class="submenu-item hover:bg-gray-800 rounded p-1 text-right w-full text-gray-300 px-2 font-semibold text-sm" :href="intervalLink('last_30_days')" @click="state.showDropdownTimepicker = !state.showDropdownTimepicker" data-trigger-for="showDropdownTimepicker">Past 30 Days</a>
|
<a id="time-option-last_30_days" class="submenu-item hover:bg-gray-800 rounded p-1 text-right w-full text-gray-300 px-2 font-semibold text-sm" :href="intervalLink('last_30_days')" @click="state.showDropdownTimepicker = !state.showDropdownTimepicker" data-trigger-for="showDropdownTimepicker">Past 30 Days</a>
|
||||||
|
<a id="time-option-last_6_months" class="submenu-item hover:bg-gray-800 rounded p-1 text-right w-full text-gray-300 px-2 font-semibold text-sm" :href="intervalLink('last_6_months')" @click="state.showDropdownTimepicker = !state.showDropdownTimepicker" data-trigger-for="showDropdownTimepicker">Past 6 Months</a>
|
||||||
<a id="time-option-last_12_months" class="submenu-item hover:bg-gray-800 rounded p-1 text-right w-full text-gray-300 px-2 font-semibold text-sm" :href="intervalLink('last_12_months')" @click="state.showDropdownTimepicker = !state.showDropdownTimepicker" data-trigger-for="showDropdownTimepicker">Past 12 Months</a>
|
<a id="time-option-last_12_months" class="submenu-item hover:bg-gray-800 rounded p-1 text-right w-full text-gray-300 px-2 font-semibold text-sm" :href="intervalLink('last_12_months')" @click="state.showDropdownTimepicker = !state.showDropdownTimepicker" data-trigger-for="showDropdownTimepicker">Past 12 Months</a>
|
||||||
<a id="time-option-any" class="submenu-item hover:bg-gray-800 rounded p-1 text-right w-full text-gray-300 px-2 font-semibold text-sm" :href="intervalLink('any')" @click="state.showDropdownTimepicker = !state.showDropdownTimepicker" data-trigger-for="showDropdownTimepicker">All Time</a>
|
<a id="time-option-any" class="submenu-item hover:bg-gray-800 rounded p-1 text-right w-full text-gray-300 px-2 font-semibold text-sm" :href="intervalLink('any')" @click="state.showDropdownTimepicker = !state.showDropdownTimepicker" data-trigger-for="showDropdownTimepicker">All Time</a>
|
||||||
<hr class="my-2">
|
<hr class="my-2">
|
||||||
|
Reference in New Issue
Block a user