Skip to content

Commit

Permalink
Add simple user interface (#12)
Browse files Browse the repository at this point in the history
* Add cors config

* add user interface (#5)

* Update README.md

* Add emoji

* chore: add vite react create-app in ui folder

* feat: add schedule list page

* fix: fixes Navbar logo link

* feat: add create schedule form page

* Api changes and improvements

* feat: add delete schedule action

* feat: add delete schedule confirmation dialog

* feat: add schedule detail page

* feat: add DialogActionTrigger component

* feat: update api url, add ScheduleCardList component

* feat: add seed button

* feat: update style, add animations

* feat: add pre-commit prettier format

* feat: format the entire project UI code using prettier

* feat: move new schedule form in a Dialog

* fix: action bar actions (wip)

* feat: add resume, pause, trigger schedule api integration

* Fixed issue with pause and resume

* Add build workflow

* Add support for build without web ui

* Add Test workflow

* Fix Less()

* Fix tests

* Add test for pause/resume

* feat: add schedule detail modal

* Generate version based on commit and branch

* Add release workflow

* Add build of ui in Dockerfile

* Fix tag

* Fix prefix of image

* Fix test

---------

Co-authored-by: Stefano Scafiti <stefano.scafiti96@gmail.com>

---------

Co-authored-by: Alessio Sferro <sferro.alessio@gmail.com>
  • Loading branch information
ostafen and alessiosferro authored Jan 9, 2025
1 parent 51ff915 commit 907fab1
Show file tree
Hide file tree
Showing 115 changed files with 9,408 additions and 537 deletions.
23 changes: 23 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Build

on:
push:
branches: [ main ]
pull_request:
branches: [ main, dev ]

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.23'

- name: Build
run: go build -v ./...
38 changes: 38 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Build and Push Docker Image

on:
push:
branches:
- release/v*.*.*

jobs:
build-and-push:
runs-on: ubuntu-latest

steps:
# Checkout the repository
- name: Checkout repository
uses: actions/checkout@v3

# Extract the version (v*.*.*) from the branch name
- name: Extract version
id: extract_version
run: echo "VERSION=${GITHUB_REF_NAME##*/}" >> $GITHUB_ENV

# Log in to GitHub Container Registry
- name: Log in to GHCR
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

# Build and Push the Docker Image
- name: Build and Push Docker Image
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: |
ghcr.io/${{ github.repository }}:${{ env.VERSION }}
ghcr.io/${{ github.repository }}:latest
24 changes: 24 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Test

on:
push:
branches:
- main
pull_request:
branches: [main, dev]

jobs:
run-tests:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.23'

- name: Run tests
run: go test ./... -v
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
vendor
node_modules
data/kronos.bolt
bin
*.sqlite
*.sqlite
5 changes: 5 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

cd UI
npx lint-staged
5 changes: 5 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/inspectionProfiles/Project_Default.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/jsLinters/eslint.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions .idea/kronos.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions .idea/prettier.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 13 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
FROM golang:1.19-buster AS build
FROM node:20 AS ui-builder

WORKDIR /app
COPY ui/package.json ui/package-lock.json ./

RUN npm install

COPY ui/ ./
RUN npm run build

FROM golang:1.23.4-bullseye AS build

ARG VERSION
ARG COMMIT
Expand All @@ -9,9 +19,10 @@ COPY go.mod ./
COPY go.sum ./
RUN go mod download
COPY . .
COPY --from=ui-builder /app/web ./webbuild/web
RUN go build -a -installsuffix cgo -ldflags "-w -s -X main.version=$VERSION -X main.commit=$COMMIT -X 'main.buildTime=$BUILD_TIME'" -buildvcs=false -o /kronos cmd/main.go

FROM gcr.io/distroless/base-debian10
WORKDIR /
COPY --from=build /kronos /kronos
ENTRYPOINT ["/kronos"]
ENTRYPOINT ["/kronos"]
21 changes: 18 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,18 +1,33 @@
ifeq ($(WEB), 1)
BUILD_UI = ui
else
BUILD_UI =
endif

BIN_FOLDER = ./bin
EXEC_NAME = kronos
TOOLS=./tools

BUILD_TIME := $(shell date)
BRANCH_NAME := $(shell git rev-parse --abbrev-ref HEAD)
COMMIT_SHORT := $(shell git rev-parse --short HEAD)
COMMIT := $(shell git rev-parse HEAD)
VERSION ?= latest
VERSION := $(shell git describe --tags --exact-match 2>/dev/null || echo $(BRANCH_NAME)-$(COMMIT_SHORT))

BUILD_TIME := $(shell date)

IMG_NAME ?= ghcr.io/ostafen/kronos
IMG_TAG ?= latest

build: vendor
$(BIN_FOLDER)/$(EXEC_NAME): vendor $(BUILD_UI)
@mkdir -p $(BIN_FOLDER)
go build -mod vendor -a -installsuffix cgo -ldflags '-w -s -X main.version=$(VERSION) -X main.commit=$(COMMIT) -X "main.buildTime=$(BUILD_TIME)"' -o $(BIN_FOLDER)/$(EXEC_NAME) cmd/main.go

.PHONY: ui

ui:
cd ui && npm run build && cd ..
cp -r ui/web webbuild

generate:
go generate ./...

Expand Down
98 changes: 37 additions & 61 deletions cmd/main.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package main

import (
"context"
"fmt"
"net/http"
"strings"
Expand All @@ -10,13 +9,13 @@ import (
"github.com/gorilla/mux"
"github.com/ostafen/kronos/internal/api"
"github.com/ostafen/kronos/internal/config"
"github.com/ostafen/kronos/internal/metrics"
"github.com/ostafen/kronos/internal/model"
"github.com/ostafen/kronos/internal/sched"
"github.com/ostafen/kronos/internal/service"
"github.com/ostafen/kronos/internal/store"
statichttp "github.com/ostafen/kronos/webbuild"

"github.com/prometheus/client_golang/prometheus/promhttp"

"github.com/rs/cors"
log "github.com/sirupsen/logrus"
)

Expand All @@ -42,9 +41,10 @@ func printLogo() {
fmt.Println("| |/ / '__/ _ \\| '_ \\ / _ \\/ __|")
fmt.Println("| <| | | (_) | | | | (_) \\__ \\")
fmt.Println("|_|\\_\\_| \\___/|_| |_|\\___/|___/")
fmt.Println()
fmt.Printf("Version: %s\n", getVersion())
fmt.Printf("Commit: %s\n", commit)
fmt.Printf("Build.Time: %s\n\n", buildTime)
fmt.Printf("Built At: %s\n\n", buildTime)
}

func main() {
Expand All @@ -62,57 +62,17 @@ func main() {
log.Fatal(err)
}

svc := service.NewScheduleService(store, service.NewNotificationService())
manager := sched.NewScheduleManager(svc.OnTick)

err = store.Iterate(func(sched *model.Schedule) error {
if sched.IsActive() {
log.Info("scheduling %s at %s", sched.ID, sched.NextTickAt())

manager.Schedule(sched.ID, sched.NextTickAt())
}
return nil
})
if err != nil {
log.Fatal(err)
}

awake := func(_ *model.Schedule) {
manager.WakeUp()
}

submitSchedule := func(s *model.Schedule) {
manager.Schedule(s.ID, s.FirstTick())
}

svc.OnScheduleRegistered(awake, submitSchedule)

svc.OnSchedulePaused(func(s *model.Schedule) {
manager.Remove(s.ID)
})

svc.OnScheduleResumed(awake, func(s *model.Schedule) {
manager.Schedule(s.ID, s.NextTickAt())
metrics.ResetScheduleFailures(s.ID)
})

svc.OnScheduleNotified(func(s *model.Schedule, code int) {
if code < 200 || code >= 300 {
metrics.IncScheduleFailures(s.ID)
} else {
metrics.ResetScheduleFailures(s.ID)
}
metrics.IncWebhookRequests(s.URL, code)
})

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

manager.Start(ctx)
svc := service.NewScheduleService(
store,
service.NewNotificationService(),
)
defer svc.Stop()

configureRouter(svc)

http.ListenAndServe(fmt.Sprintf(":%d", conf.Port), nil)

// TODO: soft shutdown
}

func setupLogging(config config.Log) {
Expand Down Expand Up @@ -149,19 +109,35 @@ func getFormatter(format string) log.Formatter {

func configureRouter(svc service.ScheduleService) {
r := mux.NewRouter()
fs := http.FileServer(http.FS(statichttp.Static))
r.PathPrefix("/web").Handler(http.StripPrefix("/", fs))

scheduleApi := api.NewScheduleApi(svc)
handler := api.NewScheduleApiHandler(svc)

r.Handle("/metrics", promhttp.Handler()).Methods("GET")

r.HandleFunc("/schedules", scheduleApi.ListSchedules).Methods("GET")
r.HandleFunc("/schedules/{id}", scheduleApi.GetSchedule).Methods("GET")
r.HandleFunc("/schedules/{id}", scheduleApi.DeleteSchedule).Methods("DELETE")
// TODO: health endpoint

r.HandleFunc("/api/v1/schedules", handler.ListSchedules).Methods("GET")
r.HandleFunc("/api/v1/schedules/{id}", handler.GetSchedule).Methods("GET")
r.HandleFunc("/api/v1/schedules/{id}", handler.DeleteSchedule).Methods("DELETE")

r.HandleFunc("/api/v1/schedules", handler.RegisterSchedule).Methods("POST")
r.HandleFunc("/api/v1/schedules/{id}/pause", handler.PauseSchedule).Methods("POST")
r.HandleFunc("/api/v1/schedules/{id}/resume", handler.ResumeSchedule).Methods("POST")
r.HandleFunc("/api/v1/schedules/{id}/trigger", handler.TriggerSchedule).Methods("POST")

r.HandleFunc("/schedules", scheduleApi.RegisterSchedule).Methods("POST")
r.HandleFunc("/schedules/{id}/pause", scheduleApi.PauseSchedule).Methods("POST")
r.HandleFunc("/schedules/{id}/resume", scheduleApi.ResumeSchedule).Methods("POST")
r.HandleFunc("/schedules/{id}/trigger", scheduleApi.TriggerSchedule).Methods("POST")
r.HandleFunc("/api/v1/history", handler.GetHistory).Methods("GET")
r.HandleFunc("/api/v1/history/{id}", handler.GetCronHistory).Methods("GET")

http.Handle("/", withCors(r, cors.Options{
AllowedOrigins: []string{"*"},
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE"},
AllowedHeaders: []string{"Content-Type", "Authorization"},
}))
}

http.Handle("/", r)
func withCors(handler http.Handler, opts cors.Options) http.Handler {
c := cors.New(opts)
return c.Handler(handler)
}
10 changes: 10 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
services:
kronos:
image: ghcr.io/ostafen/kronos
ports:
- '9175:9175'
environment:
- PORT=9175 # configuration properties can be overridden through environment variables
- STORE_PATH=/data/kronos.bolt
volumes:
- ./data:/data
Loading

0 comments on commit 907fab1

Please sign in to comment.