From abdc7fc3eae215518f908bd1198a5dd0af9b07d9 Mon Sep 17 00:00:00 2001 From: d34dscene Date: Tue, 28 Jan 2025 15:02:53 +0100 Subject: [PATCH] somewhat fix agents --- internal/api/handler/agent.go | 4 + internal/api/handler/dns_providers.go | 2 +- internal/api/handler/middlewares.go | 23 +- internal/api/handler/profiles.go | 18 +- internal/api/handler/routers.go | 24 +- internal/api/handler/traefik.go | 37 +-- internal/api/server/server.go | 4 +- internal/config/setup.go | 21 +- internal/db/db.go | 94 ++++-- internal/db/migrations/00001_base.sql | 15 +- internal/db/models.go | 1 + internal/db/querier.go | 12 +- internal/db/queries/traefik.sql | 92 ++++-- internal/db/traefik.sql.go | 277 +++++++++++++----- internal/db/types.go | 5 +- internal/dns/client.go | 4 +- internal/traefik/agent.go | 81 +++-- internal/traefik/client.go | 2 +- web/src/lib/api.ts | 26 +- web/src/lib/components/forms/router.svelte | 21 +- web/src/lib/components/forms/service.svelte | 108 ++++--- web/src/lib/components/modals/info.svelte | 2 +- web/src/lib/components/modals/router.svelte | 34 +-- .../lib/components/tables/DataTable.svelte | 14 +- web/src/lib/types/middlewares.ts | 74 ++--- web/src/lib/types/router.ts | 162 +++++----- web/src/routes/agents/+page.svelte | 33 +-- web/src/routes/middlewares/+page.svelte | 1 - web/src/routes/plugins/+page.svelte | 1 - web/src/routes/router/+page.svelte | 36 ++- 30 files changed, 711 insertions(+), 517 deletions(-) diff --git a/internal/api/handler/agent.go b/internal/api/handler/agent.go index ac0a771..7d12f07 100644 --- a/internal/api/handler/agent.go +++ b/internal/api/handler/agent.go @@ -118,6 +118,10 @@ func DeleteAgent(DB *sql.DB) http.HandlerFunc { http.Error(w, err.Error(), http.StatusInternalServerError) return } + if err := q.DeleteTraefikConfigByAgent(r.Context(), &agent.ID); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } if err := q.DeleteAgent(r.Context(), agent.ID); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return diff --git a/internal/api/handler/dns_providers.go b/internal/api/handler/dns_providers.go index fe758ad..a221f1d 100644 --- a/internal/api/handler/dns_providers.go +++ b/internal/api/handler/dns_providers.go @@ -119,7 +119,7 @@ func SetRouterDNSProvider(DB *sql.DB) http.HandlerFunc { } // Check if router exists - config, err := q.GetTraefikConfig(r.Context(), dns_provider.TraefikID) + config, err := q.GetTraefikConfigByID(r.Context(), dns_provider.TraefikID) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return diff --git a/internal/api/handler/middlewares.go b/internal/api/handler/middlewares.go index ad31e39..3764516 100644 --- a/internal/api/handler/middlewares.go +++ b/internal/api/handler/middlewares.go @@ -64,13 +64,7 @@ func UpsertMiddleware(DB *sql.DB) http.HandlerFunc { return } - existingConfig, err := q.GetTraefikConfigBySource( - r.Context(), - db.GetTraefikConfigBySourceParams{ - ProfileID: profileID, - Source: source.Local, - }, - ) + existingConfig, err := q.GetLocalTraefikConfig(r.Context(), profileID) if err != nil { http.Error( w, @@ -99,7 +93,6 @@ func UpsertMiddleware(DB *sql.DB) http.HandlerFunc { // Update configuration based on type switch params.Protocol { case "http": - fmt.Printf("params.Middleware: %+v\n", params) existingConfig.Config.Middlewares[params.Name] = params.Middleware case "tcp": existingConfig.Config.TCPMiddlewares[params.Name] = params.TCPMiddleware @@ -108,7 +101,7 @@ func UpsertMiddleware(DB *sql.DB) http.HandlerFunc { return } - err = q.UpdateTraefikConfig(r.Context(), db.UpdateTraefikConfigParams{ + err = q.UpsertTraefikConfig(r.Context(), db.UpsertTraefikConfigParams{ ProfileID: profileID, Source: source.Local, Config: existingConfig.Config, @@ -148,13 +141,7 @@ func DeleteMiddleware(DB *sql.DB) http.HandlerFunc { mwName = fmt.Sprintf("%s@http", strings.Split(mwName, "@")[0]) } - existingConfig, err := q.GetTraefikConfigBySource( - r.Context(), - db.GetTraefikConfigBySourceParams{ - ProfileID: profileID, - Source: source.Local, - }, - ) + existingConfig, err := q.GetLocalTraefikConfig(r.Context(), profileID) if err != nil { http.Error( w, @@ -175,8 +162,8 @@ func DeleteMiddleware(DB *sql.DB) http.HandlerFunc { return } - err = q.UpdateTraefikConfig(r.Context(), db.UpdateTraefikConfigParams{ - ProfileID: profileID, + err = q.UpsertTraefikConfig(r.Context(), db.UpsertTraefikConfigParams{ + ProfileID: existingConfig.ID, Source: source.Local, Config: existingConfig.Config, }) diff --git a/internal/api/handler/profiles.go b/internal/api/handler/profiles.go index 6a0e85f..eae3478 100644 --- a/internal/api/handler/profiles.go +++ b/internal/api/handler/profiles.go @@ -7,7 +7,6 @@ import ( "strconv" "github.com/MizuchiLabs/mantrae/internal/db" - "github.com/MizuchiLabs/mantrae/internal/source" ) func ListProfiles(DB *sql.DB) http.HandlerFunc { @@ -49,27 +48,12 @@ func CreateProfile(DB *sql.DB) http.HandlerFunc { http.Error(w, err.Error(), http.StatusInternalServerError) return } - profileID, err := q.CreateProfile(r.Context(), profile) + _, err := q.CreateProfile(r.Context(), profile) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } - // Create configs for all source types - sources := []source.Source{source.Local, source.API, source.Agent} - for _, src := range sources { - if err := q.CreateTraefikConfig(r.Context(), db.CreateTraefikConfigParams{ - ProfileID: profileID, - Source: src, - Entrypoints: nil, - Overview: nil, - Config: nil, - }); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - } - w.WriteHeader(http.StatusNoContent) } } diff --git a/internal/api/handler/routers.go b/internal/api/handler/routers.go index 36a49d0..073c859 100644 --- a/internal/api/handler/routers.go +++ b/internal/api/handler/routers.go @@ -47,13 +47,7 @@ func UpsertRouter(DB *sql.DB) http.HandlerFunc { return } - existingConfig, err := q.GetTraefikConfigBySource( - r.Context(), - db.GetTraefikConfigBySourceParams{ - ProfileID: profileID, - Source: source.Local, - }, - ) + existingConfig, err := q.GetLocalTraefikConfig(r.Context(), profileID) if err != nil { http.Error( w, @@ -107,8 +101,8 @@ func UpsertRouter(DB *sql.DB) http.HandlerFunc { return } - err = q.UpdateTraefikConfig(r.Context(), db.UpdateTraefikConfigParams{ - ProfileID: profileID, + err = q.UpsertTraefikConfig(r.Context(), db.UpsertTraefikConfigParams{ + ProfileID: existingConfig.ID, Source: source.Local, Config: existingConfig.Config, }) @@ -148,13 +142,7 @@ func DeleteRouter(DB *sql.DB) http.HandlerFunc { return } - existingConfig, err := q.GetTraefikConfigBySource( - r.Context(), - db.GetTraefikConfigBySourceParams{ - ProfileID: profileID, - Source: source.Local, - }, - ) + existingConfig, err := q.GetLocalTraefikConfig(r.Context(), profileID) if err != nil { http.Error( w, @@ -180,8 +168,8 @@ func DeleteRouter(DB *sql.DB) http.HandlerFunc { return } - err = q.UpdateTraefikConfig(r.Context(), db.UpdateTraefikConfigParams{ - ProfileID: profileID, + err = q.UpsertTraefikConfig(r.Context(), db.UpsertTraefikConfigParams{ + ProfileID: existingConfig.ProfileID, Source: source.Local, Config: existingConfig.Config, }) diff --git a/internal/api/handler/traefik.go b/internal/api/handler/traefik.go index e1e14a4..5f8ef48 100644 --- a/internal/api/handler/traefik.go +++ b/internal/api/handler/traefik.go @@ -10,27 +10,6 @@ import ( "github.com/MizuchiLabs/mantrae/internal/source" ) -func CreateTraefikConfig(DB *sql.DB) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - q := db.New(DB) - var config db.CreateTraefikConfigParams - if err := json.NewDecoder(r.Body).Decode(&config); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - if !config.Source.Valid() { - http.Error(w, "invalid source", http.StatusBadRequest) - return - } - - if err := q.CreateTraefikConfig(r.Context(), config); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - w.WriteHeader(http.StatusCreated) - } -} - func GetTraefikConfig(DB *sql.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { q := db.New(DB) @@ -39,13 +18,14 @@ func GetTraefikConfig(DB *sql.DB) http.HandlerFunc { http.Error(w, err.Error(), http.StatusInternalServerError) return } - if !source.Source(r.PathValue("source")).Valid() { + src := source.Source(r.PathValue("source")) + if !src.Valid() { http.Error(w, "invalid source", http.StatusBadRequest) return } config, err := q.GetTraefikConfigBySource(r.Context(), db.GetTraefikConfigBySourceParams{ ProfileID: profile_id, - Source: source.Source(r.PathValue("source")), + Source: src, }) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -56,10 +36,10 @@ func GetTraefikConfig(DB *sql.DB) http.HandlerFunc { } } -func UpdateTraefikConfig(DB *sql.DB) http.HandlerFunc { +func UpsertTraefikConfig(DB *sql.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { q := db.New(DB) - var config db.UpdateTraefikConfigParams + var config db.UpsertTraefikConfigParams if err := json.NewDecoder(r.Body).Decode(&config); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return @@ -69,7 +49,7 @@ func UpdateTraefikConfig(DB *sql.DB) http.HandlerFunc { return } - err := q.UpdateTraefikConfig(r.Context(), config) + err := q.UpsertTraefikConfig(r.Context(), config) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -103,10 +83,7 @@ func PublishTraefikConfig(DB *sql.DB) http.HandlerFunc { http.Error(w, err.Error(), http.StatusInternalServerError) return } - config, err := q.GetTraefikConfigBySource(r.Context(), db.GetTraefikConfigBySourceParams{ - ProfileID: profile.ID, - Source: source.Local, - }) + config, err := q.GetLocalTraefikConfig(r.Context(), profile.ID) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return diff --git a/internal/api/server/server.go b/internal/api/server/server.go index 72c2eae..52edd04 100644 --- a/internal/api/server/server.go +++ b/internal/api/server/server.go @@ -126,11 +126,11 @@ func (s *Server) registerServices() { s.mux.Handle(grpcreflect.NewHandlerV1(reflector)) s.mux.Handle(grpcreflect.NewHandlerV1Alpha(reflector)) - // // Serve OpenAPI specs file + // Serve OpenAPI specs file // fsHandler := http.FileServer(http.Dir("proto/gen/openapi")) // s.mux.Handle("/openapi/", http.StripPrefix("/openapi/", fsHandler)) - // // Serve Swagger UI + // Serve Swagger UI // s.mux.HandleFunc("/swagger/", func(w http.ResponseWriter, r *http.Request) { // httpSwagger.Handler( // httpSwagger.URL("/openapi/api.swagger.yaml"), diff --git a/internal/config/setup.go b/internal/config/setup.go index 0c95a08..48a390d 100644 --- a/internal/config/setup.go +++ b/internal/config/setup.go @@ -11,7 +11,6 @@ import ( "github.com/MizuchiLabs/mantrae/internal/app" "github.com/MizuchiLabs/mantrae/internal/backup" "github.com/MizuchiLabs/mantrae/internal/db" - "github.com/MizuchiLabs/mantrae/internal/source" "github.com/MizuchiLabs/mantrae/internal/util" "github.com/lmittmann/tint" "golang.org/x/crypto/bcrypt" @@ -163,7 +162,7 @@ func (a *App) setDefaultProfile(ctx context.Context) error { q := db.New(a.DB) _, err := q.GetProfileByName(ctx, a.Config.Traefik.Profile) if err != nil { - profileID, err := q.CreateProfile(ctx, db.CreateProfileParams{ + _, err := q.CreateProfile(ctx, db.CreateProfileParams{ Name: a.Config.Traefik.Profile, Url: a.Config.Traefik.URL, Username: &a.Config.Traefik.Username, @@ -174,24 +173,6 @@ func (a *App) setDefaultProfile(ctx context.Context) error { return fmt.Errorf("failed to create default profile: %w", err) } - // Create configs for all source types - sources := []source.Source{source.Local, source.API, source.Agent} - for _, src := range sources { - if err := q.CreateTraefikConfig(ctx, db.CreateTraefikConfigParams{ - ProfileID: profileID, - Source: src, - Entrypoints: nil, - Overview: nil, - Config: nil, - }); err != nil { - return fmt.Errorf( - "failed to create default traefik config for source %s: %w", - src, - err, - ) - } - } - slog.Info( "Created default profile", "url", diff --git a/internal/db/db.go b/internal/db/db.go index e6f9ceb..f9b4242 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -33,9 +33,6 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.createProfileStmt, err = db.PrepareContext(ctx, createProfile); err != nil { return nil, fmt.Errorf("error preparing query CreateProfile: %w", err) } - if q.createTraefikConfigStmt, err = db.PrepareContext(ctx, createTraefikConfig); err != nil { - return nil, fmt.Errorf("error preparing query CreateTraefikConfig: %w", err) - } if q.createUserStmt, err = db.PrepareContext(ctx, createUser); err != nil { return nil, fmt.Errorf("error preparing query CreateUser: %w", err) } @@ -57,18 +54,30 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.deleteTraefikConfigStmt, err = db.PrepareContext(ctx, deleteTraefikConfig); err != nil { return nil, fmt.Errorf("error preparing query DeleteTraefikConfig: %w", err) } + if q.deleteTraefikConfigByAgentStmt, err = db.PrepareContext(ctx, deleteTraefikConfigByAgent); err != nil { + return nil, fmt.Errorf("error preparing query DeleteTraefikConfigByAgent: %w", err) + } if q.deleteUserStmt, err = db.PrepareContext(ctx, deleteUser); err != nil { return nil, fmt.Errorf("error preparing query DeleteUser: %w", err) } + if q.getAPITraefikConfigStmt, err = db.PrepareContext(ctx, getAPITraefikConfig); err != nil { + return nil, fmt.Errorf("error preparing query GetAPITraefikConfig: %w", err) + } if q.getActiveDNSProviderStmt, err = db.PrepareContext(ctx, getActiveDNSProvider); err != nil { return nil, fmt.Errorf("error preparing query GetActiveDNSProvider: %w", err) } if q.getAgentStmt, err = db.PrepareContext(ctx, getAgent); err != nil { return nil, fmt.Errorf("error preparing query GetAgent: %w", err) } + if q.getAgentTraefikConfigsStmt, err = db.PrepareContext(ctx, getAgentTraefikConfigs); err != nil { + return nil, fmt.Errorf("error preparing query GetAgentTraefikConfigs: %w", err) + } if q.getDNSProviderStmt, err = db.PrepareContext(ctx, getDNSProvider); err != nil { return nil, fmt.Errorf("error preparing query GetDNSProvider: %w", err) } + if q.getLocalTraefikConfigStmt, err = db.PrepareContext(ctx, getLocalTraefikConfig); err != nil { + return nil, fmt.Errorf("error preparing query GetLocalTraefikConfig: %w", err) + } if q.getProfileStmt, err = db.PrepareContext(ctx, getProfile); err != nil { return nil, fmt.Errorf("error preparing query GetProfile: %w", err) } @@ -81,8 +90,8 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.getSettingStmt, err = db.PrepareContext(ctx, getSetting); err != nil { return nil, fmt.Errorf("error preparing query GetSetting: %w", err) } - if q.getTraefikConfigStmt, err = db.PrepareContext(ctx, getTraefikConfig); err != nil { - return nil, fmt.Errorf("error preparing query GetTraefikConfig: %w", err) + if q.getTraefikConfigByIDStmt, err = db.PrepareContext(ctx, getTraefikConfigByID); err != nil { + return nil, fmt.Errorf("error preparing query GetTraefikConfigByID: %w", err) } if q.getTraefikConfigBySourceStmt, err = db.PrepareContext(ctx, getTraefikConfigBySource); err != nil { return nil, fmt.Errorf("error preparing query GetTraefikConfigBySource: %w", err) @@ -132,9 +141,6 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.updateProfileStmt, err = db.PrepareContext(ctx, updateProfile); err != nil { return nil, fmt.Errorf("error preparing query UpdateProfile: %w", err) } - if q.updateTraefikConfigStmt, err = db.PrepareContext(ctx, updateTraefikConfig); err != nil { - return nil, fmt.Errorf("error preparing query UpdateTraefikConfig: %w", err) - } if q.updateUserStmt, err = db.PrepareContext(ctx, updateUser); err != nil { return nil, fmt.Errorf("error preparing query UpdateUser: %w", err) } @@ -147,6 +153,12 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { if q.upsertSettingStmt, err = db.PrepareContext(ctx, upsertSetting); err != nil { return nil, fmt.Errorf("error preparing query UpsertSetting: %w", err) } + if q.upsertTraefikAgentConfigStmt, err = db.PrepareContext(ctx, upsertTraefikAgentConfig); err != nil { + return nil, fmt.Errorf("error preparing query UpsertTraefikAgentConfig: %w", err) + } + if q.upsertTraefikConfigStmt, err = db.PrepareContext(ctx, upsertTraefikConfig); err != nil { + return nil, fmt.Errorf("error preparing query UpsertTraefikConfig: %w", err) + } return &q, nil } @@ -167,11 +179,6 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing createProfileStmt: %w", cerr) } } - if q.createTraefikConfigStmt != nil { - if cerr := q.createTraefikConfigStmt.Close(); cerr != nil { - err = fmt.Errorf("error closing createTraefikConfigStmt: %w", cerr) - } - } if q.createUserStmt != nil { if cerr := q.createUserStmt.Close(); cerr != nil { err = fmt.Errorf("error closing createUserStmt: %w", cerr) @@ -207,11 +214,21 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing deleteTraefikConfigStmt: %w", cerr) } } + if q.deleteTraefikConfigByAgentStmt != nil { + if cerr := q.deleteTraefikConfigByAgentStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing deleteTraefikConfigByAgentStmt: %w", cerr) + } + } if q.deleteUserStmt != nil { if cerr := q.deleteUserStmt.Close(); cerr != nil { err = fmt.Errorf("error closing deleteUserStmt: %w", cerr) } } + if q.getAPITraefikConfigStmt != nil { + if cerr := q.getAPITraefikConfigStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing getAPITraefikConfigStmt: %w", cerr) + } + } if q.getActiveDNSProviderStmt != nil { if cerr := q.getActiveDNSProviderStmt.Close(); cerr != nil { err = fmt.Errorf("error closing getActiveDNSProviderStmt: %w", cerr) @@ -222,11 +239,21 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing getAgentStmt: %w", cerr) } } + if q.getAgentTraefikConfigsStmt != nil { + if cerr := q.getAgentTraefikConfigsStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing getAgentTraefikConfigsStmt: %w", cerr) + } + } if q.getDNSProviderStmt != nil { if cerr := q.getDNSProviderStmt.Close(); cerr != nil { err = fmt.Errorf("error closing getDNSProviderStmt: %w", cerr) } } + if q.getLocalTraefikConfigStmt != nil { + if cerr := q.getLocalTraefikConfigStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing getLocalTraefikConfigStmt: %w", cerr) + } + } if q.getProfileStmt != nil { if cerr := q.getProfileStmt.Close(); cerr != nil { err = fmt.Errorf("error closing getProfileStmt: %w", cerr) @@ -247,9 +274,9 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing getSettingStmt: %w", cerr) } } - if q.getTraefikConfigStmt != nil { - if cerr := q.getTraefikConfigStmt.Close(); cerr != nil { - err = fmt.Errorf("error closing getTraefikConfigStmt: %w", cerr) + if q.getTraefikConfigByIDStmt != nil { + if cerr := q.getTraefikConfigByIDStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing getTraefikConfigByIDStmt: %w", cerr) } } if q.getTraefikConfigBySourceStmt != nil { @@ -332,11 +359,6 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing updateProfileStmt: %w", cerr) } } - if q.updateTraefikConfigStmt != nil { - if cerr := q.updateTraefikConfigStmt.Close(); cerr != nil { - err = fmt.Errorf("error closing updateTraefikConfigStmt: %w", cerr) - } - } if q.updateUserStmt != nil { if cerr := q.updateUserStmt.Close(); cerr != nil { err = fmt.Errorf("error closing updateUserStmt: %w", cerr) @@ -357,6 +379,16 @@ func (q *Queries) Close() error { err = fmt.Errorf("error closing upsertSettingStmt: %w", cerr) } } + if q.upsertTraefikAgentConfigStmt != nil { + if cerr := q.upsertTraefikAgentConfigStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing upsertTraefikAgentConfigStmt: %w", cerr) + } + } + if q.upsertTraefikConfigStmt != nil { + if cerr := q.upsertTraefikConfigStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing upsertTraefikConfigStmt: %w", cerr) + } + } return err } @@ -399,7 +431,6 @@ type Queries struct { createAgentStmt *sql.Stmt createDNSProviderStmt *sql.Stmt createProfileStmt *sql.Stmt - createTraefikConfigStmt *sql.Stmt createUserStmt *sql.Stmt deleteAgentStmt *sql.Stmt deleteDNSProviderStmt *sql.Stmt @@ -407,15 +438,19 @@ type Queries struct { deleteRouterDNSProviderStmt *sql.Stmt deleteSettingStmt *sql.Stmt deleteTraefikConfigStmt *sql.Stmt + deleteTraefikConfigByAgentStmt *sql.Stmt deleteUserStmt *sql.Stmt + getAPITraefikConfigStmt *sql.Stmt getActiveDNSProviderStmt *sql.Stmt getAgentStmt *sql.Stmt + getAgentTraefikConfigsStmt *sql.Stmt getDNSProviderStmt *sql.Stmt + getLocalTraefikConfigStmt *sql.Stmt getProfileStmt *sql.Stmt getProfileByNameStmt *sql.Stmt getRouterDNSProviderStmt *sql.Stmt getSettingStmt *sql.Stmt - getTraefikConfigStmt *sql.Stmt + getTraefikConfigByIDStmt *sql.Stmt getTraefikConfigBySourceStmt *sql.Stmt getUserStmt *sql.Stmt getUserByUsernameStmt *sql.Stmt @@ -432,11 +467,12 @@ type Queries struct { updateAgentTokenStmt *sql.Stmt updateDNSProviderStmt *sql.Stmt updateProfileStmt *sql.Stmt - updateTraefikConfigStmt *sql.Stmt updateUserStmt *sql.Stmt updateUserLastLoginStmt *sql.Stmt upsertRouterDNSProviderStmt *sql.Stmt upsertSettingStmt *sql.Stmt + upsertTraefikAgentConfigStmt *sql.Stmt + upsertTraefikConfigStmt *sql.Stmt } func (q *Queries) WithTx(tx *sql.Tx) *Queries { @@ -446,7 +482,6 @@ func (q *Queries) WithTx(tx *sql.Tx) *Queries { createAgentStmt: q.createAgentStmt, createDNSProviderStmt: q.createDNSProviderStmt, createProfileStmt: q.createProfileStmt, - createTraefikConfigStmt: q.createTraefikConfigStmt, createUserStmt: q.createUserStmt, deleteAgentStmt: q.deleteAgentStmt, deleteDNSProviderStmt: q.deleteDNSProviderStmt, @@ -454,15 +489,19 @@ func (q *Queries) WithTx(tx *sql.Tx) *Queries { deleteRouterDNSProviderStmt: q.deleteRouterDNSProviderStmt, deleteSettingStmt: q.deleteSettingStmt, deleteTraefikConfigStmt: q.deleteTraefikConfigStmt, + deleteTraefikConfigByAgentStmt: q.deleteTraefikConfigByAgentStmt, deleteUserStmt: q.deleteUserStmt, + getAPITraefikConfigStmt: q.getAPITraefikConfigStmt, getActiveDNSProviderStmt: q.getActiveDNSProviderStmt, getAgentStmt: q.getAgentStmt, + getAgentTraefikConfigsStmt: q.getAgentTraefikConfigsStmt, getDNSProviderStmt: q.getDNSProviderStmt, + getLocalTraefikConfigStmt: q.getLocalTraefikConfigStmt, getProfileStmt: q.getProfileStmt, getProfileByNameStmt: q.getProfileByNameStmt, getRouterDNSProviderStmt: q.getRouterDNSProviderStmt, getSettingStmt: q.getSettingStmt, - getTraefikConfigStmt: q.getTraefikConfigStmt, + getTraefikConfigByIDStmt: q.getTraefikConfigByIDStmt, getTraefikConfigBySourceStmt: q.getTraefikConfigBySourceStmt, getUserStmt: q.getUserStmt, getUserByUsernameStmt: q.getUserByUsernameStmt, @@ -479,10 +518,11 @@ func (q *Queries) WithTx(tx *sql.Tx) *Queries { updateAgentTokenStmt: q.updateAgentTokenStmt, updateDNSProviderStmt: q.updateDNSProviderStmt, updateProfileStmt: q.updateProfileStmt, - updateTraefikConfigStmt: q.updateTraefikConfigStmt, updateUserStmt: q.updateUserStmt, updateUserLastLoginStmt: q.updateUserLastLoginStmt, upsertRouterDNSProviderStmt: q.upsertRouterDNSProviderStmt, upsertSettingStmt: q.upsertSettingStmt, + upsertTraefikAgentConfigStmt: q.upsertTraefikAgentConfigStmt, + upsertTraefikConfigStmt: q.upsertTraefikConfigStmt, } } diff --git a/internal/db/migrations/00001_base.sql b/internal/db/migrations/00001_base.sql index c3183f3..57f595e 100644 --- a/internal/db/migrations/00001_base.sql +++ b/internal/db/migrations/00001_base.sql @@ -13,6 +13,7 @@ CREATE TABLE IF NOT EXISTS profiles ( CREATE TABLE IF NOT EXISTS traefik ( id INTEGER PRIMARY KEY, profile_id INTEGER NOT NULL, + agent_id TEXT, source TEXT NOT NULL, entrypoints JSON, overview JSON, @@ -21,9 +22,21 @@ CREATE TABLE IF NOT EXISTS traefik ( created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (profile_id) REFERENCES profiles (id) ON DELETE CASCADE, - UNIQUE (profile_id, source) + FOREIGN KEY (agent_id) REFERENCES agents (id) ON DELETE CASCADE, + CONSTRAINT valid_source CHECK (source IN ('local', 'api', 'agent')) ); +-- Create unique index for local and api configs (single) +CREATE UNIQUE INDEX idx_traefik_profile_source ON traefik (profile_id, source) +WHERE + source IN ('local', 'api'); + +-- Create unique index for agent configs (multiple) +CREATE UNIQUE INDEX idx_traefik_agent ON traefik (profile_id, agent_id) +WHERE + agent_id IS NOT NULL + AND source = 'agent'; + CREATE TABLE IF NOT EXISTS dns_providers ( id INTEGER PRIMARY KEY, name VARCHAR(255) NOT NULL UNIQUE, diff --git a/internal/db/models.go b/internal/db/models.go index 5b33d89..3076d41 100644 --- a/internal/db/models.go +++ b/internal/db/models.go @@ -60,6 +60,7 @@ type Setting struct { type Traefik struct { ID int64 `json:"id"` ProfileID int64 `json:"profileId"` + AgentID *string `json:"agentId"` Source source.Source `json:"source"` Entrypoints *TraefikEntryPoints `json:"entrypoints"` Overview *TraefikOverview `json:"overview"` diff --git a/internal/db/querier.go b/internal/db/querier.go index 82ad3af..13526a8 100644 --- a/internal/db/querier.go +++ b/internal/db/querier.go @@ -12,7 +12,6 @@ type Querier interface { CreateAgent(ctx context.Context, arg CreateAgentParams) error CreateDNSProvider(ctx context.Context, arg CreateDNSProviderParams) error CreateProfile(ctx context.Context, arg CreateProfileParams) (int64, error) - CreateTraefikConfig(ctx context.Context, arg CreateTraefikConfigParams) error CreateUser(ctx context.Context, arg CreateUserParams) error DeleteAgent(ctx context.Context, id string) error DeleteDNSProvider(ctx context.Context, id int64) error @@ -20,16 +19,20 @@ type Querier interface { DeleteRouterDNSProvider(ctx context.Context, arg DeleteRouterDNSProviderParams) error DeleteSetting(ctx context.Context, key string) error DeleteTraefikConfig(ctx context.Context, id int64) error + DeleteTraefikConfigByAgent(ctx context.Context, agentID *string) error DeleteUser(ctx context.Context, id int64) error + GetAPITraefikConfig(ctx context.Context, profileID int64) (Traefik, error) GetActiveDNSProvider(ctx context.Context) (DnsProvider, error) GetAgent(ctx context.Context, id string) (Agent, error) + GetAgentTraefikConfigs(ctx context.Context, profileID int64) ([]Traefik, error) GetDNSProvider(ctx context.Context, id int64) (DnsProvider, error) + GetLocalTraefikConfig(ctx context.Context, profileID int64) (Traefik, error) GetProfile(ctx context.Context, id int64) (Profile, error) GetProfileByName(ctx context.Context, name string) (Profile, error) GetRouterDNSProvider(ctx context.Context, arg GetRouterDNSProviderParams) (GetRouterDNSProviderRow, error) GetSetting(ctx context.Context, key string) (Setting, error) - GetTraefikConfig(ctx context.Context, id int64) (Traefik, error) - GetTraefikConfigBySource(ctx context.Context, arg GetTraefikConfigBySourceParams) (Traefik, error) + GetTraefikConfigByID(ctx context.Context, id int64) (Traefik, error) + GetTraefikConfigBySource(ctx context.Context, arg GetTraefikConfigBySourceParams) ([]Traefik, error) GetUser(ctx context.Context, id int64) (User, error) GetUserByUsername(ctx context.Context, username string) (User, error) ListAgents(ctx context.Context) ([]Agent, error) @@ -45,11 +48,12 @@ type Querier interface { UpdateAgentToken(ctx context.Context, arg UpdateAgentTokenParams) error UpdateDNSProvider(ctx context.Context, arg UpdateDNSProviderParams) error UpdateProfile(ctx context.Context, arg UpdateProfileParams) error - UpdateTraefikConfig(ctx context.Context, arg UpdateTraefikConfigParams) error UpdateUser(ctx context.Context, arg UpdateUserParams) error UpdateUserLastLogin(ctx context.Context, id int64) error UpsertRouterDNSProvider(ctx context.Context, arg UpsertRouterDNSProviderParams) error UpsertSetting(ctx context.Context, arg UpsertSettingParams) error + UpsertTraefikAgentConfig(ctx context.Context, arg UpsertTraefikAgentConfigParams) error + UpsertTraefikConfig(ctx context.Context, arg UpsertTraefikConfigParams) error } var _ Querier = (*Queries)(nil) diff --git a/internal/db/queries/traefik.sql b/internal/db/queries/traefik.sql index 5264e1e..f7c6a82 100644 --- a/internal/db/queries/traefik.sql +++ b/internal/db/queries/traefik.sql @@ -1,17 +1,4 @@ --- name: CreateTraefikConfig :exec -INSERT INTO - traefik ( - profile_id, - source, - entrypoints, - overview, - version, - config - ) -VALUES - (?, ?, ?, ?, ?, ?); - --- name: GetTraefikConfig :one +-- name: GetTraefikConfigByID :one SELECT * FROM @@ -19,13 +6,40 @@ FROM WHERE id = ?; +-- name: GetLocalTraefikConfig :one +SELECT + * +FROM + traefik +WHERE + profile_id = ? + AND source = 'local'; + +-- name: GetAPITraefikConfig :one +SELECT + * +FROM + traefik +WHERE + profile_id = ? + AND source = 'api'; + +-- name: GetAgentTraefikConfigs :many +SELECT + * +FROM + traefik +WHERE + profile_id = ? + AND source = 'agent'; + -- name: ListTraefikIDs :many SELECT id FROM traefik; --- name: GetTraefikConfigBySource :one +-- name: GetTraefikConfigBySource :many SELECT * FROM @@ -34,19 +48,47 @@ WHERE profile_id = ? AND source = ?; --- name: UpdateTraefikConfig :exec -UPDATE traefik -SET - entrypoints = ?, - overview = ?, - config = ?, - version = ?, - updated_at = CURRENT_TIMESTAMP +-- name: UpsertTraefikConfig :exec +INSERT INTO + traefik ( + profile_id, + source, + entrypoints, + overview, + config, + version + ) +VALUES + (?, ?, ?, ?, ?, ?) ON CONFLICT (profile_id, source) WHERE - profile_id = ? - AND source = ?; + source IN ('local', 'api') DO +UPDATE +SET + entrypoints = excluded.entrypoints, + overview = excluded.overview, + config = excluded.config, + version = excluded.version, + updated_at = CURRENT_TIMESTAMP; + +-- name: UpsertTraefikAgentConfig :exec +INSERT INTO + traefik (profile_id, agent_id, source, config) +VALUES + (?, ?, 'agent', ?) ON CONFLICT (profile_id, agent_id) +WHERE + agent_id IS NOT NULL + AND source = 'agent' DO +UPDATE +SET + config = excluded.config, + updated_at = CURRENT_TIMESTAMP; -- name: DeleteTraefikConfig :exec DELETE FROM traefik WHERE id = ?; + +-- name: DeleteTraefikConfigByAgent :exec +DELETE FROM traefik +WHERE + agent_id = ?; diff --git a/internal/db/traefik.sql.go b/internal/db/traefik.sql.go index 03d3fd0..22b7a58 100644 --- a/internal/db/traefik.sql.go +++ b/internal/db/traefik.sql.go @@ -11,41 +11,6 @@ import ( "github.com/MizuchiLabs/mantrae/internal/source" ) -const createTraefikConfig = `-- name: CreateTraefikConfig :exec -INSERT INTO - traefik ( - profile_id, - source, - entrypoints, - overview, - version, - config - ) -VALUES - (?, ?, ?, ?, ?, ?) -` - -type CreateTraefikConfigParams struct { - ProfileID int64 `json:"profileId"` - Source source.Source `json:"source"` - Entrypoints *TraefikEntryPoints `json:"entrypoints"` - Overview *TraefikOverview `json:"overview"` - Version *string `json:"version"` - Config *TraefikConfiguration `json:"config"` -} - -func (q *Queries) CreateTraefikConfig(ctx context.Context, arg CreateTraefikConfigParams) error { - _, err := q.exec(ctx, q.createTraefikConfigStmt, createTraefikConfig, - arg.ProfileID, - arg.Source, - arg.Entrypoints, - arg.Overview, - arg.Version, - arg.Config, - ) - return err -} - const deleteTraefikConfig = `-- name: DeleteTraefikConfig :exec DELETE FROM traefik WHERE @@ -57,21 +22,34 @@ func (q *Queries) DeleteTraefikConfig(ctx context.Context, id int64) error { return err } -const getTraefikConfig = `-- name: GetTraefikConfig :one +const deleteTraefikConfigByAgent = `-- name: DeleteTraefikConfigByAgent :exec +DELETE FROM traefik +WHERE + agent_id = ? +` + +func (q *Queries) DeleteTraefikConfigByAgent(ctx context.Context, agentID *string) error { + _, err := q.exec(ctx, q.deleteTraefikConfigByAgentStmt, deleteTraefikConfigByAgent, agentID) + return err +} + +const getAPITraefikConfig = `-- name: GetAPITraefikConfig :one SELECT - id, profile_id, source, entrypoints, overview, config, version, created_at, updated_at + id, profile_id, agent_id, source, entrypoints, overview, config, version, created_at, updated_at FROM traefik WHERE - id = ? + profile_id = ? + AND source = 'api' ` -func (q *Queries) GetTraefikConfig(ctx context.Context, id int64) (Traefik, error) { - row := q.queryRow(ctx, q.getTraefikConfigStmt, getTraefikConfig, id) +func (q *Queries) GetAPITraefikConfig(ctx context.Context, profileID int64) (Traefik, error) { + row := q.queryRow(ctx, q.getAPITraefikConfigStmt, getAPITraefikConfig, profileID) var i Traefik err := row.Scan( &i.ID, &i.ProfileID, + &i.AgentID, &i.Source, &i.Entrypoints, &i.Overview, @@ -83,9 +61,108 @@ func (q *Queries) GetTraefikConfig(ctx context.Context, id int64) (Traefik, erro return i, err } -const getTraefikConfigBySource = `-- name: GetTraefikConfigBySource :one +const getAgentTraefikConfigs = `-- name: GetAgentTraefikConfigs :many SELECT - id, profile_id, source, entrypoints, overview, config, version, created_at, updated_at + id, profile_id, agent_id, source, entrypoints, overview, config, version, created_at, updated_at +FROM + traefik +WHERE + profile_id = ? + AND source = 'agent' +` + +func (q *Queries) GetAgentTraefikConfigs(ctx context.Context, profileID int64) ([]Traefik, error) { + rows, err := q.query(ctx, q.getAgentTraefikConfigsStmt, getAgentTraefikConfigs, profileID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Traefik + for rows.Next() { + var i Traefik + if err := rows.Scan( + &i.ID, + &i.ProfileID, + &i.AgentID, + &i.Source, + &i.Entrypoints, + &i.Overview, + &i.Config, + &i.Version, + &i.CreatedAt, + &i.UpdatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getLocalTraefikConfig = `-- name: GetLocalTraefikConfig :one +SELECT + id, profile_id, agent_id, source, entrypoints, overview, config, version, created_at, updated_at +FROM + traefik +WHERE + profile_id = ? + AND source = 'local' +` + +func (q *Queries) GetLocalTraefikConfig(ctx context.Context, profileID int64) (Traefik, error) { + row := q.queryRow(ctx, q.getLocalTraefikConfigStmt, getLocalTraefikConfig, profileID) + var i Traefik + err := row.Scan( + &i.ID, + &i.ProfileID, + &i.AgentID, + &i.Source, + &i.Entrypoints, + &i.Overview, + &i.Config, + &i.Version, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const getTraefikConfigByID = `-- name: GetTraefikConfigByID :one +SELECT + id, profile_id, agent_id, source, entrypoints, overview, config, version, created_at, updated_at +FROM + traefik +WHERE + id = ? +` + +func (q *Queries) GetTraefikConfigByID(ctx context.Context, id int64) (Traefik, error) { + row := q.queryRow(ctx, q.getTraefikConfigByIDStmt, getTraefikConfigByID, id) + var i Traefik + err := row.Scan( + &i.ID, + &i.ProfileID, + &i.AgentID, + &i.Source, + &i.Entrypoints, + &i.Overview, + &i.Config, + &i.Version, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const getTraefikConfigBySource = `-- name: GetTraefikConfigBySource :many +SELECT + id, profile_id, agent_id, source, entrypoints, overview, config, version, created_at, updated_at FROM traefik WHERE @@ -98,21 +175,38 @@ type GetTraefikConfigBySourceParams struct { Source source.Source `json:"source"` } -func (q *Queries) GetTraefikConfigBySource(ctx context.Context, arg GetTraefikConfigBySourceParams) (Traefik, error) { - row := q.queryRow(ctx, q.getTraefikConfigBySourceStmt, getTraefikConfigBySource, arg.ProfileID, arg.Source) - var i Traefik - err := row.Scan( - &i.ID, - &i.ProfileID, - &i.Source, - &i.Entrypoints, - &i.Overview, - &i.Config, - &i.Version, - &i.CreatedAt, - &i.UpdatedAt, - ) - return i, err +func (q *Queries) GetTraefikConfigBySource(ctx context.Context, arg GetTraefikConfigBySourceParams) ([]Traefik, error) { + rows, err := q.query(ctx, q.getTraefikConfigBySourceStmt, getTraefikConfigBySource, arg.ProfileID, arg.Source) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Traefik + for rows.Next() { + var i Traefik + if err := rows.Scan( + &i.ID, + &i.ProfileID, + &i.AgentID, + &i.Source, + &i.Entrypoints, + &i.Overview, + &i.Config, + &i.Version, + &i.CreatedAt, + &i.UpdatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil } const listTraefikIDs = `-- name: ListTraefikIDs :many @@ -145,36 +239,71 @@ func (q *Queries) ListTraefikIDs(ctx context.Context) ([]int64, error) { return items, nil } -const updateTraefikConfig = `-- name: UpdateTraefikConfig :exec -UPDATE traefik -SET - entrypoints = ?, - overview = ?, - config = ?, - version = ?, - updated_at = CURRENT_TIMESTAMP +const upsertTraefikAgentConfig = `-- name: UpsertTraefikAgentConfig :exec +INSERT INTO + traefik (profile_id, agent_id, source, config) +VALUES + (?, ?, 'agent', ?) ON CONFLICT (profile_id, agent_id) WHERE - profile_id = ? - AND source = ? + agent_id IS NOT NULL + AND source = 'agent' DO +UPDATE +SET + config = excluded.config, + updated_at = CURRENT_TIMESTAMP ` -type UpdateTraefikConfigParams struct { +type UpsertTraefikAgentConfigParams struct { + ProfileID int64 `json:"profileId"` + AgentID *string `json:"agentId"` + Config *TraefikConfiguration `json:"config"` +} + +func (q *Queries) UpsertTraefikAgentConfig(ctx context.Context, arg UpsertTraefikAgentConfigParams) error { + _, err := q.exec(ctx, q.upsertTraefikAgentConfigStmt, upsertTraefikAgentConfig, arg.ProfileID, arg.AgentID, arg.Config) + return err +} + +const upsertTraefikConfig = `-- name: UpsertTraefikConfig :exec +INSERT INTO + traefik ( + profile_id, + source, + entrypoints, + overview, + config, + version + ) +VALUES + (?, ?, ?, ?, ?, ?) ON CONFLICT (profile_id, source) +WHERE + source IN ('local', 'api') DO +UPDATE +SET + entrypoints = excluded.entrypoints, + overview = excluded.overview, + config = excluded.config, + version = excluded.version, + updated_at = CURRENT_TIMESTAMP +` + +type UpsertTraefikConfigParams struct { + ProfileID int64 `json:"profileId"` + Source source.Source `json:"source"` Entrypoints *TraefikEntryPoints `json:"entrypoints"` Overview *TraefikOverview `json:"overview"` Config *TraefikConfiguration `json:"config"` Version *string `json:"version"` - ProfileID int64 `json:"profileId"` - Source source.Source `json:"source"` } -func (q *Queries) UpdateTraefikConfig(ctx context.Context, arg UpdateTraefikConfigParams) error { - _, err := q.exec(ctx, q.updateTraefikConfigStmt, updateTraefikConfig, +func (q *Queries) UpsertTraefikConfig(ctx context.Context, arg UpsertTraefikConfigParams) error { + _, err := q.exec(ctx, q.upsertTraefikConfigStmt, upsertTraefikConfig, + arg.ProfileID, + arg.Source, arg.Entrypoints, arg.Overview, arg.Config, arg.Version, - arg.ProfileID, - arg.Source, ) return err } diff --git a/internal/db/types.go b/internal/db/types.go index 34349f4..3975caf 100644 --- a/internal/db/types.go +++ b/internal/db/types.go @@ -6,6 +6,7 @@ import ( "fmt" "time" + "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/config/runtime" ) @@ -69,9 +70,7 @@ type TraefikVersion struct { } type ServiceInfo struct { - *runtime.ServiceInfo - *runtime.TCPServiceInfo - *runtime.UDPServiceInfo + *dynamic.Service ServerStatus map[string]string `json:"serverStatus,omitempty"` } diff --git a/internal/dns/client.go b/internal/dns/client.go index 623e546..c153616 100644 --- a/internal/dns/client.go +++ b/internal/dns/client.go @@ -74,7 +74,7 @@ func UpdateDNS(DB *sql.DB) (err error) { slog.Error("Failed to get provider", "error", err) continue } - config, err := q.GetTraefikConfig(context.Background(), rdp.TraefikID) + config, err := q.GetTraefikConfigByID(context.Background(), rdp.TraefikID) if err != nil { slog.Error("Failed to get traefik config", "error", err) continue @@ -114,7 +114,7 @@ func DeleteDNS(DB *sql.DB, traefikID int64, routerName string) error { } // Get traefik config to extract domains - config, err := q.GetTraefikConfig(context.Background(), traefikID) + config, err := q.GetTraefikConfigByID(context.Background(), traefikID) if err != nil { return fmt.Errorf("failed to get traefik config: %w", err) } diff --git a/internal/traefik/agent.go b/internal/traefik/agent.go index 6df9601..2a79de1 100644 --- a/internal/traefik/agent.go +++ b/internal/traefik/agent.go @@ -5,27 +5,26 @@ import ( "database/sql" "github.com/MizuchiLabs/mantrae/internal/db" - "github.com/MizuchiLabs/mantrae/internal/source" "github.com/traefik/paerser/parser" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/config/runtime" ) func DecodeAgentConfig(DB *sql.DB, agent db.Agent) error { - var params db.UpdateTraefikConfigParams - params.Source = source.Agent - params.ProfileID = agent.ProfileID - // Initialize merged configuration - params.Config = &db.TraefikConfiguration{ - Routers: make(map[string]*runtime.RouterInfo), - Middlewares: make(map[string]*runtime.MiddlewareInfo), - Services: make(map[string]*db.ServiceInfo), - TCPRouters: make(map[string]*runtime.TCPRouterInfo), - TCPMiddlewares: make(map[string]*runtime.TCPMiddlewareInfo), - TCPServices: make(map[string]*runtime.TCPServiceInfo), - UDPRouters: make(map[string]*runtime.UDPRouterInfo), - UDPServices: make(map[string]*runtime.UDPServiceInfo), + params := db.UpsertTraefikAgentConfigParams{ + ProfileID: agent.ProfileID, + AgentID: &agent.ID, + Config: &db.TraefikConfiguration{ + Routers: make(map[string]*runtime.RouterInfo), + Middlewares: make(map[string]*runtime.MiddlewareInfo), + Services: make(map[string]*db.ServiceInfo), + TCPRouters: make(map[string]*runtime.TCPRouterInfo), + TCPMiddlewares: make(map[string]*runtime.TCPMiddlewareInfo), + TCPServices: make(map[string]*runtime.TCPServiceInfo), + UDPRouters: make(map[string]*runtime.UDPRouterInfo), + UDPServices: make(map[string]*runtime.UDPServiceInfo), + }, } for _, container := range *agent.Containers { @@ -60,44 +59,42 @@ func DecodeAgentConfig(DB *sql.DB, agent db.Agent) error { Servers: []dynamic.Server{{URL: *agent.ActiveIp}}, }, } - params.Config.Services[k] = &db.ServiceInfo{ - ServiceInfo: &runtime.ServiceInfo{Service: &service}, - } + params.Config.Services[k] = &db.ServiceInfo{Service: &service} } for k, v := range runtimeConfig.Middlewares { params.Config.Middlewares[k] = v } - for k, v := range runtimeConfig.TCPRouters { - params.Config.TCPRouters[k] = v - - service := dynamic.TCPService{ - LoadBalancer: &dynamic.TCPServersLoadBalancer{ - Servers: []dynamic.TCPServer{{Address: *agent.ActiveIp}}, - }, - } - params.Config.Services[k] = &db.ServiceInfo{ - TCPServiceInfo: &runtime.TCPServiceInfo{TCPService: &service}, - } - } + // for k, v := range runtimeConfig.TCPRouters { + // params.Config.TCPRouters[k] = v + // + // service := dynamic.TCPService{ + // LoadBalancer: &dynamic.TCPServersLoadBalancer{ + // Servers: []dynamic.TCPServer{{Address: *agent.ActiveIp}}, + // }, + // } + // params.Config.Services[k] = &db.ServiceInfo{ + // Service: &service, + // } + // } for k, v := range runtimeConfig.TCPMiddlewares { params.Config.TCPMiddlewares[k] = v } - for k, v := range runtimeConfig.UDPRouters { - params.Config.UDPRouters[k] = v - - service := dynamic.UDPService{ - LoadBalancer: &dynamic.UDPServersLoadBalancer{ - Servers: []dynamic.UDPServer{{Address: *agent.ActiveIp}}, - }, - } - params.Config.Services[k] = &db.ServiceInfo{ - UDPServiceInfo: &runtime.UDPServiceInfo{UDPService: &service}, - } - } + // for k, v := range runtimeConfig.UDPRouters { + // params.Config.UDPRouters[k] = v + // + // service := dynamic.UDPService{ + // LoadBalancer: &dynamic.UDPServersLoadBalancer{ + // Servers: []dynamic.UDPServer{{Address: *agent.ActiveIp}}, + // }, + // } + // params.Config.Services[k] = &db.ServiceInfo{ + // Service: &service, + // } + // } } q := db.New(DB) - if err := q.UpdateTraefikConfig(context.Background(), params); err != nil { + if err := q.UpsertTraefikAgentConfig(context.Background(), params); err != nil { return err } diff --git a/internal/traefik/client.go b/internal/traefik/client.go index 1c21f1f..27422e3 100644 --- a/internal/traefik/client.go +++ b/internal/traefik/client.go @@ -87,7 +87,7 @@ func GetTraefikConfig(DB *sql.DB) { continue } - if err := q.UpdateTraefikConfig(context.Background(), db.UpdateTraefikConfigParams{ + if err := q.UpsertTraefikConfig(context.Background(), db.UpsertTraefikConfigParams{ ProfileID: profile.ID, Entrypoints: &entrypoints, Overview: &overview, diff --git a/web/src/lib/api.ts b/web/src/lib/api.ts index 023f76e..3258264 100644 --- a/web/src/lib/api.ts +++ b/web/src/lib/api.ts @@ -35,9 +35,9 @@ import type { Overview } from './types/overview'; export const BACKEND_PORT = import.meta.env.PORT || 3000; export const BASE_URL = import.meta.env.PROD ? '/api' : `http://127.0.0.1:${BACKEND_PORT}/api`; -// Stores +// DB Stores export const profiles: Writable = writable(); -export const traefik: Writable = writable(); +export const traefik: Writable = writable(); export const entrypoints: Writable = writable([]); export const overview: Writable = writable({} as Overview); export const version: Writable = writable(''); @@ -308,6 +308,7 @@ export const api = { }, async deleteDNSProvider(id: number) { + if (!id) return; await send(`/dns/${id}`, { method: 'DELETE' }); @@ -315,6 +316,7 @@ export const api = { }, async getRouterDNSProvider(traefikId: number, routerName: string) { + if (!traefikId || !routerName) return; return await send(`/dns/router`, { method: 'GET', body: { traefikId, routerName } @@ -322,6 +324,7 @@ export const api = { }, async listRouterDNSProviders(traefikId: number) { + if (!traefikId) return; const data = await send(`/dns/router/${traefikId}`, { method: 'GET' }); @@ -525,9 +528,10 @@ async function fetchTraefikMetadata(id: number) { } // Set metadata stores - overview.set(res.overview); - entrypoints.set(res.entrypoints); - version.set(res.version); + const meta = res[0]; + overview.set(meta.overview); + entrypoints.set(meta.entrypoints); + version.set(meta.version); // Set middleware names (used for chain) const middlewares = flattenMiddlewareData(res.config); @@ -535,23 +539,23 @@ async function fetchTraefikMetadata(id: number) { return true; } -async function fetchTraefikConfig(id: number, source: TraefikSource) { +async function fetchTraefikConfig(profileID: number, source: TraefikSource) { // Reset stores - traefik.set({} as TraefikConfig); + traefik.set([]); routers.set([]); services.set([]); middlewares.set([]); - const res = await send(`/traefik/${id}/${source}`); + const res = await send(`/traefik/${profileID}/${source}`); if (!res) { return; } // Set stores traefik.set(res); - routers.set(flattenRouterData(res.config)); - services.set(flattenServiceData(res.config)); - middlewares.set(flattenMiddlewareData(res.config)); + routers.set(flattenRouterData(res)); + services.set(flattenServiceData(res)); + middlewares.set(flattenMiddlewareData(res)); // Fetch the router dns relations await api.listRouterDNSProviders(res.id); diff --git a/web/src/lib/components/forms/router.svelte b/web/src/lib/components/forms/router.svelte index 56577a9..e3fe340 100644 --- a/web/src/lib/components/forms/router.svelte +++ b/web/src/lib/components/forms/router.svelte @@ -19,14 +19,14 @@ mode: 'create' | 'edit'; } - let { router = $bindable({} as Router), mode }: Props = $props(); + let { router = $bindable(), mode }: Props = $props(); // Computed properties let routerDNS: RouterDNSProvider = $derived( $rdps?.filter((item) => item.routerName === router.name)[0] ); let rdpName = $derived(routerDNS ? routerDNS.providerName : ''); - let routerProvider = $derived(router.name ? router.name.split('@')[1] : 'http'); + let routerProvider = $derived(router.name ? router.name?.split('@')[1] : 'http'); let isHttpProvider = $derived(routerProvider === 'http' || !routerProvider); let isHttpType = $derived(router.protocol === 'http'); let disabled = $derived(routerProvider !== 'http' && mode === 'edit'); @@ -36,20 +36,15 @@ ) ]); - function handleTypeChange(protocol: 'http' | 'tcp' | 'udp') { - if (disabled) return; - router.protocol = protocol; - } - // let routerDNSProvider = $derived(router.dnsProvider); async function handleDNSProviderChange(providerId: string) { try { if (providerId === '') { - await api.deleteRouterDNSProvider($traefik.id, router.name); + await api.deleteRouterDNSProvider($traefik[0].id, router.name); toast.success('DNS Provider deleted successfully'); } else { if (providerId === undefined) return; - await api.setRouterDNSProvider($traefik.id, parseInt(providerId), router.name); + await api.setRouterDNSProvider($traefik[0].id, parseInt(providerId), router.name); toast.success('DNS Provider updated successfully'); } } catch (err: unknown) { @@ -79,7 +74,7 @@ handleTypeChange('http')} + onPressedChange={() => (router.protocol = 'http')} {disabled} class="font-bold data-[state=on]:bg-green-300 dark:data-[state=on]:text-black" > @@ -88,7 +83,7 @@ handleTypeChange('tcp')} + onPressedChange={() => (router.protocol = 'tcp')} {disabled} class="font-bold data-[state=on]:bg-blue-300 dark:data-[state=on]:text-black" > @@ -97,7 +92,7 @@ handleTypeChange('udp')} + onPressedChange={() => (router.protocol = 'udp')} {disabled} class="font-bold data-[state=on]:bg-red-300 dark:data-[state=on]:text-black" > @@ -114,7 +109,7 @@ id="name" bind:value={router.name} placeholder="Router name" - oninput={() => (router.name = router.name.split('@')[0])} + oninput={() => (router.name = router.name?.split('@')[0])} class="col-span-3" required disabled={disabled || mode === 'edit'} diff --git a/web/src/lib/components/forms/service.svelte b/web/src/lib/components/forms/service.svelte index e2219af..b6d27c6 100644 --- a/web/src/lib/components/forms/service.svelte +++ b/web/src/lib/components/forms/service.svelte @@ -2,43 +2,55 @@ import * as Card from '$lib/components/ui/card/index.js'; import { Switch } from '$lib/components/ui/switch/index.js'; import { Label } from '$lib/components/ui/label/index.js'; + import { Input } from '$lib/components/ui/input/index.js'; + import { Button } from '$lib/components/ui/button/index.js'; import { type Router, type Service } from '$lib/types/router'; - import ArrayInput from '../ui/array-input/array-input.svelte'; + import { Plus, Trash } from 'lucide-svelte'; interface Props { service: Service; router: Router; + mode: 'create' | 'edit'; } - let { service = $bindable({} as Service), router }: Props = $props(); + let { service = $bindable(), router, mode }: Props = $props(); - let routerProvider = $derived(router.name ? router.name.split('@')[1] : 'http'); - let disabled = $derived(routerProvider !== 'http'); + let routerProvider = $derived(router.name ? router.name?.split('@')[1] : 'http'); + let disabled = $derived(routerProvider !== 'http' && mode === 'edit'); - let passHostHeader = $derived(service.loadBalancer?.passHostHeader ?? true); - let servers = $state( - service.loadBalancer?.servers - ?.map((s) => (router.protocol === 'http' ? s.url : s.address)) - .filter((s): s is string => s !== undefined) ?? [] - ); + let passHostHeader = $state(true); + let servers = $state(['']); - function updateServers(newServers: string[]) { + function update() { if (!service.loadBalancer) { service.loadBalancer = { servers: [] }; } - service.protocol = router.protocol; - service.loadBalancer.servers = newServers.map((s) => + service.loadBalancer.servers = servers.map((s) => router.protocol === 'http' ? { url: s } : { address: s } ); + service.loadBalancer.passHostHeader = passHostHeader; } - function updatePassHostHeader(value: boolean) { - if (!service.loadBalancer) { - service.loadBalancer = { servers: [] }; - } - service.loadBalancer.passHostHeader = value; + function addItem() { + servers = [...servers, '']; } + function removeItem(index: number) { + if (index < 1) return; + servers = servers.filter((_, i) => i !== index); + update(); + } + + $effect(() => { + if (service.loadBalancer?.servers) { + servers = service.loadBalancer.servers + .map((s) => (router.protocol === 'http' ? s.url : s.address)) + .filter((s): s is string => s !== undefined); + } + if (service.loadBalancer?.passHostHeader !== undefined) { + passHostHeader = service.loadBalancer.passHostHeader; + } + }); @@ -46,31 +58,47 @@ Service Configuration Configure your - {router.service} - settings + {router.service?.split('@')[0]} + service settings - - {#if router.protocol === 'http'} -
- - -
- {/if} + +
+ {#if router.protocol === 'http'} +
+ + +
+ {/if} - updateServers(detail)} - label="Servers" - placeholder={router.protocol === 'http' ? 'http://192.168.1.1:8080' : '192.168.1.1:8080'} - {disabled} - /> +
+ + + {#each servers || [] as _, i} +
+ + +
+ {/each} +
+ +
diff --git a/web/src/lib/components/modals/info.svelte b/web/src/lib/components/modals/info.svelte index 0986590..e5e647c 100644 --- a/web/src/lib/components/modals/info.svelte +++ b/web/src/lib/components/modals/info.svelte @@ -41,7 +41,7 @@ }; profile.subscribe(async (value) => { - if (value.id) { + if (value.name) { const config = await api.getDynamicConfig(value.name); code = JSON.stringify(config, null, 2); displayCode = code; diff --git a/web/src/lib/components/modals/router.svelte b/web/src/lib/components/modals/router.svelte index 4994917..f34166e 100644 --- a/web/src/lib/components/modals/router.svelte +++ b/web/src/lib/components/modals/router.svelte @@ -9,34 +9,15 @@ import ServiceForm from '../forms/service.svelte'; interface Props { - router?: Router; - service?: Service; + router: Router; + service: Service; open?: boolean; mode: 'create' | 'edit'; } - const defaultRouter: Router = { - name: '', - protocol: 'http', - tls: {}, - entryPoints: [], - middlewares: [], - rule: '', - service: '' - }; - - const defaultService: Service = { - name: '', - protocol: 'http', - loadBalancer: { - servers: [], - passHostHeader: true - } - }; - let { - router = $bindable(defaultRouter), - service = $bindable(defaultService), + router = $bindable(), + service = $bindable(), open = $bindable(false), mode = 'create' }: Props = $props(); @@ -47,9 +28,14 @@ if (!router.name.includes('@')) { router.name = `${router.name}@http`; } + if (service.loadBalancer?.servers?.length === 0) { + toast.error('At least one server is required'); + return; + } // Sync service name with router service.name = router.name; + service.protocol = router.protocol; router.service = router.name; let params: UpsertRouterParams = { @@ -94,7 +80,7 @@ - + diff --git a/web/src/lib/components/tables/DataTable.svelte b/web/src/lib/components/tables/DataTable.svelte index c2ab017..4f9c5f4 100644 --- a/web/src/lib/components/tables/DataTable.svelte +++ b/web/src/lib/components/tables/DataTable.svelte @@ -48,9 +48,16 @@ }; showSourceTabs?: boolean; onRowSelection?: (selectedRows: TData[]) => void; + getRowClassName?: (row: TData) => string; }; - let { data, columns, createButton, showSourceTabs }: DataTableProps = $props(); + let { + data, + columns, + createButton, + showSourceTabs, + getRowClassName + }: DataTableProps = $props(); // Pagination const pageSizeOptions = [10, 20, 30, 40, 50]; @@ -280,7 +287,10 @@ {#each table.getRowModel().rows as row (row.id)} - + {#each row.getVisibleCells() as cell (cell.id)} diff --git a/web/src/lib/types/middlewares.ts b/web/src/lib/types/middlewares.ts index afaf16f..f36b84c 100644 --- a/web/src/lib/types/middlewares.ts +++ b/web/src/lib/types/middlewares.ts @@ -2,7 +2,7 @@ import type { SupportedMiddlewareHTTP, SupportedMiddlewareTCP } from '$lib/components/forms/mw_registry'; -import type { BaseTraefikConfig } from '$lib/types'; +import type { TraefikConfig } from '$lib/types'; import { z } from 'zod'; export type Middleware = HTTPMiddleware | TCPMiddleware; @@ -53,43 +53,47 @@ export interface UpsertMiddlewareParams { tcpMiddleware?: TCPMiddleware; } -export function flattenMiddlewareData(config: BaseTraefikConfig): Middleware[] { +export function flattenMiddlewareData(configs: TraefikConfig[]): Middleware[] { const flatMiddleware: Middleware[] = []; - if (!config) return flatMiddleware; + if (!configs) return flatMiddleware; - Object.entries(config.middlewares || {}).forEach(([name, middleware]) => { - if (middleware) { - const [type, details] = Object.entries(middleware)[0] || [undefined, {}]; - flatMiddleware.push({ - name, - protocol: 'http', - type, - ...details - }); - } else { - flatMiddleware.push({ - name, - protocol: 'http' - }); - } - }); + for (const base of configs) { + const config = base.config; + if (!config) continue; + Object.entries(config.middlewares || {}).forEach(([name, middleware]) => { + if (middleware) { + const [type, details] = Object.entries(middleware)[0] || [undefined, {}]; + flatMiddleware.push({ + name, + protocol: 'http', + type, + ...details + }); + } else { + flatMiddleware.push({ + name, + protocol: 'http' + }); + } + }); - Object.entries(config.tcpMiddlewares || {}).forEach(([name, middleware]) => { - if (middleware) { - const [type, details] = Object.entries(middleware)[0] || [undefined, {}]; - flatMiddleware.push({ - name, - protocol: 'tcp', - type, - ...details - }); - } else { - flatMiddleware.push({ - name, - protocol: 'tcp' - }); - } - }); + Object.entries(config.tcpMiddlewares || {}).forEach(([name, middleware]) => { + if (middleware) { + const [type, details] = Object.entries(middleware)[0] || [undefined, {}]; + flatMiddleware.push({ + name, + protocol: 'tcp', + type, + ...details + }); + } else { + flatMiddleware.push({ + name, + protocol: 'tcp' + }); + } + }); + } return flatMiddleware; } diff --git a/web/src/lib/types/router.ts b/web/src/lib/types/router.ts index dd9b94e..1e3761e 100644 --- a/web/src/lib/types/router.ts +++ b/web/src/lib/types/router.ts @@ -1,4 +1,4 @@ -import type { BaseTraefikConfig } from '$lib/types'; +import type { TraefikConfig } from '$lib/types'; export interface Router { name: string; @@ -26,55 +26,59 @@ export interface UpsertRouterParams { } // Transform function to flatten the router data -export function flattenRouterData(config: BaseTraefikConfig): Router[] { +export function flattenRouterData(configs: TraefikConfig[]): Router[] { const flatRouters: Router[] = []; - if (!config) return flatRouters; + if (!configs) return flatRouters; - // Process HTTP Routers - Object.entries(config.routers || {}).forEach(([name, router]) => { - flatRouters.push({ - name, - protocol: 'http', - entryPoints: router.entryPoints || [], - middlewares: router.middlewares || [], - service: router.service, - rule: router.rule, - ruleSyntax: router.ruleSyntax, - priority: router.priority, - tls: router.tls, - observability: router.observability, - status: router.status + for (const base of configs) { + const config = base.config; + if (!config) continue; + // Process HTTP Routers + Object.entries(config.routers || {}).forEach(([name, router]) => { + flatRouters.push({ + name, + protocol: 'http', + entryPoints: router.entryPoints || [], + middlewares: router.middlewares || [], + service: router.service, + rule: router.rule, + ruleSyntax: router.ruleSyntax, + priority: router.priority, + tls: router.tls, + observability: router.observability, + status: router.status + }); }); - }); - // Process TCP Routers - Object.entries(config.tcpRouters || {}).forEach(([name, router]) => { - flatRouters.push({ - name, - protocol: 'tcp', - entryPoints: router.entryPoints || [], - middlewares: router.middlewares || [], - service: router.service, - rule: router.rule, - ruleSyntax: router.ruleSyntax, - priority: router.priority, - tls: router.tls, - observability: router.observability, - status: router.status + // Process TCP Routers + Object.entries(config.tcpRouters || {}).forEach(([name, router]) => { + flatRouters.push({ + name, + protocol: 'tcp', + entryPoints: router.entryPoints || [], + middlewares: router.middlewares || [], + service: router.service, + rule: router.rule, + ruleSyntax: router.ruleSyntax, + priority: router.priority, + tls: router.tls, + observability: router.observability, + status: router.status + }); }); - }); - // Process UDP Routers - Object.entries(config.udpRouters || {}).forEach(([name, router]) => { - flatRouters.push({ - name, - protocol: 'udp', - entryPoints: router.entryPoints || [], - service: router.service, - observability: router.observability, - status: router.status + // Process UDP Routers + Object.entries(config.udpRouters || {}).forEach(([name, router]) => { + flatRouters.push({ + name, + protocol: 'udp', + entryPoints: router.entryPoints || [], + service: router.service, + observability: router.observability, + status: router.status + }); }); - }); + } return flatRouters; } @@ -100,47 +104,51 @@ export function newService(): Service { }; } -export function flattenServiceData(config: BaseTraefikConfig): Service[] { +export function flattenServiceData(configs: TraefikConfig[]): Service[] { const flatServices: Service[] = []; - if (!config) return flatServices; + if (!configs) return flatServices; - // Process HTTP Services - Object.entries(config.services || {}).forEach(([name, service]) => { - flatServices.push({ - name, - protocol: 'http', - loadBalancer: service.loadBalancer, - weighted: service.weighted, - mirroring: service.mirroring, - failover: service.failover, - status: service.status, - serverStatus: service.serverStatus + for (const base of configs) { + const config = base.config; + if (!config) continue; + // Process HTTP Services + Object.entries(config.services || {}).forEach(([name, service]) => { + flatServices.push({ + name, + protocol: 'http', + loadBalancer: service.loadBalancer, + weighted: service.weighted, + mirroring: service.mirroring, + failover: service.failover, + status: service.status, + serverStatus: service.serverStatus + }); }); - }); - // Process TCP Services - Object.entries(config.tcpServices || {}).forEach(([name, service]) => { - flatServices.push({ - name, - protocol: 'tcp', - loadBalancer: service.loadBalancer, - weighted: service.weighted, - status: service.status, - serverStatus: service.serverStatus + // Process TCP Services + Object.entries(config.tcpServices || {}).forEach(([name, service]) => { + flatServices.push({ + name, + protocol: 'tcp', + loadBalancer: service.loadBalancer, + weighted: service.weighted, + status: service.status, + serverStatus: service.serverStatus + }); }); - }); - // Process UDP Services - Object.entries(config.udpServices || {}).forEach(([name, service]) => { - flatServices.push({ - name, - protocol: 'udp', - loadBalancer: service.loadBalancer, - weighted: service.weighted, - status: service.status, - serverStatus: service.serverStatus + // Process UDP Services + Object.entries(config.udpServices || {}).forEach(([name, service]) => { + flatServices.push({ + name, + protocol: 'udp', + loadBalancer: service.loadBalancer, + weighted: service.weighted, + status: service.status, + serverStatus: service.serverStatus + }); }); - }); + } return flatServices; } diff --git a/web/src/routes/agents/+page.svelte b/web/src/routes/agents/+page.svelte index c07ba2a..474149d 100644 --- a/web/src/routes/agents/+page.svelte +++ b/web/src/routes/agents/+page.svelte @@ -47,9 +47,8 @@ const name = row.getValue('hostname') as string; if (!name) { return 'Connect your agent!'; - } else { - return name; } + return name; } }, { @@ -57,25 +56,6 @@ accessorKey: 'activeIp', enableSorting: true }, - // { - // header: 'Containers', - // accessorKey: 'containers', - // enableSorting: true, - // cell: ({ row }) => { - // const admin = row.getValue('isAdmin') as boolean; - // if (admin) { - // return renderComponent(ColumnBadge, { - // label: 'Yes', - // variant: 'default' - // }); - // } else { - // return renderComponent(ColumnBadge, { - // label: 'No', - // variant: 'secondary' - // }); - // } - // } - // }, { header: 'Last Seen', accessorKey: 'updatedAt', @@ -128,6 +108,16 @@ } ]; + function agentOffline(agent: Agent): boolean { + const lastSeen = new Date(agent.updatedAt); + const now = new Date(); + const diffInMinutes = (now.getTime() - lastSeen.getTime()) / (1000 * 60); + return diffInMinutes <= 2; + } + function getAgentRowClassName(agent: Agent): string { + return agentOffline(agent) ? 'bg-green-500/10' : 'bg-red-500/10'; + } + profile.subscribe((value) => { if (value.id) { api.listAgentsByProfile(); @@ -147,6 +137,7 @@ api.createAgent() diff --git a/web/src/routes/middlewares/+page.svelte b/web/src/routes/middlewares/+page.svelte index cb53cea..e3f1452 100644 --- a/web/src/routes/middlewares/+page.svelte +++ b/web/src/routes/middlewares/+page.svelte @@ -108,7 +108,6 @@ label: 'Edit Middleware', icon: Pencil, onClick: () => { - console.log(row.original); modalState = { isOpen: true, middleware: row.original diff --git a/web/src/routes/plugins/+page.svelte b/web/src/routes/plugins/+page.svelte index c51c547..25c164e 100644 --- a/web/src/routes/plugins/+page.svelte +++ b/web/src/routes/plugins/+page.svelte @@ -62,7 +62,6 @@ } } }; - console.log(middleware); await api.upsertMiddleware($profile.id, middleware); selectedPlugin = plugin; diff --git a/web/src/routes/router/+page.svelte b/web/src/routes/router/+page.svelte index 5426c08..aa1bc4c 100644 --- a/web/src/routes/router/+page.svelte +++ b/web/src/routes/router/+page.svelte @@ -16,17 +16,41 @@ interface ModalState { isOpen: boolean; mode: 'create' | 'edit'; - router?: Router; - service?: Service; + router: Router; + service: Service; } - const initialModalState: ModalState = { isOpen: false, mode: 'create' }; + const defaultRouter: Router = { + name: '', + protocol: 'http', + tls: {}, + entryPoints: [], + middlewares: [], + rule: '', + service: '' + }; + const defaultService: Service = { + name: '', + protocol: 'http', + loadBalancer: { + servers: [], + passHostHeader: true + } + }; + const initialModalState: ModalState = { + isOpen: false, + mode: 'create', + router: defaultRouter, + service: defaultService + }; let modalState = $state(initialModalState); function openCreateModal() { modalState = { isOpen: true, - mode: 'create' + mode: 'create', + router: defaultRouter, + service: defaultService }; } @@ -283,6 +307,6 @@