mirror of
https://github.com/mentos1386/zdravko.git
synced 2025-03-29 20:37:55 +00:00
feat(workers): initial worker authentication and provisioning
This commit is contained in:
parent
2b6e1ca09b
commit
478232bcda
29 changed files with 916 additions and 103 deletions
.github/workflows
.gitignoreREADME.mdbuild
cmd/zdravko
go.modgo.suminternal
justfilepkg
tools/generate
web/templates/pages
zdravko.yaml
8
.github/workflows/deploy.yaml
vendored
Normal file
8
.github/workflows/deploy.yaml
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
name: Deploy
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -5,5 +5,8 @@ zdravko.db
|
||||||
temporal.db
|
temporal.db
|
||||||
temporal.db-journal
|
temporal.db-journal
|
||||||
|
|
||||||
|
# Keys
|
||||||
|
*.pem
|
||||||
|
|
||||||
# Config
|
# Config
|
||||||
.env
|
.env
|
||||||
|
|
|
@ -29,6 +29,9 @@ Demo is available at https://zdravko.fly.dev.
|
||||||
# Configure
|
# Configure
|
||||||
cp example.env .env
|
cp example.env .env
|
||||||
|
|
||||||
|
# Generate JWT key
|
||||||
|
just generate-jwt-key
|
||||||
|
|
||||||
# Start development environment
|
# Start development environment
|
||||||
just run
|
just run
|
||||||
```
|
```
|
||||||
|
|
|
@ -14,9 +14,6 @@ COPY . ./
|
||||||
# Build
|
# Build
|
||||||
RUN CGO_ENABLED=1 GOOS=linux go build -o /bin/zdravko cmd/zdravko/main.go
|
RUN CGO_ENABLED=1 GOOS=linux go build -o /bin/zdravko cmd/zdravko/main.go
|
||||||
|
|
||||||
# Prepare the data directory
|
|
||||||
RUN mkdir -p /data
|
|
||||||
|
|
||||||
###
|
###
|
||||||
# Final production
|
# Final production
|
||||||
FROM gcr.io/distroless/base-debian12:latest as production
|
FROM gcr.io/distroless/base-debian12:latest as production
|
||||||
|
@ -34,7 +31,6 @@ EXPOSE 7233
|
||||||
|
|
||||||
# Volume to persist sqlite databases
|
# Volume to persist sqlite databases
|
||||||
VOLUME /data
|
VOLUME /data
|
||||||
COPY --from=builder --chown=nonroot:nonroot /data /data
|
|
||||||
|
|
||||||
ENV DATABASE_PATH=/data/zdravko.db
|
ENV DATABASE_PATH=/data/zdravko.db
|
||||||
ENV TEMPORAL_DATABASE_PATH=/data/temporal.db
|
ENV TEMPORAL_DATABASE_PATH=/data/temporal.db
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"sync"
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"code.tjo.space/mentos1386/zdravko/internal/config"
|
"code.tjo.space/mentos1386/zdravko/internal/config"
|
||||||
"code.tjo.space/mentos1386/zdravko/pkg/server"
|
"code.tjo.space/mentos1386/zdravko/pkg/server"
|
||||||
|
@ -85,7 +86,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
c := make(chan os.Signal, 1)
|
c := make(chan os.Signal, 1)
|
||||||
signal.Notify(c, os.Interrupt)
|
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
|
||||||
go func() {
|
go func() {
|
||||||
for sig := range c {
|
for sig := range c {
|
||||||
log.Printf("Received signal: %v", sig)
|
log.Printf("Received signal: %v", sig)
|
||||||
|
@ -97,7 +98,7 @@ func main() {
|
||||||
println("Stopping", srv.Name())
|
println("Stopping", srv.Name())
|
||||||
err := srv.Stop()
|
err := srv.Stop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Unable to stop server %s: %v", srv.Name(), err)
|
log.Printf("Unable to stop server %s: %v", srv.Name(), err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
9
go.mod
9
go.mod
|
@ -3,8 +3,13 @@ module code.tjo.space/mentos1386/zdravko
|
||||||
go 1.21.6
|
go 1.21.6
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/go-playground/validator/v10 v10.18.0
|
||||||
|
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.0
|
||||||
github.com/gorilla/mux v1.8.1
|
github.com/gorilla/mux v1.8.1
|
||||||
github.com/gorilla/sessions v1.2.2
|
github.com/gorilla/sessions v1.2.2
|
||||||
|
github.com/gosimple/slug v1.13.1
|
||||||
|
github.com/spf13/viper v1.18.2
|
||||||
github.com/temporalio/ui-server/v2 v2.23.0
|
github.com/temporalio/ui-server/v2 v2.23.0
|
||||||
go.temporal.io/sdk v1.26.0-rc.2
|
go.temporal.io/sdk v1.26.0-rc.2
|
||||||
go.temporal.io/server v1.22.4
|
go.temporal.io/server v1.22.4
|
||||||
|
@ -45,11 +50,9 @@ require (
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.18.0 // indirect
|
|
||||||
github.com/go-sql-driver/mysql v1.7.1 // indirect
|
github.com/go-sql-driver/mysql v1.7.1 // indirect
|
||||||
github.com/gocql/gocql v1.5.2 // indirect
|
github.com/gocql/gocql v1.5.2 // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
github.com/golang/mock v1.7.0-rc.1 // indirect
|
github.com/golang/mock v1.7.0-rc.1 // indirect
|
||||||
|
@ -60,7 +63,6 @@ require (
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||||
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
|
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
|
||||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||||
github.com/gosimple/slug v1.13.1 // indirect
|
|
||||||
github.com/gosimple/unidecode v1.0.1 // indirect
|
github.com/gosimple/unidecode v1.0.1 // indirect
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
|
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect
|
||||||
|
@ -109,7 +111,6 @@ require (
|
||||||
github.com/spf13/afero v1.11.0 // indirect
|
github.com/spf13/afero v1.11.0 // indirect
|
||||||
github.com/spf13/cast v1.6.0 // indirect
|
github.com/spf13/cast v1.6.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/spf13/viper v1.18.2 // indirect
|
|
||||||
github.com/stretchr/objx v0.5.0 // indirect
|
github.com/stretchr/objx v0.5.0 // indirect
|
||||||
github.com/stretchr/testify v1.8.4 // indirect
|
github.com/stretchr/testify v1.8.4 // indirect
|
||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
|
|
8
go.sum
8
go.sum
|
@ -63,7 +63,6 @@ github.com/coreos/go-oidc/v3 v3.1.0/go.mod h1:rEJ/idjfUyfkBit1eI1fvyr+64/g9dcKpA
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/crossdock/crossdock-go v0.0.0-20160816171116-049aabb0122b/go.mod h1:v9FBN7gdVTpiD/+LZ7Po0UKvROyT87uLVxTHVky/dlQ=
|
github.com/crossdock/crossdock-go v0.0.0-20160816171116-049aabb0122b/go.mod h1:v9FBN7gdVTpiD/+LZ7Po0UKvROyT87uLVxTHVky/dlQ=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
@ -87,6 +86,8 @@ github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw
|
||||||
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||||
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
|
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
|
||||||
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
||||||
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||||
|
@ -105,6 +106,8 @@ github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
|
||||||
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
@ -126,6 +129,8 @@ github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keL
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
|
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
|
||||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||||
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
|
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
|
||||||
|
@ -302,7 +307,6 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
@ -16,12 +15,17 @@ type Config struct {
|
||||||
DatabasePath string `validate:"required"`
|
DatabasePath string `validate:"required"`
|
||||||
SessionSecret string `validate:"required"`
|
SessionSecret string `validate:"required"`
|
||||||
|
|
||||||
|
Jwt Jwt `validate:"required"`
|
||||||
OAuth2 OAuth2 `validate:"required"`
|
OAuth2 OAuth2 `validate:"required"`
|
||||||
|
|
||||||
Temporal Temporal `validate:"required"`
|
Temporal Temporal `validate:"required"`
|
||||||
|
|
||||||
HealthChecks []Healthcheck
|
Worker Worker `validate:"required"`
|
||||||
CronJobs []CronJob
|
}
|
||||||
|
|
||||||
|
type Jwt struct {
|
||||||
|
PrivateKey string `validate:"required"`
|
||||||
|
PublicKey string `validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OAuth2 struct {
|
type OAuth2 struct {
|
||||||
|
@ -41,30 +45,8 @@ type Temporal struct {
|
||||||
ServerHost string `validate:"required"`
|
ServerHost string `validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type HealthCheckHTTP struct {
|
type Worker struct {
|
||||||
URL string `validate:"required,url"`
|
Token string `validate:"required"`
|
||||||
Method string `validate:"required,oneof=GET POST PUT"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type HealthCheckTCP struct {
|
|
||||||
Host string `validate:"required,hostname"`
|
|
||||||
Port int `validate:"required,gte=1,lte=65535"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Healthcheck struct {
|
|
||||||
Name string `validate:"required"`
|
|
||||||
Retries int `validate:"optional,gte=0"`
|
|
||||||
Schedule string `validate:"required,cron"`
|
|
||||||
Timeout time.Duration `validate:"required"`
|
|
||||||
|
|
||||||
HTTP HealthCheckHTTP `validate:"required"`
|
|
||||||
TCP HealthCheckTCP `validate:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CronJob struct {
|
|
||||||
Name string `validate:"required"`
|
|
||||||
Schedule string `validate:"required,cron"`
|
|
||||||
Buffer time.Duration `validate:"required"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetEnvOrDefault(key, def string) string {
|
func GetEnvOrDefault(key, def string) string {
|
||||||
|
@ -93,6 +75,8 @@ func NewConfig() *Config {
|
||||||
viper.SetDefault("temporal.listenaddress", GetEnvOrDefault("TEMPORAL_LISTEN_ADDRESS", "0.0.0.0"))
|
viper.SetDefault("temporal.listenaddress", GetEnvOrDefault("TEMPORAL_LISTEN_ADDRESS", "0.0.0.0"))
|
||||||
viper.SetDefault("temporal.uihost", GetEnvOrDefault("TEMPORAL_UI_HOST", "127.0.0.1:8223"))
|
viper.SetDefault("temporal.uihost", GetEnvOrDefault("TEMPORAL_UI_HOST", "127.0.0.1:8223"))
|
||||||
viper.SetDefault("temporal.serverhost", GetEnvOrDefault("TEMPORAL_SERVER_HOST", "127.0.0.1:7233"))
|
viper.SetDefault("temporal.serverhost", GetEnvOrDefault("TEMPORAL_SERVER_HOST", "127.0.0.1:7233"))
|
||||||
|
viper.SetDefault("jwt.privatekey", os.Getenv("JWT_PRIVATE_KEY"))
|
||||||
|
viper.SetDefault("jwt.publickey", os.Getenv("JWT_PUBLIC_KEY"))
|
||||||
viper.SetDefault("oauth2.clientid", os.Getenv("OAUTH2_CLIENT_ID"))
|
viper.SetDefault("oauth2.clientid", os.Getenv("OAUTH2_CLIENT_ID"))
|
||||||
viper.SetDefault("oauth2.clientsecret", os.Getenv("OAUTH2_CLIENT_SECRET"))
|
viper.SetDefault("oauth2.clientsecret", os.Getenv("OAUTH2_CLIENT_SECRET"))
|
||||||
viper.SetDefault("oauth2.scopes", GetEnvOrDefault("OAUTH2_ENDPOINT_SCOPES", "openid profile email"))
|
viper.SetDefault("oauth2.scopes", GetEnvOrDefault("OAUTH2_ENDPOINT_SCOPES", "openid profile email"))
|
||||||
|
@ -100,6 +84,7 @@ func NewConfig() *Config {
|
||||||
viper.SetDefault("oauth2.endpointauthurl", os.Getenv("OAUTH2_ENDPOINT_AUTH_URL"))
|
viper.SetDefault("oauth2.endpointauthurl", os.Getenv("OAUTH2_ENDPOINT_AUTH_URL"))
|
||||||
viper.SetDefault("oauth2.endpointuserinfourl", os.Getenv("OAUTH2_ENDPOINT_USER_INFO_URL"))
|
viper.SetDefault("oauth2.endpointuserinfourl", os.Getenv("OAUTH2_ENDPOINT_USER_INFO_URL"))
|
||||||
viper.SetDefault("oauth2.endpointlogouturl", GetEnvOrDefault("OAUTH2_ENDPOINT_LOGOUT_URL", ""))
|
viper.SetDefault("oauth2.endpointlogouturl", GetEnvOrDefault("OAUTH2_ENDPOINT_LOGOUT_URL", ""))
|
||||||
|
viper.SetDefault("worker.token", os.Getenv("WORKER_TOKEN"))
|
||||||
|
|
||||||
err := viper.ReadInConfig()
|
err := viper.ReadInConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -15,6 +15,7 @@ func ConnectToDatabase(path string) (*gorm.DB, *query.Query, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
err = db.AutoMigrate(
|
err = db.AutoMigrate(
|
||||||
|
models.Worker{},
|
||||||
models.HealthcheckHttp{},
|
models.HealthcheckHttp{},
|
||||||
models.HealthcheckHttpHistory{},
|
models.HealthcheckHttpHistory{},
|
||||||
models.HealthcheckTcp{},
|
models.HealthcheckTcp{},
|
||||||
|
|
|
@ -35,6 +35,7 @@ var SettingsPages = []*components.Page{
|
||||||
{Path: "/settings/healthchecks/create", Title: "Healthchecks Create", Breadcrumb: "Create"},
|
{Path: "/settings/healthchecks/create", Title: "Healthchecks Create", Breadcrumb: "Create"},
|
||||||
{Path: "/settings/cronjobs", Title: "Cronjobs", Breadcrumb: "Cronjobs"},
|
{Path: "/settings/cronjobs", Title: "Cronjobs", Breadcrumb: "Cronjobs"},
|
||||||
{Path: "/settings/workers", Title: "Workers", Breadcrumb: "Workers"},
|
{Path: "/settings/workers", Title: "Workers", Breadcrumb: "Workers"},
|
||||||
|
{Path: "/settings/workers/create", Title: "Workers Create", Breadcrumb: "Create"},
|
||||||
{Path: "/temporal", Title: "Temporal", Breadcrumb: "Temporal"},
|
{Path: "/temporal", Title: "Temporal", Breadcrumb: "Temporal"},
|
||||||
{Path: "/oauth2/logout", Title: "Logout", Breadcrumb: "Logout"},
|
{Path: "/oauth2/logout", Title: "Logout", Breadcrumb: "Logout"},
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"code.tjo.space/mentos1386/zdravko/internal/services"
|
"code.tjo.space/mentos1386/zdravko/internal/services"
|
||||||
"code.tjo.space/mentos1386/zdravko/web/templates"
|
"code.tjo.space/mentos1386/zdravko/web/templates"
|
||||||
"code.tjo.space/mentos1386/zdravko/web/templates/components"
|
"code.tjo.space/mentos1386/zdravko/web/templates/components"
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/gosimple/slug"
|
"github.com/gosimple/slug"
|
||||||
)
|
)
|
||||||
|
@ -130,7 +131,12 @@ func (h *BaseHandler) SettingsHealthchecksCreatePOST(w http.ResponseWriter, r *h
|
||||||
Method: r.FormValue("method"),
|
Method: r.FormValue("method"),
|
||||||
}
|
}
|
||||||
|
|
||||||
err := services.CreateHealthcheckHttp(
|
err := validator.New(validator.WithRequiredStructEnabled()).Struct(healthcheckHttp)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = services.CreateHealthcheckHttp(
|
||||||
ctx,
|
ctx,
|
||||||
h.db,
|
h.db,
|
||||||
healthcheckHttp,
|
healthcheckHttp,
|
||||||
|
|
164
internal/handlers/settingsworkers.go
Normal file
164
internal/handlers/settingsworkers.go
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"code.tjo.space/mentos1386/zdravko/internal/jwt"
|
||||||
|
"code.tjo.space/mentos1386/zdravko/internal/models"
|
||||||
|
"code.tjo.space/mentos1386/zdravko/internal/services"
|
||||||
|
"code.tjo.space/mentos1386/zdravko/web/templates"
|
||||||
|
"code.tjo.space/mentos1386/zdravko/web/templates/components"
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/gosimple/slug"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SettingsWorkers struct {
|
||||||
|
*Settings
|
||||||
|
Workers []*models.Worker
|
||||||
|
WorkersLength int
|
||||||
|
}
|
||||||
|
|
||||||
|
type SettingsWorker struct {
|
||||||
|
*Settings
|
||||||
|
Worker *models.Worker
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *BaseHandler) SettingsWorkersGET(w http.ResponseWriter, r *http.Request, user *AuthenticatedUser) {
|
||||||
|
ts, err := template.ParseFS(templates.Templates,
|
||||||
|
"components/base.tmpl",
|
||||||
|
"components/settings.tmpl",
|
||||||
|
"pages/settings_workers.tmpl",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
workers, err := h.query.Worker.WithContext(context.Background()).Find()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ts.ExecuteTemplate(w, "base", &SettingsWorkers{
|
||||||
|
Settings: NewSettings(
|
||||||
|
user,
|
||||||
|
GetPageByTitle(SettingsPages, "Workers"),
|
||||||
|
[]*components.Page{GetPageByTitle(SettingsPages, "Workers")},
|
||||||
|
),
|
||||||
|
Workers: workers,
|
||||||
|
WorkersLength: len(workers),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *BaseHandler) SettingsWorkersDescribeGET(w http.ResponseWriter, r *http.Request, user *AuthenticatedUser) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
slug := vars["slug"]
|
||||||
|
|
||||||
|
ts, err := template.ParseFS(templates.Templates,
|
||||||
|
"components/base.tmpl",
|
||||||
|
"components/settings.tmpl",
|
||||||
|
"pages/settings_workers_describe.tmpl",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
worker, err := services.GetWorker(context.Background(), h.query, slug)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ts.ExecuteTemplate(w, "base", &SettingsWorker{
|
||||||
|
Settings: NewSettings(
|
||||||
|
user,
|
||||||
|
GetPageByTitle(SettingsPages, "Workers"),
|
||||||
|
[]*components.Page{
|
||||||
|
GetPageByTitle(SettingsPages, "Workers"),
|
||||||
|
{
|
||||||
|
Path: fmt.Sprintf("/settings/workers/%s", slug),
|
||||||
|
Title: "Describe",
|
||||||
|
Breadcrumb: worker.Name,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
Worker: worker,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *BaseHandler) SettingsWorkersCreateGET(w http.ResponseWriter, r *http.Request, user *AuthenticatedUser) {
|
||||||
|
ts, err := template.ParseFS(templates.Templates,
|
||||||
|
"components/base.tmpl",
|
||||||
|
"components/settings.tmpl",
|
||||||
|
"pages/settings_workers_create.tmpl",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ts.ExecuteTemplate(w, "base", NewSettings(
|
||||||
|
user,
|
||||||
|
GetPageByTitle(SettingsPages, "Workers"),
|
||||||
|
[]*components.Page{
|
||||||
|
GetPageByTitle(SettingsPages, "Workers"),
|
||||||
|
GetPageByTitle(SettingsPages, "Workers Create"),
|
||||||
|
},
|
||||||
|
))
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *BaseHandler) SettingsWorkersCreatePOST(w http.ResponseWriter, r *http.Request, user *AuthenticatedUser) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
worker := &models.Worker{
|
||||||
|
Name: r.FormValue("name"),
|
||||||
|
Slug: slug.Make(r.FormValue("name")),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := validator.New(validator.WithRequiredStructEnabled()).Struct(worker)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = services.CreateWorker(
|
||||||
|
ctx,
|
||||||
|
h.db,
|
||||||
|
worker,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Redirect(w, r, "/settings/workers", http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *BaseHandler) SettingsWorkersTokenGET(w http.ResponseWriter, r *http.Request, user *AuthenticatedUser) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
slug := vars["slug"]
|
||||||
|
|
||||||
|
worker, err := services.GetWorker(context.Background(), h.query, slug)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow write access to default namespace
|
||||||
|
token, err := jwt.NewToken(h.config, []string{"default:write"}, worker.Slug)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
_, _ = w.Write([]byte(`{"token": "` + token + `"}`))
|
||||||
|
}
|
64
internal/jwt/jwt.go
Normal file
64
internal/jwt/jwt.go
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
package jwt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"code.tjo.space/mentos1386/zdravko/internal/config"
|
||||||
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
func JwtPublicKeyID(key *rsa.PublicKey) string {
|
||||||
|
hash := sha256.Sum256(key.N.Bytes())
|
||||||
|
return hex.EncodeToString(hash[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func JwtPrivateKey(c *config.Config) (*rsa.PrivateKey, error) {
|
||||||
|
return jwt.ParseRSAPrivateKeyFromPEM([]byte(c.Jwt.PrivateKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
func JwtPublicKey(c *config.Config) (*rsa.PublicKey, error) {
|
||||||
|
return jwt.ParseRSAPublicKeyFromPEM([]byte(c.Jwt.PublicKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ref: https://docs.temporal.io/self-hosted-guide/security#authorization
|
||||||
|
func NewToken(config *config.Config, permissions []string, subject string) (string, error) {
|
||||||
|
privateKey, err := JwtPrivateKey(config)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
publicKey, err := JwtPublicKey(config)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
type WorkerClaims struct {
|
||||||
|
jwt.RegisteredClaims
|
||||||
|
Permissions []string `json:"permissions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create claims with multiple fields populated
|
||||||
|
claims := WorkerClaims{
|
||||||
|
jwt.RegisteredClaims{
|
||||||
|
ExpiresAt: jwt.NewNumericDate(time.Now().Add(12 * 30 * 24 * time.Hour)),
|
||||||
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||||
|
NotBefore: jwt.NewNumericDate(time.Now()),
|
||||||
|
Issuer: "zdravko",
|
||||||
|
Subject: subject,
|
||||||
|
},
|
||||||
|
permissions,
|
||||||
|
}
|
||||||
|
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
|
||||||
|
token.Header["kid"] = JwtPublicKeyID(publicKey)
|
||||||
|
|
||||||
|
signedToken, err := token.SignedString(privateKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return signedToken, nil
|
||||||
|
}
|
|
@ -11,27 +11,34 @@ type OAuth2State struct {
|
||||||
Expiry time.Time
|
Expiry time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Worker struct {
|
||||||
|
gorm.Model
|
||||||
|
Name string `gorm:"unique" validate:"required"`
|
||||||
|
Slug string `gorm:"unique"`
|
||||||
|
Status string
|
||||||
|
}
|
||||||
|
|
||||||
type Healthcheck struct {
|
type Healthcheck struct {
|
||||||
gorm.Model
|
gorm.Model
|
||||||
Slug string `gorm:"unique"`
|
Slug string `gorm:"unique"`
|
||||||
Name string `gorm:"unique"`
|
Name string `gorm:"unique" validate:"required"`
|
||||||
Status string // UP, DOWN
|
Status string // UP, DOWN
|
||||||
UptimePercentage float64
|
UptimePercentage float64
|
||||||
Schedule string
|
Schedule string `validate:"required,cron"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type HealthcheckHttp struct {
|
type HealthcheckHttp struct {
|
||||||
gorm.Model
|
gorm.Model
|
||||||
Healthcheck
|
Healthcheck
|
||||||
Url string
|
Url string `validate:"required,url"`
|
||||||
Method string
|
Method string `validate:"required,oneof=GET POST"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type HealthcheckTcp struct {
|
type HealthcheckTcp struct {
|
||||||
gorm.Model
|
gorm.Model
|
||||||
Healthcheck
|
Healthcheck
|
||||||
Hostname string
|
Hostname string `validate:"required,hostname"`
|
||||||
Port int
|
Port int `validate:"required,gte=1,lte=65535"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Cronjob struct {
|
type Cronjob struct {
|
||||||
|
|
|
@ -24,6 +24,7 @@ var (
|
||||||
HealthcheckTcp *healthcheckTcp
|
HealthcheckTcp *healthcheckTcp
|
||||||
HealthcheckTcpHistory *healthcheckTcpHistory
|
HealthcheckTcpHistory *healthcheckTcpHistory
|
||||||
OAuth2State *oAuth2State
|
OAuth2State *oAuth2State
|
||||||
|
Worker *worker
|
||||||
)
|
)
|
||||||
|
|
||||||
func SetDefault(db *gorm.DB, opts ...gen.DOOption) {
|
func SetDefault(db *gorm.DB, opts ...gen.DOOption) {
|
||||||
|
@ -35,6 +36,7 @@ func SetDefault(db *gorm.DB, opts ...gen.DOOption) {
|
||||||
HealthcheckTcp = &Q.HealthcheckTcp
|
HealthcheckTcp = &Q.HealthcheckTcp
|
||||||
HealthcheckTcpHistory = &Q.HealthcheckTcpHistory
|
HealthcheckTcpHistory = &Q.HealthcheckTcpHistory
|
||||||
OAuth2State = &Q.OAuth2State
|
OAuth2State = &Q.OAuth2State
|
||||||
|
Worker = &Q.Worker
|
||||||
}
|
}
|
||||||
|
|
||||||
func Use(db *gorm.DB, opts ...gen.DOOption) *Query {
|
func Use(db *gorm.DB, opts ...gen.DOOption) *Query {
|
||||||
|
@ -47,6 +49,7 @@ func Use(db *gorm.DB, opts ...gen.DOOption) *Query {
|
||||||
HealthcheckTcp: newHealthcheckTcp(db, opts...),
|
HealthcheckTcp: newHealthcheckTcp(db, opts...),
|
||||||
HealthcheckTcpHistory: newHealthcheckTcpHistory(db, opts...),
|
HealthcheckTcpHistory: newHealthcheckTcpHistory(db, opts...),
|
||||||
OAuth2State: newOAuth2State(db, opts...),
|
OAuth2State: newOAuth2State(db, opts...),
|
||||||
|
Worker: newWorker(db, opts...),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,6 +63,7 @@ type Query struct {
|
||||||
HealthcheckTcp healthcheckTcp
|
HealthcheckTcp healthcheckTcp
|
||||||
HealthcheckTcpHistory healthcheckTcpHistory
|
HealthcheckTcpHistory healthcheckTcpHistory
|
||||||
OAuth2State oAuth2State
|
OAuth2State oAuth2State
|
||||||
|
Worker worker
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Query) Available() bool { return q.db != nil }
|
func (q *Query) Available() bool { return q.db != nil }
|
||||||
|
@ -74,6 +78,7 @@ func (q *Query) clone(db *gorm.DB) *Query {
|
||||||
HealthcheckTcp: q.HealthcheckTcp.clone(db),
|
HealthcheckTcp: q.HealthcheckTcp.clone(db),
|
||||||
HealthcheckTcpHistory: q.HealthcheckTcpHistory.clone(db),
|
HealthcheckTcpHistory: q.HealthcheckTcpHistory.clone(db),
|
||||||
OAuth2State: q.OAuth2State.clone(db),
|
OAuth2State: q.OAuth2State.clone(db),
|
||||||
|
Worker: q.Worker.clone(db),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,6 +100,7 @@ func (q *Query) ReplaceDB(db *gorm.DB) *Query {
|
||||||
HealthcheckTcp: q.HealthcheckTcp.replaceDB(db),
|
HealthcheckTcp: q.HealthcheckTcp.replaceDB(db),
|
||||||
HealthcheckTcpHistory: q.HealthcheckTcpHistory.replaceDB(db),
|
HealthcheckTcpHistory: q.HealthcheckTcpHistory.replaceDB(db),
|
||||||
OAuth2State: q.OAuth2State.replaceDB(db),
|
OAuth2State: q.OAuth2State.replaceDB(db),
|
||||||
|
Worker: q.Worker.replaceDB(db),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,6 +112,7 @@ type queryCtx struct {
|
||||||
HealthcheckTcp IHealthcheckTcpDo
|
HealthcheckTcp IHealthcheckTcpDo
|
||||||
HealthcheckTcpHistory IHealthcheckTcpHistoryDo
|
HealthcheckTcpHistory IHealthcheckTcpHistoryDo
|
||||||
OAuth2State IOAuth2StateDo
|
OAuth2State IOAuth2StateDo
|
||||||
|
Worker IWorkerDo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Query) WithContext(ctx context.Context) *queryCtx {
|
func (q *Query) WithContext(ctx context.Context) *queryCtx {
|
||||||
|
@ -117,6 +124,7 @@ func (q *Query) WithContext(ctx context.Context) *queryCtx {
|
||||||
HealthcheckTcp: q.HealthcheckTcp.WithContext(ctx),
|
HealthcheckTcp: q.HealthcheckTcp.WithContext(ctx),
|
||||||
HealthcheckTcpHistory: q.HealthcheckTcpHistory.WithContext(ctx),
|
HealthcheckTcpHistory: q.HealthcheckTcpHistory.WithContext(ctx),
|
||||||
OAuth2State: q.OAuth2State.WithContext(ctx),
|
OAuth2State: q.OAuth2State.WithContext(ctx),
|
||||||
|
Worker: q.Worker.WithContext(ctx),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
412
internal/models/query/workers.gen.go
Normal file
412
internal/models/query/workers.gen.go
Normal file
|
@ -0,0 +1,412 @@
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package query
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
|
"gorm.io/gorm/schema"
|
||||||
|
|
||||||
|
"gorm.io/gen"
|
||||||
|
"gorm.io/gen/field"
|
||||||
|
|
||||||
|
"gorm.io/plugin/dbresolver"
|
||||||
|
|
||||||
|
"code.tjo.space/mentos1386/zdravko/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newWorker(db *gorm.DB, opts ...gen.DOOption) worker {
|
||||||
|
_worker := worker{}
|
||||||
|
|
||||||
|
_worker.workerDo.UseDB(db, opts...)
|
||||||
|
_worker.workerDo.UseModel(&models.Worker{})
|
||||||
|
|
||||||
|
tableName := _worker.workerDo.TableName()
|
||||||
|
_worker.ALL = field.NewAsterisk(tableName)
|
||||||
|
_worker.ID = field.NewUint(tableName, "id")
|
||||||
|
_worker.CreatedAt = field.NewTime(tableName, "created_at")
|
||||||
|
_worker.UpdatedAt = field.NewTime(tableName, "updated_at")
|
||||||
|
_worker.DeletedAt = field.NewField(tableName, "deleted_at")
|
||||||
|
_worker.Name = field.NewString(tableName, "name")
|
||||||
|
_worker.Slug = field.NewString(tableName, "slug")
|
||||||
|
_worker.Status = field.NewString(tableName, "status")
|
||||||
|
|
||||||
|
_worker.fillFieldMap()
|
||||||
|
|
||||||
|
return _worker
|
||||||
|
}
|
||||||
|
|
||||||
|
type worker struct {
|
||||||
|
workerDo workerDo
|
||||||
|
|
||||||
|
ALL field.Asterisk
|
||||||
|
ID field.Uint
|
||||||
|
CreatedAt field.Time
|
||||||
|
UpdatedAt field.Time
|
||||||
|
DeletedAt field.Field
|
||||||
|
Name field.String
|
||||||
|
Slug field.String
|
||||||
|
Status field.String
|
||||||
|
|
||||||
|
fieldMap map[string]field.Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w worker) Table(newTableName string) *worker {
|
||||||
|
w.workerDo.UseTable(newTableName)
|
||||||
|
return w.updateTableName(newTableName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w worker) As(alias string) *worker {
|
||||||
|
w.workerDo.DO = *(w.workerDo.As(alias).(*gen.DO))
|
||||||
|
return w.updateTableName(alias)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *worker) updateTableName(table string) *worker {
|
||||||
|
w.ALL = field.NewAsterisk(table)
|
||||||
|
w.ID = field.NewUint(table, "id")
|
||||||
|
w.CreatedAt = field.NewTime(table, "created_at")
|
||||||
|
w.UpdatedAt = field.NewTime(table, "updated_at")
|
||||||
|
w.DeletedAt = field.NewField(table, "deleted_at")
|
||||||
|
w.Name = field.NewString(table, "name")
|
||||||
|
w.Slug = field.NewString(table, "slug")
|
||||||
|
w.Status = field.NewString(table, "status")
|
||||||
|
|
||||||
|
w.fillFieldMap()
|
||||||
|
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *worker) WithContext(ctx context.Context) IWorkerDo { return w.workerDo.WithContext(ctx) }
|
||||||
|
|
||||||
|
func (w worker) TableName() string { return w.workerDo.TableName() }
|
||||||
|
|
||||||
|
func (w worker) Alias() string { return w.workerDo.Alias() }
|
||||||
|
|
||||||
|
func (w worker) Columns(cols ...field.Expr) gen.Columns { return w.workerDo.Columns(cols...) }
|
||||||
|
|
||||||
|
func (w *worker) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
|
||||||
|
_f, ok := w.fieldMap[fieldName]
|
||||||
|
if !ok || _f == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
_oe, ok := _f.(field.OrderExpr)
|
||||||
|
return _oe, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *worker) fillFieldMap() {
|
||||||
|
w.fieldMap = make(map[string]field.Expr, 7)
|
||||||
|
w.fieldMap["id"] = w.ID
|
||||||
|
w.fieldMap["created_at"] = w.CreatedAt
|
||||||
|
w.fieldMap["updated_at"] = w.UpdatedAt
|
||||||
|
w.fieldMap["deleted_at"] = w.DeletedAt
|
||||||
|
w.fieldMap["name"] = w.Name
|
||||||
|
w.fieldMap["slug"] = w.Slug
|
||||||
|
w.fieldMap["status"] = w.Status
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w worker) clone(db *gorm.DB) worker {
|
||||||
|
w.workerDo.ReplaceConnPool(db.Statement.ConnPool)
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w worker) replaceDB(db *gorm.DB) worker {
|
||||||
|
w.workerDo.ReplaceDB(db)
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
type workerDo struct{ gen.DO }
|
||||||
|
|
||||||
|
type IWorkerDo interface {
|
||||||
|
gen.SubQuery
|
||||||
|
Debug() IWorkerDo
|
||||||
|
WithContext(ctx context.Context) IWorkerDo
|
||||||
|
WithResult(fc func(tx gen.Dao)) gen.ResultInfo
|
||||||
|
ReplaceDB(db *gorm.DB)
|
||||||
|
ReadDB() IWorkerDo
|
||||||
|
WriteDB() IWorkerDo
|
||||||
|
As(alias string) gen.Dao
|
||||||
|
Session(config *gorm.Session) IWorkerDo
|
||||||
|
Columns(cols ...field.Expr) gen.Columns
|
||||||
|
Clauses(conds ...clause.Expression) IWorkerDo
|
||||||
|
Not(conds ...gen.Condition) IWorkerDo
|
||||||
|
Or(conds ...gen.Condition) IWorkerDo
|
||||||
|
Select(conds ...field.Expr) IWorkerDo
|
||||||
|
Where(conds ...gen.Condition) IWorkerDo
|
||||||
|
Order(conds ...field.Expr) IWorkerDo
|
||||||
|
Distinct(cols ...field.Expr) IWorkerDo
|
||||||
|
Omit(cols ...field.Expr) IWorkerDo
|
||||||
|
Join(table schema.Tabler, on ...field.Expr) IWorkerDo
|
||||||
|
LeftJoin(table schema.Tabler, on ...field.Expr) IWorkerDo
|
||||||
|
RightJoin(table schema.Tabler, on ...field.Expr) IWorkerDo
|
||||||
|
Group(cols ...field.Expr) IWorkerDo
|
||||||
|
Having(conds ...gen.Condition) IWorkerDo
|
||||||
|
Limit(limit int) IWorkerDo
|
||||||
|
Offset(offset int) IWorkerDo
|
||||||
|
Count() (count int64, err error)
|
||||||
|
Scopes(funcs ...func(gen.Dao) gen.Dao) IWorkerDo
|
||||||
|
Unscoped() IWorkerDo
|
||||||
|
Create(values ...*models.Worker) error
|
||||||
|
CreateInBatches(values []*models.Worker, batchSize int) error
|
||||||
|
Save(values ...*models.Worker) error
|
||||||
|
First() (*models.Worker, error)
|
||||||
|
Take() (*models.Worker, error)
|
||||||
|
Last() (*models.Worker, error)
|
||||||
|
Find() ([]*models.Worker, error)
|
||||||
|
FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*models.Worker, err error)
|
||||||
|
FindInBatches(result *[]*models.Worker, batchSize int, fc func(tx gen.Dao, batch int) error) error
|
||||||
|
Pluck(column field.Expr, dest interface{}) error
|
||||||
|
Delete(...*models.Worker) (info gen.ResultInfo, err error)
|
||||||
|
Update(column field.Expr, value interface{}) (info gen.ResultInfo, err error)
|
||||||
|
UpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)
|
||||||
|
Updates(value interface{}) (info gen.ResultInfo, err error)
|
||||||
|
UpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error)
|
||||||
|
UpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)
|
||||||
|
UpdateColumns(value interface{}) (info gen.ResultInfo, err error)
|
||||||
|
UpdateFrom(q gen.SubQuery) gen.Dao
|
||||||
|
Attrs(attrs ...field.AssignExpr) IWorkerDo
|
||||||
|
Assign(attrs ...field.AssignExpr) IWorkerDo
|
||||||
|
Joins(fields ...field.RelationField) IWorkerDo
|
||||||
|
Preload(fields ...field.RelationField) IWorkerDo
|
||||||
|
FirstOrInit() (*models.Worker, error)
|
||||||
|
FirstOrCreate() (*models.Worker, error)
|
||||||
|
FindByPage(offset int, limit int) (result []*models.Worker, count int64, err error)
|
||||||
|
ScanByPage(result interface{}, offset int, limit int) (count int64, err error)
|
||||||
|
Scan(result interface{}) (err error)
|
||||||
|
Returning(value interface{}, columns ...string) IWorkerDo
|
||||||
|
UnderlyingDB() *gorm.DB
|
||||||
|
schema.Tabler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w workerDo) Debug() IWorkerDo {
|
||||||
|
return w.withDO(w.DO.Debug())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w workerDo) WithContext(ctx context.Context) IWorkerDo {
|
||||||
|
return w.withDO(w.DO.WithContext(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w workerDo) ReadDB() IWorkerDo {
|
||||||
|
return w.Clauses(dbresolver.Read)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w workerDo) WriteDB() IWorkerDo {
|
||||||
|
return w.Clauses(dbresolver.Write)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w workerDo) Session(config *gorm.Session) IWorkerDo {
|
||||||
|
return w.withDO(w.DO.Session(config))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w workerDo) Clauses(conds ...clause.Expression) IWorkerDo {
|
||||||
|
return w.withDO(w.DO.Clauses(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w workerDo) Returning(value interface{}, columns ...string) IWorkerDo {
|
||||||
|
return w.withDO(w.DO.Returning(value, columns...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w workerDo) Not(conds ...gen.Condition) IWorkerDo {
|
||||||
|
return w.withDO(w.DO.Not(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w workerDo) Or(conds ...gen.Condition) IWorkerDo {
|
||||||
|
return w.withDO(w.DO.Or(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w workerDo) Select(conds ...field.Expr) IWorkerDo {
|
||||||
|
return w.withDO(w.DO.Select(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w workerDo) Where(conds ...gen.Condition) IWorkerDo {
|
||||||
|
return w.withDO(w.DO.Where(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w workerDo) Order(conds ...field.Expr) IWorkerDo {
|
||||||
|
return w.withDO(w.DO.Order(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w workerDo) Distinct(cols ...field.Expr) IWorkerDo {
|
||||||
|
return w.withDO(w.DO.Distinct(cols...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w workerDo) Omit(cols ...field.Expr) IWorkerDo {
|
||||||
|
return w.withDO(w.DO.Omit(cols...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w workerDo) Join(table schema.Tabler, on ...field.Expr) IWorkerDo {
|
||||||
|
return w.withDO(w.DO.Join(table, on...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w workerDo) LeftJoin(table schema.Tabler, on ...field.Expr) IWorkerDo {
|
||||||
|
return w.withDO(w.DO.LeftJoin(table, on...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w workerDo) RightJoin(table schema.Tabler, on ...field.Expr) IWorkerDo {
|
||||||
|
return w.withDO(w.DO.RightJoin(table, on...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w workerDo) Group(cols ...field.Expr) IWorkerDo {
|
||||||
|
return w.withDO(w.DO.Group(cols...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w workerDo) Having(conds ...gen.Condition) IWorkerDo {
|
||||||
|
return w.withDO(w.DO.Having(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w workerDo) Limit(limit int) IWorkerDo {
|
||||||
|
return w.withDO(w.DO.Limit(limit))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w workerDo) Offset(offset int) IWorkerDo {
|
||||||
|
return w.withDO(w.DO.Offset(offset))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w workerDo) Scopes(funcs ...func(gen.Dao) gen.Dao) IWorkerDo {
|
||||||
|
return w.withDO(w.DO.Scopes(funcs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w workerDo) Unscoped() IWorkerDo {
|
||||||
|
return w.withDO(w.DO.Unscoped())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w workerDo) Create(values ...*models.Worker) error {
|
||||||
|
if len(values) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return w.DO.Create(values)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w workerDo) CreateInBatches(values []*models.Worker, batchSize int) error {
|
||||||
|
return w.DO.CreateInBatches(values, batchSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save : !!! underlying implementation is different with GORM
|
||||||
|
// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values)
|
||||||
|
func (w workerDo) Save(values ...*models.Worker) error {
|
||||||
|
if len(values) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return w.DO.Save(values)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w workerDo) First() (*models.Worker, error) {
|
||||||
|
if result, err := w.DO.First(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*models.Worker), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w workerDo) Take() (*models.Worker, error) {
|
||||||
|
if result, err := w.DO.Take(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*models.Worker), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w workerDo) Last() (*models.Worker, error) {
|
||||||
|
if result, err := w.DO.Last(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*models.Worker), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w workerDo) Find() ([]*models.Worker, error) {
|
||||||
|
result, err := w.DO.Find()
|
||||||
|
return result.([]*models.Worker), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w workerDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*models.Worker, err error) {
|
||||||
|
buf := make([]*models.Worker, 0, batchSize)
|
||||||
|
err = w.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error {
|
||||||
|
defer func() { results = append(results, buf...) }()
|
||||||
|
return fc(tx, batch)
|
||||||
|
})
|
||||||
|
return results, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w workerDo) FindInBatches(result *[]*models.Worker, batchSize int, fc func(tx gen.Dao, batch int) error) error {
|
||||||
|
return w.DO.FindInBatches(result, batchSize, fc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w workerDo) Attrs(attrs ...field.AssignExpr) IWorkerDo {
|
||||||
|
return w.withDO(w.DO.Attrs(attrs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w workerDo) Assign(attrs ...field.AssignExpr) IWorkerDo {
|
||||||
|
return w.withDO(w.DO.Assign(attrs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w workerDo) Joins(fields ...field.RelationField) IWorkerDo {
|
||||||
|
for _, _f := range fields {
|
||||||
|
w = *w.withDO(w.DO.Joins(_f))
|
||||||
|
}
|
||||||
|
return &w
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w workerDo) Preload(fields ...field.RelationField) IWorkerDo {
|
||||||
|
for _, _f := range fields {
|
||||||
|
w = *w.withDO(w.DO.Preload(_f))
|
||||||
|
}
|
||||||
|
return &w
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w workerDo) FirstOrInit() (*models.Worker, error) {
|
||||||
|
if result, err := w.DO.FirstOrInit(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*models.Worker), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w workerDo) FirstOrCreate() (*models.Worker, error) {
|
||||||
|
if result, err := w.DO.FirstOrCreate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*models.Worker), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w workerDo) FindByPage(offset int, limit int) (result []*models.Worker, count int64, err error) {
|
||||||
|
result, err = w.Offset(offset).Limit(limit).Find()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if size := len(result); 0 < limit && 0 < size && size < limit {
|
||||||
|
count = int64(size + offset)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err = w.Offset(-1).Limit(-1).Count()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w workerDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) {
|
||||||
|
count, err = w.Count()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w.Offset(offset).Limit(limit).Scan(result)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w workerDo) Scan(result interface{}) (err error) {
|
||||||
|
return w.DO.Scan(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w workerDo) Delete(models ...*models.Worker) (result gen.ResultInfo, err error) {
|
||||||
|
return w.DO.Delete(models)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *workerDo) withDO(do gen.Dao) *workerDo {
|
||||||
|
w.DO = *do.(*gen.DO)
|
||||||
|
return w
|
||||||
|
}
|
21
internal/services/worker.go
Normal file
21
internal/services/worker.go
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"code.tjo.space/mentos1386/zdravko/internal/models"
|
||||||
|
"code.tjo.space/mentos1386/zdravko/internal/models/query"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CreateWorker(ctx context.Context, db *gorm.DB, worker *models.Worker) error {
|
||||||
|
return db.WithContext(ctx).Create(worker).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetWorker(ctx context.Context, q *query.Query, slug string) (*models.Worker, error) {
|
||||||
|
log.Println("GetWorker")
|
||||||
|
return q.Worker.WithContext(ctx).Where(
|
||||||
|
q.Worker.Slug.Eq(slug),
|
||||||
|
).First()
|
||||||
|
}
|
|
@ -1,18 +1,52 @@
|
||||||
package temporal
|
package temporal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.tjo.space/mentos1386/zdravko/internal/config"
|
"code.tjo.space/mentos1386/zdravko/internal/config"
|
||||||
|
"code.tjo.space/mentos1386/zdravko/internal/jwt"
|
||||||
"code.tjo.space/mentos1386/zdravko/pkg/retry"
|
"code.tjo.space/mentos1386/zdravko/pkg/retry"
|
||||||
"go.temporal.io/sdk/client"
|
"go.temporal.io/sdk/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ConnectToTemporal(cfg *config.Config) (client.Client, error) {
|
type AuthHeadersProvider struct {
|
||||||
|
Token string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *AuthHeadersProvider) GetHeaders(ctx context.Context) (map[string]string, error) {
|
||||||
|
return map[string]string{
|
||||||
|
"authorization": "Bearer " + p.Token,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConnectServerToTemporal(cfg *config.Config) (client.Client, error) {
|
||||||
|
// For server we generate new token with admin permissions
|
||||||
|
token, err := jwt.NewToken(cfg, []string{"temporal-system:admin", "default:admin"}, "server")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
provider := &AuthHeadersProvider{token}
|
||||||
|
|
||||||
// Try to connect to the Temporal Server
|
// Try to connect to the Temporal Server
|
||||||
return retry.Retry(5, 6*time.Second, func() (client.Client, error) {
|
return retry.Retry(5, 6*time.Second, func() (client.Client, error) {
|
||||||
return client.Dial(client.Options{
|
return client.Dial(client.Options{
|
||||||
HostPort: cfg.Temporal.ServerHost,
|
HostPort: cfg.Temporal.ServerHost,
|
||||||
|
HeadersProvider: provider,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConnectWorkerToTemporal(cfg *config.Config) (client.Client, error) {
|
||||||
|
provider := &AuthHeadersProvider{cfg.Worker.Token}
|
||||||
|
|
||||||
|
// Try to connect to the Temporal Server
|
||||||
|
return retry.Retry(5, 6*time.Second, func() (client.Client, error) {
|
||||||
|
return client.Dial(client.Options{
|
||||||
|
HostPort: cfg.Temporal.ServerHost,
|
||||||
|
HeadersProvider: provider,
|
||||||
|
Namespace: "default",
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
16
justfile
16
justfile
|
@ -3,6 +3,10 @@ set shell := ["devbox", "run"]
|
||||||
# Load dotenv
|
# Load dotenv
|
||||||
set dotenv-load
|
set dotenv-load
|
||||||
|
|
||||||
|
# Load public and private keys
|
||||||
|
export JWT_PRIVATE_KEY := `cat jwt.private.pem`
|
||||||
|
export JWT_PUBLIC_KEY := `cat jwt.public.pem`
|
||||||
|
|
||||||
GIT_SHA := `git rev-parse --short HEAD`
|
GIT_SHA := `git rev-parse --short HEAD`
|
||||||
DOCKER_IMAGE := "ghcr.io/mentos1386/zdravko:sha-"+GIT_SHA
|
DOCKER_IMAGE := "ghcr.io/mentos1386/zdravko:sha-"+GIT_SHA
|
||||||
STATIC_DIR := "./web/static"
|
STATIC_DIR := "./web/static"
|
||||||
|
@ -11,6 +15,7 @@ STATIC_DIR := "./web/static"
|
||||||
build:
|
build:
|
||||||
docker build -f build/Dockerfile -t {{DOCKER_IMAGE}} .
|
docker build -f build/Dockerfile -t {{DOCKER_IMAGE}} .
|
||||||
|
|
||||||
|
# Run Docker application.
|
||||||
run-docker:
|
run-docker:
|
||||||
docker run -p 8080:8080 \
|
docker run -p 8080:8080 \
|
||||||
-e SESSION_SECRET \
|
-e SESSION_SECRET \
|
||||||
|
@ -26,10 +31,19 @@ run-docker:
|
||||||
run:
|
run:
|
||||||
devbox services up
|
devbox services up
|
||||||
|
|
||||||
|
run-worker:
|
||||||
|
go build -o dist/zdravko cmd/zdravko/main.go
|
||||||
|
./dist/zdravko --worker=true --server=false --temporal=false
|
||||||
|
|
||||||
|
# Generates new jwt key pair
|
||||||
|
generate-jwt-key:
|
||||||
|
openssl genrsa -out jwt.private.pem 2048
|
||||||
|
openssl rsa -pubout -in jwt.private.pem -out jwt.public.pem
|
||||||
|
|
||||||
# Start zdravko
|
# Start zdravko
|
||||||
run-zdravko:
|
run-zdravko:
|
||||||
go build -o dist/zdravko cmd/zdravko/main.go
|
go build -o dist/zdravko cmd/zdravko/main.go
|
||||||
./dist/zdravko
|
./dist/zdravko --worker=false
|
||||||
|
|
||||||
# Deploy the application to fly.io
|
# Deploy the application to fly.io
|
||||||
deploy:
|
deploy:
|
||||||
|
|
|
@ -37,10 +37,11 @@ func (s *Server) Start() error {
|
||||||
}
|
}
|
||||||
log.Println("Connected to database")
|
log.Println("Connected to database")
|
||||||
|
|
||||||
temporalClient, err := temporal.ConnectToTemporal(s.cfg)
|
temporalClient, err := temporal.ConnectServerToTemporal(s.cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
log.Println("Connected to Temporal")
|
||||||
|
|
||||||
h := handlers.NewBaseHandler(db, query, temporalClient, s.cfg)
|
h := handlers.NewBaseHandler(db, query, temporalClient, s.cfg)
|
||||||
|
|
||||||
|
@ -71,6 +72,11 @@ func (s *Server) Start() error {
|
||||||
r.HandleFunc("/settings/healthchecks/create", h.Authenticated(h.SettingsHealthchecksCreateGET)).Methods("GET")
|
r.HandleFunc("/settings/healthchecks/create", h.Authenticated(h.SettingsHealthchecksCreateGET)).Methods("GET")
|
||||||
r.HandleFunc("/settings/healthchecks/create", h.Authenticated(h.SettingsHealthchecksCreatePOST)).Methods("POST")
|
r.HandleFunc("/settings/healthchecks/create", h.Authenticated(h.SettingsHealthchecksCreatePOST)).Methods("POST")
|
||||||
r.HandleFunc("/settings/healthchecks/{slug}", h.Authenticated(h.SettingsHealthchecksDescribeGET)).Methods("GET")
|
r.HandleFunc("/settings/healthchecks/{slug}", h.Authenticated(h.SettingsHealthchecksDescribeGET)).Methods("GET")
|
||||||
|
r.HandleFunc("/settings/workers", h.Authenticated(h.SettingsWorkersGET)).Methods("GET")
|
||||||
|
r.HandleFunc("/settings/workers/create", h.Authenticated(h.SettingsWorkersCreateGET)).Methods("GET")
|
||||||
|
r.HandleFunc("/settings/workers/create", h.Authenticated(h.SettingsWorkersCreatePOST)).Methods("POST")
|
||||||
|
r.HandleFunc("/settings/workers/{slug}", h.Authenticated(h.SettingsWorkersDescribeGET)).Methods("GET")
|
||||||
|
r.HandleFunc("/settings/workers/{slug}/token", h.Authenticated(h.SettingsWorkersTokenGET)).Methods("GET")
|
||||||
|
|
||||||
// OAuth2
|
// OAuth2
|
||||||
r.HandleFunc("/oauth2/login", h.OAuth2LoginGET).Methods("GET")
|
r.HandleFunc("/oauth2/login", h.OAuth2LoginGET).Methods("GET")
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
package temporal
|
package temporal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/rsa"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
internal "code.tjo.space/mentos1386/zdravko/internal/config"
|
internal "code.tjo.space/mentos1386/zdravko/internal/config"
|
||||||
|
"code.tjo.space/mentos1386/zdravko/internal/jwt"
|
||||||
"go.temporal.io/server/common/cluster"
|
"go.temporal.io/server/common/cluster"
|
||||||
"go.temporal.io/server/common/config"
|
"go.temporal.io/server/common/config"
|
||||||
"go.temporal.io/server/common/persistence/sql/sqlplugin/sqlite"
|
"go.temporal.io/server/common/persistence/sql/sqlplugin/sqlite"
|
||||||
|
@ -20,6 +23,29 @@ const HistoryPort = 7234
|
||||||
const MatchingPort = 7235
|
const MatchingPort = 7235
|
||||||
const WorkerPort = 7236
|
const WorkerPort = 7236
|
||||||
|
|
||||||
|
type TokenKeyProvider struct {
|
||||||
|
config *internal.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *TokenKeyProvider) SupportedMethods() []string {
|
||||||
|
return []string{"RS256", "RS384", "RS512"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *TokenKeyProvider) HmacKey(alg string, kid string) ([]byte, error) {
|
||||||
|
return nil, fmt.Errorf("HMAC key is not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *TokenKeyProvider) EcdsaKey(alg string, kid string) (*ecdsa.PublicKey, error) {
|
||||||
|
return nil, fmt.Errorf("ECDSA key is not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *TokenKeyProvider) RsaKey(alg string, kid string) (*rsa.PublicKey, error) {
|
||||||
|
return jwt.JwtPublicKey(p.config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *TokenKeyProvider) Close() {
|
||||||
|
}
|
||||||
|
|
||||||
func NewServerConfig(cfg *internal.Config) *config.Config {
|
func NewServerConfig(cfg *internal.Config) *config.Config {
|
||||||
return &config.Config{
|
return &config.Config{
|
||||||
Persistence: config.Persistence{
|
Persistence: config.Persistence{
|
||||||
|
@ -42,6 +68,7 @@ func NewServerConfig(cfg *internal.Config) *config.Config {
|
||||||
MaxJoinDuration: 30 * time.Second,
|
MaxJoinDuration: 30 * time.Second,
|
||||||
BroadcastAddress: BroadcastAddress,
|
BroadcastAddress: BroadcastAddress,
|
||||||
},
|
},
|
||||||
|
Authorization: config.Authorization{},
|
||||||
},
|
},
|
||||||
Services: map[string]config.Service{
|
Services: map[string]config.Service{
|
||||||
"frontend": {
|
"frontend": {
|
||||||
|
@ -69,14 +96,6 @@ func NewServerConfig(cfg *internal.Config) *config.Config {
|
||||||
BindOnIP: "",
|
BindOnIP: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"worker": {
|
|
||||||
RPC: config.RPC{
|
|
||||||
GRPCPort: WorkerPort,
|
|
||||||
MembershipPort: WorkerPort + 100,
|
|
||||||
BindOnLocalHost: true,
|
|
||||||
BindOnIP: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
ClusterMetadata: &cluster.Config{
|
ClusterMetadata: &cluster.Config{
|
||||||
EnableGlobalNamespace: false,
|
EnableGlobalNamespace: false,
|
||||||
|
|
|
@ -8,11 +8,12 @@ import (
|
||||||
"go.temporal.io/server/common/authorization"
|
"go.temporal.io/server/common/authorization"
|
||||||
"go.temporal.io/server/common/config"
|
"go.temporal.io/server/common/config"
|
||||||
"go.temporal.io/server/common/log"
|
"go.temporal.io/server/common/log"
|
||||||
|
"go.temporal.io/server/common/primitives"
|
||||||
"go.temporal.io/server/schema/sqlite"
|
"go.temporal.io/server/schema/sqlite"
|
||||||
t "go.temporal.io/server/temporal"
|
t "go.temporal.io/server/temporal"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewServer(cfg *config.Config) (t.Server, error) {
|
func NewServer(cfg *config.Config, tokenKeyProvider authorization.TokenKeyProvider) (t.Server, error) {
|
||||||
logger := log.NewZapLogger(log.BuildZapLogger(log.Config{
|
logger := log.NewZapLogger(log.BuildZapLogger(log.Config{
|
||||||
Stdout: true,
|
Stdout: true,
|
||||||
Level: "info",
|
Level: "info",
|
||||||
|
@ -43,15 +44,8 @@ func NewServer(cfg *config.Config) (t.Server, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
authorizer, err := authorization.GetAuthorizerFromConfig(&cfg.Global.Authorization)
|
authorizer := authorization.NewDefaultAuthorizer()
|
||||||
if err != nil {
|
claimMapper := authorization.NewDefaultJWTClaimMapper(tokenKeyProvider, &cfg.Global.Authorization, logger)
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
claimMapper, err := authorization.GetClaimMapperFromConfig(&cfg.Global.Authorization, logger)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
interruptChan := make(chan interface{}, 1)
|
interruptChan := make(chan interface{}, 1)
|
||||||
|
@ -67,12 +61,16 @@ func NewServer(cfg *config.Config) (t.Server, error) {
|
||||||
|
|
||||||
return t.NewServer(
|
return t.NewServer(
|
||||||
t.WithConfig(cfg),
|
t.WithConfig(cfg),
|
||||||
t.ForServices(t.DefaultServices),
|
t.ForServices([]string{
|
||||||
|
string(primitives.FrontendService),
|
||||||
|
string(primitives.HistoryService),
|
||||||
|
string(primitives.MatchingService),
|
||||||
|
}),
|
||||||
t.WithLogger(logger),
|
t.WithLogger(logger),
|
||||||
|
t.InterruptOn(interruptChan),
|
||||||
t.WithAuthorizer(authorizer),
|
t.WithAuthorizer(authorizer),
|
||||||
t.WithClaimMapper(func(cfg *config.Config) authorization.ClaimMapper {
|
t.WithClaimMapper(func(cfg *config.Config) authorization.ClaimMapper {
|
||||||
return claimMapper
|
return claimMapper
|
||||||
}),
|
}),
|
||||||
t.InterruptOn(interruptChan),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,8 @@ type Temporal struct {
|
||||||
|
|
||||||
func NewTemporal(cfg *config.Config) (*Temporal, error) {
|
func NewTemporal(cfg *config.Config) (*Temporal, error) {
|
||||||
serverConfig := NewServerConfig(cfg)
|
serverConfig := NewServerConfig(cfg)
|
||||||
server, err := NewServer(serverConfig)
|
tokenKeyProvider := TokenKeyProvider{config: cfg}
|
||||||
|
server, err := NewServer(serverConfig, &tokenKeyProvider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -45,11 +46,12 @@ func (t *Temporal) Start() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Temporal) Stop() error {
|
func (t *Temporal) Stop() error {
|
||||||
|
t.uiServer.Stop()
|
||||||
|
|
||||||
err := t.server.Stop()
|
err := t.server.Stop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
t.uiServer.Stop()
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,14 +24,14 @@ func (w *Worker) Name() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Worker) Start() error {
|
func (w *Worker) Start() error {
|
||||||
temporalClient, err := temporal.ConnectToTemporal(w.cfg)
|
temporalClient, err := temporal.ConnectWorkerToTemporal(w.cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new Worker
|
// Create a new Worker
|
||||||
// TODO: Maybe identify by region or something?
|
// TODO: Maybe identify by region or something?
|
||||||
w.worker = worker.New(temporalClient, "default", worker.Options{})
|
w.worker = worker.New(temporalClient, "test", worker.Options{})
|
||||||
|
|
||||||
// Register Workflows
|
// Register Workflows
|
||||||
w.worker.RegisterWorkflow(workflows.HealthcheckHttpWorkflowDefinition)
|
w.worker.RegisterWorkflow(workflows.HealthcheckHttpWorkflowDefinition)
|
||||||
|
|
|
@ -29,6 +29,7 @@ func main() {
|
||||||
|
|
||||||
// Generate default DAO interface for those specified structs
|
// Generate default DAO interface for those specified structs
|
||||||
g.ApplyBasic(
|
g.ApplyBasic(
|
||||||
|
models.Worker{},
|
||||||
models.HealthcheckHttp{},
|
models.HealthcheckHttp{},
|
||||||
models.HealthcheckHttpHistory{},
|
models.HealthcheckHttpHistory{},
|
||||||
models.HealthcheckTcp{},
|
models.HealthcheckTcp{},
|
||||||
|
|
|
@ -26,8 +26,7 @@
|
||||||
List of Healthchecks
|
List of Healthchecks
|
||||||
<div class="mt-1 flex">
|
<div class="mt-1 flex">
|
||||||
<p class="mt-1 text-sm font-normal text-gray-500">
|
<p class="mt-1 text-sm font-normal text-gray-500">
|
||||||
Healthchecks represent periodic checks of some HTTP or TCP service, to see if it's
|
{{ $description }}
|
||||||
responding correctly to deterime if it's healthy or not.
|
|
||||||
</p>
|
</p>
|
||||||
<a href="/settings/healthchecks/create" class="inline-flex justify-center items-center py-1 px-2 text-sm font-medium text-center text-white rounded-lg bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300">
|
<a href="/settings/healthchecks/create" class="inline-flex justify-center items-center py-1 px-2 text-sm font-medium text-center text-white rounded-lg bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300">
|
||||||
Create New
|
Create New
|
||||||
|
|
68
web/templates/pages/settings_workers.tmpl
Normal file
68
web/templates/pages/settings_workers.tmpl
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
{{define "settings"}}
|
||||||
|
|
||||||
|
{{ $description := "Workers are executing healthchecks. You can deploy multiple of thems to multiple regions for wider coverage." }}
|
||||||
|
|
||||||
|
{{ if eq .WorkersLength 0 }}
|
||||||
|
<section>
|
||||||
|
<div class="py-8 px-4 mx-auto max-w-screen-xl text-center lg:py-16">
|
||||||
|
<h1 class="mb-4 text-2xl font-extrabold tracking-tight leading-none text-gray-900 md:text-3xl lg:text-4xl">
|
||||||
|
There are no workers yet.
|
||||||
|
</h1>
|
||||||
|
<p class="mb-8 text-l font-normal text-gray-500 lg:text-l sm:px-8 lg:px-40">
|
||||||
|
{{ $description }}
|
||||||
|
</p>
|
||||||
|
<div class="flex flex-col space-y-4 sm:flex-row sm:justify-center sm:space-y-0">
|
||||||
|
<a href="/settings/workers/create" class="inline-flex justify-center items-center py-3 px-5 text-base font-medium text-center text-white rounded-lg bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300">
|
||||||
|
Create First Worker
|
||||||
|
<svg class="feather ml-1 h-5 w-5 overflow-visible"><use href="/static/icons/feather-sprite.svg#plus" /></svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{{ else }}
|
||||||
|
<div class="relative overflow-x-auto shadow-md sm:rounded-lg">
|
||||||
|
<table class="w-full text-sm text-left rtl:text-right text-gray-500">
|
||||||
|
<caption class="p-5 text-lg font-semibold text-left rtl:text-right text-gray-900 bg-white">
|
||||||
|
List of Workers
|
||||||
|
<div class="mt-1 flex">
|
||||||
|
<p class="mt-1 text-sm font-normal text-gray-500">
|
||||||
|
{{ $description }}
|
||||||
|
</p>
|
||||||
|
<a href="/settings/workers/create" class="inline-flex justify-center items-center py-1 px-2 text-sm font-medium text-center text-white rounded-lg bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300">
|
||||||
|
Create New
|
||||||
|
<svg class="feather h-5 w-5 overflow-visible"><use href="/static/icons/feather-sprite.svg#plus" /></svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</caption>
|
||||||
|
<thead class="text-xs text-gray-700 uppercase bg-gray-50">
|
||||||
|
<tr>
|
||||||
|
<th scope="col" class="px-6 py-3">
|
||||||
|
Name
|
||||||
|
</th>
|
||||||
|
<th scope="col" class="px-6 py-3">
|
||||||
|
Status
|
||||||
|
</th>
|
||||||
|
<th scope="col" class="px-6 py-3">
|
||||||
|
Action
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
{{range .Workers}}
|
||||||
|
<tbody>
|
||||||
|
<tr class="odd:bg-white even:bg-gray-50">
|
||||||
|
<th scope="row" class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap">
|
||||||
|
{{.Name}}
|
||||||
|
</th>
|
||||||
|
<td class="px-6 py-4">
|
||||||
|
OK
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4">
|
||||||
|
<a href="/settings/workers/{{.Slug}}" class="font-medium text-blue-600 hover:underline">Details</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
{{end}}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
14
web/templates/pages/settings_workers_create.tmpl
Normal file
14
web/templates/pages/settings_workers_create.tmpl
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{{define "settings"}}
|
||||||
|
<section class="relative overflow-x-auto shadow-md sm:rounded-lg p-5 text-gray-500 bg-white">
|
||||||
|
<h1 class="text-lg font-semibold text-gray-900">
|
||||||
|
Creating new worker.
|
||||||
|
</h1>
|
||||||
|
<form class="max-w-sm mt-4" action="/settings/workers/create" method="post">
|
||||||
|
<div class="mb-5">
|
||||||
|
<label for="name" class="block mb-2 text-sm font-medium text-gray-900">Name</label>
|
||||||
|
<input type="name" name="name" id="name" placeholder="FooBar" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"/>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center">Create</button>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
{{end}}
|
27
zdravko.yaml
27
zdravko.yaml
|
@ -1,27 +0,0 @@
|
||||||
healthchecks:
|
|
||||||
- name: "Google"
|
|
||||||
http:
|
|
||||||
url: "https://www.google.com"
|
|
||||||
method: GET
|
|
||||||
timeout: 5s
|
|
||||||
schedule: "* * * * *"
|
|
||||||
retries: 3
|
|
||||||
- name: "GitHub"
|
|
||||||
http:
|
|
||||||
url: "https://www.github.com"
|
|
||||||
method: GET
|
|
||||||
timeout: 5s
|
|
||||||
schedule: "* * * * *"
|
|
||||||
retries: 3
|
|
||||||
- name: "Docker"
|
|
||||||
tcp:
|
|
||||||
hostname: "docker.com"
|
|
||||||
port: 443
|
|
||||||
schedule: "* * * * *"
|
|
||||||
timeout: 60s
|
|
||||||
retries: 3
|
|
||||||
|
|
||||||
cronjobs:
|
|
||||||
- name: "Backup"
|
|
||||||
schedule: "0 0 * * *"
|
|
||||||
buffer: 1h
|
|
Loading…
Reference in a new issue