@אA
@אA כתב בהעלאת תקיות שלימות בצורה מסודרת להפליא בלי להתאמץ🥱:
הקוד המעודכן:
<!DOCTYPE html>
<html lang="he" dir="rtl">
<head>
<meta charset="UTF-8">
<title>מעלה תקיות וקבצים למערכת - עץ שלוחות</title>
<style>
:root {
--primary: #3498db; --success: #2ecc71; --danger: #e74c3c; --bg: #f4f7f6; --dark: #2c3e50;
}
body { font-family: 'Segoe UI', Arial, sans-serif; margin: 0; background-color: var(--bg); color: var(--dark); text-align: right; }
.wrapper { max-width: 900px; margin: 40px auto; padding: 0 20px; position: relative; }
.reset-btn { position: absolute; top: -10px; left: 20px; background: var(--danger); color: white; border: none; padding: 8px 15px; border-radius: 5px; cursor: pointer; font-size: 14px; font-weight: bold; transition: all 0.3s; }
.card { background: white; padding: 30px; border-radius: 15px; box-shadow: 0 10px 25px rgba(0,0,0,0.05); margin-bottom: 25px; border: 1px solid #eee; }
h2 { margin-top: 0; color: var(--dark); border-bottom: 3px solid var(--primary); display: inline-block; padding-bottom: 10px; }
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-top: 20px; }
.input-group { display: flex; flex-direction: column; gap: 8px; }
label { font-weight: bold; font-size: 14px; }
input { padding: 12px; border: 2px solid #eee; border-radius: 8px; font-size: 16px; }
.full-width { grid-column: 1 / -1; }
.btn-main { background: var(--primary); color: white; border: none; padding: 15px; border-radius: 8px; cursor: pointer; font-size: 18px; font-weight: bold; width: 100%; transition: all 0.3s; }
.btn-main:hover { background: #2980b9; }
.btn-main:disabled { background: #bdc3c7; cursor: not-allowed; }
.progress-wrapper { margin-top: 25px; display: none; padding: 15px; background: #fafafa; border-radius: 10px; border: 1px solid #eee; }
.progress-container { width: 100%; background: #e0e0e0; height: 35px; border-radius: 20px; overflow: hidden; position: relative; border: 1px solid #ccc; }
.progress-bar {
height: 100%; width: 0%;
background: linear-gradient(45deg, #2ecc71 25%, #27ae60 25%, #27ae60 50%, #2ecc71 50%, #2ecc71 75%, #27ae60 75%, #27ae60);
background-size: 40px 40px; animation: move-stripes 2s linear infinite; transition: width 0.4s;
}
@keyframes move-stripes { from { background-position: 40px 0; } to { background-position: 0 0; } }
.progress-text { position: absolute; width: 100%; text-align: center; top: 0; line-height: 35px; color: #fff; font-weight: 900; text-shadow: 1px 1px 2px rgba(0,0,0,0.5); }
.log-box { background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 10px; font-family: 'Consolas', monospace; height: 300px; overflow-y: auto; font-size: 13px; line-height: 1.6; border: 4px solid #333; }
.log-info { color: #5dade2; }
.log-success { color: #58d68d; font-weight: bold; }
.log-error { color: #ec7063; }
.log-warn { color: #f4d03f; }
</style>
</head>
<body>
<div class="wrapper">
<button class="reset-btn" onclick="location.reload()">✕ איפוס תהליך</button>
<div class="card">
<h2>מעלה תקיות וקבצים למערכת - עץ שלוחות</h2>
<div class="grid">
<div class="input-group">
<label>טוקן:</label>
<input type="text" id="token">
</div>
<div class="input-group">
<label>שלוחת יעד:</label>
<input type="text" id="targetPath">
</div>
<div class="input-group" style="flex-direction: row; align-items: center; gap: 5px;">
<input type="checkbox" id="autoFileNum"> <label for="autoFileNum">מספור קבצים (אוטומטי)</label>
</div>
<div class="input-group" style="flex-direction: row; align-items: center; gap: 5px;">
<input type="checkbox" id="autoFolderNum"> <label for="autoFolderNum">מספור שלוחות (אוטומטי)</label>
</div>
<div class="input-group full-width">
<label>בחירת תיקייה:</label>
<input type="file" id="folderInput" webkitdirectory>
</div>
<button id="startBtn" class="btn-main" onclick="processUpload()">התחל העלאה</button>
</div>
<div class="progress-wrapper" id="progBox">
<div class="progress-container">
<div class="progress-bar" id="progBar"></div>
<div class="progress-text" id="progText">0%</div>
</div>
</div>
</div>
<div class="card">
<div class="log-box" id="logBox">ממתין...</div>
</div>
</div>
<script>
const CHUNK_SIZE = 4 * 1024 * 1024;
const MAX_SINGLE_FILE = 50 * 1024 * 1024;
const sleep = ms => new Promise(res => setTimeout(res, ms));
const generateUUID = () => 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
let r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
function addLog(msg, type = "info") {
const logBox = document.getElementById('logBox');
logBox.innerHTML += `<div class="log-${type}">> ${msg}</div>`;
logBox.scrollTop = logBox.scrollHeight;
}
async function processUpload() {
const token = document.getElementById('token').value.trim();
const targetPath = document.getElementById('targetPath').value.trim().replace(/\/$/, "");
const fileList = document.getElementById('folderInput').files;
const autoFileNum = document.getElementById('autoFileNum').checked;
const autoFolderNum = document.getElementById('autoFolderNum').checked;
if (!token || !fileList.length || !targetPath) { alert("מלא פרטים"); return; }
document.getElementById('startBtn').disabled = true;
document.getElementById('progBox').style.display = 'block';
const folderStructure = {};
for (let file of fileList) {
const parts = file.webkitRelativePath.split('/'); parts.shift();
const fileName = parts.pop(); const relPath = parts.join('/');
if (!folderStructure[relPath]) folderStructure[relPath] = [];
folderStructure[relPath].push({ name: fileName, file: file });
}
if (!folderStructure[""]) folderStructure[""] = [];
const sortedPaths = Object.keys(folderStructure).sort((a,b) => (a===""?-1:b===""?1:a.split('/').length - b.split('/').length));
// לוגיקת מספור שלוחות
const folderRenameMap = {};
if (autoFolderNum) {
sortedPaths.forEach(path => {
if (path === "") return;
const parts = path.split('/');
const mappedParts = [];
parts.forEach((part, index) => {
const parentPath = parts.slice(0, index).join('/');
const siblings = sortedPaths.filter(p => p !== "" && p.split('/').length === index + 1 && p.split('/').slice(0, index).join('/') === parentPath).sort();
mappedParts.push(siblings.indexOf(parts.slice(0, index + 1).join('/')) + 1);
});
folderRenameMap[path] = mappedParts.join('/');
});
}
for (let i = 0; i < sortedPaths.length; i++) {
const relPath = sortedPaths[i];
const finalSubPath = (autoFolderNum && relPath !== "") ? folderRenameMap[relPath] : relPath;
const currentIvrPath = finalSubPath ? `${targetPath}/${finalSubPath}` : targetPath;
const files = folderStructure[relPath];
// ext.ini
const extFileObj = files.find(f => f.name.toLowerCase().split('.')[0] === 'ext');
let extContent = extFileObj ? await extFileObj.file.text() : (relPath === "" ? "type=menu" : "type=playfile");
await uploadFileNormal(token, `${currentIvrPath}/ext.ini`, new Blob([extContent], {type: 'text/plain'}));
addLog(`שלוחה ${currentIvrPath} הוגדרה`, "success");
files.sort((a, b) => a.name.localeCompare(b.name, undefined, {numeric: true}));
let audioCount = 0;
for (let fObj of files) {
if (fObj.name.toLowerCase().split('.')[0] === 'ext') continue;
let destName = fObj.name; let blob = fObj.file;
if (/\.(wav|mp3|ogg|wma)$/i.test(fObj.name)) {
destName = (autoFileNum ? audioCount.toString().padStart(3, '0') : fObj.name.replace(/\.[^/.]+$/, "")) + ".wav";
audioCount++;
} else if (fObj.name.toLowerCase().includes('.tts')) {
destName = fObj.name.split('.')[0] + ".tts";
blob = new Blob([await fObj.file.text()], {type: 'text/plain'});
}
if (blob.size > MAX_SINGLE_FILE) {
await uploadFileChunked(token, currentIvrPath, destName, blob);
} else {
await uploadFileNormal(token, `${currentIvrPath}/${destName}`, blob);
}
addLog(`הועלה: ${destName}`, "info");
}
const pct = Math.round(((i + 1) / sortedPaths.length) * 100);
document.getElementById('progBar').style.width = pct + '%';
document.getElementById('progText').innerText = pct + '%';
}
addLog("העלאה הסתיימה בהצלחה!", "success");
document.getElementById('startBtn').disabled = false;
}
async function uploadFileNormal(token, fullPath, blob) {
const fd = new FormData();
fd.append('token', token); fd.append('path', `ivr2:${fullPath}`);
fd.append('qqfile', blob, fullPath.split('/').pop());
const res = await fetch(`https://www.call2all.co.il/ym/api/UploadFile`, { method: 'POST', body: fd });
return await res.json();
}
async function uploadFileChunked(token, folderPath, fileName, fileBlob) {
const uuid = generateUUID();
const totalParts = Math.ceil(fileBlob.size / CHUNK_SIZE);
addLog(`מפצל קובץ גדול: ${fileName}`, "warn");
for (let index = 0; index < totalParts; index++) {
const start = index * CHUNK_SIZE; const end = Math.min(start + CHUNK_SIZE, fileBlob.size);
const chunk = fileBlob.slice(start, end);
const fd = new FormData();
fd.append('qquuid', uuid); fd.append('qqpartindex', index);
fd.append('qqpartbyteoffset', start); fd.append('qqchunksize', chunk.size);
fd.append('qqtotalparts', totalParts); fd.append('qqtotalfilesize', fileBlob.size);
fd.append('qqfilename', fileName); fd.append('uploader', 'yemot-admin');
fd.append('qqfile', chunk, fileName);
await fetch(`https://www.call2all.co.il/ym/api/UploadFile`, { method: 'POST', body: fd });
}
const finalUrl = `https://www.call2all.co.il/ym/api/UploadFile?done&token=${token}&path=ivr2:${folderPath}/${fileName}&qquuid=${uuid}&qqfilename=${fileName}&qqtotalfilesize=${fileBlob.size}&qqtotalparts=${totalParts}`;
const res = await fetch(finalUrl, { method: 'POST' });
return await res.json();
}
</script>
</body>
</html>
זה