feat: SMB-Freigaben-API hinzugefügt und Versionsnummer auf 1.0.34 erhöht

This commit is contained in:
2026-05-09 13:11:33 +02:00
parent e83810fb76
commit c89c2143ce
2 changed files with 98 additions and 37 deletions

133
app.py
View File

@@ -1192,6 +1192,37 @@ def r_upload_del(tid):
return jsonify(ok=True)
@app.route('/api/upload/browse', methods=['POST'])
def r_upload_browse():
"""Listet SMB-Freigaben eines Servers ohne gespeicherte Config (rclone connection string)."""
data = request.get_json(force=True)
host = data.get('host', '').strip()
user = data.get('user', '').strip()
pw = data.get('pass', '')
if not host:
return jsonify(error='Server-Adresse fehlt'), 400
conn = f':smb,host={host}'
if user:
conn += f',user={user}'
if pw:
try:
obscured = _rclone_obscure(pw)
conn += f',pass={obscured}'
except Exception:
pass
conn += ':'
r = subprocess.run(
['rclone', '--config', str(RCLONE_CONF), 'lsd', conn],
capture_output=True, text=True, timeout=15
)
if r.returncode != 0:
lines = r.stderr.strip().splitlines()
err = lines[-1] if lines else 'Verbindung fehlgeschlagen'
return jsonify(error=err), 400
shares = [line.strip().split()[-1] for line in r.stdout.splitlines() if line.strip()]
return jsonify(shares=shares)
@app.route('/api/upload/targets/<tid>/toggle', methods=['POST'])
def r_upload_toggle(tid):
cfg = load_cfg()
@@ -1857,19 +1888,41 @@ body{background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSys
<button class="btn" onclick="utToggleForm()" id="ut-add-btn">+&nbsp;NAS-Ziel hinzufügen</button>
<div id="ut-form" class="add-panel" style="display:none">
<div class="sec" style="margin-top:0">SMB / Netzlaufwerk</div>
<div class="field"><label>Name</label><input type="text" id="ut-name" placeholder="z.B. Heimserver NAS"></div>
<div class="field"><label>Ziel-Ordner auf dem NAS</label><input type="text" id="ut-dest" value="PiCopy" placeholder="PiCopy"></div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:.5rem">
<div class="field"><label>Server (IP / Hostname)</label><input type="text" id="ut-host" placeholder="192.168.1.100"></div>
<div class="field"><label>Freigabe-Name</label><input type="text" id="ut-share" placeholder="backup"></div>
<div class="field"><label>Benutzer</label><input type="text" id="ut-user" placeholder="tobias"></div>
<div class="field"><label>Passwort</label><input type="password" id="ut-pass" placeholder="(leer = kein Passwort)"></div>
<!-- Schritt 1: Verbindungsdaten -->
<div id="ut-step1">
<div class="sec" style="margin-top:0">Schritt 1 Server-Verbindung</div>
<div class="field"><label>Server (IP / Hostname)</label>
<input type="text" id="ut-host" placeholder="192.168.1.100 oder nas.local" autocomplete="off"></div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:.5rem">
<div class="field"><label>Benutzer</label>
<input type="text" id="ut-user" placeholder="(leer = anonym)" autocomplete="off"></div>
<div class="field"><label>Passwort</label>
<input type="password" id="ut-pass" placeholder="(leer = kein Passwort)" autocomplete="off"></div>
</div>
<div class="btn-row">
<button class="btn pri" id="ut-connect-btn" onclick="utConnect()">&#128279;&nbsp;Verbinden &amp; Freigaben laden</button>
<button class="btn ghost" onclick="utToggleForm()">Abbrechen</button>
</div>
</div>
<div class="btn-row">
<button class="btn pri" onclick="utSave()">✓&nbsp;Speichern &amp; Verbindung testen</button>
<button class="btn ghost" onclick="utToggleForm()">Abbrechen</button>
<!-- Schritt 2: Freigabe wählen (nach erfolgreicher Verbindung) -->
<div id="ut-step2" style="display:none">
<div class="sec" style="margin-top:0">Schritt 2 Freigabe &amp; Details</div>
<div class="field"><label>Freigabe wählen</label>
<select id="ut-share-sel" style="width:100%"></select></div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:.5rem">
<div class="field"><label>Name (Anzeigename)</label>
<input type="text" id="ut-name" placeholder="z.B. Heimserver NAS"></div>
<div class="field"><label>Ziel-Ordner auf dem NAS</label>
<input type="text" id="ut-dest" placeholder="PiCopy" value="PiCopy"></div>
</div>
<div class="btn-row">
<button class="btn pri" onclick="utSave()">✓&nbsp;Speichern &amp; Verbindung testen</button>
<button class="btn ghost" onclick="utBack()">&#8592;&nbsp;Zurück</button>
</div>
</div>
<div id="ut-form-flash" class="flash" style="margin-top:.4rem"></div>
</div>
</div>
@@ -2239,8 +2292,7 @@ async function saveAP(){
// -- Upload-Ziele --------------------------------------------------------------
const UT_ICONS={smb:'🖧',onedrive:'',drive:'📄',dropbox:'📦'};
const UT_LABELS={smb:'SMB/NAS',onedrive:'OneDrive',drive:'Google Drive',dropbox:'Dropbox'};
const UT_CMD={onedrive:'rclone authorize "onedrive"',drive:'rclone authorize "drive"',dropbox:'rclone authorize "dropbox"'};
let utType='smb', utTargets=[];
let utTargets=[], _utConn={};
async function loadUTs(){utTargets=await api('/upload/targets');renderUTs();}
function renderUTs(){
@@ -2264,41 +2316,50 @@ function renderUTs(){
function utToggleForm(){
const f=$('ut-form'),b=$('ut-add-btn'),show=f.style.display==='none';
f.style.display=show?'block':'none';
b.innerHTML=show?'✕ Abbrechen':'+ Ziel hinzufügen';
if(show){utSelectType('smb',document.querySelector('.sel-opt'));}
b.innerHTML=show?'✕ Abbrechen':'+ NAS-Ziel hinzufügen';
if(show){
$('ut-step1').style.display=''; $('ut-step2').style.display='none';
['ut-host','ut-user','ut-pass','ut-name'].forEach(id=>{$(id).value='';});
$('ut-dest').value='PiCopy';
$('ut-form-flash').style.display='none';
_utConn={};
}
}
function utSelectType(type,el){
utType=type;
document.querySelectorAll('.sel-opt').forEach(b=>b.classList.remove('on'));
el.classList.add('on');
$('ut-smb-fields').style.display=type==='smb'?'block':'none';
$('ut-cloud-fields').style.display=type!=='smb'?'block':'none';
if(UT_CMD[type]) $('ut-auth-cmd').textContent=UT_CMD[type];
async function utConnect(){
const host=$('ut-host').value.trim();
if(!host){flash('ut-form-flash','err','Server-Adresse fehlt');return;}
const btn=$('ut-connect-btn');
btn.disabled=true; btn.textContent='Verbinde...';
$('ut-form-flash').style.display='none';
const r=await api('/upload/browse','POST',{
host, user:$('ut-user').value.trim(), pass:$('ut-pass').value
});
btn.disabled=false; btn.innerHTML='&#128279;&nbsp;Verbinden &amp; Freigaben laden';
if(r.error){flash('ut-form-flash','err',''+r.error);return;}
if(!r.shares||!r.shares.length){flash('ut-form-flash','warn','Verbunden, aber keine Freigaben gefunden');return;}
_utConn={host, user:$('ut-user').value.trim(), pass:$('ut-pass').value};
$('ut-share-sel').innerHTML=r.shares.map(s=>`<option value="${s}">${s}</option>`).join('');
if(!$('ut-name').value) $('ut-name').value=host;
$('ut-step1').style.display='none'; $('ut-step2').style.display='';
}
function utCopyCmd(){
navigator.clipboard?.writeText($('ut-auth-cmd').textContent);
const b=document.querySelector('[onclick="utCopyCmd()"]');
b.textContent=''; setTimeout(()=>b.textContent='📋',1500);
function utBack(){
$('ut-step1').style.display=''; $('ut-step2').style.display='none';
$('ut-form-flash').style.display='none';
}
async function utSave(){
const name=$('ut-name').value.trim(), dest=$('ut-dest').value.trim()||'PiCopy';
const share=$('ut-share-sel').value;
if(!name){flash('ut-form-flash','err','Name fehlt');return;}
let body={type:utType,name,dest_path:dest};
if(utType==='smb'){
body.host=$('ut-host').value.trim(); body.share=$('ut-share').value.trim();
body.user=$('ut-user').value.trim(); body.pass=$('ut-pass').value;
if(!body.host||!body.share){flash('ut-form-flash','err','Server und Freigabe sind Pflicht');return;}
} else {
body.token=$('ut-token').value.trim();
if(!body.token){flash('ut-form-flash','err','Token fehlt');return;}
}
if(!share){flash('ut-form-flash','err','Bitte eine Freigabe wählen');return;}
const body={type:'smb',name,dest_path:dest,share,
host:_utConn.host, user:_utConn.user, pass:_utConn.pass};
flash('ut-form-flash','ok','Speichere...');
const r=await api('/upload/targets','POST',body);
if(r.error){flash('ut-form-flash','err',r.error);return;}
flash('ut-form-flash','ok','Teste Verbindung...');
const t=await api('/upload/targets/'+r.id+'/test','POST');
if(t.ok){flash('ut-form-flash','ok','✓ Verbindung OK - Lesen & Schreiben erfolgreich');utToggleForm();await loadUTs();}
else flash('ut-form-flash','err','' + (t.error||'Test fehlgeschlagen'));
else flash('ut-form-flash','err',''+(t.error||'Test fehlgeschlagen'));
}
async function utTest(id){
const btn=$('ut-test-'+id), res=$('ut-test-result-'+id);