Loading portal-gui/src/app/(app)/apis/[id]/apiPage.module.scss +34 −9 Original line number Diff line number Diff line Loading @@ -44,6 +44,28 @@ margin: 0.4rem 0; } .operationsLabel { font-size: 0.75rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: #999; margin-top: 1rem; } .operations { margin: 0.4rem 0 0 1.1rem; display: flex; flex-direction: column; gap: 0.35rem; li { font-size: 0.85rem; color: var(--dark-grey); line-height: 1.4; } } .badge { background: linear-gradient(45deg, #f159225b 0%, #fdbb135b 100%); color: var(--blue-color); Loading Loading @@ -102,36 +124,41 @@ } } /* Hide top description block from Swagger UI */ .infos { display: flex; flex-direction: column; gap: 0.5rem; } :global(.swagger-ui button) { all: revert; cursor: pointer; box-sizing: border-box; } :global(.swagger-ui .information-container) { display: none; } /* Hide top header */ :global(.swagger-ui .topbar) { display: none; } /* Hide Schemas section */ :global(.swagger-ui .models) { display: none; } /* Hide server selection */ :global(.swagger-ui .scheme-container) { display: none; } /* Hide Filter Field */ :global(.swagger-ui .filter-container) { display: none; } /* JSON keys */ :global(.swagger-ui .hljs-attr) { color: #ffffff !important; } /* JSON punctuation like :, {}, [] */ :global(.swagger-ui .example) { color: #ffffff !important; } Loading @@ -144,13 +171,11 @@ :global(.swagger-ui .highlight-code span) { color: #ffffff !important; } /* Colons and brackets */ :global(.swagger-ui .language-json) { color: #ffffff !important; } /* Make sure responsesare also affected */ :global(.swagger-ui .headerline) { color: #ffffff !important; } Loading portal-gui/src/app/(app)/apis/[id]/page.tsx +23 −43 Original line number Diff line number Diff line "use client"; import "swagger-ui-react/swagger-ui.css"; import styles from "./apiPage.module.scss"; import { global } from "styled-jsx/css"; import { useEffect, useState } from "react"; import { useParams, useRouter } from "next/navigation"; Loading @@ -14,42 +13,26 @@ import ErrorComponent from "@/app/components/Error/Error"; import Loader from "@/app/components/Loader/Loader"; import { format } from "date-fns"; import { StatusChip } from "@/app/components/Chip/StatusChip"; // import { StatusChip } from "@/app/components/Chip/StatusChip"; import SwaggerUI from "swagger-ui-react"; // import { oeg } from "@/app/utils/constants"; import { filterOpenApiByTag } from "@/app/utils/openapi-utils"; const ApiPage = () => { const {id} = useParams(); const router = useRouter(); const [api, setApi] = useState<IApi | null>(null); // const [swaggerSpec, setSwaggerSpec] = useState<any | null>(null); const [isLoading, setIsLoading] = useState(true); useEffect(() => { const fetchApi = async() => { try { // Fetch the hardcoded API details const res = await fetch(`/api/apis/${id}`); if (!res.ok) throw new Error("Not found"); const data = await res.json(); setApi(data); // Fetch the OpenAPI JSON // const specRes = await fetch("/api/swagger"); // const spec = await specRes.json(); // Filter spec by the API category (?) // const filteredSpec = filterOpenApiByTag(spec, data.category || data.title); // delete filteredSpec.info.description; // console.log("filteredSpec = " + filteredSpec); // setSwaggerSpec(filteredSpec); } catch (err) { setApi(null); // setSwaggerSpec(null); } finally { setIsLoading(false); } Loading @@ -76,16 +59,6 @@ const ApiPage = () => { <p className={styles.category}>{api.category}</p> <div className={styles.functionalities}> {api.functionalities.map((func: string, idx: number) => ( // <span key={func} className={styles.badge}> // {func.toLocaleUpperCase()} // </span> <StatusChip key={idx} status={func} /> ))} </div> {/* <Button className={buttons.primary}>Activate</Button> */} <div className={styles.descriptionBox}> <h3>Description</h3> <p>{api.description}</p> Loading @@ -97,25 +70,32 @@ const ApiPage = () => { <strong>Version:</strong> {api.version} </p> <p className={styles.meta}> <strong>Protocol:</strong> {api.protocol} </p> <p className={styles.meta}> <strong>Live URL:</strong> {api.liveUrl} <strong>Protocol:</strong> REST </p> <p className={styles.meta}> <strong>Released Date:</strong>{" "} {format(api.published, "dd/MM/yyyy")} </p> <p className={styles.meta}> <strong>Status:</strong> {api.status} <strong>Status:</strong> Active </p> </div> <div className={styles.howItWorks}> <h3>How it works</h3> <p>{api.instructions}</p> <p className={styles.operationsLabel}>Available operations</p> {api.functionalities.some((f) => f.length > 30) ? ( <ul className={styles.operations}> {api.functionalities.map((func: string, idx: number) => ( <li key={idx}>{func}</li> ))} </ul> ) : null} {/* fallback chips — re-enable when short labels are needed {api.functionalities.some((f) => f.length <= 30) && ( <div className={styles.functionalities}> {api.functionalities.map((func: string, idx: number) => ( <StatusChip key={idx} status={func} /> ))} </div> )} */} </div> </div> <Divider size="sm" label="API Documentation" color="#004a8d" /> Loading @@ -124,8 +104,8 @@ const ApiPage = () => { <SwaggerUI spec={swaggerSpec} docExpansion="none" // collapse endpoints defaultModelsExpandDepth={-1} // hides Schemas section defaultModelExpandDepth={-1} // hides model details defaultModelsExpandDepth={-1} defaultModelExpandDepth={-1} displayRequestDuration={false} displayOperationId={false} tryItOutEnabled={true} Loading @@ -138,8 +118,8 @@ const ApiPage = () => { <SwaggerUI url="/api/swagger" docExpansion="list" defaultModelsExpandDepth={-1} // hides Schemas section defaultModelExpandDepth={-1} // hides model details defaultModelsExpandDepth={-1} defaultModelExpandDepth={-1} displayOperationId={false} displayRequestDuration={false} filter={api.tag} Loading portal-gui/src/app/(app)/page.tsx +15 −15 Original line number Diff line number Diff line Loading @@ -8,7 +8,7 @@ import { Input, InputGroup, Pagination, SegmentedControl, // SegmentedControl, } from "rsuite"; import styles from "../styles/page.module.scss"; import buttons from "../styles/buttons.module.scss"; Loading @@ -17,7 +17,7 @@ import { IApi } from "../utils/interfaces"; import { FilterSection } from "../components/Filter/FilterSection"; import { FilterChips } from "../components/Filter/FilterChips"; import { ApiCard } from "../components/Card/ApiCard"; import { sortOptions } from "../utils/constants"; // import { sortOptions } from "../utils/constants"; import { SidebarFilter } from "../components/Filter/SidebarFilter"; import { useFilters } from "../hooks/useFilters"; Loading @@ -34,21 +34,21 @@ export default function Home() { setPageSize, search, setSearch, sort, setSort, //sort, //setSort, selectedCategories, //selectedProviders, selectedFunctionalities, //selectedFunctionalities, setSelectedCategories, setSelectedProviders, setSelectedFunctionalities, //setSelectedFunctionalities, categoryCounts, //providerCounts, functionalityCounts, //functionalityCounts, resetFilters, categories, //providers, functionalities, //functionalities, removeFilter, sumSelectedFilters, } = useFilters(apis); Loading @@ -72,16 +72,16 @@ export default function Home() { <SidebarFilter categories={categories} //providers={providers} functionalities={functionalities} //functionalities={functionalities} selectedCategories={selectedCategories} //selectedProviders={selectedProviders} selectedFunctionalities={selectedFunctionalities} //selectedFunctionalities={selectedFunctionalities} setSelectedCategories={setSelectedCategories} //setSelectedProviders={setSelectedProviders} setSelectedFunctionalities={setSelectedFunctionalities} //setSelectedFunctionalities={setSelectedFunctionalities} categoryCounts={categoryCounts} //providerCounts={providerCounts} functionalityCounts={functionalityCounts} //functionalityCounts={functionalityCounts} resetFilters={resetFilters} /> ); Loading @@ -107,7 +107,7 @@ export default function Home() { filters={{ categories: selectedCategories, //providers: selectedProviders, functionalities: selectedFunctionalities, //functionalities: selectedFunctionalities, }} removeFilter={removeFilter} /> Loading @@ -126,7 +126,7 @@ export default function Home() { {filtersIcon} Filters </Button> </Badge> <div className={styles.sortBox}> {/* <div className={styles.sortBox}> <h6>Sort by:</h6> <SegmentedControl defaultValue={sort} Loading @@ -134,7 +134,7 @@ export default function Home() { data={sortOptions} onChange={(v) => setSort(v as string)} /> </div> </div> */} </div> <div className={styles.cards}> Loading portal-gui/src/app/api/apis/[id]/route.ts 0 → 100644 +43 −0 Original line number Diff line number Diff line import { NextResponse } from "next/server"; import { apiData } from "@/app/utils/tableHelpers"; import { oeg } from "@/app/utils/constants"; export async function GET( _req: Request, { params }: { params: Promise<{ id: string }> } ) { try { const { id } = await params; const api = apiData.find((a) => a.id === id); if (!api) { return NextResponse.json({ error: "Not found" }, { status: 404 }); } let liveFunctionalities: string[] = []; try { const specRes = await fetch(oeg.jsonUrl, { cache: "no-store" }); const spec = await specRes.json(); for (const methods of Object.values(spec.paths as Record<string, any>)) { for (const operation of Object.values(methods as Record<string, any>)) { const op = operation as any; if (op.tags?.includes(api.tag) && op.summary) { liveFunctionalities.push(op.summary); } } } } catch { liveFunctionalities = []; } const functionalities = liveFunctionalities.length > 0 ? liveFunctionalities : api.functionalities; return NextResponse.json({ ...api, functionalities }); } catch { return NextResponse.json({ error: "Failed to load API" }, { status: 500 }); } } portal-gui/src/app/api/proxy/[...path]/route.ts +31 −52 Original line number Diff line number Diff line // import { error } from "console"; import { NextResponse, NextRequest } from "next/server"; import { oeg } from "@/app/utils/constants"; async function proxy( req: NextRequest) { req: NextRequest, { params }: { params: Promise<{ path: string[] }> } ) { try { // console.log("Request = " + req.nextUrl); const urlparts1 = req.nextUrl.toString().split('/'); const urlparts2 = urlparts1[urlparts1.length - 1].split('?'); const tag = urlparts2[0]; // console.log("Tag = " + tag); const { path } = await params; const search = req.nextUrl.search; // console.log("search = " + search); const targetUrl = `${oeg.baseUrl}${tag}${search}`; // console.log("Proxying to: " + targetUrl); const targetUrl = `${oeg.baseUrl}/${path.join("/")}${search}`; const backendRes = await fetch( targetUrl, { const backendRes = await fetch(targetUrl, { method: req.method, headers: { "Content-Type": req.headers.get("content-type") || "application/json", Accept: req.headers.get("accept") || "application/json" Accept: req.headers.get("accept") || "application/json", }, body: req.method !== "GET" && req.method !== "HEAD" ? await req.text() : undefined } ); : undefined, }); // Return raw response return new NextResponse(backendRes.body, { status: backendRes.status, headers: { "Content-Type": backendRes.headers.get("content-type") || "application/json" } "Content-Type": backendRes.headers.get("content-type") || "application/json", }, }); } catch (err) { console.error("Proxy error:", err); return NextResponse.json( { error: "Proxy failed" }, { status: 500 } ); }; return NextResponse.json({ error: "Proxy failed" }, { status: 500 }); } } export const GET = proxy; Loading Loading
portal-gui/src/app/(app)/apis/[id]/apiPage.module.scss +34 −9 Original line number Diff line number Diff line Loading @@ -44,6 +44,28 @@ margin: 0.4rem 0; } .operationsLabel { font-size: 0.75rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: #999; margin-top: 1rem; } .operations { margin: 0.4rem 0 0 1.1rem; display: flex; flex-direction: column; gap: 0.35rem; li { font-size: 0.85rem; color: var(--dark-grey); line-height: 1.4; } } .badge { background: linear-gradient(45deg, #f159225b 0%, #fdbb135b 100%); color: var(--blue-color); Loading Loading @@ -102,36 +124,41 @@ } } /* Hide top description block from Swagger UI */ .infos { display: flex; flex-direction: column; gap: 0.5rem; } :global(.swagger-ui button) { all: revert; cursor: pointer; box-sizing: border-box; } :global(.swagger-ui .information-container) { display: none; } /* Hide top header */ :global(.swagger-ui .topbar) { display: none; } /* Hide Schemas section */ :global(.swagger-ui .models) { display: none; } /* Hide server selection */ :global(.swagger-ui .scheme-container) { display: none; } /* Hide Filter Field */ :global(.swagger-ui .filter-container) { display: none; } /* JSON keys */ :global(.swagger-ui .hljs-attr) { color: #ffffff !important; } /* JSON punctuation like :, {}, [] */ :global(.swagger-ui .example) { color: #ffffff !important; } Loading @@ -144,13 +171,11 @@ :global(.swagger-ui .highlight-code span) { color: #ffffff !important; } /* Colons and brackets */ :global(.swagger-ui .language-json) { color: #ffffff !important; } /* Make sure responsesare also affected */ :global(.swagger-ui .headerline) { color: #ffffff !important; } Loading
portal-gui/src/app/(app)/apis/[id]/page.tsx +23 −43 Original line number Diff line number Diff line "use client"; import "swagger-ui-react/swagger-ui.css"; import styles from "./apiPage.module.scss"; import { global } from "styled-jsx/css"; import { useEffect, useState } from "react"; import { useParams, useRouter } from "next/navigation"; Loading @@ -14,42 +13,26 @@ import ErrorComponent from "@/app/components/Error/Error"; import Loader from "@/app/components/Loader/Loader"; import { format } from "date-fns"; import { StatusChip } from "@/app/components/Chip/StatusChip"; // import { StatusChip } from "@/app/components/Chip/StatusChip"; import SwaggerUI from "swagger-ui-react"; // import { oeg } from "@/app/utils/constants"; import { filterOpenApiByTag } from "@/app/utils/openapi-utils"; const ApiPage = () => { const {id} = useParams(); const router = useRouter(); const [api, setApi] = useState<IApi | null>(null); // const [swaggerSpec, setSwaggerSpec] = useState<any | null>(null); const [isLoading, setIsLoading] = useState(true); useEffect(() => { const fetchApi = async() => { try { // Fetch the hardcoded API details const res = await fetch(`/api/apis/${id}`); if (!res.ok) throw new Error("Not found"); const data = await res.json(); setApi(data); // Fetch the OpenAPI JSON // const specRes = await fetch("/api/swagger"); // const spec = await specRes.json(); // Filter spec by the API category (?) // const filteredSpec = filterOpenApiByTag(spec, data.category || data.title); // delete filteredSpec.info.description; // console.log("filteredSpec = " + filteredSpec); // setSwaggerSpec(filteredSpec); } catch (err) { setApi(null); // setSwaggerSpec(null); } finally { setIsLoading(false); } Loading @@ -76,16 +59,6 @@ const ApiPage = () => { <p className={styles.category}>{api.category}</p> <div className={styles.functionalities}> {api.functionalities.map((func: string, idx: number) => ( // <span key={func} className={styles.badge}> // {func.toLocaleUpperCase()} // </span> <StatusChip key={idx} status={func} /> ))} </div> {/* <Button className={buttons.primary}>Activate</Button> */} <div className={styles.descriptionBox}> <h3>Description</h3> <p>{api.description}</p> Loading @@ -97,25 +70,32 @@ const ApiPage = () => { <strong>Version:</strong> {api.version} </p> <p className={styles.meta}> <strong>Protocol:</strong> {api.protocol} </p> <p className={styles.meta}> <strong>Live URL:</strong> {api.liveUrl} <strong>Protocol:</strong> REST </p> <p className={styles.meta}> <strong>Released Date:</strong>{" "} {format(api.published, "dd/MM/yyyy")} </p> <p className={styles.meta}> <strong>Status:</strong> {api.status} <strong>Status:</strong> Active </p> </div> <div className={styles.howItWorks}> <h3>How it works</h3> <p>{api.instructions}</p> <p className={styles.operationsLabel}>Available operations</p> {api.functionalities.some((f) => f.length > 30) ? ( <ul className={styles.operations}> {api.functionalities.map((func: string, idx: number) => ( <li key={idx}>{func}</li> ))} </ul> ) : null} {/* fallback chips — re-enable when short labels are needed {api.functionalities.some((f) => f.length <= 30) && ( <div className={styles.functionalities}> {api.functionalities.map((func: string, idx: number) => ( <StatusChip key={idx} status={func} /> ))} </div> )} */} </div> </div> <Divider size="sm" label="API Documentation" color="#004a8d" /> Loading @@ -124,8 +104,8 @@ const ApiPage = () => { <SwaggerUI spec={swaggerSpec} docExpansion="none" // collapse endpoints defaultModelsExpandDepth={-1} // hides Schemas section defaultModelExpandDepth={-1} // hides model details defaultModelsExpandDepth={-1} defaultModelExpandDepth={-1} displayRequestDuration={false} displayOperationId={false} tryItOutEnabled={true} Loading @@ -138,8 +118,8 @@ const ApiPage = () => { <SwaggerUI url="/api/swagger" docExpansion="list" defaultModelsExpandDepth={-1} // hides Schemas section defaultModelExpandDepth={-1} // hides model details defaultModelsExpandDepth={-1} defaultModelExpandDepth={-1} displayOperationId={false} displayRequestDuration={false} filter={api.tag} Loading
portal-gui/src/app/(app)/page.tsx +15 −15 Original line number Diff line number Diff line Loading @@ -8,7 +8,7 @@ import { Input, InputGroup, Pagination, SegmentedControl, // SegmentedControl, } from "rsuite"; import styles from "../styles/page.module.scss"; import buttons from "../styles/buttons.module.scss"; Loading @@ -17,7 +17,7 @@ import { IApi } from "../utils/interfaces"; import { FilterSection } from "../components/Filter/FilterSection"; import { FilterChips } from "../components/Filter/FilterChips"; import { ApiCard } from "../components/Card/ApiCard"; import { sortOptions } from "../utils/constants"; // import { sortOptions } from "../utils/constants"; import { SidebarFilter } from "../components/Filter/SidebarFilter"; import { useFilters } from "../hooks/useFilters"; Loading @@ -34,21 +34,21 @@ export default function Home() { setPageSize, search, setSearch, sort, setSort, //sort, //setSort, selectedCategories, //selectedProviders, selectedFunctionalities, //selectedFunctionalities, setSelectedCategories, setSelectedProviders, setSelectedFunctionalities, //setSelectedFunctionalities, categoryCounts, //providerCounts, functionalityCounts, //functionalityCounts, resetFilters, categories, //providers, functionalities, //functionalities, removeFilter, sumSelectedFilters, } = useFilters(apis); Loading @@ -72,16 +72,16 @@ export default function Home() { <SidebarFilter categories={categories} //providers={providers} functionalities={functionalities} //functionalities={functionalities} selectedCategories={selectedCategories} //selectedProviders={selectedProviders} selectedFunctionalities={selectedFunctionalities} //selectedFunctionalities={selectedFunctionalities} setSelectedCategories={setSelectedCategories} //setSelectedProviders={setSelectedProviders} setSelectedFunctionalities={setSelectedFunctionalities} //setSelectedFunctionalities={setSelectedFunctionalities} categoryCounts={categoryCounts} //providerCounts={providerCounts} functionalityCounts={functionalityCounts} //functionalityCounts={functionalityCounts} resetFilters={resetFilters} /> ); Loading @@ -107,7 +107,7 @@ export default function Home() { filters={{ categories: selectedCategories, //providers: selectedProviders, functionalities: selectedFunctionalities, //functionalities: selectedFunctionalities, }} removeFilter={removeFilter} /> Loading @@ -126,7 +126,7 @@ export default function Home() { {filtersIcon} Filters </Button> </Badge> <div className={styles.sortBox}> {/* <div className={styles.sortBox}> <h6>Sort by:</h6> <SegmentedControl defaultValue={sort} Loading @@ -134,7 +134,7 @@ export default function Home() { data={sortOptions} onChange={(v) => setSort(v as string)} /> </div> </div> */} </div> <div className={styles.cards}> Loading
portal-gui/src/app/api/apis/[id]/route.ts 0 → 100644 +43 −0 Original line number Diff line number Diff line import { NextResponse } from "next/server"; import { apiData } from "@/app/utils/tableHelpers"; import { oeg } from "@/app/utils/constants"; export async function GET( _req: Request, { params }: { params: Promise<{ id: string }> } ) { try { const { id } = await params; const api = apiData.find((a) => a.id === id); if (!api) { return NextResponse.json({ error: "Not found" }, { status: 404 }); } let liveFunctionalities: string[] = []; try { const specRes = await fetch(oeg.jsonUrl, { cache: "no-store" }); const spec = await specRes.json(); for (const methods of Object.values(spec.paths as Record<string, any>)) { for (const operation of Object.values(methods as Record<string, any>)) { const op = operation as any; if (op.tags?.includes(api.tag) && op.summary) { liveFunctionalities.push(op.summary); } } } } catch { liveFunctionalities = []; } const functionalities = liveFunctionalities.length > 0 ? liveFunctionalities : api.functionalities; return NextResponse.json({ ...api, functionalities }); } catch { return NextResponse.json({ error: "Failed to load API" }, { status: 500 }); } }
portal-gui/src/app/api/proxy/[...path]/route.ts +31 −52 Original line number Diff line number Diff line // import { error } from "console"; import { NextResponse, NextRequest } from "next/server"; import { oeg } from "@/app/utils/constants"; async function proxy( req: NextRequest) { req: NextRequest, { params }: { params: Promise<{ path: string[] }> } ) { try { // console.log("Request = " + req.nextUrl); const urlparts1 = req.nextUrl.toString().split('/'); const urlparts2 = urlparts1[urlparts1.length - 1].split('?'); const tag = urlparts2[0]; // console.log("Tag = " + tag); const { path } = await params; const search = req.nextUrl.search; // console.log("search = " + search); const targetUrl = `${oeg.baseUrl}${tag}${search}`; // console.log("Proxying to: " + targetUrl); const targetUrl = `${oeg.baseUrl}/${path.join("/")}${search}`; const backendRes = await fetch( targetUrl, { const backendRes = await fetch(targetUrl, { method: req.method, headers: { "Content-Type": req.headers.get("content-type") || "application/json", Accept: req.headers.get("accept") || "application/json" Accept: req.headers.get("accept") || "application/json", }, body: req.method !== "GET" && req.method !== "HEAD" ? await req.text() : undefined } ); : undefined, }); // Return raw response return new NextResponse(backendRes.body, { status: backendRes.status, headers: { "Content-Type": backendRes.headers.get("content-type") || "application/json" } "Content-Type": backendRes.headers.get("content-type") || "application/json", }, }); } catch (err) { console.error("Proxy error:", err); return NextResponse.json( { error: "Proxy failed" }, { status: 500 } ); }; return NextResponse.json({ error: "Proxy failed" }, { status: 500 }); } } export const GET = proxy; Loading