checkers: add NoOverride field support for checker options
Prevent more specific scopes from overriding option values locked at a higher scope (e.g. admin). Includes defense-in-depth stripping on Set/Add operations, merge-time preservation, and frontend filtering.
This commit is contained in:
parent
526c0f4bb7
commit
82b73fa0bd
6 changed files with 349 additions and 10 deletions
|
|
@ -126,10 +126,24 @@ func (u *CheckerOptionsUsecase) GetCheckerOptions(
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine which fields are NoOverride.
|
||||||
|
var noOverrideIds map[string]bool
|
||||||
|
if def := checkerPkg.FindChecker(checkerName); def != nil {
|
||||||
|
noOverrideIds = getNoOverrideFieldIds(def)
|
||||||
|
}
|
||||||
|
|
||||||
merged := make(happydns.CheckerOptions)
|
merged := make(happydns.CheckerOptions)
|
||||||
// positionals are returned in order of increasing specificity.
|
// positionals are returned in order of increasing specificity.
|
||||||
for _, p := range positionals {
|
for _, p := range positionals {
|
||||||
maps.Copy(merged, p.Options)
|
for k, v := range p.Options {
|
||||||
|
// If the key is NoOverride and already set by a less specific scope, skip it.
|
||||||
|
if noOverrideIds[k] {
|
||||||
|
if _, exists := merged[k]; exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
merged[k] = v
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return merged, nil
|
return merged, nil
|
||||||
}
|
}
|
||||||
|
|
@ -153,17 +167,26 @@ func (u *CheckerOptionsUsecase) SetCheckerOptions(
|
||||||
serviceId *happydns.Identifier,
|
serviceId *happydns.Identifier,
|
||||||
opts happydns.CheckerOptions,
|
opts happydns.CheckerOptions,
|
||||||
) error {
|
) error {
|
||||||
// Determine which field IDs are auto-filled for this checker.
|
// Determine which field IDs are auto-filled or NoOverride for this checker.
|
||||||
var autoFillIds map[string]string
|
var autoFillIds map[string]string
|
||||||
|
var noOverrideScopes map[string]happydns.CheckScopeType
|
||||||
if def := checkerPkg.FindChecker(checkerName); def != nil {
|
if def := checkerPkg.FindChecker(checkerName); def != nil {
|
||||||
autoFillIds = getAutoFillFieldIds(def)
|
autoFillIds = getAutoFillFieldIds(def)
|
||||||
|
noOverrideScopes = getNoOverrideFieldScopes(def)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
currentScope := scopeFromIdentifiers(userId, domainId, serviceId)
|
||||||
|
|
||||||
filtered := make(happydns.CheckerOptions, len(opts))
|
filtered := make(happydns.CheckerOptions, len(opts))
|
||||||
for k, v := range opts {
|
for k, v := range opts {
|
||||||
if !isEmptyValue(v) && autoFillIds[k] == "" {
|
if isEmptyValue(v) || autoFillIds[k] != "" {
|
||||||
filtered[k] = v
|
continue
|
||||||
}
|
}
|
||||||
|
// Defense-in-depth: strip NoOverride fields at scopes below their definition.
|
||||||
|
if defScope, ok := noOverrideScopes[k]; ok && currentScope > defScope {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
filtered[k] = v
|
||||||
}
|
}
|
||||||
return u.store.UpdateCheckerConfiguration(checkerName, userId, domainId, serviceId, filtered)
|
return u.store.UpdateCheckerConfiguration(checkerName, userId, domainId, serviceId, filtered)
|
||||||
}
|
}
|
||||||
|
|
@ -181,7 +204,19 @@ func (u *CheckerOptionsUsecase) AddCheckerOptions(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine NoOverride scopes for defense-in-depth stripping.
|
||||||
|
var noOverrideScopes map[string]happydns.CheckScopeType
|
||||||
|
if def := checkerPkg.FindChecker(checkerName); def != nil {
|
||||||
|
noOverrideScopes = getNoOverrideFieldScopes(def)
|
||||||
|
}
|
||||||
|
currentScope := scopeFromIdentifiers(userId, domainId, serviceId)
|
||||||
|
|
||||||
for k, v := range newOpts {
|
for k, v := range newOpts {
|
||||||
|
// Defense-in-depth: skip NoOverride fields at scopes below their definition.
|
||||||
|
if defScope, ok := noOverrideScopes[k]; ok && currentScope > defScope {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if isEmptyValue(v) {
|
if isEmptyValue(v) {
|
||||||
delete(existing, k)
|
delete(existing, k)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -331,6 +366,17 @@ func (u *CheckerOptionsUsecase) SetCheckerOption(
|
||||||
optName string,
|
optName string,
|
||||||
value any,
|
value any,
|
||||||
) error {
|
) error {
|
||||||
|
// Defense-in-depth: reject NoOverride fields at scopes below their definition.
|
||||||
|
if def := checkerPkg.FindChecker(checkerName); def != nil {
|
||||||
|
noOverrideScopes := getNoOverrideFieldScopes(def)
|
||||||
|
if defScope, ok := noOverrideScopes[optName]; ok {
|
||||||
|
currentScope := scopeFromIdentifiers(userId, domainId, serviceId)
|
||||||
|
if currentScope > defScope {
|
||||||
|
return fmt.Errorf("option %q cannot be overridden at this scope level", optName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
existing, err := u.getScopedOptions(checkerName, userId, domainId, serviceId)
|
existing, err := u.getScopedOptions(checkerName, userId, domainId, serviceId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -374,6 +420,72 @@ func getAutoFillFieldIds(def *happydns.CheckerDefinition) map[string]string {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// collectNoOverrideFromDoc scans all option groups in a CheckerOptionsDocumentation
|
||||||
|
// and adds any fields with NoOverride set to the result set.
|
||||||
|
func collectNoOverrideFromDoc(doc happydns.CheckerOptionsDocumentation, result map[string]bool) {
|
||||||
|
for _, group := range [][]happydns.Field{
|
||||||
|
doc.AdminOpts,
|
||||||
|
doc.UserOpts,
|
||||||
|
doc.DomainOpts,
|
||||||
|
doc.ServiceOpts,
|
||||||
|
doc.RunOpts,
|
||||||
|
} {
|
||||||
|
for _, f := range group {
|
||||||
|
if f.NoOverride {
|
||||||
|
result[f.Id] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getNoOverrideFieldIds returns the set of field IDs that have NoOverride set
|
||||||
|
// for the given checker definition across all option groups and rules.
|
||||||
|
func getNoOverrideFieldIds(def *happydns.CheckerDefinition) map[string]bool {
|
||||||
|
result := make(map[string]bool)
|
||||||
|
collectNoOverrideFromDoc(def.Options, result)
|
||||||
|
for _, rule := range def.Rules {
|
||||||
|
if rwo, ok := rule.(happydns.CheckRuleWithOptions); ok {
|
||||||
|
collectNoOverrideFromDoc(rwo.Options(), result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// getNoOverrideFieldScopes returns a map from field ID to the scope at which
|
||||||
|
// the NoOverride field is defined. Used for defense-in-depth stripping.
|
||||||
|
func getNoOverrideFieldScopes(def *happydns.CheckerDefinition) map[string]happydns.CheckScopeType {
|
||||||
|
result := make(map[string]happydns.CheckScopeType)
|
||||||
|
scanGroups := func(doc happydns.CheckerOptionsDocumentation) {
|
||||||
|
for _, f := range doc.AdminOpts {
|
||||||
|
if f.NoOverride {
|
||||||
|
result[f.Id] = happydns.CheckScopeAdmin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, f := range doc.UserOpts {
|
||||||
|
if f.NoOverride {
|
||||||
|
result[f.Id] = happydns.CheckScopeUser
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, f := range doc.DomainOpts {
|
||||||
|
if f.NoOverride {
|
||||||
|
result[f.Id] = happydns.CheckScopeDomain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, f := range doc.ServiceOpts {
|
||||||
|
if f.NoOverride {
|
||||||
|
result[f.Id] = happydns.CheckScopeService
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scanGroups(def.Options)
|
||||||
|
for _, rule := range def.Rules {
|
||||||
|
if rwo, ok := rule.(happydns.CheckRuleWithOptions); ok {
|
||||||
|
scanGroups(rwo.Options())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// buildAutoFillContext loads domain/zone data from storage and builds a map
|
// buildAutoFillContext loads domain/zone data from storage and builds a map
|
||||||
// of auto-fill key to resolved value.
|
// of auto-fill key to resolved value.
|
||||||
func (u *CheckerOptionsUsecase) buildAutoFillContext(
|
func (u *CheckerOptionsUsecase) buildAutoFillContext(
|
||||||
|
|
@ -484,6 +596,15 @@ func (u *CheckerOptionsUsecase) BuildMergedCheckerOptionsWithAutoFill(
|
||||||
|
|
||||||
merged := BuildMergedCheckerOptions(storedOpts, runOpts)
|
merged := BuildMergedCheckerOptions(storedOpts, runOpts)
|
||||||
|
|
||||||
|
// Restore NoOverride fields from storedOpts so that runOpts cannot override them.
|
||||||
|
if def := checkerPkg.FindChecker(checkerName); def != nil {
|
||||||
|
for id := range getNoOverrideFieldIds(def) {
|
||||||
|
if v, ok := storedOpts[id]; ok {
|
||||||
|
merged[id] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
target := happydns.CheckTarget{
|
target := happydns.CheckTarget{
|
||||||
UserId: userId,
|
UserId: userId,
|
||||||
DomainId: domainId,
|
DomainId: domainId,
|
||||||
|
|
|
||||||
|
|
@ -1440,3 +1440,215 @@ func TestValidateOptions_SkipsAutoFillFields(t *testing.T) {
|
||||||
t.Fatalf("auto-fill required field should be skipped during validation, got: %v", err)
|
t.Fatalf("auto-fill required field should be skipped during validation, got: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- NoOverride tests ---
|
||||||
|
|
||||||
|
func TestGetCheckerOptions_NoOverridePreservesAdminValue(t *testing.T) {
|
||||||
|
registerTestChecker("no_override_merge", &happydns.CheckerDefinition{
|
||||||
|
Options: happydns.CheckerOptionsDocumentation{
|
||||||
|
AdminOpts: []happydns.CheckerOptionDocumentation{
|
||||||
|
{Id: "locked", Type: "boolean", NoOverride: true},
|
||||||
|
},
|
||||||
|
UserOpts: []happydns.CheckerOptionDocumentation{
|
||||||
|
{Id: "threshold", Type: "number"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
store := newOptionsStore()
|
||||||
|
uc := checkerUC.NewCheckerOptionsUsecase(store, nil)
|
||||||
|
|
||||||
|
uid := idPtr()
|
||||||
|
|
||||||
|
// Set at admin scope.
|
||||||
|
store.UpdateCheckerConfiguration("no_override_merge", nil, nil, nil, happydns.CheckerOptions{
|
||||||
|
"locked": true,
|
||||||
|
})
|
||||||
|
// Attempt to override at user scope (should be ignored during merge).
|
||||||
|
store.UpdateCheckerConfiguration("no_override_merge", uid, nil, nil, happydns.CheckerOptions{
|
||||||
|
"locked": false,
|
||||||
|
"threshold": float64(42),
|
||||||
|
})
|
||||||
|
|
||||||
|
merged, err := uc.GetCheckerOptions("no_override_merge", uid, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if merged["locked"] != true {
|
||||||
|
t.Errorf("expected locked=true (admin value preserved), got %v", merged["locked"])
|
||||||
|
}
|
||||||
|
if merged["threshold"] != float64(42) {
|
||||||
|
t.Errorf("expected threshold=42 (user value applied), got %v", merged["threshold"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetCheckerOptions_NoOverrideAllowsSameScope(t *testing.T) {
|
||||||
|
registerTestChecker("no_override_same_scope", &happydns.CheckerDefinition{
|
||||||
|
Options: happydns.CheckerOptionsDocumentation{
|
||||||
|
AdminOpts: []happydns.CheckerOptionDocumentation{
|
||||||
|
{Id: "locked", Type: "boolean", NoOverride: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
store := newOptionsStore()
|
||||||
|
uc := checkerUC.NewCheckerOptionsUsecase(store, nil)
|
||||||
|
|
||||||
|
// Only admin scope sets the value — no conflict.
|
||||||
|
store.UpdateCheckerConfiguration("no_override_same_scope", nil, nil, nil, happydns.CheckerOptions{
|
||||||
|
"locked": true,
|
||||||
|
})
|
||||||
|
|
||||||
|
merged, err := uc.GetCheckerOptions("no_override_same_scope", nil, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if merged["locked"] != true {
|
||||||
|
t.Errorf("expected locked=true, got %v", merged["locked"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildMergedCheckerOptionsWithAutoFill_NoOverrideBlocksRunOpts(t *testing.T) {
|
||||||
|
registerTestChecker("no_override_runopt", &happydns.CheckerDefinition{
|
||||||
|
Options: happydns.CheckerOptionsDocumentation{
|
||||||
|
AdminOpts: []happydns.CheckerOptionDocumentation{
|
||||||
|
{Id: "locked", Type: "boolean", NoOverride: true},
|
||||||
|
},
|
||||||
|
UserOpts: []happydns.CheckerOptionDocumentation{
|
||||||
|
{Id: "threshold", Type: "number"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
store := newOptionsStore()
|
||||||
|
uc := checkerUC.NewCheckerOptionsUsecase(store, nil)
|
||||||
|
|
||||||
|
uid := idPtr()
|
||||||
|
|
||||||
|
// Admin sets locked=true.
|
||||||
|
store.UpdateCheckerConfiguration("no_override_runopt", nil, nil, nil, happydns.CheckerOptions{
|
||||||
|
"locked": true,
|
||||||
|
})
|
||||||
|
// User sets threshold.
|
||||||
|
store.UpdateCheckerConfiguration("no_override_runopt", uid, nil, nil, happydns.CheckerOptions{
|
||||||
|
"threshold": float64(10),
|
||||||
|
})
|
||||||
|
|
||||||
|
// RunOpts tries to override locked.
|
||||||
|
merged, err := uc.BuildMergedCheckerOptionsWithAutoFill("no_override_runopt", uid, nil, nil, happydns.CheckerOptions{
|
||||||
|
"locked": false,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if merged["locked"] != true {
|
||||||
|
t.Errorf("expected locked=true (NoOverride should block runOpts), got %v", merged["locked"])
|
||||||
|
}
|
||||||
|
if merged["threshold"] != float64(10) {
|
||||||
|
t.Errorf("expected threshold=10, got %v", merged["threshold"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetCheckerOptions_StripsNoOverrideAtLowerScope(t *testing.T) {
|
||||||
|
registerTestChecker("no_override_set", &happydns.CheckerDefinition{
|
||||||
|
Options: happydns.CheckerOptionsDocumentation{
|
||||||
|
AdminOpts: []happydns.CheckerOptionDocumentation{
|
||||||
|
{Id: "locked", Type: "boolean", NoOverride: true},
|
||||||
|
},
|
||||||
|
UserOpts: []happydns.CheckerOptionDocumentation{
|
||||||
|
{Id: "threshold", Type: "number"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
store := newOptionsStore()
|
||||||
|
uc := checkerUC.NewCheckerOptionsUsecase(store, nil)
|
||||||
|
|
||||||
|
uid := idPtr()
|
||||||
|
|
||||||
|
// Try to set locked at user scope — should be silently stripped.
|
||||||
|
err := uc.SetCheckerOptions("no_override_set", uid, nil, nil, happydns.CheckerOptions{
|
||||||
|
"locked": true,
|
||||||
|
"threshold": float64(99),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check what was actually stored.
|
||||||
|
stored := store.data[posKey("no_override_set", uid, nil, nil)]
|
||||||
|
if _, ok := stored["locked"]; ok {
|
||||||
|
t.Error("expected locked to be stripped from user-scope storage")
|
||||||
|
}
|
||||||
|
if stored["threshold"] != float64(99) {
|
||||||
|
t.Errorf("expected threshold=99 to be stored, got %v", stored["threshold"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddCheckerOptions_StripsNoOverrideAtLowerScope(t *testing.T) {
|
||||||
|
registerTestChecker("no_override_add", &happydns.CheckerDefinition{
|
||||||
|
Options: happydns.CheckerOptionsDocumentation{
|
||||||
|
AdminOpts: []happydns.CheckerOptionDocumentation{
|
||||||
|
{Id: "locked", Type: "boolean", NoOverride: true},
|
||||||
|
},
|
||||||
|
UserOpts: []happydns.CheckerOptionDocumentation{
|
||||||
|
{Id: "threshold", Type: "number"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
store := newOptionsStore()
|
||||||
|
uc := checkerUC.NewCheckerOptionsUsecase(store, nil)
|
||||||
|
|
||||||
|
uid := idPtr()
|
||||||
|
|
||||||
|
// Pre-populate user scope with threshold.
|
||||||
|
store.UpdateCheckerConfiguration("no_override_add", uid, nil, nil, happydns.CheckerOptions{
|
||||||
|
"threshold": float64(50),
|
||||||
|
})
|
||||||
|
|
||||||
|
// Try to add locked at user scope — should be silently skipped.
|
||||||
|
result, err := uc.AddCheckerOptions("no_override_add", uid, nil, nil, happydns.CheckerOptions{
|
||||||
|
"locked": true,
|
||||||
|
"threshold": float64(75),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if _, ok := result["locked"]; ok {
|
||||||
|
t.Error("expected locked to be skipped in AddCheckerOptions result")
|
||||||
|
}
|
||||||
|
if result["threshold"] != float64(75) {
|
||||||
|
t.Errorf("expected threshold=75, got %v", result["threshold"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetCheckerOption_RejectsNoOverrideAtLowerScope(t *testing.T) {
|
||||||
|
registerTestChecker("no_override_set_single", &happydns.CheckerDefinition{
|
||||||
|
Options: happydns.CheckerOptionsDocumentation{
|
||||||
|
AdminOpts: []happydns.CheckerOptionDocumentation{
|
||||||
|
{Id: "locked", Type: "boolean", NoOverride: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
store := newOptionsStore()
|
||||||
|
uc := checkerUC.NewCheckerOptionsUsecase(store, nil)
|
||||||
|
|
||||||
|
uid := idPtr()
|
||||||
|
|
||||||
|
// Setting at admin scope should work.
|
||||||
|
err := uc.SetCheckerOption("no_override_set_single", nil, nil, nil, "locked", true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected SetCheckerOption at admin scope to succeed, got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setting at user scope should fail.
|
||||||
|
err = uc.SetCheckerOption("no_override_set_single", uid, nil, nil, "locked", false)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error when setting NoOverride field at lower scope")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "cannot be overridden") {
|
||||||
|
t.Errorf("unexpected error message: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,10 @@ type Field struct {
|
||||||
// AutoFill indicates that this field is automatically filled by the system
|
// AutoFill indicates that this field is automatically filled by the system
|
||||||
// based on execution context (e.g. domain name, zone, service type).
|
// based on execution context (e.g. domain name, zone, service type).
|
||||||
AutoFill string `json:"autoFill,omitempty"`
|
AutoFill string `json:"autoFill,omitempty"`
|
||||||
|
|
||||||
|
// NoOverride indicates that once this field is set at a given scope,
|
||||||
|
// more specific scopes cannot override its value.
|
||||||
|
NoOverride bool `json:"noOverride,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FormState struct {
|
type FormState struct {
|
||||||
|
|
|
||||||
|
|
@ -75,11 +75,13 @@
|
||||||
onclean,
|
onclean,
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
// Filter out auto-fill fields from editable groups (they are system-provided).
|
// Filter out auto-fill and noOverride fields from editable groups.
|
||||||
|
// Auto-fill fields are system-provided; noOverride fields can only be
|
||||||
|
// changed at the scope where they are defined (typically admin).
|
||||||
let filteredEditableGroups = $derived(
|
let filteredEditableGroups = $derived(
|
||||||
editableGroups.map((g) => ({
|
editableGroups.map((g) => ({
|
||||||
...g,
|
...g,
|
||||||
opts: g.opts.filter((opt) => !opt.autoFill),
|
opts: g.opts.filter((opt) => !opt.autoFill && !opt.noOverride),
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,7 @@
|
||||||
const ids = new Set<string>();
|
const ids = new Set<string>();
|
||||||
if (!resolvedStatus) return ids;
|
if (!resolvedStatus) return ids;
|
||||||
const addOpts = (opts: HappydnsCheckerOptionDocumentation[] | undefined) =>
|
const addOpts = (opts: HappydnsCheckerOptionDocumentation[] | undefined) =>
|
||||||
opts?.forEach((o) => o.id && ids.add(o.id));
|
opts?.forEach((o) => o.id && !o.noOverride && ids.add(o.id));
|
||||||
addOpts(resolvedStatus.options?.runOpts);
|
addOpts(resolvedStatus.options?.runOpts);
|
||||||
addOpts(resolvedStatus.options?.adminOpts);
|
addOpts(resolvedStatus.options?.adminOpts);
|
||||||
addOpts(resolvedStatus.options?.userOpts);
|
addOpts(resolvedStatus.options?.userOpts);
|
||||||
|
|
@ -204,7 +204,7 @@
|
||||||
{@const runOpts = [
|
{@const runOpts = [
|
||||||
...(status.options?.runOpts || []),
|
...(status.options?.runOpts || []),
|
||||||
...activeRulesForOpts.flatMap((r: any) => r?.options?.runOpts || []),
|
...activeRulesForOpts.flatMap((r: any) => r?.options?.runOpts || []),
|
||||||
]}
|
].filter((o: any) => !o.noOverride)}
|
||||||
{@const otherOpts = [
|
{@const otherOpts = [
|
||||||
...(status.options?.adminOpts || []),
|
...(status.options?.adminOpts || []),
|
||||||
...(status.options?.userOpts || []),
|
...(status.options?.userOpts || []),
|
||||||
|
|
@ -214,7 +214,7 @@
|
||||||
...(r?.options?.userOpts || []),
|
...(r?.options?.userOpts || []),
|
||||||
...(r?.options?.domainOpts || []),
|
...(r?.options?.domainOpts || []),
|
||||||
]),
|
]),
|
||||||
].filter((o: any) => o.id)}
|
].filter((o: any) => o.id && !o.noOverride)}
|
||||||
<Form
|
<Form
|
||||||
id="run-check-modal"
|
id="run-check-modal"
|
||||||
onsubmit={(e: Event) => {
|
onsubmit={(e: Event) => {
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,7 @@ export function collectAllOptionDocs(
|
||||||
...(r.options?.userOpts || []),
|
...(r.options?.userOpts || []),
|
||||||
...(r.options?.domainOpts || []),
|
...(r.options?.domainOpts || []),
|
||||||
]),
|
]),
|
||||||
];
|
].filter((o) => !o.noOverride);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function downloadBlob(content: string, filename: string, mime: string) {
|
export function downloadBlob(content: string, filename: string, mime: string) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue