diff --git a/app/assets/javascripts/client_management/jquery.validate.min.js b/app/assets/javascripts/client_management/jquery.validate.min.js new file mode 100644 index 0000000..442a413 --- /dev/null +++ b/app/assets/javascripts/client_management/jquery.validate.min.js @@ -0,0 +1,4 @@ +/*! jQuery Validation Plugin - v1.19.5 - 7/1/2022 + * https://jqueryvalidation.org/ + * Copyright (c) 2022 Jörn Zaefferer; Licensed MIT */ +!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){a.extend(a.fn,{validate:function(b){if(!this.length)return void(b&&b.debug&&window.console&&console.warn("Nothing selected, can't validate, returning nothing."));var c=a.data(this[0],"validator");return c?c:(this.attr("novalidate","novalidate"),c=new a.validator(b,this[0]),a.data(this[0],"validator",c),c.settings.onsubmit&&(this.on("click.validate",":submit",function(b){c.submitButton=b.currentTarget,a(this).hasClass("cancel")&&(c.cancelSubmit=!0),void 0!==a(this).attr("formnovalidate")&&(c.cancelSubmit=!0)}),this.on("submit.validate",function(b){function d(){var d,e;return c.submitButton&&(c.settings.submitHandler||c.formSubmitted)&&(d=a("").attr("name",c.submitButton.name).val(a(c.submitButton).val()).appendTo(c.currentForm)),!(c.settings.submitHandler&&!c.settings.debug)||(e=c.settings.submitHandler.call(c,c.currentForm,b),d&&d.remove(),void 0!==e&&e)}return c.settings.debug&&b.preventDefault(),c.cancelSubmit?(c.cancelSubmit=!1,d()):c.form()?c.pendingRequest?(c.formSubmitted=!0,!1):d():(c.focusInvalid(),!1)})),c)},valid:function(){var b,c,d;return a(this[0]).is("form")?b=this.validate().form():(d=[],b=!0,c=a(this[0].form).validate(),this.each(function(){b=c.element(this)&&b,b||(d=d.concat(c.errorList))}),c.errorList=d),b},rules:function(b,c){var d,e,f,g,h,i,j=this[0],k="undefined"!=typeof this.attr("contenteditable")&&"false"!==this.attr("contenteditable");if(null!=j&&(!j.form&&k&&(j.form=this.closest("form")[0],j.name=this.attr("name")),null!=j.form)){if(b)switch(d=a.data(j.form,"validator").settings,e=d.rules,f=a.validator.staticRules(j),b){case"add":a.extend(f,a.validator.normalizeRule(c)),delete f.messages,e[j.name]=f,c.messages&&(d.messages[j.name]=a.extend(d.messages[j.name],c.messages));break;case"remove":return c?(i={},a.each(c.split(/\s/),function(a,b){i[b]=f[b],delete f[b]}),i):(delete e[j.name],f)}return g=a.validator.normalizeRules(a.extend({},a.validator.classRules(j),a.validator.attributeRules(j),a.validator.dataRules(j),a.validator.staticRules(j)),j),g.required&&(h=g.required,delete g.required,g=a.extend({required:h},g)),g.remote&&(h=g.remote,delete g.remote,g=a.extend(g,{remote:h})),g}}});var b=function(a){return a.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"")};a.extend(a.expr.pseudos||a.expr[":"],{blank:function(c){return!b(""+a(c).val())},filled:function(c){var d=a(c).val();return null!==d&&!!b(""+d)},unchecked:function(b){return!a(b).prop("checked")}}),a.validator=function(b,c){this.settings=a.extend(!0,{},a.validator.defaults,b),this.currentForm=c,this.init()},a.validator.format=function(b,c){return 1===arguments.length?function(){var c=a.makeArray(arguments);return c.unshift(b),a.validator.format.apply(this,c)}:void 0===c?b:(arguments.length>2&&c.constructor!==Array&&(c=a.makeArray(arguments).slice(1)),c.constructor!==Array&&(c=[c]),a.each(c,function(a,c){b=b.replace(new RegExp("\\{"+a+"\\}","g"),function(){return c})}),b)},a.extend(a.validator,{defaults:{messages:{},groups:{},rules:{},errorClass:"error",pendingClass:"pending",validClass:"valid",errorElement:"label",focusCleanup:!1,focusInvalid:!0,errorContainer:a([]),errorLabelContainer:a([]),onsubmit:!0,ignore:":hidden",ignoreTitle:!1,onfocusin:function(a){this.lastActive=a,this.settings.focusCleanup&&(this.settings.unhighlight&&this.settings.unhighlight.call(this,a,this.settings.errorClass,this.settings.validClass),this.hideThese(this.errorsFor(a)))},onfocusout:function(a){this.checkable(a)||!(a.name in this.submitted)&&this.optional(a)||this.element(a)},onkeyup:function(b,c){var d=[16,17,18,20,35,36,37,38,39,40,45,144,225];9===c.which&&""===this.elementValue(b)||a.inArray(c.keyCode,d)!==-1||(b.name in this.submitted||b.name in this.invalid)&&this.element(b)},onclick:function(a){a.name in this.submitted?this.element(a):a.parentNode.name in this.submitted&&this.element(a.parentNode)},highlight:function(b,c,d){"radio"===b.type?this.findByName(b.name).addClass(c).removeClass(d):a(b).addClass(c).removeClass(d)},unhighlight:function(b,c,d){"radio"===b.type?this.findByName(b.name).removeClass(c).addClass(d):a(b).removeClass(c).addClass(d)}},setDefaults:function(b){a.extend(a.validator.defaults,b)},messages:{required:"This field is required.",remote:"Please fix this field.",email:"Please enter a valid email address.",url:"Please enter a valid URL.",date:"Please enter a valid date.",dateISO:"Please enter a valid date (ISO).",number:"Please enter a valid number.",digits:"Please enter only digits.",equalTo:"Please enter the same value again.",maxlength:a.validator.format("Please enter no more than {0} characters."),minlength:a.validator.format("Please enter at least {0} characters."),rangelength:a.validator.format("Please enter a value between {0} and {1} characters long."),range:a.validator.format("Please enter a value between {0} and {1}."),max:a.validator.format("Please enter a value less than or equal to {0}."),min:a.validator.format("Please enter a value greater than or equal to {0}."),step:a.validator.format("Please enter a multiple of {0}.")},autoCreateRanges:!1,prototype:{init:function(){function b(b){var c="undefined"!=typeof a(this).attr("contenteditable")&&"false"!==a(this).attr("contenteditable");if(!this.form&&c&&(this.form=a(this).closest("form")[0],this.name=a(this).attr("name")),d===this.form){var e=a.data(this.form,"validator"),f="on"+b.type.replace(/^validate/,""),g=e.settings;g[f]&&!a(this).is(g.ignore)&&g[f].call(e,this,b)}}this.labelContainer=a(this.settings.errorLabelContainer),this.errorContext=this.labelContainer.length&&this.labelContainer||a(this.currentForm),this.containers=a(this.settings.errorContainer).add(this.settings.errorLabelContainer),this.submitted={},this.valueCache={},this.pendingRequest=0,this.pending={},this.invalid={},this.reset();var c,d=this.currentForm,e=this.groups={};a.each(this.settings.groups,function(b,c){"string"==typeof c&&(c=c.split(/\s/)),a.each(c,function(a,c){e[c]=b})}),c=this.settings.rules,a.each(c,function(b,d){c[b]=a.validator.normalizeRule(d)}),a(this.currentForm).on("focusin.validate focusout.validate keyup.validate",":text, [type='password'], [type='file'], select, textarea, [type='number'], [type='search'], [type='tel'], [type='url'], [type='email'], [type='datetime'], [type='date'], [type='month'], [type='week'], [type='time'], [type='datetime-local'], [type='range'], [type='color'], [type='radio'], [type='checkbox'], [contenteditable], [type='button']",b).on("click.validate","select, option, [type='radio'], [type='checkbox']",b),this.settings.invalidHandler&&a(this.currentForm).on("invalid-form.validate",this.settings.invalidHandler)},form:function(){return this.checkForm(),a.extend(this.submitted,this.errorMap),this.invalid=a.extend({},this.errorMap),this.valid()||a(this.currentForm).triggerHandler("invalid-form",[this]),this.showErrors(),this.valid()},checkForm:function(){this.prepareForm();for(var a=0,b=this.currentElements=this.elements();b[a];a++)this.check(b[a]);return this.valid()},element:function(b){var c,d,e=this.clean(b),f=this.validationTargetFor(e),g=this,h=!0;return void 0===f?delete this.invalid[e.name]:(this.prepareElement(f),this.currentElements=a(f),d=this.groups[f.name],d&&a.each(this.groups,function(a,b){b===d&&a!==f.name&&(e=g.validationTargetFor(g.clean(g.findByName(a))),e&&e.name in g.invalid&&(g.currentElements.push(e),h=g.check(e)&&h))}),c=this.check(f)!==!1,h=h&&c,c?this.invalid[f.name]=!1:this.invalid[f.name]=!0,this.numberOfInvalids()||(this.toHide=this.toHide.add(this.containers)),this.showErrors(),a(b).attr("aria-invalid",!c)),h},showErrors:function(b){if(b){var c=this;a.extend(this.errorMap,b),this.errorList=a.map(this.errorMap,function(a,b){return{message:a,element:c.findByName(b)[0]}}),this.successList=a.grep(this.successList,function(a){return!(a.name in b)})}this.settings.showErrors?this.settings.showErrors.call(this,this.errorMap,this.errorList):this.defaultShowErrors()},resetForm:function(){a.fn.resetForm&&a(this.currentForm).resetForm(),this.invalid={},this.submitted={},this.prepareForm(),this.hideErrors();var b=this.elements().removeData("previousValue").removeAttr("aria-invalid");this.resetElements(b)},resetElements:function(a){var b;if(this.settings.unhighlight)for(b=0;a[b];b++)this.settings.unhighlight.call(this,a[b],this.settings.errorClass,""),this.findByName(a[b].name).removeClass(this.settings.validClass);else a.removeClass(this.settings.errorClass).removeClass(this.settings.validClass)},numberOfInvalids:function(){return this.objectLength(this.invalid)},objectLength:function(a){var b,c=0;for(b in a)void 0!==a[b]&&null!==a[b]&&a[b]!==!1&&c++;return c},hideErrors:function(){this.hideThese(this.toHide)},hideThese:function(a){a.not(this.containers).text(""),this.addWrapper(a).hide()},valid:function(){return 0===this.size()},size:function(){return this.errorList.length},focusInvalid:function(){if(this.settings.focusInvalid)try{a(this.findLastActive()||this.errorList.length&&this.errorList[0].element||[]).filter(":visible").trigger("focus").trigger("focusin")}catch(b){}},findLastActive:function(){var b=this.lastActive;return b&&1===a.grep(this.errorList,function(a){return a.element.name===b.name}).length&&b},elements:function(){var b=this,c={};return a(this.currentForm).find("input, select, textarea, [contenteditable]").not(":submit, :reset, :image, :disabled").not(this.settings.ignore).filter(function(){var d=this.name||a(this).attr("name"),e="undefined"!=typeof a(this).attr("contenteditable")&&"false"!==a(this).attr("contenteditable");return!d&&b.settings.debug&&window.console&&console.error("%o has no name assigned",this),e&&(this.form=a(this).closest("form")[0],this.name=d),this.form===b.currentForm&&(!(d in c||!b.objectLength(a(this).rules()))&&(c[d]=!0,!0))})},clean:function(b){return a(b)[0]},errors:function(){var b=this.settings.errorClass.split(" ").join(".");return a(this.settings.errorElement+"."+b,this.errorContext)},resetInternals:function(){this.successList=[],this.errorList=[],this.errorMap={},this.toShow=a([]),this.toHide=a([])},reset:function(){this.resetInternals(),this.currentElements=a([])},prepareForm:function(){this.reset(),this.toHide=this.errors().add(this.containers)},prepareElement:function(a){this.reset(),this.toHide=this.errorsFor(a)},elementValue:function(b){var c,d,e=a(b),f=b.type,g="undefined"!=typeof e.attr("contenteditable")&&"false"!==e.attr("contenteditable");return"radio"===f||"checkbox"===f?this.findByName(b.name).filter(":checked").val():"number"===f&&"undefined"!=typeof b.validity?b.validity.badInput?"NaN":e.val():(c=g?e.text():e.val(),"file"===f?"C:\\fakepath\\"===c.substr(0,12)?c.substr(12):(d=c.lastIndexOf("/"),d>=0?c.substr(d+1):(d=c.lastIndexOf("\\"),d>=0?c.substr(d+1):c)):"string"==typeof c?c.replace(/\r/g,""):c)},check:function(b){b=this.validationTargetFor(this.clean(b));var c,d,e,f,g=a(b).rules(),h=a.map(g,function(a,b){return b}).length,i=!1,j=this.elementValue(b);"function"==typeof g.normalizer?f=g.normalizer:"function"==typeof this.settings.normalizer&&(f=this.settings.normalizer),f&&(j=f.call(b,j),delete g.normalizer);for(d in g){e={method:d,parameters:g[d]};try{if(c=a.validator.methods[d].call(this,j,b,e.parameters),"dependency-mismatch"===c&&1===h){i=!0;continue}if(i=!1,"pending"===c)return void(this.toHide=this.toHide.not(this.errorsFor(b)));if(!c)return this.formatAndAdd(b,e),!1}catch(k){throw this.settings.debug&&window.console&&console.log("Exception occurred when checking element "+b.id+", check the '"+e.method+"' method.",k),k instanceof TypeError&&(k.message+=". Exception occurred when checking element "+b.id+", check the '"+e.method+"' method."),k}}if(!i)return this.objectLength(g)&&this.successList.push(b),!0},customDataMessage:function(b,c){return a(b).data("msg"+c.charAt(0).toUpperCase()+c.substring(1).toLowerCase())||a(b).data("msg")},customMessage:function(a,b){var c=this.settings.messages[a];return c&&(c.constructor===String?c:c[b])},findDefined:function(){for(var a=0;aWarning: No message defined for "+b.name+""),e=/\$?\{(\d+)\}/g;return"function"==typeof d?d=d.call(this,c.parameters,b):e.test(d)&&(d=a.validator.format(d.replace(e,"{$1}"),c.parameters)),d},formatAndAdd:function(a,b){var c=this.defaultMessage(a,b);this.errorList.push({message:c,element:a,method:b.method}),this.errorMap[a.name]=c,this.submitted[a.name]=c},addWrapper:function(a){return this.settings.wrapper&&(a=a.add(a.parent(this.settings.wrapper))),a},defaultShowErrors:function(){var a,b,c;for(a=0;this.errorList[a];a++)c=this.errorList[a],this.settings.highlight&&this.settings.highlight.call(this,c.element,this.settings.errorClass,this.settings.validClass),this.showLabel(c.element,c.message);if(this.errorList.length&&(this.toShow=this.toShow.add(this.containers)),this.settings.success)for(a=0;this.successList[a];a++)this.showLabel(this.successList[a]);if(this.settings.unhighlight)for(a=0,b=this.validElements();b[a];a++)this.settings.unhighlight.call(this,b[a],this.settings.errorClass,this.settings.validClass);this.toHide=this.toHide.not(this.toShow),this.hideErrors(),this.addWrapper(this.toShow).show()},validElements:function(){return this.currentElements.not(this.invalidElements())},invalidElements:function(){return a(this.errorList).map(function(){return this.element})},showLabel:function(b,c){var d,e,f,g,h=this.errorsFor(b),i=this.idOrName(b),j=a(b).attr("aria-describedby");h.length?(h.removeClass(this.settings.validClass).addClass(this.settings.errorClass),h.html(c)):(h=a("<"+this.settings.errorElement+">").attr("id",i+"-error").addClass(this.settings.errorClass).html(c||""),d=h,this.settings.wrapper&&(d=h.hide().show().wrap("<"+this.settings.wrapper+"/>").parent()),this.labelContainer.length?this.labelContainer.append(d):this.settings.errorPlacement?this.settings.errorPlacement.call(this,d,a(b)):d.insertAfter(b),h.is("label")?h.attr("for",i):0===h.parents("label[for='"+this.escapeCssMeta(i)+"']").length&&(f=h.attr("id"),j?j.match(new RegExp("\\b"+this.escapeCssMeta(f)+"\\b"))||(j+=" "+f):j=f,a(b).attr("aria-describedby",j),e=this.groups[b.name],e&&(g=this,a.each(g.groups,function(b,c){c===e&&a("[name='"+g.escapeCssMeta(b)+"']",g.currentForm).attr("aria-describedby",h.attr("id"))})))),!c&&this.settings.success&&(h.text(""),"string"==typeof this.settings.success?h.addClass(this.settings.success):this.settings.success(h,b)),this.toShow=this.toShow.add(h)},errorsFor:function(b){var c=this.escapeCssMeta(this.idOrName(b)),d=a(b).attr("aria-describedby"),e="label[for='"+c+"'], label[for='"+c+"'] *";return d&&(e=e+", #"+this.escapeCssMeta(d).replace(/\s+/g,", #")),this.errors().filter(e)},escapeCssMeta:function(a){return void 0===a?"":a.replace(/([\\!"#$%&'()*+,./:;<=>?@\[\]^`{|}~])/g,"\\$1")},idOrName:function(a){return this.groups[a.name]||(this.checkable(a)?a.name:a.id||a.name)},validationTargetFor:function(b){return this.checkable(b)&&(b=this.findByName(b.name)),a(b).not(this.settings.ignore)[0]},checkable:function(a){return/radio|checkbox/i.test(a.type)},findByName:function(b){return a(this.currentForm).find("[name='"+this.escapeCssMeta(b)+"']")},getLength:function(b,c){switch(c.nodeName.toLowerCase()){case"select":return a("option:selected",c).length;case"input":if(this.checkable(c))return this.findByName(c.name).filter(":checked").length}return b.length},depend:function(a,b){return!this.dependTypes[typeof a]||this.dependTypes[typeof a](a,b)},dependTypes:{"boolean":function(a){return a},string:function(b,c){return!!a(b,c.form).length},"function":function(a,b){return a(b)}},optional:function(b){var c=this.elementValue(b);return!a.validator.methods.required.call(this,c,b)&&"dependency-mismatch"},startRequest:function(b){this.pending[b.name]||(this.pendingRequest++,a(b).addClass(this.settings.pendingClass),this.pending[b.name]=!0)},stopRequest:function(b,c){this.pendingRequest--,this.pendingRequest<0&&(this.pendingRequest=0),delete this.pending[b.name],a(b).removeClass(this.settings.pendingClass),c&&0===this.pendingRequest&&this.formSubmitted&&this.form()&&0===this.pendingRequest?(a(this.currentForm).trigger("submit"),this.submitButton&&a("input:hidden[name='"+this.submitButton.name+"']",this.currentForm).remove(),this.formSubmitted=!1):!c&&0===this.pendingRequest&&this.formSubmitted&&(a(this.currentForm).triggerHandler("invalid-form",[this]),this.formSubmitted=!1)},previousValue:function(b,c){return c="string"==typeof c&&c||"remote",a.data(b,"previousValue")||a.data(b,"previousValue",{old:null,valid:!0,message:this.defaultMessage(b,{method:c})})},destroy:function(){this.resetForm(),a(this.currentForm).off(".validate").removeData("validator").find(".validate-equalTo-blur").off(".validate-equalTo").removeClass("validate-equalTo-blur").find(".validate-lessThan-blur").off(".validate-lessThan").removeClass("validate-lessThan-blur").find(".validate-lessThanEqual-blur").off(".validate-lessThanEqual").removeClass("validate-lessThanEqual-blur").find(".validate-greaterThanEqual-blur").off(".validate-greaterThanEqual").removeClass("validate-greaterThanEqual-blur").find(".validate-greaterThan-blur").off(".validate-greaterThan").removeClass("validate-greaterThan-blur")}},classRuleSettings:{required:{required:!0},email:{email:!0},url:{url:!0},date:{date:!0},dateISO:{dateISO:!0},number:{number:!0},digits:{digits:!0},creditcard:{creditcard:!0}},addClassRules:function(b,c){b.constructor===String?this.classRuleSettings[b]=c:a.extend(this.classRuleSettings,b)},classRules:function(b){var c={},d=a(b).attr("class");return d&&a.each(d.split(" "),function(){this in a.validator.classRuleSettings&&a.extend(c,a.validator.classRuleSettings[this])}),c},normalizeAttributeRule:function(a,b,c,d){/min|max|step/.test(c)&&(null===b||/number|range|text/.test(b))&&(d=Number(d),isNaN(d)&&(d=void 0)),d||0===d?a[c]=d:b===c&&"range"!==b&&(a["date"===b?"dateISO":c]=!0)},attributeRules:function(b){var c,d,e={},f=a(b),g=b.getAttribute("type");for(c in a.validator.methods)"required"===c?(d=b.getAttribute(c),""===d&&(d=!0),d=!!d):d=f.attr(c),this.normalizeAttributeRule(e,g,c,d);return e.maxlength&&/-1|2147483647|524288/.test(e.maxlength)&&delete e.maxlength,e},dataRules:function(b){var c,d,e={},f=a(b),g=b.getAttribute("type");for(c in a.validator.methods)d=f.data("rule"+c.charAt(0).toUpperCase()+c.substring(1).toLowerCase()),""===d&&(d=!0),this.normalizeAttributeRule(e,g,c,d);return e},staticRules:function(b){var c={},d=a.data(b.form,"validator");return d.settings.rules&&(c=a.validator.normalizeRule(d.settings.rules[b.name])||{}),c},normalizeRules:function(b,c){return a.each(b,function(d,e){if(e===!1)return void delete b[d];if(e.param||e.depends){var f=!0;switch(typeof e.depends){case"string":f=!!a(e.depends,c.form).length;break;case"function":f=e.depends.call(c,c)}f?b[d]=void 0===e.param||e.param:(a.data(c.form,"validator").resetElements(a(c)),delete b[d])}}),a.each(b,function(a,d){b[a]="function"==typeof d&&"normalizer"!==a?d(c):d}),a.each(["minlength","maxlength"],function(){b[this]&&(b[this]=Number(b[this]))}),a.each(["rangelength","range"],function(){var a;b[this]&&(Array.isArray(b[this])?b[this]=[Number(b[this][0]),Number(b[this][1])]:"string"==typeof b[this]&&(a=b[this].replace(/[\[\]]/g,"").split(/[\s,]+/),b[this]=[Number(a[0]),Number(a[1])]))}),a.validator.autoCreateRanges&&(null!=b.min&&null!=b.max&&(b.range=[b.min,b.max],delete b.min,delete b.max),null!=b.minlength&&null!=b.maxlength&&(b.rangelength=[b.minlength,b.maxlength],delete b.minlength,delete b.maxlength)),b},normalizeRule:function(b){if("string"==typeof b){var c={};a.each(b.split(/\s/),function(){c[this]=!0}),b=c}return b},addMethod:function(b,c,d){a.validator.methods[b]=c,a.validator.messages[b]=void 0!==d?d:a.validator.messages[b],c.length<3&&a.validator.addClassRules(b,a.validator.normalizeRule(b))},methods:{required:function(b,c,d){if(!this.depend(d,c))return"dependency-mismatch";if("select"===c.nodeName.toLowerCase()){var e=a(c).val();return e&&e.length>0}return this.checkable(c)?this.getLength(b,c)>0:void 0!==b&&null!==b&&b.length>0},email:function(a,b){return this.optional(b)||/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test(a)},url:function(a,b){return this.optional(b)||/^(?:(?:(?:https?|ftp):)?\/\/)(?:(?:[^\]\[?\/<~#`!@$^&*()+=}|:";',>{ ]|%[0-9A-Fa-f]{2})+(?::(?:[^\]\[?\/<~#`!@$^&*()+=}|:";',>{ ]|%[0-9A-Fa-f]{2})*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\u00a1-\uffff][a-z0-9\u00a1-\uffff_-]{0,62})?[a-z0-9\u00a1-\uffff]\.)+(?:[a-z\u00a1-\uffff]{2,}\.?))(?::\d{2,5})?(?:[/?#]\S*)?$/i.test(a)},date:function(){var a=!1;return function(b,c){return a||(a=!0,this.settings.debug&&window.console&&console.warn("The `date` method is deprecated and will be removed in version '2.0.0'.\nPlease don't use it, since it relies on the Date constructor, which\nbehaves very differently across browsers and locales. Use `dateISO`\ninstead or one of the locale specific methods in `localizations/`\nand `additional-methods.js`.")),this.optional(c)||!/Invalid|NaN/.test(new Date(b).toString())}}(),dateISO:function(a,b){return this.optional(b)||/^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test(a)},number:function(a,b){return this.optional(b)||/^(?:-?\d+|-?\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test(a)},digits:function(a,b){return this.optional(b)||/^\d+$/.test(a)},minlength:function(a,b,c){var d=Array.isArray(a)?a.length:this.getLength(a,b);return this.optional(b)||d>=c},maxlength:function(a,b,c){var d=Array.isArray(a)?a.length:this.getLength(a,b);return this.optional(b)||d<=c},rangelength:function(a,b,c){var d=Array.isArray(a)?a.length:this.getLength(a,b);return this.optional(b)||d>=c[0]&&d<=c[1]},min:function(a,b,c){return this.optional(b)||a>=c},max:function(a,b,c){return this.optional(b)||a<=c},range:function(a,b,c){return this.optional(b)||a>=c[0]&&a<=c[1]},step:function(b,c,d){var e,f=a(c).attr("type"),g="Step attribute on input type "+f+" is not supported.",h=["text","number","range"],i=new RegExp("\\b"+f+"\\b"),j=f&&!i.test(h.join()),k=function(a){var b=(""+a).match(/(?:\.(\d+))?$/);return b&&b[1]?b[1].length:0},l=function(a){return Math.round(a*Math.pow(10,e))},m=!0;if(j)throw new Error(g);return e=k(d),(k(b)>e||l(b)%l(d)!==0)&&(m=!1),this.optional(c)||m},equalTo:function(b,c,d){var e=a(d);return this.settings.onfocusout&&e.not(".validate-equalTo-blur").length&&e.addClass("validate-equalTo-blur").on("blur.validate-equalTo",function(){a(c).valid()}),b===e.val()},remote:function(b,c,d,e){if(this.optional(c))return"dependency-mismatch";e="string"==typeof e&&e||"remote";var f,g,h,i=this.previousValue(c,e);return this.settings.messages[c.name]||(this.settings.messages[c.name]={}),i.originalMessage=i.originalMessage||this.settings.messages[c.name][e],this.settings.messages[c.name][e]=i.message,d="string"==typeof d&&{url:d}||d,h=a.param(a.extend({data:b},d.data)),i.old===h?i.valid:(i.old=h,f=this,this.startRequest(c),g={},g[c.name]=b,a.ajax(a.extend(!0,{mode:"abort",port:"validate"+c.name,dataType:"json",data:g,context:f.currentForm,success:function(a){var d,g,h,j=a===!0||"true"===a;f.settings.messages[c.name][e]=i.originalMessage,j?(h=f.formSubmitted,f.resetInternals(),f.toHide=f.errorsFor(c),f.formSubmitted=h,f.successList.push(c),f.invalid[c.name]=!1,f.showErrors()):(d={},g=a||f.defaultMessage(c,{method:e,parameters:b}),d[c.name]=i.message=g,f.invalid[c.name]=!0,f.showErrors(d)),i.valid=j,f.stopRequest(c,j)}},d)),"pending")}}});var c,d={};return a.ajaxPrefilter?a.ajaxPrefilter(function(a,b,c){var e=a.port;"abort"===a.mode&&(d[e]&&d[e].abort(),d[e]=c)}):(c=a.ajax,a.ajax=function(b){var e=("mode"in b?b:a.ajaxSettings).mode,f=("port"in b?b:a.ajaxSettings).port;return"abort"===e?(d[f]&&d[f].abort(),d[f]=c.apply(this,arguments),d[f]):c.apply(this,arguments)}),a}); \ No newline at end of file diff --git a/app/controllers/admin/site_panel_controller.rb b/app/controllers/admin/site_panel_controller.rb index 2303911..962f40d 100644 --- a/app/controllers/admin/site_panel_controller.rb +++ b/app/controllers/admin/site_panel_controller.rb @@ -45,6 +45,65 @@ class Admin::SitePanelController < OrbitAdminController return super end end + def super_manager_management + @site_server = SiteServer.find(params[:id]) + end + def update_super_manager_management + @site_server = SiteServer.find(params[:id]) + site_server_params = params.require(:site_server).permit! rescue {} + removed_super_users = [] + site_server_params["site_super_users_attributes"].each do |k, v| + if v["_destroy"] == 'true' + super_user = SiteSuperUser.find(v["id"]) + if super_user.old_user_name.nil? + removed_super_users << super_user.user_name + elsif super_user.old_user_name != '' + removed_super_users << super_user.old_user_name + end + end + end if site_server_params["site_super_users_attributes"] + @site_server.update_attributes(site_server_params) + @site_server.check_super_user_changed + if removed_super_users.count != 0 + @site_server.removed_super_users += removed_super_users + @site_server.super_user_changed = true + @site_server.save + end + redirect_back_custom + end + def super_manager_management_for_site + @site_construct = SiteConstruct.find(params[:id]) + end + def update_super_manager_management_for_site + @site_construct = SiteConstruct.find(params[:id]) + site_construct_params = params.require(:site_construct).permit! rescue {} + removed_super_users = [] + site_construct_params["site_super_users_attributes"].each do |k, v| + if v["_destroy"] == 'true' + super_user = SiteSuperUser.find(v["id"]) + if super_user.old_user_name.nil? + removed_super_users << super_user.user_name + elsif super_user.old_user_name != '' + removed_super_users << super_user.old_user_name + end + end + end if site_construct_params["site_super_users_attributes"] + @site_construct.update_attributes(site_construct_params) + @site_construct.check_super_user_changed + if removed_super_users.count != 0 + @site_construct.removed_super_users += removed_super_users + @site_construct.super_user_changed = true + @site_construct.save + end + redirect_back_custom + end + def redirect_back_custom + if Is_Rails5 + redirect_back(fallback_location: { action: "setting"}) + else + redirect_to :back + end + end def setting ClientManagerSetting.create if ClientManagerSetting.count == 0 @setting = ClientManagerSetting.first @@ -53,11 +112,7 @@ class Admin::SitePanelController < OrbitAdminController setting_params = params.require(:client_manager_setting).permit! setting = ClientManagerSetting.first setting.update_attributes(setting_params) - if Is_Rails5 - redirect_back(fallback_location: { action: "setting"}) - else - redirect_to :back and return - end + redirect_back_custom end def upload_cert @site_cert = SiteCert.new @@ -331,24 +386,101 @@ class Admin::SitePanelController < OrbitAdminController system("bundle exec rake create_site:change_site_cert[#{params[:id]},#{is_certbot},#{params[:is_server]}]") end end + elsif params[:type] == 'apply_change_users' + Thread.new do + exec_commands_args = {} + site_server = nil + site_construct = nil + if params[:is_server] == 'true' + site_server = SiteServer.find(params[:id]) + site_super_users = site_server.site_super_users.where(:is_changed=>true).to_a + removed_super_users = site_server.removed_super_users + exec_commands_args = {:type=>'exec_all', :server_name=>site_server.server_name} + clean_thread_execing_info + else + site_construct = SiteConstruct.find(params[:id]) + site_super_users = site_construct.site_super_users.where(:is_changed=>true).to_a + removed_super_users = site_construct.removed_super_users + exec_commands_args = {:site_construct_id=>params[:id], :type=>'exec_commands'} + end + commands = [] + commands_i18n = [] + removed_super_users.uniq.each do |user_name| + commands << remove_user_mongo_eval_cmd(user_name) + commands_i18n << "Delete Super Manager: #{user_name}" + end + site_super_users.each do |super_user| + user_attrs = super_user.get_attrs + old_user_name = super_user.old_user_name + member_profile = MemberProfile.new(id: nil, is_hidden: true, position: 0) + member_profile.send(:generate_uid) + member_attrs = member_profile.attributes + member_attrs.delete('_id') + commands << user_mongo_eval_cmd(user_attrs, member_attrs, (old_user_name.blank? ? nil : old_user_name)) + if old_user_name == '' + commands_i18n << "Create Super Manager: #{super_user.user_name}" + else + commands_i18n << "Update Super Manager: #{super_user.user_name}" + end + end + if commands.count > 0 + site_construct.update(:status=>"execing",:infos=>[""]) if site_construct + exec_commands_args[:commands] = commands + exec_commands_args[:commands_i18n] = commands_i18n + if system("bundle exec rake exec_commands:exec_commands[\"#{Base64.strict_encode64(exec_commands_args.to_json)}\"]") + SiteSuperUser.where(:id.in=>site_super_users.map{|u| u.id}).clear_changed + if site_server + site_server.update(:super_user_changed => false, :removed_super_users => []) + end + if site_construct + site_construct.update(:super_user_changed => false, :removed_super_users => []) + end + end + end + end else Thread.new do cmds = params[:commands].split('////') + commands_i18n = (0...cmds.count).collect{nil} if cmds.include?('{{create_super_manager}}') && !params[:account].blank? && !params[:password].blank? + site_server_ids = [] + site_construct_id = nil + if params[:id].blank? && !params[:server_names].blank? + site_server_ids = SiteServer.where(:server_name.in=>params[:server_names]).pluck(:id) + else + site_construct = SiteConstruct.find(params[:id]) + site_construct.update(:status=>"execing",:infos=>[""]) + site_construct_id = site_construct.id rescue nil + end idx = cmds.index('{{create_super_manager}}') - cmds[idx] = create_super_manager_cmd(params[:account],params[:password]) + cmds[idx] = create_super_manager_cmd(params[:account],params[:password], site_server_ids, site_construct_id) + commands_i18n[idx] = "#{@create_super_user ? 'Create' : 'Update'} Super Manager: #{params[:account]}" + end + if commands_i18n.compact.count == 0 + commands_i18n = nil end exec_commands_args = {} if params[:id].blank? && !params[:server_names].blank? exec_commands_args = {:commands=>cmds.join('////'), :type=>'exec_all', :server_name=>params[:server_names].join('////')} + clean_thread_execing_info else exec_commands_args = {:site_construct_id=>params[:id], :commands=>cmds.join('////'), :type=>'exec_commands'} end + exec_commands_args[:commands_i18n] = commands_i18n system("bundle exec rake exec_commands:exec_commands[\"#{Base64.strict_encode64(exec_commands_args.to_json)}\"]") end end render :json =>{"success"=>true} end + def clean_thread_execing_info + Multithread.where(:key=>'execing_commands').each do |thread| + if thread.status["status"] == "error" || thread.status["status"] == "finish" + thread.destroy + elsif thread.updated_at < (Time.now - 5.minute) + thread.destroy + end + end + end def sites_list @module_app = ModuleApp.where(:title=>@app_title).first @categories = @module_app.categories @@ -419,17 +551,50 @@ class Admin::SitePanelController < OrbitAdminController site_params = params.require(:site_construct).permit! site_params.except(:domain_name,:port) end - def create_super_manager_cmd(user_name, password) - user = User.new(id: nil, user_name: user_name, password: password, beta_tester: true, approved: true) - member_profile = user.build_member_profile(id: nil, is_hidden: true) - user_attrs = user.attributes - user_attrs.delete('_id') - user_attrs.delete('member_profile_id') + def create_super_manager_cmd(user_name, password, site_server_ids, site_construct_id) + password_digest = SiteSuperUser.generate_password_digest(password) + user_attrs = nil + super_user = nil + if site_server_ids.present? + site_server_ids.each_with_index do |site_server_id, i| + super_user = SiteSuperUser.where(:site_construct_id=>site_construct_id, :user_name=>user_name).first + if super_user.nil? + super_user = SiteSuperUser.new(:site_construct_id=>site_construct_id, :user_name=>user_name) + @create_super_user = true + end + super_user.update_password_digest(password_digest) + if i == 0 + user_attrs = super_user.get_attrs + end + end + elsif site_construct_id.present? + super_user = SiteSuperUser.where(:site_construct_id=>site_construct_id, :user_name=>user_name).first + if super_user.nil? + super_user = SiteSuperUser.new(:site_construct_id=>site_construct_id, :user_name=>user_name) + @create_super_user = true + end + super_user.update_password_digest(password_digest) + user_attrs = super_user.get_attrs + end + member_profile = MemberProfile.new(id: nil, is_hidden: true, position: 0) member_profile.send(:generate_uid) member_attrs = member_profile.attributes member_attrs.delete('_id') + user_mongo_eval_cmd(user_attrs, member_attrs) + end + def remove_user_mongo_eval_cmd(user_name) cmd = """mongo --eval 'a=function(){ - var user_query = {user_name: \"#{user_attrs['user_name']}\"}; + var user_query = {user_name: \"#{user_name}\"}; + user = db.users.findOne(user_query); + if (user) { + db.member_profiles.deleteOne({_id: user[\"member_profile_id\"]}); + db.users.deleteOne(user_query); + } + }()' {{db_name}}""".gsub(/\n|^ */,"") + end + def user_mongo_eval_cmd(user_attrs, member_attrs, old_user_name=nil) + cmd = """mongo --eval 'a=function(){ + var user_query = {user_name: \"#{old_user_name ? old_user_name : user_attrs['user_name']}\"}; var user = db.users.findOne(user_query); var user_id; var user_data = #{user_attrs.to_json}; @@ -443,6 +608,7 @@ class Admin::SitePanelController < OrbitAdminController }else{ db.users.insertOne(user_data); } + user_query = {user_name: \"#{user_attrs['user_name']}\"}; user = db.users.findOne(user_query); var member_query = {_id: user.member_profile_id}; var member = user.member_profile_id ? db.member_profiles.findOne(member_query) : null; diff --git a/app/models/site_construct.rb b/app/models/site_construct.rb index 3a82759..fc37624 100644 --- a/app/models/site_construct.rb +++ b/app/models/site_construct.rb @@ -28,7 +28,11 @@ class SiteConstruct field :cert_ver_added_text field :cert_ver_file_content field :cert_ver_location_path + field :super_user_changed, type: Boolean, type: false + field :removed_super_users, type: Array, default: [] belongs_to :site_cert + has_many :site_super_users, :autosave => true, :dependent => :destroy + accepts_nested_attributes_for :site_super_users, :allow_destroy => true after_initialize do |record| unless record.new_record? save_flag = false @@ -60,6 +64,9 @@ class SiteConstruct @skip_callback = false end end + def check_super_user_changed + self.update(:super_user_changed => (self.site_super_users.where(:is_changed=>true).count != 0 || self.removed_super_users.count != 0)) + end def generate_nginx_text(old_nginx_text="") sock_text = "upstream #{self.get_site_name}_sock {\n"+ " server unix:#{self.full_site_path}/tmp/unicorn.sock;\n"+ diff --git a/app/models/site_server.rb b/app/models/site_server.rb index e50d353..4d7eed9 100644 --- a/app/models/site_server.rb +++ b/app/models/site_server.rb @@ -15,6 +15,13 @@ class SiteServer field :active , type: Boolean ,default: true field :has_certbot, type: Boolean , default: false field :need_update_site_ids, type: Array, default: [] + field :super_user_changed, type: Boolean, type: false + field :removed_super_users, type: Array, default: [] + has_many :site_super_users, :autosave => true, :dependent => :destroy + accepts_nested_attributes_for :site_super_users, :allow_destroy => true + def check_super_user_changed + self.update(:super_user_changed => (self.site_super_users.where(:is_changed=>true).count != 0 || self.removed_super_users.count != 0)) + end def domain_names if self.domain_name != '' [self.domain_name] diff --git a/app/models/site_super_user.rb b/app/models/site_super_user.rb new file mode 100644 index 0000000..21d7d9e --- /dev/null +++ b/app/models/site_super_user.rb @@ -0,0 +1,118 @@ +class SiteSuperUser + include Mongoid::Document + include Mongoid::Timestamps + require_dependency 'bcrypt' + begin + require_or_load 'bcrypt/engine' + rescue + Object.send(:remove_const, 'BCrypt') rescue nil + $LOADED_FEATURES.select!{|p| !p.include? 'bcrypt'} + require 'bcrypt' + end + require_dependency 'active_model/secure_password' + include ActiveModel::SecurePassword + field :is_changed, type: Boolean, default: true + field :old_user_name, type: String, default: '' + field :user_name, type: String + field :password_high_security, type: Boolean, default: false + field :password_updated_at, type: Time + field :password_digest, type: String + field :old_password_digest_list, type: Array, default: [] + field :beta_tester, type: Boolean, default: true + field :approved, type: Boolean, default: true + belongs_to :site_server + belongs_to :site_construct + has_secure_password + CurrentSite = Site.first + PasswordValidRegex = ::Regexp.new("^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.{8,})") + CurrentSite.password_change_constrained = 5 + before_save do + if self.user_name_changed? && self.user_name_was + self.old_user_name = self.user_name_was if !(self.is_changed) || self.old_user_name.blank? + self.is_changed = true + end + end + before_destroy do + if self.site_server + self.site_server.removed_super_users << (self.old_user_name.blank? ? self.user_name : self.old_user_name) + self.site_server.super_user_changed = true + self.site_server.save + elsif self.site_construct + self.site_construct.removed_super_users << (self.old_user_name.blank? ? self.user_name : self.old_user_name) + self.site_construct.super_user_changed = true + self.site_construct.save + end + end + def self.clear_changed + self.update_all(:is_changed=>false, :old_user_name=>nil) + end + def clear_changed + self.is_changed = false + self.old_user_name = nil + self.save + end + def self.generate_password_digest(password) + if password.length < 8 + raise StandardError.new('Password too short!') + end + BCrypt::Password.create(password) + end + def old_password_digest_list_check(password) + tmps = self.old_password_digest_list + [self.password_digest] + tmps = tmps.compact.uniq + if tmps.length>0 + tmp_len = self.class::CurrentSite.password_change_constrained+1 + tmp_len = tmps.length the same as current password + self.is_changed = true + puts "@old_password_index: #{@old_password_index}" + self.old_password_digest_list << self.password_digest + self.old_password_digest_list.delete_at(@old_password_index) + super(value) + if !self.new_record? + self.save + end + end + end + self + end +end \ No newline at end of file diff --git a/app/views/admin/site_panel/_server_manager_index.html.erb b/app/views/admin/site_panel/_server_manager_index.html.erb index 8771d89..28d54d1 100644 --- a/app/views/admin/site_panel/_server_manager_index.html.erb +++ b/app/views/admin/site_panel/_server_manager_index.html.erb @@ -70,6 +70,7 @@ "><%= t('client_management.see_sites') %> <%= t('client_management.backup_setting') %> Detect sites + <%=t('client_management.super_manager_management')%> <% end %> diff --git a/app/views/admin/site_panel/_super_manager_form.html.erb b/app/views/admin/site_panel/_super_manager_form.html.erb new file mode 100644 index 0000000..a369226 --- /dev/null +++ b/app/views/admin/site_panel/_super_manager_form.html.erb @@ -0,0 +1,85 @@ +<%= javascript_include_tag('/assets/client_management/jquery.validate.min') %> +<% + relation_field = "#{type.pluralize}" + records = f.object.send(relation_field) +%> +
+ +
+ <% if f.object.super_user_changed %> + <%= t('client_management.apply_change') %> +
+ <% end %> +
+
+ <% records.each do |record| %> + <%= f.fields_for relation_field, record do |f| %> + <%= render :partial => "super_manager_form_block", :locals=>{:f=>f} %> + <% end %> + <% end %> +
+

+ <%= hidden_field_tag "#{relation_field}_count", records.count %> + <%= t("client_management.create_super_manager") %> +

+
+ + \ No newline at end of file diff --git a/app/views/admin/site_panel/_super_manager_form_block.html.erb b/app/views/admin/site_panel/_super_manager_form_block.html.erb new file mode 100644 index 0000000..5fcdf87 --- /dev/null +++ b/app/views/admin/site_panel/_super_manager_form_block.html.erb @@ -0,0 +1,27 @@ +<% object_name_underscore = f.object_name.gsub(/[\[\]]/,'_') %> +
+ X +
+
+ <%= f.label :user_name ,t("client_management.user_name"), :class => "control-label muted" %> +
+ <%= f.text_field :user_name, :placeholder => t("client_management.user_name") %> +
+
+
+ <%= f.label :new_password ,t("client_management.new_password"), :class => "control-label muted", :for=>"#{object_name_underscore}_new_password" %> +
+ <%= f.password_field_tag "#{f.object_name}[password]", nil, :placeholder => t("client_management.new_password"), :id=>"#{object_name_underscore}_new_password", :data => {"rule-password"=> true} %> +
+
+
+ <%= f.label :confirm_new_password ,t("client_management.confirm_new_password"), :class => "control-label muted", :for=>"#{object_name_underscore}_confirm_new_password" %> +
+ <%= f.password_field_tag "#{f.object_name}[password_confirmation]", nil, :placeholder => t("client_management.confirm_new_password"), :id=>"#{object_name_underscore}_confirm_new_password", :data => {"rule-password"=> true, "rule-equalTo"=>"##{object_name_underscore}_new_password"} %> +
+
+ <% unless f.object.new_record? %> + <%= f.hidden_field :id %> + <%= f.hidden_field :_destroy, :value => nil, :class => 'should_destroy' %> + <% end %> +
\ No newline at end of file diff --git a/app/views/admin/site_panel/super_manager_management.html.erb b/app/views/admin/site_panel/super_manager_management.html.erb new file mode 100644 index 0000000..7c83887 --- /dev/null +++ b/app/views/admin/site_panel/super_manager_management.html.erb @@ -0,0 +1,108 @@ +<%= form_for @site_server, :url => {:action=>"update_super_manager_management"}, :html => {:class => 'form-horizontal main-forms super_manager_form'} do |f| %> +<% content_for :page_specific_css do %> + <%= stylesheet_link_tag "lib/main-forms" %> +<% end %> +<% content_for :page_specific_javascript do %> +<% end %> +<%#= f.error_messages %> +
+

<%= "#{f.object.server_name} (IP: #{f.object.ip}) " %>

+ +
+ <%= render :partial => "super_manager_form", :locals=>{:f=>f,:type=>"site_super_user"} %> +
+ <%= f.submit t('submit'), class: 'btn btn-primary' %> +
+
+
+ + +<% end %> \ No newline at end of file diff --git a/app/views/admin/site_panel/super_manager_management_for_site.html.erb b/app/views/admin/site_panel/super_manager_management_for_site.html.erb new file mode 100644 index 0000000..f1a21cf --- /dev/null +++ b/app/views/admin/site_panel/super_manager_management_for_site.html.erb @@ -0,0 +1,124 @@ +<%= form_for @site_construct, :url => {:action=>"update_super_manager_management_for_site"}, :html => {:class => 'form-horizontal main-forms super_manager_form'} do |f| %> +<% content_for :page_specific_css do %> + <%= stylesheet_link_tag "lib/main-forms" %> +<% end %> +<% content_for :page_specific_javascript do %> +<% end %> +<%#= f.error_messages %> +
+

<%= "#{f.object.site_name} (Domain: #{f.object.get_default_domain(true)}) " %>

+ +
+ <%= render :partial => "super_manager_form", :locals=>{:f=>f,:type=>"site_super_user"} %> +
+ <%= f.submit t('submit'), class: 'btn btn-primary' %> +
+
+
+ + +<% end %> \ No newline at end of file diff --git a/config/locales/en.yml b/config/locales/en.yml index d360f38..2cec32f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -7,6 +7,12 @@ en: upload_cert: Upload Cert cert_management: Cert Management client_management: + apply_change: "Apply Change" + are_you_sure_to_delete: "Are you sure to delete?" + user_name: "User Account" + new_password: "New Password" + confirm_new_password: "Confirm new password" + super_manager_management: "Super Manager Management" create_super_manager: Create Super Manager For Site enable_api: Enable API api_key: API Key diff --git a/config/locales/zh_tw.yml b/config/locales/zh_tw.yml index afabc3a..d2d1d93 100644 --- a/config/locales/zh_tw.yml +++ b/config/locales/zh_tw.yml @@ -7,6 +7,12 @@ zh_tw: upload_cert: 上傳憑證 cert_management: 憑證管理 client_management: + apply_change: "設定生效" + are_you_sure_to_delete: "您確定要刪除嗎?" + user_name: "使用者帳號" + new_password: "新密碼" + confirm_new_password: "確認新密碼" + super_manager_management: "管理網站超級管理者" create_super_manager: 創建網站超級管理者 enable_api: 開啟API api_key: API Key diff --git a/config/routes.rb b/config/routes.rb index 75512c1..feb563b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -67,6 +67,12 @@ Rails.application.routes.draw do post 'create_cert' patch 'create_cert' end + member do + get "super_manager_management" + patch "update_super_manager_management" + get "super_manager_management_for_site" + patch "update_super_manager_management_for_site" + end end end