@הדיבל-המעופף
בשמחה
הקוד
Spoiler
<!DOCTYPE html>
<html lang="he" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>מערכת העברת מאגר מוזיקה ושלוחות</title>
<style>
:root {
--bg-gradient: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 50%, #bae6fd 100%);
--card-bg: #ffffff;
--card-border: #e0f2fe;
--text-main: #0f172a;
--text-secondary: #475569;
--primary-blue: #0284c7;
--primary-blue-hover: #0369a1;
--secondary-blue: #38bdf8;
--danger-red: #ef4444;
--danger-red-hover: #dc2626;
--input-bg: #f8fafc;
--input-border: #cbd5e1;
}
body {
font-family: 'Segoe UI', system-ui, -apple-system, BlinkMacSystemFont, Roboto, sans-serif;
margin: 0;
padding: 40px 20px;
background: var(--bg-gradient);
color: var(--text-main);
min-height: 100vh;
text-align: right;
direction: rtl;
box-sizing: border-box;
}
.container {
max-width: 950px;
margin: 0 auto;
background: var(--card-bg);
border: 1px solid var(--card-border);
border-radius: 20px;
padding: 40px;
box-shadow: 0 20px 40px -15px rgba(2, 132, 199, 0.12);
}
.header {
text-align: center;
margin-bottom: 35px;
}
h2 {
font-size: 2.2rem;
font-weight: 800;
margin: 0 0 8px 0;
color: #0369a1;
letter-spacing: -0.5px;
}
.subtitle {
color: var(--text-secondary);
font-size: 1rem;
margin: 0;
}
h3 {
color: #0c4a6e;
font-size: 1.25rem;
margin-top: 0;
margin-bottom: 16px;
font-weight: 700;
display: flex;
align-items: center;
gap: 8px;
}
.section {
background: #f8fafc;
border: 1px solid #e2e8f0;
padding: 24px;
margin-bottom: 20px;
border-radius: 14px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.02);
}
.input-group {
display: flex;
gap: 12px;
align-items: center;
flex-wrap: wrap;
margin-bottom: 12px;
}
input[type="text"], input[type="password"], input[type="number"] {
padding: 12px 16px;
background: #ffffff;
border: 1px solid var(--input-border);
border-radius: 10px;
color: var(--text-main);
font-size: 14px;
outline: none;
transition: all 0.2s ease;
box-sizing: border-box;
}
input[type="text"]:focus, input[type="password"]:focus, input[type="number"]:focus {
border-color: var(--primary-blue);
box-shadow: 0 0 0 3px rgba(2, 132, 199, 0.15);
}
input[type="text"], input[type="password"] {
flex: 1;
min-width: 180px;
}
.config-input {
width: 70px;
text-align: center;
}
button {
padding: 12px 26px;
background: linear-gradient(135deg, #0284c7 0%, #0369a1 100%);
color: #ffffff;
border: none;
border-radius: 10px;
cursor: pointer;
font-weight: 700;
font-size: 14px;
transition: all 0.2s ease;
box-shadow: 0 4px 12px rgba(2, 132, 199, 0.25);
}
button:hover {
background: linear-gradient(135deg, #0369a1 0%, #075985 100%);
transform: translateY(-1px);
box-shadow: 0 6px 16px rgba(2, 132, 199, 0.35);
}
button.stop-btn {
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.2);
}
button.stop-btn:hover {
background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%);
box-shadow: 0 6px 16px rgba(239, 68, 68, 0.3);
}
button.log-toggle-btn {
background: linear-gradient(135deg, #475569 0%, #334155 100%);
box-shadow: 0 4px 12px rgba(71, 85, 105, 0.2);
}
button.log-toggle-btn:hover {
background: linear-gradient(135deg, #334155 0%, #1e293b 100%);
}
button:disabled {
background: #cbd5e1 !important;
color: #94a3b8 !important;
cursor: not-allowed !important;
transform: none !important;
box-shadow: none !important;
}
.progress-container {
width: 100%;
background: #e2e8f0;
border-radius: 50px;
margin: 20px 0 10px 0;
display: none;
overflow: hidden;
height: 22px;
}
.progress-bar {
width: 0%;
height: 100%;
background: linear-gradient(90deg, #38bdf8 0%, #0284c7 100%);
text-align: center;
line-height: 22px;
color: #ffffff;
transition: width 0.3s ease;
font-size: 12px;
font-weight: 800;
}
.status-text {
font-weight: 700;
color: var(--primary-blue-hover);
text-align: center;
margin-top: 10px;
font-size: 0.95rem;
}
.info-box {
background: #f0f9ff;
border: 1px solid #bae6fd;
color: #0369a1;
padding: 14px 18px;
border-radius: 12px;
margin-bottom: 25px;
font-size: 0.95rem;
display: flex;
align-items: center;
gap: 10px;
font-weight: 600;
}
.log-box {
background: #0f172a;
color: #38bdf8;
padding: 16px;
border-radius: 10px;
height: 220px;
overflow-y: auto;
font-family: 'Consolas', 'Fira Code', monospace;
font-size: 12.5px;
margin-top: 12px;
text-align: left;
direction: ltr;
display: none; /* מוסתר כברירת מחדל */
}
.log-info { color: #38bdf8; }
.log-success { color: #4ade80; font-weight: bold; }
.log-error { color: #f87171; }
.settings-label {
color: var(--text-secondary);
font-size: 0.9rem;
margin-bottom: 6px;
display: block;
font-weight: 600;
}
</style>
</head>
<body onbeforeunload="return 'האם אתה בטוח שברצונך לרענן או לעזוב את הדף? תהליכים פעילים עלולים להיעצר.';">
<div class="container">
<div class="header">
<h2>מערכת העברת מאגר מוזיקה ושלוחות</h2>
<p class="subtitle">שירות העברת קבצים ותצורות שלוחות באופן אוטומטי ומאובטח</p>
</div>
<div class="info-box">
⚡ שלב 1: סרוק את מערכת המקור | שלב 2: הזן פרטי יעד והתחל העברה.
</div>
<!-- חלק 1: סריקת מערכת מקור -->
<div class="section">
<h3>1. סריקת מערכת מקור (סריקת עומק)</h3>
<div class="input-group">
<input type="text" id="srcSystem" placeholder="מספר מערכת מקור">
<input type="password" id="srcPassword" placeholder="סיסמת מערכת מקור">
</div>
<div style="margin-top: 10px;">
<span class="settings-label">הגדרות סריקת מקור:</span>
<div class="input-group">
<input type="text" id="srcStartPath" placeholder="שלוחת התחלה במקור (למשל /1 או /music)" value="/" style="max-width: 300px;">
<span style="color: var(--text-secondary); font-size: 0.9rem;">ספרות לשלוחות:</span>
<input type="number" id="digitCount" class="config-input" value="2" min="1" max="4">
</div>
</div>
<div class="input-group" style="margin-top: 15px;">
<button id="scanBtn" onclick="startScan()">🔍 התחל סריקת עומק</button>
<button id="stopScanBtn" class="stop-btn" onclick="stopScanProcess()" disabled>⏹️ עצור סריקה</button>
</div>
<div id="scanStatus" class="status-text"></div>
</div>
<!-- חלק 2: העברה למערכת יעד -->
<div class="section">
<h3>2. העברה למערכת יעד</h3>
<div class="input-group">
<input type="text" id="destSystem" placeholder="מספר מערכת יעד">
<input type="password" id="destPassword" placeholder="סיסמת מערכת יעד">
<input type="text" id="destStartPath" placeholder="שלוחת יעד (למשל 2, השאר ריק לשרש)" value="">
</div>
<div class="input-group" style="margin-top: 15px;">
<button id="transferBtn" onclick="startTransfer()">🚀 התחל העברה</button>
<button id="stopTransferBtn" class="stop-btn" onclick="stopTransferProcess()" disabled>⏹️ עצור העברה</button>
</div>
<div class="progress-container" id="progCont">
<div id="progBar" class="progress-bar">0%</div>
</div>
<div id="transferStatus" class="status-text"></div>
</div>
<!-- יומן בזמן אמת ומתג הצגה -->
<div class="section">
<div style="display: flex; justify-content: space-between; align-items: center;">
<span style="font-weight: 600; color: var(--text-secondary); font-size: 0.95rem;">יומן מעקב מעודכן (Live Log):</span>
<button id="toggleLogBtn" class="log-toggle-btn" onclick="toggleLog()">👁️ צפה בלוג</button>
</div>
<div class="log-box" id="logBox">היומן מוכן...</div>
</div>
</div>
<script>
let isScanCancelled = false;
let isTransferCancelled = false;
let scannedFiles = [];
let activeSrcToken = "";
function logMessage(msg, type = 'info') {
const logBox = document.getElementById('logBox');
const div = document.createElement('div');
div.className = `log-${type}`;
div.innerHTML = `[${new Date().toLocaleTimeString()}] ${msg}`;
logBox.appendChild(div);
logBox.scrollTop = logBox.scrollHeight;
}
function toggleLog() {
const logBox = document.getElementById('logBox');
const toggleBtn = document.getElementById('toggleLogBtn');
if (logBox.style.display === 'block') {
logBox.style.display = 'none';
toggleBtn.innerText = '👁️ צפה בלוג';
} else {
logBox.style.display = 'block';
toggleBtn.innerText = '🙈 הסתר לוג';
}
}
function stopScanProcess() {
isScanCancelled = true;
document.getElementById('scanStatus').innerText = "עוצר סריקה... נא להמתין לקובץ הנוכחי.";
document.getElementById('stopScanBtn').disabled = true;
}
function stopTransferProcess() {
isTransferCancelled = true;
document.getElementById('transferStatus').innerText = "עוצר העברה... נא להמתין לקובץ הנוכחי.";
document.getElementById('stopTransferBtn').disabled = true;
}
async function fetchApiToken(rawUsername, password) {
const cleanUser = rawUsername.replace(/[^0-9]/g, '');
logMessage(`מתחבר למערכת: ${cleanUser}...`, 'info');
try {
const loginUrl = `https://www.call2all.co.il/ym/api/Login?username=${encodeURIComponent(cleanUser)}&password=${encodeURIComponent(password)}`;
const res = await fetch(loginUrl);
const data = await res.json();
if (data.responseStatus === "OK" && data.token) {
logMessage(`התחברות הצליחה!`, 'success');
return data.token;
}
} catch (err) {}
const directToken = `${cleanUser}:${password}`;
const testUrl = `https://www.call2all.co.il/ym/api/GetIVR2Dir?token=${encodeURIComponent(directToken)}&path=/`;
const testRes = await fetch(testUrl);
const testData = await testRes.json();
if (testData.responseStatus === "OK") {
logMessage(`אימות הצליח באמצעות טוקן ישיר.`, 'success');
return directToken;
} else {
const errorMsg = testData.message || "מספר מערכת או סיסמה שגויים";
logMessage(`שגיאה מהשרת: ${errorMsg}`, 'error');
throw new Error(`אימות נכשל עבור מערכת ${cleanUser}: ${errorMsg}`);
}
}
function getRelativePath(fullPath, startPath) {
let cleanFull = fullPath.replace(/\/+/g, '/');
let cleanStart = startPath.replace(/\/+/g, '/');
if (cleanStart !== '/' && cleanFull.startsWith(cleanStart)) {
let rel = cleanFull.substring(cleanStart.length);
return rel.startsWith('/') ? rel : '/' + rel;
}
return cleanFull;
}
function addOrUpdateFile(fileAcc, fileObj) {
const existing = fileAcc.find(f => f.fullPath === fileObj.fullPath);
if (!existing) {
fileAcc.push(fileObj);
}
}
// סריקת עומק רקורסיבית הנכנסת לכל השלוחות והתת-שלוחות
async function scanFolderRecursive(token, sPath, rootStartPath, digits, fileAcc, statusElement) {
if (isScanCancelled) return;
sPath = sPath.replace(/\/+/g, '/');
const maxRange = Math.pow(10, digits) - 1;
logMessage(`סורק שלוחה: ${sPath}`, 'info');
statusElement.innerText = `סורק שלוחה: ${sPath}... (${fileAcc.length} קבצים נמצאו עד כה)`;
let children = [];
try {
const res = await fetch(`https://www.call2all.co.il/ym/api/GetIVR2Dir?token=${encodeURIComponent(token)}&path=${encodeURIComponent(sPath)}`);
const data = await res.json();
if (data.files && Array.isArray(data.files)) {
children = data.files;
}
} catch(e) {}
// תחקור אקטיבי לשלוחות מספריות (0 עד maxRange)
const subScan = [];
for (let i = 0; i <= maxRange; i++) {
const n = i.toString();
if (!children.find(c => c.name === n)) {
const probePath = `${sPath}/${n}/ext.ini`.replace(/\/+/g, '/');
subScan.push(
fetch(`https://www.call2all.co.il/ym/api/GetTextFile?token=${encodeURIComponent(token)}&what=ivr2:${probePath}`)
.then(r => r.json())
.then(d => {
if (d && d.contents !== undefined) {
children.push({ name: n, fileType: "DIR" });
}
})
.catch(() => {})
);
}
}
await Promise.all(subScan);
// מעבר על הפריטים והמשך סריקה רקורסיבית עמוקה
for (const f of children) {
if (isScanCancelled) break;
const itemPath = `${sPath}/${f.name}`.replace(/\/+/g, '/');
const itemType = (f.fileType || '').toUpperCase();
const isDir = itemType === "DIR" || itemType === "FOLDER" || f.isDir || (!isNaN(f.name) && !f.name.includes('.'));
if (isDir) {
try {
const iniPath = `${itemPath}/ext.ini`.replace(/\/+/g, '/');
const iniRes = await fetch(`https://www.call2all.co.il/ym/api/GetTextFile?token=${encodeURIComponent(token)}&what=ivr2:${iniPath}`);
const iniData = await iniRes.json();
if (iniData && iniData.contents !== undefined) {
addOrUpdateFile(fileAcc, {
fullPath: iniPath,
relPath: getRelativePath(iniPath, rootStartPath),
fileName: 'ext.ini',
isIni: true,
contents: iniData.contents
});
logMessage(`נמצא קובץ הגדרות: ${iniPath}`, 'info');
}
} catch(e) {}
// כניסה רקורסיבית עמוקה לתוך תת-השלוחה!
await scanFolderRecursive(token, itemPath, rootStartPath, digits, fileAcc, statusElement);
} else {
if (f.name === "ext.ini") continue;
logMessage(`נמצא קובץ: ${itemPath}`, 'info');
addOrUpdateFile(fileAcc, {
fullPath: itemPath,
relPath: getRelativePath(itemPath, rootStartPath),
fileName: f.name,
isIni: false
});
}
}
}
async function startScan() {
const srcSys = document.getElementById('srcSystem').value.trim();
const srcPass = document.getElementById('srcPassword').value.trim();
let srcStartPath = document.getElementById('srcStartPath').value.trim() || '/';
const digits = parseInt(document.getElementById('digitCount').value) || 2;
if (!srcSys || !srcPass) return alert("נא למלא מספר מערכת מקור וסיסמה!");
if (!srcStartPath.startsWith('/')) srcStartPath = '/' + srcStartPath;
isScanCancelled = false;
scannedFiles = [];
const scanBtn = document.getElementById('scanBtn');
const stopScanBtn = document.getElementById('stopScanBtn');
const status = document.getElementById('scanStatus');
scanBtn.disabled = true;
stopScanBtn.disabled = false;
status.innerText = "מתחבר למערכת המקור...";
try {
logMessage("--- התחלת סריקת עומק למערכת מקור ---", 'info');
activeSrcToken = await fetchApiToken(srcSys, srcPass);
localStorage.setItem('src_token', activeSrcToken);
status.innerText = `סורק משלוחת התחלה (${srcStartPath})...`;
// הרצת סריקת העומק הרקורסיבית
await scanFolderRecursive(activeSrcToken, srcStartPath, srcStartPath, digits, scannedFiles, status);
if (isScanCancelled) {
status.innerText = "🛑 הסריקה נעצרה על ידי המשתמש.";
} else {
localStorage.setItem('music_database_files', JSON.stringify(scannedFiles));
status.innerText = `✅ סריקת העומק הושלמה בהצלחה! נמצאו ${scannedFiles.length} קבצים ושלוחות מוכנים להעברה.`;
logMessage(`סריקה הושלמה! ${scannedFiles.length} קבצים נשמרו בזיכרון.`, 'success');
}
} catch (err) {
alert(err.message);
status.innerText = "הסריקה הופסקה בשל שגיאה.";
} finally {
scanBtn.disabled = false;
stopScanBtn.disabled = true;
}
}
async function startTransfer() {
const destSys = document.getElementById('destSystem').value.trim();
const destPass = document.getElementById('destPassword').value.trim();
let destStartPath = document.getElementById('destStartPath').value.trim();
if (!destSys || !destPass) return alert("נא למלא מספר מערכת יעד וסיסמה.");
if (!scannedFiles || scannedFiles.length === 0) {
scannedFiles = JSON.parse(localStorage.getItem('music_database_files') || '[]');
}
const srcToken = activeSrcToken || localStorage.getItem('src_token');
if (!srcToken || scannedFiles.length === 0) {
return alert("טרם בוצעה סריקה! נא לבצע סריקת עומק של מערכת המקור בחלק 1 תחילה.");
}
if (destStartPath && !destStartPath.startsWith('/')) {
destStartPath = '/' + destStartPath;
}
isTransferCancelled = false;
const transferBtn = document.getElementById('transferBtn');
const stopTransferBtn = document.getElementById('stopTransferBtn');
const status = document.getElementById('transferStatus');
const progBar = document.getElementById('progBar');
transferBtn.disabled = true;
stopTransferBtn.disabled = false;
document.getElementById('progCont').style.display = 'block';
progBar.style.width = '0%';
progBar.innerText = '0%';
status.innerText = "מתחבר למערכת היעד...";
try {
logMessage("--- התחלת העברה למערכת יעד ---", 'info');
const destToken = await fetchApiToken(destSys, destPass);
for (let i = 0; i < scannedFiles.length; i++) {
if (isTransferCancelled) {
status.innerText = "🛑 ההעברה נעצרה על ידי המשתמש.";
break;
}
const item = scannedFiles[i];
let itemRel = item.relPath || item.fullPath;
let targetFullPath = (destStartPath ? `${destStartPath}/${itemRel}` : itemRel).replace(/\/+/g, '/');
status.innerText = `מעביר קובץ (${i + 1}/${scannedFiles.length}): ${targetFullPath}`;
logMessage(`מעביר: ${item.fullPath} ➔ ${targetFullPath}`, 'info');
try {
let fileBlob;
if (item.isIni && item.contents !== undefined) {
fileBlob = new Blob([item.contents], { type: 'text/plain;charset=utf-8' });
} else {
const downloadUrl = `https://www.call2all.co.il/ym/api/DownloadFile?token=${encodeURIComponent(srcToken)}&path=ivr2:${encodeURIComponent(item.fullPath)}`;
const downloadRes = await fetch(downloadUrl);
if (!downloadRes.ok) throw new Error('הורדת קובץ נכשלה');
fileBlob = await downloadRes.blob();
}
const formData = new FormData();
formData.append('token', destToken);
formData.append('path', `ivr2:${targetFullPath}`);
formData.append('qqfile', fileBlob, item.fileName);
await fetch(`https://www.call2all.co.il/ym/api/UploadFile`, {
method: 'POST',
body: formData
});
} catch (err) {
logMessage(`שגיאה בהעברת ${targetFullPath}: ${err.message}`, 'error');
}
const percent = Math.round(((i + 1) / scannedFiles.length) * 100);
progBar.style.width = percent + '%';
progBar.innerText = percent + '%';
}
if (!isTransferCancelled) {
status.innerText = '✅ העברת מאגר המוזיקה והשלוחות הושלמה בהצלחה!';
logMessage('ההעברה הושלמה בהצלחה!', 'success');
}
} catch (err) {
alert(err.message);
status.innerText = "ההעברה הופסקה בשל שגיאה.";
} finally {
transferBtn.disabled = false;
stopTransferBtn.disabled = true;
}
}
</script>
</body>
</html>