Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
70b90d1
Improve API creation UI
Piumal1999 Aug 2, 2026
41c1250
Improve tags field
Piumal1999 Aug 2, 2026
3ba0be2
Improve mobile responsiveness
Piumal1999 Aug 3, 2026
8f67c4a
Address comments
Piumal1999 Aug 3, 2026
5e344fd
Correct homepage whitespace
Piumal1999 Aug 3, 2026
a2ae69e
Remove handle from webhook config UI
Piumal1999 Aug 3, 2026
6bfcd66
Correct column order in views table
Piumal1999 Aug 3, 2026
5845d0f
Add save button loading states
Piumal1999 Aug 3, 2026
cea8032
Remove artifact types field from org edit form
Piumal1999 Aug 3, 2026
2965068
Remove unrelated types from api type dropdown
Piumal1999 Aug 3, 2026
8f7362a
Make api-workflows ui consistant with other admin UIs
Piumal1999 Aug 3, 2026
70899a2
Disable save button if mandatory fields are empty
Piumal1999 Aug 3, 2026
ad48813
Improve the alert popup
Piumal1999 Aug 3, 2026
15d22b5
Update the graphql and ws icons
Piumal1999 Aug 3, 2026
bf190f5
Fix image loading issue in custom content
Piumal1999 Aug 3, 2026
dc81236
Fix logout button
Piumal1999 Aug 3, 2026
1739838
Fix mcp button colour
Piumal1999 Aug 3, 2026
1f8d10d
Fix docs page active tab issue
Piumal1999 Aug 3, 2026
9ee85f4
Fix sub menu item width
Piumal1999 Aug 3, 2026
2343011
Use label display names in views form
Piumal1999 Aug 3, 2026
1516648
Improve api type chips
Piumal1999 Aug 3, 2026
2ca6e0d
Fix the ai ready button in mcps
Piumal1999 Aug 3, 2026
79104a8
Fix issues with ai readiness chips
Piumal1999 Aug 3, 2026
8917cbb
Address comments
Piumal1999 Aug 3, 2026
0342e20
Add pagination to apis, mcps
Piumal1999 Aug 3, 2026
58a3ef8
Fix TLS issue
Piumal1999 Aug 3, 2026
4aae45a
Fix api workflows breadcrumb
Piumal1999 Aug 3, 2026
b540165
Address comments
Piumal1999 Aug 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 38 additions & 7 deletions distribution/all-in-one/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,18 @@ services:
# platform-api signs its tokens with RS256; the devportal verifies them
# against jwt_public.pem from the shared keypair volume mounted below
# (auth.local.jwt_public_key in devportal-config.toml). There is no shared
# HMAC secret. tls_skip_verify covers platform-api's self-signed cert.
# HMAC secret.
- APIP_AP_AUTH_LOCAL_PLATFORM_API_URL=https://platform-api:9243
- APIP_AP_AUTH_LOCAL_TLS_SKIP_VERIFY=true
# Trust platform-api's self-signed cert at Node's trust-store level instead of
# skipping verification per client. NODE_EXTRA_CA_CERTS *appends* to Node's
# built-in CAs (public CAs keep working) and applies process-wide, so it covers
# BOTH the auth login client (authController's https.Agent passes no custom
# `ca`, so it honours this) AND the webhook delivery worker
# (src/services/webhooks/deliveryWorker.js), which POSTs events such as
# apikey.generated over Node's default TLS and has no tls_skip_verify hatch of
# its own. That is why APIP_AP_AUTH_LOCAL_TLS_SKIP_VERIFY is no longer set here:
# verification stays ON for every platform-api call, self-signed cert and all.
- NODE_EXTRA_CA_CERTS=/etc/api-portal/certs/cert.pem
# Required — devportal fails closed at startup if either doesn't resolve
# to a 64-char hex string. Set both before `docker compose up`, e.g.:
# export APIP_AP_SECURITY_ENCRYPTION_KEY=$(openssl rand -hex 32)
Expand All @@ -70,11 +79,24 @@ services:
# Same RS256 keypair volume platform-api signs with — the devportal reads
# only jwt_public.pem (0644) from it to verify those tokens.
- platform-api-jwt-keys:/etc/api-portal/keys:ro
# platform-api's self-signed TLS cert, trusted via NODE_EXTRA_CA_CERTS above.
# Subpath mount exposes only the public cert.pem — never the private key.pem
# that also lives in this volume (same pattern as the e2e compose).
- type: volume
source: platform-api-certs
target: /etc/api-portal/certs/cert.pem
read_only: true
volume:
subpath: cert.pem
depends_on:
postgres:
condition: service_healthy
platform-api:
condition: service_healthy
# cert.pem must exist in the certs volume before Node reads NODE_EXTRA_CA_CERTS
# at startup (a missing file is silently ignored with a warning).
platform-api-certgen:
condition: service_completed_successfully
Comment thread
coderabbitai[bot] marked this conversation as resolved.
platform-api-jwtkeygen:
condition: service_completed_successfully

