VAULT

MEDTells Vault — Service Portal (Master Admin)

MEDTells Vault — Service Portal (Master Admin)

Disconnected
This portal talks to your Vault at http://localhost:8080. Admin editing uses these Vault routes: POST /admin/partners, GET/POST/DELETE /admin/connectors/routes, GET/POST/DELETE /admin/connectors/access, GET /admin/audit, GET /admin/connectors/audit.
If this portal is hosted by the Vault container, use a same-origin URL (recommended) so browser CORS doesn’t block requests.
Used for /v1/*, /partners, and /connectors/* calls.
Required to edit partners/routes/access and view admin audits. Don’t embed this key into public WordPress pages.
Used for Quick Chat (override on AI tab if needed).

Service Selector

Partner Connectors
AI (NIM via Vault)
Admin Editor
Proxy path: /connectors/<partner>/…
Auth: Bearer Tenant Key
Allowlist: method + path_prefix
Selecting a partner opens their main URL in a new tab.
Filters the partner cards below (local).
Calls Vault connector proxy with Tenant Key. Path must begin with /connectors/.

Activity Log

WordPress iframe note: clipboard APIs can be blocked. Use Select All (then Ctrl/Cmd+C) or Pop-out.
`); w.document.close(); log("Opened pop-out log window."); }); $("btnClear")?.addEventListener("click", () => { $("log").textContent = ""; }); // Core buttons: Health, Models, Chat async function doHealth(){ try{ const data = await httpJson("/healthz", { method:"GET" }); setConnected(true, "Connected"); log("Healthz OK: " + JSON.stringify(data, null, 2)); }catch(e){ setConnected(false, "Disconnected"); log("Healthz failed: " + e.message); } } async function doModels(){ const tk = $("tenantKey")?.value || ""; try{ const data = await httpJson("/v1/models", { method:"GET", headers: { ...bearerHeader(tk) } }); const arr = data?.data || data?.models || data?.results || data; $("modelsDump").textContent = JSON.stringify(data, null, 2); $("modelsTag").textContent = Array.isArray(arr) ? (arr.length + " models") : "loaded"; log("Loaded /v1/models"); return data; }catch(e){ log("List Models failed: " + e.message); throw e; } } async function doChat(){ const tk = $("tenantKey")?.value || ""; const prompt = $("chatPrompt")?.value || "Hello"; const override = ($("chatModel")?.value || "").trim(); const model = override || ($("defaultModel")?.value || "").trim(); const temperature = parseFloat(($("chatTemp")?.value || "0.2").toString()) || 0.2; try{ let useModel = model; if(!useModel){ const m = await doModels().catch(()=>null); const first = (m && m.data && m.data[0] && (m.data[0].id || m.data[0].model)) ? (m.data[0].id || m.data[0].model) : ""; useModel = first || ""; } const payload = { model: useModel || undefined, messages: [{ role:"user", content: prompt }], temperature }; const data = await httpJson("/v1/chat/completions", { method:"POST", headers: { "Content-Type":"application/json", ...bearerHeader(tk) }, body: JSON.stringify(payload) }); log("Chat OK:\n" + JSON.stringify(data, null, 2)); }catch(e){ log("Quick Chat failed: " + e.message); } } $("btnHealth")?.addEventListener("click", doHealth); $("btnModels")?.addEventListener("click", () => doModels().catch(()=>{})); $("btnChat")?.addEventListener("click", doChat); $("btnRunModels")?.addEventListener("click", () => doModels().catch(()=>{})); $("btnRunChat")?.addEventListener("click", doChat); // Connect & Load = health + partners + (optional) models async function loadPartnersFromVault(){ const tk = $("tenantKey")?.value || ""; const data = await httpJson("/partners", { method:"GET", headers:{ ...bearerHeader(tk) } }); const partners = data?.partners || []; renderPartnerGrid(partners.length ? partners : DEFAULT_PARTNERS); // update dropdown (keep first option) const sel = $("selectedPartner"); if(sel){ const current = sel.value; const firstOpt = sel.querySelector('option[value=""]') ? sel.querySelector('option[value=""]') : null; sel.innerHTML = ""; if(firstOpt){ sel.appendChild(firstOpt); }else{ const o = document.createElement("option"); o.value=""; o.textContent="Select a partner…"; sel.appendChild(o); } partners.forEach(p => { const o = document.createElement("option"); o.value = p.key; o.setAttribute("data-url", p.website_url || p.url || ""); o.textContent = `${p.name} — ${p.category || "partner"} — ${p.website_url || p.url || ""}`.trim(); sel.appendChild(o); }); // restore selection if still present if(current) sel.value = current; } log(`Loaded ${partners.length} partners from /partners`); return partners; } $("btnConnect")?.addEventListener("click", async () => { $("vaultBaseKbd").textContent = baseUrl(); await doHealth(); await loadPartnersFromVault().catch(e => { renderPartnerGrid(DEFAULT_PARTNERS); log("Partner reload failed; showing static list. Error: " + e.message); }); }); $("btnReloadPartners")?.addEventListener("click", async () => { await loadPartnersFromVault().catch(e => log("Reload from Vault failed: " + e.message)); }); $("btnOpenProxyHelp")?.addEventListener("click", () => { const b = baseUrl(); log( "Proxy examples:\n" + `- GET ${b}/connectors/perch_mobile/healthz\n` + `- POST ${b}/connectors/careflowiq/v1/events (if allowlisted)\n` + "Note: allowlists live in connector_routes and are enforced by the Vault." ); }); // Proxy test async function doProxy(method){ const tk = $("tenantKey")?.value || ""; let path = ($("proxyPath")?.value || "").trim(); if(!path.startsWith("/connectors/")){ log("Proxy path must start with /connectors/ — e.g. /connectors/perch_mobile/healthz"); return; } try{ const opts = { method, headers:{ ...bearerHeader(tk) } }; if(method !== "GET"){ let body = ($("proxyJson")?.value || "").trim(); if(!body) body = "{}"; opts.headers["Content-Type"] = "application/json"; opts.body = body; } const data = await httpJson(path, opts); log(`Proxy ${method} OK (${path}):\n` + JSON.stringify(data, null, 2)); }catch(e){ log(`Proxy ${method} failed (${path}): ` + e.message); } } $("btnProxyGET")?.addEventListener("click", () => doProxy("GET")); $("btnProxyPOST")?.addEventListener("click", () => doProxy("POST")); // ---------- ADMIN EDITOR (exact routes from vault.py) ---------- function requireAdminKey(){ const ak = ($("adminKey")?.value || "").trim(); if(!ak){ log("Admin API Key is required for Admin actions."); throw new Error("admin key required"); } return ak; } async function adminListPartnersForTable(){ // /partners is non-admin; it returns active partners (key,name,category,website_url,api_base_url,docs_url,notes) const tk = ($("tenantKey")?.value || "").trim(); // bearer optional; some deployments might require tenant even for /partners const data = await httpJson("/partners", { method:"GET", headers:{ ...bearerHeader(tk) } }); const partners = data?.partners || []; const tb = $("partnersTable")?.querySelector("tbody"); if(tb){ tb.innerHTML = partners.map(p => ` ${escapeHtml(p.key)} ${escapeHtml(p.name)} ${escapeHtml(p.category || "")} ${p.website_url ? `${escapeHtml(p.website_url)}` : ""} ${escapeHtml(p.api_base_url || "")} `).join(""); } $("partnersTag").textContent = partners.length + " loaded"; return partners; } async function adminUpsertPartner(){ const ak = requireAdminKey(); const payload = { key: ($("ap_key")?.value || "").trim(), name: ($("ap_name")?.value || "").trim(), category: ($("ap_category")?.value || "").trim() || undefined, website_url: ($("ap_website")?.value || "").trim(), api_base_url: ($("ap_api")?.value || "").trim() || undefined, docs_url: ($("ap_docs")?.value || "").trim() || undefined, notes: ($("ap_notes")?.value || "").trim() || undefined, }; if(!payload.key || !payload.name || !payload.website_url){ log("Partner upsert missing required fields: key, name, website_url"); return; } await httpJson("/admin/partners", { method:"POST", headers:{ "Content-Type":"application/json", ...bearerHeader(ak) }, body: JSON.stringify(payload) }); log("Admin: partner upsert OK for key=" + payload.key); await adminListPartnersForTable().catch(()=>{}); await loadPartnersFromVault().catch(()=>{}); } function fillPartnerFormFromSelected(){ const sel = $("selectedPartner"); const k = (sel?.value || "").trim(); if(!k){ log("Select a partner from the dropdown first."); return; } // try to parse fields from option text (fallback) const opt = sel.selectedOptions?.[0]; const url = opt?.getAttribute("data-url") || ""; const label = (opt?.textContent || "").trim(); $("ap_key").value = k; $("ap_name").value = label.split("—")[0]?.trim() || k; $("ap_website").value = url; log("Filled Partner form from selected dropdown entry: " + k); } async function adminListRoutes(){ const ak = requireAdminKey(); const partner_key = ($("routesFilterPartner")?.value || "").trim(); const limit = parseInt(($("routesLimit")?.value || "500"), 10) || 500; const qs = new URLSearchParams(); if(partner_key) qs.set("partner_key", partner_key); qs.set("limit", String(limit)); const data = await httpJson("/admin/connectors/routes?" + qs.toString(), { method:"GET", headers:{ ...bearerHeader(ak) } }); const routes = data?.routes || []; const tb = $("routesTable")?.querySelector("tbody"); if(tb){ tb.innerHTML = routes.map(r => ` ${escapeHtml(r.partner_key)} ${escapeHtml(r.method)} ${escapeHtml(r.path_prefix)} ${r.is_active ? 'true' : 'false'} ${escapeHtml(r.updated_at || "")} `).join(""); } $("routesTag").textContent = routes.length + " loaded"; log("Admin: loaded connector routes (" + routes.length + ")"); return routes; } async function adminUpsertRoute(){ const ak = requireAdminKey(); const payload = { partner_key: ($("ar_partner")?.value || "").trim(), method: ($("ar_method")?.value || "").trim(), path_prefix: ($("ar_path")?.value || "").trim() }; if(!payload.partner_key || !payload.method || !payload.path_prefix){ log("Route upsert missing fields: partner_key, method, path_prefix"); return; } await httpJson("/admin/connectors/routes", { method:"POST", headers:{ "Content-Type":"application/json", ...bearerHeader(ak) }, body: JSON.stringify(payload) }); log(`Admin: route upsert OK (${payload.partner_key} ${payload.method} ${payload.path_prefix})`); await adminListRoutes().catch(()=>{}); } async function adminDisableRoute(){ const ak = requireAdminKey(); const partner_key = ($("ar_partner")?.value || "").trim(); const method = ($("ar_method")?.value || "").trim(); const path_prefix = ($("ar_path")?.value || "").trim(); if(!partner_key || !method || !path_prefix){ log("Disable route requires partner_key, method, path_prefix (use the fields above)."); return; } const qs = new URLSearchParams({ partner_key, method, path_prefix }); await httpJson("/admin/connectors/routes?" + qs.toString(), { method:"DELETE", headers:{ ...bearerHeader(ak) } }); log(`Admin: route disabled (${partner_key} ${method} ${path_prefix})`); await adminListRoutes().catch(()=>{}); } async function adminListAccess(){ const ak = requireAdminKey(); const tenant_id = ($("accessFilterTenant")?.value || "").trim(); const partner_key = ($("accessFilterPartner")?.value || "").trim(); const limit = parseInt(($("accessLimit")?.value || "1000"), 10) || 1000; const qs = new URLSearchParams(); if(tenant_id) qs.set("tenant_id", tenant_id); if(partner_key) qs.set("partner_key", partner_key); qs.set("limit", String(limit)); const data = await httpJson("/admin/connectors/access?" + qs.toString(), { method:"GET", headers:{ ...bearerHeader(ak) } }); const access = data?.access || []; const tb = $("accessTable")?.querySelector("tbody"); if(tb){ tb.innerHTML = access.map(a => ` ${escapeHtml(a.tenant_id)} ${escapeHtml(a.partner_key)} ${a.is_active ? 'true' : 'false'} ${escapeHtml(a.updated_at || "")} `).join(""); } $("accessTag").textContent = access.length + " loaded"; log("Admin: loaded connector access (" + access.length + ")"); return access; } async function adminUpsertAccess(){ const ak = requireAdminKey(); const payload = { tenant_id: ($("aa_tenant")?.value || "").trim(), partner_key: ($("aa_partner")?.value || "").trim(), is_active: ($("aa_active")?.value || "true") === "true" }; if(!payload.tenant_id || !payload.partner_key){ log("Access upsert missing fields: tenant_id, partner_key"); return; } await httpJson("/admin/connectors/access", { method:"POST", headers:{ "Content-Type":"application/json", ...bearerHeader(ak) }, body: JSON.stringify(payload) }); log(`Admin: access upsert OK (${payload.tenant_id} -> ${payload.partner_key}, active=${payload.is_active})`); await adminListAccess().catch(()=>{}); } async function adminDisableAccess(){ const ak = requireAdminKey(); const tenant_id = ($("aa_tenant")?.value || "").trim(); const partner_key = ($("aa_partner")?.value || "").trim(); if(!tenant_id || !partner_key){ log("Disable access requires tenant_id and partner_key (use the fields above)."); return; } const qs = new URLSearchParams({ tenant_id, partner_key }); await httpJson("/admin/connectors/access?" + qs.toString(), { method:"DELETE", headers:{ ...bearerHeader(ak) } }); log(`Admin: access disabled (${tenant_id} -> ${partner_key})`); await adminListAccess().catch(()=>{}); } async function adminAudit(){ const ak = requireAdminKey(); const limit = parseInt(($("auditLimit")?.value || "50"), 10) || 50; const data = await httpJson("/admin/audit?limit=" + encodeURIComponent(String(limit)), { method:"GET", headers:{ ...bearerHeader(ak) } }); $("auditDump").textContent = JSON.stringify(data, null, 2); $("auditTag").textContent = "loaded"; log("Admin: loaded NIM audit"); } async function adminConnectorAudit(){ const ak = requireAdminKey(); const limit = parseInt(($("cAuditLimit")?.value || "100"), 10) || 100; const data = await httpJson("/admin/connectors/audit?limit=" + encodeURIComponent(String(limit)), { method:"GET", headers:{ ...bearerHeader(ak) } }); $("cAuditDump").textContent = JSON.stringify(data, null, 2); $("auditTag").textContent = "loaded"; log("Admin: loaded connector audit"); } // Admin button wiring $("btnAdminLoad")?.addEventListener("click", async () => { try{ await adminListPartnersForTable(); await adminListRoutes(); await adminListAccess(); await adminAudit().catch(()=>{}); await adminConnectorAudit().catch(()=>{}); log("Admin: all data loaded."); }catch(e){ log("Admin load failed: " + e.message); } }); $("btnPartnersRefresh")?.addEventListener("click", () => adminListPartnersForTable().catch(e => log("Partners refresh failed: " + e.message))); $("btnPartnerUpsert")?.addEventListener("click", () => adminUpsertPartner().catch(e => log("Partner upsert failed: " + e.message))); $("btnPartnerFillSelected")?.addEventListener("click", fillPartnerFormFromSelected); $("btnRouteList")?.addEventListener("click", () => adminListRoutes().catch(e => log("Routes refresh failed: " + e.message))); $("btnRoutesRefresh2")?.addEventListener("click", () => adminListRoutes().catch(e => log("Routes refresh failed: " + e.message))); $("btnRouteUpsert")?.addEventListener("click", () => adminUpsertRoute().catch(e => log("Route upsert failed: " + e.message))); $("btnRouteDisable")?.addEventListener("click", () => adminDisableRoute().catch(e => log("Route disable failed: " + e.message))); $("btnAccessList")?.addEventListener("click", () => adminListAccess().catch(e => log("Access refresh failed: " + e.message))); $("btnAccessRefresh2")?.addEventListener("click", () => adminListAccess().catch(e => log("Access refresh failed: " + e.message))); $("btnAccessUpsert")?.addEventListener("click", () => adminUpsertAccess().catch(e => log("Access upsert failed: " + e.message))); $("btnAccessDisable")?.addEventListener("click", () => adminDisableAccess().catch(e => log("Access disable failed: " + e.message))); $("btnAccessFillSelected")?.addEventListener("click", () => { const sel = $("selectedPartner"); const k = (sel?.value || "").trim(); if(!k){ log("Select a partner from the dropdown first."); return; } $("aa_partner").value = k; log("Filled partner_key for Access from selected partner: " + k); }); $("btnAdminAudit")?.addEventListener("click", () => adminAudit().catch(e => log("Admin audit failed: " + e.message))); $("btnAdminConnectorAudit")?.addEventListener("click", () => adminConnectorAudit().catch(e => log("Connector audit failed: " + e.message))); $("btnAuditRefresh2")?.addEventListener("click", () => adminAudit().catch(e => log("Admin audit failed: " + e.message))); $("btnCAuditRefresh2")?.addEventListener("click", () => adminConnectorAudit().catch(e => log("Connector audit failed: " + e.message))); // Initial render function init(){ $("vaultBaseKbd").textContent = baseUrl(); renderPartnerGrid(DEFAULT_PARTNERS); log("Portal loaded. Admin tab uses exact Vault routes. No password in this frontend."); } init();
Your Language
Scroll to Top