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 .
Service Selector
Partner Connectors
AI (NIM via Vault)
Admin Editor
Proxy path: /connectors/<partner>/…
Auth: Bearer Tenant Key
Allowlist: method + path_prefix
Select Partner (opens partner website)
Select a partner…
Perch Mobile — Wireless/data plans — https://perchmobile.app/
VDO Mobile (VDOMobile Apps) — Mobile app + PWA platform — https://www.vdomobile.com/
CareFlowIQ (Milliman CareFlowIQ) — Clinical intelligence pipeline — https://careflowiq.com/
Wanido — Workforce analytics — https://wanido.com/
CorePLUS — Security + managed security services — https://www.coreplus.net/
NVIDIA Inception — AI startup ecosystem — https://www.nvidia.com/en-us/startups/
JetFacilities (The JET Group) — Tier 3 data centers — https://www.jetfacilities.com/
Juice Financial (Praxell) — Digital payments rails — https://juicefin.com/
rapid! (Rapid PayCard) — Paycards + disbursements — https://www.rapidpaycard.com/
Selecting a partner opens their main URL in a new tab.
Reload from Vault
Proxy Examples
Models: GET /v1/models
Chat: POST /v1/chat/completions
Audit: SQLite
Chat prompt
Uses Default AI Model above unless overridden below.
Run Chat
Refresh Models
Show last /v1/models response
empty
Auth: Bearer Admin Key
Partners: POST /admin/partners
Routes: GET/POST/DELETE /admin/connectors/routes
Access: GET/POST/DELETE /admin/connectors/access
Admin Status
This Admin Editor calls the exact Vault routes defined in your FastAPI service (vault/vault.py ).
If requests fail in WordPress iframe, host this file on the same domain/port as the Vault or enable CORS server-side.
Load Admin Data
Refresh NIM Audit
Refresh Connector Audit
Partners (Upsert)
0 loaded
Upsert Partner (POST /admin/partners)
Required: key , name , website_url .
Optional: category , api_base_url , docs_url , notes .
This endpoint sets is_active=1 on upsert.
notes (optional)
Save Partner
Fill from Selected Partner
Loaded Partners (GET /partners)
Read-only list from /partners . Admin upserts will be reflected after refresh.
key
name
category
website
api_base_url
Refresh Partners
Connector Routes Allowlist
0 loaded
Add/Enable Route (POST /admin/connectors/routes)
Required JSON: partner_key , method , path_prefix .
Vault normalizes: method uppercased; path_prefix forced to start with / ; trailing * stripped.
path_prefix
Example allowlist entries: GET /healthz , POST /v1/events .
Save Route
Refresh Routes
Disable Route (DELETE /admin/connectors/routes)
Query params: partner_key , method , path_prefix .
Disable Using Fields Above
Loaded Routes (GET /admin/connectors/routes)
Use the filters below to reduce results.
partner_key
method
path_prefix
active
updated_at
Refresh Routes
Tenant ↔ Partner Access
0 loaded
Upsert Access (POST /admin/connectors/access)
Required JSON: tenant_id , partner_key .
Optional: is_active (default true).
is_active
true
false
Save Access
Refresh Access
Disable Access (DELETE /admin/connectors/access)
Query params: tenant_id , partner_key .
Disable Using Fields Above
Loaded Access (GET /admin/connectors/access)
tenant_id
partner_key
active
updated_at
Refresh Access
Audits
not loaded
NIM Audit (GET /admin/audit)
Connector Audit (GET /admin/connectors/audit)
Activity Log
Copy
Select All
Pop-out
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();