flock-core 0.4.0b35__py3-none-any.whl → 0.4.0b37__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of flock-core might be problematic. Click here for more details.
- flock/__init__.py +34 -11
- flock/cli/loaded_flock_cli.py +38 -18
- flock/core/flock.py +48 -3
- flock/themes/alabaster.toml +43 -43
- flock/themes/guezwhoz.toml +43 -43
- flock/themes/wildcherry.toml +43 -43
- flock/themes/wombat.toml +43 -43
- flock/themes/zenburn.toml +43 -43
- flock/webapp/app/config.py +80 -2
- flock/webapp/app/main.py +506 -3
- flock/webapp/app/templates/theme_mapper.html +326 -0
- flock/webapp/app/theme_mapper.py +812 -0
- flock/webapp/run.py +116 -14
- flock/webapp/static/css/custom.css +168 -83
- flock/webapp/templates/base.html +14 -7
- flock/webapp/templates/partials/_agent_detail_form.html +4 -3
- flock/webapp/templates/partials/_agent_list.html +1 -6
- flock/webapp/templates/partials/_agent_manager_view.html +52 -14
- flock/webapp/templates/partials/_agent_manager_view_old.html +19 -0
- flock/webapp/templates/partials/_create_flock_form.html +1 -1
- flock/webapp/templates/partials/_dashboard_flock_properties_preview.html +1 -1
- flock/webapp/templates/partials/_env_vars_table.html +25 -0
- flock/webapp/templates/partials/_execution_form.html +1 -1
- flock/webapp/templates/partials/_execution_view_container.html +13 -12
- flock/webapp/templates/partials/_flock_properties_form.html +2 -1
- flock/webapp/templates/partials/_header_flock_status.html +5 -0
- flock/webapp/templates/partials/_load_manager_view.html +50 -0
- flock/webapp/templates/partials/_settings_env_content.html +10 -0
- flock/webapp/templates/partials/_settings_theme_content.html +15 -0
- flock/webapp/templates/partials/_settings_view.html +36 -0
- flock/webapp/templates/partials/_sidebar.html +13 -6
- flock/webapp/templates/partials/_structured_data_view.html +4 -4
- flock/webapp/templates/partials/_theme_preview.html +23 -0
- {flock_core-0.4.0b35.dist-info → flock_core-0.4.0b37.dist-info}/METADATA +1 -1
- {flock_core-0.4.0b35.dist-info → flock_core-0.4.0b37.dist-info}/RECORD +38 -29
- flock/webapp/templates/partials/_load_manage_view.html +0 -88
- {flock_core-0.4.0b35.dist-info → flock_core-0.4.0b37.dist-info}/WHEEL +0 -0
- {flock_core-0.4.0b35.dist-info → flock_core-0.4.0b37.dist-info}/entry_points.txt +0 -0
- {flock_core-0.4.0b35.dist-info → flock_core-0.4.0b37.dist-info}/licenses/LICENSE +0 -0
flock/webapp/app/main.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# ... (keep existing imports and app setup) ...
|
|
2
2
|
import json
|
|
3
|
+
import os # Needed for environment variable helpers
|
|
3
4
|
import shutil
|
|
5
|
+
import sys # For path
|
|
4
6
|
import urllib.parse
|
|
5
7
|
from pathlib import Path
|
|
6
8
|
|
|
@@ -15,7 +17,15 @@ from flock.webapp.app.api import (
|
|
|
15
17
|
flock_management,
|
|
16
18
|
registry_viewer,
|
|
17
19
|
)
|
|
18
|
-
|
|
20
|
+
|
|
21
|
+
# Import config functions
|
|
22
|
+
from flock.webapp.app.config import (
|
|
23
|
+
DEFAULT_THEME_NAME, # Import default for fallback
|
|
24
|
+
FLOCK_FILES_DIR,
|
|
25
|
+
THEMES_DIR, # Import THEMES_DIR from config
|
|
26
|
+
get_current_theme_name,
|
|
27
|
+
# set_current_theme_name, # Not directly used in main.py, but available
|
|
28
|
+
)
|
|
19
29
|
from flock.webapp.app.services.flock_service import (
|
|
20
30
|
clear_current_flock,
|
|
21
31
|
create_new_flock_service,
|
|
@@ -25,6 +35,100 @@ from flock.webapp.app.services.flock_service import (
|
|
|
25
35
|
get_flock_preview_service,
|
|
26
36
|
load_flock_from_file_service,
|
|
27
37
|
)
|
|
38
|
+
from flock.webapp.app.theme_mapper import alacritty_to_pico
|
|
39
|
+
|
|
40
|
+
# Helper for theme loading
|
|
41
|
+
|
|
42
|
+
# Find the 'src/flock' directory - This can be removed if THEMES_DIR from config is sufficient
|
|
43
|
+
# flock_base_dir = (
|
|
44
|
+
# Path(__file__).resolve().parent.parent.parent
|
|
45
|
+
# ) # src/flock/webapp/app -> src/flock
|
|
46
|
+
|
|
47
|
+
# Calculate themes directory relative to the flock base dir - This can be removed
|
|
48
|
+
# themes_dir = flock_base_dir / "themes"
|
|
49
|
+
|
|
50
|
+
# Ensure the parent ('src') is in the path for core imports
|
|
51
|
+
# This path manipulation might still be needed if core imports are relative in a specific way
|
|
52
|
+
flock_webapp_dir = Path(__file__).resolve().parent.parent # src/flock/webapp/
|
|
53
|
+
flock_base_dir = flock_webapp_dir.parent # src/flock/
|
|
54
|
+
src_dir = flock_base_dir.parent # src/
|
|
55
|
+
if str(src_dir) not in sys.path:
|
|
56
|
+
sys.path.insert(0, str(src_dir))
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
from flock.core.logging.formatters.themed_formatter import (
|
|
60
|
+
load_theme_from_file,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
THEME_LOADER_AVAILABLE = True
|
|
64
|
+
# themes_dir is now imported from config
|
|
65
|
+
except ImportError:
|
|
66
|
+
print(
|
|
67
|
+
"Warning: Could not import flock.core theme loading utilities.",
|
|
68
|
+
file=sys.stderr,
|
|
69
|
+
)
|
|
70
|
+
THEME_LOADER_AVAILABLE = False
|
|
71
|
+
# THEMES_DIR will be None if not imported, or its value from config
|
|
72
|
+
|
|
73
|
+
# --- Lightweight .env helpers (self-contained, no external deps) ---
|
|
74
|
+
ENV_FILE = ".env"
|
|
75
|
+
SHOW_SECRETS_KEY = "SHOW_SECRETS"
|
|
76
|
+
|
|
77
|
+
def load_env_file() -> dict[str, str]:
|
|
78
|
+
env_vars: dict[str, str] = {}
|
|
79
|
+
if not os.path.exists(ENV_FILE):
|
|
80
|
+
return env_vars
|
|
81
|
+
with open(ENV_FILE) as f:
|
|
82
|
+
lines = f.readlines()
|
|
83
|
+
for line in lines:
|
|
84
|
+
line = line.strip()
|
|
85
|
+
if not line:
|
|
86
|
+
env_vars[""] = ""
|
|
87
|
+
continue
|
|
88
|
+
if line.startswith("#"):
|
|
89
|
+
env_vars[line] = ""
|
|
90
|
+
continue
|
|
91
|
+
if "=" in line:
|
|
92
|
+
k, v = line.split("=", 1)
|
|
93
|
+
env_vars[k] = v
|
|
94
|
+
else:
|
|
95
|
+
env_vars[line] = ""
|
|
96
|
+
return env_vars
|
|
97
|
+
|
|
98
|
+
def save_env_file(env_vars: dict[str, str]):
|
|
99
|
+
try:
|
|
100
|
+
with open(ENV_FILE, "w") as f:
|
|
101
|
+
for k, v in env_vars.items():
|
|
102
|
+
if k.startswith("#"):
|
|
103
|
+
f.write(f"{k}\n")
|
|
104
|
+
elif not k:
|
|
105
|
+
f.write("\n")
|
|
106
|
+
else:
|
|
107
|
+
f.write(f"{k}={v}\n")
|
|
108
|
+
except Exception as e:
|
|
109
|
+
print(f"[Settings] Failed to save .env: {e}")
|
|
110
|
+
|
|
111
|
+
def is_sensitive(key: str) -> bool:
|
|
112
|
+
patterns = ["key", "token", "secret", "password", "api", "pat"]
|
|
113
|
+
low = key.lower()
|
|
114
|
+
return any(p in low for p in patterns)
|
|
115
|
+
|
|
116
|
+
def mask_sensitive_value(value: str) -> str:
|
|
117
|
+
if not value:
|
|
118
|
+
return value
|
|
119
|
+
if len(value) <= 4:
|
|
120
|
+
return "••••"
|
|
121
|
+
return value[:2] + "•" * (len(value) - 4) + value[-2:]
|
|
122
|
+
|
|
123
|
+
def get_show_secrets_setting(env_vars: dict[str, str]) -> bool:
|
|
124
|
+
return env_vars.get(SHOW_SECRETS_KEY, "false").lower() == "true"
|
|
125
|
+
|
|
126
|
+
def set_show_secrets_setting(show: bool):
|
|
127
|
+
env_vars = load_env_file()
|
|
128
|
+
env_vars[SHOW_SECRETS_KEY] = str(show)
|
|
129
|
+
save_env_file(env_vars)
|
|
130
|
+
|
|
131
|
+
# -------------------------------------------------------------------
|
|
28
132
|
|
|
29
133
|
app = FastAPI(title="Flock UI")
|
|
30
134
|
|
|
@@ -49,12 +153,163 @@ app.include_router(
|
|
|
49
153
|
)
|
|
50
154
|
|
|
51
155
|
|
|
156
|
+
def generate_theme_css(theme_name: str | None) -> str:
|
|
157
|
+
"""Loads a theme TOML and generates CSS variable overrides."""
|
|
158
|
+
if not THEME_LOADER_AVAILABLE or THEMES_DIR is None: # Use imported THEMES_DIR
|
|
159
|
+
return "" # Return empty if theme loading isn't possible
|
|
160
|
+
|
|
161
|
+
active_theme_name = theme_name or DEFAULT_THEME_NAME
|
|
162
|
+
theme_filename = f"{active_theme_name}.toml"
|
|
163
|
+
theme_path = THEMES_DIR / theme_filename # Use imported THEMES_DIR
|
|
164
|
+
|
|
165
|
+
if not theme_path.exists():
|
|
166
|
+
print(
|
|
167
|
+
f"Warning: Theme file not found: {theme_path}. Using default theme.",
|
|
168
|
+
file=sys.stderr,
|
|
169
|
+
)
|
|
170
|
+
# Optionally load the default theme file if the requested one isn't found
|
|
171
|
+
theme_filename = f"{DEFAULT_THEME_NAME}.toml"
|
|
172
|
+
theme_path = THEMES_DIR / theme_filename
|
|
173
|
+
if not theme_path.exists():
|
|
174
|
+
print(
|
|
175
|
+
f"Warning: Default theme file not found: {theme_path}. No theme CSS generated.",
|
|
176
|
+
file=sys.stderr,
|
|
177
|
+
)
|
|
178
|
+
return "" # Return empty if even default isn't found
|
|
179
|
+
active_theme_name = (
|
|
180
|
+
DEFAULT_THEME_NAME # Update active name if defaulted
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
try:
|
|
184
|
+
theme_dict = load_theme_from_file(str(theme_path))
|
|
185
|
+
except Exception as e:
|
|
186
|
+
print(f"Error loading theme file {theme_path}: {e}", file=sys.stderr)
|
|
187
|
+
return "" # Return empty on error
|
|
188
|
+
|
|
189
|
+
# --- Define TOML Color -> CSS Variable Mapping ---
|
|
190
|
+
# This mapping is crucial and may need adjustment based on theme intent & Pico usage
|
|
191
|
+
css_vars = {}
|
|
192
|
+
try:
|
|
193
|
+
# Basic Colors
|
|
194
|
+
# Base colors
|
|
195
|
+
css_vars["--pico-background-color"] = theme_dict["colors"]["primary"].get("background") # Main background
|
|
196
|
+
css_vars["--pico-color"] = theme_dict["colors"]["primary"].get("foreground") # Main text
|
|
197
|
+
|
|
198
|
+
# Headings
|
|
199
|
+
css_vars["--pico-h1-color"] = theme_dict["colors"]["selection"].get("text") # Primary heading
|
|
200
|
+
css_vars["--pico-h2-color"] = theme_dict["colors"]["selection"].get("text") # Secondary heading
|
|
201
|
+
css_vars["--pico-h3-color"] = theme_dict["colors"]["primary"].get("foreground") # Body heading
|
|
202
|
+
css_vars["--pico-muted-color"] = theme_dict["colors"]["selection"].get("text") # Muted/subtext
|
|
203
|
+
css_vars["--pico-primary-inverse"] = theme_dict["colors"]["cursor"].get("text") # Contrast on primary
|
|
204
|
+
css_vars["--pico-contrast"] = theme_dict["colors"]["primary"].get("background") # Contrast text on dark
|
|
205
|
+
css_vars["--pico-contrast-inverse"] = theme_dict["colors"]["primary"].get("foreground") # Contrast text on dark
|
|
206
|
+
|
|
207
|
+
# Primary interaction
|
|
208
|
+
css_vars["--pico-primary"] = theme_dict["colors"]["normal"].get("blue")
|
|
209
|
+
css_vars["--pico-primary-hover"] = theme_dict["colors"]["bright"].get("blue")
|
|
210
|
+
css_vars["--pico-primary-focus"] = f"rgba({theme_dict['colors']['bright'].get('blue')}, 0.25)"
|
|
211
|
+
css_vars["--pico-primary-active"] = theme_dict["colors"]["normal"].get("blue")
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
# Secondary interaction
|
|
216
|
+
css_vars["--pico-secondary"] = theme_dict["colors"]["normal"].get("magenta")
|
|
217
|
+
css_vars["--pico-secondary-hover"] = theme_dict["colors"]["bright"].get("magenta")
|
|
218
|
+
css_vars["--pico-secondary-focus"] = f"rgba({theme_dict['colors']['bright'].get('magenta')}, 0.25)"
|
|
219
|
+
css_vars["--pico-secondary-active"] = theme_dict["colors"]["normal"].get("magenta")
|
|
220
|
+
|
|
221
|
+
# Cards and containers
|
|
222
|
+
css_vars["--pico-card-background-color"] = theme_dict["colors"]["primary"].get("background")
|
|
223
|
+
css_vars["--pico-card-border-color"] = theme_dict["colors"]["bright"].get("black") # Mid-tone, visible on bright bg
|
|
224
|
+
css_vars["--pico-card-sectioning-background-color"] = theme_dict["colors"]["selection"].get("background") # Subtle contrast
|
|
225
|
+
css_vars["--pico-border-color"] = theme_dict["colors"]["bright"].get("black")
|
|
226
|
+
css_vars["--pico-muted-border-color"] = theme_dict["colors"]["normal"].get("black") # More subtle than main border
|
|
227
|
+
|
|
228
|
+
# Forms
|
|
229
|
+
css_vars["--pico-form-element-background-color"] = theme_dict["colors"]["primary"].get("background")
|
|
230
|
+
css_vars["--pico-form-element-border-color"] = theme_dict["colors"]["bright"].get("black")
|
|
231
|
+
css_vars["--pico-form-element-color"] = theme_dict["colors"]["primary"].get("foreground")
|
|
232
|
+
css_vars["--pico-form-element-focus-color"] = theme_dict["colors"]["bright"].get("blue")
|
|
233
|
+
css_vars["--pico-form-element-placeholder-color"] = theme_dict["colors"]["bright"].get("black")
|
|
234
|
+
css_vars["--pico-form-element-active-border-color"] = theme_dict["colors"]["bright"].get("blue")
|
|
235
|
+
css_vars["--pico-form-element-active-background-color"] = theme_dict["colors"]["selection"].get("background")
|
|
236
|
+
css_vars["--pico-form-element-disabled-background-color"] = theme_dict["colors"]["normal"].get("black")
|
|
237
|
+
css_vars["--pico-form-element-disabled-border-color"] = theme_dict["colors"]["bright"].get("black")
|
|
238
|
+
css_vars["--pico-form-element-invalid-border-color"] = theme_dict["colors"]["normal"].get("red")
|
|
239
|
+
css_vars["--pico-form-element-invalid-focus-color"] = theme_dict["colors"]["bright"].get("red")
|
|
240
|
+
|
|
241
|
+
# Buttons
|
|
242
|
+
css_vars["--pico-button-base-background-color"] = theme_dict["colors"]["primary"].get("background")
|
|
243
|
+
css_vars["--pico-button-base-color"] = theme_dict["colors"]["primary"].get("foreground")
|
|
244
|
+
css_vars["--pico-button-hover-background-color"] = theme_dict["colors"]["selection"].get("background")
|
|
245
|
+
css_vars["--pico-button-hover-color"] = theme_dict["colors"]["selection"].get("text")
|
|
246
|
+
|
|
247
|
+
# Code blocks
|
|
248
|
+
css_vars["--pico-code-background-color"] = theme_dict["colors"]["cursor"].get("text") # Background behind code
|
|
249
|
+
css_vars["--pico-code-color"] = theme_dict["colors"]["primary"].get("foreground") # Code text
|
|
250
|
+
css_vars["--pico-code-kbd-background-color"] = theme_dict["colors"]["selection"].get("background")
|
|
251
|
+
css_vars["--pico-code-kbd-color"] = theme_dict["colors"]["selection"].get("text")
|
|
252
|
+
css_vars["--pico-code-tag-color"] = theme_dict["colors"]["normal"].get("blue") # Tag elements
|
|
253
|
+
css_vars["--pico-code-property-color"] = theme_dict["colors"]["normal"].get("green") # CSS property names
|
|
254
|
+
css_vars["--pico-code-value-color"] = theme_dict["colors"]["normal"].get("red") # Values and literals
|
|
255
|
+
css_vars["--pico-code-comment-color"] = theme_dict["colors"]["bright"].get("black") # Dim comment color
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
# Semantic markup
|
|
259
|
+
css_vars["--pico-mark-background-color"] = theme_dict["colors"]["normal"].get("yellow") + "33"
|
|
260
|
+
css_vars["--pico-mark-color"] = theme_dict["colors"]["primary"].get("foreground")
|
|
261
|
+
css_vars["--pico-ins-color"] = theme_dict["colors"]["normal"].get("green")
|
|
262
|
+
css_vars["--pico-del-color"] = theme_dict["colors"]["normal"].get("red")
|
|
263
|
+
# Deleted content - red
|
|
264
|
+
# Custom flock vars (mapped previously, ensure they are kept)
|
|
265
|
+
css_vars["--flock-sidebar-background"] = theme_dict["colors"]["primary"].get("background")# css_vars["--pico-card-background-color"] # Example: Link sidebar to card background
|
|
266
|
+
css_vars["--flock-header-background"] = theme_dict["colors"]["selection"].get("background") # Example: Link header to card background
|
|
267
|
+
css_vars["--flock-error-color"] = theme_dict["colors"]["normal"].get("red", "#dc3545")
|
|
268
|
+
css_vars["--flock-success-color"] = theme_dict["colors"]["normal"].get("green", "#28a745")
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
#css_vars.update(pico_vars)
|
|
273
|
+
|
|
274
|
+
except KeyError as e:
|
|
275
|
+
print(
|
|
276
|
+
f"Warning: Missing expected key in theme '{active_theme_name}': {e}. CSS may be incomplete.",
|
|
277
|
+
file=sys.stderr,
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
if not css_vars:
|
|
281
|
+
return "" # Return empty if no variables were mapped
|
|
282
|
+
|
|
283
|
+
pico_vars = alacritty_to_pico(theme_dict)
|
|
284
|
+
css_rules = [f" {name}: {value};" for name, value in pico_vars.items()]
|
|
285
|
+
|
|
286
|
+
# Apply overrides within the currently active theme selector for better specificity
|
|
287
|
+
# We could get the theme name passed in, or maybe check the <html> tag attribute?
|
|
288
|
+
# For simplicity, let's assume we want to override Pico's dark theme vars when a theme is loaded.
|
|
289
|
+
# A better approach might involve removing data-theme="dark" and applying theme to :root
|
|
290
|
+
# or having specific data-theme selectors for each flock theme.
|
|
291
|
+
# Let's try applying to [data-theme="dark"] first.
|
|
292
|
+
selector = '[data-theme="dark"]'
|
|
293
|
+
css_string = ":root {\n" + "\n".join(css_rules) + "\n}"
|
|
294
|
+
|
|
295
|
+
print(
|
|
296
|
+
f"--- Generated CSS for theme '{active_theme_name}' ---"
|
|
297
|
+
) # Debugging print
|
|
298
|
+
print(css_string) # Debugging print
|
|
299
|
+
print(
|
|
300
|
+
"----------------------------------------------------"
|
|
301
|
+
) # Debugging print
|
|
302
|
+
return css_string
|
|
303
|
+
|
|
304
|
+
|
|
52
305
|
def get_base_context(
|
|
53
306
|
request: Request,
|
|
54
307
|
error: str = None,
|
|
55
308
|
success: str = None,
|
|
56
309
|
ui_mode: str = "standalone",
|
|
57
310
|
) -> dict:
|
|
311
|
+
theme_name = get_current_theme_name() # Get theme from config
|
|
312
|
+
theme_css = generate_theme_css(theme_name)
|
|
58
313
|
return {
|
|
59
314
|
"request": request,
|
|
60
315
|
"current_flock": get_current_flock_instance(),
|
|
@@ -62,6 +317,8 @@ def get_base_context(
|
|
|
62
317
|
"error_message": error,
|
|
63
318
|
"success_message": success,
|
|
64
319
|
"ui_mode": ui_mode,
|
|
320
|
+
"theme_css": theme_css, # Add generated CSS to context
|
|
321
|
+
"active_theme_name": theme_name, # Added active theme name
|
|
65
322
|
}
|
|
66
323
|
|
|
67
324
|
|
|
@@ -226,6 +483,21 @@ async def htmx_get_sidebar(
|
|
|
226
483
|
)
|
|
227
484
|
|
|
228
485
|
|
|
486
|
+
@app.get("/ui/htmx/header-flock-status", response_class=HTMLResponse)
|
|
487
|
+
async def htmx_get_header_flock_status(
|
|
488
|
+
request: Request, ui_mode: str = Query("standalone")
|
|
489
|
+
):
|
|
490
|
+
# ui_mode isn't strictly needed for this partial's content, but good to accept if passed by hx-get
|
|
491
|
+
return templates.TemplateResponse(
|
|
492
|
+
"partials/_header_flock_status.html",
|
|
493
|
+
{
|
|
494
|
+
"request": request,
|
|
495
|
+
"current_flock": get_current_flock_instance(),
|
|
496
|
+
"current_filename": get_current_flock_filename(),
|
|
497
|
+
},
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
|
|
229
501
|
@app.get("/ui/htmx/load-flock-view", response_class=HTMLResponse)
|
|
230
502
|
async def htmx_get_load_flock_view(
|
|
231
503
|
request: Request,
|
|
@@ -237,7 +509,7 @@ async def htmx_get_load_flock_view(
|
|
|
237
509
|
# This view is part of the "standalone" functionality.
|
|
238
510
|
# If somehow accessed in scoped mode, it might be confusing, but let it render.
|
|
239
511
|
return templates.TemplateResponse(
|
|
240
|
-
"partials/
|
|
512
|
+
"partials/_load_manager_view.html",
|
|
241
513
|
{
|
|
242
514
|
"request": request,
|
|
243
515
|
"error_message": error,
|
|
@@ -459,7 +731,7 @@ async def ui_load_flock_by_name_action(
|
|
|
459
731
|
{"notify": {"type": "error", "message": error_message}}
|
|
460
732
|
)
|
|
461
733
|
return templates.TemplateResponse(
|
|
462
|
-
"partials/
|
|
734
|
+
"partials/_load_manager_view.html",
|
|
463
735
|
{"request": request, "error_message_inline": error_message},
|
|
464
736
|
headers=response_headers,
|
|
465
737
|
)
|
|
@@ -569,3 +841,234 @@ async def ui_create_flock_action(
|
|
|
569
841
|
},
|
|
570
842
|
headers=response_headers,
|
|
571
843
|
)
|
|
844
|
+
|
|
845
|
+
|
|
846
|
+
# --- Settings Page ---
|
|
847
|
+
@app.get("/ui/settings", response_class=HTMLResponse)
|
|
848
|
+
async def page_settings(
|
|
849
|
+
request: Request,
|
|
850
|
+
error: str = None,
|
|
851
|
+
success: str = None,
|
|
852
|
+
ui_mode: str = Query("standalone"),
|
|
853
|
+
):
|
|
854
|
+
"""Render the Settings top-level page which in turn loads the HTMX settings view."""
|
|
855
|
+
context = get_base_context(request, error, success, ui_mode)
|
|
856
|
+
context["initial_content_url"] = "/ui/htmx/settings-view"
|
|
857
|
+
return templates.TemplateResponse("base.html", context)
|
|
858
|
+
|
|
859
|
+
|
|
860
|
+
# Helper to build env var list for templates
|
|
861
|
+
|
|
862
|
+
def _prepare_env_vars_for_template():
|
|
863
|
+
env_vars_raw = load_env_file()
|
|
864
|
+
show_secrets = get_show_secrets_setting(env_vars_raw)
|
|
865
|
+
env_vars_list = []
|
|
866
|
+
for name, value in env_vars_raw.items():
|
|
867
|
+
if name.startswith("#") or name == "":
|
|
868
|
+
# skip comments/blank for table
|
|
869
|
+
continue
|
|
870
|
+
display_value = (
|
|
871
|
+
value
|
|
872
|
+
if (not is_sensitive(name) or show_secrets)
|
|
873
|
+
else mask_sensitive_value(value)
|
|
874
|
+
)
|
|
875
|
+
env_vars_list.append({"name": name, "value": display_value})
|
|
876
|
+
return env_vars_list, show_secrets
|
|
877
|
+
|
|
878
|
+
|
|
879
|
+
@app.get("/ui/htmx/settings-view", response_class=HTMLResponse)
|
|
880
|
+
async def htmx_get_settings_view(request: Request):
|
|
881
|
+
"""Return the settings composite view (env vars + theme switcher)."""
|
|
882
|
+
env_vars_list, show_secrets = _prepare_env_vars_for_template()
|
|
883
|
+
theme_name = get_current_theme_name()
|
|
884
|
+
themes_available = []
|
|
885
|
+
if THEMES_DIR and THEMES_DIR.exists():
|
|
886
|
+
themes_available = [p.stem for p in THEMES_DIR.glob("*.toml")]
|
|
887
|
+
return templates.TemplateResponse(
|
|
888
|
+
"partials/_settings_view.html",
|
|
889
|
+
{
|
|
890
|
+
"request": request,
|
|
891
|
+
"env_vars": env_vars_list,
|
|
892
|
+
"show_secrets": show_secrets,
|
|
893
|
+
"themes": themes_available,
|
|
894
|
+
"current_theme": theme_name,
|
|
895
|
+
},
|
|
896
|
+
)
|
|
897
|
+
|
|
898
|
+
|
|
899
|
+
# --- Env Var Manager Endpoints ---
|
|
900
|
+
@app.post("/ui/htmx/toggle-show-secrets", response_class=HTMLResponse)
|
|
901
|
+
async def htmx_toggle_show_secrets(request: Request):
|
|
902
|
+
# Toggle and return updated table
|
|
903
|
+
env_vars_raw = load_env_file()
|
|
904
|
+
current = get_show_secrets_setting(env_vars_raw)
|
|
905
|
+
set_show_secrets_setting(not current)
|
|
906
|
+
env_vars_list, show_secrets = _prepare_env_vars_for_template()
|
|
907
|
+
return templates.TemplateResponse(
|
|
908
|
+
"partials/_env_vars_table.html",
|
|
909
|
+
{
|
|
910
|
+
"request": request,
|
|
911
|
+
"env_vars": env_vars_list,
|
|
912
|
+
"show_secrets": show_secrets,
|
|
913
|
+
},
|
|
914
|
+
)
|
|
915
|
+
|
|
916
|
+
|
|
917
|
+
@app.post("/ui/htmx/env-delete", response_class=HTMLResponse)
|
|
918
|
+
async def htmx_env_delete(request: Request, var_name: str = Form(...)):
|
|
919
|
+
env_vars_raw = load_env_file()
|
|
920
|
+
if var_name in env_vars_raw:
|
|
921
|
+
del env_vars_raw[var_name]
|
|
922
|
+
save_env_file(env_vars_raw)
|
|
923
|
+
env_vars_list, show_secrets = _prepare_env_vars_for_template()
|
|
924
|
+
return templates.TemplateResponse(
|
|
925
|
+
"partials/_env_vars_table.html",
|
|
926
|
+
{
|
|
927
|
+
"request": request,
|
|
928
|
+
"env_vars": env_vars_list,
|
|
929
|
+
"show_secrets": show_secrets,
|
|
930
|
+
},
|
|
931
|
+
)
|
|
932
|
+
|
|
933
|
+
|
|
934
|
+
@app.post("/ui/htmx/env-edit", response_class=HTMLResponse)
|
|
935
|
+
async def htmx_env_edit(
|
|
936
|
+
request: Request,
|
|
937
|
+
var_name: str = Form(...),
|
|
938
|
+
):
|
|
939
|
+
# New value is provided via HX-Prompt header
|
|
940
|
+
new_value = request.headers.get("HX-Prompt")
|
|
941
|
+
if new_value is None:
|
|
942
|
+
# Nothing entered; just return current table
|
|
943
|
+
env_vars_list, show_secrets = _prepare_env_vars_for_template()
|
|
944
|
+
return templates.TemplateResponse(
|
|
945
|
+
"partials/_env_vars_table.html",
|
|
946
|
+
{
|
|
947
|
+
"request": request,
|
|
948
|
+
"env_vars": env_vars_list,
|
|
949
|
+
"show_secrets": show_secrets,
|
|
950
|
+
},
|
|
951
|
+
)
|
|
952
|
+
env_vars_raw = load_env_file()
|
|
953
|
+
env_vars_raw[var_name] = new_value
|
|
954
|
+
save_env_file(env_vars_raw)
|
|
955
|
+
env_vars_list, show_secrets = _prepare_env_vars_for_template()
|
|
956
|
+
return templates.TemplateResponse(
|
|
957
|
+
"partials/_env_vars_table.html",
|
|
958
|
+
{
|
|
959
|
+
"request": request,
|
|
960
|
+
"env_vars": env_vars_list,
|
|
961
|
+
"show_secrets": show_secrets,
|
|
962
|
+
},
|
|
963
|
+
)
|
|
964
|
+
|
|
965
|
+
|
|
966
|
+
@app.get("/ui/htmx/env-add-form", response_class=HTMLResponse)
|
|
967
|
+
async def htmx_env_add_form(request: Request):
|
|
968
|
+
# Return simple form row at top of table
|
|
969
|
+
return HTMLResponse(
|
|
970
|
+
"""
|
|
971
|
+
<form hx-post='/ui/htmx/env-add' hx-target='#env-vars-container' hx-swap='outerHTML' style='display:flex; gap:0.5rem; margin-bottom:0.5rem;'>
|
|
972
|
+
<input name='var_name' placeholder='NAME' required style='flex:2;'>
|
|
973
|
+
<input name='var_value' placeholder='VALUE' style='flex:3;'>
|
|
974
|
+
<button type='submit'>Add</button>
|
|
975
|
+
</form>
|
|
976
|
+
"""
|
|
977
|
+
)
|
|
978
|
+
|
|
979
|
+
|
|
980
|
+
@app.post("/ui/htmx/env-add", response_class=HTMLResponse)
|
|
981
|
+
async def htmx_env_add(request: Request, var_name: str = Form(...), var_value: str = Form("")):
|
|
982
|
+
env_vars_raw = load_env_file()
|
|
983
|
+
env_vars_raw[var_name] = var_value
|
|
984
|
+
save_env_file(env_vars_raw)
|
|
985
|
+
env_vars_list, show_secrets = _prepare_env_vars_for_template()
|
|
986
|
+
return templates.TemplateResponse(
|
|
987
|
+
"partials/_env_vars_table.html",
|
|
988
|
+
{
|
|
989
|
+
"request": request,
|
|
990
|
+
"env_vars": env_vars_list,
|
|
991
|
+
"show_secrets": show_secrets,
|
|
992
|
+
},
|
|
993
|
+
)
|
|
994
|
+
|
|
995
|
+
|
|
996
|
+
# --- Theme Preview and Apply Endpoints ---
|
|
997
|
+
@app.get("/ui/htmx/theme-preview", response_class=HTMLResponse)
|
|
998
|
+
async def htmx_theme_preview(request: Request, theme: str = Query(None)):
|
|
999
|
+
theme_name = theme or get_current_theme_name()
|
|
1000
|
+
# Load theme data
|
|
1001
|
+
try:
|
|
1002
|
+
theme_path = THEMES_DIR / f"{theme_name}.toml" if THEMES_DIR else None
|
|
1003
|
+
if not (theme_path and theme_path.exists()):
|
|
1004
|
+
return HTMLResponse("<p>Theme not found.</p>")
|
|
1005
|
+
from flock.core.logging.formatters.themed_formatter import (
|
|
1006
|
+
load_theme_from_file,
|
|
1007
|
+
)
|
|
1008
|
+
theme_data = load_theme_from_file(str(theme_path))
|
|
1009
|
+
except Exception as e:
|
|
1010
|
+
return HTMLResponse(f"<p>Error loading theme: {e}</p>")
|
|
1011
|
+
|
|
1012
|
+
css_vars = alacritty_to_pico(theme_data)
|
|
1013
|
+
css_vars_str = ":root {\n" + "\n".join([f" {k}: {v};" for k, v in css_vars.items()]) + "\n}"
|
|
1014
|
+
|
|
1015
|
+
main_colors = [
|
|
1016
|
+
("Background", css_vars["--pico-background-color"]),
|
|
1017
|
+
("Text", css_vars["--pico-color"]),
|
|
1018
|
+
("Primary", css_vars["--pico-primary"]),
|
|
1019
|
+
("Secondary", css_vars["--pico-secondary"]),
|
|
1020
|
+
("Muted", css_vars["--pico-muted-color"]),
|
|
1021
|
+
]
|
|
1022
|
+
|
|
1023
|
+
return templates.TemplateResponse(
|
|
1024
|
+
"partials/_theme_preview.html",
|
|
1025
|
+
{
|
|
1026
|
+
"request": request,
|
|
1027
|
+
"theme_name": theme_name,
|
|
1028
|
+
"css_vars_str": css_vars_str,
|
|
1029
|
+
"main_colors": main_colors,
|
|
1030
|
+
},
|
|
1031
|
+
)
|
|
1032
|
+
|
|
1033
|
+
|
|
1034
|
+
@app.post("/ui/apply-theme")
|
|
1035
|
+
async def apply_theme(request: Request, theme: str = Form(...)):
|
|
1036
|
+
try:
|
|
1037
|
+
from flock.webapp.app.config import set_current_theme_name
|
|
1038
|
+
|
|
1039
|
+
set_current_theme_name(theme)
|
|
1040
|
+
# Trigger full refresh via HTMX
|
|
1041
|
+
headers = {"HX-Refresh": "true"}
|
|
1042
|
+
return HTMLResponse("", headers=headers)
|
|
1043
|
+
except Exception as e:
|
|
1044
|
+
return HTMLResponse(f"Failed to apply theme: {e}", status_code=500)
|
|
1045
|
+
|
|
1046
|
+
|
|
1047
|
+
# --- Settings Content Endpoints (for tab navigation) ---
|
|
1048
|
+
@app.get("/ui/htmx/settings/env-vars", response_class=HTMLResponse)
|
|
1049
|
+
async def htmx_settings_env_vars(request: Request):
|
|
1050
|
+
env_vars_list, show_secrets = _prepare_env_vars_for_template()
|
|
1051
|
+
return templates.TemplateResponse(
|
|
1052
|
+
"partials/_settings_env_content.html",
|
|
1053
|
+
{
|
|
1054
|
+
"request": request,
|
|
1055
|
+
"env_vars": env_vars_list,
|
|
1056
|
+
"show_secrets": show_secrets,
|
|
1057
|
+
},
|
|
1058
|
+
)
|
|
1059
|
+
|
|
1060
|
+
|
|
1061
|
+
@app.get("/ui/htmx/settings/theme", response_class=HTMLResponse)
|
|
1062
|
+
async def htmx_settings_theme(request: Request):
|
|
1063
|
+
theme_name = get_current_theme_name()
|
|
1064
|
+
themes_available = []
|
|
1065
|
+
if THEMES_DIR and THEMES_DIR.exists():
|
|
1066
|
+
themes_available = [p.stem for p in THEMES_DIR.glob("*.toml")]
|
|
1067
|
+
return templates.TemplateResponse(
|
|
1068
|
+
"partials/_settings_theme_content.html",
|
|
1069
|
+
{
|
|
1070
|
+
"request": request,
|
|
1071
|
+
"themes": themes_available,
|
|
1072
|
+
"current_theme": theme_name,
|
|
1073
|
+
},
|
|
1074
|
+
)
|