diff --git a/db/Template.go b/db/Template.go index d4ca07d03..0bd96d342 100644 --- a/db/Template.go +++ b/db/Template.go @@ -1,7 +1,9 @@ package db import ( + "bytes" "encoding/json" + "fmt" ) type TemplateType string @@ -59,11 +61,63 @@ func (t TemplateApp) IsTerraform() bool { type SurveyVarType string const ( - SurveyVarStr TemplateType = "" - SurveyVarInt TemplateType = "int" - SurveyVarEnum TemplateType = "enum" + SurveyVarStr SurveyVarType = "str" + SurveyVarInt SurveyVarType = "int" + SurveyVarEnum SurveyVarType = "enum" + SurveyVarSelect SurveyVarType = "select" ) +// SurveyVarDefaultValue supports both a single string or an array of strings in JSON. +// It preserves whether the original JSON was an array so encoding will keep the +// original shape when possible (single value -> string, multiple -> array). +type SurveyVarDefaultValue struct { + Values []string `json:"-"` + originalWasArray bool `json:"-"` +} + +func (d *SurveyVarDefaultValue) UnmarshalJSON(b []byte) error { + if len(bytes.TrimSpace(b)) == 0 || bytes.Equal(bytes.TrimSpace(b), []byte("null")) { + d.Values = nil + d.originalWasArray = false + return nil + } + + // try string + var s string + if err := json.Unmarshal(b, &s); err == nil { + d.Values = []string{s} + d.originalWasArray = false + return nil + } + + // try []string + var arr []string + if err := json.Unmarshal(b, &arr); err == nil { + d.Values = arr + d.originalWasArray = true + return nil + } + + return fmt.Errorf("invalid default_value: must be string or []string") +} + +func (d SurveyVarDefaultValue) MarshalJSON() ([]byte, error) { + if d.Values == nil { + return []byte("null"), nil + } + if len(d.Values) == 1 && !d.originalWasArray { + return json.Marshal(d.Values[0]) + } + return json.Marshal(d.Values) +} + +func (d SurveyVarDefaultValue) String() string { + if len(d.Values) == 0 { + return "" + } + return d.Values[0] +} + type AnsibleTemplateParams struct { AllowDebug bool `json:"allow_debug"` AllowOverrideInventory bool `json:"allow_override_inventory"` @@ -89,13 +143,13 @@ type SurveyVarEnumValue struct { } type SurveyVar struct { - Name string `json:"name" backup:"name"` - Title string `json:"title" backup:"title"` - Required bool `json:"required,omitempty" backup:"required"` - Type SurveyVarType `json:"type,omitempty" backup:"type"` - Description string `json:"description,omitempty" backup:"description"` - Values []SurveyVarEnumValue `json:"values,omitempty" backup:"values"` - DefaultValue string `json:"default_value,omitempty" backup:"default_value"` + Name string `json:"name" backup:"name"` + Title string `json:"title" backup:"title"` + Required bool `json:"required,omitempty" backup:"required"` + Type SurveyVarType `json:"type,omitempty" backup:"type"` + Description string `json:"description,omitempty" backup:"description"` + Values []SurveyVarEnumValue `json:"values,omitempty" backup:"values"` + DefaultValue *SurveyVarDefaultValue `json:"default_value,omitempty" backup:"default_value"` } type TemplateFilter struct { diff --git a/web/src/components/SurveyVars.vue b/web/src/components/SurveyVars.vue index 55587f160..3ed14d6b2 100644 --- a/web/src/components/SurveyVars.vue +++ b/web/src/components/SurveyVars.vue @@ -48,7 +48,7 @@ > Add Value + :multiple="editedVar.type === 'select'" + :chips="editedVar.type === 'select'" + > + + 0 + ? this.editedVar.default_value[0] + : ''; + } + this.editedVarIndex = index; if (this.$refs.form) { @@ -248,7 +279,7 @@ export default { return; } - if (this.editedVar.type === 'enum') { + if (this.editedVar.type === 'enum' || this.editedVar.type === 'select') { if (this.editedValues.length === 0) { this.formError = 'Enumeration must have values.'; return; @@ -274,6 +305,18 @@ export default { this.editedVar.values = []; } + // normalize default_value before saving: select keeps array, others use string + if (this.editedVar.type === 'select') { + if (!Array.isArray(this.editedVar.default_value)) { + const dv = this.editedVar.default_value; + this.editedVar.default_value = dv == null || dv === '' ? [] : [dv]; + } + } else if (Array.isArray(this.editedVar.default_value)) { + this.editedVar.default_value = this.editedVar.default_value.length > 0 + ? this.editedVar.default_value[0] + : ''; + } + if (this.editedVarIndex != null) { this.modifiedVars[this.editedVarIndex] = this.editedVar; } else { @@ -289,6 +332,39 @@ export default { this.modifiedVars.splice(index, 1); this.$emit('change', this.modifiedVars); }, + + // remove one selected default item (for multiple select) + removeDefaultItem(index) { + if (!this.editedVar || !Array.isArray(this.editedVar.default_value)) { + return; + } + + if (index >= 0 && index < this.editedVar.default_value.length) { + this.editedVar.default_value.splice(index, 1); + } + }, + + // label shown in selection chips - item could be the value or an object + selectedItemLabel(item) { + if (!item) return ''; + if (typeof item === 'object' && item.name) return item.name; + const found = this.editedValues.find((v) => v.value === item || v.name === item); + return (found && found.name) || String(item); + }, + + onTypeChange(newType) { + if (!this.editedVar) return; + if (newType === 'select') { + if (!Array.isArray(this.editedVar.default_value)) { + const dv = this.editedVar.default_value; + this.editedVar.default_value = dv == null || dv === '' ? [] : [dv]; + } + } else if (Array.isArray(this.editedVar.default_value)) { + this.editedVar.default_value = this.editedVar.default_value.length > 0 + ? this.editedVar.default_value[0] + : ''; + } + }, }, }; diff --git a/web/src/components/TaskForm.vue b/web/src/components/TaskForm.vue index 452f24910..7ff41e8c1 100644 --- a/web/src/components/TaskForm.vue +++ b/web/src/components/TaskForm.vue @@ -80,7 +80,7 @@ + :multiple="v.type === 'select'" + :chips="v.type === 'select'" + > + +
- - -
+ +
+ clearable + v-else-if="v.type === 'enum' || v.type === 'select'" + :label="v.title + (v.required ? ' *' : '')" + :hint="v.description" + v-model="editedEnvironment[v.name]" + :required="v.required" + :rules="[(val) => !v.required || val != null || v.title + ' ' + $t('isRequired')]" + :items="v.values" + item-text="name" + item-value="value" + :multiple="v.type === 'select'" + :chips="v.type === 'select'" + outlined + dense + > + +
-
diff --git a/web/src/lang/en.js b/web/src/lang/en.js index 3ec2a724f..bdd7ae828 100644 --- a/web/src/lang/en.js +++ b/web/src/lang/en.js @@ -133,6 +133,7 @@ export default { key: '{expr}', surveyVariables: 'Survey Variables', addVariable: 'Add variable', + allowMultipleValues: 'Allow multiple values', vaultName: 'Vault ID (optional)', vaultNameDefault: 'Only one `default` (empty) name may exist', default_value: 'Default value',