@danieltmn/openbridge 0.2.0 → 0.4.0
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/CHANGELOG.md +30 -0
- package/README.md +221 -144
- package/docs/ARCHITECTURE.md +49 -8
- package/package.json +1 -1
- package/src/auth.js +44 -23
- package/src/bridge/bridge.js +77 -1
- package/src/cli.js +155 -9
- package/src/config.js +74 -4
- package/src/web/assets/app.js +286 -43
- package/src/web/assets/manifest.webmanifest +2 -0
- package/src/web/assets/sw.js +1 -1
- package/src/web/routes.js +49 -13
- package/src/web/server.js +7 -0
- package/src/web/templates/chat.html +54 -2
- package/src/web/templates/login.html +5 -1
package/src/web/assets/app.js
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
var CFG = window.APP_CONFIG || {};
|
|
4
4
|
var CSRF = CFG.csrf || '';
|
|
5
5
|
var INITIAL_SESSION = typeof CFG.initialSession === 'number' ? CFG.initialSession : null;
|
|
6
|
+
var IS_ADMIN = String(CFG.userRole || 'user') === 'admin';
|
|
7
|
+
function isAdmin() { return IS_ADMIN; }
|
|
6
8
|
|
|
7
9
|
var els = {
|
|
8
10
|
body: document.body,
|
|
@@ -28,6 +30,8 @@ var els = {
|
|
|
28
30
|
sendBtn: document.getElementById('sendBtn'),
|
|
29
31
|
btnImg: document.getElementById('btnImg'),
|
|
30
32
|
btnMic: document.getElementById('btnMic'),
|
|
33
|
+
btnTpl: document.getElementById('btnTpl'),
|
|
34
|
+
tplPanel: document.getElementById('tplPanel'),
|
|
31
35
|
imgInput: document.getElementById('imgInput'),
|
|
32
36
|
imgPreview: document.getElementById('imgPreview'),
|
|
33
37
|
imgThumb: document.getElementById('imgThumb'),
|
|
@@ -45,6 +49,7 @@ var els = {
|
|
|
45
49
|
btnSkin: document.getElementById('btnSkin'),
|
|
46
50
|
skinPanel: document.getElementById('skinPanel'),
|
|
47
51
|
viewProcs: document.getElementById('viewProcs'),
|
|
52
|
+
viewChanges: document.getElementById('viewChanges'),
|
|
48
53
|
themeColor: document.getElementById('themeColor'),
|
|
49
54
|
themeLink: document.getElementById('themeStylesheet'),
|
|
50
55
|
statusbar: document.getElementById('statusbar'),
|
|
@@ -84,6 +89,12 @@ var els = {
|
|
|
84
89
|
btnCancel: document.getElementById('btnCancel'),
|
|
85
90
|
};
|
|
86
91
|
|
|
92
|
+
// Marca visual del usuario actual (admin en color de acento).
|
|
93
|
+
(function () {
|
|
94
|
+
var ub = document.getElementById('userBadge');
|
|
95
|
+
if (ub) ub.classList.toggle('admin', IS_ADMIN);
|
|
96
|
+
})();
|
|
97
|
+
|
|
87
98
|
var ONLINE_MAX_AGE_MS = 60000;
|
|
88
99
|
var PROC_TIMEOUT_MS = 45000;
|
|
89
100
|
var POLL_BASE_MS = 4000; // refresco pasivo (sin nada pendiente)
|
|
@@ -980,6 +991,7 @@ function showView(name) {
|
|
|
980
991
|
els.viewHistory.style.display = 'none';
|
|
981
992
|
els.viewPreview.style.display = 'none';
|
|
982
993
|
els.viewProcs.style.display = 'none';
|
|
994
|
+
els.viewChanges.style.display = 'none';
|
|
983
995
|
|
|
984
996
|
if (name !== 'procs') stopProcView();
|
|
985
997
|
|
|
@@ -1046,6 +1058,12 @@ function showView(name) {
|
|
|
1046
1058
|
var pf = sessionProcFolder();
|
|
1047
1059
|
els.hSub.textContent = pf ? projectLabel(pf) : 'de la sesión abierta';
|
|
1048
1060
|
enterProcsView();
|
|
1061
|
+
} else if (name === 'changes') {
|
|
1062
|
+
els.viewChanges.style.display = '';
|
|
1063
|
+
els.btnBack.style.display = 'none';
|
|
1064
|
+
els.sendForm.style.display = 'none';
|
|
1065
|
+
els.btnCmds.style.display = 'none';
|
|
1066
|
+
renderChangesView();
|
|
1049
1067
|
}
|
|
1050
1068
|
|
|
1051
1069
|
if (els.fabNew) els.fabNew.classList.toggle('show', name === 'home');
|
|
@@ -1098,6 +1116,7 @@ function composeOpenSheet() {
|
|
|
1098
1116
|
function composeCloseSheet() {
|
|
1099
1117
|
if (!els.sendForm) return;
|
|
1100
1118
|
voiceStop();
|
|
1119
|
+
tplClose();
|
|
1101
1120
|
els.sendForm.classList.remove('open');
|
|
1102
1121
|
document.body.classList.remove('compose-open');
|
|
1103
1122
|
if (els.imgPreview && els.imgPreview.parentNode === els.sendForm && els.composeOpen) {
|
|
@@ -1253,7 +1272,7 @@ function renderHome(sessions) {
|
|
|
1253
1272
|
+ '<div class="cmeta">' + busy + esc(s.model || 'sin modelo') + ' · ' + esc(s.agent || 'build') + '</div>'
|
|
1254
1273
|
+ (s.preview ? '<div class="cpreview">' + prev + '</div>' : '')
|
|
1255
1274
|
+ '</div>'
|
|
1256
|
-
+ '<button type="button" class="kebab" aria-label="Opciones" title="Opciones">⋮</button>'
|
|
1275
|
+
+ (isAdmin() ? '<button type="button" class="kebab" aria-label="Opciones" title="Opciones">⋮</button>' : '')
|
|
1257
1276
|
+ '</div>';
|
|
1258
1277
|
}
|
|
1259
1278
|
html += '</div>';
|
|
@@ -1632,13 +1651,13 @@ function onMsgScroll() {
|
|
|
1632
1651
|
// reemplaza solo ese nodo; lo demás no se toca.
|
|
1633
1652
|
function msgSig(m) {
|
|
1634
1653
|
return (m.status || '') + '|' + (m.text || '').length + '|' + (m.reasoning || '').length
|
|
1635
|
-
+ '|' + (m.canceled ? 1 : 0) + '|' + (m.agent || '') + '|' + (m.img ? m.img.length : 0);
|
|
1654
|
+
+ '|' + (m.canceled ? 1 : 0) + '|' + (m.agent || '') + '|' + (m.author || '') + '|' + (m.img ? m.img.length : 0);
|
|
1636
1655
|
}
|
|
1637
1656
|
|
|
1638
1657
|
function msgNodeHtml(m, chatQuery) {
|
|
1639
1658
|
var cls = m.role === 'user' ? 'mine' : 'theirs';
|
|
1640
1659
|
if (m.status === 'canceled') cls += ' canceled';
|
|
1641
|
-
var who = m.role === 'user' ? '❯ vos' : '● Agente';
|
|
1660
|
+
var who = m.role === 'user' ? '❯ ' + esc(m.author || 'vos') : '● Agente';
|
|
1642
1661
|
if (m.agent === 'plan') cls += ' plan';
|
|
1643
1662
|
var agentBadge = m.agent ? ' <span class="abadge ' + esc(m.agent) + '">' + esc(m.agent) + '</span>' : '';
|
|
1644
1663
|
var stopBadge = m.canceled ? ' <span class="stopbadge">⏹ detenido</span>' : '';
|
|
@@ -2485,6 +2504,85 @@ if (els.btnMic) {
|
|
|
2485
2504
|
}
|
|
2486
2505
|
}
|
|
2487
2506
|
|
|
2507
|
+
// ---------------------------------------------------------------------------
|
|
2508
|
+
// Plantillas de prompts (atajos guardados en el navegador).
|
|
2509
|
+
// ---------------------------------------------------------------------------
|
|
2510
|
+
var TPL_KEY = 'ob_tpl';
|
|
2511
|
+
|
|
2512
|
+
function tplLoad() {
|
|
2513
|
+
try {
|
|
2514
|
+
var a = JSON.parse(localStorage.getItem(TPL_KEY) || '[]');
|
|
2515
|
+
if (!Array.isArray(a)) return [];
|
|
2516
|
+
return a.filter(function (x) { return typeof x === 'string' && x.trim() !== ''; }).slice(0, 30);
|
|
2517
|
+
} catch (e) { return []; }
|
|
2518
|
+
}
|
|
2519
|
+
function tplStore(list) {
|
|
2520
|
+
try { localStorage.setItem(TPL_KEY, JSON.stringify(list)); } catch (e) {}
|
|
2521
|
+
}
|
|
2522
|
+
function tplClose() {
|
|
2523
|
+
if (els.tplPanel) els.tplPanel.style.display = 'none';
|
|
2524
|
+
}
|
|
2525
|
+
function tplRender() {
|
|
2526
|
+
var list = tplLoad();
|
|
2527
|
+
var html = '';
|
|
2528
|
+
for (var i = 0; i < list.length; i++) {
|
|
2529
|
+
html += '<div class="tpl-row" data-tpl="' + i + '"><span class="tpl-text">' + esc(list[i]) + '</span>'
|
|
2530
|
+
+ '<button type="button" class="tpl-del" data-del="' + i + '" title="Borrar plantilla" aria-label="Borrar plantilla">✕</button></div>';
|
|
2531
|
+
}
|
|
2532
|
+
if (!list.length) html += '<div class="tpl-empty">No hay plantillas. Escribí un prompt y tocá “guardar lo escrito”.</div>';
|
|
2533
|
+
html += '<button type="button" class="tpl-save" id="tplSave">+ guardar lo escrito</button>';
|
|
2534
|
+
els.tplPanel.innerHTML = html;
|
|
2535
|
+
}
|
|
2536
|
+
function tplOpen() {
|
|
2537
|
+
closeSlash();
|
|
2538
|
+
tplRender();
|
|
2539
|
+
els.tplPanel.style.display = 'block';
|
|
2540
|
+
}
|
|
2541
|
+
function tplInsert(text) {
|
|
2542
|
+
var cur = els.input.value;
|
|
2543
|
+
els.input.value = cur && cur.trim() !== '' ? (cur.replace(/\s+$/, '') + ' ' + text) : text;
|
|
2544
|
+
tplClose();
|
|
2545
|
+
autoGrow();
|
|
2546
|
+
els.input.focus();
|
|
2547
|
+
try { els.input.setSelectionRange(els.input.value.length, els.input.value.length); } catch (e) {}
|
|
2548
|
+
}
|
|
2549
|
+
function tplSaveCurrent() {
|
|
2550
|
+
var text = els.input.value.trim();
|
|
2551
|
+
if (!text) { toast('escribí un prompt primero', 'error'); return; }
|
|
2552
|
+
var list = tplLoad();
|
|
2553
|
+
if (list.indexOf(text) >= 0) { toast('esa plantilla ya existe', 'ok'); return; }
|
|
2554
|
+
list.unshift(text);
|
|
2555
|
+
tplStore(list.slice(0, 30));
|
|
2556
|
+
toast('plantilla guardada', 'ok');
|
|
2557
|
+
tplRender();
|
|
2558
|
+
}
|
|
2559
|
+
|
|
2560
|
+
if (els.btnTpl && els.tplPanel) {
|
|
2561
|
+
els.btnTpl.addEventListener('click', function () {
|
|
2562
|
+
if (state.currentId === null) return;
|
|
2563
|
+
if (els.tplPanel.style.display === 'block') tplClose();
|
|
2564
|
+
else tplOpen();
|
|
2565
|
+
});
|
|
2566
|
+
els.tplPanel.addEventListener('click', function (e) {
|
|
2567
|
+
var del = e.target.closest('.tpl-del');
|
|
2568
|
+
if (del) {
|
|
2569
|
+
e.stopPropagation();
|
|
2570
|
+
var list = tplLoad();
|
|
2571
|
+
list.splice(parseInt(del.getAttribute('data-del'), 10), 1);
|
|
2572
|
+
tplStore(list);
|
|
2573
|
+
tplRender();
|
|
2574
|
+
return;
|
|
2575
|
+
}
|
|
2576
|
+
if (e.target.closest('.tpl-save')) { tplSaveCurrent(); return; }
|
|
2577
|
+
var row = e.target.closest('.tpl-row');
|
|
2578
|
+
if (row) {
|
|
2579
|
+
var list2 = tplLoad();
|
|
2580
|
+
var t = list2[parseInt(row.getAttribute('data-tpl'), 10)];
|
|
2581
|
+
if (typeof t === 'string') tplInsert(t);
|
|
2582
|
+
}
|
|
2583
|
+
});
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2488
2586
|
// ---------------------------------------------------------------------------
|
|
2489
2587
|
// Composer: textarea multilínea (Enter envía, Shift+Enter salta de línea)
|
|
2490
2588
|
// y menú de comandos "/" con filtro, flechas y Enter.
|
|
@@ -2532,6 +2630,7 @@ els.input.addEventListener('input', function () {
|
|
|
2532
2630
|
autoGrow();
|
|
2533
2631
|
var v = els.input.value;
|
|
2534
2632
|
if (state.view === 'chat' && v.charAt(0) === '/') {
|
|
2633
|
+
tplClose();
|
|
2535
2634
|
slash.items = slashFilter(v);
|
|
2536
2635
|
slash.sel = 0;
|
|
2537
2636
|
renderSlash();
|
|
@@ -2564,7 +2663,8 @@ els.input.addEventListener('keydown', function (e) {
|
|
|
2564
2663
|
}
|
|
2565
2664
|
if (e.key === 'Escape') {
|
|
2566
2665
|
e.stopPropagation();
|
|
2567
|
-
|
|
2666
|
+
if (els.tplPanel && els.tplPanel.style.display === 'block') tplClose();
|
|
2667
|
+
else closeSlash();
|
|
2568
2668
|
return;
|
|
2569
2669
|
}
|
|
2570
2670
|
}
|
|
@@ -2602,6 +2702,7 @@ els.sendForm.addEventListener('submit', async function (ev) {
|
|
|
2602
2702
|
els.input.value = '';
|
|
2603
2703
|
autoGrow();
|
|
2604
2704
|
closeSlash();
|
|
2705
|
+
tplClose();
|
|
2605
2706
|
setPendingImage(null);
|
|
2606
2707
|
await loadHistory(state.currentId, true);
|
|
2607
2708
|
} else {
|
|
@@ -2881,6 +2982,133 @@ function filesJoin(name) {
|
|
|
2881
2982
|
return state.filesPath ? state.filesPath + '/' + name : name;
|
|
2882
2983
|
}
|
|
2883
2984
|
|
|
2985
|
+
// ---------------------------------------------------------------------------
|
|
2986
|
+
// Cambios del proyecto (git status/diff) vía la cola del puente.
|
|
2987
|
+
// ---------------------------------------------------------------------------
|
|
2988
|
+
function changesFolder() {
|
|
2989
|
+
return (state.currentSession && state.currentSession.folder) || '';
|
|
2990
|
+
}
|
|
2991
|
+
|
|
2992
|
+
function renderChangesView() {
|
|
2993
|
+
els.hTitle.textContent = 'cambios';
|
|
2994
|
+
var folder = changesFolder();
|
|
2995
|
+
els.hSub.textContent = folder ? projectLabel(folder) : 'de la sesión abierta';
|
|
2996
|
+
if (!folder) {
|
|
2997
|
+
els.viewChanges.innerHTML = '<div class="placeholder">Abrí un chat para ver los cambios de su proyecto.</div>';
|
|
2998
|
+
return;
|
|
2999
|
+
}
|
|
3000
|
+
if (!state.online) {
|
|
3001
|
+
els.viewChanges.innerHTML = '<div class="placeholder">El puente está apagado. Iniciá “OpenBridge” en tu PC.</div>';
|
|
3002
|
+
return;
|
|
3003
|
+
}
|
|
3004
|
+
els.viewChanges.innerHTML = '<div class="view-head"><h2>cambios sin commitear</h2>'
|
|
3005
|
+
+ '<div class="view-sub">' + esc(projectLabel(folder)) + '</div></div>'
|
|
3006
|
+
+ '<div class="placeholder">consultando git…</div>';
|
|
3007
|
+
loadChanges(folder);
|
|
3008
|
+
}
|
|
3009
|
+
|
|
3010
|
+
function chgStatusClass(st) {
|
|
3011
|
+
if (st === '??' || st === 'A') return 'add';
|
|
3012
|
+
if (st.indexOf('D') >= 0) return 'del';
|
|
3013
|
+
if (st.indexOf('M') >= 0 || st.indexOf('R') >= 0) return 'mod';
|
|
3014
|
+
return '';
|
|
3015
|
+
}
|
|
3016
|
+
|
|
3017
|
+
function renderDiff(text) {
|
|
3018
|
+
var max = 200000;
|
|
3019
|
+
var trunc = false;
|
|
3020
|
+
if (text.length > max) { text = text.slice(0, max); trunc = true; }
|
|
3021
|
+
var lines = text.split('\n');
|
|
3022
|
+
var html = '';
|
|
3023
|
+
for (var i = 0; i < lines.length; i++) {
|
|
3024
|
+
var l = lines[i];
|
|
3025
|
+
var cls = 'diff-line';
|
|
3026
|
+
if (l.charAt(0) === '+') cls += ' add';
|
|
3027
|
+
else if (l.charAt(0) === '-') cls += ' del';
|
|
3028
|
+
else if (l.indexOf('@@') === 0) cls += ' hunk';
|
|
3029
|
+
else if (l.indexOf('diff --git') === 0 || l.indexOf('index ') === 0 || l.indexOf('--- ') === 0 || l.indexOf('+++ ') === 0) cls += ' head';
|
|
3030
|
+
html += '<div class="' + cls + '">' + esc(l || ' ') + '</div>';
|
|
3031
|
+
}
|
|
3032
|
+
if (trunc) html += '<div class="diff-line head">… (diff recortado)</div>';
|
|
3033
|
+
return '<div class="diff">' + html + '</div>';
|
|
3034
|
+
}
|
|
3035
|
+
|
|
3036
|
+
function changesHead(folder, branch) {
|
|
3037
|
+
return '<div class="view-head"><h2>cambios sin commitear</h2><div class="view-sub">'
|
|
3038
|
+
+ esc(projectLabel(folder)) + (branch ? ' · ' + esc(branch) : '') + '</div></div>';
|
|
3039
|
+
}
|
|
3040
|
+
|
|
3041
|
+
async function loadChanges(folder) {
|
|
3042
|
+
var st = await ocCommand('git_status', [folder], 15, 500);
|
|
3043
|
+
if (!st.ok) {
|
|
3044
|
+
els.viewChanges.innerHTML = changesHead(folder, '')
|
|
3045
|
+
+ '<div class="placeholder">' + esc(st.error || 'no se pudo consultar git') + '</div>';
|
|
3046
|
+
return;
|
|
3047
|
+
}
|
|
3048
|
+
var status = st.data || {};
|
|
3049
|
+
var files = status.files || [];
|
|
3050
|
+
if (!files.length) {
|
|
3051
|
+
els.viewChanges.innerHTML = changesHead(folder, status.branch)
|
|
3052
|
+
+ '<div class="placeholder">Sin cambios: el árbol está limpio.</div>';
|
|
3053
|
+
return;
|
|
3054
|
+
}
|
|
3055
|
+
var list = '<div class="chg-files">';
|
|
3056
|
+
for (var i = 0; i < files.length; i++) {
|
|
3057
|
+
var rev = (isAdmin() && files[i].status !== '??')
|
|
3058
|
+
? '<button type="button" class="chg-rev" data-path="' + esc(files[i].path) + '" title="Revertir este archivo" aria-label="Revertir este archivo">↩</button>'
|
|
3059
|
+
: '';
|
|
3060
|
+
list += '<div class="chg-row"><span class="chg-st ' + chgStatusClass(files[i].status) + '">' + esc(files[i].status) + '</span>'
|
|
3061
|
+
+ '<span class="chg-path">' + esc(files[i].path) + '</span>' + rev + '</div>';
|
|
3062
|
+
}
|
|
3063
|
+
list += '</div>';
|
|
3064
|
+
var tracked = files.some(function (f) { return f.status !== '??'; });
|
|
3065
|
+
var toolbar = (isAdmin() && tracked)
|
|
3066
|
+
? '<div class="files-toolbar"><button type="button" class="linkbtn" id="chgRevert">↩ revertir cambios rastreados</button></div>'
|
|
3067
|
+
: '';
|
|
3068
|
+
els.viewChanges.innerHTML = changesHead(folder, status.branch) + toolbar + list + '<div class="placeholder">cargando diff…</div>';
|
|
3069
|
+
|
|
3070
|
+
var diff = await ocCommand('git_diff', [folder], 20, 700);
|
|
3071
|
+
var diffHtml;
|
|
3072
|
+
if (diff.ok) {
|
|
3073
|
+
var text = (diff.data && diff.data.diff) || '';
|
|
3074
|
+
diffHtml = text ? renderDiff(text) : '<div class="placeholder">Hay archivos nuevos o renombrados sin diff de contenido.</div>';
|
|
3075
|
+
} else {
|
|
3076
|
+
diffHtml = '<div class="placeholder">' + esc(diff.error || 'no se pudo obtener el diff') + '</div>';
|
|
3077
|
+
}
|
|
3078
|
+
els.viewChanges.innerHTML = changesHead(folder, status.branch) + toolbar + list + diffHtml;
|
|
3079
|
+
}
|
|
3080
|
+
|
|
3081
|
+
// Revertir cambios rastreados (con confirmacion): borra las modificaciones que
|
|
3082
|
+
// el agente u otros hicieron en archivos ya versionados. No toca los nuevos.
|
|
3083
|
+
// Sirve para un archivo puntual (boton de la fila) o para todos.
|
|
3084
|
+
if (els.viewChanges) {
|
|
3085
|
+
els.viewChanges.addEventListener('click', function (e) {
|
|
3086
|
+
if (!isAdmin()) return;
|
|
3087
|
+
var folder = changesFolder();
|
|
3088
|
+
if (!folder) return;
|
|
3089
|
+
var one = e.target.closest('.chg-rev');
|
|
3090
|
+
if (one) {
|
|
3091
|
+
var rel = one.getAttribute('data-path');
|
|
3092
|
+
if (!window.confirm('¿Revertir “' + rel + '”?\n\nNo se puede deshacer.')) return;
|
|
3093
|
+
one.disabled = true;
|
|
3094
|
+
ocCommand('git_checkout', [folder, rel], 15, 500).then(function (r) {
|
|
3095
|
+
toast(r.ok ? 'revertido: ' + rel : (r.error || 'no se pudo revertir'), r.ok ? 'ok' : 'error');
|
|
3096
|
+
renderChangesView();
|
|
3097
|
+
});
|
|
3098
|
+
return;
|
|
3099
|
+
}
|
|
3100
|
+
var all = e.target.closest('#chgRevert');
|
|
3101
|
+
if (!all) return;
|
|
3102
|
+
if (!window.confirm('¿Revertir TODOS los cambios de archivos rastreados en “' + projectLabel(folder) + '”?\n\nNo se puede deshacer. Los archivos nuevos sin seguimiento no se tocan.')) return;
|
|
3103
|
+
all.disabled = true;
|
|
3104
|
+
all.textContent = 'revirtiendo…';
|
|
3105
|
+
ocCommand('git_checkout', [folder], 15, 500).then(function (r) {
|
|
3106
|
+
toast(r.ok ? 'cambios revertidos' : (r.error || 'no se pudo revertir'), r.ok ? 'ok' : 'error');
|
|
3107
|
+
renderChangesView();
|
|
3108
|
+
});
|
|
3109
|
+
});
|
|
3110
|
+
}
|
|
3111
|
+
|
|
2884
3112
|
function renderFilesView() {
|
|
2885
3113
|
state.fileView = null;
|
|
2886
3114
|
var ws = (state.catalog && state.catalog.workspace) || (state.filesWorkspace || '');
|
|
@@ -3178,21 +3406,26 @@ function renderPreviewView() {
|
|
|
3178
3406
|
}
|
|
3179
3407
|
els.viewPreview.innerHTML = '<div class="view-head"><h2>vista previa</h2>'
|
|
3180
3408
|
+ '<div class="view-sub">túneles del proyecto (TunnelMole, corren en tu PC)</div></div>'
|
|
3181
|
-
+
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3409
|
+
+ (isAdmin()
|
|
3410
|
+
? '<div class="tunnel-form">'
|
|
3411
|
+
+ '<input type="text" id="tunnelPort" inputmode="numeric" placeholder="puerto (ej: 8080)" value="' + esc(state.previewPort) + '" autocomplete="off">'
|
|
3412
|
+
+ '<button type="button" class="linkbtn" id="tunnelStart">abrir túnel</button>'
|
|
3413
|
+
+ '</div>'
|
|
3414
|
+
: '')
|
|
3185
3415
|
+ '<div class="tnote-sec" style="margin:0 0 12px">los túneles son públicos en internet y viven mientras el puente esté corriendo · cerrálos al terminar</div>'
|
|
3186
3416
|
+ '<div id="tunnelList"></div>';
|
|
3187
3417
|
var portInput = document.getElementById('tunnelPort');
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3418
|
+
if (portInput) {
|
|
3419
|
+
portInput.addEventListener('input', function () {
|
|
3420
|
+
state.previewPort = portInput.value.replace(/[^0-9]/g, '');
|
|
3421
|
+
portInput.value = state.previewPort;
|
|
3422
|
+
});
|
|
3423
|
+
portInput.addEventListener('keydown', function (e) {
|
|
3424
|
+
if (e.key === 'Enter') { e.preventDefault(); startTunnel(); }
|
|
3425
|
+
});
|
|
3426
|
+
var tunStartBtn = document.getElementById('tunnelStart');
|
|
3427
|
+
if (tunStartBtn) tunStartBtn.addEventListener('click', startTunnel);
|
|
3428
|
+
}
|
|
3196
3429
|
renderTunnels();
|
|
3197
3430
|
refreshTunnels();
|
|
3198
3431
|
}
|
|
@@ -3209,7 +3442,8 @@ function renderTunnels(busyMsg) {
|
|
|
3209
3442
|
+ '<div class="thead"><span class="dot"' + (ready ? '' : ' style="background:var(--muted)"') + '></span>'
|
|
3210
3443
|
+ '<b>puerto ' + esc(t.port) + '</b>'
|
|
3211
3444
|
+ '<span class="tnote">' + (ready ? 'activo' : 'levantando…') + '</span>'
|
|
3212
|
-
+ '<button type="button" class="linkbtn" data-tstop="' + esc(t.port) + '">cerrar túnel</button
|
|
3445
|
+
+ (isAdmin() ? '<button type="button" class="linkbtn" data-tstop="' + esc(t.port) + '">cerrar túnel</button>' : '')
|
|
3446
|
+
+ '</div>'
|
|
3213
3447
|
+ (t.https ? '<div class="turl"><a href="' + esc(t.https) + '" target="_blank" rel="noopener noreferrer">' + esc(t.https) + '</a>'
|
|
3214
3448
|
+ '<button type="button" class="copybtn" data-tcopy="' + esc(t.https) + '" title="Copiar URL">⧉</button></div>' : '')
|
|
3215
3449
|
+ (t.http && t.http !== t.https ? '<div class="turl"><a href="' + esc(t.http) + '" target="_blank" rel="noopener noreferrer">' + esc(t.http) + '</a>'
|
|
@@ -3369,6 +3603,7 @@ function enterProcsView() {
|
|
|
3369
3603
|
}
|
|
3370
3604
|
|
|
3371
3605
|
function renderProcView(folder) {
|
|
3606
|
+
var admin = isAdmin();
|
|
3372
3607
|
els.viewProcs.innerHTML =
|
|
3373
3608
|
'<div class="pp-head"><b>procesos</b><span class="pp-sub">' + esc(sessionProcLabel(folder)) + '</span>'
|
|
3374
3609
|
+ (state.currentId !== null ? '<button type="button" class="linkbtn pp-back" id="ppBack">← volver al chat</button>' : '')
|
|
@@ -3376,35 +3611,42 @@ function renderProcView(folder) {
|
|
|
3376
3611
|
+ '<div class="pp-status" id="procStatus"></div>'
|
|
3377
3612
|
+ '<div id="ppPortWarn"></div>'
|
|
3378
3613
|
+ '<pre id="procLog"><span class="pl-empty">sin salida todavía</span></pre>'
|
|
3379
|
-
+ '<div class="pp-tunnel">'
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
+ '<div class="pp-chips" id="procChips"></div>'
|
|
3386
|
-
+ '<div class="pp-form"><input type="text" id="procCmdInput" placeholder="npm run dev · node server.js …" autocomplete="off">'
|
|
3387
|
-
|
|
3614
|
+
+ (admin ? '<div class="pp-tunnel">'
|
|
3615
|
+
+ '<div class="pp-tunhead">compartir por túnel (tunnelmole, corre en tu PC)</div>'
|
|
3616
|
+
+ '<div class="pp-tunform"><input type="text" id="ppTunPort" inputmode="numeric" placeholder="puerto (ej: 3000)" autocomplete="off">'
|
|
3617
|
+
+ '<button type="button" class="linkbtn" id="ppTunStart">abrir túnel</button></div>'
|
|
3618
|
+
+ '<div id="ppTunList"></div>'
|
|
3619
|
+
+ '</div>' : '')
|
|
3620
|
+
+ (admin ? '<div class="pp-chips" id="procChips"></div>' : '')
|
|
3621
|
+
+ (admin ? '<div class="pp-form"><input type="text" id="procCmdInput" placeholder="npm run dev · node server.js …" autocomplete="off">'
|
|
3622
|
+
+ '<button type="button" class="linkbtn" id="procStartBtn">iniciar</button></div>' : '')
|
|
3623
|
+
+ (!admin ? '<div class="placeholder">solo un admin puede iniciar procesos o túneles.</div>' : '');
|
|
3388
3624
|
var back = document.getElementById('ppBack');
|
|
3389
3625
|
if (back) back.addEventListener('click', function () { showView('chat'); });
|
|
3390
3626
|
var inp = document.getElementById('procCmdInput');
|
|
3391
|
-
inp
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3627
|
+
if (inp) {
|
|
3628
|
+
inp.value = procState.input || '';
|
|
3629
|
+
inp.addEventListener('input', function () { procState.input = inp.value; });
|
|
3630
|
+
inp.addEventListener('keydown', function (e) {
|
|
3631
|
+
if (e.key === 'Enter') { e.preventDefault(); startSessionProc(); }
|
|
3632
|
+
});
|
|
3633
|
+
}
|
|
3634
|
+
var startBtn = document.getElementById('procStartBtn');
|
|
3635
|
+
if (startBtn) startBtn.addEventListener('click', startSessionProc);
|
|
3397
3636
|
var tunPort = document.getElementById('ppTunPort');
|
|
3398
|
-
tunPort
|
|
3399
|
-
|
|
3400
|
-
tunPort.
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3637
|
+
if (tunPort) {
|
|
3638
|
+
tunPort.value = procState.detectedPort || '';
|
|
3639
|
+
tunPort.addEventListener('input', function () {
|
|
3640
|
+
tunPort.value = tunPort.value.replace(/[^0-9]/g, '');
|
|
3641
|
+
if (tunPort.value) state.previewPort = tunPort.value;
|
|
3642
|
+
});
|
|
3643
|
+
tunPort.addEventListener('keydown', function (e) {
|
|
3644
|
+
if (e.key === 'Enter') { e.preventDefault(); panelStartTunnel(); }
|
|
3645
|
+
});
|
|
3646
|
+
}
|
|
3647
|
+
var tunStart = document.getElementById('ppTunStart');
|
|
3648
|
+
if (tunStart) tunStart.addEventListener('click', panelStartTunnel);
|
|
3649
|
+
if (admin) buildProcChips();
|
|
3408
3650
|
procLogBox = document.getElementById('procLog');
|
|
3409
3651
|
renderProcStatus();
|
|
3410
3652
|
renderPortWarn();
|
|
@@ -3689,7 +3931,8 @@ function renderProcTunnels(busyMsg) {
|
|
|
3689
3931
|
var ready = !!(t.https || t.http);
|
|
3690
3932
|
html += '<div class="tunnel"><div class="thead"><span class="dot"' + (ready ? '' : ' style="background:var(--muted)"') + '></span>'
|
|
3691
3933
|
+ '<b>puerto ' + esc(t.port) + '</b><span class="tnote">' + (ready ? 'activo' : 'levantando…') + '</span>'
|
|
3692
|
-
+ '<button type="button" class="linkbtn" data-ptstop="' + esc(t.port) + '">cerrar</button
|
|
3934
|
+
+ (isAdmin() ? '<button type="button" class="linkbtn" data-ptstop="' + esc(t.port) + '">cerrar</button>' : '')
|
|
3935
|
+
+ '</div>'
|
|
3693
3936
|
+ (t.https ? '<div class="turl"><a href="' + esc(t.https) + '" target="_blank" rel="noopener noreferrer">' + esc(t.https) + '</a>'
|
|
3694
3937
|
+ '<button type="button" class="copybtn" data-ptcopy="' + esc(t.https) + '" title="Copiar URL">⧉</button></div>' : '')
|
|
3695
3938
|
+ (t.http && t.http !== t.https ? '<div class="turl"><a href="' + esc(t.http) + '" target="_blank" rel="noopener noreferrer">' + esc(t.http) + '</a>'
|