למה אין נתונים לקווים? 🤔
-
אני זוכר שכתבתי על הנושא הזה כבר לפני כמה חודשים, אבל לא הייתה היענות מספקת
ולכן אני מעלה את זה שוב.
לדעתי, לכל מי שמפעיל קו שמכיל תוכן
– זה דבר חשוב מאוד.הכוונה היא לאפשר גישה לנתונים מלאים ומפורטים
, כמו למשל:אילו קבצים מקבלים את אחוזי ההאזנה הגבוהים ביותר

כמות ההאזנות לכל קובץ
(שאת זה אפשר לבדוק חלקית כבר היום)
אילו שלוחות הכי מואזנות בקו
לאילו שלוחות נכנסים הכי הרבה
בקיצור – מערכת שמרכזת נתונים סטטיסטיים מלאים וברורים על פעילות המשתמשים בקו

אני אישית מאוד הייתי רוצה לפתח משהו כזה
, אבל אין לי מספיק ידע בתחום הפיתוח 
אולי אם כמה אנשים כאן ישתפו פעולה וירימו את זה יחד
– זה יכול להועיל מאוד לכולנו, במיוחד למי שמנהל קווי תוכן 
אם כבר קיים פתרון כזה – אשמח מאוד לשמוע עליו

ובכל מקרה, אשמח לכל תגובה או מחשבה בנושא
לדעתי זה נושא חשוב מאוד!
-
ק קו המוסיקה סימן נושא זה כשאלה
-
@קו-המוסיקה @אa @565906
מצורף קוד HTML בסיסי ניתן לשדרג עוד
אין לי במערכת לוג האזנה אז אני לא יודע איך עובד נראה טוב
יש לו באג שאין לי כוח כרגע לתקן שבציון דקות שהם מעל רף של דקות נתין לבחור כמה דקות הוא מציין בשיחות מתחת לרף כל מעבר בשלוחה שלא התעכבו בה את הרף
עוד לא מצאתי איך לחשב לפי שיחה אולי בהמשך אני אשדרג<!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> body { font-family: Arial, sans-serif; background: #f0f2f5; margin: 0; padding: 20px; color: #333; } .container { max-width: 1200px; margin: auto; background: #fff; padding: 25px; border-radius: 15px; box-shadow: 0 8px 30px rgba(0,0,0,0.1); } h1 { margin-top: 0; color: #1a4388; text-align: center; border-bottom: 2px solid #eee; padding-bottom: 15px; } .setup-panel { background: #f8fafc; padding: 20px; border-radius: 12px; border: 1px solid #e2e8f0; margin-bottom: 20px; display: flex; flex-wrap: wrap; gap: 15px; align-items: flex-end; } .input-group { display: flex; flex-direction: column; gap: 5px; } label { font-weight: bold; font-size: 14px; } input, select { padding: 10px; border: 1px solid #cbd5e1; border-radius: 8px; font-size: 14px; } button { border: none; background: #1a4388; color: #fff; padding: 10px 20px; border-radius: 8px; cursor: pointer; font-weight: bold; transition: background 0.2s; } button:hover { background: #133266; } button.secondary { background: #10b981; } button.secondary:hover { background: #059669; } .summary-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 15px; margin-bottom: 20px; } .card { background: #fff; padding: 15px; border-radius: 10px; border: 1px solid #e2e8f0; box-shadow: 0 2px 4px rgba(0,0,0,0.02); text-align: center; } .card .val { font-size: 22px; font-weight: bold; color: #1a4388; display: block; margin-bottom: 5px; } .card .lbl { font-size: 13px; color: #64748b; font-weight: bold; } .tabs { display: flex; gap: 10px; margin-bottom: 15px; border-bottom: 2px solid #e2e8f0; padding-bottom: 10px; display: none; } .tab-btn { background: #e2e8f0; color: #333; padding: 10px 15px; border-radius: 8px; cursor: pointer; font-weight: bold; border: none; } .tab-btn.active { background: #1a4388; color: #fff; } .tab-content { display: none; } .tab-content.active { display: block; } .grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; } @media (max-width: 768px) { .grid-2 { grid-template-columns: 1fr; } } .table-container { overflow-x: auto; max-height: 400px; border: 1px solid #e2e8f0; border-radius: 10px; margin-bottom: 20px; background: #fff;} table { width: 100%; border-collapse: collapse; text-align: right; } th { position: sticky; top: 0; background: #1a4388; color: white; padding: 10px; font-size: 14px; } td { padding: 10px; border-bottom: 1px solid #f1f5f9; font-size: 14px; } tr:hover { background: #f8fafc; } .error { color: #991b1b; background: #fef2f2; padding: 12px; border-radius: 8px; border: 1px solid #fee2e2; margin: 10px 0; } .success { color: #065f46; background: #ecfdf5; padding: 12px; border-radius: 8px; border: 1px solid #d1fae5; margin: 10px 0; } .warning { color: #854d0e; background: #fefce8; padding: 12px; border-radius: 8px; border: 1px solid #fef08a; margin: 10px 0; } h3 { color: #1a4388; margin-bottom: 10px; border-bottom: 1px solid #e2e8f0; padding-bottom: 5px; } </style> </head> <body> <div class="container"> <h1>ניהול נתוני שיחות מתקדם - ימות המשיח</h1> <div class="setup-panel"> <div class="input-group"> <label>טוקן API:</label> <input type="password" id="token" placeholder="הכנס טוקן..." /> </div> <div class="input-group"> <label>שנה:</label> <input type="number" id="year" value="2026" style="width: 80px;" /> </div> <div class="input-group"> <label>חודש:</label> <select id="month"> <option value="01">01</option><option value="02">02</option> <option value="03">03</option><option value="04" selected>04</option> <option value="05">05</option><option value="06">06</option> <option value="07">07</option><option value="08">08</option> <option value="09">09</option><option value="10">10</option> <option value="11">11</option><option value="12">12</option> </select> </div> <div class="input-group"> <label>רף דקות להגדרת שיחה (מעל/מתחת):</label> <input type="number" id="minLimit" value="15" style="width: 100px;" /> </div> <button onclick="fetchData()">משוך נתונים</button> </div> <div id="statusMessage"></div> <div id="summaryCards" class="summary-cards" style="display:none;"> <div class="card"><span class="val" id="totalCalls">0</span><span class="lbl">סה"כ כניסות למערכת</span></div> <div class="card"><span class="val" id="totalListeners">0</span><span class="lbl">מאזינים יוניקיים</span></div> <div class="card"><span class="val" id="avgListenerTime">0:00</span><span class="lbl">ממוצע למאזין</span></div> <div class="card"><span class="val" id="callsOverLimit">0</span><span class="lbl">שיחות מעל הרף</span></div> <div class="card"><span class="val" id="callsUnderLimit">0</span><span class="lbl">שיחות מתחת לרף</span></div> </div> <div class="tabs" id="tabsMenu"> <button class="tab-btn active" onclick="switchTab('tab-extensions')">נתוני שלוחות</button> <button class="tab-btn" onclick="switchTab('tab-files')">נתוני קבצים והאזנות</button> <button class="tab-btn" onclick="switchTab('tab-daily')">סטטיסטיקה יומית ושעות</button> </div> <div id="tab-extensions" class="tab-content active"> <div class="grid-2"> <div> <h3>השלוחות המובילות בזמן האזנה (טופ 10)</h3> <div class="table-container"> <table> <thead><tr><th>מקום</th><th>שלוחה</th><th>שם</th><th>זמן כולל</th></tr></thead> <tbody id="topTimeExtensions"></tbody> </table> </div> </div> <div> <h3>השלוחות עם הכי הרבה כניסות</h3> <div class="table-container"> <table> <thead><tr><th>מקום</th><th>שלוחה</th><th>שם</th><th>כמות כניסות</th></tr></thead> <tbody id="topCallsExtensions"></tbody> </table> </div> </div> </div> </div> <div id="tab-files" class="tab-content"> <div class="grid-2"> <div> <h3>הקבצים המושמעים ביותר</h3> <div class="table-container"> <table> <thead><tr><th>שלוחה</th><th>קובץ</th><th>כמות השמעות</th><th>זמן השמעה מצטבר</th></tr></thead> <tbody id="topFiles"></tbody> </table> </div> </div> <div> <h3>קבצים עם הכי הרבה עזיבות (האזנה חלקית)</h3> <div class="table-container"> <table> <thead><tr><th>שלוחה</th><th>קובץ</th><th>מספר עזיבות באמצע</th></tr></thead> <tbody id="dropoffFiles"></tbody> </table> </div> </div> </div> </div> <div id="tab-daily" class="tab-content"> <div class="grid-2"> <div> <h3>מאזינים לפי ימים</h3> <div class="table-container"> <table> <thead><tr><th>תאריך</th><th>סה"כ כניסות</th><th>מאזינים יוניקיים</th></tr></thead> <tbody id="dailyStats"></tbody> </table> </div> </div> <div> <h3>השעות הפעילות ביותר (לפי כניסות)</h3> <div class="table-container"> <table> <thead><tr><th>שעה</th><th>כמות כניסות</th></tr></thead> <tbody id="hourlyStats"></tbody> </table> </div> </div> </div> </div> </div> <script> function showMsg(text, type) { const el = document.getElementById("statusMessage"); el.innerHTML = `<div class="${type}">${text}</div>`; } function switchTab(tabId) { document.querySelectorAll('.tab-content').forEach(el => el.classList.remove('active')); document.querySelectorAll('.tab-btn').forEach(el => el.classList.remove('active')); document.getElementById(tabId).classList.add('active'); event.currentTarget.classList.add('active'); } function formatHMS(s) { if (isNaN(s) || s < 0) return "0:00"; const h = Math.floor(s / 3600); const m = Math.floor((s % 3600) / 60); const sec = Math.floor(s % 60); return h > 0 ? `${h}:${String(m).padStart(2,'0')}:${String(sec).padStart(2,'0')}` : `${m}:${String(sec).padStart(2,'0')}`; } async function fetchData() { const token = document.getElementById("token").value; const year = document.getElementById("year").value; const month = document.getElementById("month").value; if (!token) { showMsg("אנא הזן טוקן תקני", "error"); return; } const pathEnterExit = `ivr2:Log/LogFolderEnterExit-${year}-${month}.ymgr`; const pathPlayback = `ivr2:Log/LogPlaybackPlayStop/LogPlaybackPlayStop.${year}-${month}.ymgr`; const urlEnterExit = `https://private.call2all.co.il/ym/api/RenderYMGRFile?token=${token}&wath=${pathEnterExit}&convertType=json¬LoadLang=1`; const urlPlayback = `https://private.call2all.co.il/ym/api/RenderYMGRFile?token=${token}&wath=${pathPlayback}&convertType=json¬LoadLang=1`; showMsg("מתחבר לשרת ימות המשיח, מושך נתונים...", "success"); try { const [resEnterExit, resPlayback] = await Promise.all([ fetch(urlEnterExit).then(r => r.json()).catch(() => ({ responseStatus: "ERROR" })), fetch(urlPlayback).then(r => r.json()).catch(() => ({ responseStatus: "ERROR" })) ]); if (resEnterExit.responseStatus !== "OK" && resPlayback.responseStatus !== "OK") { showMsg("שגיאה מהשרת: לא נמצאו קבצי לוג לחודש זה.", "error"); return; } let warning = ""; if (resEnterExit.responseStatus !== "OK") warning += " לא נמצא לוג כניסות (EnterExit)."; if (resPlayback.responseStatus !== "OK") warning += " לא נמצא לוג השמעות קבצים (PlaybackPlayStop)."; if (warning) showMsg("הערה: " + warning, "warning"); else showMsg("הנתונים נמשכו ועובדו בהצלחה!", "success"); processData(resEnterExit.data || [], resPlayback.data || []); } catch (err) { showMsg("שגיאת תקשורת עם השרת.", "error"); } } function processData(folderData, playbackData) { const limitMinutes = parseFloat(document.getElementById("minLimit").value) || 15; const limitSeconds = limitMinutes * 60; // ----- עיבוד נתוני כניסה/יציאה (FolderEnterExit) ----- const extensions = {}; const daily = {}; const hourly = {}; const phones = {}; let gCalls = 0; let gSeconds = 0; let callsOverLimit = 0; let callsUnderLimit = 0; folderData.forEach(row => { const ext = row["Folder"]; const seconds = parseFloat(row["TimeTotal"]) || 0; const title = row["PathTitle"] || row["ValName"] || ""; const phone = row["Phone"]; const date = row["EnterDate"]; const time = row["EnterTime"]; if (!ext) return; // שלוחות if (!extensions[ext]) extensions[ext] = { ext, title, calls: 0, seconds: 0 }; extensions[ext].calls++; extensions[ext].seconds += seconds; // מאזינים (Phone) if (phone) { if (!phones[phone]) phones[phone] = { count: 0, totalSeconds: 0 }; phones[phone].count++; phones[phone].totalSeconds += seconds; } // חתך זמן (רף) if (seconds >= limitSeconds) callsOverLimit++; else callsUnderLimit++; // נתונים יומיים if (date) { if (!daily[date]) daily[date] = { calls: 0, uniquePhones: new Set() }; daily[date].calls++; if (phone) daily[date].uniquePhones.add(phone); } // שעות פעילות if (time) { const hour = time.split(":")[0]; if (!hourly[hour]) hourly[hour] = 0; hourly[hour]++; } gCalls++; gSeconds += seconds; }); // ----- עיבוד נתוני קבצים (PlaybackPlayStop) ----- const files = {}; playbackData.forEach(row => { const ext = row["Folder"] || ""; const fileName = row["FileName"] || row["ValName"] || ""; const playSeconds = parseFloat(row["TimeTotal"] || row["PlayTotalTime"]) || 0; const fileKey = ext + "/" + fileName; if (!fileName) return; if (!files[fileKey]) files[fileKey] = { ext, fileName, plays: 0, totalSeconds: 0, dropoffs: 0 }; files[fileKey].plays++; files[fileKey].totalSeconds += playSeconds; // חישוב עזיבות אמצע: נניח שעזיבה היא אם הושמע פחות מ-80% מהקובץ או אם סטאטוס/סיבה היא לא רגילה // מכיוון שהמפתחות משתנים, הדרך הפשוטה לזהות "נטישה" מהירה היא שיחה שנותקה פחות מ-15 שניות לסיום (אם יש אורך) או חיתוך. // נשתמש בהגיון בסיסי: אם המאזין שמע פחות מ-10 שניות או פחות מהאורך (במידה וקיים). const fileLength = parseFloat(row["FileLength"]); if (fileLength && playSeconds < fileLength - 5) { files[fileKey].dropoffs++; } else if (!fileLength && playSeconds < 30) { // גיבוי למקרה שאין אורך קובץ מדויק files[fileKey].dropoffs++; } }); renderUI(extensions, daily, hourly, phones, files, gCalls, callsOverLimit, callsUnderLimit); } function renderUI(extensions, daily, hourly, phones, files, gCalls, callsOverLimit, callsUnderLimit) { document.getElementById("summaryCards").style.display = "grid"; document.getElementById("tabsMenu").style.display = "flex"; const uniqueListenersCount = Object.keys(phones).length; let avgTime = 0; if (uniqueListenersCount > 0) { const totalSystemTime = Object.values(phones).reduce((sum, p) => sum + p.totalSeconds, 0); avgTime = totalSystemTime / uniqueListenersCount; } // כרטיסיות למעלה document.getElementById("totalCalls").innerText = gCalls; document.getElementById("totalListeners").innerText = uniqueListenersCount; document.getElementById("avgListenerTime").innerText = formatHMS(avgTime); document.getElementById("callsOverLimit").innerText = callsOverLimit; document.getElementById("callsUnderLimit").innerText = callsUnderLimit; const extArr = Object.values(extensions); const filesArr = Object.values(files); // 1. שלוחות טופ זמן const topTimeExt = [...extArr].sort((a, b) => b.seconds - a.seconds).slice(0, 10); document.getElementById("topTimeExtensions").innerHTML = topTimeExt.map((e, i) => `<tr><td>${i+1}</td><td><strong>${e.ext}</strong></td><td>${e.title}</td><td>${formatHMS(e.seconds)}</td></tr>` ).join(""); // 2. שלוחות טופ כניסות const topCallsExt = [...extArr].sort((a, b) => b.calls - a.calls); document.getElementById("topCallsExtensions").innerHTML = topCallsExt.map((e, i) => `<tr><td>${i+1}</td><td><strong>${e.ext}</strong></td><td>${e.title}</td><td>${e.calls}</td></tr>` ).join(""); // 3. קבצים מושמעים ביותר const topFiles = [...filesArr].sort((a, b) => b.plays - a.plays).slice(0, 30); // מציג עד 30 document.getElementById("topFiles").innerHTML = topFiles.map(f => `<tr><td>${f.ext}</td><td>${f.fileName}</td><td>${f.plays}</td><td>${formatHMS(f.totalSeconds)}</td></tr>` ).join(""); // 4. עזיבות באמצע const dropoffFiles = [...filesArr].sort((a, b) => b.dropoffs - a.dropoffs).filter(f => f.dropoffs > 0).slice(0, 20); document.getElementById("dropoffFiles").innerHTML = dropoffFiles.map(f => `<tr><td>${f.ext}</td><td>${f.fileName}</td><td style="color:red; font-weight:bold;">${f.dropoffs}</td></tr>` ).join(""); // 5. יומי const dailyHtml = Object.keys(daily).sort().map(date => `<tr><td>${date}</td><td>${daily[date].calls}</td><td>${daily[date].uniquePhones.size}</td></tr>` ).join(""); document.getElementById("dailyStats").innerHTML = dailyHtml; // 6. שעות const hourlyHtml = Object.keys(hourly).sort((a, b) => hourly[b] - hourly[a]).map(hour => `<tr><td>${hour}:00</td><td>${hourly[hour]}</td></tr>` ).join(""); document.getElementById("hourlyStats").innerHTML = hourlyHtml; } </script> </body> </html> כמובן שכל אחד יכול לשדרג אם בינה -
@קו-המוסיקה
אני אשמח לפתח כזה דבר אם יש צורך, רק שאצטרך לעזרה באפיון.
תנו לי את מה שנצרך בכזה קובץ. -
כתב בלמה אין נתונים לקווים?
:אילו קבצים מקבלים את אחוזי ההאזנה הגבוהים ביותר
כמות ההאזנות לכל קובץ (שאת זה אפשר לבדוק חלקית כבר היום)
אילו שלוחות הכי מואזנות בקו
לאילו שלוחות נכנסים הכי הרבהזה לדעתי הבסיס,
אם יש לשהו אחר עוד משהו שלא חשבתי על שיכתוב -
@אa יש פה קוד שהכנתי למישהו הוא עובד על שלוחות לא על קבצים זה הרעיון
@קו-המוסיקה אם תפרט יותר מה צריך אני יכול גם לשדרג את הקוד הזה -
@BEN-ZION נראה לי שהסברתי מה שאני צריך,
אממ...
תן לי לחשוב אם יש לי עוד רעיונות לנתונים -
@BEN-ZION @אa
אוקיי הנה עוד כמה דברים
כמה מאזינים נכנסו היום / השבוע / החודש
זמן האזנה ממוצע לכל מאזין
הקבצים הכי מושמעים (Top 10)
איפה אנשים נוטשים (באיזה קטע הם יוצאים)
כמה פעמים כל קובץ הושמע
כמה שיחות נכנסו לעומת כמה נותקו
השעות הכי פעילות
אולי גם משהו שאומר לך איזה קבצים קיבלו הכי הרבה קבצים השבועואם הולכים חזק יותר עם כל הדבר הזה,
אפשר גם לעשות התראות כשיש האזנה נמוכה ביום מסוים בהשוואה ליום אחר,
(זה הייתי יותר ממליץ שזה יעבוד בבינה מלכותית)קיצור זה אם באמת משהו פה רוצה מאוד להשקיע, אבל את הבסיס כבר הבאתי למעלה
-
@קו-המוסיקה
זה הנמסקנות שהגעתי האם יש ההארות ההערות?
דוח סטטיסטיקה למערכות
בכל שלוחה איזה נתונים מקבלים זמני האזנה הכי גבוהים מ1 - 10
אילו שלוחות הכי מואזנות בכל המערכת
לאלו שלוחות נכנסים הכי הרבה בכל המערכת
כמה מאזינים נכנסים כל יום למערכת /לכל שלוחה
זמן האזנה ממוצע לכל מאזין
הקבצים אם הכי הרבה עזיבות באמצע
כמה פעמים כל קובץ הושמע
כמה שיחות נכנסו ושהו מעל 15 דקות וכמה נותקו בפחות מכך(שיהיה ניתן לבחור את הדקות)
מה השעות הכי פעילות להאזנה -
@BEN-ZION כן זה לדעתי הדברים שבאת חשובים
-
@BEN-ZION אשמח מאוד אם תצליח ליצור כזה דבר!
אני בטוח שכולם יודו לך על זה.
זה באמת אחד הדברים שהכי חסרים למהלי מערכות...
חשבתי שגם בכמות מאזינים גדאי לעשות (כמובן אם מתאפשר...) שאם מאזין מתקשר מממספרים שונים, אבל מזוהה לדוג' בקובץ listallinformation.ini שיזוהה כמאזין 1 ולא כשני מאזינים.... -
@קו-המוסיקה @אa @565906
מצורף קוד HTML בסיסי ניתן לשדרג עוד
אין לי במערכת לוג האזנה אז אני לא יודע איך עובד נראה טוב
יש לו באג שאין לי כוח כרגע לתקן שבציון דקות שהם מעל רף של דקות נתין לבחור כמה דקות הוא מציין בשיחות מתחת לרף כל מעבר בשלוחה שלא התעכבו בה את הרף
עוד לא מצאתי איך לחשב לפי שיחה אולי בהמשך אני אשדרג<!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> body { font-family: Arial, sans-serif; background: #f0f2f5; margin: 0; padding: 20px; color: #333; } .container { max-width: 1200px; margin: auto; background: #fff; padding: 25px; border-radius: 15px; box-shadow: 0 8px 30px rgba(0,0,0,0.1); } h1 { margin-top: 0; color: #1a4388; text-align: center; border-bottom: 2px solid #eee; padding-bottom: 15px; } .setup-panel { background: #f8fafc; padding: 20px; border-radius: 12px; border: 1px solid #e2e8f0; margin-bottom: 20px; display: flex; flex-wrap: wrap; gap: 15px; align-items: flex-end; } .input-group { display: flex; flex-direction: column; gap: 5px; } label { font-weight: bold; font-size: 14px; } input, select { padding: 10px; border: 1px solid #cbd5e1; border-radius: 8px; font-size: 14px; } button { border: none; background: #1a4388; color: #fff; padding: 10px 20px; border-radius: 8px; cursor: pointer; font-weight: bold; transition: background 0.2s; } button:hover { background: #133266; } button.secondary { background: #10b981; } button.secondary:hover { background: #059669; } .summary-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 15px; margin-bottom: 20px; } .card { background: #fff; padding: 15px; border-radius: 10px; border: 1px solid #e2e8f0; box-shadow: 0 2px 4px rgba(0,0,0,0.02); text-align: center; } .card .val { font-size: 22px; font-weight: bold; color: #1a4388; display: block; margin-bottom: 5px; } .card .lbl { font-size: 13px; color: #64748b; font-weight: bold; } .tabs { display: flex; gap: 10px; margin-bottom: 15px; border-bottom: 2px solid #e2e8f0; padding-bottom: 10px; display: none; } .tab-btn { background: #e2e8f0; color: #333; padding: 10px 15px; border-radius: 8px; cursor: pointer; font-weight: bold; border: none; } .tab-btn.active { background: #1a4388; color: #fff; } .tab-content { display: none; } .tab-content.active { display: block; } .grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; } @media (max-width: 768px) { .grid-2 { grid-template-columns: 1fr; } } .table-container { overflow-x: auto; max-height: 400px; border: 1px solid #e2e8f0; border-radius: 10px; margin-bottom: 20px; background: #fff;} table { width: 100%; border-collapse: collapse; text-align: right; } th { position: sticky; top: 0; background: #1a4388; color: white; padding: 10px; font-size: 14px; } td { padding: 10px; border-bottom: 1px solid #f1f5f9; font-size: 14px; } tr:hover { background: #f8fafc; } .error { color: #991b1b; background: #fef2f2; padding: 12px; border-radius: 8px; border: 1px solid #fee2e2; margin: 10px 0; } .success { color: #065f46; background: #ecfdf5; padding: 12px; border-radius: 8px; border: 1px solid #d1fae5; margin: 10px 0; } .warning { color: #854d0e; background: #fefce8; padding: 12px; border-radius: 8px; border: 1px solid #fef08a; margin: 10px 0; } h3 { color: #1a4388; margin-bottom: 10px; border-bottom: 1px solid #e2e8f0; padding-bottom: 5px; } </style> </head> <body> <div class="container"> <h1>ניהול נתוני שיחות מתקדם - ימות המשיח</h1> <div class="setup-panel"> <div class="input-group"> <label>טוקן API:</label> <input type="password" id="token" placeholder="הכנס טוקן..." /> </div> <div class="input-group"> <label>שנה:</label> <input type="number" id="year" value="2026" style="width: 80px;" /> </div> <div class="input-group"> <label>חודש:</label> <select id="month"> <option value="01">01</option><option value="02">02</option> <option value="03">03</option><option value="04" selected>04</option> <option value="05">05</option><option value="06">06</option> <option value="07">07</option><option value="08">08</option> <option value="09">09</option><option value="10">10</option> <option value="11">11</option><option value="12">12</option> </select> </div> <div class="input-group"> <label>רף דקות להגדרת שיחה (מעל/מתחת):</label> <input type="number" id="minLimit" value="15" style="width: 100px;" /> </div> <button onclick="fetchData()">משוך נתונים</button> </div> <div id="statusMessage"></div> <div id="summaryCards" class="summary-cards" style="display:none;"> <div class="card"><span class="val" id="totalCalls">0</span><span class="lbl">סה"כ כניסות למערכת</span></div> <div class="card"><span class="val" id="totalListeners">0</span><span class="lbl">מאזינים יוניקיים</span></div> <div class="card"><span class="val" id="avgListenerTime">0:00</span><span class="lbl">ממוצע למאזין</span></div> <div class="card"><span class="val" id="callsOverLimit">0</span><span class="lbl">שיחות מעל הרף</span></div> <div class="card"><span class="val" id="callsUnderLimit">0</span><span class="lbl">שיחות מתחת לרף</span></div> </div> <div class="tabs" id="tabsMenu"> <button class="tab-btn active" onclick="switchTab('tab-extensions')">נתוני שלוחות</button> <button class="tab-btn" onclick="switchTab('tab-files')">נתוני קבצים והאזנות</button> <button class="tab-btn" onclick="switchTab('tab-daily')">סטטיסטיקה יומית ושעות</button> </div> <div id="tab-extensions" class="tab-content active"> <div class="grid-2"> <div> <h3>השלוחות המובילות בזמן האזנה (טופ 10)</h3> <div class="table-container"> <table> <thead><tr><th>מקום</th><th>שלוחה</th><th>שם</th><th>זמן כולל</th></tr></thead> <tbody id="topTimeExtensions"></tbody> </table> </div> </div> <div> <h3>השלוחות עם הכי הרבה כניסות</h3> <div class="table-container"> <table> <thead><tr><th>מקום</th><th>שלוחה</th><th>שם</th><th>כמות כניסות</th></tr></thead> <tbody id="topCallsExtensions"></tbody> </table> </div> </div> </div> </div> <div id="tab-files" class="tab-content"> <div class="grid-2"> <div> <h3>הקבצים המושמעים ביותר</h3> <div class="table-container"> <table> <thead><tr><th>שלוחה</th><th>קובץ</th><th>כמות השמעות</th><th>זמן השמעה מצטבר</th></tr></thead> <tbody id="topFiles"></tbody> </table> </div> </div> <div> <h3>קבצים עם הכי הרבה עזיבות (האזנה חלקית)</h3> <div class="table-container"> <table> <thead><tr><th>שלוחה</th><th>קובץ</th><th>מספר עזיבות באמצע</th></tr></thead> <tbody id="dropoffFiles"></tbody> </table> </div> </div> </div> </div> <div id="tab-daily" class="tab-content"> <div class="grid-2"> <div> <h3>מאזינים לפי ימים</h3> <div class="table-container"> <table> <thead><tr><th>תאריך</th><th>סה"כ כניסות</th><th>מאזינים יוניקיים</th></tr></thead> <tbody id="dailyStats"></tbody> </table> </div> </div> <div> <h3>השעות הפעילות ביותר (לפי כניסות)</h3> <div class="table-container"> <table> <thead><tr><th>שעה</th><th>כמות כניסות</th></tr></thead> <tbody id="hourlyStats"></tbody> </table> </div> </div> </div> </div> </div> <script> function showMsg(text, type) { const el = document.getElementById("statusMessage"); el.innerHTML = `<div class="${type}">${text}</div>`; } function switchTab(tabId) { document.querySelectorAll('.tab-content').forEach(el => el.classList.remove('active')); document.querySelectorAll('.tab-btn').forEach(el => el.classList.remove('active')); document.getElementById(tabId).classList.add('active'); event.currentTarget.classList.add('active'); } function formatHMS(s) { if (isNaN(s) || s < 0) return "0:00"; const h = Math.floor(s / 3600); const m = Math.floor((s % 3600) / 60); const sec = Math.floor(s % 60); return h > 0 ? `${h}:${String(m).padStart(2,'0')}:${String(sec).padStart(2,'0')}` : `${m}:${String(sec).padStart(2,'0')}`; } async function fetchData() { const token = document.getElementById("token").value; const year = document.getElementById("year").value; const month = document.getElementById("month").value; if (!token) { showMsg("אנא הזן טוקן תקני", "error"); return; } const pathEnterExit = `ivr2:Log/LogFolderEnterExit-${year}-${month}.ymgr`; const pathPlayback = `ivr2:Log/LogPlaybackPlayStop/LogPlaybackPlayStop.${year}-${month}.ymgr`; const urlEnterExit = `https://private.call2all.co.il/ym/api/RenderYMGRFile?token=${token}&wath=${pathEnterExit}&convertType=json¬LoadLang=1`; const urlPlayback = `https://private.call2all.co.il/ym/api/RenderYMGRFile?token=${token}&wath=${pathPlayback}&convertType=json¬LoadLang=1`; showMsg("מתחבר לשרת ימות המשיח, מושך נתונים...", "success"); try { const [resEnterExit, resPlayback] = await Promise.all([ fetch(urlEnterExit).then(r => r.json()).catch(() => ({ responseStatus: "ERROR" })), fetch(urlPlayback).then(r => r.json()).catch(() => ({ responseStatus: "ERROR" })) ]); if (resEnterExit.responseStatus !== "OK" && resPlayback.responseStatus !== "OK") { showMsg("שגיאה מהשרת: לא נמצאו קבצי לוג לחודש זה.", "error"); return; } let warning = ""; if (resEnterExit.responseStatus !== "OK") warning += " לא נמצא לוג כניסות (EnterExit)."; if (resPlayback.responseStatus !== "OK") warning += " לא נמצא לוג השמעות קבצים (PlaybackPlayStop)."; if (warning) showMsg("הערה: " + warning, "warning"); else showMsg("הנתונים נמשכו ועובדו בהצלחה!", "success"); processData(resEnterExit.data || [], resPlayback.data || []); } catch (err) { showMsg("שגיאת תקשורת עם השרת.", "error"); } } function processData(folderData, playbackData) { const limitMinutes = parseFloat(document.getElementById("minLimit").value) || 15; const limitSeconds = limitMinutes * 60; // ----- עיבוד נתוני כניסה/יציאה (FolderEnterExit) ----- const extensions = {}; const daily = {}; const hourly = {}; const phones = {}; let gCalls = 0; let gSeconds = 0; let callsOverLimit = 0; let callsUnderLimit = 0; folderData.forEach(row => { const ext = row["Folder"]; const seconds = parseFloat(row["TimeTotal"]) || 0; const title = row["PathTitle"] || row["ValName"] || ""; const phone = row["Phone"]; const date = row["EnterDate"]; const time = row["EnterTime"]; if (!ext) return; // שלוחות if (!extensions[ext]) extensions[ext] = { ext, title, calls: 0, seconds: 0 }; extensions[ext].calls++; extensions[ext].seconds += seconds; // מאזינים (Phone) if (phone) { if (!phones[phone]) phones[phone] = { count: 0, totalSeconds: 0 }; phones[phone].count++; phones[phone].totalSeconds += seconds; } // חתך זמן (רף) if (seconds >= limitSeconds) callsOverLimit++; else callsUnderLimit++; // נתונים יומיים if (date) { if (!daily[date]) daily[date] = { calls: 0, uniquePhones: new Set() }; daily[date].calls++; if (phone) daily[date].uniquePhones.add(phone); } // שעות פעילות if (time) { const hour = time.split(":")[0]; if (!hourly[hour]) hourly[hour] = 0; hourly[hour]++; } gCalls++; gSeconds += seconds; }); // ----- עיבוד נתוני קבצים (PlaybackPlayStop) ----- const files = {}; playbackData.forEach(row => { const ext = row["Folder"] || ""; const fileName = row["FileName"] || row["ValName"] || ""; const playSeconds = parseFloat(row["TimeTotal"] || row["PlayTotalTime"]) || 0; const fileKey = ext + "/" + fileName; if (!fileName) return; if (!files[fileKey]) files[fileKey] = { ext, fileName, plays: 0, totalSeconds: 0, dropoffs: 0 }; files[fileKey].plays++; files[fileKey].totalSeconds += playSeconds; // חישוב עזיבות אמצע: נניח שעזיבה היא אם הושמע פחות מ-80% מהקובץ או אם סטאטוס/סיבה היא לא רגילה // מכיוון שהמפתחות משתנים, הדרך הפשוטה לזהות "נטישה" מהירה היא שיחה שנותקה פחות מ-15 שניות לסיום (אם יש אורך) או חיתוך. // נשתמש בהגיון בסיסי: אם המאזין שמע פחות מ-10 שניות או פחות מהאורך (במידה וקיים). const fileLength = parseFloat(row["FileLength"]); if (fileLength && playSeconds < fileLength - 5) { files[fileKey].dropoffs++; } else if (!fileLength && playSeconds < 30) { // גיבוי למקרה שאין אורך קובץ מדויק files[fileKey].dropoffs++; } }); renderUI(extensions, daily, hourly, phones, files, gCalls, callsOverLimit, callsUnderLimit); } function renderUI(extensions, daily, hourly, phones, files, gCalls, callsOverLimit, callsUnderLimit) { document.getElementById("summaryCards").style.display = "grid"; document.getElementById("tabsMenu").style.display = "flex"; const uniqueListenersCount = Object.keys(phones).length; let avgTime = 0; if (uniqueListenersCount > 0) { const totalSystemTime = Object.values(phones).reduce((sum, p) => sum + p.totalSeconds, 0); avgTime = totalSystemTime / uniqueListenersCount; } // כרטיסיות למעלה document.getElementById("totalCalls").innerText = gCalls; document.getElementById("totalListeners").innerText = uniqueListenersCount; document.getElementById("avgListenerTime").innerText = formatHMS(avgTime); document.getElementById("callsOverLimit").innerText = callsOverLimit; document.getElementById("callsUnderLimit").innerText = callsUnderLimit; const extArr = Object.values(extensions); const filesArr = Object.values(files); // 1. שלוחות טופ זמן const topTimeExt = [...extArr].sort((a, b) => b.seconds - a.seconds).slice(0, 10); document.getElementById("topTimeExtensions").innerHTML = topTimeExt.map((e, i) => `<tr><td>${i+1}</td><td><strong>${e.ext}</strong></td><td>${e.title}</td><td>${formatHMS(e.seconds)}</td></tr>` ).join(""); // 2. שלוחות טופ כניסות const topCallsExt = [...extArr].sort((a, b) => b.calls - a.calls); document.getElementById("topCallsExtensions").innerHTML = topCallsExt.map((e, i) => `<tr><td>${i+1}</td><td><strong>${e.ext}</strong></td><td>${e.title}</td><td>${e.calls}</td></tr>` ).join(""); // 3. קבצים מושמעים ביותר const topFiles = [...filesArr].sort((a, b) => b.plays - a.plays).slice(0, 30); // מציג עד 30 document.getElementById("topFiles").innerHTML = topFiles.map(f => `<tr><td>${f.ext}</td><td>${f.fileName}</td><td>${f.plays}</td><td>${formatHMS(f.totalSeconds)}</td></tr>` ).join(""); // 4. עזיבות באמצע const dropoffFiles = [...filesArr].sort((a, b) => b.dropoffs - a.dropoffs).filter(f => f.dropoffs > 0).slice(0, 20); document.getElementById("dropoffFiles").innerHTML = dropoffFiles.map(f => `<tr><td>${f.ext}</td><td>${f.fileName}</td><td style="color:red; font-weight:bold;">${f.dropoffs}</td></tr>` ).join(""); // 5. יומי const dailyHtml = Object.keys(daily).sort().map(date => `<tr><td>${date}</td><td>${daily[date].calls}</td><td>${daily[date].uniquePhones.size}</td></tr>` ).join(""); document.getElementById("dailyStats").innerHTML = dailyHtml; // 6. שעות const hourlyHtml = Object.keys(hourly).sort((a, b) => hourly[b] - hourly[a]).map(hour => `<tr><td>${hour}:00</td><td>${hourly[hour]}</td></tr>` ).join(""); document.getElementById("hourlyStats").innerHTML = hourlyHtml; } </script> </body> </html> כמובן שכל אחד יכול לשדרג אם בינה -
@BEN-ZION מה אני אמור להכניס פה?

-
@קו-המוסיקה
טוקן -
@אA
סליחה על הבורות אבל מאיפה אני משיג טוקן?
(אני מרגיש שזה שאלה מצחיקה...) -
@קו-המוסיקה אתה יכול לכתוב
מספר מערכת:סיסמהאו ליצור מפתח גישה באתר הניהול>אבטחה>מפתחות גישה -
@קו-המוסיקה
מצחיקה, לא.
אבל אחד כמוך חייב ללמוד את העניין הזה.
באתר הישן>לשונית אבטחה>מפתחות גישה>נפתח החלון>צור מפתח>תכניס שם ואז שמור>תעתיק את מה שמופיע לשורה של הטוקן.
תתכונן כי גם במה שאני עובד עליו צריך טוקן... -
@אA כן אולי באמת הגיע הזמן...
-
@CUBASE
מספר מערכת:סיסמא, כבר לא עובד חלק בכל הפקודות. -
@אA מעניין מה שאתה אומר, לא שמעתי על כך, אילו פקודות?
-
@CUBASE
אני לא יודע להגיד לך בוודאות, אבל כשאני ניסיתי בכמה מהן לא עבד לי. -
@אA מוזר, אני עובד הרבה עם פקודות מהירות מהדפדפן באמצעות מספר וסיסמה ולא נתקלתי בשגיאה..

(ובמיוחד שלא פורסם משהו על כך, רשמית זה עדיין אמור להיות פעיל)