g360-cli 1.15.0 → 1.15.8
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.
- package/README.md +1477 -1358
- package/package.json +1 -1
- package/src/assets/templates/python-flet-polished/README.md +39 -0
- package/src/assets/templates/python-flet-polished/main.py +7 -0
- package/src/assets/templates/python-flet-polished/pyproject.toml +1 -0
- package/src/assets/templates/python-flet-polished/requirements.txt +1 -0
- package/src/assets/templates/python-flet-polished/skill.json +27 -1
- package/src/assets/templates/python-flet-polished/src/app.py +44 -1
- package/src/assets/templates/python-flet-polished/src/config/theme.py +153 -74
- package/src/assets/templates/python-flet-polished/src/core/g360_registry.py +562 -0
- package/src/assets/templates/python-flet-polished/src/ui/dashboard.py +2 -2
- package/src/assets/templates/python-flet-polished/src/ui/kpi_card.py +1 -1
- package/src/assets/templates/python-flet-polished/src/ui/search_overlay.py +4 -4
- package/src/commands/addon.test.js +2 -2
package/package.json
CHANGED
|
@@ -132,6 +132,45 @@ Colores semanticos definidos en `skill.json`, accesibles via `get_colors(mode)`:
|
|
|
132
132
|
| KPI red | `#EF4444` | `#DC2626` | Indicador critico |
|
|
133
133
|
| KPI amber | `#F59E0B` | `#B45309` | Indicador warning |
|
|
134
134
|
|
|
135
|
+
### Personalizar Colores
|
|
136
|
+
|
|
137
|
+
Los colores se leen automaticamente de `skill.json`. Para personalizar:
|
|
138
|
+
|
|
139
|
+
**Opcion 1: Modificar skill.json**
|
|
140
|
+
```json
|
|
141
|
+
{
|
|
142
|
+
"colors": {
|
|
143
|
+
"accent": "#2563eb", // Azul corporativo
|
|
144
|
+
"surface": "#1e293b", // Gris oscuro
|
|
145
|
+
"success": "#16a34a", // Verde mas oscuro
|
|
146
|
+
...
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**Opcion 2: Sobrescribir en runtime**
|
|
152
|
+
```python
|
|
153
|
+
from src.config.theme import set_brand_colors
|
|
154
|
+
|
|
155
|
+
set_brand_colors({
|
|
156
|
+
"dark": {
|
|
157
|
+
"accent": "#2563eb",
|
|
158
|
+
"surface": "#1e273e",
|
|
159
|
+
},
|
|
160
|
+
"light": {
|
|
161
|
+
"accent": "#1d4ed8",
|
|
162
|
+
}
|
|
163
|
+
})
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Opcion 3: Usar paleta existente**
|
|
167
|
+
- **Verde esmeralda** (default): `#10B981` — Moderno, fresco
|
|
168
|
+
- **Verde corporativo**: `#2563eb` — Azul/verde profesional
|
|
169
|
+
- **Verde bosque**: `#059669` — Mas sobrio, enterprise
|
|
170
|
+
- **Verde fluorescente**: `#34d399` — Mas vibrante, tech
|
|
171
|
+
|
|
172
|
+
> **Nota**: Todos los colores deben ser validos hex (`#RRGGBB`). El sistema usa defaults G360 y aplica overrides solo de los colores especificados.
|
|
173
|
+
|
|
135
174
|
## Arquitectura
|
|
136
175
|
|
|
137
176
|
```
|
|
@@ -6,6 +6,13 @@ import traceback
|
|
|
6
6
|
from logging.handlers import RotatingFileHandler
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
|
|
9
|
+
# Import pip-system-certs to fix SSL issues on Windows corporate networks
|
|
10
|
+
try:
|
|
11
|
+
import pip_system_certs
|
|
12
|
+
pip_system_certs.install()
|
|
13
|
+
except ImportError:
|
|
14
|
+
pass # Not available, continue without it
|
|
15
|
+
|
|
9
16
|
import flet as ft
|
|
10
17
|
|
|
11
18
|
BASE_DIR = Path(__file__).resolve().parent
|
|
@@ -6,16 +6,30 @@
|
|
|
6
6
|
"brand": "g360",
|
|
7
7
|
"description": "G360 Flet Polished - Plantilla base con patrones de UI avanzada",
|
|
8
8
|
"framework": "flet",
|
|
9
|
+
"flet_version": "0.28.3",
|
|
9
10
|
"portable": true,
|
|
10
11
|
"colors": {
|
|
11
12
|
"bg": "#0b1220",
|
|
12
13
|
"surface": "#1a2333",
|
|
13
14
|
"accent": "#34d399",
|
|
15
|
+
"accent_dark": "#047857",
|
|
14
16
|
"text": "#f0f6fc",
|
|
15
17
|
"muted": "#8b949e",
|
|
16
18
|
"success": "#34d399",
|
|
17
19
|
"warning": "#f59e0b",
|
|
18
|
-
"error": "#ef4444"
|
|
20
|
+
"error": "#ef4444",
|
|
21
|
+
"info": "#3b82f6",
|
|
22
|
+
"violet": "#8b5cf6",
|
|
23
|
+
"pink": "#ec4899",
|
|
24
|
+
"cyan": "#06b6d4",
|
|
25
|
+
"orange": "#f97316",
|
|
26
|
+
"surface_variant": "#1b2740",
|
|
27
|
+
"surface_sunken": "#0e1627",
|
|
28
|
+
"background": "#0a0f1e",
|
|
29
|
+
"border": "#ffffff17",
|
|
30
|
+
"text_muted": "#8fa0ba",
|
|
31
|
+
"text_primary": "#f1f5fb",
|
|
32
|
+
"text_secondary": "#c9d4e6"
|
|
19
33
|
},
|
|
20
34
|
"effects": {
|
|
21
35
|
"glassmorphism": true,
|
|
@@ -24,5 +38,17 @@
|
|
|
24
38
|
"signature": {
|
|
25
39
|
"mode": "powered",
|
|
26
40
|
"text": "powered by G360"
|
|
41
|
+
},
|
|
42
|
+
"events": [
|
|
43
|
+
"app:{name}:refresh",
|
|
44
|
+
"app:{name}:data:update",
|
|
45
|
+
"app:{name}:theme:change",
|
|
46
|
+
"g360:app:list",
|
|
47
|
+
"g360:app:register"
|
|
48
|
+
],
|
|
49
|
+
"endpoints": {
|
|
50
|
+
"health": "/api/health",
|
|
51
|
+
"data": "/api/data",
|
|
52
|
+
"events": "/api/events"
|
|
27
53
|
}
|
|
28
54
|
}
|
|
@@ -24,6 +24,7 @@ from src.core.constants import (
|
|
|
24
24
|
get_app_name,
|
|
25
25
|
)
|
|
26
26
|
from src.ui.dashboard import Dashboard
|
|
27
|
+
from src.core.g360_registry import register_g360_app, get_event_bus
|
|
27
28
|
|
|
28
29
|
import logging
|
|
29
30
|
_log_logger = logging.getLogger("g360.app")
|
|
@@ -156,6 +157,9 @@ class G360App:
|
|
|
156
157
|
self.dashboard.register_overlay()
|
|
157
158
|
_log("_build: overlay (FilePickers) registrado")
|
|
158
159
|
|
|
160
|
+
# Registrar app en registry G360
|
|
161
|
+
self._register_app()
|
|
162
|
+
|
|
159
163
|
sample_path = Path(__file__).resolve().parent.parent / "assets" / "data" / "sample_data.json"
|
|
160
164
|
cache, ts = _load_cache()
|
|
161
165
|
if cache:
|
|
@@ -215,8 +219,38 @@ class G360App:
|
|
|
215
219
|
except Exception as ex:
|
|
216
220
|
_log(f"_start_auto_refresh: ERROR {ex}")
|
|
217
221
|
|
|
222
|
+
def _register_app(self):
|
|
223
|
+
"""Registrar la app en el registry G360."""
|
|
224
|
+
try:
|
|
225
|
+
skill_data = self._load_skill_config()
|
|
226
|
+
events = skill_data.get("events", [])
|
|
227
|
+
endpoints = skill_data.get("endpoints", {})
|
|
228
|
+
|
|
229
|
+
register_g360_app(
|
|
230
|
+
name=get_app_name(),
|
|
231
|
+
version=self._local_version,
|
|
232
|
+
skill=skill_data.get("skill", "custom"),
|
|
233
|
+
framework="flet",
|
|
234
|
+
events=events,
|
|
235
|
+
endpoints=endpoints,
|
|
236
|
+
description=skill_data.get("description", ""),
|
|
237
|
+
)
|
|
238
|
+
_log(f"_register_app: {get_app_name()} registrado en G360 registry")
|
|
239
|
+
except Exception as ex:
|
|
240
|
+
_log(f"_register_app: ERROR {ex}")
|
|
241
|
+
|
|
242
|
+
def _load_skill_config(self) -> dict:
|
|
243
|
+
"""Cargar config del skill actual."""
|
|
244
|
+
import json
|
|
245
|
+
from pathlib import Path
|
|
246
|
+
skill_path = Path(__file__).resolve().parent.parent.parent / "skill.json"
|
|
247
|
+
if skill_path.exists():
|
|
248
|
+
with open(skill_path, encoding="utf-8") as f:
|
|
249
|
+
return json.load(f)
|
|
250
|
+
return {}
|
|
251
|
+
|
|
218
252
|
def shutdown(self):
|
|
219
|
-
"""Detiene el auto-refresh
|
|
253
|
+
"""Detiene el auto-refresh y desregistra la app antes de cerrar."""
|
|
220
254
|
if self._auto_refresh_stop:
|
|
221
255
|
_log("_shutdown: deteniendo auto-refresh...")
|
|
222
256
|
self._auto_refresh_stop.set()
|
|
@@ -224,6 +258,15 @@ class G360App:
|
|
|
224
258
|
if t and t.is_alive():
|
|
225
259
|
t.join(timeout=3)
|
|
226
260
|
_log("_shutdown: auto-refresh detenido")
|
|
261
|
+
|
|
262
|
+
# Desregistrar del registry
|
|
263
|
+
try:
|
|
264
|
+
from src.core.g360_registry import get_app_registry
|
|
265
|
+
registry = get_app_registry()
|
|
266
|
+
registry.update_status(get_app_name(), "offline")
|
|
267
|
+
_log(f"_shutdown: {get_app_name()} desregistrado")
|
|
268
|
+
except Exception as ex:
|
|
269
|
+
_log(f"_shutdown: ERROR desregistrando {ex}")
|
|
227
270
|
|
|
228
271
|
def _auto_refresh_loop(self):
|
|
229
272
|
while not self._auto_refresh_stop.is_set():
|
|
@@ -1,105 +1,171 @@
|
|
|
1
1
|
import json
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
|
|
4
|
+
# Colores G360 estandar como referencia
|
|
5
|
+
G360_DEFAULTS = {
|
|
6
|
+
"dark": {
|
|
7
|
+
"accent": "#10B981",
|
|
8
|
+
"accent_dark": "#047857",
|
|
9
|
+
"success": "#34D399",
|
|
10
|
+
"warning": "#F59E0B",
|
|
11
|
+
"error": "#EF4444",
|
|
12
|
+
"info": "#3B82F6",
|
|
13
|
+
"violet": "#8B5CF6",
|
|
14
|
+
"pink": "#EC4899",
|
|
15
|
+
"cyan": "#06B6D4",
|
|
16
|
+
"orange": "#F97316",
|
|
17
|
+
"surface": "#141D33",
|
|
18
|
+
"surface_variant": "#1B2740",
|
|
19
|
+
"surface_sunken": "#0E1627",
|
|
20
|
+
"background": "#0A0F1E",
|
|
21
|
+
"border": "#FFFFFF17",
|
|
22
|
+
"text_muted": "#8FA0BA",
|
|
23
|
+
"text_primary": "#F1F5FB",
|
|
24
|
+
"text_secondary": "#C9D4E6",
|
|
25
|
+
"kpis": {
|
|
26
|
+
"primary": "#06B6D4",
|
|
27
|
+
"secondary": "#8B5CF6",
|
|
28
|
+
"tertiary": "#0EA5E9",
|
|
29
|
+
"quaternary": "#6366F1",
|
|
30
|
+
"quinary": "#EC4899",
|
|
31
|
+
"senary": "#F59E0B",
|
|
32
|
+
"septenary": "#EF4444",
|
|
33
|
+
"octonary": "#F97316",
|
|
34
|
+
},
|
|
27
35
|
},
|
|
28
|
-
"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
"octonary": "#EA580C",
|
|
36
|
+
"light": {
|
|
37
|
+
"accent": "#047857",
|
|
38
|
+
"accent_dark": "#065F46",
|
|
39
|
+
"success": "#15803D",
|
|
40
|
+
"warning": "#B45309",
|
|
41
|
+
"error": "#DC2626",
|
|
42
|
+
"info": "#2563EB",
|
|
43
|
+
"violet": "#7C3AED",
|
|
44
|
+
"pink": "#DB2777",
|
|
45
|
+
"cyan": "#0891B2",
|
|
46
|
+
"orange": "#EA580C",
|
|
47
|
+
"surface": "#FFFFFF",
|
|
48
|
+
"surface_variant": "#F7F9FC",
|
|
49
|
+
"surface_sunken": "#EEF1F6",
|
|
50
|
+
"background": "#F3F5F9",
|
|
51
|
+
"border": "#E3E8F0",
|
|
52
|
+
"text_muted": "#64748B",
|
|
53
|
+
"text_primary": "#0F172A",
|
|
54
|
+
"text_secondary": "#334155",
|
|
55
|
+
"kpis": {
|
|
56
|
+
"primary": "#0891B2",
|
|
57
|
+
"secondary": "#7C3AED",
|
|
58
|
+
"tertiary": "#0284C7",
|
|
59
|
+
"quaternary": "#4F46E5",
|
|
60
|
+
"quinary": "#DB2777",
|
|
61
|
+
"senary": "#B45309",
|
|
62
|
+
"septenary": "#DC2626",
|
|
63
|
+
"octonary": "#EA580C",
|
|
64
|
+
},
|
|
58
65
|
},
|
|
59
|
-
"surface": "#FFFFFF",
|
|
60
|
-
"surface_variant": "#F7F9FC",
|
|
61
|
-
"surface_sunken": "#EEF1F6",
|
|
62
|
-
"background": "#F3F5F9",
|
|
63
|
-
"border": "#E3E8F0",
|
|
64
|
-
"text_muted": "#64748B",
|
|
65
|
-
"text_primary": "#0F172A",
|
|
66
|
-
"text_secondary": "#334155",
|
|
67
66
|
}
|
|
68
67
|
|
|
69
68
|
|
|
70
|
-
def get_colors(mode: str) -> dict:
|
|
71
|
-
|
|
69
|
+
def get_colors(mode: str = None, skill_path: Path = None) -> dict:
|
|
70
|
+
"""
|
|
71
|
+
Carga la paleta de colores para el modo especificado.
|
|
72
|
+
|
|
73
|
+
Prioridad:
|
|
74
|
+
1. skill.json del proyecto (si existe)
|
|
75
|
+
2. Configuracion guardada en ~/.g360/
|
|
76
|
+
3. Valores por defecto G360
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
mode: 'dark' o 'light'. Si es None, lee de la preferencia guardada.
|
|
80
|
+
skill_path: Ruta al skill.json. Si es None, busca automaticamente.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
dict con los colores aplicados
|
|
84
|
+
"""
|
|
85
|
+
# Determinar modo
|
|
86
|
+
if mode is None:
|
|
87
|
+
mode = load_theme_preference()
|
|
88
|
+
|
|
89
|
+
# Cargar desde skill.json si existe
|
|
90
|
+
skill_colors = _load_skill_colors(skill_path)
|
|
91
|
+
|
|
92
|
+
# Combinar: defaults -> skill overrides
|
|
93
|
+
defaults = G360_DEFAULTS.get(mode, G360_DEFAULTS["dark"])
|
|
94
|
+
|
|
95
|
+
if skill_colors:
|
|
96
|
+
# Skill tiene colores propios, usarlos
|
|
97
|
+
colors = {**defaults, **skill_colors}
|
|
98
|
+
# Asegurar que kpis se fusionen correctamente
|
|
99
|
+
if "kpis" in skill_colors:
|
|
100
|
+
colors["kpis"] = {**defaults.get("kpis", {}), **skill_colors["kpis"]}
|
|
101
|
+
else:
|
|
102
|
+
colors = defaults.copy()
|
|
103
|
+
if "kpis" not in colors:
|
|
104
|
+
colors["kpis"] = defaults.get("kpis", {}).copy()
|
|
105
|
+
|
|
106
|
+
return colors
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _load_skill_colors(skill_path: Path = None) -> dict | None:
|
|
110
|
+
"""Carga colores desde skill.json del proyecto."""
|
|
111
|
+
try:
|
|
112
|
+
if skill_path is None:
|
|
113
|
+
skill_path = _find_skill_json()
|
|
114
|
+
|
|
115
|
+
if skill_path and skill_path.exists():
|
|
116
|
+
with open(skill_path, encoding="utf-8") as f:
|
|
117
|
+
data = json.load(f)
|
|
118
|
+
return data.get("colors", {})
|
|
119
|
+
except Exception:
|
|
120
|
+
pass
|
|
121
|
+
return None
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _find_skill_json() -> Path | None:
|
|
125
|
+
"""Busca skill.json en las ubicaciones posibles."""
|
|
126
|
+
candidates = [
|
|
127
|
+
Path("skill.json"),
|
|
128
|
+
Path("src/core/skill.json"),
|
|
129
|
+
Path(__file__).resolve().parent.parent.parent / "skill.json",
|
|
130
|
+
]
|
|
131
|
+
for candidate in candidates:
|
|
132
|
+
if candidate.exists():
|
|
133
|
+
return candidate
|
|
134
|
+
return None
|
|
72
135
|
|
|
73
136
|
|
|
74
137
|
def rgba(color: str, opacity: float) -> str:
|
|
138
|
+
"""Convierte un color hex a rgba con la opacidad especificada."""
|
|
75
139
|
if color.startswith("#") and len(color) == 9:
|
|
76
|
-
return color
|
|
140
|
+
return color # Ya tiene alpha
|
|
77
141
|
if color.startswith("#") and len(color) == 7:
|
|
78
142
|
hex_color = color[1:]
|
|
79
143
|
alpha = int(max(0.0, min(1.0, opacity)) * 255)
|
|
80
144
|
return f"#{alpha:02x}{hex_color}"
|
|
81
|
-
return color
|
|
145
|
+
return color # Retornar como esta si no es hex valido
|
|
82
146
|
|
|
83
147
|
|
|
84
148
|
def get_theme_file() -> Path:
|
|
85
|
-
|
|
149
|
+
"""Retorna la ruta del archivo de preferencia de tema."""
|
|
150
|
+
app_name = _get_app_name()
|
|
151
|
+
return Path.home() / ".g360" / f"{app_name}_config.json"
|
|
86
152
|
|
|
87
153
|
|
|
88
154
|
def _get_app_name() -> str:
|
|
155
|
+
"""Obtiene el nombre de la app desde skill.json o el nombre del directorio."""
|
|
89
156
|
try:
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
_data = json.load(_f)
|
|
96
|
-
return _data.get("name", "g360-app").replace("-", "_")
|
|
157
|
+
skill_path = _find_skill_json()
|
|
158
|
+
if skill_path and skill_path.exists():
|
|
159
|
+
with open(skill_path, encoding="utf-8") as f:
|
|
160
|
+
data = json.load(f)
|
|
161
|
+
return data.get("name", Path(__file__).resolve().parent.parent.parent.name)
|
|
97
162
|
except Exception:
|
|
98
163
|
pass
|
|
99
|
-
return
|
|
164
|
+
return Path(__file__).resolve().parent.parent.parent.name
|
|
100
165
|
|
|
101
166
|
|
|
102
167
|
def load_theme_preference() -> str:
|
|
168
|
+
"""Carga la preferencia de tema guardada (dark/light)."""
|
|
103
169
|
try:
|
|
104
170
|
config_file = get_theme_file()
|
|
105
171
|
if config_file.exists():
|
|
@@ -112,6 +178,7 @@ def load_theme_preference() -> str:
|
|
|
112
178
|
|
|
113
179
|
|
|
114
180
|
def save_theme_preference(mode: str):
|
|
181
|
+
"""Guarda la preferencia de tema."""
|
|
115
182
|
try:
|
|
116
183
|
config_file = get_theme_file()
|
|
117
184
|
config_file.parent.mkdir(parents=True, exist_ok=True)
|
|
@@ -120,3 +187,15 @@ def save_theme_preference(mode: str):
|
|
|
120
187
|
json.dump(config, f)
|
|
121
188
|
except Exception:
|
|
122
189
|
pass
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def set_brand_colors(colors: dict):
|
|
193
|
+
"""
|
|
194
|
+
Permite sobrescribir los colores de la marca en runtime.
|
|
195
|
+
Useful para apps que necesitan colores dinamicos.
|
|
196
|
+
"""
|
|
197
|
+
global G360_DEFAULTS
|
|
198
|
+
# Actualizar solo los modos que existan
|
|
199
|
+
for mode in ["dark", "light"]:
|
|
200
|
+
if mode in G360_DEFAULTS and mode in colors:
|
|
201
|
+
G360_DEFAULTS[mode].update(colors[mode])
|