Added SFTP to sources
This commit is contained in:
1 parent
360eb1caed
commit
e35bbe78a5
20 files changed
+826
-120
No files matched your search
@@ -31,6 +31,7 @@ Never revert to `!!window._googleConnected` / `_fileSources.length > 0` — thos
|
||||
|
||||
## Gotchas
|
||||
|
||||
- **`scheduler.js` strings must use `t()`** — frequency labels (`m365_sched_freq_daily/weekly/monthly`), "Next" (`m365_sched_next`), "Running..." (`m365_sched_running`), "Disabled" (`m365_sched_disabled`), empty-job text (`m365_sched_no_jobs`), and empty-history text (`m365_sched_no_runs`) all have translation keys. Do not hard-code English strings in `schedLoad()` or `schedRenderJobs()`.
|
||||
- **Profile editor accounts** — default to unchecked. Only explicitly saved `user_ids` are checked.
|
||||
- **Date presets** — stored as `years * 365` (integer days). Do not use `* 365.25`.
|
||||
- **`copyTokenLink` is async** — called from `onclick` attributes as a fire-and-forget (the Promise is unhandled, which is fine). It `await`s `_getShareBaseUrl()` to get the machine's LAN IP before building the URL. Do not make it synchronous or revert to `window.location.origin` directly.
|
||||
+142
-35
@@ -378,6 +378,19 @@ function getGoogleScanOptions() {
|
||||
|
||||
// ── File sources pane ─────────────────────────────────────────────────────────
|
||||
|
||||
function _srcIcon(s) {
|
||||
if (s.source_type === 'sftp') return '\uD83D\uDD12';
|
||||
const isSmb = s.path && (s.path.startsWith('//') || s.path.startsWith('\\\\'));
|
||||
return isSmb ? '\uD83C\uDF10' : '\uD83D\uDCC1';
|
||||
}
|
||||
|
||||
function _srcSubtitle(s) {
|
||||
if (s.source_type === 'sftp') {
|
||||
return _esc((s.sftp_user||'')+'@'+(s.sftp_host||'')+(s.path||'/'));
|
||||
}
|
||||
return _esc(s.path||'')+(s.smb_user?' \u00b7 \uD83D\uDC64 '+_esc(s.smb_user):'');
|
||||
}
|
||||
|
||||
function srcFileRenderList() {
|
||||
const list = document.getElementById('srcFileList');
|
||||
if (!list) return;
|
||||
@@ -386,9 +399,8 @@ function srcFileRenderList() {
|
||||
return;
|
||||
}
|
||||
list.innerHTML = S._fileSources.map(function(s) {
|
||||
const isSmb = s.path && (s.path.startsWith('//') || s.path.startsWith('\\\\'));
|
||||
const icon = isSmb ? '\uD83C\uDF10' : '\uD83D\uDCC1';
|
||||
const sid = _esc(s.id||'');
|
||||
const icon = _srcIcon(s);
|
||||
const sid = _esc(s.id||'');
|
||||
const slabel = _esc(s.label||s.path||'');
|
||||
return '<div class="fsrc-row">'
|
||||
+'<div class="fsrc-row-head">'
|
||||
@@ -398,11 +410,47 @@ function srcFileRenderList() {
|
||||
+'<button class="btn-edit" onclick="srcFileEdit(\''+sid+'\')" style="background:none;border:1px solid var(--border);color:var(--muted);padding:2px 7px;border-radius:4px;font-size:10px;cursor:pointer">'+t('m365_fsrc_edit_btn','Edit')+'</button>'
|
||||
+'<button class="btn-del" onclick="srcFileDelete(\''+sid+'\',\''+slabel+'\')">'+t('m365_profile_delete','Delete')+'</button>'
|
||||
+'</div></div>'
|
||||
+'<div class="fsrc-row-path">'+_esc(s.path||'')+(s.smb_user?' \u00b7 \uD83D\uDC64 '+_esc(s.smb_user):'')+'</div>'
|
||||
+'<div class="fsrc-row-path">'+_srcSubtitle(s)+'</div>'
|
||||
+'</div>';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function srcFileTypeSelect(type) {
|
||||
document.getElementById('srcFileSourceType').value = type;
|
||||
var pathRow = document.getElementById('srcFilePathRow');
|
||||
var smbFields = document.getElementById('srcFileSmbFields');
|
||||
var sftpFields= document.getElementById('srcFileSftpFields');
|
||||
if (pathRow) pathRow.style.display = type === 'sftp' ? 'none' : '';
|
||||
if (smbFields) smbFields.style.display = type === 'smb' ? 'flex' : 'none';
|
||||
if (sftpFields)sftpFields.style.display= type === 'sftp' ? 'flex' : 'none';
|
||||
['srcTypeLocal','srcTypeSmb','srcTypeSftp'].forEach(function(id) {
|
||||
var btn = document.getElementById(id);
|
||||
if (!btn) return;
|
||||
var active = (id === 'srcType' + type.charAt(0).toUpperCase() + type.slice(1));
|
||||
btn.style.background = active ? 'var(--accent)' : 'none';
|
||||
btn.style.color = active ? '#fff' : 'var(--muted)';
|
||||
});
|
||||
}
|
||||
|
||||
function srcFileAutoNameSftp() {
|
||||
var labelEl = document.getElementById('srcFileLabel');
|
||||
if (labelEl && labelEl._userEdited) return;
|
||||
var host = (document.getElementById('srcFileSftpHost')||{}).value || '';
|
||||
if (labelEl && host) labelEl.value = host;
|
||||
}
|
||||
|
||||
function srcFileSftpAuthSelect(authType) {
|
||||
document.getElementById('srcFileSftpAuth').value = authType;
|
||||
var pwFields = document.getElementById('srcSftpPwFields');
|
||||
var keyFields = document.getElementById('srcSftpKeyFields');
|
||||
var btnPw = document.getElementById('srcSftpAuthPw');
|
||||
var btnKey = document.getElementById('srcSftpAuthKey');
|
||||
if (pwFields) pwFields.style.display = authType === 'password' ? '' : 'none';
|
||||
if (keyFields) keyFields.style.display = authType === 'key' ? 'flex' : 'none';
|
||||
if (btnPw) { btnPw.style.background = authType==='password'?'var(--accent)':'none'; btnPw.style.color = authType==='password'?'#fff':'var(--muted)'; }
|
||||
if (btnKey) { btnKey.style.background = authType==='key'?'var(--accent)':'none'; btnKey.style.color = authType==='key'?'#fff':'var(--muted)'; }
|
||||
}
|
||||
|
||||
function srcFileDetectSmb() {
|
||||
const p = document.getElementById('srcFilePath').value;
|
||||
const isSmb = p.startsWith('//') || p.startsWith('\\\\');
|
||||
@@ -427,30 +475,80 @@ function srcFileAutoName() {
|
||||
}
|
||||
|
||||
async function srcFileAdd() {
|
||||
const label = document.getElementById('srcFileLabel').value.trim();
|
||||
const path = document.getElementById('srcFilePath').value.trim();
|
||||
const smbHost = document.getElementById('srcFileSmbHost').value.trim();
|
||||
const smbUser = document.getElementById('srcFileSmbUser').value.trim();
|
||||
const smbPw = document.getElementById('srcFileSmbPw').value;
|
||||
const stat = document.getElementById('srcFileStatus');
|
||||
const label = document.getElementById('srcFileLabel').value.trim();
|
||||
const sourceType = (document.getElementById('srcFileSourceType')||{}).value || 'local';
|
||||
const stat = document.getElementById('srcFileStatus');
|
||||
const editIdEl = document.getElementById('srcFileEditId');
|
||||
const existingId = editIdEl ? editIdEl.value : '';
|
||||
|
||||
if (!label) { stat.style.color='var(--danger)'; stat.textContent=t('m365_fsrc_name_required','Name is required.'); document.getElementById('srcFileLabel').focus(); return; }
|
||||
if (!path) { stat.style.color='var(--danger)'; stat.textContent=t('m365_fsrc_path_required','Path is required.'); return; }
|
||||
stat.style.color='var(--muted)'; stat.textContent=t('m365_fsrc_saving','Saving...');
|
||||
if (smbPw && smbUser) {
|
||||
try { await fetch('/api/file_sources/store_creds',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({smb_host:smbHost,smb_user:smbUser,password:smbPw})}); } catch(e){}
|
||||
|
||||
var body = {label, source_type: sourceType};
|
||||
if (existingId) body.id = existingId;
|
||||
|
||||
if (sourceType === 'sftp') {
|
||||
const sftpHost = document.getElementById('srcFileSftpHost').value.trim();
|
||||
const sftpUser = document.getElementById('srcFileSftpUser').value.trim();
|
||||
const sftpPath = document.getElementById('srcFileSftpPath').value.trim() || '/';
|
||||
const sftpPort = parseInt(document.getElementById('srcFileSftpPort').value) || 22;
|
||||
const sftpAuth = document.getElementById('srcFileSftpAuth').value || 'password';
|
||||
if (!sftpHost) { stat.style.color='var(--danger)'; stat.textContent=t('m365_fsrc_sftp_host_required','SFTP host is required.'); return; }
|
||||
if (!sftpUser) { stat.style.color='var(--danger)'; stat.textContent=t('m365_fsrc_sftp_user_required','SFTP username is required.'); return; }
|
||||
|
||||
Object.assign(body, {sftp_host:sftpHost, sftp_port:sftpPort, sftp_user:sftpUser, sftp_auth:sftpAuth, path:sftpPath});
|
||||
|
||||
if (sftpAuth === 'password') {
|
||||
const sftpPw = document.getElementById('srcFileSftpPw').value;
|
||||
if (sftpPw) {
|
||||
try { await fetch('/api/file_sources/store_creds',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({source_type:'sftp',sftp_host:sftpHost,sftp_user:sftpUser,password:sftpPw})}); } catch(e){}
|
||||
}
|
||||
} else {
|
||||
// Upload key file if one is selected
|
||||
const keyFileEl = document.getElementById('srcFileSftpKeyFile');
|
||||
const keyStatusEl = document.getElementById('srcFileSftpKeyStatus');
|
||||
const keyPathEl = document.getElementById('srcFileSftpKeyPath');
|
||||
if (keyFileEl && keyFileEl.files.length && !keyPathEl.value) {
|
||||
try {
|
||||
const fd = new FormData(); fd.append('key_file', keyFileEl.files[0]);
|
||||
const kr = await fetch('/api/file_sources/upload_key',{method:'POST',body:fd});
|
||||
const kd = await kr.json();
|
||||
if (kd.error) { stat.style.color='var(--danger)'; stat.textContent=kd.error; return; }
|
||||
keyPathEl.value = kd.key_path;
|
||||
if (keyStatusEl) keyStatusEl.textContent = t('m365_fsrc_sftp_key_uploaded','Key uploaded');
|
||||
} catch(e){ stat.style.color='var(--danger)'; stat.textContent=e.message; return; }
|
||||
}
|
||||
body.sftp_key_path = keyPathEl ? keyPathEl.value : '';
|
||||
const passphrase = (document.getElementById('srcFileSftpPassphrase')||{}).value || '';
|
||||
if (passphrase) {
|
||||
const passphraseKey = sftpHost+':'+sftpUser+':passphrase';
|
||||
try { await fetch('/api/file_sources/store_creds',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({source_type:'sftp',sftp_host:sftpHost,sftp_user:sftpUser,password:passphrase,keychain_key:passphraseKey})}); } catch(e){}
|
||||
body.keychain_key = passphraseKey;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const path = document.getElementById('srcFilePath').value.trim();
|
||||
const smbHost = document.getElementById('srcFileSmbHost').value.trim();
|
||||
const smbUser = document.getElementById('srcFileSmbUser').value.trim();
|
||||
const smbPw = document.getElementById('srcFileSmbPw').value;
|
||||
if (!path) { stat.style.color='var(--danger)'; stat.textContent=t('m365_fsrc_path_required','Path is required.'); return; }
|
||||
Object.assign(body, {path, smb_host:smbHost, smb_user:smbUser});
|
||||
if (smbPw && smbUser) {
|
||||
try { await fetch('/api/file_sources/store_creds',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({source_type:'smb',smb_host:smbHost,smb_user:smbUser,password:smbPw})}); } catch(e){}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const editId = document.getElementById('srcFileEditId');
|
||||
const existingId = editId ? editId.value : '';
|
||||
const body = {label, path, smb_host:smbHost, smb_user:smbUser};
|
||||
if (existingId) body.id = existingId;
|
||||
const r = await fetch('/api/file_sources/save',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});
|
||||
const d = await r.json();
|
||||
if (d.error) { stat.style.color='var(--danger)'; stat.textContent=d.error; return; }
|
||||
['srcFileLabel','srcFilePath','srcFileSmbHost','srcFileSmbUser','srcFileSmbPw'].forEach(function(id){const el=document.getElementById(id);if(el){el.value='';el._userEdited=false;}});
|
||||
if (editId) editId.value='';
|
||||
// Reset form
|
||||
['srcFileLabel','srcFilePath','srcFileSmbHost','srcFileSmbUser','srcFileSmbPw',
|
||||
'srcFileSftpHost','srcFileSftpUser','srcFileSftpPw','srcFileSftpPassphrase','srcFileSftpKeyPath'].forEach(function(id){const el=document.getElementById(id);if(el){el.value='';if(el._userEdited!==undefined)el._userEdited=false;}});
|
||||
var portEl = document.getElementById('srcFileSftpPort'); if(portEl) portEl.value='22';
|
||||
if (editIdEl) editIdEl.value='';
|
||||
const addBtn=document.getElementById('srcFileAddBtn'); if(addBtn) addBtn.textContent=t('m365_fsrc_add_btn','Add');
|
||||
document.getElementById('srcFileSmbFields').style.display='none';
|
||||
srcFileTypeSelect('local');
|
||||
stat.style.color='var(--accent)'; stat.textContent='\u2714 '+t('m365_fsrc_saved','Source saved');
|
||||
await _loadFileSources();
|
||||
srcFileRenderList();
|
||||
@@ -462,20 +560,28 @@ function srcFileEdit(id) {
|
||||
const s = S._fileSources.find(function(x){return x.id===id;});
|
||||
if (!s) return;
|
||||
const labelEl = document.getElementById('srcFileLabel');
|
||||
const pathEl = document.getElementById('srcFilePath');
|
||||
const hostEl = document.getElementById('srcFileSmbHost');
|
||||
const userEl = document.getElementById('srcFileSmbUser');
|
||||
const pwEl = document.getElementById('srcFileSmbPw');
|
||||
const editId = document.getElementById('srcFileEditId');
|
||||
if (labelEl) { labelEl.value = s.label||''; labelEl._userEdited = true; }
|
||||
if (pathEl) pathEl.value = s.path||'';
|
||||
if (hostEl) hostEl.value = s.smb_host||'';
|
||||
if (userEl) userEl.value = s.smb_user||'';
|
||||
if (pwEl) pwEl.value = s.smb_user ? '\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022' : '';
|
||||
if (editId) editId.value = id;
|
||||
const isSmb = (s.path||'').startsWith('//') || (s.path||'').startsWith('\\\\');
|
||||
const smbFields = document.getElementById('srcFileSmbFields');
|
||||
if (smbFields) smbFields.style.display = isSmb ? 'flex' : 'none';
|
||||
|
||||
var sourceType = s.source_type || (((s.path||'').startsWith('//')||(s.path||'').startsWith('\\\\')) ? 'smb' : 'local');
|
||||
srcFileTypeSelect(sourceType);
|
||||
|
||||
if (sourceType === 'sftp') {
|
||||
var hostEl = document.getElementById('srcFileSftpHost'); if(hostEl) hostEl.value = s.sftp_host||'';
|
||||
var portEl = document.getElementById('srcFileSftpPort'); if(portEl) portEl.value = s.sftp_port||22;
|
||||
var userEl = document.getElementById('srcFileSftpUser'); if(userEl) userEl.value = s.sftp_user||'';
|
||||
var pathEl = document.getElementById('srcFileSftpPath'); if(pathEl) pathEl.value = s.path||'/';
|
||||
var authEl = document.getElementById('srcFileSftpAuth'); if(authEl) authEl.value = s.sftp_auth||'password';
|
||||
srcFileSftpAuthSelect(s.sftp_auth||'password');
|
||||
if (s.sftp_key_path) { var kp = document.getElementById('srcFileSftpKeyPath'); if(kp) kp.value=s.sftp_key_path; }
|
||||
} else {
|
||||
var pathEl2 = document.getElementById('srcFilePath'); if(pathEl2) pathEl2.value = s.path||'';
|
||||
var smbHostEl = document.getElementById('srcFileSmbHost'); if(smbHostEl) smbHostEl.value = s.smb_host||'';
|
||||
var smbUserEl = document.getElementById('srcFileSmbUser'); if(smbUserEl) smbUserEl.value = s.smb_user||'';
|
||||
var smbPwEl = document.getElementById('srcFileSmbPw'); if(smbPwEl) smbPwEl.value = s.smb_user ? '\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022' : '';
|
||||
}
|
||||
|
||||
const btn = document.getElementById('srcFileAddBtn');
|
||||
if (btn) btn.textContent = t('m365_fsrc_save_changes','Save changes');
|
||||
const stat = document.getElementById('srcFileStatus');
|
||||
@@ -547,9 +653,7 @@ function _renderFileSources() {
|
||||
return;
|
||||
}
|
||||
list.innerHTML = S._fileSources.map(function(s) {
|
||||
const isSmb = s.path && (s.path.startsWith('//') || s.path.startsWith('\\\\'));
|
||||
const icon = isSmb ? '\uD83C\uDF10' : '\uD83D\uDCC1';
|
||||
const userPart = s.smb_user ? ' \u00b7 \uD83D\uDC64 ' + _esc(s.smb_user) : '';
|
||||
const icon = _srcIcon(s);
|
||||
const sid = _esc(s.id || '');
|
||||
const slabel = _esc(s.label || s.path || '');
|
||||
return '<div class="fsrc-row">'
|
||||
@@ -559,7 +663,7 @@ function _renderFileSources() {
|
||||
+ '<button class="btn-scan" onclick="fsrcScan(\'' + sid + '\')">▶ ' + t('m365_fsrc_scan_btn','Scan') + '</button>'
|
||||
+ '<button class="btn-del" onclick="fsrcDelete(\'' + sid + '\',\'' + slabel + '\')">' + t('m365_profile_delete','Delete') + '</button>'
|
||||
+ '</div></div>'
|
||||
+ '<div class="fsrc-row-path">' + _esc(s.path || '') + userPart + '</div>'
|
||||
+ '<div class="fsrc-row-path">' + _srcSubtitle(s) + '</div>'
|
||||
+ '</div>';
|
||||
}).join('');
|
||||
}
|
||||
@@ -667,6 +771,9 @@ window.getGoogleScanOptions = getGoogleScanOptions;
|
||||
window.srcFileRenderList = srcFileRenderList;
|
||||
window.srcFileDetectSmb = srcFileDetectSmb;
|
||||
window.srcFileAutoName = srcFileAutoName;
|
||||
window.srcFileAutoNameSftp = srcFileAutoNameSftp;
|
||||
window.srcFileTypeSelect = srcFileTypeSelect;
|
||||
window.srcFileSftpAuthSelect = srcFileSftpAuthSelect;
|
||||
window.srcFileAdd = srcFileAdd;
|
||||
window.srcFileEdit = srcFileEdit;
|
||||
window.srcFileDelete = srcFileDelete;
|
||||
|
||||
@@ -18,19 +18,19 @@ function schedLoad() {
|
||||
var descEl = document.getElementById('schedDesc_' + js.id);
|
||||
if (!descEl) return;
|
||||
var j2 = _schedJobs.find(function(x){ return x.id === js.id; });
|
||||
var freqLabel = !j2 ? '' : (j2.frequency === 'weekly' ? 'Weekly' : j2.frequency === 'monthly' ? 'Monthly' : 'Daily');
|
||||
var freqLabel = !j2 ? '' : (j2.frequency === 'weekly' ? t('m365_sched_freq_weekly','Weekly') : j2.frequency === 'monthly' ? t('m365_sched_freq_monthly','Monthly') : t('m365_sched_freq_daily','Daily'));
|
||||
var timeStr = !j2 ? '' : String(j2.hour||0).padStart(2,'0') + ':' + String(j2.minute||0).padStart(2,'0');
|
||||
var base = freqLabel + ' ' + timeStr;
|
||||
var runBtn = document.getElementById('schedRunBtn_' + js.id);
|
||||
if (js.is_running) {
|
||||
descEl.textContent = base + ' \u00b7 Running...';
|
||||
descEl.textContent = base + ' \u00b7 ' + t('m365_sched_running','Running...');
|
||||
if (runBtn) { runBtn.style.borderColor='#22c55e'; runBtn.style.color='#22c55e'; }
|
||||
} else if (js.next_run) {
|
||||
var dt = new Date(js.next_run);
|
||||
descEl.textContent = base + ' \u00b7 Next: ' + dt.toLocaleString(undefined,{month:'short',day:'numeric',hour:'2-digit',minute:'2-digit'});
|
||||
descEl.textContent = base + ' \u00b7 ' + t('m365_sched_next','Next') + ': ' + dt.toLocaleString(undefined,{month:'short',day:'numeric',hour:'2-digit',minute:'2-digit'});
|
||||
if (runBtn) { runBtn.style.borderColor='var(--border)'; runBtn.style.color='var(--muted)'; }
|
||||
} else {
|
||||
descEl.textContent = base + (js.enabled ? '' : ' \u00b7 Disabled');
|
||||
descEl.textContent = base + (js.enabled ? '' : ' \u00b7 ' + t('m365_sched_disabled','Disabled'));
|
||||
if (runBtn) { runBtn.style.borderColor='var(--border)'; runBtn.style.color='var(--muted)'; }
|
||||
}
|
||||
});
|
||||
@@ -41,13 +41,13 @@ function schedRenderJobs() {
|
||||
var list = document.getElementById('schedJobList');
|
||||
if (!list) return;
|
||||
if (!_schedJobs.length) {
|
||||
list.innerHTML = '<div style="font-size:11px;color:var(--muted);padding:4px 0">No scheduled scans yet.</div>';
|
||||
list.innerHTML = '<div style="font-size:11px;color:var(--muted);padding:4px 0">' + t('m365_sched_no_jobs','No scheduled scans yet.') + '</div>';
|
||||
return;
|
||||
}
|
||||
list.innerHTML = _schedJobs.map(function(j) {
|
||||
var sid = _esc(j.id);
|
||||
var sname = _esc(j.name || 'Unnamed');
|
||||
var freqLabel = j.frequency === 'weekly' ? 'Weekly' : j.frequency === 'monthly' ? 'Monthly' : 'Daily';
|
||||
var freqLabel = j.frequency === 'weekly' ? t('m365_sched_freq_weekly','Weekly') : j.frequency === 'monthly' ? t('m365_sched_freq_monthly','Monthly') : t('m365_sched_freq_daily','Daily');
|
||||
var timeStr = String(j.hour||0).padStart(2,'0') + ':' + String(j.minute||0).padStart(2,'0');
|
||||
var desc = freqLabel + ' ' + timeStr;
|
||||
var chk = j.enabled ? ' checked' : '';
|
||||
@@ -217,7 +217,7 @@ function schedLoadHistory() {
|
||||
if (!el) return;
|
||||
fetch('/api/scheduler/history?limit=10').then(function(r){ return r.json(); }).then(function(d) {
|
||||
var runs = d.runs || [];
|
||||
if (!runs.length) { el.innerHTML = '<em>No scheduled runs yet</em>'; return; }
|
||||
if (!runs.length) { el.innerHTML = '<em>' + t('m365_sched_no_runs','No scheduled runs yet') + '</em>'; return; }
|
||||
var html = '';
|
||||
runs.forEach(function(r) {
|
||||
var ts = r.started_at ? new Date(r.started_at * 1000).toLocaleString() : '-';
|
||||
|
||||
@@ -62,14 +62,15 @@ function renderSourcesPanel() {
|
||||
S._pendingGoogleSources = null;
|
||||
}
|
||||
|
||||
// File sources (local / SMB) — one entry per saved source
|
||||
// File sources (local / SMB / SFTP) — one entry per saved source
|
||||
if (S._fileSources.length > 0) {
|
||||
html += '<div style="margin:6px 0 2px;font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.04em">'
|
||||
+ '<hr style="border:none;border-top:1px solid var(--border);margin:1px 0 2px">';
|
||||
S._fileSources.forEach(function(s) {
|
||||
const isSmb = s.path && (s.path.startsWith('//') || s.path.startsWith('\\\\'));
|
||||
const icon = isSmb ? '\uD83C\uDF10' : '\uD83D\uDCC1';
|
||||
const label = s.label || s.path || s.id;
|
||||
const isSftp = s.source_type === 'sftp';
|
||||
const isSmb = !isSftp && s.path && (s.path.startsWith('//') || s.path.startsWith('\\\\'));
|
||||
const icon = isSftp ? '\uD83D\uDD12' : (isSmb ? '\uD83C\uDF10' : '\uD83D\uDCC1');
|
||||
const label = s.label || s.path || s.id;
|
||||
const isChecked = (s.id in checked) ? checked[s.id] : true;
|
||||
html += '<label class="source-check">'
|
||||
+ '<input type="checkbox" data-source-id="' + _esc(s.id) + '" data-source-type="file"' + (isChecked ? ' checked' : '') + '>'
|
||||
|
||||
Reference in new issue
Block a user