Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 46 additions & 1 deletion openwisp_controller/config/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,15 @@
from django.contrib.admin import ModelAdmin


def is_recover_view(request):
resolver_match = getattr(request, "resolver_match", None)
return getattr(request, "_recover_view", False) or bool(
resolver_match
and resolver_match.url_name
and resolver_match.url_name.endswith("_recover")
)


class SystemDefinedVariableMixin(object):
def system_context(self, obj):
system_context = obj.get_system_context()
Expand Down Expand Up @@ -349,6 +358,12 @@ class Meta:

class ConfigForm(AlwaysHasChangedMixin, BaseForm):
_old_templates = None
readonly_backend = False

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.readonly_backend and "backend" in self.fields:
self.fields["backend"].disabled = True

def get_temp_model_instance(self, **options):
config_model = self.Meta.model
Expand Down Expand Up @@ -457,6 +472,17 @@ class ConfigInline(
verbose_name_plural = verbose_name
multitenant_shared_relations = ("templates",)

def get_formset(self, request, obj=None, **kwargs):
readonly_backend = bool(
obj and obj._has_config() and not is_recover_view(request)
)
kwargs["form"] = type(
"ConfigInlineForm",
(self.form,),
{"readonly_backend": readonly_backend},
)
return super().get_formset(request, obj, **kwargs)

def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.select_related(*self.change_select_related)
Expand All @@ -468,7 +494,14 @@ def _error_reason_field_conditional(self, obj, fields):
return fields

def get_readonly_fields(self, request, obj):
fields = super().get_readonly_fields(request, obj)
fields = list(super().get_readonly_fields(request, obj))
if (
obj
and obj._has_config()
and not is_recover_view(request)
and "backend" not in fields
):
fields.append("backend")
return self._error_reason_field_conditional(obj, fields)

def get_fields(self, request, obj):
Expand Down Expand Up @@ -1082,6 +1115,12 @@ class TemplateAdmin(MultitenantAdminMixin, BaseConfigAdmin, SystemDefinedVariabl
readonly_fields = ["system_context"]
autocomplete_fields = ["vpn"]

def get_readonly_fields(self, request, obj=None):
fields = list(super().get_readonly_fields(request, obj))
if obj and not is_recover_view(request) and "backend" not in fields:
fields.append("backend")
return fields

@admin.action(permissions=["add"])
def clone_selected_templates(self, request, queryset):
selectable_orgs = None
Expand Down Expand Up @@ -1261,6 +1300,12 @@ class VpnAdmin(
"modified",
]

def get_readonly_fields(self, request, obj=None):
fields = list(super().get_readonly_fields(request, obj))
if obj and not is_recover_view(request) and "backend" not in fields:
fields.append("backend")
return fields

class Media(BaseConfigAdmin):
js = list(BaseConfigAdmin.Media.js) + [f"{prefix}js/vpn.js"]

Expand Down
26 changes: 19 additions & 7 deletions openwisp_controller/config/static/config/js/relevant_templates.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ django.jQuery(function ($) {
return isDeviceGroup() ? "templates" : "config-0-templates";
},
isAddingNewObject = function () {
return isDeviceGroup()
? !$(".add-form").length
: $('input[name="config-0-id"]').val().length === 0;
if (isDeviceGroup()) {
return $(".add-form").length > 0;
}
var configIdField = $('input[name="config-0-id"]');
return !configIdField.length || configIdField.val().length === 0;
},
getTemplateOptionElement = function (
index,
Expand Down Expand Up @@ -123,11 +125,12 @@ django.jQuery(function ($) {
},
showRelevantTemplates = function () {
var orgID = $(orgFieldSelector).val(),
backend = isDeviceGroup() ? "" : $(backendFieldSelector).val(),
backend = isDeviceGroup() ? "" : $(backendFieldSelector).val() || "",
configID = $('input[name="config-0-id"]').val(),
currentSelection = getSelectedTemplates();

// Hide templates if no organization or backend is selected
if (!orgID || (!isDeviceGroup() && backend.length === 0)) {
if (!orgID || (!isDeviceGroup() && backend.length === 0 && !configID)) {
resetTemplateOptions();
updateTemplateHelpText();
return;
Expand Down Expand Up @@ -193,14 +196,23 @@ django.jQuery(function ($) {
initTemplateField();
var backendField = $(backendFieldSelector);
$(orgFieldSelector).change(function () {
// Only fetch templates when backend field is present
if ($(backendFieldSelector).length > 0 || isDeviceGroup()) {
// Fetch templates when backend can be determined either from
// an editable backend field or from an existing config object.
if (
$(backendFieldSelector).length > 0 ||
isDeviceGroup() ||
!isAddingNewObject()
) {
showRelevantTemplates();
}
});
// Change view: backendField is rendered on page load
if (backendField.length > 0) {
addChangeEventHandlerToBackendField();
} else if (!isDeviceGroup() && !isAddingNewObject()) {
// Change view for device config has readonly backend with no input element.
// In this case the backend is inferred server-side from config_id.
showRelevantTemplates();
} else if (isDeviceGroup()) {
// Initially request data to get templates
initTemplateField();
Expand Down
35 changes: 27 additions & 8 deletions openwisp_controller/config/static/config/js/vpn.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,29 @@ django.jQuery(function ($) {
return el.parents(".form-row").eq(0);
};

var getBackendValue = function () {
var backendInput = $("#id_backend");
if (backendInput.length && backendInput.val() !== undefined) {
return String(backendInput.val()).toLocaleLowerCase();
}
var readonlyBackendEl = $(".field-backend .readonly").first();
if (!readonlyBackendEl.length) {
return "";
}
var readonlyBackend = readonlyBackendEl.data("backend");
if (
readonlyBackend === undefined ||
readonlyBackend === null ||
String(readonlyBackend).trim() === ""
) {
readonlyBackend = readonlyBackendEl.text().trim();
}
return String(readonlyBackend).toLocaleLowerCase();
};

var toggleRelatedFields = function () {
// Show IP and Subnet field only for WireGuard backend
var backendValue =
$("#id_backend").val() === undefined
? ""
: $("#id_backend").val().toLocaleLowerCase().toLocaleLowerCase(),
var backendValue = getBackendValue(),
op;
if (backendValue.includes("wireguard") || backendValue.includes("vxlan")) {
op = "show";
Expand All @@ -62,10 +79,12 @@ django.jQuery(function ($) {
};

// clean config when VPN backend is changed
$("#id_backend").change(function () {
$("#id_config").val("{}");
toggleRelatedFields();
});
if ($("#id_backend").length) {
$("#id_backend").change(function () {
$("#id_config").val("{}");
toggleRelatedFields();
});
}

toggleRelatedFields();
});
56 changes: 56 additions & 0 deletions openwisp_controller/config/static/config/js/widget.js
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,57 @@
});
};

var getReadonlySchemaKey = function (schemas) {
var readonlyBackendEl = $(".field-backend .readonly").first();
if (!readonlyBackendEl.length) {
return false;
}
var backendValue = String(
readonlyBackendEl.data("backend") || readonlyBackendEl.attr("data-backend") || "",
).trim();
if (backendValue) {
if (schemas[backendValue] !== undefined) {
return backendValue;
}
var normalizedBackendValue = backendValue.toLocaleLowerCase(),
directSchemaKey = false;
$.each(Object.keys(schemas), function (index, key) {
if (String(key).toLocaleLowerCase() === normalizedBackendValue) {
directSchemaKey = key;
return false;
}
});
if (directSchemaKey) {
return directSchemaKey;
}
}
// Fallback for deployments that do not expose data-backend yet.
// Match readonly backend labels to schema keys using normalize().
var backendLabel = readonlyBackendEl.text().trim();
if (!backendLabel) {
return false;
}
var normalize = function (value) {
return String(value)
.toLocaleLowerCase()
.replace(/[^a-z0-9]/g, "");
};
var normalizedBackendLabel = normalize(backendLabel);
var schemaKey = false;
$.each(Object.keys(schemas), function (index, key) {
var normalizedBackendKey = normalize(key.split(".").pop());
if (
normalizedBackendLabel === normalizedBackendKey ||
normalizedBackendLabel.includes(normalizedBackendKey) ||
normalizedBackendKey.includes(normalizedBackendLabel)
) {
schemaKey = key;
return false;
}
});
return schemaKey;
};

var bindLoadUi = function () {
$('.jsoneditor-raw:not([name*="__prefix__"]):not(.manual)').each(function (i, el) {
// Add query parameters defined in the widget
Expand All @@ -439,7 +490,12 @@
schemaSelector = "#id_backend, #id_config-0-backend";
}
var selector = $(schemaSelector),
schemaKey = false;
if (selector.length) {
schemaKey = selector.val() || false;
} else {
schemaKey = getReadonlySchemaKey(schemas);
}
// load first time
loadUi(el, schemaKey, schemas, true);
// reload when selector is changed
Expand Down
10 changes: 10 additions & 0 deletions openwisp_controller/config/templates/admin/config/change_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,16 @@
{% block content %}
<div class="{% if not add %}change-form{% else %}add-form{% endif %}">
{{ block.super }}
{% if not add and original %}
{% firstof original.config.backend original.backend "" as readonly_backend %}
{% if readonly_backend %}
<script>
django
.jQuery(".field-backend .readonly")
.attr("data-backend", "{{ readonly_backend|escapejs }}");
</script>
{% endif %}
{% endif %}
<div class="djnjc-overlay">
<div class="inner"></div>
</div>
Expand Down
Loading
Loading