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

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

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

      יצרתי קוד אם בינה לניהול של שלוחה 1 מלאה
      אם אתה לא רוצה לתת סיסמה לכל מי שצריך גישה לקו שלך דוגמא בת"ת שכל מלמד יוכל לנהל את השלוחה שלו
      קוד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;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" />
          
          <style>
              :root {
                  --primary: #2563eb;
                  --primary-hover: #1d4ed8;
                  --bg-color: #f8fafc;
                  --card-bg: #ffffff;
                  --text-main: #1e293b;
                  --text-muted: #64748b;
                  --border: #e2e8f0;
                  --success: #10b981;
                  --error: #ef4444;
              }
       
              body { font-family: 'Heebo', sans-serif; margin: 0; padding: 0; background-color: var(--bg-color); color: var(--text-main); }
              
              /* === מסך התחברות === */
              #login-screen { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: var(--bg-color); z-index: 9999; display: flex; justify-content: center; align-items: center; }
              .login-card { background: white; padding: 40px; border-radius: 12px; box-shadow: 0 10px 25px rgba(0,0,0,0.05); width: 400px; max-width: 90%; text-align: center; border: 1px solid var(--border); }
              .login-card h2 { margin-top: 0; color: var(--text-main); font-size: 24px; margin-bottom: 5px; }
              .login-card p { color: var(--text-muted); font-size: 14px; margin-bottom: 25px; }
              .form-group { text-align: right; margin-bottom: 20px; }
              .form-group label { display: block; font-weight: 600; margin-bottom: 8px; font-size: 14px; color: var(--text-main); }
              .form-group input { width: 100%; padding: 12px; border: 1px solid var(--border); border-radius: 8px; box-sizing: border-box; font-family: 'Heebo', sans-serif; font-size: 15px; transition: 0.2s; direction: ltr; text-align: left; }
              .form-group input:focus { border-color: var(--primary); outline: none; box-shadow: 0 0 0 3px rgba(37,99,235,0.1); }
              
              /* === המערכת המרכזית === */
              #app-container { display: none; }
              .top-header { background: white; padding: 20px 40px; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--border); box-shadow: 0 1px 3px rgba(0,0,0,0.05); }
              .top-header h1 { margin: 0; font-size: 28px; font-weight: 600; }
              .actions-bar { display: flex; gap: 15px; }
              
              button { display: inline-flex; align-items: center; justify-content: center; gap: 8px; font-family: 'Heebo', sans-serif; font-size: 15px; font-weight: 500; cursor: pointer; border: none; border-radius: 8px; padding: 10px 20px; transition: 0.2s; }
              .btn-primary { background: var(--primary); color: white; }
              .btn-primary:hover { background: var(--primary-hover); }
              .btn-secondary { background: #475569; color: white; }
              .btn-secondary:hover { background: #334155; }
              .btn-danger { background: var(--error); color: white; }
              .btn-danger:hover { background: #dc2626; }
              .btn-warning { background: #f59e0b; color: white; }
              .btn-warning:hover { background: #d97706; }
              .btn-icon { padding: 8px; font-size: 14px; }
              .btn-full { width: 100%; padding: 12px; font-size: 16px; margin-top: 10px; }
       
              .main-container { max-width: 1400px; margin: 0 auto; padding: 30px; }
              .breadcrumbs { font-size: 16px; color: var(--text-muted); margin-bottom: 20px; display: flex; align-items: center; gap: 10px; }
              .breadcrumbs span.clickable { cursor: pointer; transition: 0.2s; font-weight: 600; }
              .breadcrumbs span.clickable:hover { color: var(--primary); }
              .breadcrumbs .separator { color: #cbd5e1; cursor: default; }
              
              .tabs-header { display: flex; gap: 30px; border-bottom: 2px solid var(--border); margin-bottom: 30px; }
              .tab-btn { background: none; border: none; color: var(--text-muted); font-size: 18px; font-weight: 600; padding: 10px 5px; cursor: pointer; border-bottom: 3px solid transparent; margin-bottom: -2px; border-radius: 0; }
              .tab-btn.active { color: var(--primary); border-bottom-color: var(--primary); }
              .tab-content { display: none; }
              .tab-content.active { display: block; }
       
              .folders-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 20px; margin-bottom: 40px; }
              .folder-card { background: var(--card-bg); border: 1px solid var(--border); border-radius: 12px; padding: 20px; display: flex; align-items: center; gap: 15px; cursor: pointer; transition: box-shadow 0.2s, transform 0.2s; box-shadow: 0 1px 3px rgba(0,0,0,0.05); }
              .folder-card:hover { box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1); transform: translateY(-2px); border-color: #cbd5e1; }
              .folder-icon { color: var(--text-muted); font-size: 40px; }
              .folder-details { flex: 1; }
              .folder-title { font-size: 20px; font-weight: 700; margin: 0 0 5px 0; }
              .folder-subtitle { font-size: 14px; color: var(--text-muted); margin: 0; }
       
              .files-section { background: white; border-radius: 12px; border: 1px solid var(--border); overflow: hidden; }
              .section-title { padding: 15px 20px; background: #f8fafc; border-bottom: 1px solid var(--border); margin: 0; font-size: 16px; color: var(--text-muted); }
              table { width: 100%; border-collapse: collapse; }
              th, td { padding: 15px 20px; text-align: right; border-bottom: 1px solid var(--border); }
              th { color: var(--text-muted); font-weight: 600; font-size: 14px; }
              tr:hover { background: #f1f5f9; }
              .td-actions { display: flex; gap: 8px; justify-content: flex-end; }
       
              .editor-container { background: white; padding: 20px; border-radius: 12px; border: 1px solid var(--border); }
              textarea.code-editor { width: 100%; height: 400px; font-family: monospace; font-size: 16px; direction: ltr; padding: 15px; box-sizing: border-box; border: 1px solid var(--border); border-radius: 8px; background: #1e1e1e; color: #d4d4d4; margin-bottom: 15px; resize: vertical;}
              
              .modal-overlay { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.6); z-index: 1000; justify-content: center; align-items: center; }
              .modal-content { background: white; width: 700px; max-width: 90%; border-radius: 12px; padding: 25px; display: flex; flex-direction: column; gap: 15px; box-shadow: 0 10px 25px rgba(0,0,0,0.2); }
              .modal-content h3 { margin: 0; font-size: 20px; }
              .modal-actions { display: flex; justify-content: flex-end; gap: 10px; }
              .hidden-input { display: none; }
       
              /* === הודעות צפות (Toasts) === */
              #toast-container { position: fixed; bottom: 20px; left: 20px; z-index: 10000; display: flex; flex-direction: column; gap: 10px; }
              .toast { background: var(--success); color: white; padding: 12px 24px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); display: flex; align-items: center; gap: 8px; font-size: 15px; opacity: 0; transform: translateY(20px); transition: all 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55); }
              .toast.show { opacity: 1; transform: translateY(0); }
              .toast.error { background: var(--error); }
          </style>
      </head>
      <body>
       
          <!-- קונטיינר להודעות צפות -->
          <div id="toast-container"></div>
       
          <!-- מסך התחברות -->
          <div id="login-screen">
              <div class="login-card">
                  <span class="material-symbols-outlined" style="font-size: 48px; color: var(--primary); margin-bottom: 10px;">cloud_sync</span>
                  <h2>התחברות למערכת</h2>
                  <p>הזן את מפתח ה-API (טוקן) שלך כדי להמשיך</p>
                  
                  <div class="form-group">
                      <label>מפתח API (Token)</label>
                      <input type="password" id="login-token" placeholder="הדבק את הטוקן כאן..." autocomplete="off">
                  </div>
                  
                  <div class="form-group">
                      <label>הגבלת שלוחה (אופציונלי)</label>
                      <input type="text" id="login-ext" placeholder="לדוגמה: 18 (השאר ריק אם אין הגבלה)" autocomplete="off">
                  </div>
                  
                  <button class="btn-primary btn-full" id="login-btn" onclick="attemptLogin()">
                      <span class="material-symbols-outlined">login</span> התחבר למערכת
                  </button>
              </div>
          </div>
       
          <!-- המערכת עצמה -->
          <div id="app-container">
              <header class="top-header">
                  <h1 id="main-title">ניהול קבצים</h1>
                  <div class="actions-bar">
                      <button class="btn-primary" style="background: #10b981;" onclick="openNewExtModal()">
                          <span class="material-symbols-outlined">create_new_folder</span> צור שלוחה חדשה
                      </button>
                      <button class="btn-primary" onclick="document.getElementById('file-upload-new').click()">
                          <span class="material-symbols-outlined">upload</span> העלה קובץ חדש
                      </button>
                      <button class="btn-secondary" onclick="logout()">
                          <span class="material-symbols-outlined">logout</span> התנתק
                      </button>
                      <input type="file" id="file-upload-new" class="hidden-input" onchange="uploadNewFile(this)">
                      <input type="file" id="file-replace-input" class="hidden-input" onchange="submitReplaceFile(this)">
                  </div>
              </header>
           
              <div class="main-container">
                  <div class="breadcrumbs" id="breadcrumbs"></div>
           
                  <div class="tabs-header">
                      <button class="tab-btn active" onclick="switchTab('tab-folders')">תיקיות וקבצים</button>
                      <button class="tab-btn" onclick="switchTab('tab-messages')">הודעות מערכת</button>
                      <button class="tab-btn" onclick="switchTab('tab-settings')">הגדרות שלוחה (ext.ini)</button>
                  </div>
           
                  <div id="tab-folders" class="tab-content active">
                      <div class="folders-grid" id="folders-grid"></div>
                      <div class="files-section">
                          <h3 class="section-title">קבצים בשלוחה (כולל קבצי שמע, הגדרות ודוחות)</h3>
                          <table>
                              <thead><tr><th>שם קובץ</th><th>סוג</th><th>גודל</th><th>תאריך שינוי</th><th>פעולות</th></tr></thead>
                              <tbody id="files-list"></tbody>
                          </table>
                      </div>
                  </div>
           
                  <div id="tab-messages" class="tab-content">
                      <div class="files-section">
                          <h3 class="section-title">הודעות מערכת מוקלטות (קבצי M)</h3>
                          <table>
                              <thead><tr><th>שם קובץ</th><th>תיאור הודעה</th><th>גודל/אורך</th><th>פעולות</th></tr></thead>
                              <tbody id="messages-list"></tbody>
                          </table>
                      </div>
                  </div>
           
                  <div id="tab-settings" class="tab-content">
                      <div class="editor-container">
                          <h3 style="margin-top:0;">עריכת ext.ini (שלוחה נוכחית)</h3>
                          <textarea id="ini-editor" class="code-editor" spellcheck="false" placeholder="טוען נתונים..."></textarea>
                          <div style="text-align: left;">
                              <button class="btn-primary" onclick="saveExtIniFile(this)">
                                  <span class="material-symbols-outlined">save</span> שמור הגדרות
                              </button>
                          </div>
                      </div>
                  </div>
              </div>
          </div>
       
          <!-- מודל עריכה קבצים קיימים -->
          <div id="file-editor-modal" class="modal-overlay">
              <div class="modal-content">
                  <h3 id="modal-editor-title">עריכת קובץ</h3>
                  <textarea id="modal-editor-textarea" class="code-editor" spellcheck="false"></textarea>
                  <div class="modal-actions">
                      <button class="btn-secondary" onclick="closeEditorModal()">ביטול</button>
                      <button class="btn-primary" onclick="saveModalFile(this)">
                          <span class="material-symbols-outlined">save</span> שמור קובץ
                      </button>
                  </div>
                  <input type="hidden" id="modal-editor-path">
              </div>
          </div>
       
          <!-- מודל יצירת שלוחה חדשה -->
          <div id="new-ext-modal" class="modal-overlay">
              <div class="modal-content">
                  <h3>יצירת שלוחה חדשה</h3>
                  <div class="form-group" style="text-align: right;">
                      <label>מספר / שם השלוחה החדשה (תיווצר בתוך הנתיב הנוכחי):</label>
                      <input type="text" id="new-ext-name" placeholder="לדוגמה: 1 או test">
                  </div>
                  <div class="form-group" style="text-align: right; margin-bottom: 0;">
                      <label>הגדרות השלוחה (ext.ini - כל הגדרה בשורה נפרדת):</label>
                      <textarea id="new-ext-settings" class="code-editor" style="height: 200px;" spellcheck="false" placeholder="type=menu&#10;title=שלוחה חדשה"></textarea>
                  </div>
                  <div class="modal-actions">
                      <button class="btn-secondary" onclick="closeNewExtModal()">ביטול</button>
                      <button class="btn-primary" style="background: #10b981;" onclick="createNewExtension(this)">
                          <span class="material-symbols-outlined">save</span> צור שלוחה
                      </button>
                  </div>
              </div>
          </div>
       
          <script>
              let TOKEN = ''; 
              let BASE_EXTENSION = ''; 
              let currentPath = '';
              let currentReplacePath = '';
              const API_URL = 'https://private.call2all.co.il/ym/api/';
              
              document.addEventListener('DOMContentLoaded', () => {
                  const savedToken = localStorage.getItem('ym_token');
                  const savedExt = localStorage.getItem('ym_ext');
                  if (savedToken) {
                      document.getElementById('login-token').value = savedToken;
                      if (savedExt) document.getElementById('login-ext').value = savedExt;
                      attemptLogin();
                  }
              });
       
              function showToast(message, isError = false) {
                  const container = document.getElementById('toast-container');
                  const toast = document.createElement('div');
                  toast.className = `toast ${isError ? 'error' : ''}`;
                  toast.innerHTML = `<span class="material-symbols-outlined">${isError ? 'error' : 'check_circle'}</span> ${message}`;
                  container.appendChild(toast);
                  
                  setTimeout(() => toast.classList.add('show'), 10);
                  setTimeout(() => {
                      toast.classList.remove('show');
                      setTimeout(() => toast.remove(), 300);
                  }, 3000);
              }
       
              function normalizePath(p) {
                  if (!p || p === '/' || p === 'ivr2:') return 'ivr2:/';
                  if (p.startsWith('ivr2:')) {
                      if (!p.startsWith('ivr2:/')) p = p.replace('ivr2:', 'ivr2:/');
                  } else if (p.startsWith('/')) {
                      p = 'ivr2:' + p;
                  } else { p = 'ivr2:/' + p; }
                  if (p.endsWith('/') && p.length > 6) p = p.slice(0, -1);
                  return p;
              }
       
              async function attemptLogin() {
                  const tokenInput = document.getElementById('login-token').value.trim();
                  const extInput = document.getElementById('login-ext').value.trim();
                  const btn = document.getElementById('login-btn');
                  
                  if (!tokenInput) { alert('חובה להזין מפתח API (טוקן).'); return; }
       
                  TOKEN = tokenInput;
                  BASE_EXTENSION = extInput ? normalizePath(extInput) : 'ivr2:/';
                  currentPath = BASE_EXTENSION;
       
                  btn.innerHTML = '<span class="material-symbols-outlined">hourglass_empty</span> מתחבר...';
                  btn.disabled = true;
       
                  try {
                      const data = await apiRequest('GetIVR2Dir', { path: currentPath });
                      if (data && (data.responseStatus === 'ERROR' || data.responseStatus === 'EXCEPTION')) {
                          alert("שגיאת גישה: " + (data.message || data.exceptionMessage));
                          resetLoginBtn(btn);
                          return;
                      }
                      
                      localStorage.setItem('ym_token', TOKEN);
                      localStorage.setItem('ym_ext', extInput);
                      
                      document.getElementById('login-screen').style.display = 'none';
                      document.getElementById('app-container').style.display = 'block';
                      
                      let displayTitle = BASE_EXTENSION === 'ivr2:/' ? 'הראשית' : BASE_EXTENSION.replace(/^ivr2:\//, '');
                      document.getElementById('main-title').innerText = 'ניהול קבצים - שלוחה ' + displayTitle;
                      document.title = 'ניהול תוכן - שלוחה ' + displayTitle;
                      
                      renderBreadcrumbs(currentPath);
                      renderFolders(data.dirs ||[]);
                      renderFiles(data.files ||[], data.html || [], data.ini ||[]);
                      renderMessages(data.messages ||[], data.msgDescriptions || {});
                      loadExtIniEditor(currentPath);
       
                  } catch (error) {
                      alert('שגיאת תקשורת מול שרתי ימות המשיח.');
                      resetLoginBtn(btn);
                  }
              }
       
              function resetLoginBtn(btn) {
                  btn.innerHTML = '<span class="material-symbols-outlined">login</span> התחבר למערכת';
                  btn.disabled = false;
              }
       
              function logout() {
                  if(!confirm('האם אתה בטוח שברצונך להתנתק?')) return;
                  localStorage.removeItem('ym_token');
                  localStorage.removeItem('ym_ext');
                  location.reload(); 
              }
       
              // פונקציית תקשורת כללית ללא קבצים - שולחת מידע כפרמטרים כדי לרצות את חומת האש
              async function apiRequest(endpoint, params = {}) {
                  const urlParams = new URLSearchParams();
                  urlParams.append('token', TOKEN);
                  for (const key in params) {
                      urlParams.append(key, params[key]);
                  }
                  try {
                      const response = await fetch(API_URL + endpoint, { 
                          method: 'POST', 
                          body: urlParams 
                      });
                      return await response.json();
                  } catch (error) { throw error; }
              }
       
              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');
              }
       
              async function loadDirectory(path) {
                  let normalizedPath = normalizePath(path);
                  try {
                      const data = await apiRequest('GetIVR2Dir', { path: normalizedPath });
                      if (!data || data.responseStatus === 'ERROR' || data.responseStatus === 'EXCEPTION') {
                          showToast('שגיאה מול השרת: ' + (data.message || data.exceptionMessage), true);
                          return;
                      }
                      currentPath = normalizedPath;
                      renderBreadcrumbs(normalizedPath);
                      renderFolders(data.dirs ||[]);
                      renderFiles(data.files || [], data.html ||[], data.ini ||[]);
                      renderMessages(data.messages ||[], data.msgDescriptions || {});
                      loadExtIniEditor(normalizedPath);
                  } catch (error) { showToast('שגיאת תקשורת במשיכת נתוני התיקייה.', true); }
              }
       
              function renderBreadcrumbs(path) {
                  const container = document.getElementById('breadcrumbs');
                  container.innerHTML = '';
                  let normPath = normalizePath(path);
                  let normBase = normalizePath(BASE_EXTENSION);
       
                  let rootSpan = document.createElement('span');
                  rootSpan.className = 'material-symbols-outlined clickable';
                  rootSpan.style.fontSize = '24px';
                  rootSpan.innerText = 'home';
                  rootSpan.title = 'ראשי';
                  rootSpan.onclick = () => loadDirectory(normBase);
                  container.appendChild(rootSpan);
       
                  if (normPath === normBase) return; 
                  
                  let relativePath = '';
                  let prefix = normBase === 'ivr2:/' ? 'ivr2:/' : normBase + '/';
                  if (normPath.startsWith(prefix)) { relativePath = normPath.substring(prefix.length); } 
                  else { relativePath = normPath.replace(/^ivr2:\//, ''); }
       
                  if(!relativePath) return;
                  let parts = relativePath.split('/');
                  let buildPath = normBase;
       
                  parts.forEach((part) => {
                      buildPath += (buildPath === 'ivr2:/' ? '' : '/') + part;
                      let clickPath = buildPath;
                      let sep = document.createElement('span');
                      sep.className = 'separator';
                      sep.innerText = ' / ';
                      container.appendChild(sep);
                      let span = document.createElement('span');
                      span.className = 'clickable';
                      span.innerText = part;
                      span.onclick = () => loadDirectory(clickPath);
                      container.appendChild(span);
                  });
              }
       
              function renderFolders(dirs) {
                  const grid = document.getElementById('folders-grid');
                  grid.innerHTML = '';
                  if(!dirs || dirs.length === 0) return;
                  dirs.forEach(dir => {
                      const title = dir.extTitle || 'ללא תיאור';
                      const type = dir.extType || 'תיקייה / תפריט';
                      grid.innerHTML += `
                          <div class="folder-card" onclick="loadDirectory('${dir.what}')">
                              <span class="material-symbols-outlined folder-icon">folder</span>
                              <div class="folder-details">
                                  <h3 class="folder-title">${dir.name}</h3>
                                  <p class="folder-subtitle">${type} • ${title}</p>
                              </div>
                          </div>
                      `;
                  });
              }
       
              function renderFiles(files, htmlFiles, iniFiles) {
                  const tbody = document.getElementById('files-list');
                  tbody.innerHTML = '';
                  let allRegular =[...(files||[]), ...(htmlFiles||[]), ...(iniFiles||[])];
                  if (allRegular.length === 0) { tbody.innerHTML = '<tr><td colspan="5" style="text-align:center;">אין קבצים בשלוחה זו</td></tr>'; return; }
       
                  allRegular.forEach(file => {
                      let sizeInfo = file.size ? (file.size / 1024).toFixed(2) + ' KB' : '-';
                      let isEditable = file.fileType === 'INI' || file.fileType === 'HTML' || file.name.endsWith('.txt');
                      let icon = (file.fileType === 'AUDIO' || file.name.endsWith('.wav') || file.name.endsWith('.mp3')) ? 'audio_file' : (file.fileType === 'INI' ? 'settings' : 'description');
                      let editBtn = isEditable ? `<button class="btn-primary btn-icon" onclick="openFileEditorModal('${file.what}', '${file.name}')" title="ערוך קובץ"><span class="material-symbols-outlined">edit</span></button>` : '';
       
                      tbody.innerHTML += `
                          <tr>
                              <td><span class="material-symbols-outlined" style="vertical-align: middle; margin-left:5px;">${icon}</span> ${file.name}</td>
                              <td>${file.fileType || 'FILE'}</td>
                              <td dir="ltr" style="text-align: right;">${sizeInfo}</td>
                              <td dir="ltr" style="text-align: right;">${file.mtime || '-'}</td>
                              <td class="td-actions">
                                  ${editBtn}
                                  <button class="btn-warning btn-icon" onclick="triggerReplace('${file.what}')" title="החלף קובץ קיים"><span class="material-symbols-outlined">find_replace</span></button>
                                  <button class="btn-secondary btn-icon" onclick="downloadFile('${file.what}')" title="הורדה"><span class="material-symbols-outlined">download</span></button>
                                  <button class="btn-danger btn-icon" onclick="deleteFile('${file.what}')" title="מחיקה"><span class="material-symbols-outlined">delete</span></button>
                              </td>
                          </tr>
                      `;
                  });
              }
       
              function renderMessages(messages, descriptions) {
                  const tbody = document.getElementById('messages-list');
                  tbody.innerHTML = '';
                  if (!messages || messages.length === 0) { tbody.innerHTML = '<tr><td colspan="4" style="text-align:center;">אין הודעות מערכת (M) בשלוחה זו</td></tr>'; return; }
       
                  messages.forEach(file => {
                      let desc = (descriptions && descriptions[file.name.replace('.wav','')]) ? descriptions[file.name.replace('.wav','')] : 'ללא תיאור';
                      let sizeInfo = file.durationStr ? file.durationStr : (file.size ? (file.size / 1024).toFixed(2) + ' KB' : '-');
                      tbody.innerHTML += `
                          <tr>
                              <td style="font-weight:bold; color:var(--primary);">${file.name}</td>
                              <td>${desc}</td>
                              <td dir="ltr" style="text-align: right;">${sizeInfo}</td>
                              <td class="td-actions">
                                  <button class="btn-warning btn-icon" onclick="triggerReplace('${file.what}')" title="החלף קובץ"><span class="material-symbols-outlined">find_replace</span></button>
                                  <button class="btn-secondary btn-icon" onclick="downloadFile('${file.what}')" title="הורדה"><span class="material-symbols-outlined">download</span></button>
                                  <button class="btn-danger btn-icon" onclick="deleteFile('${file.what}')" title="מחיקה"><span class="material-symbols-outlined">delete</span></button>
                              </td>
                          </tr>
                      `;
                  });
              }
       
              function triggerReplace(targetPath) {
                  currentReplacePath = targetPath;
                  document.getElementById('file-replace-input').click();
              }
       
              async function submitReplaceFile(inputElement) {
                  if (inputElement.files.length === 0) return;
                  const file = inputElement.files[0];
                  showToast('הקובץ עולה לשרת, אנא המתן...');
                  
                  try {
                      const formData = new FormData();
                      formData.append('token', TOKEN);
                      formData.append('path', normalizePath(currentReplacePath));
                      formData.append('file', file);
       
                      const response = await fetch(API_URL + 'UploadFile', { method: 'POST', body: formData });
                      const result = await response.json();
                      
                      if (result.responseStatus === 'OK' || result.path) {
                          showToast('הקובץ הוחלף בהצלחה!');
                          loadDirectory(currentPath);
                      } else { showToast('שגיאה: ' + (result.message || 'חסום'), true); }
                  } catch (error) { showToast('שגיאת תקשורת בהחלפת הקובץ', true); }
                  inputElement.value = '';
              }
       
              async function uploadNewFile(inputElement) {
                  if (inputElement.files.length === 0) return;
                  const file = inputElement.files[0];
                  let targetPath = currentPath === 'ivr2:/' ? 'ivr2:/' + file.name : currentPath + '/' + file.name;
                  showToast('מעלה קובץ חדש, אנא המתן...');
                  
                  try {
                      const formData = new FormData(); 
                      formData.append('token', TOKEN);
                      formData.append('path', normalizePath(targetPath));
                      formData.append('file', file);
       
                      const response = await fetch(API_URL + 'UploadFile', { method: 'POST', body: formData });
                      const result = await response.json();
                      
                      if (result.responseStatus === 'OK' || result.path) {
                          showToast('הקובץ הועלה בהצלחה!');
                          loadDirectory(currentPath);
                      } else { showToast('שגיאה: ' + (result.message || 'חסום ע"י השרת'), true); }
                  } catch (error) { showToast('שגיאת תקשורת', true); }
                  inputElement.value = '';
              }
       
              function downloadFile(what) { window.location.href = API_URL + 'DownloadFile?token=' + TOKEN + '&path=' + encodeURIComponent(normalizePath(what)); }
       
              async function deleteFile(what) {
                  if (!confirm('האם למחוק קובץ זה לצמיתות?')) return;
                  try {
                      const result = await apiRequest('FileAction', { action: 'delete', what: normalizePath(what) });
                      if (result.success || result.responseStatus === 'OK') { 
                          showToast('הקובץ נמחק בהצלחה!');
                          loadDirectory(currentPath); 
                      } 
                      else { showToast('שגיאה במחיקה (ייתכן שחסום ע"י הפיירוול).', true); }
                  } catch(e) { showToast('שגיאת תקשורת במחיקה', true); }
              }
       
              async function openFileEditorModal(what, name) {
                  document.getElementById('modal-editor-title').innerText = `עורך קובץ: ${name}`;
                  document.getElementById('modal-editor-path').value = normalizePath(what);
                  document.getElementById('modal-editor-textarea').value = 'טוען תוכן...';
                  document.getElementById('file-editor-modal').style.display = 'flex';
       
                  try {
                      const result = await apiRequest('GetTextFile', { what: normalizePath(what) });
                      document.getElementById('modal-editor-textarea').value = result.contents !== undefined ? result.contents : '';
                  } catch (e) { document.getElementById('modal-editor-textarea').value = 'שגיאה בטעינת הקובץ'; }
              }
       
              function closeEditorModal() { document.getElementById('file-editor-modal').style.display = 'none'; }
       
              // שמירת קובץ ערוך
              async function saveModalFile(btn) {
                  const what = document.getElementById('modal-editor-path').value;
                  const contents = document.getElementById('modal-editor-textarea').value;
                  const originalHTML = btn.innerHTML;
                  
                  btn.innerHTML = '<span class="material-symbols-outlined">hourglass_empty</span> שומר...';
                  btn.disabled = true;
                  
                  try {
                      const filename = what.split('/').pop() || 'file.txt';
                      const blob = new Blob([contents], { type: 'text/plain;charset=utf-8' });
                      const file = new File([blob], filename, { type: 'text/plain' });
                      
                      const formData = new FormData();
                      formData.append('token', TOKEN);
                      formData.append('path', normalizePath(what));
                      formData.append('file', file);
       
                      const response = await fetch(API_URL + 'UploadFile', { method: 'POST', body: formData });
                      const result = await response.json();
                      
                      if (result.responseStatus === 'OK' || result.path) {
                          btn.innerHTML = '<span class="material-symbols-outlined">check</span> נשמר בהצלחה!';
                          btn.style.backgroundColor = 'var(--success)';
                          
                          setTimeout(() => {
                              btn.innerHTML = originalHTML;
                              btn.style.backgroundColor = '';
                              btn.disabled = false;
                              closeEditorModal();
                              loadDirectory(currentPath);
                          }, 1500);
                      } else { 
                          showToast('שגיאה בשמירה: ' + (result.message || 'חסום'), true);
                          btn.innerHTML = originalHTML;
                          btn.disabled = false;
                      }
                  } catch (e) { 
                      showToast('שגיאת תקשורת', true);
                      btn.innerHTML = originalHTML;
                      btn.disabled = false;
                  }
              }
       
              async function loadExtIniEditor(path) {
                  let targetPath = path === 'ivr2:/' ? 'ivr2:/ext.ini' : path + '/ext.ini';
                  const editor = document.getElementById('ini-editor');
                  editor.value = 'טוען...';
                  try {
                      const result = await apiRequest('GetTextFile', { what: targetPath });
                      editor.value = result.contents !== undefined ? result.contents : '';
                  } catch (e) { editor.value = ''; }
              }
       
              // שמירת ext.ini של השלוחה הנוכחית
              async function saveExtIniFile(btn) {
                  let targetPath = currentPath === 'ivr2:/' ? 'ivr2:/ext.ini' : currentPath + '/ext.ini';
                  const contents = document.getElementById('ini-editor').value;
                  const originalHTML = btn.innerHTML;
                  
                  btn.innerHTML = '<span class="material-symbols-outlined">hourglass_empty</span> שומר...';
                  btn.disabled = true;
                  
                  try {
                      const blob = new Blob([contents], { type: 'text/plain;charset=utf-8' });
                      const file = new File([blob], 'ext.ini', { type: 'text/plain' });
                      
                      const formData = new FormData();
                      formData.append('token', TOKEN);
                      formData.append('path', normalizePath(targetPath));
                      formData.append('file', file);
       
                      const response = await fetch(API_URL + 'UploadFile', { method: 'POST', body: formData });
                      const result = await response.json();
                      
                      if (result.responseStatus === 'OK' || result.path) {
                          btn.innerHTML = '<span class="material-symbols-outlined">check</span> הגדרות נשמרו!';
                          btn.style.backgroundColor = 'var(--success)';
                          
                          setTimeout(() => {
                              btn.innerHTML = originalHTML;
                              btn.style.backgroundColor = '';
                              btn.disabled = false;
                          }, 2500);
                      } else { 
                          showToast('שגיאה בשמירה: ' + (result.message || 'בדוק חומת אש'), true); 
                          btn.innerHTML = originalHTML;
                          btn.disabled = false;
                      }
                  } catch (e) { 
                      showToast('שגיאת תקשורת', true);
                      btn.innerHTML = originalHTML;
                      btn.disabled = false;
                  }
              }
       
              /* ========================================================
                 === פונקציות ליצירת שלוחה חדשה (UpdateExtension) === 
                 ======================================================== */
       
              function openNewExtModal() {
                  document.getElementById('new-ext-name').value = '';
                  document.getElementById('new-ext-settings').value = 'type=menu\ntitle=שלוחה חדשה\n';
                  document.getElementById('new-ext-modal').style.display = 'flex';
              }
       
              function closeNewExtModal() {
                  document.getElementById('new-ext-modal').style.display = 'none';
              }
       
              async function createNewExtension(btn) {
                  const extName = document.getElementById('new-ext-name').value.trim();
                  const settingsText = document.getElementById('new-ext-settings').value.trim();
       
                  if (!extName) {
                      alert('חובה להזין מספר או שם לשלוחה החדשה.');
                      return;
                  }
       
                  // יצירת הנתיב המלא בהתאם למיקום הנוכחי שבו אנחנו נמצאים
                  let targetExtPath = currentPath === 'ivr2:/' ? 'ivr2:/' + extName : currentPath + '/' + extName;
                  targetExtPath = normalizePath(targetExtPath);
       
                  const originalHTML = btn.innerHTML;
                  btn.innerHTML = '<span class="material-symbols-outlined">hourglass_empty</span> יוצר שלוחה...';
                  btn.disabled = true;
       
                  try {
                      // שלב 1: קריאה בסיסית ליצירת השלוחה בשרת
                      await apiRequest('UpdateExtension', { path: targetExtPath });
       
                      // שלב 2: יצירת קובץ וירטואלי ושמירת ההגדרות (עוקף חומת אש)
                      const blob = new Blob([settingsText], { type: 'text/plain;charset=utf-8' });
                      const file = new File([blob], 'ext.ini', { type: 'text/plain' });
                      
                      const formData = new FormData();
                      formData.append('token', TOKEN);
                      formData.append('path', targetExtPath + '/ext.ini'); // מכניס את הקובץ לתיקייה החדשה
                      formData.append('file', file);
       
                      const response = await fetch(API_URL + 'UploadFile', { method: 'POST', body: formData });
                      const result = await response.json();
       
                      if (result.responseStatus === 'OK' || result.path) {
                          showToast('השלוחה נוצרה וההגדרות נשמרו בהצלחה!');
                          closeNewExtModal();
                          loadDirectory(currentPath); // טוען מחדש לראות את השלוחה החדשה
                      } else {
                          showToast('השלוחה נוצרה אך הייתה שגיאה בשמירת ההגדרות', true);
                      }
                  } catch (e) {
                      showToast('שגיאת תקשורת מול השרת', true);
                  } finally {
                      btn.innerHTML = originalHTML;
                      btn.disabled = false;
                  }
              }
       
          </script>
      </body>
      </html>
      

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

      {
        "tokenNike": "הגבלה לשלוחה 1",
        "tokenUser": "pIi0j5kxIJmxCGStG3DNiQ",
        "tokenMore": {
          "ws_parms_whitelist": [
            "wath",
            "path",
            "what",
            "action",
            "contents",
            "target",
            "file"
          ],
          "ws_parms_mismatch_action": "remove",
          "default_acl_policy": "deny",
          "acl_rules": [
            {
              "ip": [],
              "name": "Allow Extension 18 and Sub-folders",
              "active": true,
              "params": [
                "what=ivr2:/18/**",
                "what=ivr2:/18",
                "what=ivr2:18/**",
                "what=ivr2:18",
                "wath=ivr2:/18/**",
                "wath=ivr2:/18",
                "wath=ivr2:18/**",
                "wath=ivr2:18",
                "path=ivr2:/18/**",
                "path=ivr2:/18",
                "path=ivr2:18/**",
                "path=ivr2:18",
                "path=/18/**",
                "path=/18",
                "path=18/**",
                "path=18",
                "target=ivr2:/18/**",
                "target=ivr2:/18",
                "target=ivr2:18/**",
                "target=ivr2:18"
              ],
              "policy": "allow",
              "endpoint": [],
              "set_params": {}
            }
          ]
        }
      }
      

      בחלונית צריך להכניס טוקן מוגבל ולכתוב לאיזה שלוחה הוא מוגבל
      מי שיש לו הערות הארות אשמח לשמוע
      נתקלתם בבאג/יש לכם רעיון לשדרוג ספרו כאן

      אני לא מבין במפתחות API גם אין כל כך הסבר ברור זה הגדרות שגימיני כתב אז אולי הוא חרטט כמה הגדרות תכלס זה עובד רק לשלוחה השאתי וזה מה שחשוב לי אם יש פה מישהו שמבין בזה אשמח אם יעבור על זה וייתן לי גסון תקין לניהול וביצוע כל הפעולות שתחת שלוחה 18 כולל תתי שלוחות

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