Expand All @@ -85,11 +107,20 @@ services:
entrypoint: ["/bin/sh", "-c"]
command:
- |
[ -f /certs/cert.pem ] && [ -f /certs/key.pem ] && exit 0
openssl req -x509 -newkey rsa:2048 -sha256 -days 365 -nodes \
-keyout /certs/key.pem -out /certs/cert.pem \
-subj "/O=WSO2 API Platform/CN=platform-api" \
-addext "subjectAltName=DNS:localhost,DNS:platform-api,IP:127.0.0.1"
set -e
if [ ! -f /certs/cert.pem ] || [ ! -f /certs/key.pem ]; then
openssl req -x509 -newkey rsa:2048 -sha256 -days 365 -nodes \
-keyout /certs/key.pem -out /certs/cert.pem \
-subj "/O=WSO2 API Platform/CN=platform-api" \
-addext "subjectAltName=DNS:localhost,DNS:platform-api,IP:127.0.0.1"
fi
# platform-api runs as uid 10001 and reads both files. Keep the private key
# owned by that uid and non-world-readable; keep the public cert world-readable
# so api-portal can trust it via NODE_EXTRA_CA_CERTS. Applied every run (like
# platform-api-jwtkeygen) so a cached certs volume gets the same permissions.
chown 10001 /certs/key.pem
chmod 0600 /certs/key.pem
chmod 0644 /certs/cert.pem
volumes:
- platform-api-certs:/certs

Expand Down
27 changes: 22 additions & 5 deletions portals/api-portal/src/controllers/apiContentController.js
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,12 @@ function requestOrigin(req) {
return `${protocol}://${req.get('host')}`;
}

const TRYOUT_CAPABLE_TYPES = new Set([
constants.API_TYPE.WS,
constants.API_TYPE.WEBSUB,
constants.API_TYPE.GRAPHQL,
]);

