lampson 0.2.1 → 0.2.2
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/package.json +1 -1
- package/public/css/panel.css +18 -0
- package/public/js/procs.js +2 -1
- package/public/js/workspaces.js +34 -20
package/package.json
CHANGED
package/public/css/panel.css
CHANGED
|
@@ -132,3 +132,21 @@
|
|
|
132
132
|
.panel .bdetail .dform label.col { max-width:560px; }
|
|
133
133
|
.panel .bdetail .dacts .ds { display:inline; color:var(--ink-3); }
|
|
134
134
|
.panel .bdetail .dfoot { font-family:var(--mono); }
|
|
135
|
+
|
|
136
|
+
/* nuevo workspace: elegir carpeta → carpeta elegida → crear */
|
|
137
|
+
.panel .pickrow { display:flex; align-items:center; gap:12px; margin:6px 0 12px; }
|
|
138
|
+
.panel .pickrow .ds { display:inline; margin:0; }
|
|
139
|
+
.panel .pickrow a { color:var(--accent); text-decoration:none; }
|
|
140
|
+
.panel .pickrow a:hover { text-decoration:underline; text-underline-offset:.18em; }
|
|
141
|
+
.panel .chosen { display:flex; align-items:center; gap:10px; padding:10px 12px; border:1px solid var(--rule-2); border-radius:var(--r); background:var(--paper-2); margin:0 0 6px; max-width:640px; }
|
|
142
|
+
.panel .chosen .lk { font:400 10.5px/1 var(--mono); text-transform:uppercase; letter-spacing:.1em; color:var(--ink-3); }
|
|
143
|
+
.panel .chosen code { font:400 12.5px/1.4 var(--mono); color:var(--ink); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; flex:1; }
|
|
144
|
+
.panel .chosen .del { color:var(--ink-3); cursor:pointer; font-size:11px; }
|
|
145
|
+
.panel .chosen .del:hover { color:var(--rubric); }
|
|
146
|
+
.panel .wsbrowser { border:1px solid var(--rule); border-radius:var(--r); max-width:640px; margin:0 0 10px; display:flex; flex-direction:column; }
|
|
147
|
+
.panel .wsbrowser .bhead { display:flex; align-items:center; gap:10px; padding:8px 10px; border-bottom:1px solid var(--rule); }
|
|
148
|
+
.panel .wsbrowser .bhead .ds { flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-family:var(--mono); color:var(--ink); display:inline; }
|
|
149
|
+
.panel .wsbrowser .bhead input { flex:0 0 240px; height:28px; padding:0 8px; font-size:12px; background:transparent; }
|
|
150
|
+
.panel .wsbrowser .bitems { max-height:30vh; overflow-y:auto; padding:4px; }
|
|
151
|
+
.panel .wsbrowser .bfoot { padding:8px 10px; border-top:1px solid var(--rule); display:flex; justify-content:flex-end; }
|
|
152
|
+
.panel .wsbrowser .bfoot button { font:400 12px/1 var(--mono); padding:6px 12px; }
|
package/public/js/procs.js
CHANGED
|
@@ -3,7 +3,8 @@ let procOpen = null, procTimer = null;
|
|
|
3
3
|
async function loadPorts() {
|
|
4
4
|
let r; try { r = await (await fetch(BASE + '/api/ports')).json(); } catch (e) { return; }
|
|
5
5
|
const box = $('#ports'); box.innerHTML = '';
|
|
6
|
-
|
|
6
|
+
// los procesos de lampson (hub :8080 y los workspaces :808N) no son «servidores del proyecto»
|
|
7
|
+
const list = (r.ports || []).filter(p => !(p.name === 'synsema.exe' || p.name === 'synsema') || !/\b(web|hub)\.syn\b/.test(p.command || '') && p.port !== 8080);
|
|
7
8
|
$('#portCount').textContent = list.length || ''; autoSec('ports', list.length > 0);
|
|
8
9
|
if (!list.length) { box.innerHTML = '<div class="p none">ninguno</div>'; return; }
|
|
9
10
|
for (const p of list) {
|
package/public/js/workspaces.js
CHANGED
|
@@ -44,40 +44,54 @@ function openWorkspaces(selectSlug) {
|
|
|
44
44
|
function wsNewForm() {
|
|
45
45
|
return `<div class="dhead"><span class="nm serif">Nuevo workspace</span></div><div class="dform">
|
|
46
46
|
<p class="lead">Un workspace es una carpeta de tu disco: el agente solo puede tocar lo que hay adentro. Corre en su propio proceso, con sus sesiones, tareas programadas, MCP y lámparas.</p>
|
|
47
|
-
<div class="
|
|
48
|
-
<
|
|
49
|
-
<div data-browser
|
|
50
|
-
|
|
47
|
+
<div class="pickrow"><button class="primary" data-pick>Elegir carpeta…</button><span class="ds">se abre el diálogo de carpetas de tu sistema · <a href="#" data-browse>o explorá desde acá</a></span></div>
|
|
48
|
+
<div class="chosen" data-chosen style="display:none"><span class="lk">carpeta</span><code data-chosen-path></code><span class="del" data-clear title="elegir otra">✕</span></div>
|
|
49
|
+
<div data-browser class="wsbrowser" style="display:none">
|
|
50
|
+
<div class="bhead"><span class="ds" data-here></span><input name="path" spellcheck="false" autocomplete="off" placeholder="o pegá una ruta y Enter"></div>
|
|
51
|
+
<div class="bitems" data-dirs></div>
|
|
52
|
+
<div class="bfoot"><button data-use>Usar esta carpeta</button></div>
|
|
53
|
+
</div>
|
|
54
|
+
<div class="pfoot" data-go-row style="display:none;justify-content:flex-end"><span class="derr" style="margin:0 auto 0 0"></span><button class="primary" data-go>Crear y abrir</button></div>
|
|
55
|
+
<div class="derr" data-err2></div></div>`;
|
|
51
56
|
}
|
|
52
|
-
function wsNewWire(box,
|
|
57
|
+
function wsNewWire(box, err0) {
|
|
58
|
+
const chosenBox = box.querySelector('[data-chosen]'), goRow = box.querySelector('[data-go-row]'), browser = box.querySelector('[data-browser]');
|
|
53
59
|
const f = n => box.querySelector(`[name="${n}"]`);
|
|
54
|
-
const
|
|
60
|
+
const err = (m) => { const e = goRow.style.display === 'none' ? box.querySelector('[data-err2]') : goRow.querySelector('.derr'); box.querySelectorAll('.derr').forEach(x => x.textContent = ''); if (e) e.textContent = m || ''; };
|
|
61
|
+
let chosen = '';
|
|
62
|
+
const choose = (p) => { chosen = (p || '').trim(); chosenBox.style.display = chosen ? '' : 'none'; goRow.style.display = chosen ? 'flex' : 'none'; box.querySelector('[data-chosen-path]').textContent = chosen; if (chosen) { browser.style.display = 'none'; err(''); } };
|
|
63
|
+
let here = '';
|
|
55
64
|
const showDir = async (p) => {
|
|
65
|
+
err('');
|
|
56
66
|
const r = await (await fetch('/api/workspaces/browse?path=' + encodeURIComponent(p || ''))).json();
|
|
57
67
|
browser.style.display = '';
|
|
58
|
-
box.querySelector('[data-here]').textContent =
|
|
59
|
-
f('path').value = r.path || '';
|
|
68
|
+
here = r.path || ''; box.querySelector('[data-here]').textContent = here + (r.error ? ' — ' + r.error : ''); f('path').value = '';
|
|
60
69
|
const list = box.querySelector('[data-dirs]'); list.innerHTML = '';
|
|
61
|
-
const up = document.createElement('div'); up.className = 'li nodot'; up.innerHTML = '<div><div class="nm">..</div></div>'; up.onclick = () => showDir(r.parent); list.appendChild(up);
|
|
62
|
-
for (const d of (r.dirs || [])) { const it = document.createElement('div'); it.className = 'li nodot'; it.innerHTML = `<div><div class="nm" style="font-weight:400">${esc(d)}/</div></div>`; it.onclick = () => showDir(
|
|
70
|
+
const up = document.createElement('div'); up.className = 'li nodot'; up.innerHTML = '<div><div class="nm">..</div><div class="meta">subir un nivel</div></div>'; up.onclick = () => showDir(r.parent); list.appendChild(up);
|
|
71
|
+
for (const d of (r.dirs || [])) { const it = document.createElement('div'); it.className = 'li nodot'; it.innerHTML = `<div><div class="nm" style="font-weight:400">${esc(d)}/</div></div>`; it.onclick = () => showDir(here.replace(/[\\/]$/, '') + (here.includes('\\') ? '\\' : '/') + d); list.appendChild(it); }
|
|
63
72
|
};
|
|
64
|
-
box.querySelector('[data-browse]').onclick = () => showDir(
|
|
73
|
+
box.querySelector('[data-browse]').onclick = (e) => { e.preventDefault(); showDir(chosen || ''); };
|
|
74
|
+
box.querySelector('[data-use]').onclick = () => choose(here);
|
|
75
|
+
f('path').addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); const v = f('path').value.trim(); if (v) showDir(v); } });
|
|
76
|
+
box.querySelector('[data-clear]').onclick = () => choose('');
|
|
65
77
|
box.querySelector('[data-pick]').onclick = async () => {
|
|
66
|
-
err('esperando el diálogo de carpetas… (si no
|
|
78
|
+
err('esperando el diálogo de carpetas… (si no lo ves, mirá la barra de tareas)');
|
|
67
79
|
const r = await api('/api/workspaces/pick', {});
|
|
68
|
-
if (!r.
|
|
69
|
-
|
|
80
|
+
if (!r.ok) { err('esta página no es el hub: abrí ' + location.protocol + '//' + location.hostname + ':8080/'); return; }
|
|
81
|
+
if (!r.data.native) { err('no hay diálogo en este equipo (VPS): explorá desde acá'); showDir(''); return; }
|
|
82
|
+
err(''); if (r.data.path) choose(r.data.path);
|
|
70
83
|
};
|
|
71
84
|
box.querySelector('[data-go]').onclick = async () => {
|
|
72
|
-
|
|
85
|
+
if (!chosen) { err('elegí una carpeta'); return; }
|
|
73
86
|
err('creando y arrancando… (el hub se reinicia un instante)');
|
|
74
|
-
const r = await api('/api/workspaces', { path });
|
|
87
|
+
const r = await api('/api/workspaces', { path: chosen });
|
|
75
88
|
if (!r.ok) { err(r.data.error || ('error ' + r.status)); return; }
|
|
76
|
-
// el hub se regenera con la ruta nueva y se reinicia
|
|
89
|
+
// el hub se regenera con la ruta nueva y se reinicia: esperar a que vuelva y navegar
|
|
77
90
|
const url = r.data.url; let tries = 0;
|
|
78
|
-
const poll = async () => { tries++; try { const h = await fetch(
|
|
91
|
+
const poll = async () => { tries++; try { const h = await fetch('/api/hub'); if (h.ok && tries > 2) { location.href = url; return; } } catch (e) {} if (tries < 40) setTimeout(poll, 500); else err('el hub no volvió: abrí ' + url + ' a mano'); };
|
|
79
92
|
setTimeout(poll, 1200);
|
|
80
93
|
};
|
|
81
|
-
f('path').focus();
|
|
82
94
|
}
|
|
83
|
-
|
|
95
|
+
// standalone (abierto por el puerto del workspace, no por el hub): la pill lleva al hub
|
|
96
|
+
const HUB_URL = (location.port && location.port !== '8080') ? location.protocol + '//' + location.hostname + ':8080/' : '';
|
|
97
|
+
if ($('#wsPill')) { if (HUB_URL) { const pill = $('#wsPill'); pill.style.display = ''; pill.textContent = 'workspaces ↗'; pill.title = 'esta pestaña es el proceso del workspace; los workspaces se gestionan en el hub'; pill.onclick = () => window.open(HUB_URL, '_blank'); } else { $('#wsPill').onclick = () => openWorkspaces(); paintWorkspacePill(); } }
|