Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions src/controllers/apiContentController.js
Original file line number Diff line number Diff line change
Expand Up @@ -877,11 +877,13 @@ function replaceEndpointParams(apiDefinition, prodEndpoint, sandboxEndpoint) {
let servers = [];
if (prodEndpoint.trim().length !== 0) {
servers.push({
description: "Production",
url: prodEndpoint
});
}
if (sandboxEndpoint.trim().length !== 0) {
servers.push({
description: "Sandbox",
url: sandboxEndpoint
});
}
Expand Down
505 changes: 329 additions & 176 deletions src/controllers/applicationsContentController.js

Large diffs are not rendered by default.

27 changes: 27 additions & 0 deletions src/controllers/customContentController.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,19 @@ const loadCustomContent = async (req, res) => {
let html = "";
const { orgName, viewName } = req.params;
let filePath = req.originalUrl.split("/" + orgName + constants.ROUTE.VIEWS_PATH + viewName + "/")[1];

// Skip manage-keys routes - they should be handled by the application controller
// This is a safety check in case the route doesn't match properly
if (filePath && filePath.match(/^applications\/[^/]+\/manage-keys/)) {
logger.warn('Manage-keys route intercepted by catch-all route - this should not happen', {
orgName,
viewName,
filePath,
originalUrl: req.originalUrl
});
res.status(404).send('Not found');
return;
}
if (config.mode === constants.DEV_MODE) {
let templateContent = {};
templateContent[constants.BASE_URL_NAME] = baseURLDev + viewName;
Expand All @@ -50,6 +63,20 @@ const loadCustomContent = async (req, res) => {
let content = {};
try {
filePath = 'pages/' + filePath;
// Normalize application-specific manage-keys paths to the shared manage-keys page
const manageKeysMatch = filePath.match(/^pages\/applications\/[^/]+\/manage-keys/);
if (manageKeysMatch) {
filePath = 'pages/manage-keys';
}
// Check if the file exists before attempting to render
const resolvedPagePath = path.join(process.cwd(), filePrefix + filePath + '/page.hbs');
if (!fs.existsSync(resolvedPagePath)) {
// If it's a manage-keys route that doesn't exist, return 404 or redirect
if (filePath.includes('manage-keys')) {
throw new Error(`Manage keys page not found. This route should be handled by the application controller.`);
}
throw new Error(`Content page not found at ${resolvedPagePath}`);
}
let orgId = await adminDao.getOrgId(orgName);
let markDownFiles = await adminDao.getOrgContent({
orgId: orgId,
Expand Down
5 changes: 4 additions & 1 deletion src/controllers/devportalController.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,10 @@ const generateAPIKeys = async (req, res) => {
const environments = orgDetails?.data?.environments || [];
const apiHandle = await apiDao.getAPIHandle(orgID, req.body.apiId);

requestBody.name = apiHandle + "-" + cpAppID;
if (!requestBody.keyType || ![constants.KEY_TYPE.PRODUCTION, constants.KEY_TYPE.SANDBOX].includes(requestBody.keyType)) {
throw new Error('Invalid or missing keyType. Expected ' + constants.KEY_TYPE.PRODUCTION + ' or ' + constants.KEY_TYPE.SANDBOX + '.');
}
requestBody.name = apiHandle + "-" + cpAppID + "-" + requestBody.keyType;
requestBody.environmentTemplateId = environments.find(env => env.name === 'Production').templateId;
requestBody.applicationId = cpAppID;
delete requestBody.projectID;
Expand Down
17 changes: 17 additions & 0 deletions src/defaultContent/pages/manage-keys/page.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<head>
<link rel="stylesheet" href="/technical-styles/manage-keys.css" />
<link rel="stylesheet" href="/technical-styles/oauth2-key-generation.css" />
<script src="/technical-scripts/oauth2-key-generation.js" defer></script>
<script src="/technical-scripts/api-key-generation.js" defer></script>
</head>
<main class="container-fluid py-3">
{{> manage-keys }}
{{> keys-view }}
{{> keys-modify }}
{{> keys-token }}
{{> keys-instructions }}
{{> alert }}
</main>



26 changes: 20 additions & 6 deletions src/defaultContent/partials/sidebar.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,26 @@
</a>
</div>
{{/in}}
<a href="{{baseUrl}}/applications" class="nav-link" id="applications">
<img
src="https://raw.githubusercontent.com/wso2/docs-bijira/refs/heads/main/en/devportal-theming/applications-icon.svg"
/>
<span class="nav-text">Applications</span>
</a>
<a href="{{baseUrl}}/applications" class="nav-link" id="applications">
<img src="https://raw.githubusercontent.com/wso2/docs-bijira/refs/heads/main/en/devportal-theming/applications-icon.svg" />
<span class="nav-text">Applications</span>
</a>
<div class="submenu applications-submenu" id="applications-submenu">
<a href="#" class="nav-link submenu-link fw-light" id="applications-overview">
<img
src="https://raw.githubusercontent.com/wso2/docs-bijira/refs/heads/main/en/devportal-theming/MenuOverview.svg"
class="submenu-icon"
/>
<span class="nav-text">Overview</span>
</a>
<a href="#" class="nav-link submenu-link fw-light" id="applications-keys">
<img
src="https://raw.githubusercontent.com/wso2/docs-bijira/refs/heads/main/en/devportal-theming/documentation.svg"
class="submenu-icon"
/>
<span class="nav-text">Manage Keys</span>
</a>
</div>
</nav>

<button class="collapse-btn" id="collapseBtn">
Expand Down
198 changes: 198 additions & 0 deletions src/defaultContent/styles/manage-keys.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
/* Manage Keys Page Styles */

.keys-section {
margin-top: 0rem;
}

.key-btn-container {
display: flex;
flex-direction: row;
gap: 0;
margin-bottom: 1.5rem;
border-bottom: 2px solid var(--secondary-bg-color);
background-color: transparent;
}

.key-btn-container .nav-item {
flex: 1;
}

/* Tab Buttons */
.key-btn {
background-color: var(--main-bg-color);
color: var(--main-text-color);
border: 1px solid var(--secondary-bg-color);
border-bottom: none;
font-size: 0.875rem;
font-weight: 500;
padding: 0.75rem 1.5rem;
text-decoration: none;
cursor: pointer;
text-align: center;
transition: all 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
border-radius: 8px 8px 0 0;
width: 100%;
position: relative;
margin-bottom: -2px;
z-index: 0;
}

.key-btn:hover {
background-color: var(--primary-lightest-color);
color: var(--primary-main-color);
border-color: var(--primary-lightest-color);
}

.key-btn.active {
background-color: var(--primary-light-color);
color: var(--light-text-color);
border-color: var(--primary-light-color);
border-bottom: 2px solid var(--primary-light-color);
font-weight: 600;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
z-index: 1;
}

.key-btn:focus {
outline: 2px solid transparent;
box-shadow: 0 0 0 2px var(--primary-lightest-color);
}

.accordion-item {
margin-bottom: 0.75rem;
border: 1px solid var(--secondary-bg-color);
border-radius: 8px !important;
background-color: var(--main-bg-color);
transition: box-shadow 0.2s ease;
}

.accordion-item:hover {
box-shadow: 0 2px 8px rgba(26, 76, 109, 0.08);
}

.accordion-button {
font-weight: 600;
color: var(--main-text-color);
padding: 0.875rem 1rem;
font-size: 0.9rem;
background-color: var(--secondary-bg-color);
border: none;
transition: background-color 0.2s ease, color 0.2s ease;
}

.accordion-button:not(.collapsed) {
background-color: var(--primary-lightest-color);
color: var(--primary-main-color);
border-bottom: 1px solid var(--secondary-bg-color);
}

.accordion-button:focus {
background-color: var(--secondary-bg-color);
border-color: transparent;
}

.accordion-button:focus-visible {
outline: 2px solid var(--primary-main-color);
outline-offset: 2px;
}

.accordion-button:hover {
background-color: var(--primary-lightest-color);
color: var(--primary-main-color);
}

.accordion-button::after {
transition: transform 0.25s ease;
}

#headingOne.accordion-header,
#headingTwo.accordion-header,
#headingThree.accordion-header,
#headingFour.accordion-header {
margin-bottom: 0;
}

.accordion-body {
padding: 1.25rem;
background-color: var(--main-bg-color);
border-top: none;
min-height: 30vh;
max-height: 60vh;
overflow-y: auto;
overflow-x: hidden;
}

.accordion-body::-webkit-scrollbar {
width: 6px;
}

.accordion-body::-webkit-scrollbar-track {
background: var(--secondary-bg-color);
border-radius: 3px;
}

.accordion-body::-webkit-scrollbar-thumb {
background: var(--primary-light-color);
border-radius: 3px;
}

.accordion-body::-webkit-scrollbar-thumb:hover {
background: var(--primary-main-color);
}

.accordion-body .row {
margin-bottom: 1rem;
}

.accordion-body .col-form-label {
font-weight: 500;
color: var(--main-text-color);
padding-top: 0.5rem;
font-size: 0.875rem;
}

.accordion-body .form-control {
display: block;
width: 100%;
border: 1px solid var(--secondary-bg-color);
border-radius: 6px;
padding: 0.5rem 0.75rem;
font-size: 0.875rem;
background-color: var(--main-bg-color);
color: var(--main-text-color);
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

.accordion-body .form-control:focus {
border-color: var(--primary-main-color);
outline: 2px solid var(--primary-main-color);
outline-offset: 2px;
}

.accordion-body .input-group {
display: flex;
flex-wrap: wrap;
align-items: stretch;
width: 100%;
}

/* Table Styling in Accordion */
.app-table {
border-collapse: separate;
border-spacing: 0;
}

.app-table-container {
border-radius: 6px;
overflow: hidden;
border: 1px solid var(--secondary-bg-color);
}

/* Key Action Buttons Row */
.key-action-buttons-row {
display: flex;
flex-direction: row;
gap: 0.75rem;
align-items: center;
flex-wrap: wrap;
}
6 changes: 1 addition & 5 deletions src/pages/application/page.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,8 @@
{{> application-modal }}
{{> sdk-drawer }}

{{!-- Modals for oauth2 buttons --}}
{{!-- Keys modals now served from manage-keys page --}}
{{> keys-view }}
{{> keys-modify }}
{{> keys-token }}
{{!-- {{> keys-generate }} --}}
{{> keys-instructions }}

{{> alert }}
{{!-- {{> oauth2-key-generation}} --}}
Expand Down
20 changes: 10 additions & 10 deletions src/pages/application/partials/api-key.hbs
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
<div class="modal custom-modal" id="apiKeyModal-{{subID}}">
<div class="modal custom-modal" id="apiKeyModal-{{subID}}-{{keyType}}">
<div class="modal-dialog modal-xl d-flex justify-content-center align-items-center" role="document">
<div class="modal-content custom-modal-content">
<div class="custom-modal-header">
<h2 class="custom-modal-title m-0">API Key</h2>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"
onclick="closeModal('apiKeyModal-{{subID}}')"></button>
onclick="closeModal('apiKeyModal-{{subID}}-{{keyType}}')"></button>
</div>
<div class="custom-modal-body">
<div class="container">
<div class="row">
<div>
<div class="info-title pb-2" id="scopeTitle-{{subID}}">Scopes for the API Key</div>
<div class="input-scopes" id="scopeContainer-{{subID}}" data-scopes="">
<div class="info-title pb-2" id="scopeTitle-{{subID}}-{{keyType}}">Scopes for the API Key</div>
<div class="input-scopes" id="scopeContainer-{{subID}}-{{keyType}}" data-scopes="">
</div>
<div class="info-box d-flex mb-4" id="apiKeyInfo-{{subID}}">
<div class="info-box d-flex mb-4" id="apiKeyInfo-{{subID}}-{{keyType}}">
<div class="info-content">
<div class="info-title">Copy the API Key</div>
<p class="info-text">
Expand All @@ -25,19 +25,19 @@
<i class="bi bi-info-circle"></i>
</div>
</div>
<div id="apiKeyCard-{{subID}}" class="token-card">
<pre id="token_apiKeyText-{{subID}}" class="token-text"></pre>
<div id="apiKeyCard-{{subID}}-{{keyType}}" class="token-card">
<pre id="token_apiKeyText-{{subID}}-{{keyType}}" class="token-text"></pre>
<button id="copyButton_apiKeyText" class="copy-button" aria-label="Copy token"
type="button" onclick="copyToken('apiKeyText-{{subID}}')">
type="button" onclick="copyToken('apiKeyText-{{subID}}-{{keyType}}')">
<i class="bi bi-copy"></i>
</button>
</div>
<div class="d-flex justify-content-end mt-4 mb-3">
<button class="common-btn-outlined pr-3 me-3" type="button"
onClick="closeModal('apiKeyModal-{{subID}}')">
onClick="closeModal('apiKeyModal-{{subID}}-{{keyType}}')">
Done
</button>
<button class="common-btn-primary btn-sm me-1 {{#if @root.isReadOnlyMode}}disabled{{/if}}" id="generateAPIKeyBtn-{{subID}}"
<button class="common-btn-primary btn-sm me-1 {{#if @root.isReadOnlyMode}}disabled{{/if}}" id="generateAPIKeyBtn-{{subID}}-{{keyType}}"
onclick="">
<span class="button-normal-state">
<i class="bi bi-key me-1"></i> Generate
Expand Down
Loading