const loadDocument = async (req, res, next) => {
const { orgName, apiHandle, viewName, docType, docName } = req.params;

Expand All @@ -599,11 +605,14 @@ const loadDocument = async (req, res, next) => {
isAPIDefinition: false,
isWebSocketTryout: false,
isGraphQLTryout: false,
isTryout: false,
};
templateContent.apiType = definitionResponse.apiType;
templateContent.supportsTryout = TRYOUT_CAPABLE_TYPES.has(definitionResponse.apiType);
if (isSpecPage && definitionResponse.swagger) {
const specType = definitionResponse.apiType;
const tryoutEnabled = !!req.query.tryout;
const tryoutEnabled = req.query.tryout === true || req.query.tryout === 'true';
templateContent.isTryout = templateContent.supportsTryout && tryoutEnabled;
if (specType === constants.API_TYPE.WS || specType === constants.API_TYPE.WEBSUB) {
templateContent.asyncapi = JSON.stringify(parseApiDefinitionContent(definitionResponse.swagger));
templateContent.isWebSocketTryout = tryoutEnabled;
Expand Down Expand Up @@ -653,12 +662,20 @@ const loadDocument = async (req, res, next) => {
let templateContent = {
"isAPIDefinition": false,
"isWebSocketTryout": false,
"isGraphQLTryout": false
"isGraphQLTryout": false,
"isTryout": false
};
const definitionResponse = await getAPIDefinition(orgName, viewName, apiHandle);
templateContent.apiType = definitionResponse.apiType;

const tryoutEnabled = req.query.tryout ? true : false;

// Only an explicit tryout=true opts in, and only on the specification page —
// the tryout payload (asyncapi/GraphQL introspection) is loaded only there, so a
// non-spec document must never render the tryout console. Any other query value
// (e.g. tryout=false) is a truthy string and must not enable it.
const isSpecPage = req.originalUrl.includes(constants.FILE_NAME.API_SPECIFICATION_PATH);
const tryoutEnabled = isSpecPage && (req.query.tryout === true || req.query.tryout === 'true');
templateContent.supportsTryout = TRYOUT_CAPABLE_TYPES.has(definitionResponse.apiType);
templateContent.isTryout = templateContent.supportsTryout && tryoutEnabled;
if (definitionResponse.apiType === constants.API_TYPE.WS || definitionResponse.apiType === constants.API_TYPE.WEBSUB) {
templateContent.isWebSocketTryout = tryoutEnabled;
} else if (definitionResponse.apiType === constants.API_TYPE.GRAPHQL) {
Expand All @@ -667,7 +684,7 @@ const loadDocument = async (req, res, next) => {
let apiMetadata = definitionResponse.metaData;

//load API definition
if (req.originalUrl.includes(constants.FILE_NAME.API_SPECIFICATION_PATH)) {
if (isSpecPage) {

if (definitionResponse.apiType === constants.API_TYPE.MCP) {
// The playground reads its server URL from servers[0].url. A
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ const loadAPIApiKeys = async (req, res, next) => {
const images = metaData.apiImageMetadata;
if (images) {
for (const key in images) {
images[key] = `${constants.API_PORTAL_API.orgPath(orgId)}${constants.ROUTE.API_FILE_PATH}${apiId}${constants.API_TEMPLATE_FILE_NAME}${images[key]}`;
images[key] = `${constants.API_PORTAL_API.orgPath(orgId)}${constants.ROUTE.API_FILE_PATH}${metaData.id}${constants.API_TEMPLATE_FILE_NAME}${images[key]}`;
}
}
} else {
Expand Down
12 changes: 11 additions & 1 deletion portals/api-portal/src/controllers/apiWorkflowsController.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const apiWorkflowService = require('../services/apiWorkflowService');
const { config } = require('../config/configLoader');
const logger = require('../config/logger');
const util = require('../utils/util');
const { loadLayoutFromAPI, renderGivenTemplate, renderTemplateFromAPI, rewriteViewStyles, isAiDisabledForPortal } = require('../utils/util');
const { loadLayoutFromAPI, renderGivenTemplate, renderTemplateFromAPI, rewriteViewStyles, isAiDisabledForPortal, resolveAiEnabled } = require('../utils/util');
const constants = require('../utils/constants');
const fs = require('fs');
const path = require('path');
Expand Down Expand Up @@ -114,6 +114,11 @@ const loadAPIWorkflows = async (req, res, next) => {
viewName,
baseUrl: `/${orgName}/views/${viewName}`,
profile,
// Gate the AI Ready chips on the real AI toggle, not the artifact type.
// Set explicitly so the custom-view renderGivenTemplate path (which hardcodes
// aiEnabled: true) also respects it, matching renderTemplateFromAPI. Uses the
// fail-open helper so a transient config-read error doesn't reject the page.
aiEnabled: await resolveAiEnabled(orgId, viewName),
};

const dbLayout = await loadLayoutFromAPI(orgId, viewName);
Expand Down Expand Up @@ -194,6 +199,11 @@ const loadAPIWorkflowDetail = async (req, res, next) => {
viewName,
baseUrl: `/${orgName}/views/${viewName}`,
profile,
// Gate the "Try with AI" button/modal on the real AI toggle. Set explicitly so
// the custom-view renderGivenTemplate path (which hardcodes aiEnabled: true) also
// respects it, matching renderTemplateFromAPI. Uses the fail-open helper so a
// transient config-read error doesn't reject the page.
aiEnabled: await resolveAiEnabled(orgId, viewName),
};

const dbLayout = await loadLayoutFromAPI(orgId, viewName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,12 @@ const loadSettingsPage = async (req, res) => {
}
templateContent.orgLabels = orgLabels;

const labelNameByHandle = new Map(orgLabels.map(l => [l.id, l.displayName]));
templateContent.views = views.map(view => ({
...view,
labelNames: (view.labels || []).map(handle => labelNameByHandle.get(handle) || handle),
}));

let orgPlans = [];
try {
const plansRaw = await subscriptionPlanDao.list(orgId);
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
{{> alert }}

{{!-- ── AGENT PROMPT MODAL ── --}}
{{#if showApiWorkflowsNav}}
{{#if aiEnabled}}
{{#unless (eq apiMetadata.agentVisibility "HIDDEN")}}
{{> api-agent-prompt-modal}}
{{/unless}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,16 @@
{{#if (eq apiMetadata.type "GRAPHQL")}}dp-badge--graphql
{{else if (eq apiMetadata.type "WS")}}dp-badge--ws
{{else if (eq apiMetadata.type "SOAP")}}dp-badge--soap
{{else if (eq apiMetadata.type "WebSubApi")}}dp-badge--ws
{{else}}dp-badge--rest{{/if}}">
{{#if (eq apiMetadata.type "GRAPHQL")}}<i class="bi bi-share"></i> GraphQL
{{else if (eq apiMetadata.type "WS")}}<i class="bi bi-broadcast"></i> WebSocket
{{#if (eq apiMetadata.type "GRAPHQL")}}<img src="/images/graphql-icon.svg" class="dp-badge-icon" alt="" /> GraphQL
{{else if (eq apiMetadata.type "WS")}}<img src="/images/websocket-icon.svg" class="dp-badge-icon" alt="" /> WebSocket
{{else if (eq apiMetadata.type "WebSubApi")}}<i class="bi bi-broadcast-pin"></i> WebSub
{{else if (eq apiMetadata.type "SOAP")}}<i class="bi bi-file-code"></i> SOAP
{{else}}<i class="bi bi-braces"></i> {{apiMetadata.type}}{{/if}}
{{else}}<i class="bi bi-braces"></i> REST{{/if}}
</span>
{{/if}}
{{#if showApiWorkflowsNav}}
{{#if aiEnabled}}
{{#unless (eq apiMetadata.agentVisibility "HIDDEN")}}
<span class="dp-badge dp-badge--ai" title="AI agents can discover and use this API">
<i class="bi bi-robot"></i> AI Ready
Expand Down Expand Up @@ -68,7 +70,7 @@
</a>
{{/if}}
{{/in}}
{{#if showApiWorkflowsNav}}
{{#if aiEnabled}}
{{#unless (eq apiMetadata.agentVisibility "HIDDEN")}}
<button type="button" class="dp-btn dp-btn--ai" data-bs-toggle="modal" data-bs-target="#apiAgentPromptModal">
<i class="bi bi-robot"></i> Try with AI
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<div data-flow-handle="{{flow.handle}}" data-flow-content-type="{{flow.contentType}}">
<div class="flow-detail-section">
{{!-- Breadcrumb --}}
<nav class="dp-breadcrumb">
<nav class="dp-breadcrumb flow-detail-breadcrumb">
<a class="dp-breadcrumb-item" href="{{baseUrl}}/api-workflows">API Workflows</a>
<i class="bi bi-chevron-right dp-breadcrumb-sep"></i>
<span class="dp-breadcrumb-current">{{flow.displayName}}</span>
Expand All @@ -33,11 +33,13 @@
<p>{{flow.description}}</p>
</div>
<div class="flow-header-actions">
{{#if aiEnabled}}
{{#unless (eq flow.agentVisibility "HIDDEN")}}
<button class="common-btn-primary discover-ai-btn" id="openPromptBtn" data-bs-toggle="modal" data-bs-target="#agentPromptModal">
<img src="/images/ai-stars-icon.svg" width="16" height="16" aria-hidden="true" alt="" style="margin-right:6px;vertical-align:middle;">Try with AI
</button>
{{/unless}}
{{/if}}
</div>
</div>

Expand All @@ -58,7 +60,11 @@
{{> alert}}

{{!-- ── AGENT PROMPT MODAL ── --}}
{{#if aiEnabled}}
{{#unless (eq flow.agentVisibility "HIDDEN")}}
{{> agent-prompt-modal}}
{{/unless}}
{{/if}}

{{!-- Hidden container for spec content --}}
<textarea id="specContent" style="display: none;">{{flow.content}}</textarea>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,6 @@
<h1 class="page-title">API Workflows</h1>
<p class="page-desc">Pre-configured workflows let your AI agents reference these best practices to use your APIs correctly, every time — no hallucination, no manual setup.</p>
</div>
{{#if apiWorkflows.length}}
{{#if profile.isAdmin}}
<a href="{{beforeSeparator baseUrl '/views/'}}/settings#cfg-workflows" class="dp-btn dp-btn--primary">
<i class="bi bi-pencil"></i> Manage workflows
</a>
{{/if}}
{{/if}}
</div>

{{#if apiWorkflows.length}}
Expand All @@ -31,12 +24,7 @@
<i class="bi bi-diagram-3"></i>
</span>
<h2 class="dp-empty-title">No workflows yet</h2>
<p class="dp-empty-desc">Create a workflow to encode a multi-step API sequence your AI agents can follow. Published workflows will appear here for every consumer.</p>
{{#if profile.isAdmin}}
<a href="{{beforeSeparator baseUrl '/views/'}}/settings#cfg-workflows" class="dp-btn dp-btn--primary dp-btn--lg" style="margin-top:1.5rem;">
<i class="bi bi-plus"></i> Create workflow
</a>
{{/if}}
<p class="dp-empty-desc">There are no published workflows available right now. Once they're published, they'll appear here for your AI agents to follow.</p>
</div>
{{/if}}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@
{{/if}}
</div>
<div class="wf-card-footer">
{{#if @root.aiEnabled}}
{{#unless (eq this.agentVisibility "HIDDEN")}}
<span class="dp-badge dp-badge--ai" title="AI agents can discover and use this workflow">
<i class="bi bi-robot"></i> AI Ready
</span>
{{/unless}}
{{/if}}
</div>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,13 @@
{{else if (eq type "SOAP")}}dp-badge--soap
{{else if (eq type "WebSubApi")}}dp-badge--ws
{{else}}dp-badge--rest{{/if}}">
{{#if (eq type "GRAPHQL")}}<i class="bi bi-share"></i> GraphQL
{{else if (eq type "WS")}}<i class="bi bi-broadcast"></i> WebSocket
{{#if (eq type "GRAPHQL")}}<img src="/images/graphql-icon.svg" class="dp-badge-icon" alt="" /> GraphQL
{{else if (eq type "WS")}}<img src="/images/websocket-icon.svg" class="dp-badge-icon" alt="" /> WebSocket
{{else if (eq type "WebSubApi")}}<i class="bi bi-broadcast-pin"></i> WebSub
{{else if (eq type "SOAP")}}<i class="bi bi-file-code"></i> SOAP
{{else}}<i class="bi bi-braces"></i> REST{{/if}}
</span>
{{#if ../showApiWorkflowsNav}}
{{#if ../aiEnabled}}
{{#unless (eq agentVisibility "HIDDEN")}}
<span class="dp-badge dp-badge--ai" title="AI agents can discover and use this API">
<i class="bi bi-robot"></i> AI Ready
Expand Down Expand Up @@ -136,7 +136,7 @@
<div class="api-card-footer">
<div class="api-card-footer-left">
<a href="{{../baseUrl}}/api/{{id}}#subscriptionPlans" class="api-plans-link">
<i class="bi bi-tag"></i> {{subscriptionPlans.length}} plans
<i class="bi bi-tag"></i> {{subscriptionPlans.length}} {{conditionalIf (eq subscriptionPlans.length 1) "plan" "plans"}}
</a>
</div>
{{#if hasSubscription}}
Expand Down Expand Up @@ -165,4 +165,5 @@
</div>

<script src='/technical-scripts/listing-cards.js' defer></script>
<script src='/technical-scripts/paginate.js' defer></script>
</section>
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,13 @@
<div class="adoc-nav-group-title">SPECIFICATION</div>
<div class="adoc-nav-items">
<a href="{{../baseDocUrl}}/docs/specification"
class="adoc-nav-item doc-link{{#if ../isAPIDefinition}} adoc-nav-item--active{{/if}}">
class="adoc-nav-item doc-link{{#if ../isAPIDefinition}}{{#unless ../isTryout}} adoc-nav-item--active{{/unless}}{{/if}}">
<i class="bi bi-code-slash"></i>
<span>{{#if (eq ../apiType "Mcp")}}MCP Playground{{else}}API Definition{{/if}}</span>
</a>
{{#if (eq ../apiType "WS")}}
<a href="{{../baseDocUrl}}/docs/specification?tryout=true" class="adoc-nav-item doc-link">
<i class="bi bi-play-circle"></i><span>Tryout</span>
</a>
{{/if}}
{{#if (eq ../apiType "WebSubApi")}}
<a href="{{../baseDocUrl}}/docs/specification?tryout=true" class="adoc-nav-item doc-link">
<i class="bi bi-play-circle"></i><span>Tryout</span>
</a>
{{/if}}
{{#if (eq ../apiType "GRAPHQL")}}
<a href="{{../baseDocUrl}}/docs/specification?tryout=true" class="adoc-nav-item doc-link">
{{#if ../supportsTryout}}
<a href="{{../baseDocUrl}}/docs/specification?tryout=true"
class="adoc-nav-item doc-link{{#if ../isTryout}} adoc-nav-item--active{{/if}}">
<i class="bi bi-play-circle"></i><span>Tryout</span>
</a>
{{/if}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@
{{#pageScripts}}
<script src="/technical-scripts/home-discover.js" defer></script>
{{/pageScripts}}
{{> home showApiWorkflowsNav=showApiWorkflowsNav}}
{{> home aiEnabled=aiEnabled}}
Loading
Loading