diff --git a/.github/workflows/cleanup.yml b/.github/workflows/cleanup.yml index 2123962..ae2c301 100644 --- a/.github/workflows/cleanup.yml +++ b/.github/workflows/cleanup.yml @@ -4,7 +4,7 @@ on: push: branches: - main - + jobs: build: runs-on: ubuntu-latest diff --git a/internal/config/backup.go b/internal/config/backup.go index be04015..f353eb7 100644 --- a/internal/config/backup.go +++ b/internal/config/backup.go @@ -25,10 +25,10 @@ var backupCron *cron.Cron // BackupData is the structure for the full manual backup type BackupData struct { Profiles []db.Profile `json:"profiles"` - Configs []*traefik.Dynamic `json:"configs"` Providers []db.Provider `json:"providers"` Settings []db.Setting `json:"settings"` Users []db.User `json:"users"` + Configs []*traefik.Dynamic `json:"configs"` } func DumpBackup(ctx context.Context) (*BackupData, error) { @@ -44,12 +44,37 @@ func DumpBackup(ctx context.Context) (*BackupData, error) { if err != nil { return nil, fmt.Errorf("failed to get configs: %w", err) } + + // We're only interested in the our local provider for _, config := range configs { dynamic, err := traefik.DecodeConfig(config) if err != nil { return nil, fmt.Errorf("failed to decode config: %w", err) } - data.Configs = append(data.Configs, dynamic) + + newDynamic := &traefik.Dynamic{ + ProfileID: config.ProfileID, + Routers: make(map[string]traefik.Router), + Services: make(map[string]traefik.Service), + Middlewares: make(map[string]traefik.Middleware), + } + for i, router := range dynamic.Routers { + if router.Provider == "http" { + newDynamic.Routers[i] = router + } + } + for i, service := range dynamic.Services { + if service.Provider == "http" { + newDynamic.Services[i] = service + } + } + for i, middleware := range dynamic.Middlewares { + if middleware.Provider == "http" { + newDynamic.Middlewares[i] = middleware + } + } + + data.Configs = append(data.Configs, newDynamic) } data.Providers, err = db.Query.ListProviders(ctx) diff --git a/pkg/traefik/client.go b/pkg/traefik/client.go index e3239b5..25445d3 100644 --- a/pkg/traefik/client.go +++ b/pkg/traefik/client.go @@ -450,7 +450,7 @@ func GetTraefikConfig() { // Sync periodically syncs the Traefik configuration func Sync(ctx context.Context) { - ticker := time.NewTicker(time.Second * 60) + ticker := time.NewTicker(time.Second * 10) defer ticker.Stop() GetTraefikConfig() diff --git a/web/src/lib/components/forms/headers.svelte b/web/src/lib/components/forms/headers.svelte index 9d2c3da..8f22e65 100644 --- a/web/src/lib/components/forms/headers.svelte +++ b/web/src/lib/components/forms/headers.svelte @@ -127,7 +127,6 @@ const validate = () => { try { middleware.headers = headersSchema.parse(middleware.headers); - console.log(middleware.headers.stsSeconds); errors = {}; } catch (err) { if (err instanceof z.ZodError) { diff --git a/web/src/lib/components/forms/middleware.svelte b/web/src/lib/components/forms/middleware.svelte index 837b410..b59b401 100644 --- a/web/src/lib/components/forms/middleware.svelte +++ b/web/src/lib/components/forms/middleware.svelte @@ -83,7 +83,6 @@ $: isNameTaken = $middlewares.some((m) => m.name === middleware.name + '@' + middleware.provider); onMount(async () => { - console.log(middleware); checkType(); form = await LoadMiddlewareForm(middleware); }); diff --git a/web/src/lib/components/forms/router.svelte b/web/src/lib/components/forms/router.svelte index c3234f7..c3e523c 100644 --- a/web/src/lib/components/forms/router.svelte +++ b/web/src/lib/components/forms/router.svelte @@ -14,7 +14,7 @@ toggleMiddleware, toggleDNSProvider } from '$lib/api'; - import { newRouter, type Router } from '$lib/types/config'; + import { type Router } from '$lib/types/config'; import RuleEditor from '../utils/ruleEditor.svelte'; import ArrayInput from '../ui/array-input/array-input.svelte'; import logo from '$lib/images/logo.svg'; @@ -25,23 +25,39 @@ export let router: Router; export let disabled = false; - const formSchema = z.object({ - name: z.string({ required_error: 'Name is required' }).min(1).max(255), - provider: z.string().optional(), - status: z.string().optional(), - routerType: z - .string() - .toLowerCase() - .regex(/^(http|tcp|udp)$/), - dnsProvider: z.coerce.number().int().nonnegative().optional(), - entrypoints: z.array(z.string()).optional(), - middlewares: z.array(z.string()).optional(), - rule: z.string({ required_error: 'Rule is required' }).min(1, { message: 'Rule is required' }), - priority: z.coerce.number().int().nonnegative().optional(), - tls: z.object({ - certResolver: z.string().trim().optional() + const formSchema = z + .object({ + name: z.string().trim().min(1, 'Name is required').max(255), + provider: z.string().trim().optional(), + status: z.string().trim().optional(), + routerType: z + .string() + .toLowerCase() + .regex(/^(http|tcp|udp)$/), + dnsProvider: z.coerce.number().int().nonnegative().optional(), + entrypoints: z.array(z.string()).optional(), + middlewares: z.array(z.string()).optional(), + rule: z.string().trim().optional(), + priority: z.coerce.number().int().nonnegative().optional(), + tls: z + .object({ + certResolver: z.string().trim().optional() + }) + .optional() }) - }); + .refine( + (data) => { + // Conditionally check if `rule` is required for `http` or `tcp` + if (['http', 'tcp'].includes(data.routerType) && !data.rule) { + return false; + } + return true; + }, + { + message: 'Rule is required for HTTP and TCP routers', + path: ['rule'] // This points to the 'rule' field + } + ); let errors: Record = {}; export const validate = () => { @@ -298,6 +314,9 @@ {#if router.routerType === 'http' || router.routerType === 'tcp'} + {#if errors.rule} +
{errors.rule}
+ {/if} {/if} diff --git a/web/src/lib/components/forms/service.svelte b/web/src/lib/components/forms/service.svelte index 156facd..a93c352 100644 --- a/web/src/lib/components/forms/service.svelte +++ b/web/src/lib/components/forms/service.svelte @@ -24,7 +24,19 @@ .regex(/^(http|tcp|udp)$/), serverStatus: z.record(z.string()).optional(), loadBalancer: z.object({ - servers: z.array(z.object({ url: z.string() }).or(z.object({ address: z.string() }))), + servers: z + .array( + z + .object({ + url: z.string().trim().optional(), + address: z.string().trim().optional() + }) + .refine((data) => data.url || data.address, { + message: 'At least one server is required', + path: ['servers'] // Points to the 'servers' array in case of error + }) + ) + .nonempty('At least one server is required'), passHostHeader: z.boolean().optional() }) }); @@ -33,7 +45,6 @@ export const validate = () => { try { serviceSchema.parse({ ...service }); - errors = {}; return true; } catch (err) { @@ -45,7 +56,6 @@ }; const update = () => { - validate(); if (service.loadBalancer === undefined) service.loadBalancer = { servers: [] }; service.loadBalancer.passHostHeader = passHostHeader;