From 3801ca13d9c46b79aa5630dbbcd46e49b1ab2a96 Mon Sep 17 00:00:00 2001 From: busya Date: Wed, 25 Feb 2026 20:46:00 +0300 Subject: [PATCH] feat(env): add global production context and safety indicators --- backend/src/api/routes/environments.py | 2 + backend/src/core/config_models.py | 23 ++-- .../lib/components/layout/TopNavbar.svelte | 46 ++++++- frontend/src/lib/i18n/locales/en.json | 15 ++- frontend/src/lib/i18n/locales/ru.json | 12 +- frontend/src/lib/stores/environmentContext.js | 116 ++++++++++++++++++ frontend/src/routes/+layout.svelte | 26 +++- frontend/src/routes/dashboards/+page.svelte | 65 +++++----- frontend/src/routes/git/+page.svelte | 22 +++- frontend/src/routes/settings/+page.svelte | 33 +++++ .../src/routes/storage/repos/+page.svelte | 22 +++- 11 files changed, 320 insertions(+), 62 deletions(-) create mode 100644 frontend/src/lib/stores/environmentContext.js diff --git a/backend/src/api/routes/environments.py b/backend/src/api/routes/environments.py index b09adfd..ecb1981 100644 --- a/backend/src/api/routes/environments.py +++ b/backend/src/api/routes/environments.py @@ -31,6 +31,7 @@ class EnvironmentResponse(BaseModel): id: str name: str url: str + is_production: bool = False backup_schedule: Optional[ScheduleSchema] = None # [/DEF:EnvironmentResponse:DataClass] @@ -63,6 +64,7 @@ async def get_environments( id=e.id, name=e.name, url=e.url, + is_production=getattr(e, "is_production", False), backup_schedule=ScheduleSchema( enabled=e.backup_schedule.enabled, cron_expression=e.backup_schedule.cron_expression diff --git a/backend/src/core/config_models.py b/backend/src/core/config_models.py index 5f98231..6d03616 100755 --- a/backend/src/core/config_models.py +++ b/backend/src/core/config_models.py @@ -24,17 +24,18 @@ class Schedule(BaseModel): # [DEF:Environment:DataClass] # @PURPOSE: Represents a Superset environment configuration. -class Environment(BaseModel): - id: str - name: str - url: str - username: str - password: str # Will be masked in UI - verify_ssl: bool = True - timeout: int = 30 - is_default: bool = False - backup_schedule: Schedule = Field(default_factory=Schedule) -# [/DEF:Environment:DataClass] +class Environment(BaseModel): + id: str + name: str + url: str + username: str + password: str # Will be masked in UI + verify_ssl: bool = True + timeout: int = 30 + is_default: bool = False + is_production: bool = False + backup_schedule: Schedule = Field(default_factory=Schedule) +# [/DEF:Environment:DataClass] # [DEF:LoggingConfig:DataClass] # @PURPOSE: Defines the configuration for the application's logging system. diff --git a/frontend/src/lib/components/layout/TopNavbar.svelte b/frontend/src/lib/components/layout/TopNavbar.svelte index 85f236d..d876823 100644 --- a/frontend/src/lib/components/layout/TopNavbar.svelte +++ b/frontend/src/lib/components/layout/TopNavbar.svelte @@ -16,7 +16,7 @@ * @UX_TEST: ActivityClick -> {click: activity button, expected: task drawer opens} */ - import { createEventDispatcher } from "svelte"; + import { createEventDispatcher, onMount } from "svelte"; import { page } from "$app/stores"; import { activityStore } from "$lib/stores/activity.js"; import { @@ -30,6 +30,12 @@ import { toggleAssistantChat } from "$lib/stores/assistantChat.js"; import Icon from "$lib/ui/Icon.svelte"; import LanguageSwitcher from "$lib/ui/LanguageSwitcher.svelte"; + import { + environmentContextStore, + initializeEnvironmentContext, + setSelectedEnvironment, + selectedEnvironmentStore, + } from "$lib/stores/environmentContext.js"; const dispatch = createEventDispatcher(); @@ -40,6 +46,10 @@ $: activeCount = $activityStore?.activeCount || 0; $: recentTasks = $activityStore?.recentTasks || []; $: user = $auth?.user || null; + $: globalEnvironments = $environmentContextStore?.environments || []; + $: globalSelectedEnvId = $environmentContextStore?.selectedEnvId || ""; + $: globalSelectedEnv = $selectedEnvironmentStore; + $: isProdContext = Boolean(globalSelectedEnv?.is_production); function toggleUserMenu(event) { event.stopPropagation(); @@ -94,6 +104,14 @@ event.stopPropagation(); toggleMobileSidebar(); } + + function handleGlobalEnvironmentChange(event) { + setSelectedEnvironment(event.target.value); + } + + onMount(async () => { + await initializeEnvironmentContext(); + });