From d58c0a0eebd6545fdb634b2ceaec3a67f4710dd9 Mon Sep 17 00:00:00 2001 From: Federico Capoano Date: Wed, 22 Jan 2025 16:11:30 -0300 Subject: [PATCH] [qa] Switched JS and CSS linting to prettier --- .jshintrc | 19 - .jshintignore => .prettierignore | 0 .stylelintrc.json | 20 - .../static/config/css/lib/advanced-mode.css | 195 +- .../static/config/css/lib/jsonschema-ui.css | 443 +++- .../config/static/config/js/preview.js | 199 +- .../static/config/js/relevant_templates.js | 394 ++-- .../config/static/config/js/switcher.js | 80 +- .../config/static/config/js/tabs.js | 75 +- .../static/config/js/unsaved_changes.js | 210 +- .../config/static/config/js/utils.js | 176 +- .../config/static/config/js/vpn.js | 121 +- .../config/static/config/js/widget.js | 1890 +++++++++-------- .../static/sortedm2m/patch_sortedm2m.js | 25 +- .../static/connection/js/commands.js | 1034 ++++----- .../static/connection/js/credentials.js | 33 +- .../pki/static/admin/pki/js/show-org-field.js | 14 +- .../subnet-division/js/subnet-division.js | 92 +- requirements-test.txt | 2 +- 19 files changed, 2788 insertions(+), 2234 deletions(-) delete mode 100644 .jshintrc rename .jshintignore => .prettierignore (100%) delete mode 100644 .stylelintrc.json diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index bfd1e9492..000000000 --- a/.jshintrc +++ /dev/null @@ -1,19 +0,0 @@ -{ - "undef": false, - "unused": true, - "esversion": 6, - "curly": true, - "strict": "global", - "globals": { - "advancedJSONEditor": true, - "alert": true, - "django": true, - "gettext": true, - "JSONEditor": true, - "ReconnectingWebSocket": true, - "userLanguage": true, - "owControllerApiHost": true, - "owCommandApiEndpoint": true - }, - "browser": true -} diff --git a/.jshintignore b/.prettierignore similarity index 100% rename from .jshintignore rename to .prettierignore diff --git a/.stylelintrc.json b/.stylelintrc.json deleted file mode 100644 index cc6cead8b..000000000 --- a/.stylelintrc.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "rules": { - "block-no-empty": null, - "color-no-invalid-hex": true, - "comment-empty-line-before": [ - "always", - { - "ignore": ["stylelint-commands", "after-comment"] - } - ], - "rule-empty-line-before": [ - "never-multi-line", - { - "except": ["first-nested"], - "ignore": ["after-comment", "inside-block"] - } - ], - "unit-allowed-list": ["em", "rem", "%", "s", "px", "vh", "deg", "pt"] - } -} diff --git a/openwisp_controller/config/static/config/css/lib/advanced-mode.css b/openwisp_controller/config/static/config/css/lib/advanced-mode.css index 6c53b2c48..a31294f98 100644 --- a/openwisp_controller/config/static/config/css/lib/advanced-mode.css +++ b/openwisp_controller/config/static/config/css/lib/advanced-mode.css @@ -81,13 +81,13 @@ a.jsoneditor-value.jsoneditor-url:focus { vertical-align: top; color: gray; } -div.jsoneditor-field[contenteditable=true]:focus, -div.jsoneditor-field[contenteditable=true]:hover, -div.jsoneditor-value[contenteditable=true]:focus, -div.jsoneditor-value[contenteditable=true]:hover, +div.jsoneditor-field[contenteditable="true"]:focus, +div.jsoneditor-field[contenteditable="true"]:hover, +div.jsoneditor-value[contenteditable="true"]:focus, +div.jsoneditor-value[contenteditable="true"]:hover, div.jsoneditor-field.jsoneditor-highlight, div.jsoneditor-value.jsoneditor-highlight { - background-color: #FFFFAB; + background-color: #ffffab; border: 1px solid yellow; border-radius: 2px; } @@ -116,7 +116,7 @@ div.jsoneditor-value.jsoneditor-boolean { color: #ff8c00; } div.jsoneditor-value.jsoneditor-null { - color: #004ED0; + color: #004ed0; } div.jsoneditor-value.jsoneditor-invalid { color: #000000; @@ -153,7 +153,6 @@ div.jsoneditor-tree *:focus { outline: none; } div.jsoneditor-tree button:focus { - /* TODO: nice outline for buttons with focus outline: #97B0F8 solid 2px; box-shadow: 0 0 8px #97B0F8; @@ -166,7 +165,7 @@ div.jsoneditor-tree button.jsoneditor-invisible { background: none; } .field-config div.jsoneditor { - color: #1A1A1A; + color: #1a1a1a; border: 1px solid #3883fa; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; @@ -256,9 +255,15 @@ div.jsoneditor-value, .field-config div.jsoneditor th, .field-config div.jsoneditor textarea, .jsoneditor-schema-error { - font-family: droid sans mono, consolas, monospace, courier new, courier, sans-serif; + font-family: + droid sans mono, + consolas, + monospace, + courier new, + courier, + sans-serif; font-size: 10pt; - color: #1A1A1A; + color: #1a1a1a; } /* popover */ @@ -279,12 +284,12 @@ div.jsoneditor-tree .jsoneditor-schema-error { height: 24px; padding: 0; margin: 0 4px 0 0; - background: url("img/jsoneditor-icons.svg") -168px -48px; + background: url("img/jsoneditor-icons.svg") -168px -48px; } .jsoneditor-schema-error .jsoneditor-popover { background-color: #4c4c4c; border-radius: 3px; - box-shadow: 0 0 5px rgba(0,0,0,0.4); + box-shadow: 0 0 5px rgba(0, 0, 0, 0.4); color: #fff; display: none; padding: 7px 10px; @@ -311,7 +316,7 @@ div.jsoneditor-tree .jsoneditor-schema-error { .jsoneditor-schema-error .jsoneditor-popover:before { border-right: 7px solid transparent; border-left: 7px solid transparent; - content: ''; + content: ""; display: block; left: 50%; margin-left: -7px; @@ -329,7 +334,7 @@ div.jsoneditor-tree .jsoneditor-schema-error { border-left: 7px solid #4c4c4c; border-top: 7px solid transparent; border-bottom: 7px solid transparent; - content: ''; + content: ""; top: 19px; right: -14px; left: inherit; @@ -341,7 +346,7 @@ div.jsoneditor-tree .jsoneditor-schema-error { border-right: 7px solid #4c4c4c; border-top: 7px solid transparent; border-bottom: 7px solid transparent; - content: ''; + content: ""; top: 19px; left: -14px; margin-left: inherit; @@ -351,9 +356,15 @@ div.jsoneditor-tree .jsoneditor-schema-error { .jsoneditor-schema-error:hover .jsoneditor-popover, .jsoneditor-schema-error:focus .jsoneditor-popover { display: block; - -webkit-animation: fade-in .3s linear 1, move-up .3s linear 1; - -moz-animation: fade-in .3s linear 1, move-up .3s linear 1; - -ms-animation: fade-in .3s linear 1, move-up .3s linear 1; + -webkit-animation: + fade-in 0.3s linear 1, + move-up 0.3s linear 1; + -moz-animation: + fade-in 0.3s linear 1, + move-up 0.3s linear 1; + -ms-animation: + fade-in 0.3s linear 1, + move-up 0.3s linear 1; } @-webkit-keyframes fade-in { @@ -428,7 +439,7 @@ div.jsoneditor-tree .jsoneditor-schema-error { height: 24px; padding: 0; margin: 0 4px 0 0; - background: url("img/jsoneditor-icons.svg") -168px -48px; + background: url("img/jsoneditor-icons.svg") -168px -48px; } /* ContextMenu - main menu */ @@ -517,8 +528,16 @@ div.jsoneditor-contextmenu ul li button div.jsoneditor-expand { div.jsoneditor-contextmenu ul li button:hover div.jsoneditor-expand, div.jsoneditor-contextmenu ul li button:focus div.jsoneditor-expand, div.jsoneditor-contextmenu ul li.jsoneditor-selected div.jsoneditor-expand, -div.jsoneditor-contextmenu ul li button.jsoneditor-expand:hover div.jsoneditor-expand, -div.jsoneditor-contextmenu ul li button.jsoneditor-expand:focus div.jsoneditor-expand { +div.jsoneditor-contextmenu + ul + li + button.jsoneditor-expand:hover + div.jsoneditor-expand, +div.jsoneditor-contextmenu + ul + li + button.jsoneditor-expand:focus + div.jsoneditor-expand { opacity: 1; } div.jsoneditor-contextmenu div.jsoneditor-separator { @@ -531,42 +550,60 @@ div.jsoneditor-contextmenu button.jsoneditor-remove > div.jsoneditor-icon { background-position: -24px -24px; } div.jsoneditor-contextmenu button.jsoneditor-remove:hover > div.jsoneditor-icon, -div.jsoneditor-contextmenu button.jsoneditor-remove:focus > div.jsoneditor-icon { +div.jsoneditor-contextmenu + button.jsoneditor-remove:focus + > div.jsoneditor-icon { background-position: -24px 0; } div.jsoneditor-contextmenu button.jsoneditor-append > div.jsoneditor-icon { background-position: 0 -24px; } div.jsoneditor-contextmenu button.jsoneditor-append:hover > div.jsoneditor-icon, -div.jsoneditor-contextmenu button.jsoneditor-append:focus > div.jsoneditor-icon { +div.jsoneditor-contextmenu + button.jsoneditor-append:focus + > div.jsoneditor-icon { background-position: 0 0; } div.jsoneditor-contextmenu button.jsoneditor-insert > div.jsoneditor-icon { background-position: 0 -24px; } div.jsoneditor-contextmenu button.jsoneditor-insert:hover > div.jsoneditor-icon, -div.jsoneditor-contextmenu button.jsoneditor-insert:focus > div.jsoneditor-icon { +div.jsoneditor-contextmenu + button.jsoneditor-insert:focus + > div.jsoneditor-icon { background-position: 0 0; } div.jsoneditor-contextmenu button.jsoneditor-duplicate > div.jsoneditor-icon { background-position: -48px -24px; } -div.jsoneditor-contextmenu button.jsoneditor-duplicate:hover > div.jsoneditor-icon, -div.jsoneditor-contextmenu button.jsoneditor-duplicate:focus > div.jsoneditor-icon { +div.jsoneditor-contextmenu + button.jsoneditor-duplicate:hover + > div.jsoneditor-icon, +div.jsoneditor-contextmenu + button.jsoneditor-duplicate:focus + > div.jsoneditor-icon { background-position: -48px 0; } div.jsoneditor-contextmenu button.jsoneditor-sort-asc > div.jsoneditor-icon { background-position: -168px -24px; } -div.jsoneditor-contextmenu button.jsoneditor-sort-asc:hover > div.jsoneditor-icon, -div.jsoneditor-contextmenu button.jsoneditor-sort-asc:focus > div.jsoneditor-icon { +div.jsoneditor-contextmenu + button.jsoneditor-sort-asc:hover + > div.jsoneditor-icon, +div.jsoneditor-contextmenu + button.jsoneditor-sort-asc:focus + > div.jsoneditor-icon { background-position: -168px 0; } div.jsoneditor-contextmenu button.jsoneditor-sort-desc > div.jsoneditor-icon { background-position: -192px -24px; } -div.jsoneditor-contextmenu button.jsoneditor-sort-desc:hover > div.jsoneditor-icon, -div.jsoneditor-contextmenu button.jsoneditor-sort-desc:focus > div.jsoneditor-icon { +div.jsoneditor-contextmenu + button.jsoneditor-sort-desc:hover + > div.jsoneditor-icon, +div.jsoneditor-contextmenu + button.jsoneditor-sort-desc:focus + > div.jsoneditor-icon { background-position: -192px 0; } @@ -607,33 +644,57 @@ div.jsoneditor-contextmenu ul li ul li button:focus { div.jsoneditor-contextmenu button.jsoneditor-type-string > div.jsoneditor-icon { background-position: -144px -24px; } -div.jsoneditor-contextmenu button.jsoneditor-type-string:hover > div.jsoneditor-icon, -div.jsoneditor-contextmenu button.jsoneditor-type-string:focus > div.jsoneditor-icon, -div.jsoneditor-contextmenu button.jsoneditor-type-string.jsoneditor-selected > div.jsoneditor-icon { +div.jsoneditor-contextmenu + button.jsoneditor-type-string:hover + > div.jsoneditor-icon, +div.jsoneditor-contextmenu + button.jsoneditor-type-string:focus + > div.jsoneditor-icon, +div.jsoneditor-contextmenu + button.jsoneditor-type-string.jsoneditor-selected + > div.jsoneditor-icon { background-position: -144px 0; } div.jsoneditor-contextmenu button.jsoneditor-type-auto > div.jsoneditor-icon { background-position: -120px -24px; } -div.jsoneditor-contextmenu button.jsoneditor-type-auto:hover > div.jsoneditor-icon, -div.jsoneditor-contextmenu button.jsoneditor-type-auto:focus > div.jsoneditor-icon, -div.jsoneditor-contextmenu button.jsoneditor-type-auto.jsoneditor-selected > div.jsoneditor-icon { +div.jsoneditor-contextmenu + button.jsoneditor-type-auto:hover + > div.jsoneditor-icon, +div.jsoneditor-contextmenu + button.jsoneditor-type-auto:focus + > div.jsoneditor-icon, +div.jsoneditor-contextmenu + button.jsoneditor-type-auto.jsoneditor-selected + > div.jsoneditor-icon { background-position: -120px 0; } div.jsoneditor-contextmenu button.jsoneditor-type-object > div.jsoneditor-icon { background-position: -72px -24px; } -div.jsoneditor-contextmenu button.jsoneditor-type-object:hover > div.jsoneditor-icon, -div.jsoneditor-contextmenu button.jsoneditor-type-object:focus > div.jsoneditor-icon, -div.jsoneditor-contextmenu button.jsoneditor-type-object.jsoneditor-selected > div.jsoneditor-icon { +div.jsoneditor-contextmenu + button.jsoneditor-type-object:hover + > div.jsoneditor-icon, +div.jsoneditor-contextmenu + button.jsoneditor-type-object:focus + > div.jsoneditor-icon, +div.jsoneditor-contextmenu + button.jsoneditor-type-object.jsoneditor-selected + > div.jsoneditor-icon { background-position: -72px 0; } div.jsoneditor-contextmenu button.jsoneditor-type-array > div.jsoneditor-icon { background-position: -96px -24px; } -div.jsoneditor-contextmenu button.jsoneditor-type-array:hover > div.jsoneditor-icon, -div.jsoneditor-contextmenu button.jsoneditor-type-array:focus > div.jsoneditor-icon, -div.jsoneditor-contextmenu button.jsoneditor-type-array.jsoneditor-selected > div.jsoneditor-icon { +div.jsoneditor-contextmenu + button.jsoneditor-type-array:hover + > div.jsoneditor-icon, +div.jsoneditor-contextmenu + button.jsoneditor-type-array:focus + > div.jsoneditor-icon, +div.jsoneditor-contextmenu + button.jsoneditor-type-array.jsoneditor-selected + > div.jsoneditor-icon { background-position: -96px 0; } div.jsoneditor-contextmenu button.jsoneditor-type-modes > div.jsoneditor-icon { @@ -668,14 +729,14 @@ div.jsoneditor-menu > div.jsoneditor-modes > button { } div.jsoneditor-menu > button:hover, div.jsoneditor-menu > div.jsoneditor-modes > button:hover { - background-color: rgba(255,255,255,0.2); - border: 1px solid rgba(255,255,255,0.4); + background-color: rgba(255, 255, 255, 0.2); + border: 1px solid rgba(255, 255, 255, 0.4); } div.jsoneditor-menu > button:focus, div.jsoneditor-menu > button:active, div.jsoneditor-menu > div.jsoneditor-modes > button:focus, div.jsoneditor-menu > div.jsoneditor-modes > button:active { - background-color: rgba(255,255,255,0.3); + background-color: rgba(255, 255, 255, 0.3); } div.jsoneditor-menu > button:disabled, div.jsoneditor-menu > div.jsoneditor-modes > button:disabled { @@ -740,12 +801,14 @@ div.jsoneditor-menu a.jsoneditor-exit { div.jsoneditor-menu a.jsoneditor-exit:hover { background-color: rgba(255, 255, 255, 1); } -div.jsoneditor-menu a.jsoneditor-exit img{ margin: -2px 1px 0 0 } +div.jsoneditor-menu a.jsoneditor-exit img { + margin: -2px 1px 0 0; +} table.jsoneditor-search input, table.jsoneditor-search div.jsoneditor-results { font-family: arial, sans-serif; font-size: 10pt; - color: #1A1A1A; + color: #1a1a1a; background: transparent; /* For Firefox */ @@ -809,20 +872,30 @@ table.jsoneditor-search button.jsoneditor-previous { table.jsoneditor-search button.jsoneditor-previous:hover { background-position: -148px -49px; } -.field-config div.jsoneditor{ +.field-config div.jsoneditor { min-height: 300px !important; } -.jsoneditor-text-errors{ width: 100% !important } -.jsoneditor-text-errors td:first-child{ padding-left: 15px !important } -.jsoneditor-text-errors td:last-child{ padding-right: 15px !important } -.jsoneditor-text-errors td{ vertical-align: middle !important } +.jsoneditor-text-errors { + width: 100% !important; +} +.jsoneditor-text-errors td:first-child { + padding-left: 15px !important; +} +.jsoneditor-text-errors td:last-child { + padding-right: 15px !important; +} +.jsoneditor-text-errors td { + vertical-align: middle !important; +} .jsoneditor-text-errors td button { vertical-align: bottom !important; margin-top: 1px !important; } -.jsoneditor-text-errors td pre{ margin: 0 } -.full-screen{ +.jsoneditor-text-errors td pre { + margin: 0; +} +.full-screen { position: fixed; top: 0; left: 0; @@ -833,24 +906,24 @@ table.jsoneditor-search button.jsoneditor-previous:hover { background-color: #ffffff; z-index: 10000; } -.editor-full{ +.editor-full { overflow: hidden; } -.field-config .screen-mode{ +.field-config .screen-mode { float: right; } -.jsoneditor-menu label{ +.jsoneditor-menu label { color: #fff !important; line-height: 28px; padding: 0; margin-left: 20px; } -.jsoneditor-menu label a{ +.jsoneditor-menu label a { font-weight: bold; color: #fff !important; } .jsoneditor-menu #netjsonconfig-hint-advancedmode, -.jsoneditor-menu #netjsonconfig-hint-advancedmode a{ +.jsoneditor-menu #netjsonconfig-hint-advancedmode a { padding: 0; font-size: 14px !important; } @@ -860,8 +933,8 @@ table.jsoneditor-search button.jsoneditor-previous:hover { #main .property-selector { text-align: left; } -@media (max-width: 768px){ - .jsoneditor-menu #netjsonconfig-hint-advancedmode{ +@media (max-width: 768px) { + .jsoneditor-menu #netjsonconfig-hint-advancedmode { display: none; } } diff --git a/openwisp_controller/config/static/config/css/lib/jsonschema-ui.css b/openwisp_controller/config/static/config/css/lib/jsonschema-ui.css index 497c8d74e..8bf103178 100644 --- a/openwisp_controller/config/static/config/css/lib/jsonschema-ui.css +++ b/openwisp_controller/config/static/config/css/lib/jsonschema-ui.css @@ -1,122 +1,222 @@ -.jsoneditor-wrapper div.jsoneditor .grid-row > .inline-group{ margin-top: 15px } -.jsoneditor-wrapper div.jsoneditor .grid-row:first-child > .inline-group{ margin-top: 0 } -.form-row.field-config{ display: none } -.form-row.field-config .advanced-mode{ display: none } -.jsoneditor-wrapper div.jsoneditor-wrapper > fieldset{ +.jsoneditor-wrapper div.jsoneditor .grid-row > .inline-group { + margin-top: 15px; +} +.jsoneditor-wrapper div.jsoneditor .grid-row:first-child > .inline-group { + margin-top: 0; +} +.form-row.field-config { + display: none; +} +.form-row.field-config .advanced-mode { + display: none; +} +.jsoneditor-wrapper div.jsoneditor-wrapper > fieldset { margin-bottom: 0; border-bottom: 1px solid #eee; } -.jsoneditor-wrapper div.jsoneditor-wrapper h2{ padding: 18px } -input.deletelink{ background: #ba2121 } +.jsoneditor-wrapper div.jsoneditor-wrapper h2 { + padding: 18px; +} +input.deletelink { + background: #ba2121; +} input.deletelink:hover, -input.deletelink:focus{ background: #a41515 } +input.deletelink:focus { + background: #a41515; +} .jsoneditor-wrapper div.jsoneditor input.button, .jsoneditor-wrapper div.jsoneditor input.deletelink, -.form-row .json-editor-btn-edit{ +.form-row .json-editor-btn-edit { margin-left: 10px; margin-right: 5px; padding: 6px 12px; } -a.json-editor-btn-edit{ +a.json-editor-btn-edit { margin-left: 10px; padding: 6px 12px; color: #ffffff; } -.form-row .json-editor-btn-edit{ margin-bottom: 10px !important } -#main .jsoneditor-wrapper div.jsoneditor .form-row{ padding: 15px 15px 15px 0 } -.jsoneditor-wrapper div.jsoneditor label{ margin-left: 20px } -.jsoneditor-wrapper div.jsoneditor .form-row:last-child{ border-bottom-width: 0 } -.jsoneditor-wrapper div.jsoneditor .inline-group{ +.form-row .json-editor-btn-edit { + margin-bottom: 10px !important; +} +#main .jsoneditor-wrapper div.jsoneditor .form-row { + padding: 15px 15px 15px 0; +} +.jsoneditor-wrapper div.jsoneditor label { + margin-left: 20px; +} +.jsoneditor-wrapper div.jsoneditor .form-row:last-child { + border-bottom-width: 0; +} +.jsoneditor-wrapper div.jsoneditor .inline-group { clear: both; margin: 0; border: 1px solid #eee; } /* avoid redundant borders */ -.jsoneditor-wrapper div.jsoneditor .inline-group > .inline-related > .grid-container > div > .grid-row > .grid-column > div > .inline-group, -.jsoneditor-wrapper div.jsoneditor .inline-group > .inline-related > .grid-container > div > .inline-group{ +.jsoneditor-wrapper + div.jsoneditor + .inline-group + > .inline-related + > .grid-container + > div + > .grid-row + > .grid-column + > div + > .inline-group, +.jsoneditor-wrapper + div.jsoneditor + .inline-group + > .inline-related + > .grid-container + > div + > .inline-group { border: 0 none; } /* advanced mode and object properties */ -.jsoneditor-wrapper div.jsoneditor > div > h3.controls{ +.jsoneditor-wrapper div.jsoneditor > div > h3.controls { padding: 12px 0; margin: 0; } -.jsoneditor-wrapper div.jsoneditor > div > h3.controls .button{ margin: 0 13px } -.jsoneditor-wrapper div.jsoneditor div[data-schematype="array"] > .inline-related > div > .inline-related, -.jsoneditor-wrapper div.jsoneditor div[data-schematype="array"] > .inline-related > div > .inline-group{ +.jsoneditor-wrapper div.jsoneditor > div > h3.controls .button { + margin: 0 13px; +} +.jsoneditor-wrapper + div.jsoneditor + div[data-schematype="array"] + > .inline-related + > div + > .inline-related, +.jsoneditor-wrapper + div.jsoneditor + div[data-schematype="array"] + > .inline-related + > div + > .inline-group { margin: 20px; } -.jsoneditor-wrapper div.jsoneditor div[data-schematype="array"] > .inline-related > div > .inline-related:last-child, -.jsoneditor-wrapper div.jsoneditor div[data-schematype="array"] > .inline-related > div > .inline-group:last-child{ - margin-bottom: 0 +.jsoneditor-wrapper + div.jsoneditor + div[data-schematype="array"] + > .inline-related + > div + > .inline-related:last-child, +.jsoneditor-wrapper + div.jsoneditor + div[data-schematype="array"] + > .inline-related + > div + > .inline-group:last-child { + margin-bottom: 0; } -.jsoneditor-wrapper div.jsoneditor div[data-schematype="array"] > .inline-related > div > .inline-related[data-schematype="string"]{ +.jsoneditor-wrapper + div.jsoneditor + div[data-schematype="array"] + > .inline-related + > div + > .inline-related[data-schematype="string"] { margin: 0; } -.jsoneditor-wrapper div.jsoneditor div[data-schemapath="root.interfaces"] > div > div > div { +.jsoneditor-wrapper + div.jsoneditor + div[data-schemapath="root.interfaces"] + > div + > div + > div { border-bottom: 2px dotted #ccc; } -.jsoneditor-wrapper div.jsoneditor div.control{ margin: 15px 0 } -.jsoneditor-wrapper div.jsoneditor h3{ +.jsoneditor-wrapper div.jsoneditor div.control { + margin: 15px 0; +} +.jsoneditor-wrapper div.jsoneditor h3 { height: 56px; line-height: 55px; padding: 0 12px 0 15px; overflow: hidden; } -.jsoneditor-wrapper div.jsoneditor h3 span{ +.jsoneditor-wrapper div.jsoneditor h3 span { display: inline-block; margin-right: 15px; line-height: 0; } -.jsoneditor-wrapper div.jsoneditor h3 span.control{ +.jsoneditor-wrapper div.jsoneditor h3 span.control { margin-top: 12px; float: right; } -.jsoneditor-wrapper div.jsoneditor .grid-column > h3{ +.jsoneditor-wrapper div.jsoneditor .grid-column > h3 { border: 0 none; outline: 1px solid #eee; } -span.control input{ margin: 0 0 0 15px } -.jsoneditor-wrapper div.jsoneditor span.control{ +span.control input { + margin: 0 0 0 15px; +} +.jsoneditor-wrapper div.jsoneditor span.control { padding: 0; margin-right: 0; } -.jsoneditor-wrapper div.jsoneditor div[data-schemapath="root.interfaces"] > div > div > div { +.jsoneditor-wrapper + div.jsoneditor + div[data-schemapath="root.interfaces"] + > div + > div + > div { border-bottom: 2px dotted #ccc; padding-bottom: 20px; margin-bottom: 22px !important; } -.jsoneditor-wrapper div.jsoneditor div[data-schemapath="root.interfaces"] > div > div > div:last-child { +.jsoneditor-wrapper + div.jsoneditor + div[data-schemapath="root.interfaces"] + > div + > div + > div:last-child { margin-bottom: 0 !important; } -.jsoneditor-wrapper div.jsoneditor div[data-schemapath="root.interfaces"] > div > div > div > .control{ +.jsoneditor-wrapper + div.jsoneditor + div[data-schemapath="root.interfaces"] + > div + > div + > div + > .control { margin-bottom: 0; } -div[data-schematype="array"] > div > div > -div[data-schematype="string"].inline-related .control{ +div[data-schematype="array"] + > div + > div + > div[data-schematype="string"].inline-related + .control { margin: -49px 10px 0; float: right; clear: both; } -div[data-schematype="array"] > div > div > -div[data-schematype="string"].inline-related{ padding: 0 !important } +div[data-schematype="array"] + > div + > div + > div[data-schematype="string"].inline-related { + padding: 0 !important; +} /* hide empty divs */ -.jsoneditor-wrapper div.jsoneditor div:empty{ display: none !important } +.jsoneditor-wrapper div.jsoneditor div:empty { + display: none !important; +} /* begin custom properties adjustments */ -.jsoneditor-wrapper div.jsoneditor .inline-group{ margin-bottom: 0 } +.jsoneditor-wrapper div.jsoneditor .inline-group { + margin-bottom: 0; +} .jsoneditor-wrapper div.jsoneditor .inline-group > label, -.jsoneditor-wrapper div.jsoneditor .inline-group > select{ +.jsoneditor-wrapper div.jsoneditor .inline-group > select { vertical-align: top; margin: 15px 0 10px 15px; } -.jsoneditor-wrapper div.jsoneditor .inline-group > label{ +.jsoneditor-wrapper div.jsoneditor .inline-group > label { font-style: italic; display: inline-block; clear: both; @@ -124,48 +224,133 @@ div[data-schematype="string"].inline-related{ padding: 0 !important } margin-left: 15px; margin-bottom: 0; } -.inline-group[data-schematype="object"] > .inline-related > .grid-container > -div > .grid-row > .inline-group > div > .inline-group{ +.inline-group[data-schematype="object"] + > .inline-related + > .grid-container + > div + > .grid-row + > .inline-group + > div + > .inline-group { border: 0 none !important; } -div[data-schematype="object"] > .inline-related > .grid-container > -div > .grid-row > .inline-group { +div[data-schematype="object"] + > .inline-related + > .grid-container + > div + > .grid-row + > .inline-group { border-bottom: 1px solid #eee; } -.grid-row > .inline-group > div > .inline-group > div.form-row{ +.grid-row > .inline-group > div > .inline-group > div.form-row { border: 0 none; padding: 0; margin: 0 0 15px 15px; } -div[data-schematype="object"] > .inline-related > .grid-container > div > -.grid-row > .inline-group > div > div{ +div[data-schematype="object"] + > .inline-related + > .grid-container + > div + > .grid-row + > .inline-group + > div + > div { border: 0 none; } -.jsoneditor-wrapper div.jsoneditor > div > .inline-related > .grid-container > div > .grid-row > -.inline-group > div { +.jsoneditor-wrapper + div.jsoneditor + > div + > .inline-related + > .grid-container + > div + > .grid-row + > .inline-group + > div { display: block; } -.jsoneditor-wrapper div.jsoneditor .grid-row > .inline-group > div > .inline-group > h3 span{ display: inline-block !important } -.jsoneditor-wrapper div.jsoneditor .grid-row > .inline-group > div > .inline-group > .inline-related > div > .inline-related > label, -.jsoneditor-wrapper div.jsoneditor .grid-row > .inline-group > div > .inline-group > .inline-related > div > .inline-related > select{ +.jsoneditor-wrapper + div.jsoneditor + .grid-row + > .inline-group + > div + > .inline-group + > h3 + span { + display: inline-block !important; +} +.jsoneditor-wrapper + div.jsoneditor + .grid-row + > .inline-group + > div + > .inline-group + > .inline-related + > div + > .inline-related + > label, +.jsoneditor-wrapper + div.jsoneditor + .grid-row + > .inline-group + > div + > .inline-group + > .inline-related + > div + > .inline-related + > select { position: static; margin-bottom: 15px; margin-left: 0; } -.jsoneditor-wrapper div.jsoneditor .grid-row > .inline-group > div > .inline-group > .inline-related > div > .inline-related -.grid-row > .inline-group{ +.jsoneditor-wrapper + div.jsoneditor + .grid-row + > .inline-group + > div + > .inline-group + > .inline-related + > div + > .inline-related + .grid-row + > .inline-group { border-top: 0 none; border-left: 0 none; border-right: 0 none; } -.jsoneditor-wrapper div.jsoneditor .grid-row > .inline-group > div > .inline-group > .inline-related > div > .inline-related -.inline-group[data-schematype="array"] .inline-related > div > .inline-related > div > .inline-group .form-row{ +.jsoneditor-wrapper + div.jsoneditor + .grid-row + > .inline-group + > div + > .inline-group + > .inline-related + > div + > .inline-related + .inline-group[data-schematype="array"] + .inline-related + > div + > .inline-related + > div + > .inline-group + .form-row { margin-left: 15px; border: 0 none; } -.jsoneditor-wrapper div.jsoneditor .grid-row > .inline-group > div > .inline-group > .inline-related > div > .inline-related -.inline-group[data-schematype="array"] .inline-related > div > .inline-related .deletelink{ +.jsoneditor-wrapper + div.jsoneditor + .grid-row + > .inline-group + > div + > .inline-group + > .inline-related + > div + > .inline-related + .inline-group[data-schematype="array"] + .inline-related + > div + > .inline-related + .deletelink { margin-left: 0; } @@ -175,7 +360,7 @@ div[data-schematype="object"] > .inline-related > .grid-container > div > .jsoneditor-wrapper div.jsoneditor .inline-related > label, .jsoneditor-wrapper div.jsoneditor .inline-related > select, .jsoneditor-wrapper div.jsoneditor .grid-column > label, -.jsoneditor-wrapper div.jsoneditor .grid-column > select{ +.jsoneditor-wrapper div.jsoneditor .grid-column > select { position: absolute; left: 0; top: 12px; @@ -183,15 +368,17 @@ div[data-schematype="object"] > .inline-related > .grid-container > div > font-weight: bold; } .jsoneditor-wrapper div.jsoneditor .inline-related > select, -.jsoneditor-wrapper div.jsoneditor .grid-column > select{ +.jsoneditor-wrapper div.jsoneditor .grid-column > select { margin-left: 188px; background-color: #fff; } -.jsoneditor-wrapper div.jsoneditor .grid-row .grid-column{ position: relative } -.jsoneditor-wrapper .grid-column[data-schemapath="root.files"]{ +.jsoneditor-wrapper div.jsoneditor .grid-row .grid-column { + position: relative; +} +.jsoneditor-wrapper .grid-column[data-schemapath="root.files"] { margin-top: 15px; } -.jsoneditor-wrapper div.jsoneditor .modal{ +.jsoneditor-wrapper div.jsoneditor .modal { position: absolute; z-index: 10; background-color: white; @@ -202,79 +389,101 @@ div[data-schematype="object"] > .inline-related > .grid-container > div > } /* header */ -.jsoneditor-wrapper div.jsoneditor .advanced-mode{ margin-top: -4px !important } -.normal-mode{ float: right } -.jsoneditor-wrapper div.jsoneditor > div > h3.controls{ text-align: right } -.jsoneditor-wrapper div.jsoneditor > div > h3.controls > .control{ float: left } +.jsoneditor-wrapper div.jsoneditor .advanced-mode { + margin-top: -4px !important; +} +.normal-mode { + float: right; +} +.jsoneditor-wrapper div.jsoneditor > div > h3.controls { + text-align: right; +} +.jsoneditor-wrapper div.jsoneditor > div > h3.controls > .control { + float: left; +} /* configuration menu modal */ -.jsoneditor-wrapper div.jsoneditor > div > h3.controls > .control > .modal{ +.jsoneditor-wrapper div.jsoneditor > div > h3.controls > .control > .modal { margin-left: 0; width: auto; } -.jsoneditor-wrapper div.jsoneditor .modal .vTextField[type=text], -.jsoneditor-wrapper div.jsoneditor .modal .button{ +.jsoneditor-wrapper div.jsoneditor .modal .vTextField[type="text"], +.jsoneditor-wrapper div.jsoneditor .modal .button { margin-top: 12px !important; - margin-bottom: 3px !important + margin-bottom: 3px !important; } -.jsoneditor-wrapper div.jsoneditor input[type=checkbox]{ margin-right: 7px } -.jsoneditor-wrapper div.jsoneditor textarea{ +.jsoneditor-wrapper div.jsoneditor input[type="checkbox"] { + margin-right: 7px; +} +.jsoneditor-wrapper div.jsoneditor textarea { min-width: 75%; - min-height: 330px + min-height: 330px; } -.jsoneditor-wrapper div.jsoneditor .modal textarea{ +.jsoneditor-wrapper div.jsoneditor .modal textarea { margin-bottom: 10px; min-width: 450px; } -.jsoneditor-wrapper div.jsoneditor .modal label{ margin-left: 5px } -.jsoneditor-wrapper div.jsoneditor .property-selector{ +.jsoneditor-wrapper div.jsoneditor .modal label { + margin-left: 5px; +} +.jsoneditor-wrapper div.jsoneditor .property-selector { max-height: 240px !important; width: auto !important; } -.jsoneditor-wrapper div.jsoneditor .property-selector{ +.jsoneditor-wrapper div.jsoneditor .property-selector { padding: 10px !important; } -.jsoneditor-wrapper div.jsoneditor .property-selector input{ - margin: 0 8px 0 0 +.jsoneditor-wrapper div.jsoneditor .property-selector input { + margin: 0 8px 0 0; } -.jsoneditor-wrapper div.jsoneditor .property-selector .form-row{ - padding: 2px 10px !important +.jsoneditor-wrapper div.jsoneditor .property-selector .form-row { + padding: 2px 10px !important; } -.jsoneditor-wrapper div.jsoneditor .errorlist{ +.jsoneditor-wrapper div.jsoneditor .errorlist { margin-top: 3px; margin-left: 181px; } -#content .jsoneditor-wrapper div.jsoneditor div.form-row > .help{ +#content .jsoneditor-wrapper div.jsoneditor div.form-row > .help { margin-left: 181px; margin-top: 4px; color: #888; } #content .jsoneditor-wrapper div.jsoneditor .inline-group > .help, -#content .jsoneditor-wrapper div.jsoneditor .grid-column > .help{ +#content .jsoneditor-wrapper div.jsoneditor .grid-column > .help { padding: 20px 15px 20px 18px; margin: 0; border-bottom: 1px solid #eee; } -#content .jsoneditor-wrapper div.jsoneditor .modal label{ width: auto } -#content .jsoneditor-wrapper div.jsoneditor div.grid-column[data-schematype="boolean"] label{ +#content .jsoneditor-wrapper div.jsoneditor .modal label { + width: auto; +} +#content + .jsoneditor-wrapper + div.jsoneditor + div.grid-column[data-schematype="boolean"] + label { float: left; } -#content .jsoneditor-wrapper div.jsoneditor div.grid-column[data-schematype="boolean"] .help{ +#content + .jsoneditor-wrapper + div.jsoneditor + div.grid-column[data-schematype="boolean"] + .help { float: left; clear: none; margin: 0; } #id_config_jsoneditor, -#id_config-0-config_jsoneditor{ +#id_config-0-config_jsoneditor { border: 0px !important; height: auto !important; } /* support django admin inline */ -div.jsoneditor > div > h3.controls{ +div.jsoneditor > div > h3.controls { background: transparent !important; } #main div.jsoneditor .inline-related h3 { @@ -286,17 +495,31 @@ div.jsoneditor > div > h3.controls{ padding-bottom: 0; } -#config-0.inline-related{ position: static } +#config-0.inline-related { + position: static; +} .jsoneditor-wrapper div[data-schemaid="root"] > label:first-of-type, .jsoneditor-wrapper div[data-schemaid="root"] > select:first-of-type { position: relative; bottom: -52px; z-index: 99; } -.jsoneditor-wrapper div.jsoneditor .inline-related > .grid-container > div > .grid-row > .grid-column > select.switcher{ +.jsoneditor-wrapper + div.jsoneditor + .inline-related + > .grid-container + > div + > .grid-row + > .grid-column + > select.switcher { top: 15px; } -.jsoneditor-wrapper div.jsoneditor .grid-container .grid-row > .grid-column[data-schematype="object"] > select.switcher{ +.jsoneditor-wrapper + div.jsoneditor + .grid-container + .grid-row + > .grid-column[data-schematype="object"] + > select.switcher { top: 11px !important; } .jsoneditor-wrapper div.jsoneditor .grid-row > .grid-column > label { @@ -305,17 +528,31 @@ div.jsoneditor > div > h3.controls{ .jsoneditor-wrapper div.jsoneditor .inline-group > .form-row > label { display: block !important; } -.jsoneditor-wrapper div.jsoneditor .grid-column > div > .inline-group > .form-row > .vTextField { - padding-left: 100px !important -} -.jsoneditor-wrapper div.jsoneditor .grid-column > div > .inline-group[data-schemapath="root.vxlan.0.vni"] > .form-row > .vTextField { +.jsoneditor-wrapper + div.jsoneditor + .grid-column + > div + > .inline-group + > .form-row + > .vTextField { + padding-left: 100px !important; +} +.jsoneditor-wrapper + div.jsoneditor + .grid-column + > div + > .inline-group[data-schemapath="root.vxlan.0.vni"] + > .form-row + > .vTextField { padding: 8px 12px !important; margin-top: 45px; margin-left: -2px; } -.jsoneditor-wrapper div[data-schemapath^="root.radios"][data-schemapath$=".hwmode"] { +.jsoneditor-wrapper + div[data-schemapath^="root.radios"][data-schemapath$=".hwmode"] { display: none; } -.jsoneditor-wrapper div[data-schemapath^="root.radios"][data-schemapath$=".band"] { +.jsoneditor-wrapper + div[data-schemapath^="root.radios"][data-schemapath$=".band"] { display: none; } diff --git a/openwisp_controller/config/static/config/js/preview.js b/openwisp_controller/config/static/config/js/preview.js index 07a2371ac..7a814fbdf 100644 --- a/openwisp_controller/config/static/config/js/preview.js +++ b/openwisp_controller/config/static/config/js/preview.js @@ -1,99 +1,112 @@ "use strict"; django.jQuery(function ($) { - var overlay = $('.djnjc-overlay'), - html = $('html'), - inner = overlay.find('.inner'), - preview_url = $('.previewlink').attr('data-url'); - var openPreview = function () { - var selectors = 'input[type=text], input[type=hidden], select, textarea', - fields = $(selectors, '#content-main form').not('#id_config_jsoneditor *'), - $id = $('#id_uuid'), - data = {}, - loadingOverlay = $('#loading-overlay'); - loadingOverlay.show(); - // add id to POST data - // note: may be overridden by fields of OneToOne relation - if ($id.length) { data.id = $id.val(); } - // gather data to send in POST - fields.each(function (i, field) { - var $field = $(field), - name = $field.attr('name'); - // skip management fields - if (!name || - name.indexOf('initial-') === 0 || - name.indexOf('config-__') === 0 || - name.indexOf('_FORMS') != -1) { return; } - // rename fields of OneToOne relation - if (name.indexOf('config-0-') === 0) { - name = name.replace('config-0-', ''); - } - // Avoid sending null which would raise a validation error - if ($field.attr('name') === 'organization' && $field.val() === 'null') { - $field.val(''); - } - data[name] = $field.val(); - }); - // show preview - $.post(preview_url, data, function (htmlContent) { - inner.html($('#content-main div', htmlContent).html()); - overlay.show(); - html.css('overflow', 'hidden'); - overlay.find('pre').trigger('click'); - // close preview - overlay.find('.close').click(function (e) { - e.preventDefault(); - closePreview(); - }); - loadingOverlay.fadeOut(250); - }) - .fail(function (xhr) { - // if validation error, show it on page - if (xhr.status == 400) { - alert('There was an issue while generating the preview \n' + - 'Details: ' + xhr.responseText); - } - // 500 internal server error - // rare case, leaving it untranslated for simplicity - else { - var message = 'Error while generating preview'; - if (gettext) { message = gettext(message); } - alert(message + ':\n\n' + xhr.responseText); - } - closePreview(); - }); - }; - var closePreview = function () { - overlay.hide(); - inner.html(''); - html.attr('style', ''); - }; - $('.previewlink').click(function (e) { - var configUi = $('#id_config_jsoneditor, #id_config-0-config_jsoneditor'), - message; - e.preventDefault(); - // show preview only if there's a configuration - // (device items may not have one) - if (configUi.length) { - openPreview(); - } - else { - message = 'No configuration available'; - if (gettext) { message = gettext(message); } - alert(message); - } + var overlay = $(".djnjc-overlay"), + html = $("html"), + inner = overlay.find(".inner"), + preview_url = $(".previewlink").attr("data-url"); + var openPreview = function () { + var selectors = "input[type=text], input[type=hidden], select, textarea", + fields = $(selectors, "#content-main form").not( + "#id_config_jsoneditor *", + ), + $id = $("#id_uuid"), + data = {}, + loadingOverlay = $("#loading-overlay"); + loadingOverlay.show(); + // add id to POST data + // note: may be overridden by fields of OneToOne relation + if ($id.length) { + data.id = $id.val(); + } + // gather data to send in POST + fields.each(function (i, field) { + var $field = $(field), + name = $field.attr("name"); + // skip management fields + if ( + !name || + name.indexOf("initial-") === 0 || + name.indexOf("config-__") === 0 || + name.indexOf("_FORMS") != -1 + ) { + return; + } + // rename fields of OneToOne relation + if (name.indexOf("config-0-") === 0) { + name = name.replace("config-0-", ""); + } + // Avoid sending null which would raise a validation error + if ($field.attr("name") === "organization" && $field.val() === "null") { + $field.val(""); + } + data[name] = $field.val(); }); - $(document).keyup(function (e) { - // ALT+P - if (e.altKey && e.which == 80) { - // unfocus any active input before proceeding - $(document.activeElement).trigger('blur'); - // wait for JSON editor to update the - // corresonding raw value before proceding - setTimeout(openPreview, 15); - } - // ESC - else if (!e.ctrlKey && e.which == 27) { - closePreview(); + // show preview + $.post(preview_url, data, function (htmlContent) { + inner.html($("#content-main div", htmlContent).html()); + overlay.show(); + html.css("overflow", "hidden"); + overlay.find("pre").trigger("click"); + // close preview + overlay.find(".close").click(function (e) { + e.preventDefault(); + closePreview(); + }); + loadingOverlay.fadeOut(250); + }).fail(function (xhr) { + // if validation error, show it on page + if (xhr.status == 400) { + alert( + "There was an issue while generating the preview \n" + + "Details: " + + xhr.responseText, + ); + } + // 500 internal server error + // rare case, leaving it untranslated for simplicity + else { + var message = "Error while generating preview"; + if (gettext) { + message = gettext(message); } + alert(message + ":\n\n" + xhr.responseText); + } + closePreview(); }); + }; + var closePreview = function () { + overlay.hide(); + inner.html(""); + html.attr("style", ""); + }; + $(".previewlink").click(function (e) { + var configUi = $("#id_config_jsoneditor, #id_config-0-config_jsoneditor"), + message; + e.preventDefault(); + // show preview only if there's a configuration + // (device items may not have one) + if (configUi.length) { + openPreview(); + } else { + message = "No configuration available"; + if (gettext) { + message = gettext(message); + } + alert(message); + } + }); + $(document).keyup(function (e) { + // ALT+P + if (e.altKey && e.which == 80) { + // unfocus any active input before proceeding + $(document.activeElement).trigger("blur"); + // wait for JSON editor to update the + // corresonding raw value before proceding + setTimeout(openPreview, 15); + } + // ESC + else if (!e.ctrlKey && e.which == 27) { + closePreview(); + } + }); }); diff --git a/openwisp_controller/config/static/config/js/relevant_templates.js b/openwisp_controller/config/static/config/js/relevant_templates.js index 3e458efd2..9684d409c 100644 --- a/openwisp_controller/config/static/config/js/relevant_templates.js +++ b/openwisp_controller/config/static/config/js/relevant_templates.js @@ -1,181 +1,231 @@ -'use strict'; +"use strict"; django.jQuery(function ($) { - var firstRun = true, - backendFieldSelector = '#id_config-0-backend', - orgFieldSelector = '#id_organization', - isDeviceGroup = function () { - return window._deviceGroup; - }, - templatesFieldName = function () { - return isDeviceGroup() ? 'templates' : 'config-0-templates'; - }, - getTemplateOptionElement = function (index, templateId, templateConfig, isSelected = false, isPrefix = false) { - if (templateConfig===undefined) { - return; // relevant templates do not contain this template - } - var prefix = isPrefix ? '__prefix__-' : '', - requiredString = templateConfig.required ? ' (required)' : '', - backendString = isDeviceGroup() && templateConfig.backend ? ` (backend: ${templateConfig.backend})` : '', - element = $(`
  • `), - inputField = element.children().children('input'); + var firstRun = true, + backendFieldSelector = "#id_config-0-backend", + orgFieldSelector = "#id_organization", + isDeviceGroup = function () { + return window._deviceGroup; + }, + templatesFieldName = function () { + return isDeviceGroup() ? "templates" : "config-0-templates"; + }, + getTemplateOptionElement = function ( + index, + templateId, + templateConfig, + isSelected = false, + isPrefix = false, + ) { + if (templateConfig === undefined) { + return; // relevant templates do not contain this template + } + var prefix = isPrefix ? "__prefix__-" : "", + requiredString = templateConfig.required ? " (required)" : "", + backendString = + isDeviceGroup() && templateConfig.backend + ? ` (backend: ${templateConfig.backend})` + : "", + element = $( + `
  • `, + ), + inputField = element.children().children("input"); - if (templateConfig.required) { - inputField.prop('disabled', true); - } - if (isSelected || templateConfig.required) { - inputField.prop('checked', true); - } - return element; - }, - resetTemplateOptions = function () { - $('ul.sortedm2m-items').empty(); - }, - updateTemplateSelection = function (selectedTemplates) { - // Marks currently applied templates from database as selected - // Only executed at page load. - selectedTemplates.forEach(function (templateId) { - $(`li.sortedm2m-item input[type="checkbox"][value="${templateId}"]:first`).prop('checked', true); - }); - }, - updateTemplateHelpText = function () { - var helpText = 'Choose items and order by drag & drop.'; - if ($('li.sortedm2m-item:first').length === 0) { - helpText = 'No Template available'; - } - if (gettext) { - helpText = gettext(helpText); - } - $('.sortedm2m-container > .help').text(helpText); - }, - addChangeEventHandlerToBackendField = function () { - $(backendFieldSelector).change(function () { - setTimeout(function () { - // ensures getDefaultTemplates execute only after other - // onChange event handlers attached this field has been - // executed. - showRelevantTemplates(); - }); - }); - // Initially request data to populate everything - showRelevantTemplates(); - }, - updateConfigTemplateField = function (templates) { - $(`input[name="${templatesFieldName()}"]`).attr( - 'value', templates.join(',') - ); - $('input.sortedm2m:first').trigger('change'); - }, - parseSelectedTemplates = function (selectedTemplates) { - if (selectedTemplates !== undefined) { - if (selectedTemplates === '') { - return []; - } else { - return selectedTemplates.split(','); - } - } - }, - showRelevantTemplates = function () { - var orgID = $(orgFieldSelector).val(), - backend = isDeviceGroup() ? "" : $(backendFieldSelector).val(), - selectedTemplates; + if (templateConfig.required) { + inputField.prop("disabled", true); + } + if (isSelected || templateConfig.required) { + inputField.prop("checked", true); + } + return element; + }, + resetTemplateOptions = function () { + $("ul.sortedm2m-items").empty(); + }, + updateTemplateSelection = function (selectedTemplates) { + // Marks currently applied templates from database as selected + // Only executed at page load. + selectedTemplates.forEach(function (templateId) { + $( + `li.sortedm2m-item input[type="checkbox"][value="${templateId}"]:first`, + ).prop("checked", true); + }); + }, + updateTemplateHelpText = function () { + var helpText = "Choose items and order by drag & drop."; + if ($("li.sortedm2m-item:first").length === 0) { + helpText = "No Template available"; + } + if (gettext) { + helpText = gettext(helpText); + } + $(".sortedm2m-container > .help").text(helpText); + }, + addChangeEventHandlerToBackendField = function () { + $(backendFieldSelector).change(function () { + setTimeout(function () { + // ensures getDefaultTemplates execute only after other + // onChange event handlers attached this field has been + // executed. + showRelevantTemplates(); + }); + }); + // Initially request data to populate everything + showRelevantTemplates(); + }, + updateConfigTemplateField = function (templates) { + $(`input[name="${templatesFieldName()}"]`).attr( + "value", + templates.join(","), + ); + $("input.sortedm2m:first").trigger("change"); + }, + parseSelectedTemplates = function (selectedTemplates) { + if (selectedTemplates !== undefined) { + if (selectedTemplates === "") { + return []; + } else { + return selectedTemplates.split(","); + } + } + }, + showRelevantTemplates = function () { + var orgID = $(orgFieldSelector).val(), + backend = isDeviceGroup() ? "" : $(backendFieldSelector).val(), + selectedTemplates; - // Hide templates if no organization or backend is selected - if (orgID.length === 0 || !isDeviceGroup() && backend.length === 0) { - resetTemplateOptions(); - updateTemplateHelpText(); - return; - } - - if (firstRun) { - // selectedTemplates will be undefined on device add page or - // when the user has changed any of organization or backend field. - // selectedTemplates will be an empty string if no template is selected - // ''.split(',') returns [''] hence, this case requires special handling - selectedTemplates = isDeviceGroup() ? parseSelectedTemplates($("#id_templates").val()) : parseSelectedTemplates(django._owcInitialValues[templatesFieldName()]); - } + // Hide templates if no organization or backend is selected + if (orgID.length === 0 || (!isDeviceGroup() && backend.length === 0)) { + resetTemplateOptions(); + updateTemplateHelpText(); + return; + } - var url = window._relevantTemplateUrl.replace('org_id', orgID); - // Get relevant templates of selected org and backend - url = url + '?backend=' + backend; - $.get(url).done(function (data) { - resetTemplateOptions(); - var enabledTemplates = [], - sortedm2mUl = $('ul.sortedm2m-items:first'), - sortedm2mPrefixUl = $('ul.sortedm2m-items:last'); + if (firstRun) { + // selectedTemplates will be undefined on device add page or + // when the user has changed any of organization or backend field. + // selectedTemplates will be an empty string if no template is selected + // ''.split(',') returns [''] hence, this case requires special handling + selectedTemplates = isDeviceGroup() + ? parseSelectedTemplates($("#id_templates").val()) + : parseSelectedTemplates( + django._owcInitialValues[templatesFieldName()], + ); + } - // Adds "li" elements for templates that are already selected - // in the database. Select these templates and remove their key from "data" - // This maintains the order of the templates and keep - // enabled templates on the top - if (selectedTemplates !== undefined) { - selectedTemplates.forEach(function (templateId, index) { - // corner case in which backend of template does not match - if (!data[templateId]) { return; } - var element = getTemplateOptionElement(index, templateId, data[templateId], true, false), - prefixElement = getTemplateOptionElement(index, templateId, data[templateId], true, true); - sortedm2mUl.append(element); - if (!isDeviceGroup()) { - sortedm2mPrefixUl.append(prefixElement); - } - delete data[templateId]; - }); - } + var url = window._relevantTemplateUrl.replace("org_id", orgID); + // Get relevant templates of selected org and backend + url = url + "?backend=" + backend; + $.get(url).done(function (data) { + resetTemplateOptions(); + var enabledTemplates = [], + sortedm2mUl = $("ul.sortedm2m-items:first"), + sortedm2mPrefixUl = $("ul.sortedm2m-items:last"); - // Adds "li" elements for templates that are not selected - // in the database. - var counter = selectedTemplates !== undefined ? selectedTemplates.length : 0; - Object.keys(data).forEach(function (templateId, index) { - // corner case in which backend of template does not match - if (!data[templateId]) { return; } - index = index + counter; - var isSelected = (data[templateId].default && (selectedTemplates === undefined)) && (!data[templateId].required), - element = getTemplateOptionElement(index, templateId, data[templateId], isSelected), - prefixElement = getTemplateOptionElement(index, templateId, data[templateId], isSelected, true); - // Default templates should only be enabled for new - // device or when user has changed any of organization - // or backend field - if (isSelected === true) { - enabledTemplates.push(templateId); - } - sortedm2mUl.append(element); - if (!isDeviceGroup()) { - sortedm2mPrefixUl.append(prefixElement); - } - }); - if (firstRun === true && selectedTemplates !== undefined) { - updateTemplateSelection(selectedTemplates); - } - updateTemplateHelpText(); - updateConfigTemplateField(enabledTemplates); - }); - }, - bindDefaultTemplateLoading = function () { - var backendField = $(backendFieldSelector); - $(orgFieldSelector).change(function () { - // Only fetch templates when backend field is present - if ($(backendFieldSelector).length > 0 || isDeviceGroup()) { - showRelevantTemplates(); - } - }); - // Change view: backendField is rendered on page load - if (backendField.length > 0) { - addChangeEventHandlerToBackendField(); - } else if (isDeviceGroup()) { - // Initially request data to get templates - showRelevantTemplates(); + // Adds "li" elements for templates that are already selected + // in the database. Select these templates and remove their key from "data" + // This maintains the order of the templates and keep + // enabled templates on the top + if (selectedTemplates !== undefined) { + selectedTemplates.forEach(function (templateId, index) { + // corner case in which backend of template does not match + if (!data[templateId]) { + return; } - else { - // Add view: backendField is added when user adds configuration - $('#config-group > fieldset.module').ready(function () { - $('div.add-row > a').one('click', function () { - addChangeEventHandlerToBackendField(); - }); - }); + var element = getTemplateOptionElement( + index, + templateId, + data[templateId], + true, + false, + ), + prefixElement = getTemplateOptionElement( + index, + templateId, + data[templateId], + true, + true, + ); + sortedm2mUl.append(element); + if (!isDeviceGroup()) { + sortedm2mPrefixUl.append(prefixElement); } - firstRun = false; - $('#content-main form').submit(function () { - $('ul.sortedm2m-items:first input[type="checkbox"][data-required="true"]').prop('checked', false); - }); - }; - window.bindDefaultTemplateLoading = bindDefaultTemplateLoading; + delete data[templateId]; + }); + } + + // Adds "li" elements for templates that are not selected + // in the database. + var counter = + selectedTemplates !== undefined ? selectedTemplates.length : 0; + Object.keys(data).forEach(function (templateId, index) { + // corner case in which backend of template does not match + if (!data[templateId]) { + return; + } + index = index + counter; + var isSelected = + data[templateId].default && + selectedTemplates === undefined && + !data[templateId].required, + element = getTemplateOptionElement( + index, + templateId, + data[templateId], + isSelected, + ), + prefixElement = getTemplateOptionElement( + index, + templateId, + data[templateId], + isSelected, + true, + ); + // Default templates should only be enabled for new + // device or when user has changed any of organization + // or backend field + if (isSelected === true) { + enabledTemplates.push(templateId); + } + sortedm2mUl.append(element); + if (!isDeviceGroup()) { + sortedm2mPrefixUl.append(prefixElement); + } + }); + if (firstRun === true && selectedTemplates !== undefined) { + updateTemplateSelection(selectedTemplates); + } + updateTemplateHelpText(); + updateConfigTemplateField(enabledTemplates); + }); + }, + bindDefaultTemplateLoading = function () { + var backendField = $(backendFieldSelector); + $(orgFieldSelector).change(function () { + // Only fetch templates when backend field is present + if ($(backendFieldSelector).length > 0 || isDeviceGroup()) { + showRelevantTemplates(); + } + }); + // Change view: backendField is rendered on page load + if (backendField.length > 0) { + addChangeEventHandlerToBackendField(); + } else if (isDeviceGroup()) { + // Initially request data to get templates + showRelevantTemplates(); + } else { + // Add view: backendField is added when user adds configuration + $("#config-group > fieldset.module").ready(function () { + $("div.add-row > a").one("click", function () { + addChangeEventHandlerToBackendField(); + }); + }); + } + firstRun = false; + $("#content-main form").submit(function () { + $( + 'ul.sortedm2m-items:first input[type="checkbox"][data-required="true"]', + ).prop("checked", false); + }); + }; + window.bindDefaultTemplateLoading = bindDefaultTemplateLoading; }); diff --git a/openwisp_controller/config/static/config/js/switcher.js b/openwisp_controller/config/static/config/js/switcher.js index b46e7aedf..3a6b733f4 100644 --- a/openwisp_controller/config/static/config/js/switcher.js +++ b/openwisp_controller/config/static/config/js/switcher.js @@ -1,33 +1,51 @@ -'use strict'; +"use strict"; django.jQuery(function ($) { - var type_select = $('#id_type'), - vpn_specific = $('.field-vpn, .field-auto_cert'), - gettext = window.gettext || function (v) { return v; }, - toggle_vpn_specific = function (changed) { - if (type_select.val() == 'vpn') { - vpn_specific.show(); - if (changed === true && $('.autovpn').length < 1 && $('#id_config').val() === '{}') { - var p1 = gettext('Click on Save to automatically generate the ' + - 'VPN client configuration (will be based on ' + - 'the configuration of the server).'), - p2 = gettext('You can then tweak the VPN client ' + - 'configuration in the next step.'); - $('.jsoneditor-wrapper').hide() - .after('
    '); - $('.autovpn').html('

    ' + p1 + '

    ' + - '

    ' + p2 + '

    '); - } - } - else { - vpn_specific.hide(); - if ($('.autovpn').length > 0) { - $('.jsoneditor-wrapper').show(); - $('.autovpn').hide(); - } - } - }; - type_select.on('change', function () { - toggle_vpn_specific(true); - }); - toggle_vpn_specific(); + var type_select = $("#id_type"), + vpn_specific = $(".field-vpn, .field-auto_cert"), + gettext = + window.gettext || + function (v) { + return v; + }, + toggle_vpn_specific = function (changed) { + if (type_select.val() == "vpn") { + vpn_specific.show(); + if ( + changed === true && + $(".autovpn").length < 1 && + $("#id_config").val() === "{}" + ) { + var p1 = gettext( + "Click on Save to automatically generate the " + + "VPN client configuration (will be based on " + + "the configuration of the server).", + ), + p2 = gettext( + "You can then tweak the VPN client " + + "configuration in the next step.", + ); + $(".jsoneditor-wrapper") + .hide() + .after('
    '); + $(".autovpn").html( + "

    " + + p1 + + "

    " + + "

    " + + p2 + + "

    ", + ); + } + } else { + vpn_specific.hide(); + if ($(".autovpn").length > 0) { + $(".jsoneditor-wrapper").show(); + $(".autovpn").hide(); + } + } + }; + type_select.on("change", function () { + toggle_vpn_specific(true); + }); + toggle_vpn_specific(); }); diff --git a/openwisp_controller/config/static/config/js/tabs.js b/openwisp_controller/config/static/config/js/tabs.js index 4dd381429..bc5f44945 100644 --- a/openwisp_controller/config/static/config/js/tabs.js +++ b/openwisp_controller/config/static/config/js/tabs.js @@ -1,61 +1,68 @@ -'use strict'; +"use strict"; django.jQuery(function ($) { - - if ($('.add-form').length || !$('#device_form').length) { + if ($(".add-form").length || !$("#device_form").length) { return; } // trigger window resize event // workaround that fixes problems with leafelet maps var triggerResize = function () { - var resizeEvent = window.document.createEvent('UIEvents'); - resizeEvent.initUIEvent('resize', true, false, window, 0); - window.dispatchEvent(resizeEvent); - }, + var resizeEvent = window.document.createEvent("UIEvents"); + resizeEvent.initUIEvent("resize", true, false, window, 0); + window.dispatchEvent(resizeEvent); + }, showTab = function (menuLink) { - var tabId = menuLink.attr('href'); - $('ul.tabs a').removeClass('current'); - $('.tab-content').removeClass('current'); - menuLink.addClass('current'); - $(tabId).addClass('current'); + var tabId = menuLink.attr("href"); + $("ul.tabs a").removeClass("current"); + $(".tab-content").removeClass("current"); + menuLink.addClass("current"); + $(tabId).addClass("current"); triggerResize(); $.event.trigger({ - type: 'tabshown', + type: "tabshown", tabId: tabId, }); return tabId; }, showFragment = function (fragment) { - if (!fragment) { return; } + if (!fragment) { + return; + } showTab($('ul.tabs a[href="' + fragment + '"]')); }; - $('ul.tabs a').click(function (e) { + $("ul.tabs a").click(function (e) { var tabId = showTab($(this)); e.preventDefault(); - history.pushState(tabId, '', tabId); + history.pushState(tabId, "", tabId); }); - var overview = $('#device_form > div > fieldset.module.aligned') - .addClass('tab-content') - .attr('id', 'overview-group'), - tabs = $('#device_form > div > div.inline-group') - .addClass('tab-content'), - tabsContainer = $('#tabs-container ul'); + var overview = $("#device_form > div > fieldset.module.aligned") + .addClass("tab-content") + .attr("id", "overview-group"), + tabs = $("#device_form > div > div.inline-group").addClass("tab-content"), + tabsContainer = $("#tabs-container ul"); tabs.each(function (i, el) { var $el = $(el), - tabId = $el.attr('id'), - label = $el.find('> fieldset.module > h2, ' + - '> .tabular > fieldset.module > h2').text(); + tabId = $el.attr("id"), + label = $el + .find("> fieldset.module > h2, " + "> .tabular > fieldset.module > h2") + .text(); tabsContainer.append( - '
  • ' + label + '
  • ' + '
  • ' + + label + + "
  • ", ); }); - $('.tabs-loading').hide(); + $(".tabs-loading").hide(); // open fragment - $(window).on('hashchange', function () { + $(window).on("hashchange", function () { showFragment(window.location.hash); }); @@ -63,18 +70,18 @@ django.jQuery(function ($) { if (window.location.hash) { showFragment(window.location.hash); } else { - $('ul.tabs li:first-child a').addClass('current'); - overview.addClass('current'); + $("ul.tabs li:first-child a").addClass("current"); + overview.addClass("current"); } // if there's any validation error, show the first one - var errors = $('.errorlist'); + var errors = $(".errorlist"); if (errors.length) { - var erroredTab = errors.eq(0).parents('.tab-content'); + var erroredTab = errors.eq(0).parents(".tab-content"); if (erroredTab.length) { - window.location.hash = '#' + erroredTab.attr('id'); + window.location.hash = "#" + erroredTab.attr("id"); } } - $('#loading-overlay').fadeOut(400); + $("#loading-overlay").fadeOut(400); }); diff --git a/openwisp_controller/config/static/config/js/unsaved_changes.js b/openwisp_controller/config/static/config/js/unsaved_changes.js index 5bee77ead..8bc0af521 100644 --- a/openwisp_controller/config/static/config/js/unsaved_changes.js +++ b/openwisp_controller/config/static/config/js/unsaved_changes.js @@ -1,106 +1,124 @@ -'use strict'; +"use strict"; (function ($) { - var form = '#content-main form', - mapValues = function (object) { - $('input, select, textarea', form).each(function (i, el) { - var field = $(el), - name = field.attr('name'), - value = field.val(), - jsonValues = ['config', 'config-0-config', 'config-0-context', 'devicelocation-0-geometry']; - // ignore fields that have no name attribute, begin with "_" or "initial-" - if (!name || name.substr(0, 1) == '_' || name.substr(0, 8) == 'initial-' || - // ignore hidden fields - name == 'csrfmiddlewaretoken' || - // ignore hidden inline helper fields - name.indexOf('__prefix__') >= 0 || - name.indexOf('root') === 0) { - return; - } - // fix checkbox values inconsistency - if (field.attr('type') == 'checkbox') { - object[name] = field.is(':checked'); - } - else { - object[name] = value; - } - // convert JSON string to Javascript object in order - // to perform object comparison with `objectIsEqual` - if (jsonValues.indexOf(name) > -1) { - try { - object[name] = JSON.parse(value); - } - catch (ignore) { } - } - }); - $(document).trigger('owcInitialValuesLoaded'); - }; - - var unsavedChanges = function (e) { - // get current values - var currentValues = {}; - mapValues(currentValues); - var changed = false, - message = 'You haven\'t saved your changes yet!', - initialValue, - name; - // django initial values returns organization as 'null' when it is empty - if (currentValues.organization === '') { - currentValues.organization = 'null'; + var form = "#content-main form", + mapValues = function (object) { + $("input, select, textarea", form).each(function (i, el) { + var field = $(el), + name = field.attr("name"), + value = field.val(), + jsonValues = [ + "config", + "config-0-config", + "config-0-context", + "devicelocation-0-geometry", + ]; + // ignore fields that have no name attribute, begin with "_" or "initial-" + if ( + !name || + name.substr(0, 1) == "_" || + name.substr(0, 8) == "initial-" || + // ignore hidden fields + name == "csrfmiddlewaretoken" || + // ignore hidden inline helper fields + name.indexOf("__prefix__") >= 0 || + name.indexOf("root") === 0 + ) { + return; } - if (gettext) { message = gettext(message); } // i18n if enabled - // compare initial with current values - for (name in django._owcInitialValues) { - initialValue = django._owcInitialValues[name]; - - if (!objectIsEqual(initialValue, currentValues[name])) { - changed = true; - break; - } + // fix checkbox values inconsistency + if (field.attr("type") == "checkbox") { + object[name] = field.is(":checked"); + } else { + object[name] = value; } - if (changed) { - e.returnValue = message; - return message; + // convert JSON string to Javascript object in order + // to perform object comparison with `objectIsEqual` + if (jsonValues.indexOf(name) > -1) { + try { + object[name] = JSON.parse(value); + } catch (ignore) {} } + }); + $(document).trigger("owcInitialValuesLoaded"); }; - // compares equality of two objects - var objectIsEqual = function (obj1, obj2) { - if (typeof obj1 != 'object' && typeof obj2 != 'object') { - return obj1 == obj2; - } + var unsavedChanges = function (e) { + // get current values + var currentValues = {}; + mapValues(currentValues); + var changed = false, + message = "You haven't saved your changes yet!", + initialValue, + name; + // django initial values returns organization as 'null' when it is empty + if (currentValues.organization === "") { + currentValues.organization = "null"; + } + if (gettext) { + message = gettext(message); + } // i18n if enabled + // compare initial with current values + for (name in django._owcInitialValues) { + initialValue = django._owcInitialValues[name]; + + if (!objectIsEqual(initialValue, currentValues[name])) { + changed = true; + break; + } + } + if (changed) { + e.returnValue = message; + return message; + } + }; + + // compares equality of two objects + var objectIsEqual = function (obj1, obj2) { + if (typeof obj1 != "object" && typeof obj2 != "object") { + return obj1 == obj2; + } - // jslint doesn't like comparing typeof with a non-constant - // see https://stackoverflow.com/a/18526510 - var obj1Type = typeof obj1, - obj2Type = typeof obj2; - if (obj1Type != obj2Type) { + // jslint doesn't like comparing typeof with a non-constant + // see https://stackoverflow.com/a/18526510 + var obj1Type = typeof obj1, + obj2Type = typeof obj2; + if (obj1Type != obj2Type) { + return false; + } + var p; + for (p in obj1) { + switch (typeof obj1[p]) { + case "object": + if (!objectIsEqual(obj1[p], obj2[p])) { return false; - } - var p; - for (p in obj1) { - switch (typeof obj1[p]) { - case 'object': - if (!objectIsEqual(obj1[p], obj2[p])) { return false; } break; - default: - if (obj1[p] != obj2[p]) { return false; } - } - } - for (p in obj2) { - if (obj1[p] === undefined) { return false; } - } - return true; - }; + } + break; + default: + if (obj1[p] != obj2[p]) { + return false; + } + } + } + for (p in obj2) { + if (obj1[p] === undefined) { + return false; + } + } + return true; + }; - $(function ($) { - if (!$('.submit-row').length) { return; } - // populate initial map of form values - django._owcInitialValues = {}; - mapValues(django._owcInitialValues); - // do not perform unsavedChanges if submitting form - $(form).submit(function () { - $(window).unbind('beforeunload', unsavedChanges); - }); - // bind unload event - $(window).bind('beforeunload', unsavedChanges); + $(function ($) { + if (!$(".submit-row").length) { + return; + } + // populate initial map of form values + django._owcInitialValues = {}; + mapValues(django._owcInitialValues); + // do not perform unsavedChanges if submitting form + $(form).submit(function () { + $(window).unbind("beforeunload", unsavedChanges); }); -}(django.jQuery)); + // bind unload event + $(window).bind("beforeunload", unsavedChanges); + }); +})(django.jQuery); diff --git a/openwisp_controller/config/static/config/js/utils.js b/openwisp_controller/config/static/config/js/utils.js index 051235781..fd898cebe 100644 --- a/openwisp_controller/config/static/config/js/utils.js +++ b/openwisp_controller/config/static/config/js/utils.js @@ -1,111 +1,115 @@ -'use strict'; +"use strict"; var cleanedData, - pattern = /^\{\{\s*(\w*)\s*\}\}$/g, - getContext, - evaluateVars, - cleanData, - getAllContext, - isContextValid, - span = document.createElement('span'); + pattern = /^\{\{\s*(\w*)\s*\}\}$/g, + getContext, + evaluateVars, + cleanData, + getAllContext, + isContextValid, + span = document.createElement("span"); -span.setAttribute('style', 'color:red'); -span.setAttribute('id', 'context-error'); +span.setAttribute("style", "color:red"); +span.setAttribute("id", "context-error"); getContext = function () { - var contextDiv = document.querySelectorAll('.field-context, .field-default_values')[0]; - if (contextDiv && !contextDiv.querySelector('span')) { - contextDiv.appendChild(span); - } - return document.querySelectorAll('#id_config-0-context, #id_default_values')[0]; + var contextDiv = document.querySelectorAll( + ".field-context, .field-default_values", + )[0]; + if (contextDiv && !contextDiv.querySelector("span")) { + contextDiv.appendChild(span); + } + return document.querySelectorAll( + "#id_config-0-context, #id_default_values", + )[0]; }; // check default_values is valid isContextValid = function () { - var json = getContext(); - if (!json) { return true; } // VPN server - try { - JSON.parse(json.value); - } catch (e) { - span.innerHTML = 'Invalid JSON: ' + e.message; - return false; - } - span.innerHTML = ''; + var json = getContext(); + if (!json) { return true; + } // VPN server + try { + JSON.parse(json.value); + } catch (e) { + span.innerHTML = "Invalid JSON: " + e.message; + return false; + } + span.innerHTML = ""; + return true; }; evaluateVars = function (data, context) { - if (typeof data === 'object') { - Object.keys(data).forEach(function (key) { - data[key] = evaluateVars(data[key], context); - }); - } - if (typeof data === 'string') { - var found_vars = data.match(pattern); - if (found_vars !== null) { - found_vars.forEach(function (element) { - element = element.replace(/^\{\{\s+|\s+\}\}$|^\{\{|\}\}$/g, ''); - if (context.hasOwnProperty(element)) { - data = data.replace(pattern, context[element]); - } - }); + if (typeof data === "object") { + Object.keys(data).forEach(function (key) { + data[key] = evaluateVars(data[key], context); + }); + } + if (typeof data === "string") { + var found_vars = data.match(pattern); + if (found_vars !== null) { + found_vars.forEach(function (element) { + element = element.replace(/^\{\{\s+|\s+\}\}$|^\{\{|\}\}$/g, ""); + if (context.hasOwnProperty(element)) { + data = data.replace(pattern, context[element]); } + }); } - return data; + } + return data; }; getAllContext = function () { - var userContextField = getContext(), - systemContextField = document.getElementById('system_context'), - value; - if (userContextField) { - var defaultValues = JSON.parse(userContextField.value), - systemContext = JSON.parse(systemContextField.textContent); - value = Object.assign( - {}, - defaultValues, - systemContext - ); - } - return value; + var userContextField = getContext(), + systemContextField = document.getElementById("system_context"), + value; + if (userContextField) { + var defaultValues = JSON.parse(userContextField.value), + systemContext = JSON.parse(systemContextField.textContent); + value = Object.assign({}, defaultValues, systemContext); + } + return value; }; cleanData = function (data) { - var json = getAllContext(); - if (json && data && isContextValid()) { - cleanedData = evaluateVars(data, json); - return cleanedData; - } else { - return data; - } + var json = getAllContext(); + if (json && data && isContextValid()) { + cleanedData = evaluateVars(data, json); + return cleanedData; + } else { + return data; + } }; (function ($) { - $(document).ready(function($){ - var systemContext = $('#system-context'); - var systemContextBtn = $('.system-context'); - var btnText; - function setSystemContextHeight() { - // Hides System Defined Variables when - // its height is > 182px - if (systemContext.height() > 182) { - systemContext.addClass('hide-sc'); - systemContextBtn.addClass('show-sc'); - } - } - systemContextBtn.on('click', function (event) { - event.preventDefault(); - systemContext.toggleClass('hide-sc'); - btnText = "Hide"; - if (systemContext.hasClass('hide-sc')) { - btnText = "Show"; - } - if (gettext) { btnText = gettext(btnText); } - systemContextBtn.text(btnText); - }); - $(window).on('resize', function () { - setSystemContextHeight(); - }); - setSystemContextHeight(); + $(document).ready(function ($) { + var systemContext = $("#system-context"); + var systemContextBtn = $(".system-context"); + var btnText; + function setSystemContextHeight() { + // Hides System Defined Variables when + // its height is > 182px + if (systemContext.height() > 182) { + systemContext.addClass("hide-sc"); + systemContextBtn.addClass("show-sc"); + } + } + systemContextBtn.on("click", function (event) { + event.preventDefault(); + systemContext.toggleClass("hide-sc"); + btnText = "Hide"; + if (systemContext.hasClass("hide-sc")) { + btnText = "Show"; + } + if (gettext) { + btnText = gettext(btnText); + } + systemContextBtn.text(btnText); + }); + $(window).on("resize", function () { + setSystemContextHeight(); }); -}(django.jQuery)); + setSystemContextHeight(); + }); +})(django.jQuery); diff --git a/openwisp_controller/config/static/config/js/vpn.js b/openwisp_controller/config/static/config/js/vpn.js index 2fbb5aca3..68f368184 100644 --- a/openwisp_controller/config/static/config/js/vpn.js +++ b/openwisp_controller/config/static/config/js/vpn.js @@ -1,68 +1,71 @@ -'use strict'; +"use strict"; django.jQuery(function ($) { - if ($('.add-form').length) { - var showOverlay = function () { - var loading = $('#loading-overlay'); - if (!loading.length) { - $('body').append( - '
    ' - ); - loading = $('#loading-overlay'); - } - loading.fadeIn(100, function () { - loading.css('display', 'flex'); - var spinner = loading.find('.spinner'); - spinner.fadeOut(100, function () { - var message = gettext( - 'Please be patient, we are creating all the necessary ' + - 'cyrptographic keys which may take some time' - ); - spinner.remove(); - loading.append('

    '); - loading.find('p').hide().text(message).fadeIn(250); - }); - }); - }; - - $('#vpn_form').submit(function () { - showOverlay(); + if ($(".add-form").length) { + var showOverlay = function () { + var loading = $("#loading-overlay"); + if (!loading.length) { + $("body").append( + '

    ', + ); + loading = $("#loading-overlay"); + } + loading.fadeIn(100, function () { + loading.css("display", "flex"); + var spinner = loading.find(".spinner"); + spinner.fadeOut(100, function () { + var message = gettext( + "Please be patient, we are creating all the necessary " + + "cyrptographic keys which may take some time", + ); + spinner.remove(); + loading.append("

    "); + loading.find("p").hide().text(message).fadeIn(250); }); - } - - var getParentRow = function (el) { - return el.parents('.form-row').eq(0); + }); }; - var toggleRelatedFields = function () { - // Show IP and Subnet field only for WireGuard backend - var backendValue = $('#id_backend').val() === undefined ? '' : $('#id_backend').val().toLocaleLowerCase().toLocaleLowerCase(), - op; - if (backendValue.includes('wireguard') || backendValue.includes('vxlan')) { - op = 'show'; - } else { - op = 'hide'; - } - getParentRow($('label[for="id_webhook_endpoint"]'))[op](); - getParentRow($('label[for="id_auth_token"]'))[op](); + $("#vpn_form").submit(function () { + showOverlay(); + }); + } - if (backendValue.includes('openvpn')) { - op = 'show'; - } else { - op = 'hide'; - } - getParentRow($('label[for="id_ca"]'))[op](); - getParentRow($('label[for="id_cert"]'))[op](); - // For Zerotier VPN backend - if(backendValue.includes('zerotier')){ - getParentRow($('label[for="id_auth_token"]')).show(); - } - }; + var getParentRow = function (el) { + return el.parents(".form-row").eq(0); + }; - // clean config when VPN backend is changed - $('#id_backend').change(function () { - $('#id_config').val('{}'); - toggleRelatedFields(); - }); + var toggleRelatedFields = function () { + // Show IP and Subnet field only for WireGuard backend + var backendValue = + $("#id_backend").val() === undefined + ? "" + : $("#id_backend").val().toLocaleLowerCase().toLocaleLowerCase(), + op; + if (backendValue.includes("wireguard") || backendValue.includes("vxlan")) { + op = "show"; + } else { + op = "hide"; + } + getParentRow($('label[for="id_webhook_endpoint"]'))[op](); + getParentRow($('label[for="id_auth_token"]'))[op](); + if (backendValue.includes("openvpn")) { + op = "show"; + } else { + op = "hide"; + } + getParentRow($('label[for="id_ca"]'))[op](); + getParentRow($('label[for="id_cert"]'))[op](); + // For Zerotier VPN backend + if (backendValue.includes("zerotier")) { + getParentRow($('label[for="id_auth_token"]')).show(); + } + }; + + // clean config when VPN backend is changed + $("#id_backend").change(function () { + $("#id_config").val("{}"); toggleRelatedFields(); + }); + + toggleRelatedFields(); }); diff --git a/openwisp_controller/config/static/config/js/widget.js b/openwisp_controller/config/static/config/js/widget.js index 65a92e3bd..9bcbe457a 100644 --- a/openwisp_controller/config/static/config/js/widget.js +++ b/openwisp_controller/config/static/config/js/widget.js @@ -1,885 +1,975 @@ -'use strict'; +"use strict"; (function ($) { - django._schemas = new Map(); - django._jsonEditors = new Map(); - var inFullScreenMode = false, - prevDefaultValues = {}, - defaultValuesUrl = window.location.origin + '/admin/config/device/get-default-values/', - removeDefaultValues = function(contextValue, defaultValues) { - // remove default values when template is removed. - Object.keys(prevDefaultValues).forEach(function (key) { - if (!defaultValues.hasOwnProperty(key) && contextValue.hasOwnProperty(key)) { - delete contextValue[key]; - } - }); - return contextValue; + django._schemas = new Map(); + django._jsonEditors = new Map(); + var inFullScreenMode = false, + prevDefaultValues = {}, + defaultValuesUrl = + window.location.origin + "/admin/config/device/get-default-values/", + removeDefaultValues = function (contextValue, defaultValues) { + // remove default values when template is removed. + Object.keys(prevDefaultValues).forEach(function (key) { + if ( + !defaultValues.hasOwnProperty(key) && + contextValue.hasOwnProperty(key) + ) { + delete contextValue[key]; + } + }); + return contextValue; }, - removeUnchangedDefaultValues = function(contextValue) { - // This method is called on the submit event to remove any template default - // value which was not customized which allows to avoid saving redundant data - Object.keys(prevDefaultValues).forEach(function (key) { - if (prevDefaultValues[key] == contextValue[key]) { - delete contextValue[key]; - } - }); - return contextValue; + removeUnchangedDefaultValues = function (contextValue) { + // This method is called on the submit event to remove any template default + // value which was not customized which allows to avoid saving redundant data + Object.keys(prevDefaultValues).forEach(function (key) { + if (prevDefaultValues[key] == contextValue[key]) { + delete contextValue[key]; + } + }); + return contextValue; }, - updateContext = function (isLoading, defaultValues={}) { - var contextField = $('#id_config-0-context'), - systemContextField = $('#system_context'); - if (contextField.length && systemContextField.length) { - var contextValue = JSON.parse(contextField.val()), - systemContextValue = JSON.parse(systemContextField.text()); - // add default values to contextValue - Object.keys(defaultValues).forEach(function (key) { - if ( - // Handles the case when different templates and group contains the keys. - // If the contextValue was set by a template or group, then - // override the value. - (prevDefaultValues.hasOwnProperty(key) && prevDefaultValues[key] !== defaultValues[key]) || - // Gives precedence to device's context (saved in database) - (!contextValue.hasOwnProperty(key) && - // Gives precedence to systemContextValue. - // But if the default value is equal to the system context value, - // then add the variable in the contextValue. This allows users - // to override the value. - (!systemContextValue.hasOwnProperty(key) || systemContextValue[key] === defaultValues[key]) - ) - ) { - contextValue[key] = defaultValues[key]; - } - }); - - if (isLoading && django._owcInitialValues) { - django._owcInitialValues['config-0-context'] = removeDefaultValues( - contextValue, - defaultValues - ); - } + updateContext = function (isLoading, defaultValues = {}) { + var contextField = $("#id_config-0-context"), + systemContextField = $("#system_context"); + if (contextField.length && systemContextField.length) { + var contextValue = JSON.parse(contextField.val()), + systemContextValue = JSON.parse(systemContextField.text()); + // add default values to contextValue + Object.keys(defaultValues).forEach(function (key) { + if ( + // Handles the case when different templates and group contains the keys. + // If the contextValue was set by a template or group, then + // override the value. + (prevDefaultValues.hasOwnProperty(key) && + prevDefaultValues[key] !== defaultValues[key]) || + // Gives precedence to device's context (saved in database) + (!contextValue.hasOwnProperty(key) && + // Gives precedence to systemContextValue. + // But if the default value is equal to the system context value, + // then add the variable in the contextValue. This allows users + // to override the value. + (!systemContextValue.hasOwnProperty(key) || + systemContextValue[key] === defaultValues[key])) + ) { + contextValue[key] = defaultValues[key]; + } + }); - contextField.val(JSON.stringify( - removeDefaultValues(contextValue, defaultValues), - null, - 4 - )); - prevDefaultValues = JSON.parse(JSON.stringify(defaultValues)); - $('.flat-json-toggle-textarea').trigger('click'); - $('.flat-json-toggle-textarea').trigger('click'); + if (isLoading && django._owcInitialValues) { + django._owcInitialValues["config-0-context"] = removeDefaultValues( + contextValue, + defaultValues, + ); } + + contextField.val( + JSON.stringify( + removeDefaultValues(contextValue, defaultValues), + null, + 4, + ), + ); + prevDefaultValues = JSON.parse(JSON.stringify(defaultValues)); + $(".flat-json-toggle-textarea").trigger("click"); + $(".flat-json-toggle-textarea").trigger("click"); + } }, - getDefaultValues = function (isLoading=false) { - var templatePks = $('input[name="config-0-templates"]').attr('value'), - groupPk = $('#id_group').val(), - orgPk = $('#id_organization').val(); - if (templatePks) { - var payload = {pks: templatePks}; - if (groupPk) { - payload.group = groupPk; - } - if (orgPk) { - payload.organization = orgPk; - } - $.get(defaultValuesUrl, payload) - .done( function (data) { - updateContext(isLoading, data.default_values); - }) - .fail(function (data) { - window.console.error(data.responseText); - }); - } else { - // remove existing default values if no template is selected - updateContext(isLoading, {}); + getDefaultValues = function (isLoading = false) { + var templatePks = $('input[name="config-0-templates"]').attr("value"), + groupPk = $("#id_group").val(), + orgPk = $("#id_organization").val(); + if (templatePks) { + var payload = { pks: templatePks }; + if (groupPk) { + payload.group = groupPk; + } + if (orgPk) { + payload.organization = orgPk; } + $.get(defaultValuesUrl, payload) + .done(function (data) { + updateContext(isLoading, data.default_values); + }) + .fail(function (data) { + window.console.error(data.responseText); + }); + } else { + // remove existing default values if no template is selected + updateContext(isLoading, {}); + } }, toggleFullScreen = function () { - var advanced = $('.advanced_editor:visible'); - if (!inFullScreenMode) { - advanced.addClass('full-screen'); - $('html').addClass('editor-full'); - inFullScreenMode = true; - advanced.find('.jsoneditor-menu a').show(); - advanced.find('.jsoneditor-menu label').show(); - } - else { - advanced.removeClass('full-screen'); - $('html').removeClass('editor-full'); - inFullScreenMode = false; - advanced.find('.jsoneditor-menu a').hide(); - advanced.find('.jsoneditor-menu label').hide(); - } + var advanced = $(".advanced_editor:visible"); + if (!inFullScreenMode) { + advanced.addClass("full-screen"); + $("html").addClass("editor-full"); + inFullScreenMode = true; + advanced.find(".jsoneditor-menu a").show(); + advanced.find(".jsoneditor-menu label").show(); + } else { + advanced.removeClass("full-screen"); + $("html").removeClass("editor-full"); + inFullScreenMode = false; + advanced.find(".jsoneditor-menu a").hide(); + advanced.find(".jsoneditor-menu label").hide(); + } }; - var initAdvancedEditor = function (target, data, schema, disableSchema) { - var advanced = $(target).prev('.advanced_editor'); - if (advanced.length === 0){ - advanced = $('

    '); - $(advanced).insertBefore($(target)); - } else { - advanced.empty(); - } - $(target).hide(); - // if disableSchema is true, do not validate againsts schema, default is false - schema = disableSchema ? {} : schema; - var options = { - mode: 'code', - theme: 'ace/theme/tomorrow_night_bright', - indentation: 4, - onEditable: function () { - return true; - }, - onChange: function () { - $(target).val(editor.getText()); - }, - schema: schema - }; - - var editor = new advancedJSONEditor(advanced.get(0), options, data); - editor.aceEditor.setOptions({ - fontSize: 14, - showInvisibles: true - }); - // remove powered by ace link - advanced.find('.jsoneditor-menu a').remove(); - // add listener to .screen-mode button for toggleScreenMode - advanced.parents('.field-config').find('.screen-mode').click(toggleFullScreen); - // add controls to the editor header - advanced.find('.jsoneditor-menu') - .append($(` back to normal mode`)) - .append(advanced.parents('.field-config').find('#netjsonconfig-hint') - .clone(true) - .attr('id', 'netjsonconfig-hint-advancedmode')); - // hide on esc button - $('html').on('keydown', function (e) { - if (inFullScreenMode && e.keyCode === 27) { // ESC - $('.advanced_editor:visible').find('.jsoneditor-exit').click(); - } - }); - return editor; + var initAdvancedEditor = function (target, data, schema, disableSchema) { + var advanced = $(target).prev(".advanced_editor"); + if (advanced.length === 0) { + advanced = $('
    '); + $(advanced).insertBefore($(target)); + } else { + advanced.empty(); + } + $(target).hide(); + // if disableSchema is true, do not validate againsts schema, default is false + schema = disableSchema ? {} : schema; + var options = { + mode: "code", + theme: "ace/theme/tomorrow_night_bright", + indentation: 4, + onEditable: function () { + return true; + }, + onChange: function () { + $(target).val(editor.getText()); + }, + schema: schema, }; - // returns true if JSON is well formed - // and valid according to its schema - var isValidJson = function (advanced) { - var valid, - cleanedData; - try { - cleanedData = window.cleanData(advanced.get()); - valid = advanced.validateSchema(cleanedData); - } catch (e) { - valid = false; - } - return valid; - }; + var editor = new advancedJSONEditor(advanced.get(0), options, data); + editor.aceEditor.setOptions({ + fontSize: 14, + showInvisibles: true, + }); + // remove powered by ace link + advanced.find(".jsoneditor-menu a").remove(); + // add listener to .screen-mode button for toggleScreenMode + advanced + .parents(".field-config") + .find(".screen-mode") + .click(toggleFullScreen); + // add controls to the editor header + advanced + .find(".jsoneditor-menu") + .append( + $( + ` back to normal mode`, + ), + ) + .append( + advanced + .parents(".field-config") + .find("#netjsonconfig-hint") + .clone(true) + .attr("id", "netjsonconfig-hint-advancedmode"), + ); + // hide on esc button + $("html").on("keydown", function (e) { + if (inFullScreenMode && e.keyCode === 27) { + // ESC + $(".advanced_editor:visible").find(".jsoneditor-exit").click(); + } + }); + return editor; + }; - var alertInvalidJson = function () { - alert("The JSON entered is not valid"); - }; + // returns true if JSON is well formed + // and valid according to its schema + var isValidJson = function (advanced) { + var valid, cleanedData; + try { + cleanedData = window.cleanData(advanced.get()); + valid = advanced.validateSchema(cleanedData); + } catch (e) { + valid = false; + } + return valid; + }; - var getEditorErrors = function (editor) { - var value = JSON.parse(JSON.stringify(editor.getValue())); - var cleanedData = window.cleanData(value), - error = editor.validate(cleanedData); - return error; - }; + var alertInvalidJson = function () { + alert("The JSON entered is not valid"); + }; - var handleMaxLengthAttr = function() { - $('.jsoneditor input[maxlength]:not(.has-max-length)').map((i, field) => { - $(field).attr('data-maxlength', $(field).attr('maxLength')); - }); - $('.jsoneditor input[maxlength]:not(.has-max-length)').addClass('has-max-length'); - }; + var getEditorErrors = function (editor) { + var value = JSON.parse(JSON.stringify(editor.getValue())); + var cleanedData = window.cleanData(value), + error = editor.validate(cleanedData); + return error; + }; - var validateOnDefaultValuesChange = function (editor, advancedEditor) { - window.isContextValid(); - if (inFullScreenMode) { - advancedEditor.validate(); - } else { - editor.onChange(); - } + var handleMaxLengthAttr = function () { + $(".jsoneditor input[maxlength]:not(.has-max-length)").map((i, field) => { + $(field).attr("data-maxlength", $(field).attr("maxLength")); + }); + $(".jsoneditor input[maxlength]:not(.has-max-length)").addClass( + "has-max-length", + ); + }; + + var validateOnDefaultValuesChange = function (editor, advancedEditor) { + window.isContextValid(); + if (inFullScreenMode) { + advancedEditor.validate(); + } else { + editor.onChange(); + } + }; + + var loadUi = function (el, backend, schemas, setInitialValue) { + var field = $(el), + form = field.parents("form").eq(0), + value = JSON.parse(field.val()), + id = field.attr("id") + "_jsoneditor", + initialField = $("#initial-" + field.attr("id")), + container = field.parents(".form-row").eq(0), + labelText = container.find("label:not(#netjsonconfig-hint)").text(), + startval = $.isEmptyObject(value) ? null : value, + editorContainer = $("#" + id), + html, + editor, + options, + wrapper, + header, + getEditorValue, + updateRaw, + advancedEditor, + $advancedEl, + contextField, + flatJsonField; + // inject editor unless already present + if (!editorContainer.length) { + html = '
    '; + html += '

    ' + labelText + "

    "; + html += '
    '; + html += "
    "; + container.hide().after(html); + editorContainer = $("#" + id); + } else { + editorContainer.html(""); + } + + // stop operation if empty admin inline object + if (field.attr("id").indexOf("__prefix__") > -1) { + return; + } + + wrapper = editorContainer.parents(".jsoneditor-wrapper"); + options = { + theme: "django", + disable_collapse: true, + disable_edit_json: true, + startval: startval, + keep_oneof_values: false, + show_errors: field.data("show-errors") + ? field.data("show-errors") + : "change", + // if no backend selected use empty schema + schema: backend ? schemas[backend] : {}, }; + if (backend) { + options.schema = schemas[backend]; + } + // single schema mode + else if (backend === false) { + options.schema = schemas; + } + // if no backend selected use empty schema + else { + options.schema = {}; + } + if (field.attr("data-options") !== undefined) { + $.extend(options, JSON.parse(field.attr("data-options"))); + } - var loadUi = function (el, backend, schemas, setInitialValue) { - var field = $(el), - form = field.parents('form').eq(0), - value = JSON.parse(field.val()), - id = field.attr('id') + '_jsoneditor', - initialField = $('#initial-' + field.attr('id')), - container = field.parents('.form-row').eq(0), - labelText = container.find('label:not(#netjsonconfig-hint)').text(), - startval = $.isEmptyObject(value) ? null : value, - editorContainer = $('#' + id), - html, editor, options, wrapper, header, - getEditorValue, updateRaw, advancedEditor, - $advancedEl, - contextField, - flatJsonField; - // inject editor unless already present - if (!editorContainer.length) { - html = '
    '; - html += '

    ' + labelText + '

    '; - html += '
    '; - html += '
    '; - container.hide().after(html); - editorContainer = $('#' + id); - } - else { - editorContainer.html(''); - } + varValidationWorkaround(startval, options.schema); + editor = new JSONEditor(document.getElementById(id), options); + django._jsonEditors[id] = editor; + // initialise advanced json editor here (disable schema validation in VPN admin) + advancedEditor = initAdvancedEditor( + field, + value, + options.schema, + $("#vpn_form").length === 1, + ); + $advancedEl = $(advancedEditor.container); + getEditorValue = function () { + return JSON.stringify(editor.getValue(), null, 4); + }; + updateRaw = function () { + editor.root.showValidationErrors(getEditorErrors(editor)); + field.val(getEditorValue()); + }; - // stop operation if empty admin inline object - if (field.attr('id').indexOf('__prefix__') > -1) { - return; - } + if (editor.editors.root.addproperty_button) { + editor.editors.root.addproperty_button.value = "Configuration Menu"; + } + // set initial field value to the schema default + if (setInitialValue) { + initialField.val(getEditorValue()); + } + // update raw value on change event + editor.on("change", updateRaw); + editor.on("change", handleMaxLengthAttr); - wrapper = editorContainer.parents('.jsoneditor-wrapper'); - options = { - theme: 'django', - disable_collapse: true, - disable_edit_json: true, - startval: startval, - keep_oneof_values: false, - show_errors: field.data('show-errors') ? field.data('show-errors'): 'change', - // if no backend selected use empty schema - schema: backend ? schemas[backend] : {} - }; - if (backend) { - options.schema = schemas[backend]; + // update raw value before form submit + form.submit(function (e) { + // only submit form if the editor is clear of all validation errors + // eliminating vpn because it's UI is not yet using default values + if (getEditorErrors(editor).length && !$(".model-vpn").length) { + e.preventDefault(); + var message = "Please correct all validation errors below"; + if (gettext) { + message = gettext(message); } - // single schema mode - else if (backend === false) { - options.schema = schemas; - } - // if no backend selected use empty schema - else { - options.schema = {}; - } - if (field.attr("data-options") !== undefined) { - $.extend(options, JSON.parse(field.attr("data-options"))); + alert(message); + } + var contextField = $("#id_config-0-context"); + if (contextField.length) { + var contextValue = JSON.parse(contextField.val()); + contextField.val( + JSON.stringify(removeUnchangedDefaultValues(contextValue)), + ); + } + if ($advancedEl.is(":hidden")) { + return; + } + // only submit the form if the json in the advanced editor is valid + if (!isValidJson(advancedEditor)) { + e.preventDefault(); + alertInvalidJson(); + } else { + if (container.is(":hidden")) { + updateRaw(); } + } + }); - varValidationWorkaround(startval, options.schema); - editor = new JSONEditor(document.getElementById(id), options); - django._jsonEditors[id] = editor; - // initialise advanced json editor here (disable schema validation in VPN admin) - advancedEditor = initAdvancedEditor(field, value, options.schema, $('#vpn_form').length === 1); - $advancedEl = $(advancedEditor.container); - getEditorValue = function () { - return JSON.stringify(editor.getValue(), null, 4); - }; - updateRaw = function () { - editor.root.showValidationErrors(getEditorErrors(editor)); - field.val(getEditorValue()); - }; - - if (editor.editors.root.addproperty_button) { - editor.editors.root.addproperty_button.value = 'Configuration Menu'; - } - // set initial field value to the schema default - if (setInitialValue) { - initialField.val(getEditorValue()); - } - // update raw value on change event - editor.on('change', updateRaw); - editor.on('change', handleMaxLengthAttr); - - // update raw value before form submit - form.submit(function (e) { - // only submit form if the editor is clear of all validation errors - // eliminating vpn because it's UI is not yet using default values - if (getEditorErrors(editor).length && !$('.model-vpn').length) { - e.preventDefault(); - var message = 'Please correct all validation errors below'; - if (gettext) { message = gettext(message); } - alert(message); - } - var contextField = $('#id_config-0-context'); - if (contextField.length) { - var contextValue = JSON.parse(contextField.val()); - contextField.val(JSON.stringify( - removeUnchangedDefaultValues(contextValue) - )); - } - if ($advancedEl.is(':hidden')) { return; } - // only submit the form if the json in the advanced editor is valid - if (!isValidJson(advancedEditor)) { - e.preventDefault(); - alertInvalidJson(); - } - else { - if (container.is(':hidden')) { updateRaw(); } - } - }); + // trigger schema-data validation on default values change + contextField = window.getContext(); + if (contextField) { + contextField.addEventListener("change", function () { + validateOnDefaultValuesChange(editor, advancedEditor); + }); + } + // trigger schema-data validation on flat-json-value change + flatJsonField = $(".flat-json-rows"); + if (flatJsonField.length) { + flatJsonField.on("change", function () { + validateOnDefaultValuesChange(editor, advancedEditor); + }); + } + // add advanced edit button + header = editorContainer.find("> div > h3"); + header.find("span:first-child").hide(); // hides editor title + header.attr("class", "controls"); + // move advanced mode button in auto-generated UI + container.find(".advanced-mode").clone().prependTo(header); + // advanced mode button + header.find(".advanced-mode").click(function () { + if (!window.isContextValid()) { + alert( + "Advanced mode does not work when default value field is invalid JSON!", + ); + } else { + // update autogenrated advanced json editor with new data + advancedEditor.set(JSON.parse(field.val())); + wrapper.hide(); + container.show(); + // set the advanced editor container to full screen mode + toggleFullScreen(); + } + }); - // trigger schema-data validation on default values change - contextField = window.getContext(); - if (contextField) { - contextField.addEventListener('change', function () { - validateOnDefaultValuesChange(editor, advancedEditor); - }); - } - // trigger schema-data validation on flat-json-value change - flatJsonField = $('.flat-json-rows'); - if (flatJsonField.length) { - flatJsonField.on('change', function () { - validateOnDefaultValuesChange(editor, advancedEditor); - }); - } - // add advanced edit button - header = editorContainer.find('> div > h3'); - header.find('span:first-child').hide(); // hides editor title - header.attr('class', 'controls'); - // move advanced mode button in auto-generated UI - container.find('.advanced-mode').clone().prependTo(header); - // advanced mode button - header.find('.advanced-mode').click(function () { - if (!window.isContextValid()) { - alert('Advanced mode does not work when default value field is invalid JSON!'); - } else { - // update autogenrated advanced json editor with new data - advancedEditor.set(JSON.parse(field.val())); - wrapper.hide(); - container.show(); - // set the advanced editor container to full screen mode - toggleFullScreen(); - } - }); + // back to normal mode button + $advancedEl.find(".jsoneditor-exit").click(function () { + // check if json in advanced mode is valid before coming back to normal mode + if (isValidJson(advancedEditor)) { + // update autogenerated UI + editor.setValue(JSON.parse(field.val())); + toggleFullScreen(); + container.hide(); + wrapper.show(); + } else { + alertInvalidJson(); + } + }); - // back to normal mode button - $advancedEl.find('.jsoneditor-exit').click(function () { - // check if json in advanced mode is valid before coming back to normal mode - if (isValidJson(advancedEditor)) { - // update autogenerated UI - editor.setValue(JSON.parse(field.val())); - toggleFullScreen(); - container.hide(); - wrapper.show(); - } - else { - alertInvalidJson(); - } - }); + // re-enable click on netjsonconfig hint + $advancedEl.find("#netjsonconfig-hint-advancedmode a").click(function () { + var window_ = window.open($(this).attr("href"), "_blank"); + window_.focus(); + }); - // re-enable click on netjsonconfig hint - $advancedEl.find('#netjsonconfig-hint-advancedmode a').click(function () { - var window_ = window.open($(this).attr('href'), '_blank'); - window_.focus(); - }); + // allow to add object properties by pressing enter + form.on("keypress", ".jsoneditor .modal input[type=text]", function (e) { + if (e.keyCode == 13) { + e.preventDefault(); + $(e.target).siblings("input.json-editor-btn-add").trigger("click"); + $(e.target).val(""); + } + }); - // allow to add object properties by pressing enter - form.on('keypress', '.jsoneditor .modal input[type=text]', function (e) { - if (e.keyCode == 13) { - e.preventDefault(); - $(e.target).siblings('input.json-editor-btn-add').trigger('click'); - $(e.target).val(''); - } - }); + // so that other files can use updateContext + window.updateContext = updateContext; - // so that other files can use updateContext - window.updateContext = updateContext; + $(".jsoneditor").on("input paste", ".has-max-length:visible", function (e) { + var field = $(e.target), + pasteValue = ""; - $('.jsoneditor').on('input paste', '.has-max-length:visible', function(e) { - var field = $(e.target), - pasteValue = ''; + if (e.originalEvent.type === "paste") { + pasteValue = e.originalEvent.clipboardData.getData("text"); + } - if (e.originalEvent.type === 'paste') { - pasteValue = e.originalEvent.clipboardData.getData('text'); - } + if (field.val().indexOf("{{") > -1 || pasteValue.indexOf("{{") > -1) { + field.removeAttr("maxlength"); + } else { + field.attr("maxlength", field.data("maxlength")); + } + }); + }; - if (field.val().indexOf('{{') > -1 || pasteValue.indexOf('{{') > -1) { - field.removeAttr('maxlength'); - } else { - field.attr('maxlength', field.data('maxlength')); + var bindLoadUi = function () { + $('.jsoneditor-raw:not([name*="__prefix__"]):not(.manual)').each( + function (i, el) { + // Add query parameters defined in the widget + var url, + queryString = "?", + queryParams = $(el).data("query-params"); + if (queryParams !== undefined) { + var queryKeys = Object.keys(queryParams); + for (var j = 0; j < queryKeys.length; ++j) { + queryString += + "&" + + queryKeys[j] + + "=" + + $("#" + queryParams[queryKeys[j]]).val(); + } + } + url = $(el).data("schema-url") + queryString; + $.getJSON(url, function (schemas) { + django._schemas[$(el).data("schema-url")] = schemas; + var field = $(el), + schema = field.attr("data-schema"), + schemaSelector = field.attr("data-schema-selector"); + if (schema !== undefined) { + loadUi(el, schema, schemas, true); + } else { + if (schemaSelector === undefined) { + schemaSelector = "#id_backend, #id_config-0-backend"; } - }); - }; - - var bindLoadUi = function () { - $('.jsoneditor-raw:not([name*="__prefix__"]):not(.manual)').each(function (i, el) { - // Add query parameters defined in the widget - var url, queryString = '?', - queryParams = $(el).data('query-params'); - if (queryParams !== undefined) { - var queryKeys = Object.keys(queryParams); - for (var j = 0; j < queryKeys.length; ++j) { - queryString += '&' + queryKeys[j] + '=' + $('#' + queryParams[queryKeys[j]]).val(); - } + var selector = $(schemaSelector), + schemaKey = selector.val() || false; + // load first time + loadUi(el, schemaKey, schemas, true); + // reload when selector is changed + if (selector.length) { + selector.change(function () { + loadUi(el, selector.val(), schemas); + }); } - url = $(el).data('schema-url') + queryString; - $.getJSON(url, function (schemas) { - django._schemas[$(el).data('schema-url')] = schemas; - var field = $(el), - schema = field.attr("data-schema"), - schemaSelector = field.attr("data-schema-selector"); - if (schema !== undefined) { - loadUi(el, schema, schemas, true); - } else { - if (schemaSelector === undefined) { - schemaSelector = '#id_backend, #id_config-0-backend'; - } - var selector = $(schemaSelector), - schemaKey = selector.val() || false; - // load first time - loadUi(el, schemaKey, schemas, true); - // reload when selector is changed - if (selector.length) { - selector.change(function () { - loadUi(el, selector.val(), schemas); - }); - } - } - $(`#${el.id}`).trigger('jsonschema-schemaloaded'); - }); + } + $(`#${el.id}`).trigger("jsonschema-schemaloaded"); }); - }; + }, + ); + }; - $(function () { - var addConfig = $('#config-group.inline-group .add-row'); - // if configuration is admin inline - // load it when add button is clicked - addConfig.click(bindLoadUi); - // otherwise load immediately - bindLoadUi(); - // fill device context field with default values of selected templates. - // If unsaved_changes have already mapped values, then fetch defaultValues, - // otherwise wait for event to be triggered. - if (django._owcInitialValues !== undefined){ - getDefaultValues(true); - } else { - $(document).one('owcInitialValuesLoaded', function () { - getDefaultValues(true); - }); - } - $('.sortedm2m-items').on('change', function() { - getDefaultValues(); - }); - $('.sortedm2m-items').on('sortstop', function() { - getDefaultValues(); - }); - $('#id_group').on('change', function() { - getDefaultValues(); - }); - $('#id_organization').on('change', function() { - getDefaultValues(); - }); + $(function () { + var addConfig = $("#config-group.inline-group .add-row"); + // if configuration is admin inline + // load it when add button is clicked + addConfig.click(bindLoadUi); + // otherwise load immediately + bindLoadUi(); + // fill device context field with default values of selected templates. + // If unsaved_changes have already mapped values, then fetch defaultValues, + // otherwise wait for event to be triggered. + if (django._owcInitialValues !== undefined) { + getDefaultValues(true); + } else { + $(document).one("owcInitialValuesLoaded", function () { + getDefaultValues(true); + }); + } + $(".sortedm2m-items").on("change", function () { + getDefaultValues(); }); + $(".sortedm2m-items").on("sortstop", function () { + getDefaultValues(); + }); + $("#id_group").on("change", function () { + getDefaultValues(); + }); + $("#id_organization").on("change", function () { + getDefaultValues(); + }); + }); - // deletes maxLength on ip address schema if address contains variable - // this workaround is necessary until we rewrite the config UI to - // deal with variables properly - var varValidationWorkaround = function(value, schema) { - if (value && value.interfaces) { - $.each(value.interfaces, function(i, interf) { - if (interf.mac && interf.mac.indexOf('{{') > -1) { - try { - delete schema.definitions.interface_settings.properties.mac.pattern; - } catch (e) {} - } - if (interf.addresses) { - $.each(interf.addresses, function(i, ip) { - if (ip.address && ip.address.indexOf('{{') > -1) { - var ipFamily = ip.family + '_address'; - try { - delete schema.definitions[ipFamily].allOf[2].properties.address.maxLength; - } catch (e) {} - } - }); - } - if (interf.wireless && interf.wireless.bssid && interf.wireless.bssid.indexOf('{{') > -1) { - try { - delete schema.definitions.bssid_wireless_property.properties.bssid.pattern; - } catch (e) {} - } - }); - } - }; + // deletes maxLength on ip address schema if address contains variable + // this workaround is necessary until we rewrite the config UI to + // deal with variables properly + var varValidationWorkaround = function (value, schema) { + if (value && value.interfaces) { + $.each(value.interfaces, function (i, interf) { + if (interf.mac && interf.mac.indexOf("{{") > -1) { + try { + delete schema.definitions.interface_settings.properties.mac.pattern; + } catch (e) {} + } + if (interf.addresses) { + $.each(interf.addresses, function (i, ip) { + if (ip.address && ip.address.indexOf("{{") > -1) { + var ipFamily = ip.family + "_address"; + try { + delete schema.definitions[ipFamily].allOf[2].properties.address + .maxLength; + } catch (e) {} + } + }); + } + if ( + interf.wireless && + interf.wireless.bssid && + interf.wireless.bssid.indexOf("{{") > -1 + ) { + try { + delete schema.definitions.bssid_wireless_property.properties.bssid + .pattern; + } catch (e) {} + } + }); + } + }; - // Export loadUi - django._loadJsonSchemaUi = loadUi; -}(django.jQuery)); + // Export loadUi + django._loadJsonSchemaUi = loadUi; +})(django.jQuery); var matchKey = (function () { - var elem = document.documentElement; - if (elem.matches) { return 'matches'; } - if (elem.webkitMatchesSelector) { return 'webkitMatchesSelector'; } - if (elem.mozMatchesSelector) { return 'mozMatchesSelector'; } - if (elem.msMatchesSelector) { return 'msMatchesSelector'; } - if (elem.oMatchesSelector) { return 'oMatchesSelector'; } -}()); + var elem = document.documentElement; + if (elem.matches) { + return "matches"; + } + if (elem.webkitMatchesSelector) { + return "webkitMatchesSelector"; + } + if (elem.mozMatchesSelector) { + return "mozMatchesSelector"; + } + if (elem.msMatchesSelector) { + return "msMatchesSelector"; + } + if (elem.oMatchesSelector) { + return "oMatchesSelector"; + } +})(); // JSON-Schema Edtor django theme JSONEditor.defaults.themes.django = JSONEditor.AbstractTheme.extend({ - getContainer: function () { - return document.createElement('div'); - }, - getFloatRightLinkHolder: function () { - var el = document.createElement('div'); - el.style = el.style || {}; - el.style.cssFloat = 'right'; - el.style.marginLeft = '10px'; - return el; - }, - getModal: function () { - var el = document.createElement('div'); - el.className = 'modal'; - el.style.display = 'none'; - return el; - }, - getGridContainer: function () { - var el = document.createElement('div'); - el.className = 'grid-container'; - return el; - }, - getGridRow: function () { - var el = document.createElement('div'); - el.className = 'grid-row'; - return el; - }, - getGridColumn: function () { - var el = document.createElement('div'); - el.className = 'grid-column'; - return el; - }, - setGridColumnSize: function (el) { - return el; - }, - getLink: function (text) { - var el = document.createElement('a'); - el.setAttribute('href', '#'); - el.appendChild(document.createTextNode(text)); - return el; - }, - disableHeader: function (header) { - header.style.color = '#ccc'; - }, - disableLabel: function (label) { - label.style.color = '#ccc'; - }, - enableHeader: function (header) { - header.style.color = ''; - }, - enableLabel: function (label) { - label.style.color = ''; - }, - getFormInputLabel: function (text) { - var el = document.createElement('label'); - el.appendChild(document.createTextNode(text)); - return el; - }, - getCheckboxLabel: function (text) { - var el = this.getFormInputLabel(text); - return el; - }, - getHeader: function (text) { - var el = document.createElement('h3'); - if (typeof text === "string") { - el.textContent = text; - } else { - el.appendChild(text); - } - return el; - }, - getCheckbox: function () { - var el = this.getFormInputField('checkbox'); - el.className = null; - el.style.display = 'inline-block'; - el.style.width = 'auto'; - return el; - }, - getMultiCheckboxHolder: function (controls, label, description) { - var el = document.createElement('div'), - i; + getContainer: function () { + return document.createElement("div"); + }, + getFloatRightLinkHolder: function () { + var el = document.createElement("div"); + el.style = el.style || {}; + el.style.cssFloat = "right"; + el.style.marginLeft = "10px"; + return el; + }, + getModal: function () { + var el = document.createElement("div"); + el.className = "modal"; + el.style.display = "none"; + return el; + }, + getGridContainer: function () { + var el = document.createElement("div"); + el.className = "grid-container"; + return el; + }, + getGridRow: function () { + var el = document.createElement("div"); + el.className = "grid-row"; + return el; + }, + getGridColumn: function () { + var el = document.createElement("div"); + el.className = "grid-column"; + return el; + }, + setGridColumnSize: function (el) { + return el; + }, + getLink: function (text) { + var el = document.createElement("a"); + el.setAttribute("href", "#"); + el.appendChild(document.createTextNode(text)); + return el; + }, + disableHeader: function (header) { + header.style.color = "#ccc"; + }, + disableLabel: function (label) { + label.style.color = "#ccc"; + }, + enableHeader: function (header) { + header.style.color = ""; + }, + enableLabel: function (label) { + label.style.color = ""; + }, + getFormInputLabel: function (text) { + var el = document.createElement("label"); + el.appendChild(document.createTextNode(text)); + return el; + }, + getCheckboxLabel: function (text) { + var el = this.getFormInputLabel(text); + return el; + }, + getHeader: function (text) { + var el = document.createElement("h3"); + if (typeof text === "string") { + el.textContent = text; + } else { + el.appendChild(text); + } + return el; + }, + getCheckbox: function () { + var el = this.getFormInputField("checkbox"); + el.className = null; + el.style.display = "inline-block"; + el.style.width = "auto"; + return el; + }, + getMultiCheckboxHolder: function (controls, label, description) { + var el = document.createElement("div"), + i; - if (label) { - label.style.display = 'block'; - el.appendChild(label); - } + if (label) { + label.style.display = "block"; + el.appendChild(label); + } - for (i in controls) { - if (!controls.hasOwnProperty(i)) { continue; } - controls[i].style.display = 'inline-block'; - controls[i].style.marginRight = '20px'; - el.appendChild(controls[i]); - } + for (i in controls) { + if (!controls.hasOwnProperty(i)) { + continue; + } + controls[i].style.display = "inline-block"; + controls[i].style.marginRight = "20px"; + el.appendChild(controls[i]); + } - if (description) { el.appendChild(description); } + if (description) { + el.appendChild(description); + } - return el; - }, - getSelectInput: function (options) { - var select = document.createElement('select'); - if (options) { this.setSelectOptions(select, options); } - return select; - }, - getSwitcher: function (options) { - var switcher = this.getSelectInput(options); - switcher.className = 'switcher'; - return switcher; - }, - getSwitcherOptions: function (switcher) { - return switcher.getElementsByTagName('option'); - }, - setSwitcherOptions: function (switcher, options, titles) { - this.setSelectOptions(switcher, options, titles); - }, - setSelectOptions: function (select, options, titles) { - titles = titles || []; - select.innerHTML = ''; - var i, option; - for (i = 0; i < options.length; i++) { - option = document.createElement('option'); - option.setAttribute('value', options[i]); - option.textContent = titles[i] || options[i]; - select.appendChild(option); - } - }, - getTextareaInput: function () { - var el = document.createElement('textarea'); - el.className = 'vLargeTextField'; - return el; - }, - getRangeInput: function (min, max, step) { - var el = this.getFormInputField('range'); - el.setAttribute('min', min); - el.setAttribute('max', max); - el.setAttribute('step', step); - return el; - }, - getFormInputField: function (type) { - var el = document.createElement('input'); - el.className = 'vTextField'; - el.setAttribute('type', type); - return el; - }, - afterInputReady: function () { - return; - }, - getFormControl: function (label, input, description) { - var el = document.createElement('div'); - el.className = 'form-row'; - if (label) { el.appendChild(label); } - if (input.type === 'checkbox') { - label.insertBefore(input, label.firstChild); - } else { - el.appendChild(input); - } - if (description) { el.appendChild(description); } - return el; - }, - getIndentedPanel: function () { - var el = document.createElement('div'); - el.className = 'inline-related'; - return el; - }, - getChildEditorHolder: function () { - var el = document.createElement('div'); - el.className = 'inline-group'; - return el; - }, - getDescription: function (text) { - var el = document.createElement('p'); - el.className = 'help'; - el.innerHTML = text; - return el; - }, - getCheckboxDescription: function (text) { - return this.getDescription(text); - }, - getFormInputDescription: function (text) { - return this.getDescription(text); - }, - getHeaderButtonHolder: function () { - var el = document.createElement('span'); - el.className = 'control'; - return el; - }, - getButtonHolder: function () { - var el = document.createElement('div'); - el.className = 'control'; - return el; - }, - getButton: function (text, icon, title) { - var el = document.createElement('input'), - className = 'button'; - if (text.indexOf('Delete') > -1) { - className += ' deletelink'; - } - el.className = className; - el.type = 'button'; - this.setButtonText(el, text, icon, title); - return el; - }, - setButtonText: function (button, text, icon, title) { - button.value = text; - if (title) { button.setAttribute('title', title); } - }, - getTable: function () { - return document.createElement('table'); - }, - getTableRow: function () { - return document.createElement('tr'); - }, - getTableHead: function () { - return document.createElement('thead'); - }, - getTableBody: function () { - return document.createElement('tbody'); - }, - getTableHeaderCell: function (text) { - var el = document.createElement('th'); - el.textContent = text; - return el; - }, - getTableCell: function () { - var el = document.createElement('td'); - return el; - }, - getErrorMessage: function (text) { - var el = document.createElement('p'); - el.style = el.style || {}; - el.style.color = 'red'; - el.appendChild(document.createTextNode(text)); - return el; - }, - addInputError: function (input, text) { - input.parentNode.className += ' errors'; - if (!input.errmsg) { - input.errmsg = document.createElement('li'); - var ul = document.createElement('ul'); - ul.className = 'errorlist'; - ul.appendChild(input.errmsg); - input.parentNode.appendChild(ul); - } - else { - input.errmsg.parentNode.style.display = ''; - } - input.errmsg.textContent = text; - }, - removeInputError: function (input) { - if (!input.errmsg) { return; } - input.errmsg.parentNode.style.display = 'none'; - input.parentNode.className = input.parentNode.className.replace(/\s?errors/g, ''); - }, - addTableRowError: function () { return; }, - removeTableRowError: function () { return; }, - getTabHolder: function () { - var el = document.createElement('div'); - el.innerHTML = "
    "; - return el; - }, - applyStyles: function (el, styles) { - el.style = el.style || {}; - var i; - for (i in styles) { - if (!styles.hasOwnProperty(i)) { continue; } - el.style[i] = styles[i]; - } - }, - closest: function (elem, selector) { - while (elem && elem !== document) { - if (matchKey) { - if (elem[matchKey](selector)) { - return elem; - } - elem = elem.parentNode; - } else { - return false; - } + return el; + }, + getSelectInput: function (options) { + var select = document.createElement("select"); + if (options) { + this.setSelectOptions(select, options); + } + return select; + }, + getSwitcher: function (options) { + var switcher = this.getSelectInput(options); + switcher.className = "switcher"; + return switcher; + }, + getSwitcherOptions: function (switcher) { + return switcher.getElementsByTagName("option"); + }, + setSwitcherOptions: function (switcher, options, titles) { + this.setSelectOptions(switcher, options, titles); + }, + setSelectOptions: function (select, options, titles) { + titles = titles || []; + select.innerHTML = ""; + var i, option; + for (i = 0; i < options.length; i++) { + option = document.createElement("option"); + option.setAttribute("value", options[i]); + option.textContent = titles[i] || options[i]; + select.appendChild(option); + } + }, + getTextareaInput: function () { + var el = document.createElement("textarea"); + el.className = "vLargeTextField"; + return el; + }, + getRangeInput: function (min, max, step) { + var el = this.getFormInputField("range"); + el.setAttribute("min", min); + el.setAttribute("max", max); + el.setAttribute("step", step); + return el; + }, + getFormInputField: function (type) { + var el = document.createElement("input"); + el.className = "vTextField"; + el.setAttribute("type", type); + return el; + }, + afterInputReady: function () { + return; + }, + getFormControl: function (label, input, description) { + var el = document.createElement("div"); + el.className = "form-row"; + if (label) { + el.appendChild(label); + } + if (input.type === "checkbox") { + label.insertBefore(input, label.firstChild); + } else { + el.appendChild(input); + } + if (description) { + el.appendChild(description); + } + return el; + }, + getIndentedPanel: function () { + var el = document.createElement("div"); + el.className = "inline-related"; + return el; + }, + getChildEditorHolder: function () { + var el = document.createElement("div"); + el.className = "inline-group"; + return el; + }, + getDescription: function (text) { + var el = document.createElement("p"); + el.className = "help"; + el.innerHTML = text; + return el; + }, + getCheckboxDescription: function (text) { + return this.getDescription(text); + }, + getFormInputDescription: function (text) { + return this.getDescription(text); + }, + getHeaderButtonHolder: function () { + var el = document.createElement("span"); + el.className = "control"; + return el; + }, + getButtonHolder: function () { + var el = document.createElement("div"); + el.className = "control"; + return el; + }, + getButton: function (text, icon, title) { + var el = document.createElement("input"), + className = "button"; + if (text.indexOf("Delete") > -1) { + className += " deletelink"; + } + el.className = className; + el.type = "button"; + this.setButtonText(el, text, icon, title); + return el; + }, + setButtonText: function (button, text, icon, title) { + button.value = text; + if (title) { + button.setAttribute("title", title); + } + }, + getTable: function () { + return document.createElement("table"); + }, + getTableRow: function () { + return document.createElement("tr"); + }, + getTableHead: function () { + return document.createElement("thead"); + }, + getTableBody: function () { + return document.createElement("tbody"); + }, + getTableHeaderCell: function (text) { + var el = document.createElement("th"); + el.textContent = text; + return el; + }, + getTableCell: function () { + var el = document.createElement("td"); + return el; + }, + getErrorMessage: function (text) { + var el = document.createElement("p"); + el.style = el.style || {}; + el.style.color = "red"; + el.appendChild(document.createTextNode(text)); + return el; + }, + addInputError: function (input, text) { + input.parentNode.className += " errors"; + if (!input.errmsg) { + input.errmsg = document.createElement("li"); + var ul = document.createElement("ul"); + ul.className = "errorlist"; + ul.appendChild(input.errmsg); + input.parentNode.appendChild(ul); + } else { + input.errmsg.parentNode.style.display = ""; + } + input.errmsg.textContent = text; + }, + removeInputError: function (input) { + if (!input.errmsg) { + return; + } + input.errmsg.parentNode.style.display = "none"; + input.parentNode.className = input.parentNode.className.replace( + /\s?errors/g, + "", + ); + }, + addTableRowError: function () { + return; + }, + removeTableRowError: function () { + return; + }, + getTabHolder: function () { + var el = document.createElement("div"); + el.innerHTML = + "
    "; + return el; + }, + applyStyles: function (el, styles) { + el.style = el.style || {}; + var i; + for (i in styles) { + if (!styles.hasOwnProperty(i)) { + continue; + } + el.style[i] = styles[i]; + } + }, + closest: function (elem, selector) { + while (elem && elem !== document) { + if (matchKey) { + if (elem[matchKey](selector)) { + return elem; } + elem = elem.parentNode; + } else { return false; - }, - getTab: function (span) { - var el = document.createElement('div'); - el.appendChild(span); - el.style = el.style || {}; - this.applyStyles(el, { - border: '1px solid #ccc', - borderWidth: '1px 0 1px 1px', - textAlign: 'center', - lineHeight: '30px', - borderRadius: '5px', - borderBottomRightRadius: 0, - borderTopRightRadius: 0, - fontWeight: 'bold', - cursor: 'pointer' - }); - return el; - }, - getTabContentHolder: function (tab_holder) { - return tab_holder.children[1]; - }, - getTabContent: function () { - return this.getIndentedPanel(); - }, - markTabActive: function (tab) { - this.applyStyles(tab, { - opacity: 1, - background: 'white' - }); - }, - markTabInactive: function (tab) { - this.applyStyles(tab, { - opacity: 0.5, - background: '' - }); - }, - addTab: function (holder, tab) { - holder.children[0].appendChild(tab); - }, - getBlockLink: function () { - var link = document.createElement('a'); - link.style.display = 'block'; - return link; - }, - getBlockLinkHolder: function () { - var el = document.createElement('div'); - return el; - }, - getLinksHolder: function () { - var el = document.createElement('div'); - return el; - }, - createMediaLink: function (holder, link, media) { - holder.appendChild(link); - media.style.width = '100%'; - holder.appendChild(media); - }, - createImageLink: function (holder, link, image) { - holder.appendChild(link); - link.appendChild(image); + } } + return false; + }, + getTab: function (span) { + var el = document.createElement("div"); + el.appendChild(span); + el.style = el.style || {}; + this.applyStyles(el, { + border: "1px solid #ccc", + borderWidth: "1px 0 1px 1px", + textAlign: "center", + lineHeight: "30px", + borderRadius: "5px", + borderBottomRightRadius: 0, + borderTopRightRadius: 0, + fontWeight: "bold", + cursor: "pointer", + }); + return el; + }, + getTabContentHolder: function (tab_holder) { + return tab_holder.children[1]; + }, + getTabContent: function () { + return this.getIndentedPanel(); + }, + markTabActive: function (tab) { + this.applyStyles(tab, { + opacity: 1, + background: "white", + }); + }, + markTabInactive: function (tab) { + this.applyStyles(tab, { + opacity: 0.5, + background: "", + }); + }, + addTab: function (holder, tab) { + holder.children[0].appendChild(tab); + }, + getBlockLink: function () { + var link = document.createElement("a"); + link.style.display = "block"; + return link; + }, + getBlockLinkHolder: function () { + var el = document.createElement("div"); + return el; + }, + getLinksHolder: function () { + var el = document.createElement("div"); + return el; + }, + createMediaLink: function (holder, link, media) { + holder.appendChild(link); + media.style.width = "100%"; + holder.appendChild(media); + }, + createImageLink: function (holder, link, image) { + holder.appendChild(link); + link.appendChild(image); + }, }); // This method has been copied from jdorn/json-editor library to facilitate // overriding JSONEditor.defaults.editors.multiple.prototype.setValue -JSONEditor.defaults.editors.multiple.prototype.$each = function (obj, callback) { - if (!obj || typeof obj !== "object") { +JSONEditor.defaults.editors.multiple.prototype.$each = function ( + obj, + callback, +) { + if (!obj || typeof obj !== "object") { + return; + } + var i; + if ( + Array.isArray(obj) || + (typeof obj.length === "number" && obj.length > 0 && obj.length - 1 in obj) + ) { + for (i = 0; i < obj.length; i++) { + if (callback(i, obj[i]) === false) { return; + } } - var i; - if (Array.isArray(obj) || (typeof obj.length === 'number' && obj.length > 0 && (obj.length - 1) in obj)) { - for (i = 0; i < obj.length; i++) { - if (callback(i, obj[i]) === false) { - return; - } + } else { + if (Object.keys) { + var keys = Object.keys(obj); + for (i = 0; i < keys.length; i++) { + if (callback(keys[i], obj[keys[i]]) === false) { + return; } + } } else { - if (Object.keys) { - var keys = Object.keys(obj); - for (i = 0; i < keys.length; i++) { - if (callback(keys[i], obj[keys[i]]) === false) { - return; - } - } - } else { - for (i in obj) { - if (!obj.hasOwnProperty(i)) { - continue; - } - if (callback(i, obj[i]) === false) { - return; - } - } + for (i in obj) { + if (!obj.hasOwnProperty(i)) { + continue; + } + if (callback(i, obj[i]) === false) { + return; } + } } + } }; // Override setValue method to allow using variables for fields with maxLength. @@ -888,102 +978,110 @@ JSONEditor.defaults.editors.multiple.prototype.$each = function (obj, callback) // contains a variable: this customization is required for validation to pass // (the variable name could be longer than maxlength and may not fit). // Later, the maxLength attribute is added back to restore validator to it's original form. -JSONEditor.defaults.editors.multiple.prototype.setValue = function (val, initial) { - // Determine type by getting the first one that validates - var self = this, - validatorModification = {}; - this.$each(this.validators, function (i, validator) { - // Customization to modify validators starts here - if ((val) && typeof val === 'object') { - Object.entries(val).forEach(function (entry) { - if (typeof entry[1] === 'string' && entry[1].indexOf('{{') > -1) { - if ((validator.schema.properties) && (validator.schema.properties[entry[0]])) { - validatorModification[i] = { - propertyName: entry[0], - maxLength: validator.schema.properties[entry[0]].maxLength - }; - delete validator.schema.properties[entry[0]].maxLength; - } - } - }); - } - // Customization to modify validators ends here - if (!validator.validate(val).length) { - self.type = i; - self.switcher.value = self.display_text[i]; - return false; +JSONEditor.defaults.editors.multiple.prototype.setValue = function ( + val, + initial, +) { + // Determine type by getting the first one that validates + var self = this, + validatorModification = {}; + this.$each(this.validators, function (i, validator) { + // Customization to modify validators starts here + if (val && typeof val === "object") { + Object.entries(val).forEach(function (entry) { + if (typeof entry[1] === "string" && entry[1].indexOf("{{") > -1) { + if ( + validator.schema.properties && + validator.schema.properties[entry[0]] + ) { + validatorModification[i] = { + propertyName: entry[0], + maxLength: validator.schema.properties[entry[0]].maxLength, + }; + delete validator.schema.properties[entry[0]].maxLength; + } } - }); - this.switchEditor(this.type); + }); + } + // Customization to modify validators ends here + if (!validator.validate(val).length) { + self.type = i; + self.switcher.value = self.display_text[i]; + return false; + } + }); + this.switchEditor(this.type); - this.editors[this.type].setValue(val, initial); + this.editors[this.type].setValue(val, initial); - // Customization to restore validators starts here - Object.entries(validatorModification).forEach(function (entry) { - self.validators[entry[0]].schema.properties[entry[1].propertyName].maxLength = entry[1].maxLength; - }); - // Customization to restore validators ends here + // Customization to restore validators starts here + Object.entries(validatorModification).forEach(function (entry) { + self.validators[entry[0]].schema.properties[ + entry[1].propertyName + ].maxLength = entry[1].maxLength; + }); + // Customization to restore validators ends here - this.refreshValue(); - self.onChange(); + this.refreshValue(); + self.onChange(); }; // Overriding following methods on the JSONEditor is required for // working of select2 fields. Refer to https://git.io/J4Qcp for // more information. JSONEditor.defaults.editors.select.prototype.enable = function () { - if (!this.always_disabled) { - this.input.disabled = false; - if (this.select2) { - this.select2.disabled = false; - } + if (!this.always_disabled) { + this.input.disabled = false; + if (this.select2) { + this.select2.disabled = false; } - this.disabled = false; + } + this.disabled = false; }; JSONEditor.defaults.editors.select.prototype.disable = function () { - this.input.disabled = true; - if (this.select2) { - this.select2.disabled = true; - } - this.disabled = true; + this.input.disabled = true; + if (this.select2) { + this.select2.disabled = true; + } + this.disabled = true; }; JSONEditor.defaults.editors.multiselect.prototype.enable = function () { - if (!this.always_disabled) { - if (this.input) { - this.input.disabled = false; - } else if (this.inputs) { - for (var i in this.inputs) { - if (!this.inputs.hasOwnProperty(i)) { - continue; - } - this.inputs[i].disabled = false; - } - } - // Modified code begins - if (this.select2) { - this.select2.disabled = false; - } - this.disabled = false; - // Modified code ends - } -}; - -JSONEditor.defaults.editors.multiselect.prototype.disable = function () { + if (!this.always_disabled) { if (this.input) { - this.input.disabled = true; + this.input.disabled = false; } else if (this.inputs) { - for (var i in this.inputs) { - if (!this.inputs.hasOwnProperty(i)) { - continue; - } - this.inputs[i].disabled = true; + for (var i in this.inputs) { + if (!this.inputs.hasOwnProperty(i)) { + continue; } + this.inputs[i].disabled = false; + } } // Modified code begins if (this.select2) { - this.select2.disabled = true; - // Modified code ends + this.select2.disabled = false; + } + this.disabled = false; + // Modified code ends + } +}; + +JSONEditor.defaults.editors.multiselect.prototype.disable = function () { + if (this.input) { + this.input.disabled = true; + } else if (this.inputs) { + for (var i in this.inputs) { + if (!this.inputs.hasOwnProperty(i)) { + continue; + } + this.inputs[i].disabled = true; } + } + // Modified code begins + if (this.select2) { + this.select2.disabled = true; + // Modified code ends + } }; diff --git a/openwisp_controller/config/static/sortedm2m/patch_sortedm2m.js b/openwisp_controller/config/static/sortedm2m/patch_sortedm2m.js index c5f8c9388..284145ed1 100644 --- a/openwisp_controller/config/static/sortedm2m/patch_sortedm2m.js +++ b/openwisp_controller/config/static/sortedm2m/patch_sortedm2m.js @@ -1,16 +1,15 @@ -'use strict'; +"use strict"; (function ($) { - $(document).ready(function () { - if ($('.sortedm2m-items').length < 2) { - destroyHiddenSortableWidget($); - } - $(document).on('click', 'a.inline-deletelink', function () { - destroyHiddenSortableWidget($); - }); - }); - - function destroyHiddenSortableWidget($) { - $('.inline-related.empty-form .sortedm2m-items').sortable('destroy'); + $(document).ready(function () { + if ($(".sortedm2m-items").length < 2) { + destroyHiddenSortableWidget($); } + $(document).on("click", "a.inline-deletelink", function () { + destroyHiddenSortableWidget($); + }); + }); -}(django.jQuery)); + function destroyHiddenSortableWidget($) { + $(".inline-related.empty-form .sortedm2m-items").sortable("destroy"); + } +})(django.jQuery); diff --git a/openwisp_controller/connection/static/connection/js/commands.js b/openwisp_controller/connection/static/connection/js/commands.js index ce0848526..47bead9e9 100644 --- a/openwisp_controller/connection/static/connection/js/commands.js +++ b/openwisp_controller/connection/static/connection/js/commands.js @@ -1,455 +1,502 @@ -'use strict'; +"use strict"; -var gettext = window.gettext || function (word) { return word; }; -var interpolate = interpolate || function(){}; +var gettext = + window.gettext || + function (word) { + return word; + }; +var interpolate = interpolate || function () {}; const deviceId = getObjectIdFromUrl(); django.jQuery(function ($) { - if((typeof(owControllerApiHost) === 'undefined')|| isDeviceRecoverForm()) { - return; - } - const commandWebSocket = new ReconnectingWebSocket( - `${getWebSocketProtocol()}${owControllerApiHost.host}/ws/controller/device/${deviceId}/command`, - null, { - debug: false, - automaticOpen: false, - // The library re-connects if it fails to establish a connection in "timeoutInterval". - // On slow internet connections, the default value of "timeoutInterval" will - // keep terminating and re-establishing the connection. - timeoutInterval: 7000, - } - ); - commandWebSocket.open(); - let selector = $('#id_command_set-0-type'), - showFields = function () { - var fields = $('#command_set-group fieldset > .form-row:not(.field-type):not(.field-params), #command_set-group .jsoneditor-wrapper'), - value = selector.val(); - if (!value) { - fields.hide(); - } else { - $('#command_set-2-group fieldset .dynamic-command_set-2:first'); - fields.show(); - } - }; - selector.change(function () { - showFields(); - }); + if (typeof owControllerApiHost === "undefined" || isDeviceRecoverForm()) { + return; + } + const commandWebSocket = new ReconnectingWebSocket( + `${getWebSocketProtocol()}${ + owControllerApiHost.host + }/ws/controller/device/${deviceId}/command`, + null, + { + debug: false, + automaticOpen: false, + // The library re-connects if it fails to establish a connection in "timeoutInterval". + // On slow internet connections, the default value of "timeoutInterval" will + // keep terminating and re-establishing the connection. + timeoutInterval: 7000, + }, + ); + commandWebSocket.open(); + let selector = $("#id_command_set-0-type"), + showFields = function () { + var fields = $( + "#command_set-group fieldset > .form-row:not(.field-type):not(.field-params), #command_set-group .jsoneditor-wrapper", + ), + value = selector.val(); + if (!value) { + fields.hide(); + } else { + $("#command_set-2-group fieldset .dynamic-command_set-2:first"); + fields.show(); + } + }; + selector.change(function () { + showFields(); + }); - $('#id_command_set-0-input').one('jsonschema-schemaloaded', function () { - showFields(); + $("#id_command_set-0-input").one("jsonschema-schemaloaded", function () { + showFields(); - initCommandDropdown($); - initCommandOverlay($); - initCommandWebSockets($, commandWebSocket); - }); + initCommandDropdown($); + initCommandOverlay($); + initCommandWebSockets($, commandWebSocket); + }); }); function initCommandDropdown($) { - // Add "Send Command" widget - $(function () { - let widgetElement, - objectTools = $('.object-tools'), - owCommandBtns = '', - schema = django._schemas[$('#id_command_set-0-input').data('schema-url')]; - - // Don't add the widget if object tools are not enabled - if (objectTools.length === 0) { - return; - } + // Add "Send Command" widget + $(function () { + let widgetElement, + objectTools = $(".object-tools"), + owCommandBtns = "", + schema = django._schemas[$("#id_command_set-0-input").data("schema-url")]; + + // Don't add the widget if object tools are not enabled + if (objectTools.length === 0) { + return; + } - Object.keys(schema).forEach(function (el) { - owCommandBtns += - ``; - }); - widgetElement = ` + Object.keys(schema).forEach(function (el) { + owCommandBtns += ``; + }); + widgetElement = `
  • - ${gettext('Send Command')} + ${gettext("Send Command")}
    ${owCommandBtns}
  • `; - $(widgetElement).insertBefore($('.object-tools li+li')[0]); - }); + $(widgetElement).insertBefore($(".object-tools li+li")[0]); + }); - // Only show "Send command" button when a device has credentials present - $(function () { - if ($('.dynamic-deviceconnection_set').length === 0) { - $('#send-command').parent().addClass('ow-hide'); - } - }); - - $('.object-tools').on('click', '#send-command', function (e) { - e.preventDefault(); - e.stopPropagation(); - $('.ow-device-command-option-container').toggleClass('ow-hide'); - }); - - $(document).click(function (e) { - e.stopPropagation(); - // Check if the clicked area is dropDown or not - if ($('.ow-device-command-option-container').has(e.target).length === 0) { - hideDropdown(); - } - }); + // Only show "Send command" button when a device has credentials present + $(function () { + if ($(".dynamic-deviceconnection_set").length === 0) { + $("#send-command").parent().addClass("ow-hide"); + } + }); + + $(".object-tools").on("click", "#send-command", function (e) { + e.preventDefault(); + e.stopPropagation(); + $(".ow-device-command-option-container").toggleClass("ow-hide"); + }); + + $(document).click(function (e) { + e.stopPropagation(); + // Check if the clicked area is dropDown or not + if ($(".ow-device-command-option-container").has(e.target).length === 0) { + hideDropdown(); + } + }); + + $(".object-tools").on( + "focusout", + ".ow-device-command-option-container", + function (e) { + // Hide dropdown while accessing dropdown through keyboard + e.stopPropagation(); + if ( + $(".ow-device-command-option-container").has(e.relatedTarget).length === + 0 + ) { + hideDropdown(); + } + }, + ); + + // Escape and enter key handlers for command dropdown + $(".object-tools").on("keyup", ".ow-command-btn", function (e) { + e.preventDefault(); + e.stopPropagation(); + // Close widget on escape key + if (e.keyCode == 27) { + hideDropdown(); + } - $('.object-tools').on('focusout', '.ow-device-command-option-container', function (e) { - // Hide dropdown while accessing dropdown through keyboard - e.stopPropagation(); - if ($('.ow-device-command-option-container').has(e.relatedTarget).length === 0) { - hideDropdown(); - } - }); + // Open command overlay for selected command option + if (e.keyCode == 13) { + $(e.target).click(); + } + }); + + $(".object-tools").on("keyup", "#send-command", function (e) { + e.preventDefault(); + e.stopPropagation(); + // Close widget on escape key + if (e.keyCode == 27) { + hideDropdown(); + } + }); + + $(".object-tools").on("click", ".ow-command-btn", function () { + let commandType = $(this).data("command"); + $("#id_command_set-0-type").val(commandType); + $("#id_command_set-0-type").trigger("change"); + + let element = $( + "#id_command_set-0-input_jsoneditor .errorlist li:first-child", + ), + schema = django._schemas[$("#id_command_set-0-input").data("schema-url")], + message = schema[commandType].message; + + // execute command if no input required + if (schema[commandType].type === "null") { + $("#ow-command-submit-btn").trigger("click"); + return; + } - // Escape and enter key handlers for command dropdown - $('.object-tools').on('keyup', '.ow-command-btn', function (e) { - e.preventDefault(); - e.stopPropagation(); - // Close widget on escape key - if (e.keyCode == 27) { - hideDropdown(); - } + // Set focus to input field inside overlay + $("#command_set-group").css("display", "block"); + $("html").css("overflow-y", "hidden"); + $( + "#id_command_set-0-input_jsoneditor input.vTextField:visible:first", + ).focus(); + $("#id_command_set-0-input_jsoneditor .form-row").removeClass("errors"); + + // Update custom validation message on command form + element.html(message); + }); + + function hideDropdown() { + $(".ow-device-command-option-container").addClass("ow-hide"); + } +} - // Open command overlay for selected command option - if (e.keyCode == 13) { - $(e.target).click(); - } - }); +function initCommandOverlay($) { + const commandConfirmationDialog = { + init: function () { + // Adds command dialog to the document + // Adds event handlers for utility functions + this.add(); - $('.object-tools').on('keyup', '#send-command', function (e) { + $("#device_form").on("click", "#ow-command-confirm-no", function (e) { e.preventDefault(); - e.stopPropagation(); - // Close widget on escape key - if (e.keyCode == 27) { - hideDropdown(); - } - }); - - $('.object-tools').on('click', '.ow-command-btn', function () { - let commandType = $(this).data('command'); - $('#id_command_set-0-type').val(commandType); - $('#id_command_set-0-type').trigger('change'); + closeOverlay(); + resetCommandForm(); + }); - let element = $('#id_command_set-0-input_jsoneditor .errorlist li:first-child'), - schema = django._schemas[$('#id_command_set-0-input').data('schema-url')], - message = schema[commandType].message; + $("#device_form").on( + "click", + "#ow-command-confirm-dialog-wrapper", + function (e) { + if ($("#ow-command-confirm-dialog").has(e.target).length === 0) { + closeOverlay(); + resetCommandForm(); + } + }, + ); - // execute command if no input required - if (schema[commandType].type === 'null') { - $('#ow-command-submit-btn').trigger('click'); - return; + // Hitting ESC key closes overlay + $("body").keyup(function (e) { + // Check if command overlay is visible or not + if ($("#ow-command-confirm-dialog-wrapper:visible").length !== 0) { + // Hide overlay on "Escape" key + if (e.keyCode === 27) { + closeOverlay(); + resetCommandForm(); + } } - - // Set focus to input field inside overlay - $('#command_set-group').css('display', 'block'); - $('html').css('overflow-y', 'hidden'); - $('#id_command_set-0-input_jsoneditor input.vTextField:visible:first').focus(); - $('#id_command_set-0-input_jsoneditor .form-row').removeClass('errors'); - - // Update custom validation message on command form - element.html(message); - }); - - function hideDropdown() { - $('.ow-device-command-option-container').addClass('ow-hide'); - } -} - -function initCommandOverlay($) { - const commandConfirmationDialog = { - init: function() { - // Adds command dialog to the document - // Adds event handlers for utility functions - this.add(); - - $('#device_form').on('click', '#ow-command-confirm-no', function (e) { - e.preventDefault(); - closeOverlay(); - resetCommandForm(); - }); - - $('#device_form').on('click', '#ow-command-confirm-dialog-wrapper', function (e) { - if ($('#ow-command-confirm-dialog').has(e.target).length === 0) { - closeOverlay(); - resetCommandForm(); - } - }); - - // Hitting ESC key closes overlay - $('body').keyup(function (e) { - // Check if command overlay is visible or not - if ($('#ow-command-confirm-dialog-wrapper:visible').length !== 0) { - // Hide overlay on "Escape" key - if (e.keyCode === 27) { - closeOverlay(); - resetCommandForm(); - } - } - }); - - }, - show: function() { - // Set the confirmation message from schema. - // If confirmation message is not defined, use a generic message. - let commandSchema = getCurrentCommandSchema(), - confirmation_message = gettext(commandSchema.confirmation_message); - if (confirmation_message === undefined){ - confirmation_message = interpolate( - gettext( - 'Are you sure you want to %s this device?' - ), - [gettext(commandSchema.title.toLowerCase())] - ); - } - $('#ow-command-confirm-text').html(confirmation_message); - - $('html').css('overflow-y', 'hidden'); - $('#ow-command-confirm-dialog-wrapper').removeClass('ow-hide'); - $('#ow-command-confirm-yes').focus(); - }, - hide: function() { - $('#ow-command-confirm-dialog-wrapper').addClass('ow-hide'); - }, - add: function() { - let confirmationElements = ` + }); + }, + show: function () { + // Set the confirmation message from schema. + // If confirmation message is not defined, use a generic message. + let commandSchema = getCurrentCommandSchema(), + confirmation_message = gettext(commandSchema.confirmation_message); + if (confirmation_message === undefined) { + confirmation_message = interpolate( + gettext("Are you sure you want to %s this device?"), + [gettext(commandSchema.title.toLowerCase())], + ); + } + $("#ow-command-confirm-text").html(confirmation_message); + + $("html").css("overflow-y", "hidden"); + $("#ow-command-confirm-dialog-wrapper").removeClass("ow-hide"); + $("#ow-command-confirm-yes").focus(); + }, + hide: function () { + $("#ow-command-confirm-dialog-wrapper").addClass("ow-hide"); + }, + add: function () { + let confirmationElements = `
    - - + +
    `; - $('#command_set-group').after(confirmationElements); - } - }; + $("#command_set-group").after(confirmationElements); + }, + }; - commandConfirmationDialog.init(); + commandConfirmationDialog.init(); - // Add close button on the overlay - $(function () { - let elements = ` + // Add close button on the overlay + $(function () { + let elements = `

    Please correct the errors below.

    An error encountered, please try sometime later.

    `; - $('#command_set-0 .form-row.field-input').prepend(elements); - }); + $("#command_set-0 .form-row.field-input").prepend(elements); + }); - // Add submit button on the overlay - $(function () { - let buttonElement = ` + // Add submit button on the overlay + $(function () { + let buttonElement = `
    `; - $('#command_set-0 > fieldset').append(buttonElement); - }); + $("#command_set-0 > fieldset").append(buttonElement); + }); - // Close overlay on clicking on blurred space - $('#command_set-group').click(function (e) { - if ($('#command_set-group > fieldset').has(e.target).length === 0) { - closeOverlay(); - } - }); + // Close overlay on clicking on blurred space + $("#command_set-group").click(function (e) { + if ($("#command_set-group > fieldset").has(e.target).length === 0) { + closeOverlay(); + } + }); + + // Close overlay on clicking close button + $("#command_set-group").on( + "click", + "#ow-command-overlay-close", + function (e) { + e.preventDefault(); + closeOverlay(); + resetCommandForm(); + }, + ); + + // Click handler for execute button + $("#command_set-group").on("click", "#ow-command-submit-btn", function (e) { + e.preventDefault(); + if (!checkInputIsValid()) { + return; + } + if (!isUserConfirmationRequired()) { + // If user confirmation is not required, then + // jump displaying confirmation dialog + $("#ow-command-confirm-yes").click(); + return; + } - // Close overlay on clicking close button - $('#command_set-group').on('click', '#ow-command-overlay-close', function (e) { - e.preventDefault(); - closeOverlay(); - resetCommandForm(); - }); + commandConfirmationDialog.show(); + }); - // Click handler for execute button - $('#command_set-group').on('click', '#ow-command-submit-btn', function (e) { - e.preventDefault(); - if (!checkInputIsValid()) { - return; - } - if (!isUserConfirmationRequired()){ - // If user confirmation is not required, then - // jump displaying confirmation dialog - $('#ow-command-confirm-yes').click(); - return; - } - - commandConfirmationDialog.show(); - }); + function checkInputIsValid() { + // Remove all error messages + $("#id_command_set-0-input_jsoneditor .errorlist").removeClass( + "ow-command-errorlist", + ); - function checkInputIsValid() { - // Remove all error messages - $('#id_command_set-0-input_jsoneditor .errorlist').removeClass('ow-command-errorlist'); - - let jsonEditor = django._jsonEditors['id_command_set-0-input_jsoneditor'], - errors = jsonEditor.validate(); - if (errors.length) { - // Show error only for input fields having error - errors.forEach(function (el) { - let inputName = el.path.replace('.', '[') + ']', - element = $(`#id_command_set-0-input_jsoneditor input[name="${inputName}"]`), - errorList = element.next(); - errorList.addClass('ow-command-errorlist'); - }); - $('#ow-command-overlay-validation-error').removeClass('ow-hide'); - return false; - } - // hide errors - $('#id_command_set-0-input_jsoneditor .errorlist').hide(); - $('#ow-command-overlay-validation-error').addClass('ow-hide'); - return true; + let jsonEditor = django._jsonEditors["id_command_set-0-input_jsoneditor"], + errors = jsonEditor.validate(); + if (errors.length) { + // Show error only for input fields having error + errors.forEach(function (el) { + let inputName = el.path.replace(".", "[") + "]", + element = $( + `#id_command_set-0-input_jsoneditor input[name="${inputName}"]`, + ), + errorList = element.next(); + errorList.addClass("ow-command-errorlist"); + }); + $("#ow-command-overlay-validation-error").removeClass("ow-hide"); + return false; } - - function isUserConfirmationRequired() { - // User confirmation is required when - // 1. There are no input fields for a command - // 2. When it is mentioned in the schema - - // Instead of counting input fields from DOM, count number of - // properties in schema to avoid miscalculation due to non input fields - // like drop down. - - let inputsLength, - commandSchema = getCurrentCommandSchema(), - commandInputs = commandSchema.properties, - commandRequiresConfirmation = commandSchema.requires_confirmation; - try { - inputsLength = Object.keys(commandInputs).length; - } - catch(e) { - inputsLength = 0; - } - - return inputsLength === 0 || commandRequiresConfirmation === true; + // hide errors + $("#id_command_set-0-input_jsoneditor .errorlist").hide(); + $("#ow-command-overlay-validation-error").addClass("ow-hide"); + return true; + } + + function isUserConfirmationRequired() { + // User confirmation is required when + // 1. There are no input fields for a command + // 2. When it is mentioned in the schema + + // Instead of counting input fields from DOM, count number of + // properties in schema to avoid miscalculation due to non input fields + // like drop down. + + let inputsLength, + commandSchema = getCurrentCommandSchema(), + commandInputs = commandSchema.properties, + commandRequiresConfirmation = commandSchema.requires_confirmation; + try { + inputsLength = Object.keys(commandInputs).length; + } catch (e) { + inputsLength = 0; } - // Click handler for command confirmation button - $('#device_form').on('click', '#ow-command-confirm-yes', function (e) { - e.preventDefault(); + return inputsLength === 0 || commandRequiresConfirmation === true; + } - // Hide confirmation dialog - $('#ow-command-confirm-dialog-wrapper').addClass('ow-hide'); - - let data = { - "type": $('#id_command_set-0-type').val(), - "input": $('#id_command_set-0-input').val() - }; - $.ajax({ - type: 'POST', - url: getCommandApiUrl(), - headers: { - 'X-CSRFToken': $('input[name="csrfmiddlewaretoken"]').val() - }, - dataType: 'json', - xhrFields: { - withCredentials: true - }, - data: data, - crossDomain: true, - beforeSend: function () { - $('#loading-overlay').show(); - }, - complete: function () { - if (!isRecentCommandsAbsent()) { - $('#loading-overlay').fadeOut(250); - } - }, - success: function (response) { - closeOverlay(); - updateRecentCommands($, response); - resetCommandForm(); - location.assign('#command_set-2-group'); - }, - error: function () { - $('#ow-command-overlay-validation-error').addClass('ow-hide'); - $('#ow-command-overlay-request-error').removeClass('ow-hide'); - } - }); - }); + // Click handler for command confirmation button + $("#device_form").on("click", "#ow-command-confirm-yes", function (e) { + e.preventDefault(); - // hitting enter in one of the input fields won't submit - // the device form but will execute the command - $('#command_set-group').on('keypress keyup keydown', '.jsoneditor-wrapper input', function (e) { - if (e.keyCode === 13) { - let execButton = $('#ow-command-submit-btn'); - // workaround to bug which prevents jsoneditor - // from getting the updated value - execButton.focus(); - $(e.target).focus(); - // submit only on keyup event - if (e.type === 'keyup') { - execButton.trigger('click'); - } - // avoid form submit - e.preventDefault(); - return false; - } - }); + // Hide confirmation dialog + $("#ow-command-confirm-dialog-wrapper").addClass("ow-hide"); - // Hitting ESC key closes overlay - $('body').keyup(function (e) { - // Check if command overlay is visible or not - if ($('#command_set-group:visible').length !== 0) { - // Hide overlay on "Escape" key - if (e.keyCode === 27) { - closeOverlay(); - } + let data = { + type: $("#id_command_set-0-type").val(), + input: $("#id_command_set-0-input").val(), + }; + $.ajax({ + type: "POST", + url: getCommandApiUrl(), + headers: { + "X-CSRFToken": $('input[name="csrfmiddlewaretoken"]').val(), + }, + dataType: "json", + xhrFields: { + withCredentials: true, + }, + data: data, + crossDomain: true, + beforeSend: function () { + $("#loading-overlay").show(); + }, + complete: function () { + if (!isRecentCommandsAbsent()) { + $("#loading-overlay").fadeOut(250); } + }, + success: function (response) { + closeOverlay(); + updateRecentCommands($, response); + resetCommandForm(); + location.assign("#command_set-2-group"); + }, + error: function () { + $("#ow-command-overlay-validation-error").addClass("ow-hide"); + $("#ow-command-overlay-request-error").removeClass("ow-hide"); + }, }); - - function closeOverlay() { - // The user may close the form without submitting it. - // The following line ensures that all input fields are cleared. - $('#command_set-0 .jsoneditor-wrapper input').val(''); - $('#command_set-group').css('display', 'none'); - $('.ow-command-overlay-errornote').addClass('ow-hide'); - commandConfirmationDialog.hide(); - $('html').css('overflow-y', ''); - // After closing the overlay, change focus to dropdown button - $('#send-command').focus(); + }); + + // hitting enter in one of the input fields won't submit + // the device form but will execute the command + $("#command_set-group").on( + "keypress keyup keydown", + ".jsoneditor-wrapper input", + function (e) { + if (e.keyCode === 13) { + let execButton = $("#ow-command-submit-btn"); + // workaround to bug which prevents jsoneditor + // from getting the updated value + execButton.focus(); + $(e.target).focus(); + // submit only on keyup event + if (e.type === "keyup") { + execButton.trigger("click"); + } + // avoid form submit + e.preventDefault(); + return false; + } + }, + ); + + // Hitting ESC key closes overlay + $("body").keyup(function (e) { + // Check if command overlay is visible or not + if ($("#command_set-group:visible").length !== 0) { + // Hide overlay on "Escape" key + if (e.keyCode === 27) { + closeOverlay(); + } } - - function resetCommandForm() { - $('#id_command_set-0-type').val(null); - $('#id_command_set-0-input').val('null'); + }); + + function closeOverlay() { + // The user may close the form without submitting it. + // The following line ensures that all input fields are cleared. + $("#command_set-0 .jsoneditor-wrapper input").val(""); + $("#command_set-group").css("display", "none"); + $(".ow-command-overlay-errornote").addClass("ow-hide"); + commandConfirmationDialog.hide(); + $("html").css("overflow-y", ""); + // After closing the overlay, change focus to dropdown button + $("#send-command").focus(); + } + + function resetCommandForm() { + $("#id_command_set-0-type").val(null); + $("#id_command_set-0-input").val("null"); + } + + function updateRecentCommands($, response) { + if (isRecentCommandsAbsent()) { + resetCommandForm(); + location.assign("#command_set-2-group"); + // If "Recent Commands" page is not available, hen wait for message + // from websocket or 4 seconds whichever is earlier + setTimeout(function () { + location.reload(); + }, 4000); + return; } - function updateRecentCommands($, response) { - if (isRecentCommandsAbsent()) { - resetCommandForm(); - location.assign('#command_set-2-group'); - // If "Recent Commands" page is not available, hen wait for message - // from websocket or 4 seconds whichever is earlier - setTimeout(function () { - location.reload(); - }, 4000); - return; + let firstElement = $( + "#command_set-2-group fieldset .dynamic-command_set-2:first", + ), + counter = firstElement.attr("id") + ? String(Number(firstElement.attr("id").split("-")[2]) - 1) + : "-1", + element = $(getElement(response, counter)); + $("#id_command_set-2-MAX_NUM_FORMS").after(element); + + function getElement(response, counter) { + let input, + sentOn = gettext("sent on"); + if (response.input !== null) { + if (response.input.command !== undefined) { + input = response.input.command; + } else { + input = response.input; } - - let firstElement = $('#command_set-2-group fieldset .dynamic-command_set-2:first'), - counter = (firstElement.attr('id')) ? String(Number(firstElement.attr('id').split('-')[2]) - 1) : '-1', - element = $(getElement(response, counter)); - $('#id_command_set-2-MAX_NUM_FORMS').after(element); - - function getElement(response, counter) { - let input, - sentOn = gettext('sent on'); - if (response.input !== null) { - if (response.input.command !== undefined) { - input = response.input.command; - } else { - input = response.input; - } - } else { - input = ''; - } - return `