paramsx 2.5.1__tar.gz → 2.5.2__tar.gz
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.
- {paramsx-2.5.1 → paramsx-2.5.2}/CHANGELOG.md +10 -0
- {paramsx-2.5.1/paramsx.egg-info → paramsx-2.5.2}/PKG-INFO +1 -1
- paramsx-2.5.2/paramsx/__init__.py +1 -0
- {paramsx-2.5.1 → paramsx-2.5.2}/paramsx/web/app.py +4 -3
- {paramsx-2.5.1 → paramsx-2.5.2}/paramsx/web/ssm.py +42 -7
- {paramsx-2.5.1 → paramsx-2.5.2}/paramsx/web/static/paramsx.js +57 -17
- {paramsx-2.5.1 → paramsx-2.5.2/paramsx.egg-info}/PKG-INFO +1 -1
- {paramsx-2.5.1 → paramsx-2.5.2}/tests/test_web.py +19 -0
- paramsx-2.5.1/paramsx/__init__.py +0 -1
- {paramsx-2.5.1 → paramsx-2.5.2}/LICENSE +0 -0
- {paramsx-2.5.1 → paramsx-2.5.2}/MANIFEST.in +0 -0
- {paramsx-2.5.1 → paramsx-2.5.2}/README.md +0 -0
- {paramsx-2.5.1 → paramsx-2.5.2}/paramsx/core/__init__.py +0 -0
- {paramsx-2.5.1 → paramsx-2.5.2}/paramsx/core/config.py +0 -0
- {paramsx-2.5.1 → paramsx-2.5.2}/paramsx/core/rutas.py +0 -0
- {paramsx-2.5.1 → paramsx-2.5.2}/paramsx/funcions.py +0 -0
- {paramsx-2.5.1 → paramsx-2.5.2}/paramsx/main.py +0 -0
- {paramsx-2.5.1 → paramsx-2.5.2}/paramsx/paramsx_config.py +0 -0
- {paramsx-2.5.1 → paramsx-2.5.2}/paramsx/text.py +0 -0
- {paramsx-2.5.1 → paramsx-2.5.2}/paramsx/web/__init__.py +0 -0
- {paramsx-2.5.1 → paramsx-2.5.2}/paramsx/web/static/ajustes.js +0 -0
- {paramsx-2.5.1 → paramsx-2.5.2}/paramsx/web/static/app.css +0 -0
- {paramsx-2.5.1 → paramsx-2.5.2}/paramsx/web/static/comun.js +0 -0
- {paramsx-2.5.1 → paramsx-2.5.2}/paramsx/web/templates/index.html +0 -0
- {paramsx-2.5.1 → paramsx-2.5.2}/paramsx.egg-info/SOURCES.txt +0 -0
- {paramsx-2.5.1 → paramsx-2.5.2}/paramsx.egg-info/dependency_links.txt +0 -0
- {paramsx-2.5.1 → paramsx-2.5.2}/paramsx.egg-info/entry_points.txt +0 -0
- {paramsx-2.5.1 → paramsx-2.5.2}/paramsx.egg-info/requires.txt +0 -0
- {paramsx-2.5.1 → paramsx-2.5.2}/paramsx.egg-info/top_level.txt +0 -0
- {paramsx-2.5.1 → paramsx-2.5.2}/setup.cfg +0 -0
- {paramsx-2.5.1 → paramsx-2.5.2}/setup.py +0 -0
- {paramsx-2.5.1 → paramsx-2.5.2}/tests/test_core.py +0 -0
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.5.2
|
|
4
|
+
|
|
5
|
+
### Corregido
|
|
6
|
+
|
|
7
|
+
- **Buscar un parámetro que existe ya no dice que no está**:
|
|
8
|
+
- En «Ruta» vale también el **nombre completo de un parámetro** (antes solo carpetas: «/dev/app/clave» no encontraba nada) y los parámetros **sin carpeta** (`MiParametro`).
|
|
9
|
+
- El buscador de «Ruta» sugiere también los **parámetros**, no solo las carpetas.
|
|
10
|
+
- Con **más de 2.000 parámetros** en la cuenta, el buscador y el filtro de Explorar preguntan también a AWS lo que se escribe; antes solo buscaban en los 2.000 primeros.
|
|
11
|
+
- Explorar muestra los parámetros sin carpeta.
|
|
12
|
+
|
|
3
13
|
## 2.5.1
|
|
4
14
|
|
|
5
15
|
### Corregido
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.5.2"
|
|
@@ -354,13 +354,14 @@ def crear_app(config_path=None, token=None, sesion_factory=None, perfiles_aws=No
|
|
|
354
354
|
|
|
355
355
|
@app.get("/api/explorar")
|
|
356
356
|
def api_explorar():
|
|
357
|
-
return jsonify(ssm_web.explorar(ssm(), request.args.get("prefijo") or "/"
|
|
357
|
+
return jsonify(ssm_web.explorar(ssm(), request.args.get("prefijo") or "/",
|
|
358
|
+
str(request.args.get("contiene") or "")[:200]))
|
|
358
359
|
|
|
359
360
|
@app.post("/api/cargar")
|
|
360
361
|
def api_cargar():
|
|
361
362
|
ruta = str(cuerpo().get("ruta") or "").strip()
|
|
362
|
-
if not ruta
|
|
363
|
-
raise ErrorWeb("
|
|
363
|
+
if not ruta:
|
|
364
|
+
raise ErrorWeb("Escribe una ruta (o el nombre de un parámetro).")
|
|
364
365
|
return jsonify(ssm_web.cargar(ssm(), ruta))
|
|
365
366
|
|
|
366
367
|
@app.post("/api/plan")
|
|
@@ -81,8 +81,11 @@ def _meta(p):
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
|
|
84
|
-
def describir(ssm, ruta, nombres=None):
|
|
85
|
-
"""{nombre: metadatos} (sin valores) de una ruta (recursivo)
|
|
84
|
+
def describir(ssm, ruta, nombres=None, contiene=""):
|
|
85
|
+
"""{nombre: metadatos} (sin valores) de una ruta (recursivo), de unos nombres concretos o de los
|
|
86
|
+
que llevan un texto (lo filtra AWS, distingue mayúsculas)."""
|
|
87
|
+
if contiene:
|
|
88
|
+
return _describir_filtro(ssm, [{"Key": "Name", "Option": "Contains", "Values": [contiene]}])
|
|
86
89
|
if nombres is not None:
|
|
87
90
|
out = {}
|
|
88
91
|
# El filtro Name admite hasta 50 valores
|
|
@@ -170,11 +173,34 @@ def etiquetas(ssm, nombres):
|
|
|
170
173
|
return dict(pool.map(una, nombres))
|
|
171
174
|
|
|
172
175
|
|
|
176
|
+
def _uno(ssm, nombre):
|
|
177
|
+
"""Lo escrito en «Ruta» es un parámetro, no una carpeta (o uno sin «/», que no está en
|
|
178
|
+
ninguna): (meta, valores) o None si no existe."""
|
|
179
|
+
try:
|
|
180
|
+
meta = describir(ssm, "", nombres=[nombre])
|
|
181
|
+
except ClientError as exc:
|
|
182
|
+
if not denegado(exc):
|
|
183
|
+
raise ParametrosError(motivo(exc)) from None
|
|
184
|
+
meta = None
|
|
185
|
+
try:
|
|
186
|
+
valores = valores_por_nombre(ssm, [nombre])
|
|
187
|
+
except ClientError as exc:
|
|
188
|
+
raise ParametrosError(motivo(exc)) from None
|
|
189
|
+
if not meta and valores.get(nombre, {}).get("no_existe"):
|
|
190
|
+
return None
|
|
191
|
+
return meta, valores
|
|
192
|
+
|
|
193
|
+
|
|
173
194
|
def cargar(ssm, ruta):
|
|
174
195
|
"""Los parámetros de una ruta (recursivo) con valor, metadatos y etiquetas. Lo que el rol no
|
|
175
|
-
puede leer va con `bloqueado`. ParametrosError si no puede ni listar ni leer la ruta.
|
|
176
|
-
ruta
|
|
196
|
+
puede leer va con `bloqueado`. ParametrosError si no puede ni listar ni leer la ruta. Si la
|
|
197
|
+
ruta es el nombre de un parámetro (o uno sin «/»), carga ese."""
|
|
177
198
|
avisos = []
|
|
199
|
+
if not ruta.strip().startswith("/"): # parámetro sin carpeta (p. ej. «MiParametro»)
|
|
200
|
+
nombre = ruta.strip()
|
|
201
|
+
meta, valores = _uno(ssm, nombre) or ({}, {})
|
|
202
|
+
return _montar(ssm, nombre, meta, valores, avisos)
|
|
203
|
+
ruta = "/" + ruta.strip("/") if ruta.strip("/") else "/"
|
|
178
204
|
try:
|
|
179
205
|
meta = describir(ssm, ruta)
|
|
180
206
|
except ClientError as exc:
|
|
@@ -200,6 +226,14 @@ def cargar(ssm, ruta):
|
|
|
200
226
|
# La ruta entera no se deja (o un descifrado tumba la página): uno a uno
|
|
201
227
|
valores = valores_por_nombre(ssm, sorted(meta))
|
|
202
228
|
|
|
229
|
+
if not meta and not valores and ruta != "/": # nada debajo: ¿es el nombre de un parámetro?
|
|
230
|
+
uno = _uno(ssm, ruta)
|
|
231
|
+
if uno:
|
|
232
|
+
meta, valores = uno
|
|
233
|
+
return _montar(ssm, ruta, meta, valores, avisos)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _montar(ssm, ruta, meta, valores, avisos):
|
|
203
237
|
nombres = sorted(n for n in set(meta or {}) | set(valores)
|
|
204
238
|
if not valores.get(n, {}).get("no_existe"))
|
|
205
239
|
legibles = [n for n in nombres if "bloqueado" not in valores.get(n, {})]
|
|
@@ -221,10 +255,11 @@ def cargar(ssm, ruta):
|
|
|
221
255
|
return {"ruta": ruta, "parametros": parametros, "avisos": avisos}
|
|
222
256
|
|
|
223
257
|
|
|
224
|
-
def explorar(ssm, prefijo="/"):
|
|
225
|
-
"""Nombres (sin valores) para el árbol de «Explorar» y la búsqueda de Ajustes.
|
|
258
|
+
def explorar(ssm, prefijo="/", contiene=""):
|
|
259
|
+
"""Nombres (sin valores) para el árbol de «Explorar» y la búsqueda de Ajustes. Con `contiene`,
|
|
260
|
+
solo los que llevan ese texto: así se encuentran también cuando hay más de MAX_LISTADO."""
|
|
226
261
|
try:
|
|
227
|
-
meta = describir(ssm, prefijo or "/")
|
|
262
|
+
meta = describir(ssm, prefijo or "/", contiene=contiene)
|
|
228
263
|
except ClientError as exc:
|
|
229
264
|
raise ParametrosError(
|
|
230
265
|
"Tu rol de IAM no puede listar parámetros (ssm:DescribeParameters)."
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
PX.oyentes.push(st => {
|
|
56
56
|
const donde = st.config ? `${st.config.profile_name}|${st.config.region_name}` : '';
|
|
57
57
|
if (donde !== S.donde) { // otro perfil o región: lo cargado era de otra cuenta
|
|
58
|
-
S.donde = donde; S.items = []; S.carpetas = null; S.ruta = ''; $('pBarra').hidden = true; $('pLista').innerHTML = '';
|
|
58
|
+
S.donde = donde; S.items = []; S.carpetas = null; B.enAws = null; S.ruta = ''; $('pBarra').hidden = true; $('pLista').innerHTML = '';
|
|
59
59
|
$('pEstado').textContent = '';
|
|
60
60
|
pintarDonde();
|
|
61
61
|
}
|
|
@@ -90,6 +90,7 @@
|
|
|
90
90
|
B.pidiendo = B.pidiendo || api('/api/explorar').then(r => {
|
|
91
91
|
const c = {};
|
|
92
92
|
r.nombres.forEach(n => { const seg = n.split('/').filter(Boolean); for (let i = 1; i < seg.length; i++) { const k = '/' + seg.slice(0, i).join('/'); c[k] = (c[k] || 0) + 1; } });
|
|
93
|
+
S.nombres = new Set(r.nombres); S.limitado = r.limitado; // los parámetros también se buscan, no solo las carpetas
|
|
93
94
|
S.carpetas = c; return c;
|
|
94
95
|
}).finally(() => { B.pidiendo = null; });
|
|
95
96
|
return B.pidiendo;
|
|
@@ -111,12 +112,33 @@
|
|
|
111
112
|
try { c = await carpetas(); } catch (e) { caja.innerHTML = `<div class="sug-ayuda error">${esc(e.message)}</div>`; return; }
|
|
112
113
|
if ($('pRuta').value.trim().toLowerCase() !== q) return; // ya se ha escrito otra cosa
|
|
113
114
|
// Primero las que empiezan por lo escrito, luego las más cortas
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
if (S.limitado && q.length >= 2 && !B.enAws?.has(q)) buscarEnAws(q);
|
|
116
|
+
const orden = (a, b) => (b.toLowerCase().startsWith(q) - a.toLowerCase().startsWith(q)) || a.split('/').length - b.split('/').length || a.localeCompare(b);
|
|
117
|
+
const dirs = Object.keys(c).filter(k => k.toLowerCase().includes(q)).sort(orden).slice(0, 200);
|
|
118
|
+
const params = [...(S.nombres || [])].filter(n => !c[n] && n.toLowerCase().includes(q)).sort(orden).slice(0, 100); // los parámetros mismos, después
|
|
119
|
+
B.lista = [...dirs, ...params];
|
|
120
|
+
caja.innerHTML = B.lista.map((k, i) => i < dirs.length
|
|
121
|
+
? `<div class="sug" role="option" data-i="${i}"><code>${resaltar(k, q)}</code><span class="exp-n">${c[k]}</span><button type="button" class="secundario sug-grupo" data-grupo-de="${esc(k)}" title="Guardar esta carpeta como grupo (se detecta dónde va el entorno)" tabindex="-1">+ Grupo</button></div>`
|
|
122
|
+
: `<div class="sug sug-param" role="option" data-i="${i}" title="Parámetro: se carga solo este"><code>${resaltar(k, q)}</code><span class="exp-n">parámetro</span></div>`).join('')
|
|
123
|
+
+ `<div class="sug-ayuda">${B.enAws?.get(q) === 'buscando' ? 'Buscando también en AWS… · ' : ''}${B.lista.length ? '<kbd>↑</kbd> <kbd>↓</kbd> para elegir · «+ Grupo» la guarda en tus grupos · ' : 'Ninguna carpeta ni parámetro con eso. '}<kbd>Intro</kbd> carga ${B.lista.length ? 'la elegida' : 'la ruta tal cual'}${q.startsWith('/') ? '' : ' (las rutas empiezan por /)'}</div>`;
|
|
118
124
|
marcarActiva(B.lista.length && !q.startsWith('/') ? 0 : -1);
|
|
119
125
|
}
|
|
126
|
+
// Con más parámetros de los que caben en la lista, lo escrito se pide a AWS (distingue mayúsculas: tal cual, en minúsculas y en mayúsculas)
|
|
127
|
+
function buscarEnAws(q) {
|
|
128
|
+
B.enAws = B.enAws || new Map(); B.enAws.set(q, 'buscando');
|
|
129
|
+
clearTimeout(B.espera);
|
|
130
|
+
B.espera = setTimeout(async () => {
|
|
131
|
+
try {
|
|
132
|
+
const vistas = await Promise.all([...new Set([q, q.toLowerCase(), q.toUpperCase()])].map(t => api('/api/explorar?contiene=' + encodeURIComponent(t))));
|
|
133
|
+
vistas.forEach(v => v.nombres.forEach(n => {
|
|
134
|
+
if (S.nombres.has(n)) return; S.nombres.add(n);
|
|
135
|
+
const seg = n.split('/').filter(Boolean); for (let i = 1; i < seg.length; i++) { const k = '/' + seg.slice(0, i).join('/'); S.carpetas[k] = (S.carpetas[k] || 0) + 1; }
|
|
136
|
+
}));
|
|
137
|
+
B.enAws.set(q, 'hecho');
|
|
138
|
+
} catch (e) { B.enAws.delete(q); toast(e.message, true); }
|
|
139
|
+
if ($('pRuta').value.trim().toLowerCase() === q) sugerir();
|
|
140
|
+
}, 400);
|
|
141
|
+
}
|
|
120
142
|
// «+ Grupo»: guarda la carpeta en la config detectando dónde va el entorno (lo mismo que Explorar y Ajustes)
|
|
121
143
|
async function anadirGrupo(carpeta) {
|
|
122
144
|
try {
|
|
@@ -155,7 +177,7 @@
|
|
|
155
177
|
// ------------------------------------------------ cargar y pintar
|
|
156
178
|
async function cargar() {
|
|
157
179
|
const ruta = $('pRuta').value.trim();
|
|
158
|
-
if (!ruta
|
|
180
|
+
if (!ruta) return toast('Escribe una ruta o el nombre de un parámetro', true);
|
|
159
181
|
$('pEstado').textContent = `Leyendo ${ruta} de AWS…`; $('pLista').innerHTML = ''; $('pBarra').hidden = true;
|
|
160
182
|
try {
|
|
161
183
|
const r = await post('/api/cargar', { ruta });
|
|
@@ -365,7 +387,7 @@
|
|
|
365
387
|
const hechos = new Set(res.resultados.filter(x => x.ok).map(x => x.name));
|
|
366
388
|
const sinHacer = pendientes().filter(p => !hechos.has(nombreEnvio(p))).map(p => ({ ...p, name: nombreEnvio(p) }));
|
|
367
389
|
const conflictos = new Set([...r.plan.filter(f => f.conflicto).map(f => f.name), ...mal.filter(x => /^Conflicto/.test(x.error)).map(x => x.name)]);
|
|
368
|
-
S.items = []; S.carpetas = null;
|
|
390
|
+
S.items = []; S.carpetas = null; B.enAws = null;
|
|
369
391
|
await cargar();
|
|
370
392
|
if (sinHacer.length && S.ruta) { reponer(S.items, sinHacer, conflictos); pintar(); }
|
|
371
393
|
toast(mal.length ? `${ok} aplicados, ${mal.length} con error` : `${ok} cambios aplicados`, !!mal.length);
|
|
@@ -500,28 +522,46 @@
|
|
|
500
522
|
modal.abrir('<h2>Explorar</h2><p>Leyendo los nombres (sin valores)…</p>');
|
|
501
523
|
let r;
|
|
502
524
|
try { r = await api('/api/explorar'); } catch (e) { return modal.abrir(`<h2>Explorar</h2><p class="error">${esc(e.message)}</p>`); }
|
|
525
|
+
const nombres = new Set(r.nombres); // con más de los que caben, lo que se filtra se pide también a AWS y se añade aquí
|
|
503
526
|
const arbol = q => {
|
|
504
527
|
const raiz = {};
|
|
505
|
-
|
|
528
|
+
[...nombres].filter(n => !q || n.toLowerCase().includes(q)).forEach(n => {
|
|
529
|
+
const seg = n.split('/').filter(Boolean);
|
|
530
|
+
if (seg.length === 1) { raiz[n] = raiz[n] || { _n: 1, _hoja: n }; return; } // sin carpeta: el parámetro mismo
|
|
506
531
|
let nodo = raiz;
|
|
507
|
-
|
|
532
|
+
seg.slice(0, -1).forEach(x => { nodo[x] = nodo[x] || { _n: 0 }; nodo[x]._n++; nodo = nodo[x]; });
|
|
508
533
|
});
|
|
509
534
|
return raiz;
|
|
510
535
|
};
|
|
511
|
-
const fila = (k, ruta, n, nivel) => {
|
|
536
|
+
const fila = (k, ruta, n, nivel, hoja) => {
|
|
512
537
|
const e = entornoDe(k);
|
|
513
538
|
return `<span class="exp-nombre nivel-${Math.min(nivel, 3)}${e ? ' entorno ' + claseEntorno(e) : ''}">${esc(k)}</span><span class="exp-n" title="Parámetros por debajo">${n}</span>
|
|
514
|
-
<span class="exp-acciones"><button type="button" class="secundario" data-ruta="${esc(ruta)}">Cargar</button
|
|
539
|
+
<span class="exp-acciones"><button type="button" class="secundario" data-ruta="${esc(ruta)}">Cargar</button>${hoja ? '' : `<button type="button" class="secundario" data-grupo-de="${esc(ruta)}" title="Guardar esta carpeta como grupo (se detecta dónde va el entorno)">+ Grupo</button>`}</span>`;
|
|
515
540
|
};
|
|
516
|
-
const rama = (nodo, ruta, nivel, abierto) => Object.keys(nodo).filter(k => k !== '_n').sort((a, b) => a.localeCompare(b, 'es', { sensitivity: 'base' })).map(k => {
|
|
517
|
-
const r2 = `${ruta}/${k}`, hijos = rama(nodo[k], r2, nivel + 1, abierto);
|
|
518
|
-
return hijos ? `<details class="rama" ${abierto ? 'open' : ''}><summary class="exp-fila">${fila(k, r2, nodo[k]._n, nivel)}</summary><div class="hijos">${hijos}</div></details>`
|
|
519
|
-
: `<div class="exp-fila exp-hoja">${fila(k, r2, nodo[k]._n, nivel)}</div>`;
|
|
541
|
+
const rama = (nodo, ruta, nivel, abierto) => Object.keys(nodo).filter(k => k !== '_n' && k !== '_hoja').sort((a, b) => a.localeCompare(b, 'es', { sensitivity: 'base' })).map(k => {
|
|
542
|
+
const r2 = nodo[k]._hoja || `${ruta}/${k}`, hijos = rama(nodo[k], r2, nivel + 1, abierto);
|
|
543
|
+
return hijos ? `<details class="rama" ${abierto ? 'open' : ''}><summary class="exp-fila">${fila(k, r2, nodo[k]._n, nivel, nodo[k]._hoja)}</summary><div class="hijos">${hijos}</div></details>`
|
|
544
|
+
: `<div class="exp-fila exp-hoja">${fila(k, r2, nodo[k]._n, nivel, nodo[k]._hoja)}</div>`;
|
|
520
545
|
}).join('');
|
|
521
|
-
modal.abrir(`<h2>Explorar · ${r.nombres.length} parámetros</h2><p>Solo nombres. «Cargar» abre la carpeta; «+ Grupo» la guarda en tu configuración para elegirla luego con su entorno.${r.limitado ? ' <span class="warn">Hay más de los que se muestran:
|
|
546
|
+
modal.abrir(`<h2>Explorar · ${r.nombres.length} parámetros</h2><p>Solo nombres. «Cargar» abre la carpeta; «+ Grupo» la guarda en tu configuración para elegirla luego con su entorno.${r.limitado ? ' <span class="warn">Hay más de los que se muestran: al filtrar se buscan también en AWS.</span>' : ''}</p>
|
|
522
547
|
<div class="exp-cab"><input type="search" id="pExpFiltro" placeholder="Filtrar rutas… (p. ej. rds, api)" ${NOFILL}><span class="leyenda">${cfg().entornos.map(e => `<span class="entorno ${claseEntorno(e)}">${esc(e)}</span>`).join('')}</span></div>
|
|
523
548
|
<div class="arbol" id="pArbol">${rama(arbol(''), '', 0, false) || '<p class="muted">No hay parámetros.</p>'}</div>`);
|
|
524
|
-
|
|
549
|
+
let buscando = false, espera;
|
|
550
|
+
const pintarArbol = () => { const q = $('pExpFiltro').value.trim().toLowerCase(); $('pArbol').innerHTML = rama(arbol(q), '', 0, !!q) || `<p class="muted">${buscando ? 'Buscando en AWS…' : 'Ninguna ruta con eso.'}</p>`; };
|
|
551
|
+
$('pExpFiltro').addEventListener('input', () => {
|
|
552
|
+
pintarArbol();
|
|
553
|
+
const q = $('pExpFiltro').value.trim();
|
|
554
|
+
clearTimeout(espera);
|
|
555
|
+
if (!r.limitado || q.length < 2) return;
|
|
556
|
+
buscando = true; pintarArbol();
|
|
557
|
+
espera = setTimeout(async () => { // AWS distingue mayúsculas: tal cual, en minúsculas y en mayúsculas
|
|
558
|
+
try {
|
|
559
|
+
const vistas = await Promise.all([...new Set([q, q.toLowerCase(), q.toUpperCase()])].map(t => api('/api/explorar?contiene=' + encodeURIComponent(t))));
|
|
560
|
+
vistas.forEach(v => v.nombres.forEach(n => nombres.add(n)));
|
|
561
|
+
} catch (e) { toast(e.message, true); }
|
|
562
|
+
buscando = false; if ($('pExpFiltro')?.value.trim() === q) pintarArbol();
|
|
563
|
+
}, 400);
|
|
564
|
+
});
|
|
525
565
|
$('pExpFiltro').focus();
|
|
526
566
|
$('pArbol').addEventListener('click', async e => {
|
|
527
567
|
const g = e.target.closest('[data-grupo-de]');
|
|
@@ -472,3 +472,22 @@ def test_js_reponer_lo_no_aplicado(tmp_path):
|
|
|
472
472
|
assert por["/r/d"]["borrar"] is True
|
|
473
473
|
# Lo que ya no está en AWS no se inventa
|
|
474
474
|
assert "/r/ya-no-esta" not in por and len(out) == 6
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
# ---------------------------------------------------------------- buscar un parámetro
|
|
478
|
+
|
|
479
|
+
def test_en_ruta_vale_el_nombre_de_un_parametro_o_uno_sin_carpeta(cliente, ssm):
|
|
480
|
+
ssm.poner("/dev/app/clave", "x1")
|
|
481
|
+
ssm.poner("SinCarpeta", "raiz", tipo="String")
|
|
482
|
+
assert list(cargar(cliente, "/dev/app/clave")) == ["/dev/app/clave"] # el nombre completo, no su carpeta
|
|
483
|
+
assert cargar(cliente, "/dev/app/clave")["/dev/app/clave"]["value"] == "x1"
|
|
484
|
+
assert cargar(cliente, "SinCarpeta")["SinCarpeta"]["value"] == "raiz"
|
|
485
|
+
assert cargar(cliente, "/dev/no/existe") == {}
|
|
486
|
+
assert cargar(cliente, "NoExiste") == {}
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
def test_explorar_busca_en_aws_lo_que_no_cabe_en_la_lista(cliente, ssm):
|
|
490
|
+
ssm.poner("/dev/app/clave", "x1")
|
|
491
|
+
ssm.poner("/prod/otra/Cosa", "x2")
|
|
492
|
+
r = cliente.get("/api/explorar?contiene=Cosa").get_json()
|
|
493
|
+
assert r["nombres"] == ["/prod/otra/Cosa"]
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "2.5.1"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|