@@ -400,6 +400,12 @@ function launchSSHPresets(presetIds) {
400400 const launched = [ ] ;
401401 const presetWindowMap = { } ;
402402
403+ // 启动前记录一次窗口集合(避免每个预设都调用 AppleScript)
404+ let beforeIds ;
405+ try {
406+ beforeIds = new Set ( getWindows ( ) . map ( w => w . id ) ) ;
407+ } catch { beforeIds = new Set ( ) ; }
408+
403409 for ( const pid of presetIds ) {
404410 const preset = presets [ pid ] ;
405411 if ( ! preset ) continue ;
@@ -409,12 +415,6 @@ function launchSSHPresets(presetIds) {
409415 const dir = ( preset . directory || '' ) . replace ( / ' / g, "'\\''" ) ;
410416 const cmd = ( preset . command || '' ) . replace ( / ' / g, "'\\''" ) ;
411417
412- // 记录启动前的窗口 ID 集合
413- let beforeIds ;
414- try {
415- beforeIds = new Set ( getWindows ( ) . map ( w => w . id ) ) ;
416- } catch { beforeIds = new Set ( ) ; }
417-
418418 if ( conn . type === 'local' ) {
419419 let localCmd = '' ;
420420 if ( dir && cmd ) localCmd = `cd '${ dir } ' && ${ cmd } ` ;
@@ -454,18 +454,21 @@ function launchSSHPresets(presetIds) {
454454 } catch { }
455455 }
456456
457- // 启动后找出新增的窗口 ID
458- try {
459- const afterWindows = getWindows ( ) ;
460- for ( const w of afterWindows ) {
461- if ( ! beforeIds . has ( w . id ) ) {
462- presetWindowMap [ pid ] = w . id ;
463- break ;
464- }
465- }
466- } catch { }
467457 }
468458
459+ // 启动后统一获取一次新窗口列表,找出所有新增窗口
460+ try {
461+ const afterWindows = getWindows ( ) ;
462+ const newWindowIds = afterWindows . filter ( w => ! beforeIds . has ( w . id ) ) . map ( w => w . id ) ;
463+ // 按顺序将新窗口分配给各预设
464+ let ni = 0 ;
465+ for ( const pid of presetIds ) {
466+ if ( ni < newWindowIds . length && launched . includes ( presets [ pid ] ?. name ) ) {
467+ presetWindowMap [ pid ] = newWindowIds [ ni ++ ] ;
468+ }
469+ }
470+ } catch { }
471+
469472 return { ok : true , launched, presetWindowMap } ;
470473}
471474
@@ -954,8 +957,8 @@ function renderLayouts(wc){
954957 var maxCells=Math.min(wc,12);
955958 var cells='';for(var i=0;i<maxCells;i++)cells+='<div class="layout-cell">'+(NUM[i]||i+1)+'</div>';
956959 if(wc>12)cells+='<div class="layout-cell">+' +(wc-12)+'</div>';
957- var displayCols=wc>12?Math.min( cols,cols):cols ;
958- var displayRows=wc>12?Math.ceil((maxCells+1)/displayCols):rows ;
960+ var displayCols=cols||1 ;
961+ var displayRows=wc>12?Math.ceil((maxCells+1)/displayCols):Math.ceil(wc/displayCols) ;
959962 var gridH=Math.max(48,Math.min(80,displayRows*16));
960963 return '<div class="layout-card'+act+'" onclick="selectLayout(\\''+l.id+'\\','+l.cols+')"><div class="layout-grid" style="grid-template-columns:repeat('+displayCols+',1fr);height:'+gridH+'px">'+cells+'</div><div class="layout-name">'+l.name+(l.cols?' ('+rows+'x'+cols+')':'')+'</div></div>';
961964 }).join('')+'<div class="layout-actions"><button class="btn primary" onclick="applyLayout()" '+(selectedLayout?'':'disabled')+'>应用排布</button><button class="btn" onclick="bringToFront()">显示终端</button></div>';
@@ -1601,7 +1604,7 @@ function renderBoard(){
16011604 var colorBar='<div class="bn-color-bar" style="background:'+color+'">';
16021605 BOARD_COLORS.forEach(function(c){colorBar+='<div class="bn-cswatch" style="background:'+c+'" onclick="event.stopPropagation();setBoardColor(\\''+escA(w.id)+'\\',\\''+c+'\\')"></div>'});
16031606 colorBar+='</div>';
1604- html+='<div class="board-node state-'+sc+'" data-winid="'+escA(w.id)+'" style="left:'+x+'px;top:'+y+'px;border-left:4px solid '+color+'" onmousedown="startNodeDrag(event,\\''+escA(w.id)+'\\')" ondblclick="onBoardNodeDblClick(event,\\''+escA(w.id)+'\\')"><div class="bn-header"><span class="bn-idx">'+(NUM[si-1]||si)+'</span><input class="bn-title" value="'+escA(titleText)+'" data-winid="'+escA(w.id)+'" readonly ondblclick="event.stopPropagation();startBoardEditTitle(this)" onkeydown="onBoardTitleKey(event,this)" onblur="cancelBoardEditTitle(this)"><span class="bn-dot dot '+dotCls+'"></span><span class="bn-stag '+sc+'">'+stLabel+'</span></div><div class="bn-preview">'+preview+'</div>'+colorBar+'</div>';
1607+ html+='<div class="board-node state-'+sc+'" data-winid="'+escA(w.id)+'" style="left:'+x+'px;top:'+y+'px;border-left:4px solid '+color+'" onmousedown="startNodeDrag(event,\\''+escA(w.id)+'\\')" ondblclick="onBoardNodeDblClick(event,\\''+escA(w.id)+'\\')"><div class="bn-header"><span class="bn-idx">'+(NUM[si-1]||si)+'</span><input class="bn-title" value="'+escA(titleText)+'" data-winid="'+escA(w.id)+'" data-original="'+escA(titleText)+'" readonly ondblclick="event.stopPropagation();startBoardEditTitle(this)" onkeydown="onBoardTitleKey(event,this)" onblur="cancelBoardEditTitle(this)"><span class="bn-dot dot '+dotCls+'"></span><span class="bn-stag '+sc+'">'+stLabel+'</span></div><div class="bn-preview">'+preview+'</div>'+colorBar+'</div>';
16051608 usedPositions[w.id]={x:x,y:y};
16061609 });
16071610
@@ -1802,6 +1805,7 @@ function resetBoardLayout(){
18021805
18031806// --- 底部色条颜色选择 ---
18041807function setBoardColor(winId,color){
1808+ if(BOARD_COLORS.indexOf(color)<0)return;
18051809 var node=document.querySelector('.board-node[data-winid="'+winId+'"]');
18061810 if(!node)return;
18071811 if(!boardData.nodes[winId])boardData.nodes[winId]={x:parseInt(node.style.left)||0,y:parseInt(node.style.top)||0};
@@ -1819,16 +1823,18 @@ function startBoardEditTitle(inp){
18191823 inp.select();
18201824}
18211825function confirmBoardTitle(inp){
1822- var label=inp.value.trim();
1826+ var label=inp.value.trim().slice(0,100) ;
18231827 if(!label){cancelBoardEditTitle(inp);return}
18241828 inp.readOnly=true;
1829+ inp.dataset.original=label;
18251830 var winId=inp.dataset.winid;
18261831 fetch('/api/label',{method:'POST',headers:{'Content-Type':'application/json'},
18271832 body:JSON.stringify({windowId:winId,label:label})
18281833 }).catch(function(){});
18291834}
18301835function cancelBoardEditTitle(inp){
18311836 if(inp.readOnly)return;
1837+ inp.value=inp.dataset.original||'';
18321838 inp.readOnly=true;
18331839}
18341840function onBoardTitleKey(e,inp){
@@ -1980,18 +1986,20 @@ export async function startPanel(port) {
19801986 }
19811987
19821988 if ( url . pathname === '/api/label' && req . method === 'POST' ) {
1983- let body = '' ;
1984- req . on ( 'data' , d => body += d ) ;
1985- req . on ( 'end' , async ( ) => {
1989+ readBody ( req , async ( body ) => {
19861990 try {
19871991 const { windowId, label, stableIndex, index } = JSON . parse ( body ) ;
1992+ // 安全:校验 windowId + 限制 label 长度
1993+ const safeLabel = String ( label || '' ) . slice ( 0 , 100 ) ;
1994+ if ( ! safeLabel ) { res . writeHead ( 400 ) ; res . end ( 'empty label' ) ; return ; }
19881995 if ( windowId ) {
1989- // 优先使用窗口 ID(稳定,不受焦点影响)
1990- await labelWindowById ( windowId , label , stableIndex || 1 ) ;
1996+ const wid = safeWindowId ( windowId ) ;
1997+ if ( ! wid ) { res . writeHead ( 400 ) ; res . end ( 'invalid windowId' ) ; return ; }
1998+ await labelWindowById ( wid , safeLabel , stableIndex || 1 ) ;
19911999 } else {
1992- await labelWindow ( index , label , { displayIndex : stableIndex || index } ) ;
2000+ await labelWindow ( index , safeLabel , { displayIndex : stableIndex || index } ) ;
19932001 }
1994- _statusCache = null ; // 清缓存让前端立即看到新标签
2002+ _statusCache = null ;
19952003 res . writeHead ( 200 , { 'Content-Type' : 'application/json' } ) ;
19962004 res . end ( JSON . stringify ( { ok : true } ) ) ;
19972005 } catch { res . writeHead ( 400 ) ; res . end ( 'bad request' ) ; }
0 commit comments