/** * Admin Panel: Azure Service Hub v2 (GET/POST /admin/azure-services*). * * An ADAPTER REGISTRY (what this repo has real code for -- see * azure_integrations.py's ADAPTER_TYPES, verified against the repository, * not invented) plus administrator-REGISTERED INSTANCES of those adapters * (repositories.py's integration_instances/integration_health_checks). * * Deliberately does NOT collapse status into one green dot: each instance * shows configured/enabled/last-check-outcome as separate badges, because * they're genuinely different facts (an integration can be configured but * disabled, or enabled but its last check was unhealthy). "Remove * registration" (this page only), "disable" (CDPI stops using it), and * "the real Azure resource" are three distinct, never-conflated actions -- * this page has no code path that can delete an actual Azure resource. * * Resource discovery is real Azure Resource Manager (azure-mgmt-resource), * only for integrations whose adapter declares a known azure_resource_type * (cosmos_db, blob_storage) and only once subscription_id+resource_group * are set in that instance's config -- every other case reports a clear * "not supported"/"scope required" message, never a bare empty list that * could be misread as "zero resources exist". No create/update/delete * resource operation is implemented for any adapter in this pass -- stated * here, not hidden. */ function AzureServiceHub(props) { var api = props.api; var permissions = props.permissions || []; var viewState = React.useState({ screen: "list", integrationId: null }); var view = viewState[0]; var setView = viewState[1]; function openDetail(id) { setView({ screen: "detail", integrationId: id }); } function backToList() { setView({ screen: "list", integrationId: null }); } return (

Azure Service Hub

The registry of Azure (and Key Vault / Application Insights) integrations this repo has real, verified code for, each as a registered instance with its own configuration, health history, and (where genuinely supported) real Azure resource discovery. Statuses are shown as distinct facts, not collapsed into one green indicator.

{window.CDPI_CONFIG.mode === "mock" && (
Mock mode: no real secrets, network access, or Azure Resource Manager credentials exist in this browser-only demo -- every check reports "unknown" and every resource discovery reports "not supported", honestly, same as a real unprovisioned deployment would. Switch config.js to mode:"real" against a deployed backend to see actual status.
)} {view.screen === "list" && } {view.screen === "detail" && }
); } // --------------------------------------------------------------------------- // Status vocabulary helpers -- shared between the list and detail views so // the same outcome always renders the same badge. // --------------------------------------------------------------------------- function outcomeBadgeClass(outcome) { if (outcome === "healthy") return "badge-good"; if (outcome === "unhealthy") return "badge-bad"; if (outcome === "degraded") return "badge-neutral"; return "badge-neutral"; // unknown / null } function outcomeLabel(outcome) { return outcome || "never checked"; } // --------------------------------------------------------------------------- // Inventory list // --------------------------------------------------------------------------- function AzureIntegrationsList(props) { var api = props.api; var permissions = props.permissions; var onOpenDetail = props.onOpenDetail; var canRegister = permissions.indexOf("azure.register") !== -1; var state = React.useState({ loading: true, error: null, integrations: [], plannedAdapters: [] }); var data = state[0]; var setData = state[1]; var showAddState = React.useState(false); var showAdd = showAddState[0]; var setShowAdd = showAddState[1]; function load(bypassCache) { if (bypassCache) window.CDPI_AZURE_CACHE.invalidatePrefix("azure-service-hub:"); setData(function (d) { return Object.assign({}, d, { loading: true, error: null }); }); Promise.all([ window.CDPI_AZURE_CACHE.get("azure-service-hub:integrations", function () { return api.listAzureIntegrations(); }), window.CDPI_AZURE_CACHE.get("azure-service-hub:adapters", function () { return api.listAzureAdapters(); }), ]) .then(function (results) { var registeredAdapterTypes = results[0].integrations.map(function (i) { return i.adapter_type; }); var planned = results[1].adapters.filter(function (a) { return a.status === "planned" && registeredAdapterTypes.indexOf(a.adapter_type) === -1; }); setData({ loading: false, error: null, integrations: results[0].integrations, plannedAdapters: planned }); }) .catch(function (err) { setData(function (d) { return Object.assign({}, d, { loading: false, error: err.message }); }); }); } React.useEffect(function () { load(false); }, []); return (
{canRegister && }
{data.error &&
Couldn't load integrations: {data.error}
} {data.loading &&

Loading…

} {!data.loading && !data.error && (
{data.integrations.map(function (integration) { return (
{integration.display_name}
{integration.category} · {integration.environment}
{integration.configured ? "configured" : "not configured"} {integration.enabled ? "enabled" : "disabled"} {outcomeLabel(integration.last_connection_outcome)} {integration.resource_discovery_supported && resource discovery supported}
{integration.last_health_checked_at && (

Last checked: {new Date(integration.last_health_checked_at).toLocaleString()}

)}
); })} {!data.integrations.length && !data.plannedAdapters.length &&

No integrations registered yet.

} {data.plannedAdapters.map(function (adapter) { return (
{adapter.name}
{adapter.category}
planned -- not implemented

{adapter.description}

); })}
)} {showAdd && }
); } // --------------------------------------------------------------------------- // Add Integration wizard: pick adapter type -> non-secret config -> validate // -> register (always starts disabled -- "enable only after required // validation succeeds" is enforced by the backend's enable endpoint, not // just this form's UX). // --------------------------------------------------------------------------- function AddIntegrationModal(props) { var api = props.api; var onClose = props.onClose; var onSaved = props.onSaved; var adaptersState = React.useState({ loading: true, error: null, adapters: [] }); var adapters = adaptersState[0]; var setAdapters = adaptersState[1]; var formState = React.useState({ adapter_type: "", integration_id: "", display_name: "", environment: "", subscription_id: "", resource_group: "", }); var form = formState[0]; var setForm = formState[1]; var validationState = React.useState(null); // {valid, errors} var validation = validationState[0]; var setValidation = validationState[1]; var saveState = React.useState({ saving: false, validating: false, error: null }); var save = saveState[0]; var setSave = saveState[1]; React.useEffect(function () { api.listAzureAdapters() .then(function (r) { setAdapters({ loading: false, error: null, adapters: r.adapters }); }) .catch(function (err) { setAdapters({ loading: false, error: err.message, adapters: [] }); }); }, []); function setField(key, value) { setForm(function (f) { return Object.assign({}, f, { [key]: value }); }); } var selectedAdapter = adapters.adapters.filter(function (a) { return a.adapter_type === form.adapter_type; })[0] || null; var supportsDiscovery = selectedAdapter && selectedAdapter.capabilities.resource_discovery; function handleValidate() { if (!form.adapter_type) return; setSave(function (s) { return Object.assign({}, s, { validating: true, error: null }); }); api.validateAzureIntegrationConfig(form.adapter_type) .then(function (r) { setValidation(r); setSave(function (s) { return Object.assign({}, s, { validating: false }); }); }) .catch(function (err) { setSave({ saving: false, validating: false, error: err.message }); }); } function handleSubmit(e) { e.preventDefault(); var config = {}; if (form.subscription_id) config.subscription_id = form.subscription_id; if (form.resource_group) config.resource_group = form.resource_group; setSave({ saving: true, validating: false, error: null }); api.registerAzureIntegration(form.integration_id, form.adapter_type, form.display_name, form.environment, config) .then(onSaved) .catch(function (err) { setSave({ saving: false, validating: false, error: err.message }); }); } return (

Add Azure Integration

{adapters.error &&
{adapters.error}
} {selectedAdapter && selectedAdapter.status === "planned" && (
{selectedAdapter.description} This adapter is planned but not implemented in this repository -- registration is not available.
)} {selectedAdapter && selectedAdapter.status !== "planned" && (

{selectedAdapter.description}

Required secret(s) ({selectedAdapter.config_schema.filter(function (f) { return f.secret; }).map(function (f) { return f.key; }).join(", ") || "none"}) must already be set via your .env file or Key Vault -- this form never accepts a secret value directly.

)} {supportsDiscovery && (

Optional -- only needed for this adapter's real Azure Resource Manager resource discovery:

)} {form.adapter_type && (
)} {validation && (
{validation.valid ? "Configuration valid -- required secrets are present." : validation.errors.join(" ")}
)} {save.error &&
{save.error}
}
); } // --------------------------------------------------------------------------- // Service Details: Overview / Configuration / Resources / Diagnostics // --------------------------------------------------------------------------- function AzureIntegrationDetail(props) { var api = props.api; var permissions = props.permissions; var integrationId = props.integrationId; var onBack = props.onBack; var tabState = React.useState("overview"); // overview | configuration | resources | diagnostics var tab = tabState[0]; var setTab = tabState[1]; var state = React.useState({ loading: true, error: null, detail: null }); var data = state[0]; var setData = state[1]; var actionState = React.useState({ busy: false, error: null, result: null }); var action = actionState[0]; var setAction = actionState[1]; var canConfigRead = permissions.indexOf("azure.config.read") !== -1; var canManage = permissions.indexOf("azure.manage") !== -1; var canEnable = permissions.indexOf("azure.enable") !== -1; var canConfigWrite = permissions.indexOf("azure.config.write") !== -1; var canDiscover = permissions.indexOf("azure.resources.discover") !== -1; var canRegister = permissions.indexOf("azure.register") !== -1; function load() { setData(function (d) { return Object.assign({}, d, { loading: true, error: null }); }); api.getAzureIntegration(integrationId) .then(function (result) { setData({ loading: false, error: null, detail: result }); }) .catch(function (err) { setData(function (d) { return Object.assign({}, d, { loading: false, error: err.message }); }); }); } React.useEffect(function () { if (canConfigRead) load(); }, [integrationId]); function handleCheck() { setAction({ busy: true, error: null, result: null }); api.checkAzureIntegration(integrationId) .then(function (result) { setAction({ busy: false, error: null, result: result }); load(); }) .catch(function (err) { setAction({ busy: false, error: err.message, result: null }); }); } function handleSetEnabled(enabled) { setAction({ busy: true, error: null, result: null }); api.setAzureIntegrationEnabled(integrationId, enabled) .then(function () { setAction({ busy: false, error: null, result: null }); load(); }) .catch(function (err) { setAction({ busy: false, error: err.message, result: null }); }); } function handleRemove() { if (!window.confirm("Remove this integration's CDPI registration? This does NOT delete, stop, or otherwise affect the real Azure resource (if any) -- it only removes CDPI's record of it.")) return; setAction({ busy: true, error: null, result: null }); api.removeAzureIntegration(integrationId) .then(onBack) .catch(function (err) { setAction({ busy: false, error: err.message, result: null }); }); } if (!canConfigRead) { return (
Your account doesn't have the 'azure.config.read' permission required to view integration detail.
); } if (data.error) return (
Couldn't load integration: {data.error}
); if (data.loading) return

