פאנל ניהול מותאם אישית
-
כמה מורכב לבנות פאנל ניהול שיעבוד אם מפתח API מותאם אישית שינהל רק שלוחה אחת על כל תתי השלוחות שלה @מ.-מ.-פליישער אשמח לחוות דעתך
וכמה יכול לעלות כזה פיתוח? -
@BEN-ZION
אולי תוכל קצת לפרט מה זה אמור להכיל? -
@אA יש ל ממ פליישער פאנל ניהול שעובד על API שיהיה את כל הפונקציות הבסיסיות יצירת שלוחות הגדרות העלאת קבצים מחיקה ניהול מלא אבל רק של התיקייה כולל תיקיות משנה ככה אני נותן לכל מלמד בת"ת קוד לניהול השלוחה של הכיתה שלו בלי חשש שיינזקו דברים אחרים
אולי גם הגבלה על ההגדרות שיכולות להיות בעלות יחי' -
@BEN-ZION
תוכל לעשות את זה עם טוקן מוגבל.
ואם תרצה עזרה בפיתוח הפאנל, אשמח לעזור במה שאוכל.
האמת שרציתי לשבת קצת וליצור ממשק שירכז את אפשרויות הניהול הבסיסיות עם פיצרים נוספים. -
@אA זה בדיוק מה שאני רוצה האמת שעבדתי על לבנות לי בטבלה איזה פונקציות נצרכות והתייאשתי באמצע יש ל מ. מ. פליישער פאנל ניהול אם טוקן אם יהיה לו דרך להגדיר שטוקן מוגבל יוכל להתחבר אז יופיע כל השלוחות וינסה לגשת לשלוחה שהיא לא שלו יהיה שגיאה אני מוכן לשלם לו סכום סמלי אם הוא יעשה את זה ואני מאמין שיהיו עוד הרבה שיהיו מוכנים לשלם על זה
-
@BEN-ZION
יש לו אפשרות התחברות עם טוקן?
אז למה שטוקן מוגבל לא יעבוד?! -
@אA יש לו חיבור אם טוקן ניסיתי להכניס טוקן מוגבל וזה עשה לי שגיאה
-
@BEN-ZION
מעניין.
בכל מקרה כשיהיה לי זמן (קצת קשה בתקופה הזו...) אני רוצה לשבת על פיתוח של כזה פאנל ניהול.
השאלה היא מה נצרך בכזה דבר? -
@אA כל מנהל/ת ת"ת בתי ספר שרוצים לתת גישה אם לב רגוע
לי יש במערכת כמה קווי ווצאפון שאני רוצה לתת לאנשים אחרים לנהל אבל אני לא יכול כי יש שם עוד דברים וטעות קטנה גורמת נזק יקר -
@BEN-ZION
התכוונתי מה נצרך להכניס בכזה פאנל.
הצורך בכזה דבר מאוד ברור... -
@אA את כל הניהול הבסיסי של שלוחה: העלאת קבצים, הורדה, מחיקה, ניהול קבצי EXT, INI, אפשרות לניהול רשימ"ת ספציפית,
זה בגדול אולי יש עוד פרטים שאני לא זוכר -
- העלאה
- הורדה
- מחיקה
- ניהול רשי"ת
- ניהול קבצי ini
ניהול קבצי ext נראה לי קצת פחות, הרי מזה בדיוק אתה מםחד שיטפלו בזה, לא?
-
@אA נכון אבל בשלוחה שלו אם ירצה להוסיף שלוחה פנימית יהיה חייב לערוך
לא אכפת לי שבשלוחה שלו יהיה נזק העיקר שהקו מתפקד
-
@אA
אם כבר מפתחים
להוסיף פיצ'רים לרשימת צינתוקים
וברישמת תפוצה נגיד לוג מי נוסף ומתי ואיך -
פעם פיתכתי משהו בערך
@אa
@ben-zion
יש את זה yemot_basic_manager.html -
@לימוד-בתורת-מרן תעלה את הקוד אי אפשר להעלות ככה או שתדחוס את זה
-
@BEN-ZION
מעלה את הקוד -
פוסט זה נמחק! -
@ben-zion
קוד חדש
כותבים פקודה בapi
ועוד דברים זה ישן<!doctype html> <html lang="he" dir="rtl"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1" /> <title>ניהול בסיסי לשלוחה - ימות המשיח</title> <style> body{ font-family:Arial,sans-serif; background:#f4f6f9; margin:0; padding:20px; color:#222; } .wrap{ max-width:1000px; margin:auto; } h1{ margin:0 0 20px; background:#243447; color:#fff; padding:14px; border-radius:12px; font-size:24px; } .grid{ display:grid; grid-template-columns:repeat(auto-fit,minmax(300px,1fr)); gap:16px; } .card{ background:#fff; border-radius:14px; padding:16px; box-shadow:0 2px 10px rgba(0,0,0,.08); } .card h2{ margin-top:0; font-size:20px; } label{ display:block; margin:10px 0 5px; font-weight:bold; } input, textarea, select, button{ width:100%; box-sizing:border-box; padding:10px; border:1px solid #cfd7df; border-radius:10px; font-size:15px; } textarea{ min-height:130px; resize:vertical; font-family:monospace; } button{ background:#0b57d0; color:#fff; border:none; cursor:pointer; font-weight:bold; margin-top:10px; } button:hover{ opacity:.92; } .btn-danger{ background:#c62828; } .btn-green{ background:#2e7d32; } .btn-gray{ background:#546e7a; } .row{ display:grid; grid-template-columns:1fr 1fr; gap:10px; } .small{ font-size:13px; color:#666; margin-top:6px; } #filesTable{ width:100%; border-collapse:collapse; margin-top:10px; font-size:14px; } #filesTable th,#filesTable td{ border:1px solid #ddd; padding:8px; text-align:right; } #filesTable th{ background:#eef3f8; } pre{ background:#111; color:#7CFC00; padding:14px; border-radius:12px; min-height:180px; overflow:auto; white-space:pre-wrap; word-break:break-word; } .muted{ color:#888; font-size:13px; } </style> </head> <body> <div class="wrap"> <h1>ניהול בסיסי לשלוחה - ימות המשיח</h1> <div class="card" style="margin-bottom:16px;"> <h2>הגדרות כלליות</h2> <label>טוקן</label> <input id="token" placeholder="077XXXXXXX:XXXXXX" /> <label>נתיב שלוחה</label> <input id="path" placeholder="לדוגמה: 1/2/3" /> <div class="small">הנתיב יישלח ל־API כ־ivr2:1/2/3</div> </div> <div class="grid"> <div class="card"> <h2>העלאת קובץ</h2> <label>בחר קובץ</label> <input type="file" id="uploadFileInput" /> <label>שם יעד לקובץ (לא חובה)</label> <input id="targetFileName" placeholder="לדוגמה: 000.wav או ext.ini" /> <label> <input type="checkbox" id="convertAudio" style="width:auto;transform:scale(1.2);margin-left:8px;"> המר אודיו ל־wav </label> <button onclick="uploadFile()">העלה קובץ</button> </div> <div class="card"> <h2>הורדת קובץ</h2> <label>שם קובץ להורדה</label> <input id="downloadFileName" placeholder="לדוגמה: 000.wav או ext.ini" /> <button class="btn-green" onclick="downloadFile()">הורד קובץ</button> </div> <div class="card"> <h2>מחיקה</h2> <label>שם קובץ / שלוחה למחיקה</label> <input id="deleteName" placeholder="לדוגמה: 000.wav או 8" /> <button class="btn-danger" onclick="deleteItem()">מחק</button> </div> <div class="card"> <h2>רשימת קבצים בשלוחה</h2> <button class="btn-gray" onclick="getDir()">רענן רשימת קבצים</button> <div id="filesArea" class="muted" style="margin-top:10px;">עדיין לא נטענה רשימה</div> </div> <div class="card"> <h2>ניהול ext.ini</h2> <div class="row"> <div> <button class="btn-gray" onclick="loadTextFile('ext.ini','extContent')">טען ext.ini</button> </div> <div> <button onclick="saveTextFile('ext.ini','extContent')">שמור ext.ini</button> </div> </div> <label>תוכן ext.ini</label> <textarea id="extContent" placeholder="type=menu title=בדיקה"></textarea> </div> <div class="card"> <h2>ניהול קובץ INI</h2> <label>שם קובץ ini</label> <input id="iniFileName" placeholder="לדוגמה: M1000.ini" /> <div class="row"> <div> <button class="btn-gray" onclick="loadNamedIni()">טען קובץ ini</button> </div> <div> <button onclick="saveNamedIni()">שמור קובץ ini</button> </div> </div> <label>תוכן קובץ ini</label> <textarea id="iniContent"></textarea> </div> <div class="card"> <h2>ניהול קובץ EXT נוסף</h2> <label>שם קובץ ext</label> <input id="extFileName" placeholder="לדוגמה: ext.ini או ext2.ini" /> <div class="row"> <div> <button class="btn-gray" onclick="loadNamedExt()">טען קובץ ext</button> </div> <div> <button onclick="saveNamedExt()">שמור קובץ ext</button> </div> </div> <label>תוכן קובץ ext</label> <textarea id="extCustomContent"></textarea> </div> <div class="card"> <h2>ניהול רשימ"ת / רשימת צינתוקים</h2> <label>שם רשימה</label> <input id="listName" placeholder="לדוגמה: 120" /> <div class="row"> <div><button class="btn-gray" onclick="getLists()">הצג כל הרשימות</button></div> <div><button class="btn-gray" onclick="getListEntries()">הצג מנויים</button></div> </div> <div class="row"> <div><button class="btn-gray" onclick="getListLog()">הצג לוג</button></div> <div><button class="btn-danger" onclick="resetList()">אפס רשימה</button></div> </div> <div class="small"> לפי התיעוד שמצאתי: יש צפייה ברשימות, מנויים, לוג ואיפוס. לא מצאתי כאן פעולה ברורה להוספה/מחיקה ידנית של מספר דרך ה־API הרגיל. </div> </div> </div> <div class="card" style="margin-top:16px;"> <h2>לוג / פלט</h2> <pre id="log"></pre> </div> </div> <script> const API = "https://www.call2all.co.il/ym/api/"; function log(msg, append = true) { const el = document.getElementById("log"); const text = typeof msg === "string" ? msg : JSON.stringify(msg, null, 2); el.textContent = append ? (el.textContent + (el.textContent ? "\n\n" : "") + text) : text; } function clearLog() { document.getElementById("log").textContent = ""; } function getToken() { return document.getElementById("token").value.trim(); } function getPathRaw() { return document.getElementById("path").value.trim().replace(/^\/+|\/+$/g, ""); } function getIvrPath() { const p = getPathRaw(); return p ? `ivr2:${p}` : "ivr2:"; } function buildFullPath(fileName) { const p = getPathRaw(); const cleanName = String(fileName || "").trim().replace(/^\/+/, ""); return p ? `ivr2:${p}/${cleanName}` : `ivr2:${cleanName}`; } function ensureTokenAndPath() { const token = getToken(); const path = getPathRaw(); if (!token) { alert("נא להזין טוקן"); return null; } if (!path) { alert("נא להזין נתיב שלוחה"); return null; } return { token, path }; } async function safeJson(res) { const txt = await res.text(); try { return JSON.parse(txt); } catch { return { raw: txt, httpStatus: res.status, ok: res.ok }; } } async function uploadFile() { clearLog(); const base = ensureTokenAndPath(); if (!base) return; const fileInput = document.getElementById("uploadFileInput"); const file = fileInput.files[0]; if (!file) { alert("נא לבחור קובץ"); return; } const targetFileName = document.getElementById("targetFileName").value.trim() || file.name; const convertAudio = document.getElementById("convertAudio").checked ? "1" : "0"; const form = new FormData(); form.append("token", base.token); form.append("path", buildFullPath(targetFileName)); form.append("convertAudio", convertAudio); form.append("qqfile", file, file.name); try { const res = await fetch(API + "UploadFile", { method: "POST", body: form }); const data = await safeJson(res); log(data, false); await getDir(); } catch (e) { log("שגיאה בהעלאה: " + e.message, false); } } function downloadFile() { clearLog(); const base = ensureTokenAndPath(); if (!base) return; const name = document.getElementById("downloadFileName").value.trim(); if (!name) { alert("נא להזין שם קובץ"); return; } const url = API + "DownloadFile?token=" + encodeURIComponent(base.token) + "&path=" + encodeURIComponent(buildFullPath(name)); log("פותח הורדה:\n" + url, false); window.open(url, "_blank"); } async function deleteItem() { clearLog(); const base = ensureTokenAndPath(); if (!base) return; const name = document.getElementById("deleteName").value.trim(); if (!name) { alert("נא להזין שם קובץ או שלוחה למחיקה"); return; } if (!confirm("למחוק את: " + name + " ?")) return; const url = API + "FileAction?token=" + encodeURIComponent(base.token) + "&action=delete" + "&what=" + encodeURIComponent(buildFullPath(name)); try { const res = await fetch(url); const data = await safeJson(res); log(data, false); await getDir(); } catch (e) { log("שגיאה במחיקה: " + e.message, false); } } async function getDir() { const base = ensureTokenAndPath(); if (!base) return; const url = API + "GetIVR2Dir?token=" + encodeURIComponent(base.token) + "&path=" + encodeURIComponent(base.path); try { const res = await fetch(url); const data = await safeJson(res); log(data, false); renderFiles(data); } catch (e) { log("שגיאה בקבלת רשימת קבצים: " + e.message, false); } } function renderFiles(data) { const area = document.getElementById("filesArea"); const files = data && data.files ? data.files : []; if (!files.length) { area.innerHTML = "לא נמצאו קבצים או שלא התקבלה רשימה."; return; } let html = ` <table id="filesTable"> <thead> <tr> <th>שם</th> <th>סוג</th> <th>גודל</th> </tr> </thead> <tbody> `; for (const f of files) { html += ` <tr> <td>${escapeHtml(f.name ?? "")}</td> <td>${escapeHtml(f.fileType ?? "")}</td> <td>${escapeHtml(String(f.size ?? ""))}</td> </tr> `; } html += `</tbody></table>`; area.innerHTML = html; } async function loadTextFile(fileName, targetTextareaId) { clearLog(); const base = ensureTokenAndPath(); if (!base) return; const url = API + "GetTextFile?token=" + encodeURIComponent(base.token) + "&what=" + encodeURIComponent(buildFullPath(fileName)); try { const res = await fetch(url); const data = await safeJson(res); log(data, false); if (data && typeof data.contents !== "undefined") { document.getElementById(targetTextareaId).value = data.contents; } else { alert("לא התקבל תוכן קובץ"); } } catch (e) { log("שגיאה בקריאת קובץ טקסט: " + e.message, false); } } async function saveTextFile(fileName, sourceTextareaId) { clearLog(); const base = ensureTokenAndPath(); if (!base) return; const content = document.getElementById(sourceTextareaId).value; const body = new URLSearchParams(); body.append("token", base.token); body.append("what", buildFullPath(fileName)); body.append("contents", content); try { const res = await fetch(API + "UploadTextFile", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8" }, body: body.toString() }); const data = await safeJson(res); log(data, false); await getDir(); } catch (e) { log("שגיאה בשמירת קובץ טקסט: " + e.message, false); } } function loadNamedIni() { const name = document.getElementById("iniFileName").value.trim(); if (!name) return alert("נא להזין שם קובץ ini"); loadTextFile(name, "iniContent"); } function saveNamedIni() { const name = document.getElementById("iniFileName").value.trim(); if (!name) return alert("נא להזין שם קובץ ini"); saveTextFile(name, "iniContent"); } function loadNamedExt() { const name = document.getElementById("extFileName").value.trim(); if (!name) return alert("נא להזין שם קובץ ext"); loadTextFile(name, "extCustomContent"); } function saveNamedExt() { const name = document.getElementById("extFileName").value.trim(); if (!name) return alert("נא להזין שם קובץ ext"); saveTextFile(name, "extCustomContent"); } async function getLists() { clearLog(); const token = getToken(); if (!token) return alert("נא להזין טוקן"); try { const url = API + "?token=" + encodeURIComponent(token) + "&action=getLists"; const res = await fetch(url); const data = await safeJson(res); log(data, false); } catch (e) { log("שגיאה בקבלת רשימות: " + e.message, false); } } async function getListEntries() { clearLog(); const token = getToken(); const listName = document.getElementById("listName").value.trim(); if (!token) return alert("נא להזין טוקן"); if (!listName) return alert("נא להזין שם רשימה"); try { const url = API + "?token=" + encodeURIComponent(token) + "&action=getlistEnteres&TzintukimList=" + encodeURIComponent(listName); const res = await fetch(url); const data = await safeJson(res); log(data, false); } catch (e) { log("שגיאה בקבלת מנויי הרשימה: " + e.message, false); } } async function getListLog() { clearLog(); const token = getToken(); const listName = document.getElementById("listName").value.trim(); if (!token) return alert("נא להזין טוקן"); if (!listName) return alert("נא להזין שם רשימה"); try { const url = API + "?token=" + encodeURIComponent(token) + "&action=getLogList&TzintukimList=" + encodeURIComponent(listName); const res = await fetch(url); const data = await safeJson(res); log(data, false); } catch (e) { log("שגיאה בקבלת לוג הרשימה: " + e.message, false); } } async function resetList() { clearLog(); const token = getToken(); const listName = document.getElementById("listName").value.trim(); if (!token) return alert("נא להזין טוקן"); if (!listName) return alert("נא להזין שם רשימה"); if (!confirm("לאפס את הרשימה " + listName + " ?")) return; try { const url = API + "?token=" + encodeURIComponent(token) + "&action=resetList&TzintukimList=" + encodeURIComponent(listName); const res = await fetch(url); const data = await safeJson(res); log(data, false); } catch (e) { log("שגיאה באיפוס רשימה: " + e.message, false); } } function escapeHtml(str) { return String(str) .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); } </script> </body> </html> -
@ben-zion
מה אומר