Improve settings hierarchy conciseness
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.urls import reverse
|
||||
from mixins.views import ObjectCreate, ObjectDelete, ObjectList, ObjectUpdate
|
||||
|
||||
from core.forms import AIForm
|
||||
@@ -11,7 +12,7 @@ log = logs.get_logger(__name__)
|
||||
class AIList(LoginRequiredMixin, ObjectList):
|
||||
list_template = "partials/ai-list.html"
|
||||
model = AI
|
||||
page_title = "AIs"
|
||||
page_title = None
|
||||
# page_subtitle = "Add times here in order to permit trading."
|
||||
|
||||
list_url_name = "ais"
|
||||
@@ -19,6 +20,28 @@ class AIList(LoginRequiredMixin, ObjectList):
|
||||
|
||||
submit_url_name = "ai_create"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
current_path = str(getattr(self.request, "path", "") or "")
|
||||
models_path = reverse("ai_models")
|
||||
traces_path = reverse("ai_execution_log")
|
||||
context["settings_nav"] = {
|
||||
"title": "AI",
|
||||
"tabs": [
|
||||
{
|
||||
"label": "Models",
|
||||
"href": models_path,
|
||||
"active": current_path == models_path,
|
||||
},
|
||||
{
|
||||
"label": "Traces",
|
||||
"href": traces_path,
|
||||
"active": current_path == traces_path,
|
||||
},
|
||||
],
|
||||
}
|
||||
return context
|
||||
|
||||
|
||||
class AICreate(LoginRequiredMixin, ObjectCreate):
|
||||
model = AI
|
||||
|
||||
@@ -482,12 +482,55 @@ class AIExecutionLogSettings(LoginRequiredMixin, View):
|
||||
"runs": runs,
|
||||
"operation_breakdown": operation_breakdown,
|
||||
"model_breakdown": model_breakdown,
|
||||
"settings_nav": {
|
||||
"title": "AI",
|
||||
"tabs": [
|
||||
{
|
||||
"label": "Models",
|
||||
"href": reverse("ai_models"),
|
||||
"active": str(getattr(request, "path", "") or "")
|
||||
== reverse("ai_models"),
|
||||
},
|
||||
{
|
||||
"label": "Traces",
|
||||
"href": reverse("ai_execution_log"),
|
||||
"active": str(getattr(request, "path", "") or "")
|
||||
== reverse("ai_execution_log"),
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
def get(self, request):
|
||||
return render(request, self.template_name, self._context(request))
|
||||
|
||||
|
||||
class AIExecutionRunDetailView(LoginRequiredMixin, View):
|
||||
template_name = "partials/ai-execution-run-detail.html"
|
||||
|
||||
def get(self, request, run_id):
|
||||
run = get_object_or_404(AIRunLog, id=run_id, user=request.user)
|
||||
return render(request, self.template_name, {"run": run})
|
||||
|
||||
|
||||
class AIExecutionRunDetailTabView(LoginRequiredMixin, View):
|
||||
template_name = "partials/ai-execution-run-detail-tab.html"
|
||||
|
||||
def get(self, request, run_id, tab_slug):
|
||||
run = get_object_or_404(AIRunLog, id=run_id, user=request.user)
|
||||
slug = str(tab_slug or "").strip().lower()
|
||||
if slug not in {"error"}:
|
||||
return JsonResponse({"ok": False, "error": "unknown_tab"}, status=404)
|
||||
return render(
|
||||
request,
|
||||
self.template_name,
|
||||
{
|
||||
"run": run,
|
||||
"tab_slug": slug,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class BusinessPlanEditor(LoginRequiredMixin, View):
|
||||
template_name = "pages/business-plan-editor.html"
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ from core.models import (
|
||||
PersonIdentifier,
|
||||
QueuedMessage,
|
||||
CommandSecurityPolicy,
|
||||
UserAccessibilitySettings,
|
||||
UserXmppOmemoState,
|
||||
UserXmppSecuritySettings,
|
||||
WorkspaceConversation,
|
||||
@@ -489,6 +490,7 @@ class SecurityPage(LoginRequiredMixin, View):
|
||||
"""Security settings page for OMEMO and command-scope policy controls."""
|
||||
|
||||
template_name = "pages/security.html"
|
||||
page_mode = "encryption"
|
||||
GLOBAL_SCOPE_KEY = "global.override"
|
||||
# Allowed Services list used by both Global Scope Override and local scopes.
|
||||
# Keep this in sync with the UI text on the Security page.
|
||||
@@ -520,6 +522,18 @@ class SecurityPage(LoginRequiredMixin, View):
|
||||
"other": "Other",
|
||||
}
|
||||
|
||||
def _show_encryption(self) -> bool:
|
||||
return str(getattr(self, "page_mode", "encryption")).strip().lower() in {
|
||||
"encryption",
|
||||
"all",
|
||||
}
|
||||
|
||||
def _show_permission(self) -> bool:
|
||||
return str(getattr(self, "page_mode", "encryption")).strip().lower() in {
|
||||
"permission",
|
||||
"all",
|
||||
}
|
||||
|
||||
def _security_settings(self, request):
|
||||
row, _ = UserXmppSecuritySettings.objects.get_or_create(user=request.user)
|
||||
return row
|
||||
@@ -616,6 +630,7 @@ class SecurityPage(LoginRequiredMixin, View):
|
||||
|
||||
def _scope_rows(self, request):
|
||||
global_overrides = self._global_override_payload(request)["values"]
|
||||
security_settings = self._security_settings(request)
|
||||
rows = {
|
||||
str(item.scope_key or "").strip().lower(): item
|
||||
for item in CommandSecurityPolicy.objects.filter(user=request.user).exclude(
|
||||
@@ -637,7 +652,10 @@ class SecurityPage(LoginRequiredMixin, View):
|
||||
if not channel_rules:
|
||||
channel_rules = [{"service": "xmpp", "pattern": ""}]
|
||||
enabled_locked = global_overrides["scope_enabled"] != "per_scope"
|
||||
require_omemo_locked = global_overrides["require_omemo"] != "per_scope"
|
||||
require_omemo_locked = (
|
||||
global_overrides["require_omemo"] != "per_scope"
|
||||
or bool(security_settings.require_omemo)
|
||||
)
|
||||
require_trusted_locked = (
|
||||
global_overrides["require_trusted_fingerprint"] != "per_scope"
|
||||
)
|
||||
@@ -661,6 +679,11 @@ class SecurityPage(LoginRequiredMixin, View):
|
||||
"require_omemo_locked": require_omemo_locked,
|
||||
"require_trusted_fingerprint_locked": require_trusted_locked,
|
||||
"lock_help": "Set this field to 'Per Scope' in Global Scope Override to edit it here.",
|
||||
"require_omemo_lock_help": (
|
||||
"Disable 'Require OMEMO encryption' in Encryption settings to edit this field."
|
||||
if bool(security_settings.require_omemo)
|
||||
else "Set this field to 'Per Scope' in Global Scope Override to edit it here."
|
||||
),
|
||||
"allowed_services": raw_allowed_services,
|
||||
"channel_rules": channel_rules,
|
||||
})
|
||||
@@ -668,6 +691,8 @@ class SecurityPage(LoginRequiredMixin, View):
|
||||
|
||||
def _scope_group_key(self, scope_key: str) -> str:
|
||||
key = str(scope_key or "").strip().lower()
|
||||
if key in {"tasks.commands", "gateway.tasks"}:
|
||||
return "tasks"
|
||||
if key in {"command.codex", "command.claude"}:
|
||||
return "agentic"
|
||||
if key.startswith("gateway."):
|
||||
@@ -712,12 +737,17 @@ class SecurityPage(LoginRequiredMixin, View):
|
||||
if "require_omemo" in request.POST:
|
||||
row.require_omemo = _to_bool(request.POST.get("require_omemo"), False)
|
||||
row.save(update_fields=["require_omemo", "updated_at"])
|
||||
redirect_to = HttpResponseRedirect(reverse("security_settings"))
|
||||
redirect_to = HttpResponseRedirect(request.path)
|
||||
scope_key = str(request.POST.get("scope_key") or "").strip().lower()
|
||||
if not self._show_permission():
|
||||
return redirect_to
|
||||
if scope_key == self.GLOBAL_SCOPE_KEY:
|
||||
global_row = self._global_override_payload(request)["row"]
|
||||
security_settings = self._security_settings(request)
|
||||
settings_payload = dict(global_row.settings or {})
|
||||
for field in self.GLOBAL_OVERRIDE_FIELDS:
|
||||
if field == "require_omemo" and bool(security_settings.require_omemo):
|
||||
continue
|
||||
settings_payload[field] = self._parse_override_value(
|
||||
request.POST.get(f"global_{field}")
|
||||
)
|
||||
@@ -742,6 +772,7 @@ class SecurityPage(LoginRequiredMixin, View):
|
||||
if str(request.POST.get("scope_change_mode") or "").strip() != "1":
|
||||
return redirect_to
|
||||
global_overrides = self._global_override_payload(request)["values"]
|
||||
security_settings = self._security_settings(request)
|
||||
allowed_services = [
|
||||
str(item or "").strip().lower()
|
||||
for item in request.POST.getlist("allowed_services")
|
||||
@@ -756,7 +787,10 @@ class SecurityPage(LoginRequiredMixin, View):
|
||||
policy.allowed_channels = allowed_channels
|
||||
if global_overrides["scope_enabled"] == "per_scope":
|
||||
policy.enabled = _to_bool(request.POST.get("policy_enabled"), True)
|
||||
if global_overrides["require_omemo"] == "per_scope":
|
||||
if (
|
||||
global_overrides["require_omemo"] == "per_scope"
|
||||
and not bool(security_settings.require_omemo)
|
||||
):
|
||||
policy.require_omemo = _to_bool(
|
||||
request.POST.get("policy_require_omemo"), False
|
||||
)
|
||||
@@ -778,35 +812,41 @@ class SecurityPage(LoginRequiredMixin, View):
|
||||
return redirect_to
|
||||
|
||||
def get(self, request):
|
||||
xmpp_state = transport.get_runtime_state("xmpp")
|
||||
try:
|
||||
omemo_row = UserXmppOmemoState.objects.get(user=request.user)
|
||||
except UserXmppOmemoState.DoesNotExist:
|
||||
omemo_row = None
|
||||
show_encryption = self._show_encryption()
|
||||
show_permission = self._show_permission()
|
||||
xmpp_state = transport.get_runtime_state("xmpp") if show_encryption else {}
|
||||
omemo_row = None
|
||||
if show_encryption:
|
||||
try:
|
||||
omemo_row = UserXmppOmemoState.objects.get(user=request.user)
|
||||
except UserXmppOmemoState.DoesNotExist:
|
||||
omemo_row = None
|
||||
security_settings = self._security_settings(request)
|
||||
sender_jid = _parse_xmpp_jid(getattr(omemo_row, "last_sender_jid", "") or "")
|
||||
omemo_plan = [
|
||||
{
|
||||
"label": "Component OMEMO active",
|
||||
"done": bool(xmpp_state.get("omemo_enabled")),
|
||||
"hint": "The gateway's OMEMO plugin must be loaded and initialised.",
|
||||
},
|
||||
{
|
||||
"label": "OMEMO observed from your client",
|
||||
"done": omemo_row is not None and omemo_row.status == "detected",
|
||||
"hint": "Send any message with OMEMO enabled in your XMPP client.",
|
||||
},
|
||||
{
|
||||
"label": "Client key on file",
|
||||
"done": bool(getattr(omemo_row, "latest_client_key", "")),
|
||||
"hint": "A device key (sid/rid) must be recorded from your client.",
|
||||
},
|
||||
{
|
||||
"label": "Encryption required",
|
||||
"done": security_settings.require_omemo,
|
||||
"hint": "Enable 'Require OMEMO encryption' in Security Policy above to enforce this policy.",
|
||||
},
|
||||
]
|
||||
omemo_plan = []
|
||||
if show_encryption:
|
||||
omemo_plan = [
|
||||
{
|
||||
"label": "Component OMEMO active",
|
||||
"done": bool(xmpp_state.get("omemo_enabled")),
|
||||
"hint": "The gateway's OMEMO plugin must be loaded and initialised.",
|
||||
},
|
||||
{
|
||||
"label": "OMEMO observed from your client",
|
||||
"done": omemo_row is not None and omemo_row.status == "detected",
|
||||
"hint": "Send any message with OMEMO enabled in your XMPP client.",
|
||||
},
|
||||
{
|
||||
"label": "Client key on file",
|
||||
"done": bool(getattr(omemo_row, "latest_client_key", "")),
|
||||
"hint": "A device key (sid/rid) must be recorded from your client.",
|
||||
},
|
||||
{
|
||||
"label": "Encryption required",
|
||||
"done": security_settings.require_omemo,
|
||||
"hint": "Enable 'Require OMEMO encryption' in Security Policy above to enforce this policy.",
|
||||
},
|
||||
]
|
||||
return render(request, self.template_name, {
|
||||
"xmpp_state": xmpp_state,
|
||||
"omemo_row": omemo_row,
|
||||
@@ -817,4 +857,62 @@ class SecurityPage(LoginRequiredMixin, View):
|
||||
"policy_groups": self._grouped_scope_rows(request),
|
||||
"sender_jid": sender_jid,
|
||||
"omemo_plan": omemo_plan,
|
||||
"show_encryption": show_encryption,
|
||||
"show_permission": show_permission,
|
||||
})
|
||||
|
||||
|
||||
class AccessibilitySettings(LoginRequiredMixin, View):
|
||||
template_name = "pages/accessibility-settings.html"
|
||||
|
||||
def _row(self, request):
|
||||
row, _ = UserAccessibilitySettings.objects.get_or_create(user=request.user)
|
||||
return row
|
||||
|
||||
def get(self, request):
|
||||
return render(request, self.template_name, {
|
||||
"accessibility_settings": self._row(request),
|
||||
})
|
||||
|
||||
def post(self, request):
|
||||
row = self._row(request)
|
||||
row.disable_animations = _to_bool(request.POST.get("disable_animations"), False)
|
||||
row.save(update_fields=["disable_animations", "updated_at"])
|
||||
return HttpResponseRedirect(reverse("accessibility_settings"))
|
||||
|
||||
|
||||
class _SettingsCategoryPage(LoginRequiredMixin, View):
|
||||
template_name = "pages/settings-category.html"
|
||||
category_key = "general"
|
||||
category_title = "General"
|
||||
category_description = ""
|
||||
tabs = ()
|
||||
|
||||
def _tab_rows(self):
|
||||
current_path = str(getattr(self.request, "path", "") or "")
|
||||
rows = []
|
||||
for label, href in self.tabs:
|
||||
rows.append({
|
||||
"label": label,
|
||||
"href": href,
|
||||
"active": current_path == href,
|
||||
})
|
||||
return rows
|
||||
|
||||
def get(self, request):
|
||||
return render(request, self.template_name, {
|
||||
"category_key": self.category_key,
|
||||
"category_title": self.category_title,
|
||||
"category_description": self.category_description,
|
||||
"category_tabs": self._tab_rows(),
|
||||
})
|
||||
|
||||
|
||||
class AISettingsPage(LoginRequiredMixin, View):
|
||||
def get(self, request):
|
||||
return HttpResponseRedirect(reverse("ai_models"))
|
||||
|
||||
|
||||
class ModulesSettingsPage(_SettingsCategoryPage):
|
||||
def get(self, request):
|
||||
return HttpResponseRedirect(reverse("command_routing"))
|
||||
|
||||
Reference in New Issue
Block a user