Spaces:
Running
Running
env-builder UX, gateway configurability, and HF Spaces runtime robustness
Browse filesImprove Jupyter security, env-builder UX, gateway configurability, and HF Spaces runtime robustness
- env-builder.html +2 -1
- env-builder.js +21 -6
- start.sh +2 -2
env-builder.html
CHANGED
|
@@ -908,7 +908,8 @@ body {
|
|
| 908 |
<span class="pblock-title">📦 Bundle Output</span>
|
| 909 |
</div>
|
| 910 |
<div class="pblock-body">
|
| 911 |
-
<
|
|
|
|
| 912 |
<input type="text" id="envLineOut" placeholder="HUGGINGCLAW_ENV_BUNDLE=…" readonly spellcheck="false">
|
| 913 |
<div class="row-btns">
|
| 914 |
<button id="copyBundle" class="btn btn-amber">⎘ Bundle</button>
|
|
|
|
| 908 |
<span class="pblock-title">📦 Bundle Output</span>
|
| 909 |
</div>
|
| 910 |
<div class="pblock-body">
|
| 911 |
+
<button id="generateBundle" class="btn btn-amber" style="width:100%;font-size:12.5px;"># Generate Bundle</button>
|
| 912 |
+
<textarea id="bundleOut" placeholder="Click # Generate to build your bundle…" readonly spellcheck="false"></textarea>
|
| 913 |
<input type="text" id="envLineOut" placeholder="HUGGINGCLAW_ENV_BUNDLE=…" readonly spellcheck="false">
|
| 914 |
<div class="row-btns">
|
| 915 |
<button id="copyBundle" class="btn btn-amber">⎘ Bundle</button>
|
env-builder.js
CHANGED
|
@@ -2104,7 +2104,7 @@ function valueControlHTML(field) {
|
|
| 2104 |
return control;
|
| 2105 |
}
|
| 2106 |
|
| 2107 |
-
function cardHTML(f) {
|
| 2108 |
const TAG_META = {
|
| 2109 |
critical: { cls: 'badge-critical', lbl: 'critical' },
|
| 2110 |
credential: { cls: 'badge-credential', lbl: 'credential' },
|
|
@@ -2116,7 +2116,7 @@ function cardHTML(f) {
|
|
| 2116 |
const tm = TAG_META[f.tag] || TAG_META.optional;
|
| 2117 |
const badge = `<span class="badge ${tm.cls}">${tm.lbl}</span>`;
|
| 2118 |
|
| 2119 |
-
return `<div class="env-card" data-row data-group="${esc(f.g)}" data-search="${esc((f.g + ' ' + f.k + ' ' + (f.lbl || '') + ' ' + (f.tag || '')).toLowerCase())}">
|
| 2120 |
<div class="card-top">
|
| 2121 |
<input type="checkbox" class="card-check" data-check="${esc(f.k)}" ${f.common ? 'data-common="1"' : ''}>
|
| 2122 |
<div class="card-info">
|
|
@@ -2194,14 +2194,17 @@ function collect() {
|
|
| 2194 |
return obj;
|
| 2195 |
}
|
| 2196 |
|
| 2197 |
-
function
|
| 2198 |
const obj = collect();
|
| 2199 |
const keys = Object.keys(obj).sort();
|
| 2200 |
const bundle = keys.length ? encodeBundle(Object.fromEntries(keys.map(k => [k, obj[k]]))) : '';
|
| 2201 |
-
|
| 2202 |
$('bundleOut').value = bundle;
|
| 2203 |
$('envLineOut').value = bundle ? `HUGGINGCLAW_ENV_BUNDLE=${bundle}` : '';
|
|
|
|
| 2204 |
|
|
|
|
|
|
|
|
|
|
| 2205 |
const s = $('summary');
|
| 2206 |
if (keys.length) {
|
| 2207 |
s.innerHTML = `<strong>${keys.length}</strong> variable${keys.length > 1 ? 's' : ''} selected<div class="sum-keys">${keys.map(k => `<span class="sum-key">${esc(k)}</span>`).join('')}</div>`;
|
|
@@ -2379,13 +2382,24 @@ function toggleField(key) {
|
|
| 2379 |
const chk = document.querySelector(`[data-check="${CSS.escape(key)}"]`);
|
| 2380 |
if (chk) {
|
| 2381 |
chk.checked = on;
|
|
|
|
| 2382 |
markSelected();
|
| 2383 |
}
|
| 2384 |
refresh();
|
| 2385 |
}
|
| 2386 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2387 |
function bindFieldEvents() {
|
| 2388 |
-
document.querySelectorAll('[data-check]').forEach(el => el.addEventListener('change', () => { markSelected(); refresh(); }));
|
| 2389 |
document.querySelectorAll('[data-key]').forEach(el => el.addEventListener('input', refresh));
|
| 2390 |
document.querySelectorAll('[data-toggle]').forEach(btn => btn.addEventListener('click', () => toggleField(btn.dataset.toggle)));
|
| 2391 |
document.querySelectorAll('[data-pick-for]').forEach(sel => sel.addEventListener('change', () => handlePickerChange(sel)));
|
|
@@ -2410,7 +2424,7 @@ function renderSections() {
|
|
| 2410 |
<span class="sec-count">${items.length}</span>
|
| 2411 |
<div class="sec-line"></div>
|
| 2412 |
</div>
|
| 2413 |
-
<div class="cards">${items.map(cardHTML).join('')}</div>`;
|
| 2414 |
wrap.appendChild(sec);
|
| 2415 |
});
|
| 2416 |
bindFieldEvents();
|
|
@@ -2503,6 +2517,7 @@ $('applyBundle').onclick = () => {
|
|
| 2503 |
showToast('Invalid bundle');
|
| 2504 |
}
|
| 2505 |
};
|
|
|
|
| 2506 |
$('copyBundle').onclick = () => copyText($('bundleOut').value);
|
| 2507 |
$('copyEnvLine').onclick = () => copyText($('envLineOut').value);
|
| 2508 |
$('copyJson').onclick = () => copyText(JSON.stringify(collect(), null, 2));
|
|
|
|
| 2104 |
return control;
|
| 2105 |
}
|
| 2106 |
|
| 2107 |
+
function cardHTML(f, origIdx = 0) {
|
| 2108 |
const TAG_META = {
|
| 2109 |
critical: { cls: 'badge-critical', lbl: 'critical' },
|
| 2110 |
credential: { cls: 'badge-credential', lbl: 'credential' },
|
|
|
|
| 2116 |
const tm = TAG_META[f.tag] || TAG_META.optional;
|
| 2117 |
const badge = `<span class="badge ${tm.cls}">${tm.lbl}</span>`;
|
| 2118 |
|
| 2119 |
+
return `<div class="env-card" data-row data-orig-idx="${origIdx}" data-group="${esc(f.g)}" data-search="${esc((f.g + ' ' + f.k + ' ' + (f.lbl || '') + ' ' + (f.tag || '')).toLowerCase())}">
|
| 2120 |
<div class="card-top">
|
| 2121 |
<input type="checkbox" class="card-check" data-check="${esc(f.k)}" ${f.common ? 'data-common="1"' : ''}>
|
| 2122 |
<div class="card-info">
|
|
|
|
| 2194 |
return obj;
|
| 2195 |
}
|
| 2196 |
|
| 2197 |
+
function generateBundle() {
|
| 2198 |
const obj = collect();
|
| 2199 |
const keys = Object.keys(obj).sort();
|
| 2200 |
const bundle = keys.length ? encodeBundle(Object.fromEntries(keys.map(k => [k, obj[k]]))) : '';
|
|
|
|
| 2201 |
$('bundleOut').value = bundle;
|
| 2202 |
$('envLineOut').value = bundle ? `HUGGINGCLAW_ENV_BUNDLE=${bundle}` : '';
|
| 2203 |
+
}
|
| 2204 |
|
| 2205 |
+
function refresh() {
|
| 2206 |
+
const obj = collect();
|
| 2207 |
+
const keys = Object.keys(obj).sort();
|
| 2208 |
const s = $('summary');
|
| 2209 |
if (keys.length) {
|
| 2210 |
s.innerHTML = `<strong>${keys.length}</strong> variable${keys.length > 1 ? 's' : ''} selected<div class="sum-keys">${keys.map(k => `<span class="sum-key">${esc(k)}</span>`).join('')}</div>`;
|
|
|
|
| 2382 |
const chk = document.querySelector(`[data-check="${CSS.escape(key)}"]`);
|
| 2383 |
if (chk) {
|
| 2384 |
chk.checked = on;
|
| 2385 |
+
sortSection(inp.closest('[data-row]'));
|
| 2386 |
markSelected();
|
| 2387 |
}
|
| 2388 |
refresh();
|
| 2389 |
}
|
| 2390 |
|
| 2391 |
+
function sortSection(cardEl) {
|
| 2392 |
+
const cards = cardEl && cardEl.closest('.cards');
|
| 2393 |
+
if (!cards) return;
|
| 2394 |
+
const all = [...cards.querySelectorAll('[data-row]')];
|
| 2395 |
+
const checked = all.filter(c => c.querySelector('[data-check]')?.checked);
|
| 2396 |
+
const rest = all.filter(c => !c.querySelector('[data-check]')?.checked);
|
| 2397 |
+
rest.sort((a, b) => Number(a.dataset.origIdx) - Number(b.dataset.origIdx));
|
| 2398 |
+
[...checked, ...rest].forEach(c => cards.appendChild(c));
|
| 2399 |
+
}
|
| 2400 |
+
|
| 2401 |
function bindFieldEvents() {
|
| 2402 |
+
document.querySelectorAll('[data-check]').forEach(el => el.addEventListener('change', () => { sortSection(el.closest('[data-row]')); markSelected(); refresh(); }));
|
| 2403 |
document.querySelectorAll('[data-key]').forEach(el => el.addEventListener('input', refresh));
|
| 2404 |
document.querySelectorAll('[data-toggle]').forEach(btn => btn.addEventListener('click', () => toggleField(btn.dataset.toggle)));
|
| 2405 |
document.querySelectorAll('[data-pick-for]').forEach(sel => sel.addEventListener('change', () => handlePickerChange(sel)));
|
|
|
|
| 2424 |
<span class="sec-count">${items.length}</span>
|
| 2425 |
<div class="sec-line"></div>
|
| 2426 |
</div>
|
| 2427 |
+
<div class="cards">${items.map((f, i) => cardHTML(f, i)).join('')}</div>`;
|
| 2428 |
wrap.appendChild(sec);
|
| 2429 |
});
|
| 2430 |
bindFieldEvents();
|
|
|
|
| 2517 |
showToast('Invalid bundle');
|
| 2518 |
}
|
| 2519 |
};
|
| 2520 |
+
$('generateBundle').onclick = () => generateBundle();
|
| 2521 |
$('copyBundle').onclick = () => copyText($('bundleOut').value);
|
| 2522 |
$('copyEnvLine').onclick = () => copyText($('envLineOut').value);
|
| 2523 |
$('copyJson').onclick = () => copyText(JSON.stringify(collect(), null, 2));
|
start.sh
CHANGED
|
@@ -323,7 +323,7 @@ CONFIG_JSON=$(cat <<'CONFIGEOF'
|
|
| 323 |
{
|
| 324 |
"gateway": {
|
| 325 |
"mode": "local",
|
| 326 |
-
"port":
|
| 327 |
"bind": "lan",
|
| 328 |
"auth": {
|
| 329 |
"token": ""
|
|
@@ -1653,7 +1653,7 @@ while true; do
|
|
| 1653 |
stdbuf -oL -eL openclaw "${GATEWAY_ARGS[@]}" 2>&1 | tee -a /home/node/.openclaw/gateway.log &
|
| 1654 |
GATEWAY_PID=$!
|
| 1655 |
|
| 1656 |
-
# Poll for the gateway to start listening on
|
| 1657 |
# on cold start (plugin install + auto-restore). Bail out early if the
|
| 1658 |
# pipeline died.
|
| 1659 |
GATEWAY_READY_TIMEOUT="${GATEWAY_READY_TIMEOUT:-90}"
|
|
|
|
| 323 |
{
|
| 324 |
"gateway": {
|
| 325 |
"mode": "local",
|
| 326 |
+
"port": "${GATEWAY_PORT}",
|
| 327 |
"bind": "lan",
|
| 328 |
"auth": {
|
| 329 |
"token": ""
|
|
|
|
| 1653 |
stdbuf -oL -eL openclaw "${GATEWAY_ARGS[@]}" 2>&1 | tee -a /home/node/.openclaw/gateway.log &
|
| 1654 |
GATEWAY_PID=$!
|
| 1655 |
|
| 1656 |
+
# Poll for the gateway to start listening on ${GATEWAY_PORT}. OpenClaw can take 20-30s
|
| 1657 |
# on cold start (plugin install + auto-restore). Bail out early if the
|
| 1658 |
# pipeline died.
|
| 1659 |
GATEWAY_READY_TIMEOUT="${GATEWAY_READY_TIMEOUT:-90}"
|