מצורף הקובץ מתוקן ועובד טוב מאוד עם מלא שידורגים
בשביל לקבל נתוני השמעות תוסיפו במערכת בהגדרות מתקדמות בשלוחה הראשית בקובץ IVR.ini את ההגדרות הבאות
log_playback_play_stop=yes
log_playback_play_stop_ymgr_to_html=yes
log_playback_play_stop_year_save_folder=yes
log_playback_play_stop_year_save_folder_ymgr_to_html=yes
log_playback_play_stop_month_save_root_log=yes
log_playback_play_stop_month_save_root_log_ymgr_to_html=yes
הנה הקוד:
<!DOCTYPE html>
<html lang="he" dir="rtl">
<head>
<meta charset="UTF-8">
<title>ניתוח נתוני מערכות | v36.0 - גרסה מאובטחת</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<style>
:root { --bg: #0f172a; --card: #1e293b; --primary: #38bdf8; --accent: #22c55e; --text: #f1f5f9; --border: #334155; --danger: #ef4444; --warning: #f59e0b; }
body { font-family: 'Segoe UI', sans-serif; background: var(--bg); color: var(--text); margin: 0; display: flex; flex-direction: column; height: 100vh; }
header { background: #1e293b; padding: 1rem 2rem; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--border); }
.logo-box { display: flex; align-items: center; gap: 12px; }
.logo-icon { background: linear-gradient(135deg, var(--primary), #0ea5e9); color: #0f172a; width: 40px; height: 40px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 1.2rem; }
.logo-text { font-size: 1.4rem; font-weight: 800; color: var(--text); }
.config-box { display: flex; gap: 12px; align-items: center; }
input, select { background: #0f172a; border: 1px solid var(--border); color: white; padding: 10px; border-radius: 8px; outline: none; }
.btn-run { background: var(--primary); color: #0f172a; border: none; padding: 10px 25px; border-radius: 8px; font-weight: bold; cursor: pointer; transition: 0.2s; }
.btn-run:hover { opacity: 0.9; transform: scale(1.02); }
.btn-reset-date { background: transparent; color: #94a3b8; border: 1px solid var(--border); padding: 8px; border-radius: 8px; cursor: pointer; }
.btn-export { background: #475569; color: white; border: none; padding: 6px 12px; border-radius: 6px; cursor: pointer; font-size: 0.8rem; display: flex; align-items: center; gap: 5px; }
.nav-tabs { background: #1e293b; display: flex; padding: 0 20px; border-bottom: 1px solid var(--border); overflow-x: auto; }
.tab { padding: 15px 25px; cursor: pointer; color: #94a3b8; border-bottom: 3px solid transparent; transition: 0.3s; white-space: nowrap; }
.tab.active { color: var(--primary); border-bottom-color: var(--primary); background: rgba(56, 189, 248, 0.07); }
.main-content { flex: 1; padding: 25px; overflow-y: auto; background: #0f172a; }
.card { background: var(--card); padding: 20px; border-radius: 16px; border: 1px solid var(--border); margin-bottom: 25px; }
.stat-num { font-size: 2.2rem; font-weight: 800; color: var(--primary); }
.control-bar { display: flex; gap: 20px; align-items: center; background: #334155; padding: 12px 20px; border-radius: 12px 12px 0 0; justify-content: space-between; flex-wrap: wrap; }
.search-input { background: #0f172a; border: 1px solid var(--border); color: white; padding: 8px 15px; border-radius: 8px; width: 250px; }
table { width: 100%; border-collapse: collapse; background: var(--card); border-radius: 0 0 12px 12px; overflow: hidden; min-width: 600px; }
th { text-align: right; padding: 12px; background: #475569; color: white; font-size: 0.85rem; cursor: pointer; user-select: none; transition: 0.2s; position: relative; }
th:hover { background: #64748b; }
th i { margin-right: 8px; font-size: 0.7rem; opacity: 0.5; }
th.sort-asc i, th.sort-desc i { opacity: 1; color: var(--primary); }
td { padding: 12px; border-bottom: 1px solid var(--border); font-size: 0.9rem; }
.progress-container { width: 100%; background: #1e293b; height: 6px; display: none; position: relative; }
#progress-fill { height: 100%; background: var(--primary); width: 0%; transition: 0.4s; }
.hidden { display: none; }
.btn-play { color: var(--primary); cursor: pointer; background: transparent; border: 1px solid var(--primary); padding: 2px 8px; border-radius: 4px; font-size: 0.8rem; }
.btn-play:hover { background: var(--primary); color: #0f172a; }
</style>
</head>
<body>
<header>
<div class="logo-box">
<div class="logo-icon"><i class="fas fa-headphones"></i></div>
<div class="logo-text">ניתוח נתוני מערכות</div>
</div>
<div class="config-box">
<!-- השדה ריק כעת לצורך אבטחה -->
<input type="password" id="apiToken" placeholder="הכנס טוקן API כאן" value="">
<button class="btn-reset-date" title="חזור לתחילת חודש" onclick="setDefaultDates()"><i class="fas fa-calendar-alt"></i></button>
<input type="date" id="startDate">
<input type="date" id="endDate">
<button class="btn-run" onclick="manualStart()">הפעל סריקה</button>
</div>
</header>
<div class="progress-container" id="progBar"><div id="progress-fill"></div></div>
<div class="nav-tabs">
<div class="tab active" onclick="switchTab(event, 'dash-tab')">דאשבורד</div>
<div class="tab" onclick="switchTab(event, 'ext-tab')">כניסות לשלוחות</div>
<div class="tab" onclick="switchTab(event, 'play-tab')">לוג השמעות</div>
<div class="tab" onclick="switchTab(event, 'users-tab')">מאזינים</div>
<div class="tab" style="color: var(--warning)" onclick="switchTab(event, 'debug-tab')">לוג טכני</div>
</div>
<div class="main-content">
<div id="dash-tab" class="tab-content">
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin-bottom: 25px;">
<div class="card"><span style="color: #94a3b8">מאזינים ייחודיים</span><br><span id="stat-users" class="stat-num">0</span></div>
<div class="card"><span style="color: #94a3b8">סה"כ דקות פעילות</span><br><span id="stat-min" class="stat-num">0</span></div>
<div class="card"><span style="color: #94a3b8">השמעות שהושלמו</span><br><span id="stat-comp" class="stat-num">0</span></div>
</div>
<div class="card"><h3>פעילות יומית</h3><canvas id="dailyChart" height="100"></canvas></div>
</div>
<div id="ext-tab" class="tab-content hidden">
<div class="control-bar">
<input type="text" id="extSearch" class="search-input" placeholder="חפש שלוחה..." onkeyup="renderExts()">
<button class="btn-export" onclick="exportTableToCSV('extTable', 'שלוחות')"><i class="fas fa-file-excel"></i> ייצוא</button>
</div>
<table id="extTable">
<thead>
<tr>
<th onclick="sortTable('extTable', 0, 'string')">שלוחה <i class="fas fa-sort"></i></th>
<th onclick="sortTable('extTable', 1, 'number')">כניסות <i class="fas fa-sort"></i></th>
<th onclick="sortTable('extTable', 2, 'number')">ייחודיים <i class="fas fa-sort"></i></th>
<th onclick="sortTable('extTable', 3, 'number')">סה"כ דקות <i class="fas fa-sort"></i></th>
<th onclick="sortTable('extTable', 4, 'number')">ממוצע <i class="fas fa-sort"></i></th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<div id="play-tab" class="tab-content hidden">
<div class="control-bar">
<input type="text" id="playSearch" class="search-input" placeholder="חפש קובץ, שלוחה או טלפון..." onkeyup="renderPlays()">
<button class="btn-export" onclick="exportTableToCSV('playTable', 'השמעות')"><i class="fas fa-file-excel"></i> ייצוא</button>
</div>
<table id="playTable">
<thead>
<tr>
<th onclick="sortTable('playTable', 0, 'string')">זמן אחרון <i class="fas fa-sort"></i></th>
<th onclick="sortTable('playTable', 1, 'string')">שלוחה <i class="fas fa-sort"></i></th>
<th onclick="sortTable('playTable', 2, 'string')">קובץ <i class="fas fa-sort"></i></th>
<th onclick="sortTable('playTable', 3, 'string')">מאזין אחרון <i class="fas fa-sort"></i></th>
<th onclick="sortTable('playTable', 4, 'number')">השמעות <i class="fas fa-sort"></i></th>
<th onclick="sortTable('playTable', 5, 'number')">סה"כ דק' <i class="fas fa-sort"></i></th>
<th onclick="sortTable('playTable', 6, 'number')">אחוז <i class="fas fa-sort"></i></th>
<th>פעולה</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<div id="users-tab" class="tab-content hidden">
<div class="control-bar">
<input type="text" id="userSearch" class="search-input" placeholder="חפש טלפון..." onkeyup="renderUsers()">
<button class="btn-export" onclick="exportTableToCSV('usersTable', 'מאזינים')"><i class="fas fa-file-excel"></i> ייצוא</button>
</div>
<table id="usersTable">
<thead>
<tr>
<th onclick="sortTable('usersTable', 0, 'string')">מספר טלפון <i class="fas fa-sort"></i></th>
<th onclick="sortTable('usersTable', 1, 'number')">דקות האזנה <i class="fas fa-sort"></i></th>
<th onclick="sortTable('usersTable', 2, 'number')">פעולות <i class="fas fa-sort"></i></th>
<th onclick="sortTable('usersTable', 3, 'string')">סוג <i class="fas fa-sort"></i></th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<div id="debug-tab" class="tab-content hidden">
<div class="card">
<div id="debug-console" style="background:#000; color:#22c55e; padding:15px; height:450px; overflow-y:auto; font-family:monospace; font-size: 12px; line-height: 1.4;">
<div>מערכת v36.0 מוכנה לאחסון בשרת. הטוקן הוסר מהקוד.</div>
</div>
</div>
</div>
</div>
<div id="playerModal" class="hidden" style="position:fixed; bottom:20px; left:20px; background:var(--card); padding:15px; border-radius:12px; border:1px solid var(--primary); z-index:1000; box-shadow: 0 10px 30px rgba(0,0,0,0.5);">
<div style="display:flex; justify-content:space-between; margin-bottom:10px;">
<span id="playerTitle" style="font-size:0.8rem; color:var(--primary)">נגן השמעה</span>
<i class="fas fa-times" style="cursor:pointer" onclick="closePlayer()"></i>
</div>
<audio id="audioPlayer" controls style="width:250px; height:35px;"></audio>
<div id="playerError" style="color:var(--danger); font-size:11px; margin-top:5px; display:none;">שגיאה בטעינת הקובץ</div>
</div>
<script>
const API = "https://www.call2all.co.il/ym/api/";
let dataStore = { exts: {}, plays: {}, users: {}, daily: {}, totalSec: 0, completed: 0 };
let dailyChart = null;
let sortState = { tableId: null, colIndex: null, direction: 'asc' };
function setDefaultDates() {
const now = new Date();
const firstDay = new Date(now.getFullYear(), now.getMonth(), 1);
const f = (d) => {
let year = d.getFullYear();
let month = String(d.getMonth() + 1).padStart(2, '0');
let day = String(d.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
document.getElementById('startDate').value = f(firstDay);
document.getElementById('endDate').value = f(now);
}
function addLog(msg, color = '#22c55e') {
const console = document.getElementById('debug-console');
const div = document.createElement('div');
div.style.color = color;
div.innerHTML = `[${new Date().toLocaleTimeString()}] ${msg}`;
console.appendChild(div);
console.scrollTop = console.scrollHeight;
}
async function manualStart() {
const token = document.getElementById('apiToken').value;
const start = document.getElementById('startDate').value;
const end = document.getElementById('endDate').value;
if (!token) return alert("נא להזין טוקן API");
addLog("מתחיל סריקה...");
dataStore = { exts: {}, plays: {}, users: {}, daily: {}, totalSec: 0, completed: 0 };
document.getElementById('progBar').style.display = 'block';
const monthKey = start.substring(0, 7);
try {
const res = await fetch(`${API}RenderYMGRFile?token=${token}&wath=ivr2:/Log/LogFolderEnterExit-${monthKey}.ymgr&convertType=json`);
const json = await res.json();
if (json.data) {
json.data.forEach(row => {
const ext = row["שלוחה"] || "ראשי";
const phone = row["טלפון"] || "חסוי";
const sec = parseInt(row["סה\"כ שניות"] || row["שניות"] || 0);
const date = row["תאריך"] || start;
if (!dataStore.exts[ext]) dataStore.exts[ext] = { count: 0, sec: 0, unique: new Set() };
dataStore.exts[ext].count++;
dataStore.exts[ext].sec += sec;
dataStore.exts[ext].unique.add(phone);
trackUser(phone, sec);
dataStore.daily[date] = (dataStore.daily[date] || 0) + 1;
dataStore.totalSec += sec;
});
}
} catch(e) { addLog("לוג כניסות לא נמצא.", "#f59e0b"); }
let days = [];
let dt = new Date(start);
const endDt = new Date(end);
while(dt <= endDt) {
days.push(new Date(dt).toISOString().split('T')[0]);
dt.setDate(dt.getDate() + 1);
}
for (let day of days) {
updateProgress(days.indexOf(day)+1, days.length);
const paths = [`ivr2:/Log/LogPlaybackPlayStop/LogPlaybackPlayStop.${day}.ymgr`,`ivr2:/Log/LogPlaybackPlayStop.${day}.ymgr`,`ivr2:/Log/LogPlayback.${day}.ymgr`];
for (let path of paths) {
try {
const res = await fetch(`${API}RenderYMGRFile?token=${token}&wath=${path}&convertType=json`);
const json = await res.json();
if (json.data && json.data.length > 0) {
addLog(`יום ${day}: נמצאו נתוני השמעות.`);
processPlaybackRows(json.data, day);
break;
}
} catch(e) {}
}
}
renderUI();
}
function processPlaybackRows(rows, day) {
rows.forEach(row => {
const keys = Object.keys(row);
const extKey = keys.find(k => k.includes("שלוחה") || k.includes("Folder") || k.includes("תיקייה")) || "שלוחה";
const fileKey = keys.find(k => k.includes("קובץ") || k.includes("השמעה") || k.includes("Play")) || keys[0];
const secKey = keys.find(k => k.includes("שניות") || k.includes("זמן") || k.includes("Sec")) || keys[1];
const exitKey = keys.find(k => k.includes("יציאה") || k.includes("Exit")) || "";
const timeKey = keys.find(k => k.includes("שעה") || k.includes("Time")) || "";
const extName = row[extKey] || "לא ידוע";
const fileName = row[fileKey] || "לא ידוע";
const phone = row["טלפון"] || "חסוי";
const sec = parseInt(row[secKey] || 0);
const time = row[timeKey] || "";
const isEnd = (row[exitKey] || "").toString().includes("סוף") || (row[exitKey] || "").toString().toLowerCase() === "end";
const playId = `${extName}|${fileName}`;
if (!dataStore.plays[playId]) {
dataStore.plays[playId] = { ext: extName, file: fileName, lastPhone: phone, lastTime: `${day} ${time}`, count: 0, sec: 0, ends: 0 };
}
dataStore.plays[playId].count++;
dataStore.plays[playId].sec += sec;
dataStore.plays[playId].lastPhone = phone;
dataStore.plays[playId].lastTime = `${day} ${time}`;
if (isEnd) dataStore.plays[playId].ends++;
trackUser(phone, sec);
dataStore.daily[day] = (dataStore.daily[day] || 0) + 1;
dataStore.totalSec += sec;
if (isEnd) dataStore.completed++;
});
}
async function playFile(ext, file) {
const token = document.getElementById('apiToken').value;
const modal = document.getElementById('playerModal');
const player = document.getElementById('audioPlayer');
const errDiv = document.getElementById('playerError');
errDiv.style.display = 'none';
modal.classList.remove('hidden');
document.getElementById('playerTitle').innerText = `טוען: שלוחה ${ext} | קובץ ${file}`;
let cleanExt = ext.replace('ivr2:', '').replace(/^\//, '');
if (!cleanExt.startsWith('ivr2:')) cleanExt = 'ivr2:/' + cleanExt;
const fileBase = file.padStart(3, '0');
const formats = ['.wav', '.mp3', '.amr'];
let success = false;
for (const fmt of formats) {
const fullPath = `${cleanExt}/${fileBase}${fmt}`;
const url = `${API}DownloadFile?token=${token}&path=${fullPath}`;
try {
player.src = url;
await player.play();
success = true;
document.getElementById('playerTitle').innerText = `מנגן: ${ext}/${fileBase}${fmt}`;
break;
} catch (e) {}
}
if (!success) errDiv.style.display = 'block';
}
function closePlayer() {
const modal = document.getElementById('playerModal');
const player = document.getElementById('audioPlayer');
modal.classList.add('hidden');
player.pause();
player.src = "";
}
function trackUser(p, s) {
if (!dataStore.users[p]) dataStore.users[p] = { sec: 0, count: 0 };
dataStore.users[p].sec += s;
dataStore.users[p].count++;
}
function sortTable(tableId, colIndex, type) {
const table = document.getElementById(tableId);
const tbody = table.querySelector('tbody');
const rows = Array.from(tbody.querySelectorAll('tr'));
const headers = table.querySelectorAll('th');
if (sortState.tableId === tableId && sortState.colIndex === colIndex) {
sortState.direction = sortState.direction === 'asc' ? 'desc' : 'asc';
} else {
sortState.tableId = tableId;
sortState.colIndex = colIndex;
sortState.direction = 'asc';
}
headers.forEach(th => th.classList.remove('sort-asc', 'sort-desc'));
headers[colIndex].classList.add(sortState.direction === 'asc' ? 'sort-asc' : 'sort-desc');
const sortedRows = rows.sort((a, b) => {
let aVal = a.children[colIndex].innerText;
let bVal = b.children[colIndex].innerText;
if (type === 'number') {
aVal = parseFloat(aVal.replace(/[^0-9.-]+/g, "")) || 0;
bVal = parseFloat(bVal.replace(/[^0-9.-]+/g, "")) || 0;
} else {
aVal = aVal.toLowerCase();
bVal = bVal.toLowerCase();
}
if (aVal < bVal) return sortState.direction === 'asc' ? -1 : 1;
if (aVal > bVal) return sortState.direction === 'asc' ? 1 : -1;
return 0;
});
tbody.innerHTML = '';
sortedRows.forEach(row => tbody.appendChild(row));
}
function renderUI() {
document.getElementById('progBar').style.display = 'none';
document.getElementById('stat-users').innerText = Object.keys(dataStore.users).length.toLocaleString();
document.getElementById('stat-min').innerText = Math.floor(dataStore.totalSec / 60).toLocaleString();
document.getElementById('stat-comp').innerText = dataStore.completed.toLocaleString();
renderExts(); renderPlays(); renderUsers(); updateChart();
}
function renderExts() {
const q = document.getElementById('extSearch').value.toLowerCase();
let h = "";
Object.entries(dataStore.exts).forEach(([n, d]) => {
if (n.toLowerCase().includes(q)) {
h += `<tr><td style="color:var(--primary); font-weight:bold">${n}</td><td>${d.count}</td><td>${d.unique.size}</td><td>${(d.sec/60).toFixed(1)}</td><td>${Math.round(d.sec/d.count || 0)} ש'</td></tr>`;
}
});
document.querySelector('#extTable tbody').innerHTML = h;
}
function renderPlays() {
const q = document.getElementById('playSearch').value.toLowerCase();
let h = "";
Object.values(dataStore.plays).forEach(d => {
if (d.file.toLowerCase().includes(q) || d.ext.toLowerCase().includes(q) || d.lastPhone.includes(q)) {
const pct = Math.round((d.ends/d.count)*100);
h += `<tr>
<td style="font-size:0.75rem; color:#94a3b8">${d.lastTime}</td>
<td style="color:var(--primary)">${d.ext}</td>
<td style="font-weight:bold">${d.file}</td>
<td>${d.lastPhone}</td>
<td>${d.count}</td>
<td>${(d.sec/60).toFixed(1)}</td>
<td>${pct}%</td>
<td><button class="btn-play" onclick="playFile('${d.ext}', '${d.file}')"><i class="fas fa-play"></i></button></td>
</tr>`;
}
});
document.querySelector('#playTable tbody').innerHTML = h || "<tr><td colspan='8' style='text-align:center'>אין נתונים</td></tr>";
}
function renderUsers() {
const q = document.getElementById('userSearch').value;
let h = "";
Object.entries(dataStore.users).forEach(([p, d]) => {
if (p.includes(q)) {
h += `<tr><td>${p}</td><td>${(d.sec/60).toFixed(1)}</td><td>${d.count}</td><td>מאזין</td></tr>`;
}
});
document.querySelector('#usersTable tbody').innerHTML = h;
}
function exportTableToCSV(tableId, filename) {
let csv = [];
const rows = document.querySelectorAll(`#${tableId} tr`);
for (let i = 0; i < rows.length; i++) {
let row = [], cols = rows[i].querySelectorAll("td, th");
for (let j = 0; j < cols.length; j++) row.push('"' + cols[j].innerText + '"');
csv.push(row.join(","));
}
const csvFile = new Blob(["\ufeff" + csv.join("\n")], { type: "text/csv" });
const downloadLink = document.createElement("a");
downloadLink.download = `${filename}.csv`;
downloadLink.href = window.URL.createObjectURL(csvFile);
downloadLink.style.display = "none";
document.body.appendChild(downloadLink);
downloadLink.click();
}
function updateProgress(c, t) { document.getElementById('progress-fill').style.width = (c/t*100) + '%'; }
function switchTab(e, id) {
document.querySelectorAll('.tab-content').forEach(c => c.classList.add('hidden'));
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.getElementById(id).classList.remove('hidden');
e.currentTarget.classList.add('active');
}
function updateChart() {
if (dailyChart) dailyChart.destroy();
const ctx = document.getElementById('dailyChart').getContext('2d');
const labels = Object.keys(dataStore.daily).sort();
dailyChart = new Chart(ctx, {
type: 'line',
data: { labels, datasets: [{ label: 'פעולות', data: labels.map(l => dataStore.daily[l]), borderColor: '#38bdf8', tension: 0.3, fill: true, backgroundColor: 'rgba(56, 189, 248, 0.1)' }] },
options: { scales: { y: { beginAtZero: true } } }
});
}
window.onload = setDefaultDates;
</script>
</body>
</html>