Implement auto-fill variables for checker option fields
Add an AutoFill attribute to the Field struct that marks option fields as automatically resolved by the software based on test context, rather than requiring user input. Auto-fill always overrides any user-provided value at execution time.
This commit is contained in:
parent
771b8bf1a8
commit
cc75779fbd
12 changed files with 277 additions and 116 deletions
|
|
@ -23,8 +23,6 @@ package controller
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"maps"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
|
@ -157,55 +155,8 @@ func (tc *CheckResultController) TriggerCheck(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
// Merge options with upper levels (user, domain, service)
|
||||
var domainID, serviceID *happydns.Identifier
|
||||
switch tc.scope {
|
||||
case happydns.CheckScopeDomain:
|
||||
domainID = &targetID
|
||||
case happydns.CheckScopeService:
|
||||
serviceID = &targetID
|
||||
}
|
||||
|
||||
mergedOptions := make(happydns.CheckerOptions)
|
||||
|
||||
// Fill opts with default plugin options
|
||||
checker, err := tc.checkerUC.GetChecker(checkName)
|
||||
if err != nil {
|
||||
log.Printf("Warning: unable to get plugin %q for default options: %v", checkName, err)
|
||||
} else {
|
||||
options := checker.Options()
|
||||
|
||||
// Collect all option documentation from different scopes
|
||||
allOpts := []happydns.CheckerOptionDocumentation{}
|
||||
allOpts = append(allOpts, options.RunOpts...)
|
||||
allOpts = append(allOpts, options.ServiceOpts...)
|
||||
allOpts = append(allOpts, options.DomainOpts...)
|
||||
allOpts = append(allOpts, options.UserOpts...)
|
||||
allOpts = append(allOpts, options.AdminOpts...)
|
||||
|
||||
// Fill defaults
|
||||
for _, opt := range allOpts {
|
||||
if opt.Default != nil {
|
||||
mergedOptions[opt.Id] = opt.Default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get merged options from upper levels
|
||||
baseOptions, err := tc.checkerUC.GetCheckerOptions(checkName, &user.Id, domainID, serviceID)
|
||||
if err != nil {
|
||||
middleware.ErrorResponse(c, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Merge request options on top of base options (request options override)
|
||||
if baseOptions != nil {
|
||||
maps.Copy(mergedOptions, *baseOptions)
|
||||
}
|
||||
maps.Copy(mergedOptions, options.Options)
|
||||
|
||||
// Trigger the check via scheduler (returns error if scheduler is disabled)
|
||||
executionID, err := tc.checkScheduler.TriggerOnDemandCheck(checkName, tc.scope, targetID, user.Id, mergedOptions)
|
||||
// Trigger the test via scheduler (returns error if scheduler is disabled)
|
||||
executionID, err := tc.checkScheduler.TriggerOnDemandCheck(checkName, tc.scope, targetID, user.Id, options.Options)
|
||||
if err != nil {
|
||||
middleware.ErrorResponse(c, http.StatusInternalServerError, err)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -264,7 +264,7 @@ func (app *App) initUsecases() {
|
|||
app.usecases.authUser = authUserService
|
||||
app.usecases.resolver = usecase.NewResolverUsecase(app.cfg)
|
||||
app.usecases.session = sessionService
|
||||
app.usecases.checker = checkUC.NewCheckerUsecase(app.cfg, app.store)
|
||||
app.usecases.checker = checkUC.NewCheckerUsecase(app.cfg, app.store, app.store)
|
||||
app.usecases.checkerSchedule = checkresultUC.NewCheckScheduleUsecase(app.store, app.cfg, app.store, app.usecases.checker)
|
||||
app.usecases.checkResult = checkresultUC.NewCheckResultUsecase(app.store, app.cfg, app.usecases.checker, app.usecases.checkerSchedule)
|
||||
|
||||
|
|
|
|||
|
|
@ -495,9 +495,9 @@ func (w *worker) executeCheck(item *queueItem) {
|
|||
|
||||
// Always update schedule NextRun after execution, whether it succeeds or fails.
|
||||
// This prevents the schedule from being re-queued on the next tick if the test fails.
|
||||
if item.execution.ScheduleId != nil {
|
||||
if execution.ScheduleId != nil {
|
||||
defer func() {
|
||||
if err := w.scheduler.scheduleUsecase.UpdateScheduleAfterRun(*item.execution.ScheduleId); err != nil {
|
||||
if err := w.scheduler.scheduleUsecase.UpdateScheduleAfterRun(*execution.ScheduleId); err != nil {
|
||||
log.Printf("Worker %d: Error updating schedule after run: %v\n", w.id, err)
|
||||
}
|
||||
}()
|
||||
|
|
@ -536,11 +536,20 @@ func (w *worker) executeCheck(item *queueItem) {
|
|||
return
|
||||
}
|
||||
|
||||
// Merge options: global defaults < user opts < domain/service opts < schedule opts
|
||||
mergedOptions, err := w.scheduler.scheduleUsecase.PrepareCheckOptions(schedule)
|
||||
if err != nil {
|
||||
// Non-fatal: PrepareTestOptions already falls back to schedule-only options
|
||||
log.Printf("Worker %d: warning, could not prepare plugin options for %s: %v\n", w.id, schedule.CheckerName, err)
|
||||
var domainId, serviceId *happydns.Identifier
|
||||
switch schedule.TargetType {
|
||||
case happydns.CheckScopeDomain:
|
||||
domainId = &schedule.TargetId
|
||||
case happydns.CheckScopeService:
|
||||
serviceId = &schedule.TargetId
|
||||
}
|
||||
|
||||
// Merge options: global defaults < user opts < domain/service opts < schedule/on-demand opts < auto-fill
|
||||
mergedOptions, mergeErr := w.scheduler.checkerUsecase.BuildMergedCheckerOptions(schedule.CheckerName, &schedule.OwnerId, domainId, serviceId, schedule.Options)
|
||||
if mergeErr != nil {
|
||||
// Non-fatal: fall back to schedule-only options
|
||||
log.Printf("Worker %d: warning, could not prepare checker options for %s: %v\n", w.id, schedule.CheckerName, mergeErr)
|
||||
mergedOptions = schedule.Options
|
||||
}
|
||||
|
||||
// Prepare metadata
|
||||
|
|
|
|||
|
|
@ -44,3 +44,19 @@ type CheckerStorage interface {
|
|||
// ClearCheckerConfigurations deletes all Providers present in the database.
|
||||
ClearCheckerConfigurations() error
|
||||
}
|
||||
|
||||
// CheckAutoFillStorage provides the domain/zone/user lookups needed to
|
||||
// resolve auto-fill variables for test check options.
|
||||
type CheckAutoFillStorage interface {
|
||||
// GetDomain retrieves the Domain with the given identifier.
|
||||
GetDomain(domainid happydns.Identifier) (*happydns.Domain, error)
|
||||
|
||||
// GetUser retrieves the User with the given identifier.
|
||||
GetUser(userid happydns.Identifier) (*happydns.User, error)
|
||||
|
||||
// ListDomains retrieves all Domains associated to the given User.
|
||||
ListDomains(user *happydns.User) ([]*happydns.Domain, error)
|
||||
|
||||
// GetZone retrieves the full Zone (including Services and metadata) for the given identifier.
|
||||
GetZone(zoneid happydns.Identifier) (*happydns.ZoneMessage, error)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ package check
|
|||
import (
|
||||
"cmp"
|
||||
"fmt"
|
||||
"log"
|
||||
"maps"
|
||||
"slices"
|
||||
|
||||
|
|
@ -33,14 +34,16 @@ import (
|
|||
)
|
||||
|
||||
type checkerUsecase struct {
|
||||
config *happydns.Options
|
||||
store CheckerStorage
|
||||
config *happydns.Options
|
||||
store CheckerStorage
|
||||
autoFillStore CheckAutoFillStorage
|
||||
}
|
||||
|
||||
func NewCheckerUsecase(cfg *happydns.Options, store CheckerStorage) happydns.CheckerUsecase {
|
||||
func NewCheckerUsecase(cfg *happydns.Options, store CheckerStorage, autoFillStore CheckAutoFillStorage) happydns.CheckerUsecase {
|
||||
return &checkerUsecase{
|
||||
config: cfg,
|
||||
store: store,
|
||||
config: cfg,
|
||||
store: store,
|
||||
autoFillStore: autoFillStore,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -120,6 +123,168 @@ func (tu *checkerUsecase) ListCheckers() (*map[string]happydns.Checker, error) {
|
|||
return checks.GetCheckers(), nil
|
||||
}
|
||||
|
||||
// GetStoredCheckerOptionsNoDefault returns the stored options (user/domain/service scopes)
|
||||
// with auto-fill variables applied, but without checker-defined defaults or run-time overrides.
|
||||
func (tu *checkerUsecase) GetStoredCheckerOptionsNoDefault(cname string, userid *happydns.Identifier, domainid *happydns.Identifier, serviceid *happydns.Identifier) (happydns.CheckerOptions, error) {
|
||||
stored, err := tu.GetCheckerOptions(cname, userid, domainid, serviceid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var opts happydns.CheckerOptions
|
||||
if stored != nil {
|
||||
opts = *stored
|
||||
} else {
|
||||
opts = make(happydns.CheckerOptions)
|
||||
}
|
||||
|
||||
checker, err := tu.GetChecker(cname)
|
||||
if err != nil {
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
return tu.applyAutoFill(checker, userid, domainid, serviceid, opts), nil
|
||||
}
|
||||
|
||||
// BuildMergedCheckerOptions merges checker options from all sources in priority order:
|
||||
// checker defaults < stored (user/domain/service) options < runOpts < auto-fill variables.
|
||||
func (tu *checkerUsecase) BuildMergedCheckerOptions(cname string, userid *happydns.Identifier, domainid *happydns.Identifier, serviceid *happydns.Identifier, runOpts happydns.CheckerOptions) (happydns.CheckerOptions, error) {
|
||||
merged := make(happydns.CheckerOptions)
|
||||
|
||||
// 1. Fill checker defaults.
|
||||
checker, err := tu.GetChecker(cname)
|
||||
if err != nil {
|
||||
log.Printf("Warning: unable to get checker %q for default options: %v", cname, err)
|
||||
} else {
|
||||
opts := checker.Options()
|
||||
|
||||
allOpts := []happydns.CheckerOptionDocumentation{}
|
||||
allOpts = append(allOpts, opts.RunOpts...)
|
||||
allOpts = append(allOpts, opts.ServiceOpts...)
|
||||
allOpts = append(allOpts, opts.DomainOpts...)
|
||||
allOpts = append(allOpts, opts.UserOpts...)
|
||||
allOpts = append(allOpts, opts.AdminOpts...)
|
||||
for _, opt := range allOpts {
|
||||
if opt.Default != nil {
|
||||
merged[opt.Id] = opt.Default
|
||||
} else if opt.Placeholder != "" {
|
||||
merged[opt.Id] = opt.Placeholder
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Override with stored options (user/domain/service scopes).
|
||||
baseOptions, err := tu.GetCheckerOptions(cname, userid, domainid, serviceid)
|
||||
if err != nil {
|
||||
return merged, fmt.Errorf("could not fetch stored checker options for %s: %w", cname, err)
|
||||
}
|
||||
if baseOptions != nil {
|
||||
copyNonEmpty(merged, *baseOptions)
|
||||
}
|
||||
|
||||
// 3. Override with caller-supplied run options.
|
||||
copyNonEmpty(merged, runOpts)
|
||||
|
||||
// 4. Inject auto-fill variables (always win over any user-supplied value).
|
||||
if checker != nil {
|
||||
merged = tu.applyAutoFill(checker, userid, domainid, serviceid, merged)
|
||||
}
|
||||
|
||||
return merged, nil
|
||||
}
|
||||
|
||||
// applyAutoFill resolves auto-fill fields declared by the checker and injects
|
||||
// the context-resolved values into a copy of opts.
|
||||
func (tu *checkerUsecase) applyAutoFill(
|
||||
checker happydns.Checker,
|
||||
userid *happydns.Identifier,
|
||||
domainid *happydns.Identifier,
|
||||
serviceid *happydns.Identifier,
|
||||
opts happydns.CheckerOptions,
|
||||
) happydns.CheckerOptions {
|
||||
// Collect which auto-fill keys are needed.
|
||||
needed := make(map[string]string) // autoFill constant → field id
|
||||
options := checker.Options()
|
||||
for _, groups := range [][]happydns.CheckerOptionDocumentation{
|
||||
options.RunOpts, options.DomainOpts, options.ServiceOpts,
|
||||
options.UserOpts, options.AdminOpts,
|
||||
} {
|
||||
for _, opt := range groups {
|
||||
if opt.AutoFill != "" {
|
||||
needed[opt.AutoFill] = opt.Id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(needed) == 0 || tu.autoFillStore == nil {
|
||||
return opts
|
||||
}
|
||||
|
||||
autoFillCtx := tu.buildAutoFillContext(userid, domainid, serviceid)
|
||||
|
||||
result := maps.Clone(opts)
|
||||
for autoFillKey, fieldId := range needed {
|
||||
if val, ok := autoFillCtx[autoFillKey]; ok {
|
||||
result[fieldId] = val
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// buildAutoFillContext resolves the available auto-fill values for the given
|
||||
// user/domain/service identifiers.
|
||||
func (tu *checkerUsecase) buildAutoFillContext(userid *happydns.Identifier, domainid *happydns.Identifier, serviceid *happydns.Identifier) map[string]any {
|
||||
ctx := make(map[string]any)
|
||||
|
||||
if domainid != nil {
|
||||
if domain, err := tu.autoFillStore.GetDomain(*domainid); err == nil {
|
||||
ctx[happydns.AutoFillDomainName] = domain.DomainName
|
||||
|
||||
if len(domain.ZoneHistory) > 0 {
|
||||
// The first element in ZoneHistory is the current (most recent) zone.
|
||||
zoneMsg, err := tu.autoFillStore.GetZone(domain.ZoneHistory[0])
|
||||
if err == nil {
|
||||
ctx[happydns.AutoFillZone] = zoneMsg
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if serviceid != nil && userid != nil {
|
||||
// To resolve service context we need to find which domain/zone owns the service.
|
||||
user, err := tu.autoFillStore.GetUser(*userid)
|
||||
if err != nil {
|
||||
return ctx
|
||||
}
|
||||
domains, err := tu.autoFillStore.ListDomains(user)
|
||||
if err != nil {
|
||||
return ctx
|
||||
}
|
||||
for _, domain := range domains {
|
||||
if len(domain.ZoneHistory) == 0 {
|
||||
continue
|
||||
}
|
||||
// The first element in ZoneHistory is the current (most recent) zone.
|
||||
zoneMsg, err := tu.autoFillStore.GetZone(domain.ZoneHistory[0])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for subdomain, svcs := range zoneMsg.Services {
|
||||
for _, svc := range svcs {
|
||||
if svc.Id.Equals(*serviceid) {
|
||||
ctx[happydns.AutoFillDomainName] = domain.DomainName
|
||||
ctx[happydns.AutoFillSubdomain] = string(subdomain)
|
||||
ctx[happydns.AutoFillZone] = zoneMsg
|
||||
ctx[happydns.AutoFillService] = svc
|
||||
ctx[happydns.AutoFillServiceType] = svc.Type
|
||||
return ctx
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (tu *checkerUsecase) SetCheckerOptions(cname string, userid *happydns.Identifier, domainid *happydns.Identifier, serviceid *happydns.Identifier, opts happydns.CheckerOptions) error {
|
||||
// filter opts that correspond to the level set
|
||||
checker, err := tu.GetChecker(cname)
|
||||
|
|
@ -129,34 +294,30 @@ func (tu *checkerUsecase) SetCheckerOptions(cname string, userid *happydns.Ident
|
|||
|
||||
options := checker.Options()
|
||||
|
||||
var optNames []string
|
||||
var relevantOpts []happydns.CheckerOptionDocumentation
|
||||
if serviceid != nil {
|
||||
for _, opt := range options.ServiceOpts {
|
||||
optNames = append(optNames, opt.Id)
|
||||
}
|
||||
relevantOpts = options.ServiceOpts
|
||||
} else if domainid != nil {
|
||||
for _, opt := range options.DomainOpts {
|
||||
optNames = append(optNames, opt.Id)
|
||||
}
|
||||
relevantOpts = options.DomainOpts
|
||||
} else if userid != nil {
|
||||
for _, opt := range options.UserOpts {
|
||||
optNames = append(optNames, opt.Id)
|
||||
}
|
||||
relevantOpts = options.UserOpts
|
||||
} else {
|
||||
for _, opt := range options.AdminOpts {
|
||||
optNames = append(optNames, opt.Id)
|
||||
}
|
||||
relevantOpts = options.AdminOpts
|
||||
}
|
||||
|
||||
allowed := make(map[string]struct{}, len(relevantOpts))
|
||||
for _, opt := range relevantOpts {
|
||||
allowed[opt.Id] = struct{}{}
|
||||
}
|
||||
|
||||
// Filter opts to only include keys that are in optNames
|
||||
filteredOpts := make(happydns.CheckerOptions)
|
||||
for _, optName := range optNames {
|
||||
if val, exists := opts[optName]; exists && val != "" {
|
||||
filteredOpts[optName] = val
|
||||
for id := range allowed {
|
||||
if val, exists := opts[id]; exists && val != "" {
|
||||
filteredOpts[id] = val
|
||||
}
|
||||
}
|
||||
|
||||
return tu.store.UpdateCheckerConfiguration(cname, userid, domainid, serviceid, opts)
|
||||
return tu.store.UpdateCheckerConfiguration(cname, userid, domainid, serviceid, filteredOpts)
|
||||
}
|
||||
|
||||
func (tu *checkerUsecase) OverwriteSomeCheckerOptions(cname string, userid *happydns.Identifier, domainid *happydns.Identifier, serviceid *happydns.Identifier, opts happydns.CheckerOptions) error {
|
||||
|
|
@ -221,7 +382,7 @@ func (tu *checkerUsecase) ValidateCheckerOptions(cname string, opts happydns.Che
|
|||
return fmt.Errorf("unknown option %q for checker %q", name, cname)
|
||||
}
|
||||
|
||||
if doc.Type == "" {
|
||||
if doc.AutoFill != "" || doc.Type == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -456,31 +456,3 @@ func (u *CheckScheduleUsecase) DiscoverAndEnsureSchedules() error {
|
|||
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
// PrepareCheckOptions fetches and merges plugin options for a scheduled check execution.
|
||||
// It combines stored options (global/user/domain/service scopes) with the
|
||||
// schedule-specific overrides, returning the final merged options.
|
||||
func (u *CheckScheduleUsecase) PrepareCheckOptions(schedule *happydns.CheckerSchedule) (happydns.CheckerOptions, error) {
|
||||
if u.checkerUsecase == nil {
|
||||
return schedule.Options, nil
|
||||
}
|
||||
|
||||
var domainId, serviceId *happydns.Identifier
|
||||
switch schedule.TargetType {
|
||||
case happydns.CheckScopeDomain:
|
||||
domainId = &schedule.TargetId
|
||||
case happydns.CheckScopeService:
|
||||
serviceId = &schedule.TargetId
|
||||
}
|
||||
|
||||
baseOptions, err := u.checkerUsecase.GetCheckerOptions(schedule.CheckerName, &schedule.OwnerId, domainId, serviceId)
|
||||
if err != nil {
|
||||
// Non-fatal: fall back to schedule-only options and surface as a warning
|
||||
return schedule.Options, fmt.Errorf("could not fetch plugin options for %s: %w", schedule.CheckerName, err)
|
||||
}
|
||||
|
||||
if baseOptions != nil {
|
||||
return u.MergeCheckOptions(nil, nil, *baseOptions, schedule.Options), nil
|
||||
}
|
||||
return schedule.Options, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -144,7 +144,4 @@ type CheckerScheduleUsecase interface {
|
|||
// RescheduleOverdueTests reschedules overdue tests to run soon, spread over a
|
||||
// short window to avoid scheduler famine after a suspend or server restart.
|
||||
RescheduleOverdueChecks() (int, error)
|
||||
|
||||
// PrepareCheckOptions fetches and merges plugin options for a scheduled check execution.
|
||||
PrepareCheckOptions(schedule *CheckerSchedule) (CheckerOptions, error)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,30 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
// Auto-fill variable identifiers for checker option fields.
|
||||
const (
|
||||
// AutoFillDomainName fills the option with the fully qualified domain name
|
||||
// of the domain being tested (e.g. "example.com.").
|
||||
AutoFillDomainName = "domain_name"
|
||||
|
||||
// AutoFillSubdomain fills the option with the subdomain relative to the zone
|
||||
// (e.g. "www" for "www.example.com." in zone "example.com."). Only
|
||||
// applicable for service-scoped tests.
|
||||
AutoFillSubdomain = "subdomain"
|
||||
|
||||
// AutoFillZone fills the option with the zone object. Only applicable
|
||||
// for domain-scoped and service-scoped tests.
|
||||
AutoFillZone = "zone"
|
||||
|
||||
// AutoFillServiceType fills the option with the service type identifier
|
||||
// (e.g. "abstract.MatrixIM"). Only applicable for service-scoped tests.
|
||||
AutoFillServiceType = "service_type"
|
||||
|
||||
// AutoFillService fills the option with the service object. Only applicable
|
||||
// for service-scoped tests.
|
||||
AutoFillService = "service"
|
||||
)
|
||||
|
||||
const (
|
||||
CheckResultStatusUnknown CheckResultStatus = iota
|
||||
CheckResultStatusCritical
|
||||
|
|
@ -110,6 +134,8 @@ type CheckerStatus struct {
|
|||
}
|
||||
|
||||
type CheckerUsecase interface {
|
||||
BuildMergedCheckerOptions(string, *Identifier, *Identifier, *Identifier, CheckerOptions) (CheckerOptions, error)
|
||||
GetStoredCheckerOptionsNoDefault(string, *Identifier, *Identifier, *Identifier) (CheckerOptions, error)
|
||||
GetChecker(string) (Checker, error)
|
||||
GetCheckerOptions(string, *Identifier, *Identifier, *Identifier) (*CheckerOptions, error)
|
||||
GetCheckerResponse(Checker) CheckerResponse
|
||||
|
|
|
|||
|
|
@ -104,6 +104,11 @@ type Field struct {
|
|||
|
||||
// Description stores an helpfull sentence describing the field.
|
||||
Description string `json:"description,omitempty"`
|
||||
|
||||
// AutoFill indicates the field value is automatically resolved by the
|
||||
// software based on test context. When set, the value should not be
|
||||
// entered by the user.
|
||||
AutoFill string `json:"autoFill,omitempty"`
|
||||
}
|
||||
|
||||
type FormState struct {
|
||||
|
|
|
|||
|
|
@ -26,6 +26,18 @@
|
|||
|
||||
import { t } from "$lib/translations";
|
||||
|
||||
const AUTO_FILL_KEYS: Record<string, string> = {
|
||||
domain_name: "checkers.auto-fill.domain_name",
|
||||
subdomain: "checkers.auto-fill.subdomain",
|
||||
service_type: "checkers.auto-fill.service_type",
|
||||
};
|
||||
|
||||
function getAutoFillLabel(autoFill: string): string {
|
||||
const tKey = AUTO_FILL_KEYS[autoFill];
|
||||
if (tKey) return $t(tKey);
|
||||
return $t("checkers.auto-fill.generic", { key: autoFill });
|
||||
}
|
||||
|
||||
interface OptionDef {
|
||||
id?: string;
|
||||
label?: string;
|
||||
|
|
@ -34,6 +46,7 @@
|
|||
placeholder?: string;
|
||||
description?: string;
|
||||
required?: boolean;
|
||||
autoFill?: string;
|
||||
}
|
||||
|
||||
interface OptionGroup {
|
||||
|
|
@ -63,7 +76,11 @@
|
|||
{optDoc.label || optDoc.id}:
|
||||
</dt>
|
||||
<dd class="col-sm-8">
|
||||
{#if optDoc.default}
|
||||
{#if optDoc.autoFill}
|
||||
<span class="badge bg-info me-1">
|
||||
{getAutoFillLabel(optDoc.autoFill)}
|
||||
</span>
|
||||
{:else if optDoc.default}
|
||||
<span class="text-muted d-block">{optDoc.default}</span>
|
||||
{:else if optDoc.placeholder}
|
||||
<em class="text-muted d-block">{optDoc.placeholder}</em>
|
||||
|
|
@ -76,7 +93,7 @@
|
|||
type: optDoc.type || "string",
|
||||
})}
|
||||
</small>
|
||||
{#if optDoc.required}
|
||||
{#if optDoc.required && !optDoc.autoFill}
|
||||
<small class="text-danger ms-2">
|
||||
{$t("checkers.option-groups.required")}
|
||||
</small>
|
||||
|
|
|
|||
|
|
@ -768,6 +768,12 @@
|
|||
"type": "Type: {{type}}",
|
||||
"required": "Required"
|
||||
},
|
||||
"auto-fill": {
|
||||
"domain_name": "auto-filled: domain name",
|
||||
"subdomain": "auto-filled: subdomain",
|
||||
"service_type": "auto-filled: service type",
|
||||
"generic": "auto-filled: {{key}}"
|
||||
},
|
||||
"messages": {
|
||||
"options-updated": "Checker options updated successfully",
|
||||
"options-cleaned": "Orphaned options removed successfully",
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ export class Field {
|
|||
required? = $state<boolean>();
|
||||
secret? = $state<boolean>();
|
||||
textarea? = $state<boolean>();
|
||||
autoFill? = $state<string>();
|
||||
}
|
||||
|
||||
export class CustomForm {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue