CORVO-AI commited on
Commit
2608bcb
·
verified ·
1 Parent(s): 9b9a92c

Update file-manager/app.py

Browse files
Files changed (1) hide show
  1. file-manager/app.py +36 -17
file-manager/app.py CHANGED
@@ -154,7 +154,7 @@ def term_page():
154
  <script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.js"></script>
155
  <script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.js"></script>
156
  <script>
157
- let term, fit, current = null, cursor = 0, pollTimer = null;
158
 
159
  function initTerm(){
160
  document.getElementById('term').innerHTML = '';
@@ -163,9 +163,12 @@ def term_page():
163
  term.loadAddon(fit);
164
  term.open(document.getElementById('term'));
165
  fit.fit();
 
 
166
  term.onData(d => {
167
  if(!current) return;
168
- fetch('/fm/term_in', {method:'POST', headers:{'Content-Type':'application/json'},
 
169
  body: JSON.stringify({name: current, data: d})});
170
  });
171
  }
@@ -179,45 +182,60 @@ def term_page():
179
  names.forEach(n => {
180
  const t = document.createElement('span');
181
  t.className = 'tab' + (n===current ? ' active':'');
182
- t.textContent = n + ' ✕';
183
- t.onclick = (e) => { attach(n); };
184
- // click the area to close
185
- t.oncontextmenu = (e)=>{ e.preventDefault(); closeSession(n); };
 
 
 
 
 
 
 
 
 
 
186
  wrap.appendChild(t);
187
  });
188
  const add = document.createElement('span');
189
  add.className = 'tab'; add.textContent = '+ New';
190
  add.onclick = newSession;
191
  wrap.appendChild(add);
192
- // if current session vanished, attach to first available
 
193
  if(current && !names.includes(current)){ current = null; }
 
194
  if(!current && names.length){ attach(names[0]); }
195
  }
196
 
197
  async function attach(name){
 
198
  current = name; cursor = 0;
199
  initTerm();
200
  // replay full scrollback so you see what happened while away
201
  const r = await fetch('/fm/term_snapshot?name=' + encodeURIComponent(name));
202
  const j = await r.json();
 
203
  term.write(j.data); cursor = j.cursor;
204
  refreshTabs();
205
  startPoll();
206
  }
207
 
 
208
  function startPoll(){
209
- if(pollTimer) clearTimeout(pollTimer);
 
210
  async function poll(){
211
  if(current){
212
  try{
213
  const r = await fetch('/fm/term_out?name=' + encodeURIComponent(current) + '&cursor=' + cursor);
214
  const j = await r.json();
215
- if(j.gone){ current = null; refreshTabs(); return; }
216
- if(j.data) term.write(j.data);
217
- cursor = j.cursor;
218
  }catch(e){}
219
  }
220
- pollTimer = setTimeout(poll, 200);
221
  }
222
  poll();
223
  }
@@ -230,10 +248,11 @@ def term_page():
230
  }
231
 
232
  async function closeSession(name){
233
- await fetch('/fm/term_close', {method:'POST', headers:{'Content-Type':'application/json'},
 
234
  body: JSON.stringify({name})});
235
- if(current===name) current=null;
236
- refreshTabs();
237
  }
238
 
239
  // boot
@@ -259,8 +278,8 @@ def term_close():
259
  name = request.get_json(force=True).get("name")
260
  sh = _shells.get(name)
261
  if sh:
262
- sh.kill()
263
- del _shells[name]
264
  return Response(json.dumps({"ok": True}), mimetype="application/json")
265
 
266
  @app.route("/term_snapshot")
 
154
  <script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.js"></script>
155
  <script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.js"></script>
156
  <script>
157
+ let term, fit, current = null, cursor = 0, polling = false;
158
 
159
  function initTerm(){
160
  document.getElementById('term').innerHTML = '';
 
163
  term.loadAddon(fit);
164
  term.open(document.getElementById('term'));
165
  fit.fit();
166
+ // FIX 1: only SEND the keystroke; never echo locally.
167
+ // The PTY (bash) echoes it back through the poll, so echoing here = duplicates.
168
  term.onData(d => {
169
  if(!current) return;
170
+ fetch('/fm/term_in', {method:'POST',
171
+ headers:{'Content-Type':'application/json'},
172
  body: JSON.stringify({name: current, data: d})});
173
  });
174
  }
 
182
  names.forEach(n => {
183
  const t = document.createElement('span');
184
  t.className = 'tab' + (n===current ? ' active':'');
185
+
186
+ const label = document.createElement('span');
187
+ label.textContent = n + ' ';
188
+ label.onclick = () => attach(n);
189
+
190
+ // FIX 3: explicit close button, separate from tab-switch click
191
+ const x = document.createElement('span');
192
+ x.textContent = '✕';
193
+ x.style.marginLeft = '8px';
194
+ x.style.color = '#F43256';
195
+ x.onclick = (e) => { e.stopPropagation(); closeSession(n); };
196
+
197
+ t.appendChild(label);
198
+ t.appendChild(x);
199
  wrap.appendChild(t);
200
  });
201
  const add = document.createElement('span');
202
  add.className = 'tab'; add.textContent = '+ New';
203
  add.onclick = newSession;
204
  wrap.appendChild(add);
205
+
206
+ // if the current session vanished, drop it (poll will stop touching it)
207
  if(current && !names.includes(current)){ current = null; }
208
+ // auto-attach to first session only if nothing is currently attached
209
  if(!current && names.length){ attach(names[0]); }
210
  }
211
 
212
  async function attach(name){
213
+ if(current === name){ return; } // already attached, don't re-stack
214
  current = name; cursor = 0;
215
  initTerm();
216
  // replay full scrollback so you see what happened while away
217
  const r = await fetch('/fm/term_snapshot?name=' + encodeURIComponent(name));
218
  const j = await r.json();
219
+ if(j.gone){ current = null; refreshTabs(); return; }
220
  term.write(j.data); cursor = j.cursor;
221
  refreshTabs();
222
  startPoll();
223
  }
224
 
225
+ // FIX 2: single guarded poll loop — never stack multiple loops (that duplicated output)
226
  function startPoll(){
227
+ if(polling) return;
228
+ polling = true;
229
  async function poll(){
230
  if(current){
231
  try{
232
  const r = await fetch('/fm/term_out?name=' + encodeURIComponent(current) + '&cursor=' + cursor);
233
  const j = await r.json();
234
+ if(j.gone){ current = null; refreshTabs(); }
235
+ else { if(j.data) term.write(j.data); cursor = j.cursor; }
 
236
  }catch(e){}
237
  }
238
+ setTimeout(poll, 200);
239
  }
240
  poll();
241
  }
 
248
  }
249
 
250
  async function closeSession(name){
251
+ await fetch('/fm/term_close', {method:'POST',
252
+ headers:{'Content-Type':'application/json'},
253
  body: JSON.stringify({name})});
254
+ if(current === name){ current = null; }
255
+ await refreshTabs();
256
  }
257
 
258
  // boot
 
278
  name = request.get_json(force=True).get("name")
279
  sh = _shells.get(name)
280
  if sh:
281
+ sh.kill() # SIGKILL the bash process
282
+ _shells.pop(name, None) # remove from the session list
283
  return Response(json.dumps({"ok": True}), mimetype="application/json")
284
 
285
  @app.route("/term_snapshot")