לדלג לתוכן

משתמש:מוטי בוט/נתוני ניטור2.js

מתוך המכלול, האנציקלופדיה היהודית

לתשומת ליבך: לאחר הפרסום, ייתכן שיהיה צורך לנקות את זיכרון המטמון (cache) של הדפדפן כדי להבחין בשינויים.

  • פיירפוקס / ספארי: להחזיק את המקש Shift בעת לחיצה על טעינה מחדש (Reload) או ללחוץ על צירוף המקשים Ctrl-F5 או Ctrl-R (במחשב מק: ⌘-R).
  • גוגל כרום: ללחוץ על צירוף המקשים Ctrl-Shift-R (במחשב מק: ⌘-Shift-R).
  • אדג': להחזיק את המקש Ctrl בעת לחיצה על רענן (Refresh) או ללחוץ על צירוף המקשים Ctrl-F5.
//לקבלת כמות ניטורים פר משתמש בזמן נתון - גרסה מורחבת עם ויזואליזציה וחלונות רחבים
mw.loader.using(["mediawiki.api", "oojs-ui-windows"]).then(() => {

    // פונקציה להצגת הודעות בתוך הדיאלוג באמצעות OOUI
    function showInDialogMessage(message, type = 'success') {
        // יצירת הודעה עם סגנון OOUI
        const messageWidget = new OO.ui.MessageWidget({
            type: type, // 'success', 'error', 'warning', 'notice'
            label: message,
            closable: true
        });

        // הוספת הודעה לראש הדיאלוג
        const dialogContent = document.querySelector('.oo-ui-dialog .oo-ui-window-body');
        if (dialogContent) {
            const messageContainer = messageWidget.$element[0];
            messageContainer.style.cssText = `
                margin: 10px;
                animation: slideDown 0.3s ease;
            `;

            // הוספת אנימציה
            const style = document.createElement('style');
            if (!document.getElementById('message-animation-style')) {
                style.id = 'message-animation-style';
                style.textContent = `
                    @keyframes slideDown {
                        from { opacity: 0; transform: translateY(-20px); }
                        to { opacity: 1; transform: translateY(0); }
                    }
                `;
                document.head.appendChild(style);
            }

            dialogContent.insertBefore(messageContainer, dialogContent.firstChild);

            // הסרה אוטומטית אחרי 4 שניות
            setTimeout(() => {
                if (messageContainer.parentNode) {
                    messageContainer.style.opacity = '0';
                    setTimeout(() => {
                        if (messageContainer.parentNode) {
                            messageContainer.remove();
                        }
                    }, 300);
                }
            }, 4000);
        }

        // fallback לתצוגה בקונסולה
        console.log(`[${type.toUpperCase()}] ${message}`);
    }

    function createProgressDialog() {
        // יצירת דיאלוג תהליך מלא עם יכולת הגדרת רוחב
        class ProgressDialog extends OO.ui.ProcessDialog {
            constructor(config) {
                super(config);
                this.size = 'medium';
                this.message = '';
                this.progress = null;
            }

            static static = {
                name: 'progressDialog',
                title: 'מעבד נתוני ניטור',
                actions: []
            };

            initialize() {
                super.initialize.call(this);
                this.content = new OO.ui.PanelLayout({
                    padded: true,
                    expanded: false
                });
                this.$body.append(this.content.$element);
            }

            getSetupProcess(data) {
                return super.getSetupProcess.call(this, data)
                    .next(() => {
                        this.updateContent(data.message || '', data.progress);
                    });
            }

            updateContent(message, progress = null) {
                this.message = message;
                this.progress = progress;

                const progressBar = progress !== null ?
                    `<div style="background: #eee; border-radius: 4px; margin: 10px 0;">
                        <div style="background: #36c; height: 20px; border-radius: 4px; width: ${progress}%; transition: width 0.3s;"></div>
                    </div>` : '';

                this.content.$element.html(`
                    <div style="text-align: center; padding: 20px; min-width: 400px;">
                        ${message}
                        ${progressBar}
                    </div>
                `);
            }
        }

        const dialog = new ProgressDialog();
        const windowManager = new OO.ui.WindowManager();
        $('body').append(windowManager.$element);
        windowManager.addWindows([dialog]);
        return { dialog, windowManager };
    }

    function updateProgress(windowManager, dialog, message, progress = null) {
        if (dialog.updateContent) {
            dialog.updateContent(message, progress);
        } else {
            // fallback לדיאלוג ישן במקרה של צורך
            const progressBar = progress !== null ?
                `<div style="background: #eee; border-radius: 4px; margin: 10px 0;">
                    <div style="background: #36c; height: 20px; border-radius: 4px; width: ${progress}%; transition: width 0.3s;"></div>
                </div>` : '';

            windowManager.openWindow(dialog, {
                title: 'מעבד נתוני ניטור',
                message: new OO.ui.HtmlSnippet(`${message}${progressBar}`)
            });
        }
    }

    function getTime(num) {
        if (!num) return;
        let numberInput = Number(num);
        if (isNaN(numberInput) || numberInput <= 0 || numberInput > 365) {
            showInDialogMessage('יש להזין מספר ימים תקין (1-365)', 'error');
            return;
        }
        const calendar = new Date(new Date() - numberInput * 1000 * 60 * 60 * 24).toISOString();
        getlogs(calendar, numberInput);
    }

    async function getlogs(lestart, days) {
        const { dialog, windowManager } = createProgressDialog();
        const api = new mw.Api();
        const logs = [];
        let lecontinue = null;
        let totalRequests = 0;

        const params = {
            list: "logevents",
            formatversion: "2",
            leprop: "user|timestamp|title",
            leaction: "patrol/patrol",
            lestart,
            ledir: "newer",
            lenamespace: "0",
            lelimit: "max",
        };

        try {
            windowManager.openWindow(dialog, {
                message: 'מתחיל איסוף נתונים...',
                progress: 0
            });

            while (true) {
                totalRequests++;
                updateProgress(windowManager, dialog,
                    `מבצע בקשה ${totalRequests}... (נאספו ${logs.length} פעולות)`,
                    Math.min((logs.length / 1000) * 100, 95)
                );

                const response = await api.get(params);
                lecontinue = response.continue?.lecontinue;
                logs.push(...response.query.logevents);

                if (lecontinue) {
                    params.lecontinue = lecontinue;
                } else {
                    updateProgress(windowManager, dialog, 'מעבד נתונים...', 100);
                    setTimeout(() => {
                        windowManager.closeWindow(dialog);
                        parserData(logs, days);
                    }, 500);
                    break;
                }
            }
        } catch (error) {
            windowManager.closeWindow(dialog);
            console.error(error);
            showInDialogMessage('אירעה שגיאה באיסוף הנתונים', 'error');
        }
    }

    function parserData(data, days) {
        const users = new Map();
        const dailyStats = new Map();

        data.forEach((log) => {
            const user = log.user.replace(/בוט/g, "").trim();
            const date = log.timestamp.substring(0, 10);

            users.set(user, (users.get(user) || 0) + 1);

            if (!dailyStats.has(date)) {
                dailyStats.set(date, new Set());
            }
            dailyStats.get(date).add(user);
        });

        const sortedUsers = Array.from(users.entries()).sort((a, b) => b[1] - a[1]);
        const topUsers = sortedUsers.slice(0, 20);

        // סטטיסטיקות בסיסיות
        const totalUsers = users.size;
        const totalActions = data.length;
        const avgActionsPerUser = Math.round(totalActions / totalUsers * 100) / 100;
        const avgActionsPerDay = Math.round(totalActions / days * 100) / 100;
        const activeUsersPerDay = Array.from(dailyStats.values()).reduce((sum, users) => sum + users.size, 0) / dailyStats.size;

        createVisualizationWindow(topUsers, {
            totalUsers,
            totalActions,
            avgActionsPerUser,
            avgActionsPerDay,
            activeUsersPerDay: Math.round(activeUsersPerDay * 100) / 100,
            days,
            dailyStats: Array.from(dailyStats.entries()).map(([date, users]) => ({
                date,
                actions: data.filter(log => log.timestamp.startsWith(date)).length,
                activeUsers: users.size
            }))
        });
    }

    function createVisualizationWindow(userData, stats) {
        console.log('נתוני משתמשים:', userData);
        console.log('סטטיסטיקות כלליות:', stats);
        // יצירת HTML לויזואליזציה
        const maxActions = userData[0] ? userData[0][1] : 1;

        const chartHtml = userData.map(([user, actions], index) => {
            const percentage = (actions / maxActions) * 100;
            const color = `hsl(${210 + index * 15}, 70%, ${60 - index * 2}%)`;

            return `
                <div style="margin: 8px 0; padding: 8px; background: #f8f9fa; border-radius: 4px; border-right: 4px solid ${color};">
                    <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 4px;">
                        <strong>${user}</strong>
                        <span style="font-weight: bold; color: ${color};">${actions}</span>
                    </div>
                    <div style="background: #e1e5e9; border-radius: 2px; height: 8px;">
                        <div style="background: ${color}; height: 100%; border-radius: 2px; width: ${percentage}%; transition: width 0.5s ease;"></div>
                    </div>
                </div>
            `;
        }).join('');

        // גרף פעילות יומית פשוט
        const dailyChart = stats.dailyStats.slice(-14).map(day => {
            const maxDaily = Math.max(...stats.dailyStats.map(d => d.actions));
            const height = (day.actions / maxDaily) * 100;
            return `
                <div style="display: inline-block; margin: 2px; text-align: center; vertical-align: bottom;">
                    <div title="${day.actions} פעולות" style="width: 20px; background: #36c; height: ${height}px; max-height: 80px; margin-bottom: 4px; border-radius: 2px 2px 0 0;"></div>
                    <div style="font-size: 10px; writing-mode: vertical-rl; text-orientation: mixed;">${day.date.substring(5)}</div>
                </div>
            `;
        }).join('');

        const windowHtml = `
            <div style="max-height: 80vh; overflow-y: auto; direction: rtl;">
                <!-- סטטיסטיקות כלליות -->
                <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; margin: -20px -20px 20px -20px; border-radius: 8px 8px 0 0;">
                    <h2 style="margin: 0 0 15px 0; text-align: center;">ניתוח נתוני ניטור - ${stats.days} ימים</h2>
                    <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 15px;">
                        <div style="text-align: center; background: rgba(255,255,255,0.1); padding: 10px; border-radius: 6px;">
                            <div style="font-size: 24px; font-weight: bold;">${stats.totalActions.toLocaleString()}</div>
                            <div style="opacity: 0.9;">סך פעולות</div>
                        </div>
                        <div style="text-align: center; background: rgba(255,255,255,0.1); padding: 10px; border-radius: 6px;">
                            <div style="font-size: 24px; font-weight: bold;">${stats.totalUsers}</div>
                            <div style="opacity: 0.9;">משתמשים פעילים</div>
                        </div>
                        <div style="text-align: center; background: rgba(255,255,255,0.1); padding: 10px; border-radius: 6px;">
                            <div style="font-size: 24px; font-weight: bold;">${stats.avgActionsPerUser}</div>
                            <div style="opacity: 0.9;">ממוצע למשתמש</div>
                        </div>
                        <div style="text-align: center; background: rgba(255,255,255,0.1); padding: 10px; border-radius: 6px;">
                            <div style="font-size: 24px; font-weight: bold;">${stats.avgActionsPerDay}</div>
                            <div style="opacity: 0.9;">ממוצע ליום</div>
                        </div>
                    </div>
                </div>
                
                <!-- פעילות יומית -->
                <div style="margin-bottom: 25px;">
                    <h3 style="margin: 0 0 10px 0; color: #333;">פעילות יומית (14 הימים האחרונים)</h3>
                    <div style="background: #f8f9fa; padding: 15px; border-radius: 6px; text-align: center; overflow-x: auto;">
                        ${dailyChart}
                    </div>
                </div>
                
                <!-- טופ 20 משתמשים -->
                <div>
                    <h3 style="margin: 0 0 15px 0; color: #333;">20 המשתמשים המובילים</h3>
                    ${chartHtml}
                </div>
                
                <!-- כפתורי פעולה 
                <div style="margin-top: 20px; text-align: center; padding-top: 15px; border-top: 1px solid #eee;">
                    <button id="consoleBtn" style="background: #36c; color: white; border: none; padding: 8px 16px; border-radius: 4px; margin: 0 5px; cursor: pointer;">
                        הצג בקונסולה
                    </button>
                    <button id="exportBtn" style="background: #28a745; color: white; border: none; padding: 8px 16px; border-radius: 4px; margin: 0 5px; cursor: pointer;">
                        ייצא ל-CSV
                    </button>
                    <button id="copyBtn" style="background: #17a2b8; color: white; border: none; padding: 8px 16px; border-radius: 4px; margin: 0 5px; cursor: pointer;">
                        העתק כתמונה
                    </button>
                </div>-->
            </div>
        `;

        // יצירת דיאלוג תוצאות מלא עם רוחב מותאם
        class ResultDialog extends OO.ui.ProcessDialog {
            constructor(config) {
                super(config);
                this.size = 'larger';  // גודל גדול יותר לתצוגה נוחה
            }

            static static = {
                name: 'resultDialog',
                title: 'תוצאות ניתוח נתוני ניטור',
                actions: [
                    {
                        action: 'close',
                        label: 'סגור',
                        flags: ['safe', 'close']
                    }
                ]
            };

            initialize() {
                super.initialize.call(this);
                this.content = new OO.ui.PanelLayout({
                    padded: true,
                    expanded: false
                });
                this.$body.append(this.content.$element);
                // הגדרת רוחב מותאם אישית
                this.$element.css({
                    'max-width': '95vw',
                    'width': '1200px'
                });
            }

            getSetupProcess(data) {
                return super.getSetupProcess.call(this, data)
                    .next(() => {
                        this.content.$element.html(data.content);
                    });
            }

            getActionProcess(action) {
                if (action === 'close') {
                    return new OO.ui.Process(() => {
                        this.close();
                    });
                }
                return super.getActionProcess.call(this, action);
            }
        }

        const resultDialog = new ResultDialog();
        const windowManager = new OO.ui.WindowManager({
            modal: true
        });

        $('body').append(windowManager.$element);
        windowManager.addWindows([resultDialog]);

        // פתיחת החלון
        windowManager.openWindow(resultDialog, {
            content: windowHtml
        }).then(() => {
            // טיפול בסגירת החלון
            resultDialog.on('close', () => {
                windowManager.destroy();
            });
        });

        // הוספת event listeners לכפתורים
        setTimeout(() => {
            const consoleBtn = document.getElementById('consoleBtn');
            const exportBtn = document.getElementById('exportBtn');
            const copyBtn = document.getElementById('copyBtn');

            if (consoleBtn) {
                consoleBtn.addEventListener('click', () => {
                    console.log('📊 סטטיסטיקות ניטור:');
                    console.log('====================');
                    console.log(`תקופה: ${stats.days} ימים`);
                    console.log(`סך פעולות: ${stats.totalActions.toLocaleString()}`);
                    console.log(`משתמשים פעילים: ${stats.totalUsers}`);
                    console.log(`ממוצע למשתמש: ${stats.avgActionsPerUser}`);
                    console.log(`ממוצע ליום: ${stats.avgActionsPerDay}`);
                    console.log('====================');
                    console.table(Object.fromEntries(userData));
                    console.log('נתונים מלאים:', stats);
                    showInDialogMessage('נתונים הודפסו בקונסולה! פתח את F12 לצפייה', 'notice');
                });
            }

            if (exportBtn) {
                exportBtn.addEventListener('click', () => {
                    try {
                        // יצירת CSV עם BOM לתמיכה בעברית
                        const BOM = '\uFEFF';
                        let csv = BOM + 'משתמש,כמות פעולות\n';
                        userData.forEach(([user, actions]) => {
                            csv += `"${user}",${actions}\n`;
                        });

                        // הוספת סטטיסטיקות נוספות
                        csv += '\n--- סטטיסטיקות כלליות ---\n';
                        csv += `"תקופה (ימים)",${stats.days}\n`;
                        csv += `"סך פעולות",${stats.totalActions}\n`;
                        csv += `"משתמשים פעילים",${stats.totalUsers}\n`;
                        csv += `"ממוצע למשתמש",${stats.avgActionsPerUser}\n`;
                        csv += `"ממוצע ליום",${stats.avgActionsPerDay}\n`;

                        const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' });
                        const url = URL.createObjectURL(blob);
                        const a = document.createElement('a');
                        a.href = url;
                        a.download = `patrol-stats-${stats.days}d-${new Date().toISOString().substring(0, 10)}.csv`;
                        document.body.appendChild(a);
                        a.click();
                        document.body.removeChild(a);
                        URL.revokeObjectURL(url);

                        showInDialogMessage('קובץ CSV נשמר בהצלחה!', 'success');
                    } catch (error) {
                        console.error('שגיאה בייצוא:', error);
                        showInDialogMessage('אירעה שגיאה בייצוא הקובץ', 'error');
                    }
                });
            }

            if (copyBtn) {
                copyBtn.addEventListener('click', async () => {
                    try {
                        showInDialogMessage('מכין תמונה להעתקה...', 'notice');

                        // יצירת SVG מהתוכן
                        const dialogContent = document.querySelector('.oo-ui-dialog .oo-ui-window-body .oo-ui-panelLayout-padded');
                        if (!dialogContent) {
                            throw new Error('לא נמצא תוכן הדיאלוג');
                        }

                        // שמירת HTML כקובץ תמונה באמצעות SVG
                        const rect = dialogContent.getBoundingClientRect();
                        const htmlContent = dialogContent.innerHTML;

                        // יצירת SVG עם HTML מוטמע
                        const svgData = `
                            <svg xmlns="http://www.w3.org/2000/svg" width="${rect.width}" height="${rect.height}">
                                <foreignObject width="100%" height="100%">
                                    <div xmlns="http://www.w3.org/1999/xhtml" style="
                                        font-family: Arial, sans-serif;
                                        background: white;
                                        padding: 20px;
                                        direction: rtl;
                                        width: ${rect.width - 40}px;
                                        height: ${rect.height - 40}px;
                                        overflow: hidden;
                                    ">
                                        ${htmlContent}
                                    </div>
                                </foreignObject>
                            </svg>
                        `;

                        // המרה ל-canvas
                        const canvas = document.createElement('canvas');
                        const ctx = canvas.getContext('2d');
                        canvas.width = rect.width * 2; // רזולוציה גבוהה
                        canvas.height = rect.height * 2;

                        const img = new Image();
                        img.onload = async () => {
                            ctx.scale(2, 2);
                            ctx.fillStyle = 'white';
                            ctx.fillRect(0, 0, rect.width, rect.height);
                            ctx.drawImage(img, 0, 0);

                            // העתקה ללוח
                            try {
                                canvas.toBlob(async (blob) => {
                                    if (navigator.clipboard && navigator.clipboard.write) {
                                        const item = new ClipboardItem({ 'image/png': blob });
                                        await navigator.clipboard.write([item]);
                                        showInDialogMessage('תמונה הועתקה ללוח בהצלחה!', 'success');
                                    } else {
                                        throw new Error('הדפדפן לא תומך בהעתקה ללוח');
                                    }
                                }, 'image/png');
                            } catch (clipboardError) {
                                // fallback - הורדת קובץ
                                canvas.toBlob((blob) => {
                                    const url = URL.createObjectURL(blob);
                                    const a = document.createElement('a');
                                    a.href = url;
                                    a.download = `patrol-stats-${stats.days}d-${new Date().toISOString().substring(0, 10)}.png`;
                                    document.body.appendChild(a);
                                    a.click();
                                    document.body.removeChild(a);
                                    URL.revokeObjectURL(url);
                                    showInDialogMessage('תמונה נשמרה כקובץ', 'warning');
                                }, 'image/png');
                            }
                        };

                        img.onerror = () => {
                            // פתרון חלופי - יצירת תמונה פשוטה עם הנתונים
                            ctx.fillStyle = 'white';
                            ctx.fillRect(0, 0, rect.width, rect.height);

                            // כותרת
                            ctx.fillStyle = '#333';
                            ctx.font = 'bold 24px Arial';
                            ctx.textAlign = 'center';
                            ctx.fillText(`ניתוח נתוני ניטור - ${stats.days} ימים`, rect.width / 2, 40);

                            // סטטיסטיקות
                            ctx.font = '16px Arial';
                            ctx.textAlign = 'right';
                            let y = 80;
                            ctx.fillText(`סך פעולות: ${stats.totalActions.toLocaleString()}`, rect.width - 20, y += 30);
                            ctx.fillText(`משתמשים פעילים: ${stats.totalUsers}`, rect.width - 20, y += 25);
                            ctx.fillText(`ממוצע למשתמש: ${stats.avgActionsPerUser}`, rect.width - 20, y += 25);
                            ctx.fillText(`ממוצע ליום: ${stats.avgActionsPerDay}`, rect.width - 20, y += 25);

                            // רשימת משתמשים מובילים
                            y += 40;
                            ctx.fillText('משתמשים מובילים:', rect.width - 20, y);
                            userData.slice(0, 10).forEach(([user, actions], index) => {
                                ctx.fillText(`${index + 1}. ${user}: ${actions}`, rect.width - 20, y += 25);
                            });

                            // העתקה
                            canvas.toBlob((blob) => {
                                const url = URL.createObjectURL(blob);
                                const a = document.createElement('a');
                                a.href = url;
                                a.download = `patrol-stats-${stats.days}d-${new Date().toISOString().substring(0, 10)}.png`;
                                document.body.appendChild(a);
                                a.click();
                                document.body.removeChild(a);
                                URL.revokeObjectURL(url);
                                showInDialogMessage('תמונה נשמרה כקובץ', 'success');
                            }, 'image/png');
                        };

                        const svgBlob = new Blob([svgData], { type: 'image/svg+xml' });
                        img.src = URL.createObjectURL(svgBlob);

                    } catch (error) {
                        console.error('שגיאה ביצירת תמונה:', error);
                        showInDialogMessage('אירעה שגיאה ביצירת התמונה', 'error');
                    }
                });
            }
        }, 500);

        // הוספת אנימציה לגרפים
        setTimeout(() => {
            const bars = document.querySelectorAll('[style*="transition: width"]');
            bars.forEach((bar, index) => {
                setTimeout(() => {
                    bar.style.width = bar.style.width;
                }, index * 50);
            });
        }, 100);
    }
	$(mw.util.addPortletLink("p-tb", "#", "הצג נתוני ניטור", "statusPatrol")).click(() => {
    	// הפעלת הסקריפט
    	OO.ui.prompt("הזן מספר ימים לניתוח:", { textInput: { value: "7" } })
        	.then((result) => {
            	if (result !== null) {
                	getTime(result);
            	}
        	});
		
	});
});