Loading…

; var instance = data.detail.instance; var adapter = data.detail.adapter; return (

{instance.display_name}

{instance.enabled ? "enabled" : "disabled"} {outcomeLabel(instance.last_connection_outcome)} {instance.environment}
{action.error &&
{action.error}
} {tab === "overview" && (

{adapter.description}

Source module: {adapter.source_module} · Category: {adapter.category} · Auth: {(adapter.auth_methods || []).join(", ")}

{canManage && } {canEnable && !instance.enabled && } {canEnable && instance.enabled && } {canRegister && }
{action.result && (
{action.result.outcome}{action.result.error_category ? " (" + action.result.error_category + ")" : ""}: {action.result.detail}
)} {!instance.enabled && !action.result && (

Enabling requires at least one recorded "healthy" connection check -- run a check first.

)} {!action.result && instance.last_failure && (
Last failure ({new Date(instance.last_failure.checked_at).toLocaleString()}): {instance.last_failure.outcome} {instance.last_failure.error_category ? " (" + instance.last_failure.error_category + ")" : ""}: {instance.last_failure.detail}
)}
)} {tab === "configuration" && ( )} {tab === "resources" && ( )} {tab === "diagnostics" && ( )} {tab === "history" && ( )}
); } function AzureIntegrationHistoryTab(props) { var api = props.api; var integrationId = props.integrationId; var canRollback = props.canRollback; var onRolledBack = props.onRolledBack; var state = React.useState({ loading: true, error: null, history: [] }); var data = state[0]; var setData = state[1]; var rollbackState = React.useState({ busy: false, error: null }); var rollback = rollbackState[0]; var setRollback = rollbackState[1]; function load() { setData(function (d) { return Object.assign({}, d, { loading: true, error: null }); }); api.getAzureIntegrationConfigHistory(integrationId, 50) .then(function (result) { setData({ loading: false, error: null, history: result.history }); }) .catch(function (err) { setData(function (d) { return Object.assign({}, d, { loading: false, error: err.message }); }); }); } React.useEffect(function () { load(); }, [integrationId]); function handleRollback(historyId) { if (!window.confirm("Roll back to this configuration snapshot? This creates a NEW config version (history is never overwritten) and will require revalidation before it can be enabled again.")) return; setRollback({ busy: true, error: null }); api.rollbackAzureIntegrationConfig(integrationId, historyId) .then(function () { setRollback({ busy: false, error: null }); load(); onRolledBack(); }) .catch(function (err) { setRollback({ busy: false, error: err.message }); }); } if (data.error) return
{data.error}
; if (data.loading) return

Loading…

; return (

Every registration, configuration change, activation/deactivation, rollback, and registration removal for this integration -- distinct from the Diagnostics tab's connection-check history (that's "did a live connection succeed"; this is "what changed and who changed it"). Never contains a secret value.

{rollback.error &&
{rollback.error}
}
{data.history.map(function (h) { var canRollbackHere = canRollback && (h.operation === "registered" || h.operation === "config_updated"); return ( ); })} {!data.history.length && }
WhenOperationActorConfig version
{h.recorded_at ? new Date(h.recorded_at).toLocaleString() : "—"} {h.operation} {h.actor} {(h.safe_summary || {}).config_version ?? "—"} {canRollbackHere && }
No configuration history recorded yet.
); } function AzureIntegrationConfigTab(props) { var api = props.api; var integrationId = props.integrationId; var instance = props.instance; var adapter = props.adapter; var secretsConfigured = props.secretsConfigured; var canWrite = props.canWrite; var onSaved = props.onSaved; var nonSecretFields = adapter.config_schema.filter(function (f) { return !f.secret; }); var formState = React.useState(function () { var initial = {}; nonSecretFields.forEach(function (f) { initial[f.key] = (instance.config || {})[f.key] || ""; }); return initial; }); var form = formState[0]; var setForm = formState[1]; var saveState = React.useState({ saving: false, error: null, saved: false }); var save = saveState[0]; var setSave = saveState[1]; function setField(key, value) { setForm(function (f) { return Object.assign({}, f, { [key]: value }); }); setSave({ saving: false, error: null, saved: false }); } function handleSave() { setSave({ saving: true, error: null, saved: false }); api.updateAzureIntegrationConfig(integrationId, form, instance.config_version) .then(function () { setSave({ saving: false, error: null, saved: true }); onSaved(); }) .catch(function (err) { setSave({ saving: false, error: err.message, saved: false }); }); } return (

Secrets (presence only -- values are never shown or stored here)

{Object.keys(secretsConfigured).map(function (key) { return {key}; })} {!Object.keys(secretsConfigured).length && This adapter has no required secrets.}

Secret values are configured via your .env file or Key Vault (see secrets_loader.py) -- never through this UI or API.

{!!nonSecretFields.length && (

Non-secret configuration (config version {instance.config_version})

{nonSecretFields.map(function (f) { return ( ); })} {canWrite && (
)} {save.error &&
{save.error}
} {save.saved &&
Saved.
}
)}
); } function AzureIntegrationResourcesTab(props) { var api = props.api; var integrationId = props.integrationId; var adapter = props.adapter; var canDiscover = props.canDiscover; var state = React.useState({ loading: false, error: null, result: null }); var data = state[0]; var setData = state[1]; function handleDiscover() { setData({ loading: true, error: null, result: null }); api.discoverAzureResources(integrationId) .then(function (result) { setData({ loading: false, error: null, result: result }); }) .catch(function (err) { setData({ loading: false, error: err.message, result: null }); }); } if (!adapter.capabilities.resource_discovery) { return

Resource discovery is not supported for this adapter type -- this repo has no azure_resource_type mapping for it. This is a stated gap, not an error.

; } return ( {canDiscover && (
)} {!canDiscover &&

Your account doesn't have the 'azure.resources.discover' permission.

} {data.error &&
{data.error}
} {data.result && !data.result.supported && (
{data.result.error}
)} {data.result && data.result.supported && ( data.result.error ?
{data.result.error}
: (
{data.result.resources.map(function (r) { return ; })} {!data.result.resources.length && }
NameTypeLocationProvisioning state
{r.name}{r.type}{r.location}{r.provisioning_state}
No resources found in this subscription/resource group.
) )}

No create/update/delete operation is implemented for any adapter in this release -- discovery is read-only, and the real Azure resource (if any) is never modified from this page.

); } function AzureIntegrationDiagnosticsTab(props) { var api = props.api; var integrationId = props.integrationId; var state = React.useState({ loading: true, error: null, checks: [] }); var data = state[0]; var setData = state[1]; function load() { setData(function (d) { return Object.assign({}, d, { loading: true, error: null }); }); api.getAzureIntegrationHealthHistory(integrationId, 50) .then(function (result) { setData({ loading: false, error: null, checks: result.checks }); }) .catch(function (err) { setData(function (d) { return Object.assign({}, d, { loading: false, error: err.message }); }); }); } React.useEffect(function () { load(); }, [integrationId]); if (data.error) return
{data.error}
; if (data.loading) return

Loading…

; return (
{data.checks.map(function (c, i) { return ( ); })} {!data.checks.length && }
Checked atOutcomeError categoryDetailDuration
{c.checked_at ? new Date(c.checked_at).toLocaleString() : "—"} {c.outcome} {c.error_category || "—"} {c.detail} {c.duration_ms} ms
No connection checks recorded yet.
); }