diff --git a/CHANGELOG.md b/CHANGELOG.md index cc0268d4..00f35f39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [5.0.1] - 2024-04-10 + +### Fixed + +- login expiryDays didn't work +- fixed non-ascii codes in ldap +- fixed approvals, broken since 4.0.19 + +### Added + +- add a form-reload route +- added mail attribute ldap +- improved error message on unevaluated fields +- allow to model arrays like foo.bar[0].ping.pong[1] +- allow placeholders in description fields of field validation + ## [5.0.0] - 2024-01-25 ### Added @@ -650,7 +666,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Allow change password for current local user - Start tracking versions -[Unreleased]: https://github.com/ansibleguy76/ansibleforms/compare/5.0.0...HEAD +[Unreleased]: https://github.com/ansibleguy76/ansibleforms/compare/5.0.1...HEAD + +[5.0.1]: https://github.com/ansibleguy76/ansibleforms/compare/5.0.0...5.0.1 [5.0.0]: https://github.com/ansibleguy76/ansibleforms/compare/4.0.19...5.0.0 diff --git a/app_versions.gradle b/app_versions.gradle index a7da55b1..d605de76 100644 --- a/app_versions.gradle +++ b/app_versions.gradle @@ -1,2 +1,2 @@ -ext.version_code = 50000 -ext.version_name = "5.0.0" +ext.version_code = 50001 +ext.version_name = "5.0.1" diff --git a/client/package.json b/client/package.json index 010d7a08..75d2018d 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "ansible_forms_vue", - "version": "5.0.0", + "version": "5.0.1", "private": true, "scripts": { "serve": "vue-cli-service serve", @@ -12,9 +12,9 @@ "lint": "vue-cli-service lint" }, "dependencies": { - "core-js": "~3.34.0", + "core-js": "~3.36.1", "vue": "~2.7.15", - "axios": "~1.6.2", + "axios": "~1.6.8", "es6-promise": "~4.2.8", "vuelidate": "~0.7.7", "vue-router": "~3.5.4", @@ -24,6 +24,7 @@ "bulmaswatch": "0.8.1", "bulma" : "0.9.4", "bulma-quickview" : "*", + "@creativebulma/bulma-tooltip" : "*", "lodash" : "~4.17.21", "bulma-checkradio": "~2.1.3", "bulma-pageloader": "~0.3.0", diff --git a/client/public/assets/main.scss b/client/public/assets/main.scss index 11055b51..8d7f7f8e 100644 --- a/client/public/assets/main.scss +++ b/client/public/assets/main.scss @@ -7,6 +7,7 @@ @import "variables"; @import "bulma"; @import "overrides"; +@import "@creativebulma/bulma-tooltip"; article.message table { tr:last-child { diff --git a/client/src/components/Form.vue b/client/src/components/Form.vue index 810425ff..9590285e 100644 --- a/client/src/components/Form.vue +++ b/client/src/components/Form.vue @@ -41,6 +41,9 @@ + + + +
@@ -513,6 +516,7 @@ subjob:{}, // output of last subjob dynamicFieldDependencies:{}, // which fields need to be re-evaluated if other fields change dynamicFieldDependentOf:{}, // which fields are dependend from others + unevaluatedFields:[], // list of unevaluatedFields defaults:{}, // holds default info per field dynamicFieldStatus:{}, // holds the status of dynamics fields (running=currently being evaluated, variable=depends on others, fixed=only need 1-time lookup, default=has defaulted, undefined=trigger eval/query) queryresults:{}, // holds the results of dynamic dropdown boxes @@ -654,6 +658,14 @@ return obj }, computed: { + // unevaluatedFieldsWarning + unevaluatedFieldsWarning(){ + if(this.canSubmit){ + return undefined + }else{ + return this.unevaluatedFields.join(",") + " " + ((this.unevaluatedFields.length==1)?"is":"are") + " unevaluated..." + } + }, // joboutput filteredJobOutput(){ if(!this.filterOutput) return this.jobResult?.data?.output?.replace(/\r\n/g,"
") @@ -1396,6 +1408,7 @@ //console.log("enter loop"); // ref.$toast("... loop ...") hasUnevaluatedFields=false; // reset flag + ref.unevaluatedFields=[]; // the list of unevaluatedfields - reset // console.log("-------------------------------") ref.currentForm.fields.forEach( function(item,index){ @@ -1408,6 +1421,7 @@ // console.log("eval expression " + item.name) // console.log(`[${item.name}][${flag}] : evaluating`) hasUnevaluatedFields=true // set the un-eval flag if this is required + ref.unevaluatedFields.push(item.name) // set flag running ref.setFieldStatus(item.name,"running",false) placeholderCheck = ref.replacePlaceholders(item) // check and replace placeholders @@ -1524,6 +1538,7 @@ // console.log("eval query : " + item.name) // set flag running hasUnevaluatedFields=true + ref.unevaluatedFields.push(item.name) ref.setFieldStatus(item.name,"running",false) placeholderCheck = ref.replacePlaceholders(item) // check and replace placeholders if(placeholderCheck.value!=undefined){ // expression is clean ? @@ -1653,6 +1668,7 @@ if(ref.watchdog>50){ // is it taking too long ? ref.jobResult.message="" // stop and reset ref.$toast.warning("It took too long to evaluate all fields before run.\r\nLet the form stabilize and try again.") + ref.$toast.warning(ref.unevaluatedFieldsWarning) }else{ //ref.$toast.info(`Stabilizing form...${ref.watchdog}`) } @@ -1839,58 +1855,120 @@ generateJsonOutput(filedata={}){ var ref=this var formdata={} - this.currentForm.fields.forEach((item, i) => { - // this.checkDependencies(item) // hide field based on dependency - if(this.visibility[item.name] && !item.noOutput){ - var fieldmodel = [].concat(item.model || []) - var outputObject = item.outputObject || item.type=="expression" || item.type=="file" || item.type=="table" || false - var outputValue = undefined - // if uploaded file info, use that - if(item.name in filedata){ - outputValue=filedata[item.name] - // else just use the formdata - }else{ - // deep clone, otherwise weird effects - outputValue = Helpers.deepClone(this.form[item.name]) - } - // if no model is given, we assign to the root - if(!outputObject){ // do we need to flatten output ? - outputValue=this.getFieldValue(outputValue,item.valueColumn || "",true) - } - if(fieldmodel.length==0){ - // deep clone = otherwise weird effects - formdata[item.name]=Helpers.deepClone(outputValue) - }else{ - fieldmodel.forEach((f)=>{ - // convert fieldmodel for actual object - // svm.lif.name => svm["lif"].name = formvalue - // using reduce, which is a recursive function - f.split(/\s*\.\s*/).reduce((master,obj, level,arr) => { - // if last - - if (level === (arr.length - 1)){ + try{ + this.currentForm.fields.forEach((item, i) => { + // this.checkDependencies(item) // hide field based on dependency + if(this.visibility[item.name] && !item.noOutput){ + var fieldmodel = [].concat(item.model || []) + var outputObject = item.outputObject || item.type=="expression" || item.type=="file" || item.type=="table" || false + var outputValue = undefined + // if uploaded file info, use that + if(item.name in filedata){ + outputValue=filedata[item.name] + // else just use the formdata + }else{ + // deep clone, otherwise weird effects + outputValue = Helpers.deepClone(this.form[item.name]) + } + // if no model is given, we assign to the root + if(!outputObject){ // do we need to flatten output ? + outputValue=this.getFieldValue(outputValue,item.valueColumn || "",true) + } + if(fieldmodel.length==0){ + // deep clone = otherwise weird effects + formdata[item.name]=Helpers.deepClone(outputValue) + }else{ + + // here we build the full model object + fieldmodel.forEach((f)=>{ + // convert fieldmodel for actual object + // svm.lif.name => svm["lif"].name = formvalue + // using reduce, which is a recursive function + f.split(/\s*\.\s*/).reduce((master,obj, level,arr) => { + + var arrsplit = undefined + + // if last + if (level === (arr.length - 1)){ + + // the last piece we assign the value to - if(master[obj]===undefined){ - master[obj]=outputValue - }else{ - master[obj]=Lodash.merge(master[obj],outputValue) + // if the last piece is an array, we need to create the array first + if(obj.match(/.*\[[0-9]+\]$/)){ + // split the array name and index + arrsplit=obj.split(/\[([0-9]+)\]$/) + // if the array doesn't exist, we create it + if(master[arrsplit[0]]===undefined){ + master[arrsplit[0]]=[] + } + // if the array index doesn't exist, we create it + if(master[arrsplit[0]][arrsplit[1]]===undefined){ + master[arrsplit[0]][arrsplit[1]]={} + } + // assign the value to the array index + master[arrsplit[0]][arrsplit[1]]=outputValue + + return master[arrsplit[0]][arrsplit[1]] + + }else{ // just an object + + // if the object doesn't exist, we create it + if(master[obj]===undefined){ + master[obj]=outputValue + }else{ + master[obj]=Lodash.merge(master[obj],outputValue) + } + + // return the result for next reduce iteration + return master[obj] + } - - }else{ - // initialize first time to object - if(master[obj]===undefined){ - master[obj]={} + + + + }else{ + // initialize first time to object or array + + // if the object is an array, we need to create the array first + if(obj.match(/.*\[[0-9]+\]$/)){ + // split the array name and index + arrsplit=obj.split(/\[([0-9]+)\]$/) + // if the array doesn't exist, we create it + if(master[arrsplit[0]]===undefined){ + master[arrsplit[0]]=[] + } + // if the array index doesn't exist, we create it + if(master[arrsplit[0]][arrsplit[1]]===undefined){ + master[arrsplit[0]][arrsplit[1]]={} + } + + // return the result for next reduce iteration + return master[arrsplit[0]][arrsplit[1]] + + }else{ // just an object + + // if the object doesn't exist, we create it + if(master[obj]===undefined){ + master[obj]={} + } + // return the result for next reduce iteration + return master[obj] } - } - // return the result for next reduce iteration - return master[obj] - },formdata); - }) + } + + + },formdata); + + }) + } } - } - }); + }); + }catch(err){ + console.log(err) + ref.$toast.error("Failed to generate json output.\r\nContact the developer.\r\n" + (err.message || err.toString())) + } // update main data Vue.set(this,"formdata",formdata) }, diff --git a/client/src/router.js b/client/src/router.js index d9cdf4be..3dba4521 100644 --- a/client/src/router.js +++ b/client/src/router.js @@ -2,6 +2,7 @@ import Vue from 'vue' import Router from 'vue-router' import Forms from './views/Forms.vue' import Form from './views/Form.vue' +import FormReload from './views/FormReload.vue' import Login from './views/Login.vue' import ErrorVue from './views/Error.vue' import Schema from './views/Schema.vue' @@ -60,6 +61,10 @@ export default new Router({ name:"Form", component:Form }, + { + path: '/form-reload', + component:FormReload + }, { path:"/login", name:"Login", diff --git a/client/src/views/FormReload.vue b/client/src/views/FormReload.vue new file mode 100644 index 00000000..6181d9ed --- /dev/null +++ b/client/src/views/FormReload.vue @@ -0,0 +1,20 @@ + + + diff --git a/client/src/views/Ldap.vue b/client/src/views/Ldap.vue index 2397d59a..4e3a1db4 100644 --- a/client/src/views/Ldap.vue +++ b/client/src/views/Ldap.vue @@ -25,6 +25,7 @@ + @@ -79,6 +80,7 @@ groups_search_base: "", group_class: "", group_member_attribute: "", + mail_attribute: "", group_member_user_attribute: "", cert:"", ca_bundle:"", @@ -150,6 +152,9 @@ groups_attribute:{ required }, + mail_attribute:{ + required + }, cert:{ requiredIf:requiredIf(function(ldap){ return ldap.enable_tls && !ldap.ignore_certs diff --git a/client/src/views/Login.vue b/client/src/views/Login.vue index 74c883b5..35d0b33d 100644 --- a/client/src/views/Login.vue +++ b/client/src/views/Login.vue @@ -15,7 +15,7 @@
- + Azure
@@ -45,6 +45,7 @@ username: "", password: "" }, + baseUrl:"/", azureAdEnabled:false, azureGroupfilter:"", azureGraphUrl:"https://graph.microsoft.com" @@ -53,7 +54,7 @@ methods: { getSettings(azuretoken){ var ref=this - axios.get(`${process.env.BASE_URL}api/v1/auth/settings`) + axios.get(`${ref.baseUrl}api/v1/auth/settings`) .then((result)=>{ if(result.data?.status=='success'){ this.azureAdEnabled=!!result.data.data.output.azureAdEnabled @@ -100,7 +101,7 @@ console.log("Groups have been filtered") } // No more nextLink, you have all the groups - axios.post(`${process.env.BASE_URL}api/v1/auth/azureadoauth2/login`, { azuretoken, groups:allGroups }) + axios.post(`${ref.baseUrl}api/v1/auth/azureadoauth2/login`, { azuretoken, groups:allGroups }) .then((result) => { if (result.data.token) { TokenStorage.storeToken(result.data.token); @@ -121,14 +122,14 @@ }); }, login() { - + var ref=this if (!this.$v.user.$invalid) { console.log("Logging in") - var basicAuth = 'Basic ' + btoa(this.user.username + ':' + this.user.password); + var basicAuth = 'Basic ' + new Buffer(this.user.username + ':' + this.user.password).toString('base64') var postconfig={ headers:{'Authorization':basicAuth} } - axios.post(`${process.env.BASE_URL}api/v1/auth/login`,{},postconfig) + axios.post(`${ref.baseUrl}api/v1/auth/login`,{},postconfig) .then((result)=>{ if(result.data.token){ console.log("Login success, storing tokens") @@ -158,7 +159,7 @@ }, mounted(){ - + this.baseUrl = process.env.BASE_URL if(this.$route.query.azuretoken){ this.loading=true this.getSettings(this.$route.query.azuretoken) diff --git a/client/src/views/Users.vue b/client/src/views/Users.vue index 3f2db4c0..832f434d 100644 --- a/client/src/views/Users.vue +++ b/client/src/views/Users.vue @@ -38,6 +38,7 @@