• דף הבית
    • אינדקס קישורים
    • פוסטים אחרונים
    • משתמשים
    • חיפוש בהגדרות המתקדמות
    • חיפוש גוגל בפורום
    • ניהול המערכת
    • ניהול המערכת - שרת private
    • הרשמה
    • התחברות

    פאנל ניהול מותאם אישית

    מתוזמן נעוץ נעול הועבר הסברים מסודרים ממשתמשים
    1 פוסטים 1 כותבים 82 צפיות 1 עוקבים
    טוען פוסטים נוספים
    • מהישן לחדש
    • מהחדש לישן
    • הכי הרבה הצבעות
    תגובה
    • תגובה כנושא
    התחברו כדי לפרסם תגובה
    נושא זה נמחק. רק משתמשים עם הרשאות מתאימות יוכלו לצפות בו.
    • B מנותק
      BEN ZION
      נערך לאחרונה על ידי BEN ZION

      יצרתי קוד אם בינה לניהול של שלוחה 18 מלאה
      אם אתה לא רוצה לתת סיסמה לכל מי שצריך גישה לקו שלך דוגמא בת"ת שכל מלמד יוכל לנהל את השלוחה שלו
      קוד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>
          <link href="https://fonts.googleapis.com/css2?family=Heebo:wght@300;400;500;600;700&display=swap" rel="stylesheet">
          <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0" />
          <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
      
          <style>
              :root {
                  --primary: #2563eb; --primary-hover: #1d4ed8; --bg: #f1f5f9; --surface: #ffffff;
                  --text: #1e293b; --text-light: #64748b; --border: #e2e8f0; --danger: #ef4444; --success: #10b981;
                  --sidebar-width: 320px; /* הורחב מעט עבור הכפתורים בעץ */
              }
      
              body { font-family: 'Heebo', sans-serif; margin: 0; padding: 0; background-color: var(--bg); color: var(--text); height: 100vh; display: flex; flex-direction: column; overflow: hidden; }
              
              /* Login Screen */
              #login-screen { position: fixed; inset: 0; background: var(--bg); display: flex; justify-content: center; align-items: center; z-index: 9999; }
              .login-box { background: white; padding: 40px; border-radius: 12px; box-shadow: 0 10px 25px rgba(0,0,0,0.1); width: 100%; max-width: 400px; }
              .login-box h2 { text-align: center; margin-top: 0; }
              .form-group { margin-bottom: 20px; }
              .form-group label { display: block; font-weight: 600; margin-bottom: 8px; }
              .form-group input { width: 100%; padding: 12px; border: 1px solid var(--border); border-radius: 8px; box-sizing: border-box; font-family: inherit; direction: ltr; text-align: right; }
              .btn-full { width: 100%; }
      
              /* Buttons */
              button { display: inline-flex; align-items: center; justify-content: center; gap: 8px; font-family: 'Heebo', sans-serif; font-size: 14px; font-weight: 500; cursor: pointer; border: none; border-radius: 6px; padding: 8px 16px; transition: 0.2s; }
              .btn-primary { background: var(--primary); color: white; }
              .btn-primary:hover { background: var(--primary-hover); }
              .btn-danger { background: var(--danger); color: white; }
              .btn-success { background: var(--success); color: white; }
              .btn-secondary { background: var(--text-light); color: white; }
              .btn-icon { padding: 6px; font-size: 12px; }
      
              /* Main Layout */
              #app { display: none; height: 100%; flex-direction: column; }
              .header { background: var(--surface); padding: 15px 30px; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--border); z-index: 10; }
              .header h1 { margin: 0; font-size: 22px; }
              .main-content { display: flex; flex: 1; overflow: hidden; }
      
              /* Sidebar (Tree) */
              .sidebar { width: var(--sidebar-width); background: var(--surface); border-left: 1px solid var(--border); padding: 20px; overflow-y: auto; display: flex; flex-direction: column; }
              .tree-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; border-bottom: 2px solid var(--primary); padding-bottom: 10px; }
              .tree-list { list-style: none; padding: 0; margin: 0; }
              .tree-item { margin-bottom: 5px; }
              .tree-row { display: flex; align-items: center; gap: 8px; padding: 8px; border-radius: 6px; cursor: pointer; transition: background 0.2s; position: relative; }
              .tree-row:hover { background: var(--bg); }
              .tree-row.active { background: #e0e7ff; color: var(--primary); font-weight: 600; }
              
              /* סגנונות חדשים עבור כפתורי הפעולות בתיקיות */
              .tree-row-title { flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: right; }
              .tree-actions { display: none; gap: 4px; }
              .tree-row:hover .tree-actions, .tree-row.active .tree-actions { display: flex; }
              .btn-tree-action { background: transparent; padding: 4px; border-radius: 4px; color: var(--text-light); transition: 0.2s; }
              .btn-tree-action:hover { background: #cbd5e1; color: var(--text); }
              .btn-tree-action.delete:hover { background: #fecaca; color: var(--danger); }
      
              .tree-children { padding-right: 20px; display: none; border-right: 1px dashed var(--border); margin-top: 5px; }
              .tree-children.open { display: block; }
      
              /* Main Area (Tabs) */
              .content-area { flex: 1; padding: 20px 30px; overflow-y: auto; display: flex; flex-direction: column; }
              .path-display { background: var(--surface); padding: 10px 15px; border-radius: 8px; margin-bottom: 20px; border: 1px solid var(--border); direction: ltr; text-align: right; font-family: monospace; font-size: 16px;}
              
              .tabs { display: flex; gap: 10px; border-bottom: 1px solid var(--border); margin-bottom: 20px; }
              .tab { background: none; border: none; padding: 12px 24px; font-size: 16px; font-weight: 600; color: var(--text-light); cursor: pointer; border-bottom: 3px solid transparent; border-radius: 0; }
              .tab.active { color: var(--primary); border-bottom-color: var(--primary); }
              
              .tab-content { display: none; background: var(--surface); border-radius: 8px; border: 1px solid var(--border); padding: 20px; flex: 1; }
              .tab-content.active { display: block; }
      
              /* Tables & Lists */
              .toolbar { display: flex; gap: 10px; margin-bottom: 15px; background: var(--bg); padding: 10px; border-radius: 8px; }
              table { width: 100%; border-collapse: collapse; text-align: right; }
              th, td { padding: 12px; border-bottom: 1px solid var(--border); }
              th { background: var(--bg); font-weight: 600; color: var(--text-light); }
              tr:hover { background: #f8fafc; }
              .actions-cell { display: flex; gap: 5px; justify-content: flex-end; }
              
              /* Editor */
              .editor { width: 100%; height: 400px; font-family: monospace; font-size: 16px; direction: ltr; padding: 15px; border: 1px solid var(--border); border-radius: 8px; background: #1e1e1e; color: #d4d4d4; resize: vertical; box-sizing: border-box; }
      
              /* Modals & Overlays */
              .modal { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.6); z-index: 1000; justify-content: center; align-items: center; }
              .modal-content { background: white; padding: 25px; border-radius: 12px; width: 500px; max-width: 90%; }
              .modal-content.large { width: 900px; height: 80vh; display: flex; flex-direction: column; }
              .modal-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px; }
              iframe.viewer { width: 100%; flex: 1; border: 1px solid var(--border); border-radius: 8px; background: white;}
      
              /* Floating Audio Player */
              #audio-player-container { position: fixed; bottom: -100px; left: 50%; transform: translateX(-50%); background: white; padding: 15px 25px; border-radius: 30px 30px 0 0; box-shadow: 0 -5px 20px rgba(0,0,0,0.1); transition: 0.3s; z-index: 100; display: flex; align-items: center; gap: 15px; }
              #audio-player-container.show { bottom: 0; }
      
              /* Toasts */
              #toasts { position: fixed; bottom: 20px; right: 20px; z-index: 10000; display: flex; flex-direction: column; gap: 10px; }
              .toast { background: var(--text); color: white; padding: 12px 24px; border-radius: 8px; font-size: 14px; opacity: 0; transform: translateY(20px); transition: 0.3s; }
              .toast.show { opacity: 1; transform: translateY(0); }
              .toast.error { background: var(--danger); }
              
              .hidden { display: none !important; }
          </style>
      </head>
      <body>
      
          <div id="toasts"></div>
      
          <!-- מסך התחברות -->
          <div id="login-screen">
              <div class="login-box">
                  <h2>התחברות למערכת ניהול</h2>
                  <div class="form-group">
                      <label>מפתח API (Token)</label>
                      <input type="password" id="input-token" placeholder="הזן טוקן...">
                  </div>
                  <div class="form-group">
                      <label>הגבלת שלוחת בסיס (לדוגמה: 8)</label>
                      <input type="text" id="input-ext" placeholder="השאר ריק עבור התיקייה הראשית">
                  </div>
                  <button class="btn-primary btn-full" onclick="login()">התחבר למערכת</button>
              </div>
          </div>
      
          <!-- המערכת המרכזית -->
          <div id="app">
              <header class="header">
                  <h1>ניהול קבצים ושלוחות</h1>
                  <button class="btn-secondary" onclick="logout()"><span class="material-symbols-outlined">logout</span> התנתק</button>
              </header>
      
              <div class="main-content">
                  <!-- צד ימין: עץ התיקיות -->
                  <aside class="sidebar">
                      <div class="tree-header">
                          <span style="font-weight: bold; font-size: 18px;">עץ שלוחות</span>
                          <button class="btn-primary btn-icon" onclick="openNewFolderModal()" title="שלוחה חדשה כאן"><span class="material-symbols-outlined">create_new_folder</span></button>
                      </div>
                      <ul class="tree-list" id="tree-root"></ul>
                  </aside>
      
                  <!-- צד שמאל: ניהול ותוכן -->
                  <main class="content-area">
                      <div class="path-display" id="current-path-display">ivr2:/</div>
                      
                      <div class="tabs">
                          <button class="tab active" onclick="switchTab('tab-files')">ניהול קבצים ודוחות</button>
                          <button class="tab" onclick="switchTab('tab-messages')">הודעות מערכת (M)</button>
                          <button class="tab" onclick="switchTab('tab-settings')">הגדרות שלוחה (ext.ini)</button>
                      </div>
      
                      <!-- כרטיסייה 1: קבצים -->
                      <div id="tab-files" class="tab-content active">
                          <div class="toolbar">
                              <button class="btn-success" onclick="document.getElementById('file-upload').click()"><span class="material-symbols-outlined">upload</span> העלה קובץ</button>
                              <input type="file" id="file-upload" class="hidden" onchange="uploadFile(this)">
                              <input type="file" id="file-replace" class="hidden" onchange="uploadFile(this, true)">
                              
                              <button class="btn-primary" onclick="downloadSelectedZip()"><span class="material-symbols-outlined">folder_zip</span> הורד מסומנים (ZIP)</button>
                              <button class="btn-danger" onclick="deleteSelectedFiles()"><span class="material-symbols-outlined">delete</span> מחק מסומנים</button>
                          </div>
                          <div style="overflow-x: auto;">
                              <table>
                                  <thead>
                                      <tr>
                                          <th style="width: 40px;"><input type="checkbox" onchange="toggleSelectAll(this, 'file-chk')"></th>
                                          <th>שם קובץ</th>
                                          <th>סוג</th>
                                          <th>גודל</th>
                                          <th>שונה לאחרונה</th>
                                          <th>פעולות</th>
                                      </tr>
                                  </thead>
                                  <tbody id="files-tbody"></tbody>
                              </table>
                          </div>
                      </div>
      
                      <!-- כרטיסייה 2: הודעות מערכת -->
                      <div id="tab-messages" class="tab-content">
                          <div style="overflow-x: auto;">
                              <table>
                                  <thead>
                                      <tr>
                                          <th>שם הודעה</th>
                                          <th>תיאור (מערכת)</th>
                                          <th>אורך / גודל</th>
                                          <th>פעולות</th>
                                      </tr>
                                  </thead>
                                  <tbody id="messages-tbody"></tbody>
                              </table>
                          </div>
                      </div>
      
                      <!-- כרטיסייה 3: עריכת הגדרות -->
                      <div id="tab-settings" class="tab-content">
                          <h3 style="margin-top:0;">עריכת ext.ini בשלוחה הנוכחית</h3>
                          <textarea id="ext-ini-editor" class="editor"></textarea>
                          <div style="margin-top: 15px; text-align: left;">
                              <button class="btn-primary" onclick="saveExtIni()"><span class="material-symbols-outlined">save</span> שמור הגדרות בשרת</button>
                          </div>
                      </div>
                  </main>
              </div>
          </div>
      
          <!-- פלייר האזנה -->
          <div id="audio-player-container">
              <span class="material-symbols-outlined" style="cursor:pointer;" onclick="closeAudioPlayer()">close</span>
              <span id="audio-title" style="font-weight: bold;"></span>
              <audio id="main-audio" controls autoplay></audio>
          </div>
      
          <!-- מודל צפייה בדוחות (YMGR/HTML) -->
          <div id="viewer-modal" class="modal">
              <div class="modal-content large">
                  <h3 id="viewer-title" style="margin-top:0;">צפייה בדוח</h3>
                  <iframe id="viewer-iframe" class="viewer"></iframe>
                  <div class="modal-actions">
                      <button class="btn-secondary" onclick="closeModal('viewer-modal')">סגור</button>
                  </div>
              </div>
          </div>
      
          <!-- מודל פעולות (העתקה/העברה) -->
          <div id="action-modal" class="modal">
              <div class="modal-content">
                  <h3 id="action-title">פעולה</h3>
                  <div class="form-group">
                      <label>נתיב יעד (לדוגמה: ivr2:1/2):</label>
                      <input type="text" id="action-target">
                  </div>
                  <input type="hidden" id="action-what">
                  <input type="hidden" id="action-type">
                  <div class="modal-actions">
                      <button class="btn-secondary" onclick="closeModal('action-modal')">ביטול</button>
                      <button class="btn-primary" onclick="executeFileAction()">בצע</button>
                  </div>
              </div>
          </div>
      
          <!-- מודל יצירת שלוחה - מעודכן עם הגדרות -->
          <div id="new-folder-modal" class="modal">
              <div class="modal-content">
                  <h3>יצירת שלוחה חדשה</h3>
                  <p>תיווצר תחת: <strong id="new-folder-path-display" dir="ltr"></strong></p>
                  <div class="form-group">
                      <label>שם / מספר השלוחה:</label>
                      <input type="text" id="new-folder-name" placeholder="למשל: 5">
                  </div>
                  <div class="form-group">
                      <label>הגדרות השלוחה (ext.ini):</label>
                      <textarea id="new-folder-settings" class="editor" style="height: 120px; direction: ltr;">type=menu
      title=שלוחה חדשה</textarea>
                  </div>
                  <div class="modal-actions">
                      <button class="btn-secondary" onclick="closeModal('new-folder-modal')">ביטול</button>
                      <button class="btn-primary" onclick="createFolder()"><span class="material-symbols-outlined">create_new_folder</span> צור שלוחה</button>
                  </div>
              </div>
          </div>
      
          <!-- מודל עריכת קובץ טקסט כלשהו -->
          <div id="text-editor-modal" class="modal">
              <div class="modal-content large">
                  <h3 id="text-editor-title" style="margin-top:0;">עריכת קובץ</h3>
                  <textarea id="text-editor-area" class="editor"></textarea>
                  <input type="hidden" id="text-editor-path">
                  <div class="modal-actions">
                      <button class="btn-secondary" onclick="closeModal('text-editor-modal')">ביטול</button>
                      <button class="btn-primary" onclick="saveTextFile()"><span class="material-symbols-outlined">save</span> שמור קובץ</button>
                  </div>
              </div>
          </div>
      
          <script>
              const API_URL = 'https://private.call2all.co.il/ym/api/';
              let TOKEN = '';
              let BASE_PATH = 'ivr2:/';
              let CURRENT_PATH = '';
              let replaceTargetFile = '';
      
              // פונקציה לייצור מזהים בטוחים במקום btoa כדי למנוע קריסה בשמות עבריים
              function safeId(str) {
                  let hash = 0;
                  for (let i = 0; i < str.length; i++) {
                      hash = ((hash << 5) - hash) + str.charCodeAt(i);
                      hash = hash & hash;
                  }
                  return 'id_' + Math.abs(hash) + '_' + str.length;
              }
      
              function getParentPath(path) {
                  if (path === 'ivr2:/' || path === BASE_PATH) return path;
                  let parts = path.split('/');
                  parts.pop();
                  return parts.join('/') || 'ivr2:/';
              }
      
              // --- איתחול ---
              document.addEventListener('DOMContentLoaded', () => {
                  if(localStorage.getItem('ym_token')) {
                      document.getElementById('input-token').value = localStorage.getItem('ym_token');
                      document.getElementById('input-ext').value = localStorage.getItem('ym_base_ext') || '';
                      login();
                  }
              });
      
              // --- פונקציות ליבה ל-API ---
              async function apiRequest(endpoint, params = {}, returnBlob = false) {
                  const formData = new FormData();
                  formData.append('token', TOKEN);
                  for(let key in params) { formData.append(key, params[key]); }
                  
                  try {
                      const res = await fetch(API_URL + endpoint, { method: 'POST', body: formData });
                      if(!res.ok) throw new Error('Network error');
                      if(returnBlob) return await res.blob();
                      const data = await res.json();
                      if(data.responseStatus === 'ERROR' || data.responseStatus === 'EXCEPTION') {
                          throw new Error(data.message || data.exceptionMessage);
                      }
                      return data;
                  } catch (err) {
                      showToast(err.message, true);
                      throw err;
                  }
              }
      
              function normalizePath(p) {
                  p = p.trim();
                  if (!p || p === '/' || p === 'ivr2:') return 'ivr2:/';
                  if (!p.startsWith('ivr2:')) p = p.startsWith('/') ? 'ivr2:' + p : 'ivr2:/' + p;
                  if (p.endsWith('/') && p.length > 6) p = p.slice(0, -1);
                  return p;
              }
      
              function showToast(msg, isError = false) {
                  const container = document.getElementById('toasts');
                  const t = document.createElement('div');
                  t.className = `toast ${isError ? 'error' : ''}`;
                  t.innerText = msg;
                  container.appendChild(t);
                  setTimeout(() => t.classList.add('show'), 10);
                  setTimeout(() => { t.classList.remove('show'); setTimeout(() => t.remove(), 300); }, 4000);
              }
      
              function formatBytes(bytes) {
                  if (!bytes || bytes === 0) return '0 B';
                  const k = 1024, sizes =['B', 'KB', 'MB', 'GB'];
                  const i = Math.floor(Math.log(bytes) / Math.log(k));
                  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
              }
      
              // --- ניווט ועץ ---
              async function login() {
                  const token = document.getElementById('input-token').value.trim();
                  const ext = document.getElementById('input-ext').value.trim();
                  if(!token) return alert('נא להזין טוקן');
      
                  TOKEN = token;
                  BASE_PATH = ext ? normalizePath(ext) : 'ivr2:/';
                  
                  try {
                      await apiRequest('GetIVR2Dir', { path: BASE_PATH });
                      localStorage.setItem('ym_token', TOKEN);
                      localStorage.setItem('ym_base_ext', ext);
                      document.getElementById('login-screen').style.display = 'none';
                      document.getElementById('app').style.display = 'flex';
                      
                      await buildRootTree();
                      loadFolder(BASE_PATH);
                  } catch(e) {}
              }
      
              function logout() {
                  localStorage.clear();
                  location.reload();
              }
      
              async function buildRootTree() {
                  const rootUl = document.getElementById('tree-root');
                  rootUl.innerHTML = '';
                  
                  const rootId = safeId(BASE_PATH);
                  const li = document.createElement('li');
                  li.className = 'tree-item';
                  li.innerHTML = `
                      <div class="tree-row active" id="tree-row-${rootId}" onclick="toggleTree('${BASE_PATH}', this)">
                          <span class="material-symbols-outlined">folder_open</span>
                          <span class="tree-row-title">${BASE_PATH.replace('ivr2:/', 'ראשי ')}</span>
                      </div>
                      <ul class="tree-children open" id="tree-children-${rootId}"></ul>
                  `;
                  rootUl.appendChild(li);
                  
                  await fetchAndAppendDirs(BASE_PATH, document.getElementById(`tree-children-${rootId}`));
              }
      
              async function toggleTree(path, rowElement) {
                  document.querySelectorAll('.tree-row').forEach(el => el.classList.remove('active'));
                  rowElement.classList.add('active');
                  
                  const childrenUl = document.getElementById(`tree-children-${safeId(path)}`);
                  if(childrenUl) {
                      if(childrenUl.classList.contains('open')) {
                          childrenUl.classList.remove('open');
                      } else {
                          childrenUl.classList.add('open');
                          if(childrenUl.innerHTML === '') await fetchAndAppendDirs(path, childrenUl);
                      }
                  }
                  loadFolder(path);
              }
      
              async function fetchAndAppendDirs(path, containerUl) {
                  containerUl.innerHTML = '<li><span style="font-size:12px; color:gray; padding-right:15px;">טוען...</span></li>';
                  try {
                      const data = await apiRequest('GetIVR2Dir', { path: path });
                      containerUl.innerHTML = '';
                      if(!data.dirs || data.dirs.length === 0) return;
                      
                      data.dirs.forEach(dir => {
                          const li = document.createElement('li');
                          li.className = 'tree-item';
                          const nodeSafeId = safeId(dir.what);
                          
                          // כפתורי ניהול ישירות על התיקייה בעץ
                          li.innerHTML = `
                              <div class="tree-row" id="tree-row-${nodeSafeId}" onclick="toggleTree('${dir.what}', this)">
                                  <span class="material-symbols-outlined">folder</span>
                                  <span class="tree-row-title">${dir.name} <small style="color:gray;">(${dir.extTitle||''})</small></span>
                                  <div class="tree-actions">
                                      <button class="btn-tree-action" onclick="event.stopPropagation(); openActionModal('copy', '${dir.what}')" title="העתק שלוחה"><span class="material-symbols-outlined" style="font-size:16px; pointer-events:none;">content_copy</span></button>
                                      <button class="btn-tree-action" onclick="event.stopPropagation(); openActionModal('move', '${dir.what}')" title="העבר שלוחה"><span class="material-symbols-outlined" style="font-size:16px; pointer-events:none;">drive_file_move</span></button>
                                      <button class="btn-tree-action delete" onclick="event.stopPropagation(); executeDelete('${dir.what}')" title="מחק שלוחה"><span class="material-symbols-outlined" style="font-size:16px; pointer-events:none;">delete</span></button>
                                  </div>
                              </div>
                              <ul class="tree-children" id="tree-children-${nodeSafeId}"></ul>
                          `;
                          containerUl.appendChild(li);
                      });
                  } catch(e) { containerUl.innerHTML = ''; }
              }
      
              // --- מערכת רענון חכמה ---
              async function refreshUI(affectedPath = null) {
                  // רענון טבלת הקבצים
                  await loadFolder(CURRENT_PATH);
                  
                  // רענון השלוחה הנוכחית בעץ
                  const currentUl = document.getElementById(`tree-children-${safeId(CURRENT_PATH)}`);
                  if (currentUl && currentUl.classList.contains('open')) {
                      await fetchAndAppendDirs(CURRENT_PATH, currentUl);
                  }
                  
                  // רענון תיקיית האב של המיקום הנוכחי בעץ
                  const parentPath = getParentPath(CURRENT_PATH);
                  if (parentPath !== CURRENT_PATH) {
                      const parentUl = document.getElementById(`tree-children-${safeId(parentPath)}`);
                      if (parentUl && parentUl.classList.contains('open')) {
                          await fetchAndAppendDirs(parentPath, parentUl);
                      }
                  }
      
                  // אם הועבר נתיב ספציפי שהושפע (למשל מחיקת תיקייה או העברה לתיקייה אחרת), נרענן גם את האבא שלו
                  if (affectedPath) {
                      const affectedParent = getParentPath(affectedPath);
                      const affParentUl = document.getElementById(`tree-children-${safeId(affectedParent)}`);
                      if (affParentUl && affParentUl.classList.contains('open')) {
                          await fetchAndAppendDirs(affectedParent, affParentUl);
                      }
                  }
              }
      
              // --- טעינת התיקייה המרכזית ---
              async function loadFolder(path) {
                  CURRENT_PATH = path;
                  document.getElementById('current-path-display').innerText = path;
                  
                  document.getElementById('files-tbody').innerHTML = '<tr><td colspan="6" style="text-align:center;">טוען...</td></tr>';
                  document.getElementById('messages-tbody').innerHTML = '<tr><td colspan="4" style="text-align:center;">טוען...</td></tr>';
                  
                  try {
                      const data = await apiRequest('GetIVR2Dir', { path: path });
                      
                      const allFiles = [...(data.files||[]), ...(data.html||[]), ...(data.ini||[])];
                      renderFiles(allFiles);
                      renderMessages(data.messages||[], data.msgDescriptions||{});
                      loadExtIni();
      
                  } catch(e) {}
              }
      
              function renderFiles(files) {
                  const tbody = document.getElementById('files-tbody');
                  tbody.innerHTML = '';
                  if(files.length === 0) { tbody.innerHTML = '<tr><td colspan="6" style="text-align:center;">התיקייה ריקה</td></tr>'; return; }
                  
                  files.forEach(f => {
                      let isEditable = f.fileType === 'INI' || f.name.endsWith('.txt');
                      let isViewable = f.name.endsWith('.ymgr') || f.name.endsWith('.html') || f.name.endsWith('.csv');
                      let isAudio = f.fileType === 'AUDIO' || f.name.endsWith('.wav') || f.name.endsWith('.mp3');
                      let isYmgr = f.name.endsWith('.ymgr');
      
                      let actions = '';
                      
                      // כפתורים ייעודיים לפי סוג
                      if(isAudio) actions += `<button class="btn-primary btn-icon" onclick="playAudio('${f.what}', '${f.name}')" title="האזן"><span class="material-symbols-outlined">play_arrow</span></button>`;
                      if(isViewable) actions += `<button class="btn-success btn-icon" onclick="viewReport('${f.what}', '${f.name}')" title="צפה בדוח"><span class="material-symbols-outlined">visibility</span></button>`;
                      if(isEditable) actions += `<button class="btn-primary btn-icon" onclick="openTextEditor('${f.what}', '${f.name}')" title="ערוך"><span class="material-symbols-outlined">edit</span></button>`;
                      
                      // אפשרויות הורדה לדוחות YMGR
                      if(isYmgr) {
                          actions += `<button class="btn-secondary btn-icon" style="font-weight:bold; font-size:12px; padding:6px 10px;" onclick="downloadYmgr('${f.what}', '${f.name}', 'csv')" title="הורד כ-CSV">CSV <span class="material-symbols-outlined" style="font-size:14px; margin-right:4px;">download</span></button>`;
                          actions += `<button class="btn-secondary btn-icon" style="font-weight:bold; font-size:12px; padding:6px 10px;" onclick="downloadYmgr('${f.what}', '${f.name}', 'html')" title="הורד כ-HTML">HTML <span class="material-symbols-outlined" style="font-size:14px; margin-right:4px;">download</span></button>`;
                      } else {
                          actions += `<button class="btn-secondary btn-icon" onclick="downloadFile('${f.what}', '${f.name}')" title="הורד קובץ"><span class="material-symbols-outlined">download</span></button>`;
                      }
      
                      // כפתורים בסיסיים לכל קובץ
                      actions += `
                          <button class="btn-secondary btn-icon" onclick="triggerReplace('${f.what}')" title="החלף קובץ"><span class="material-symbols-outlined">find_replace</span></button>
                          <button class="btn-secondary btn-icon" onclick="openActionModal('copy', '${f.what}')" title="העתק"><span class="material-symbols-outlined">content_copy</span></button>
                          <button class="btn-secondary btn-icon" onclick="openActionModal('move', '${f.what}')" title="העבר"><span class="material-symbols-outlined">drive_file_move</span></button>
                          <button class="btn-danger btn-icon" onclick="executeDelete('${f.what}')" title="מחק"><span class="material-symbols-outlined">delete</span></button>
                      `;
      
                      tbody.innerHTML += `
                          <tr>
                              <td><input type="checkbox" class="file-chk" value="${f.what}" data-name="${f.name}"></td>
                              <td dir="ltr" style="text-align:right;">${f.name}</td>
                              <td>${f.fileType || 'FILE'}</td>
                              <td dir="ltr" style="text-align:right;">${formatBytes(f.size)}</td>
                              <td dir="ltr" style="text-align:right;">${f.mtime || '-'}</td>
                              <td class="actions-cell">${actions}</td>
                          </tr>
                      `;
                  });
              }
      
              function renderMessages(messages, descs) {
                  const tbody = document.getElementById('messages-tbody');
                  tbody.innerHTML = '';
                  if(messages.length === 0) { tbody.innerHTML = '<tr><td colspan="4" style="text-align:center;">אין הודעות מערכת בשלוחה זו</td></tr>'; return; }
                  
                  messages.forEach(f => {
                      let key = f.name.replace('.wav','');
                      let desc = descs[key] || 'ללא תיאור';
                      tbody.innerHTML += `
                          <tr>
                              <td dir="ltr" style="text-align:right; font-weight:bold;">${f.name}</td>
                              <td>${desc}</td>
                              <td dir="ltr" style="text-align:right;">${f.durationStr || formatBytes(f.size)}</td>
                              <td class="actions-cell">
                                  <button class="btn-primary btn-icon" onclick="playAudio('${f.what}', '${desc}')"><span class="material-symbols-outlined">play_arrow</span></button>
                                  <button class="btn-secondary btn-icon" onclick="triggerReplace('${f.what}')" title="החלף הקלטה"><span class="material-symbols-outlined">find_replace</span></button>
                                  <button class="btn-danger btn-icon" onclick="executeDelete('${f.what}')"><span class="material-symbols-outlined">delete</span></button>
                              </td>
                          </tr>
                      `;
                  });
              }
      
              function switchTab(tabId) {
                  document.querySelectorAll('.tab').forEach(el => el.classList.remove('active'));
                  document.querySelectorAll('.tab-content').forEach(el => el.classList.remove('active'));
                  document.querySelector(`.tab[onclick="switchTab('${tabId}')"]`).classList.add('active');
                  document.getElementById(tabId).classList.add('active');
              }
      
              // --- העלאה והחלפה ---
              function triggerReplace(path) {
                  replaceTargetFile = path;
                  document.getElementById('file-replace').click();
              }
      
              async function uploadFile(inputElement, isReplace = false) {
                  if(inputElement.files.length === 0) return;
                  const file = inputElement.files[0];
                  
                  let targetPath = isReplace ? replaceTargetFile : `${CURRENT_PATH}/${file.name}`;
                  targetPath = normalizePath(targetPath);
      
                  showToast('מעלה קובץ...');
                  const formData = new FormData();
                  formData.append('token', TOKEN);
                  formData.append('path', targetPath);
                  formData.append('file', file);
      
                  try {
                      const res = await fetch(API_URL + 'UploadFile', { method: 'POST', body: formData });
                      const data = await res.json();
                      if(data.responseStatus === 'OK' || data.path) {
                          showToast('הקובץ עלה בהצלחה!', false);
                          refreshUI(); // קריאה לרענון
                      } else { showToast(data.message || 'שגיאה בהעלאה', true); }
                  } catch(e) { showToast('שגיאת תקשורת', true); }
                  
                  inputElement.value = '';
              }
      
              // --- שמע, הורדה ודוחות ---
              async function downloadFile(path, name) {
                  try {
                      const blob = await apiRequest('DownloadFile', { path: path }, true);
                      const url = window.URL.createObjectURL(blob);
                      const a = document.createElement('a');
                      a.style.display = 'none';
                      a.href = url;
                      a.download = name;
                      document.body.appendChild(a);
                      a.click();
                      window.URL.revokeObjectURL(url);
                  } catch(e) { showToast('שגיאה בהורדת הקובץ', true); }
              }
      
              // פונקציה חדשה להורדת דוחות YMGR כ-CSV או HTML
              async function downloadYmgr(path, name, format) {
                  try {
                      showToast(`מכין ומוריד דוח כ-${format.toUpperCase()}...`);
                      const blob = await apiRequest('RenderYMGRFile', { wath: path, convertType: format }, true);
                      const url = window.URL.createObjectURL(blob);
                      const a = document.createElement('a');
                      a.style.display = 'none';
                      a.href = url;
                      a.download = name.replace('.ymgr', `.${format}`);
                      document.body.appendChild(a);
                      a.click();
                      window.URL.revokeObjectURL(url);
                  } catch(e) { showToast('שגיאה בהורדת הדוח', true); }
              }
      
              async function playAudio(path, name) {
                  try {
                      showToast('טוען שמע...');
                      const blob = await apiRequest('DownloadFile', { path: path }, true);
                      const url = window.URL.createObjectURL(blob);
                      const audio = document.getElementById('main-audio');
                      document.getElementById('audio-title').innerText = name;
                      audio.src = url;
                      document.getElementById('audio-player-container').classList.add('show');
                  } catch(e) { showToast('לא ניתן לנגן קובץ זה', true); }
              }
              
              function closeAudioPlayer() {
                  const audio = document.getElementById('main-audio');
                  audio.pause();
                  audio.src = '';
                  document.getElementById('audio-player-container').classList.remove('show');
              }
      
              async function viewReport(path, name) {
                  document.getElementById('viewer-title').innerText = name;
                  const iframe = document.getElementById('viewer-iframe');
                  iframe.src = '';
                  document.getElementById('viewer-modal').style.display = 'flex';
                  showToast('טוען דוח...');
      
                  try {
                      let blob;
                      if(path.endsWith('.ymgr')) {
                          blob = await apiRequest('RenderYMGRFile', { wath: path, convertType: 'html' }, true);
                      } else {
                          blob = await apiRequest('DownloadFile', { path: path }, true);
                      }
                      const url = window.URL.createObjectURL(blob);
                      iframe.src = url;
                  } catch(e) { closeModal('viewer-modal'); showToast('שגיאה בטעינת הדוח', true); }
              }
      
              // --- מחיקה, העתקה, העברה ---
              async function executeDelete(path) {
                  if(!confirm(`האם למחוק את ${path} לצמיתות? (אם זו שלוחה, כל התוכן שלה יימחק!)`)) return;
                  try {
                      await apiRequest('FileAction', { action: 'delete', what: path });
                      showToast('נמחק בהצלחה');
                      refreshUI(path); // קריאה לרענון
                  } catch(e) {}
              }
      
              function openActionModal(action, what) {
                  document.getElementById('action-type').value = action;
                  document.getElementById('action-what').value = what;
                  document.getElementById('action-title').innerText = action === 'copy' ? 'העתקת קובץ/שלוחה' : 'העברת קובץ/שלוחה';
                  document.getElementById('action-target').value = CURRENT_PATH;
                  document.getElementById('action-modal').style.display = 'flex';
              }
      
              async function executeFileAction() {
                  const action = document.getElementById('action-type').value;
                  const what = document.getElementById('action-what').value;
                  const target = normalizePath(document.getElementById('action-target').value);
                  
                  try {
                      await apiRequest('FileAction', { action: action, what: what, target: target });
                      showToast('הפעולה בוצעה בהצלחה');
                      closeModal('action-modal');
                      refreshUI(what); // מרענן את המקור
                      refreshUI(target); // מרענן את היעד
                  } catch(e) {}
              }
      
              // --- יצירת שלוחה חדשה + הגדרות ---
              function openNewFolderModal() {
                  document.getElementById('new-folder-path-display').innerText = CURRENT_PATH;
                  document.getElementById('new-folder-name').value = '';
                  document.getElementById('new-folder-settings').value = "type=menu\ntitle=שלוחה חדשה"; // ברירת מחדל
                  document.getElementById('new-folder-modal').style.display = 'flex';
              }
      
              async function createFolder() {
                  const name = document.getElementById('new-folder-name').value.trim();
                  const settings = document.getElementById('new-folder-settings').value;
                  
                  if(!name) return alert('נא להזין שם או מספר לשלוחה');
                  
                  const target = normalizePath(`${CURRENT_PATH}/${name}`);
                  const extIniPath = normalizePath(`${target}/ext.ini`);
                  
                  try {
                      showToast('יוצר שלוחה ושומר הגדרות...');
                      // 1. יצירת התיקייה
                      await apiRequest('UpdateExtension', { path: target });
                      // 2. כתיבת ההגדרות מהטופס ישירות ל ext.ini של השלוחה החדשה
                      await apiRequest('UploadTextFile', { what: extIniPath, contents: settings });
                      
                      showToast('השלוחה נוצרה בהצלחה!');
                      closeModal('new-folder-modal');
                      refreshUI(target); // קריאה לרענון שיוסיף את התיקייה לעץ אוטומטית
                  } catch(e) {
                      showToast('שגיאה ביצירת השלוחה', true);
                  }
              }
      
              // --- עריכת Ext.ini וטקסט ---
              async function loadExtIni() {
                  const editor = document.getElementById('ext-ini-editor');
                  editor.value = 'טוען...';
                  const target = normalizePath(`${CURRENT_PATH}/ext.ini`);
                  try {
                      const data = await apiRequest('GetTextFile', { what: target });
                      editor.value = data.contents || '';
                  } catch(e) { editor.value = ''; }
              }
      
              async function saveExtIni() {
                  const contents = document.getElementById('ext-ini-editor').value;
                  const target = normalizePath(`${CURRENT_PATH}/ext.ini`);
                  try {
                      await apiRequest('UploadTextFile', { what: target, contents: contents });
                      showToast('הגדרות השלוחה נשמרו בהצלחה!');
                      refreshUI();
                  } catch(e) {}
              }
      
              async function openTextEditor(path, name) {
                  const editor = document.getElementById('text-editor-area');
                  document.getElementById('text-editor-title').innerText = `עריכה: ${name}`;
                  document.getElementById('text-editor-path').value = path;
                  editor.value = 'טוען...';
                  document.getElementById('text-editor-modal').style.display = 'flex';
                  
                  try {
                      const data = await apiRequest('GetTextFile', { what: path });
                      editor.value = data.contents || '';
                  } catch(e) { editor.value = 'שגיאה בטעינת תוכן הקובץ'; }
              }
      
              async function saveTextFile() {
                  const contents = document.getElementById('text-editor-area').value;
                  const path = document.getElementById('text-editor-path').value;
                  try {
                      await apiRequest('UploadTextFile', { what: path, contents: contents });
                      showToast('הקובץ נשמר!');
                      closeModal('text-editor-modal');
                      refreshUI();
                  } catch(e) {}
              }
      
              // --- בחירה מרובה ו-ZIP ---
              function toggleSelectAll(checkbox, className) {
                  document.querySelectorAll('.' + className).forEach(el => el.checked = checkbox.checked);
              }
      
              function getSelectedFiles() {
                  const selected =[];
                  document.querySelectorAll('.file-chk:checked').forEach(el => {
                      selected.push({ path: el.value, name: el.getAttribute('data-name') });
                  });
                  return selected;
              }
      
              async function deleteSelectedFiles() {
                  const selected = getSelectedFiles();
                  if(selected.length === 0) return alert('לא סומנו קבצים');
                  if(!confirm(`למחוק ${selected.length} קבצים לצמיתות?`)) return;
                  
                  showToast('מוחק קבצים...');
                  let hasError = false;
                  for(let i=0; i<selected.length; i++) {
                      try {
                          await apiRequest('FileAction', { action: 'delete', what: selected[i].path });
                      } catch(e) { hasError = true; }
                  }
                  if(hasError) showToast('חלק מהקבצים לא נמחקו', true);
                  else showToast('נמחקו בהצלחה');
                  
                  refreshUI();
              }
      
              async function downloadSelectedZip() {
                  const selected = getSelectedFiles();
                  if(selected.length === 0) return alert('לא סומנו קבצים להורדה');
                  
                  showToast('מכין קובץ ZIP, אנא המתן...');
                  const zip = new JSZip();
                  
                  for(let i=0; i<selected.length; i++) {
                      try {
                          const blob = await apiRequest('DownloadFile', { path: selected[i].path }, true);
                          zip.file(selected[i].name, blob);
                      } catch(e) { console.error('Failed to download ' + selected[i].name); }
                  }
                  
                  zip.generateAsync({type:"blob"}).then(function(content) {
                      const url = window.URL.createObjectURL(content);
                      const a = document.createElement('a');
                      a.href = url;
                      a.download = `backup_${Date.now()}.zip`;
                      document.body.appendChild(a);
                      a.click();
                      window.URL.revokeObjectURL(url);
                      showToast('הורדת ה-ZIP החלה!');
                  });
              }
      
              function closeModal(id) {
                  document.getElementById(id).style.display = 'none';
              }
          </script>
      </body>
      </html>
      

      גסון ליצרית מפתח מוגבל לשלוחה 18 כולל כל תתי השלוחות

        "tokenMore": {
          "ws_parms_mismatch_action": "remove",
          "default_acl_policy": "deny",
          "acl_rules": [
            {
              "ip": [],
              "name": null,
              "active": true,
              "params": [
                "path=ivr2:18/**",
                "path=ivr2:/18/**",
                "path=ivr2:18",
                "path=ivr2:/18",
                "what=ivr2:18/**",
                "what=ivr2:/18/**",
                "what=ivr2:/18",
                "what=ivr2:18",
                "target=ivr2:18/**",
                "target=ivr2:/18/**",
                "target=ivr2:18",
                "target=ivr2:/18",
                "queuePath=ivr2:/18/**",
                "queuePath=ivr2:18/**",
                "queuePath=ivr2:/18",
                "queuePath=ivr2:18",
                "wath=ivr2:18/**",
                "wath=ivr2:/18/**",
                "wath=ivr2:18",
                "wath=ivr2:/18",
                "ivrPath=ivr2:/18/**",
                "ivrPath=ivr2:/18",
                "ivrPath=ivr2:18/**",
                "ivrPath=ivr2:18",
                "action",
                "contents",
                "convertAudio",
                "autoNumbering",
                "tts",
                "qqfile",
                "file",
                "token",
                "/api/UpdateExtension#*",
                "convertType",
                "notLoadLang",
                "renderLanguage",
                "filesFrom",
                "filesLimit",
                "orderBy",
                "orderDir"
              ],
              "policy": "allow",
              "endpoint": [
                "/api/FileAction",
                "/api/GetIVR2Dir",
                "/api/CheckIfFileExists",
                "/api/DownloadFile",
                "/api/GetFile",
                "/api/UploadTextFile",
                "/api/UploadFile",
                "/api/GetIVR2DirStats",
                "/api/GetTextFile",
                "/api/UpdateExtension",
                "/api/RenderYMGRFile",
                "/api/CheckIfFolderExists"
              ],
              "set_params": {}
            }
          ]
        }
      }
      

      את הגסון צריך להחליף משורה 3 זה מוגבל לניהול שלוחה 18 וכל מה שתחתיה כולל הצגת דוחות רק משלוחה 18 ניתן להוסיף דוחות מעוד תיקיות, אם רוצים שלוחה אחרת כל איפה שכתוב 18 להחליף למספר השלוחה
      בחלונית צריך להכניס טוקן מוגבל ולכתוב לאיזה שלוחה הוא מוגבל
      מי שיש לו הערות הארות אשמח לשמוע
      נתקלתם בבאג/יש לכם רעיון לשדרוג ספרו כאן
      עריכה: ב' אייר תשפ"ו הקוד עודכן וגם הגסון למפתח.

      תגובה 1 תגובה אחרונה תגובה ציטוט 0
      • B BEN ZION התייחס לנושא זה
      • B BEN ZION התייחס לנושא זה
      • פוסט ראשון
        פוסט אחרון