diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c94dbedec..40a720396 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -74,7 +74,7 @@ jobs:
sudo apt -qq -y install sqlite3 gdal-bin libproj-dev \
libgeos-dev libspatialite-dev spatialite-bin \
libsqlite3-mod-spatialite
- sudo npm install -g jshint stylelint
+ sudo npm install -g prettier
pip install -U pip wheel setuptools
pip install -U -r requirements-test.txt
pip install -U -e .
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 = $(`
${templateConfig.name}${requiredString}${backendString} `),
- 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 = $(
+ ` ${templateConfig.name}${requiredString}${backendString} `,
+ ),
+ 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 +=
- `${schema[el].title} `;
- });
- widgetElement = `
+ Object.keys(schema).forEach(function (el) {
+ owCommandBtns += `${schema[el].title} `;
+ });
+ 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 = `
- ${gettext('Yes')}
- ${gettext('No')}
+ ${gettext(
+ "Yes",
+ )}
+ ${gettext(
+ "No",
+ )}
`;
- $('#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 = `
Execute Command
`;
- $('#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 `