feat: Unterstützung für die Auswahl mehrerer Quellgeräte hinzugefügt und Versionsnummer auf 1.0.24 erhöht
This commit is contained in:
66
app.py
66
app.py
@@ -983,9 +983,13 @@ def r_start():
|
||||
return jsonify(error='Abbruch wird noch abgeschlossen - bitte kurz warten und erneut versuchen.'), 400
|
||||
cfg = load_cfg()
|
||||
devs = usb_devices()
|
||||
body = request.get_json(force=True) or {}
|
||||
wanted_ports = body.get('ports') # None = alle konfigurierten Quellen
|
||||
src_ports = _resolve_source_ports(cfg)
|
||||
srcs = [next((d for d in devs if d['usb_port'] == sp['port']), None) for sp in src_ports]
|
||||
srcs = [s for s in srcs if s is not None]
|
||||
if wanted_ports is not None:
|
||||
srcs = [s for s in srcs if s['usb_port'] in wanted_ports]
|
||||
if not srcs: return jsonify(error='Keine Quellgeräte gefunden (Ports nicht verbunden)'), 400
|
||||
dst = next((d for d in devs if d['usb_port'] == cfg.get('dest_port')), None)
|
||||
if not dst: return jsonify(error='Zielgerät nicht gefunden'), 400
|
||||
@@ -1726,9 +1730,9 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
|
||||
<!-- File Explorer -->
|
||||
<div class="expl-wrap">
|
||||
<div class="expl-bar">
|
||||
<button class="etab on" id="etab-src" onclick="expl.switchRole('src')">⬆ Quelle</button>
|
||||
<button class="etab" id="etab-dst" onclick="expl.switchRole('dst')">⬇ Ziel</button>
|
||||
<button class="expl-reload" onclick="expl.reload()" title="Neu laden">-></button>
|
||||
<div id="src-tabs" style="display:contents"></div>
|
||||
<button class="etab" id="etab-dst" onclick="expl.switchRole('dst')">⬇ Ziel</button>
|
||||
<button class="expl-reload" onclick="expl.reload()" title="Neu laden">↻</button>
|
||||
</div>
|
||||
<div class="expl-bread" id="expl-bread"></div>
|
||||
<div class="expl-scroll" id="expl-body">
|
||||
@@ -2004,6 +2008,8 @@ async function refreshDevices(){
|
||||
populateSel();
|
||||
}
|
||||
|
||||
let selectedPortSet = new Set();
|
||||
|
||||
function renderSources(){
|
||||
const ports = cfg.source_ports || [];
|
||||
$('sources-list').innerHTML = ports.map((sp, i) => {
|
||||
@@ -2011,6 +2017,7 @@ function renderSources(){
|
||||
const info = dev
|
||||
? (dev.label||dev.device) + (dev.size ? ' | '+dev.size : '')
|
||||
: 'Gerät nicht verbunden';
|
||||
const chk = selectedPortSet.has(sp.port) ? 'checked' : '';
|
||||
return `<div class="port-slot ${dev?'src-on':''}" style="margin-bottom:.5rem">
|
||||
<div class="role-tag src">▲ Quelle ${i+1}${sp.label?' – '+sp.label:''}</div>
|
||||
<div class="port-display">
|
||||
@@ -2019,13 +2026,36 @@ function renderSources(){
|
||||
<div class="port-path">Port ${sp.port}</div>
|
||||
<div class="port-info">${info}</div>
|
||||
</div>
|
||||
<button class="btn sm danger" style="margin-left:auto;flex-shrink:0"
|
||||
<label style="margin-left:auto;display:flex;align-items:center;gap:.3rem;font-size:.76rem;cursor:pointer;flex-shrink:0;white-space:nowrap">
|
||||
<input type="checkbox" ${chk} onchange="toggleSrc('${sp.port}',this.checked)">
|
||||
Kopieren
|
||||
</label>
|
||||
<button class="btn sm danger" style="margin-left:.4rem;flex-shrink:0"
|
||||
onclick="removeSource('${sp.port}')">✕</button>
|
||||
</div>
|
||||
</div>`;
|
||||
}).join('') + (ports.length === 0
|
||||
? '<div style="color:var(--sub);font-size:.83rem;margin-bottom:.5rem">Noch keine Quelle konfiguriert.</div>'
|
||||
: '');
|
||||
renderExplorerTabs();
|
||||
}
|
||||
|
||||
function toggleSrc(port, on){
|
||||
if(on) selectedPortSet.add(port); else selectedPortSet.delete(port);
|
||||
}
|
||||
|
||||
function renderExplorerTabs(){
|
||||
const ports = cfg.source_ports || [];
|
||||
$('src-tabs').innerHTML = ports.map((sp, i) => {
|
||||
const r = 'src_'+i;
|
||||
const label = sp.label || ('Quelle '+(i+1));
|
||||
return `<button class="etab ${expl.role===r?'on':''}" id="etab-${r}"
|
||||
onclick="expl.switchRole('${r}')">▲ ${label}</button>`;
|
||||
}).join('');
|
||||
// Fallback: falls aktive Rolle nicht mehr existiert
|
||||
if(expl.role!=='dst' && !ports.some((_,i)=>expl.role==='src_'+i)){
|
||||
expl.role = ports.length>0 ? 'src_0' : 'dst';
|
||||
}
|
||||
}
|
||||
|
||||
function renderSlot(r, port, label){
|
||||
@@ -2081,6 +2111,7 @@ async function addSource(){
|
||||
if(port===cfg.dest_port){flash('src-flash','err','Port bereits als Ziel konfiguriert!');return;}
|
||||
if((cfg.source_ports||[]).some(sp=>sp.port===port)){flash('src-flash','err','Port bereits als Quelle hinzugefügt!');return;}
|
||||
cfg.source_ports = [...(cfg.source_ports||[]), {port, label}];
|
||||
selectedPortSet.add(port);
|
||||
await api('/config','POST',cfg);
|
||||
$('src-label').value='';
|
||||
flash('src-flash','ok','✓ Quelle Port '+port+' hinzugefügt.');
|
||||
@@ -2089,6 +2120,7 @@ async function addSource(){
|
||||
|
||||
async function removeSource(port){
|
||||
cfg.source_ports = (cfg.source_ports||[]).filter(sp=>sp.port!==port);
|
||||
selectedPortSet.delete(port);
|
||||
await api('/config','POST',cfg);
|
||||
renderSources(); populateSel(); renderUnassigned();
|
||||
}
|
||||
@@ -2113,7 +2145,9 @@ async function assignPort(role){
|
||||
async function startCopy(){
|
||||
_dismissed=false;
|
||||
if(_autoDismissTimer){ clearTimeout(_autoDismissTimer); _autoDismissTimer=null; }
|
||||
const r=await api('/copy/start','POST');
|
||||
const ports=[...(cfg.source_ports||[]).map(sp=>sp.port).filter(p=>selectedPortSet.has(p))];
|
||||
if(!ports.length){flash('copy-hint','warn','Keine Quelle ausgewählt – bitte mindestens eine Quelle anhaken.');return;}
|
||||
const r=await api('/copy/start','POST',{ports});
|
||||
if(r.error) flash('copy-hint','warn',r.error);
|
||||
else $('copy-hint').style.display='none';
|
||||
}
|
||||
@@ -2126,6 +2160,8 @@ async function loadCfg(){
|
||||
if(!cfg.source_ports) cfg.source_ports=[];
|
||||
if(cfg.source_ports.length===0 && cfg.source_port)
|
||||
cfg.source_ports=[{port:cfg.source_port, label:cfg.source_label||''}];
|
||||
// Alle konfigurierten Quellen standardmäßig ausgewählt
|
||||
selectedPortSet = new Set(cfg.source_ports.map(sp=>sp.port));
|
||||
$('c-fmt').value=cfg.folder_format||'%Y-%m-%d';
|
||||
$('c-time').checked=!!cfg.add_time; $('c-sub').checked=!!cfg.subfolder; $('c-auto').checked=!!cfg.auto_copy;
|
||||
$('c-filter').value=cfg.file_filter||'';
|
||||
@@ -2251,17 +2287,23 @@ async function utDel(id,name){
|
||||
|
||||
// -- File Explorer -------------------------------------------------------------
|
||||
const expl={
|
||||
role:'src', paths:{src:'',dst:''},
|
||||
role:'src_0', paths:{dst:''},
|
||||
switchRole(r){
|
||||
this.role=r;
|
||||
$('etab-src').classList.toggle('on',r==='src');
|
||||
$('etab-dst').classList.toggle('on',r==='dst');
|
||||
this.load(this.paths[r]);
|
||||
document.querySelectorAll('.etab').forEach(t=>t.classList.remove('on'));
|
||||
const tab=$('etab-'+r); if(tab) tab.classList.add('on');
|
||||
this.load(this.paths[r]||'');
|
||||
},
|
||||
reload(){this.load(this.paths[this.role]);},
|
||||
reload(){this.load(this.paths[this.role]||'');},
|
||||
navigate(p){this.load(p);},
|
||||
async load(path=''){
|
||||
const port=this.role==='src'?(cfg.source_ports&&cfg.source_ports[0]?.port):cfg.dest_port;
|
||||
let port;
|
||||
if(this.role==='dst'){
|
||||
port=cfg.dest_port;
|
||||
} else {
|
||||
const idx=parseInt(this.role.replace('src_',''),10);
|
||||
port=cfg.source_ports&&cfg.source_ports[idx]?cfg.source_ports[idx].port:null;
|
||||
}
|
||||
const body=$('expl-body'), bread=$('expl-bread');
|
||||
if(!port){body.innerHTML='<div class="expl-empty">Kein Port konfiguriert</div>';bread.innerHTML='';return;}
|
||||
const dev=devs.find(d=>d.usb_port===port);
|
||||
@@ -2270,7 +2312,7 @@ const expl={
|
||||
try{
|
||||
const data=await api('/browse?port='+encodeURIComponent(port)+'&path='+encodeURIComponent(path));
|
||||
if(data.error){body.innerHTML='<div class="expl-empty">⚠ '+data.error+'</div>';return;}
|
||||
this.paths[this.role]=data.path||'';
|
||||
this.paths[this.role]=data.path||''; // role z.B. 'src_0', 'dst'
|
||||
this._bread(data.path||'',dev.label||dev.device);
|
||||
this._list(data.entries||[],data.path||'');
|
||||
}catch(e){body.innerHTML='<div class="expl-empty">Verbindungsfehler</div>';}
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.0.23
|
||||
1.0.24
|
||||
Reference in New Issue
Block a user