Skip to content

Commit

Permalink
feat: Implements swagger API documentation (#161)
Browse files Browse the repository at this point in the history
  • Loading branch information
gruyaume authored Oct 25, 2023
1 parent ca46067 commit c704edd
Show file tree
Hide file tree
Showing 16 changed files with 2,513 additions and 101 deletions.
36 changes: 36 additions & 0 deletions app/(api)/api/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"use client";

import React, { useState, useEffect } from "react";
import dynamic from "next/dynamic";

const DynamicReactSwagger = dynamic(() => import("./react-swagger"), {
ssr: false,
loading: () => <div>Loading...</div>,
});

export default function IndexPage() {
const [spec, setSpec] = useState(null);

useEffect(() => {
async function fetchSpecs() {
const response = await fetch("/api/getSwaggerDocs");

if (response.ok) {
const fetchedSpec = await response.json();
setSpec(fetchedSpec);
} else {
console.error("Failed to fetch API docs");
}
}

fetchSpecs();
}, []);

if (!spec) return <div>Loading...</div>;

return (
<section className="container">
<DynamicReactSwagger spec={spec} />
</section>
);
}
14 changes: 14 additions & 0 deletions app/(api)/api/react-swagger.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"use client";

import SwaggerUI from "swagger-ui-react";
import "swagger-ui-react/swagger-ui.css";

type Props = {
spec: Record<string, any>;
};

function ReactSwagger({ spec }: Props) {
return <SwaggerUI spec={spec} />;
}

export default ReactSwagger;
85 changes: 85 additions & 0 deletions app/(network)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
"use client";
import "../globals.scss";
import { Inter } from "next/font/google";
import React, { useState, useEffect } from "react";
import { checkBackendAvailable } from "@/utils/checkBackendAvailable";
import { List, Notification, Row } from "@canonical/react-components";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import Navigation from "@/components/Navigation";
import PageContent from "@/components/PageContent";
import Loader from "@/components/Loader";

const inter = Inter({ subsets: ["latin"] });
const queryClient = new QueryClient();

export default function RootLayout({
children,
}: {
children: React.ReactNode;
noLayout?: boolean;
}) {
const [backendAvailable, setBackendAvailable] = useState<null | boolean>(
null,
);

useEffect(() => {
const fetchData = async () => {
const isBackendAvailable = await checkBackendAvailable();
setBackendAvailable(isBackendAvailable);
};

fetchData();
}, []);

return (
<html lang="en">
<head>
<title>SD Core</title>
<link
rel="shortcut icon"
href="https://assets.ubuntu.com/v1/49a1a858-favicon-32x32.png"
type="image/x-icon"
/>
</head>
<body className={inter.className}>
<div className="l-application" role="presentation">
<Navigation />
<main className="l-main">
<div className="p-panel">
{backendAvailable === null && <Loader text="Loading..." />}
{backendAvailable === false && (
<PageContent>
<Notification severity="negative" title="Error">
{"Backend not available"}
</Notification>
</PageContent>
)}
{backendAvailable === true && (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
)}
</div>
<footer className="l-footer--sticky p-strip--light">
<Row>
<p>
© 2023 Canonical Ltd. <a href="#">Ubuntu</a> and{" "}
<a href="#">Canonical</a> are registered trademarks of
Canonical Ltd.
</p>
<List
items={[
<a key="Legal information" href="https://ubuntu.com/legal">
Legal information
</a>,
]}
middot
/>
</Row>
</footer>
</main>
</div>
</body>
</html>
);
}
File renamed without changes.
12 changes: 9 additions & 3 deletions app/subscribers/page.tsx → app/(network)/subscribers/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ const Subscribers = () => {
onConfirm: () => handleConfirmDelete(rawIMSI),
children: (
<p>
This will permanently delete the subscriber <b>{rawIMSI}</b><br/>
This will permanently delete the subscriber{" "}
<b>{rawIMSI}</b>
<br />
This action cannot be undone.
</p>
),
Expand All @@ -83,7 +85,12 @@ const Subscribers = () => {
return (
<>
<PageHeader title={`Subscribers (${subscribers.length})`}>
<Button hasIcon appearance="base" onClick={handleRefresh} title="refresh subscriber list">
<Button
hasIcon
appearance="base"
onClick={handleRefresh}
title="refresh subscriber list"
>
<SyncOutlinedIcon style={{ color: "#666" }} />
</Button>
<Button appearance="positive" onClick={toggleModal}>
Expand All @@ -106,4 +113,3 @@ const Subscribers = () => {
);
};
export default Subscribers;

1 change: 1 addition & 0 deletions app/globals.scss
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ body {
@include vf-p-icon-close;
@include vf-p-icon-connected;
@include vf-p-icon-profile;
@include vf-p-icon-submit-bug;

@import "pattern_navigation";

Expand Down
80 changes: 6 additions & 74 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,86 +1,18 @@
"use client";
import "./globals.scss";
import { Inter } from "next/font/google";
import React, { useState, useEffect } from "react";
import { checkBackendAvailable } from "@/utils/checkBackendAvailable";
import {
List,
Notification,
Row,
} from "@canonical/react-components";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import Navigation from "@/components/Navigation";
import PageContent from "@/components/PageContent";
import Loader from "@/components/Loader";
import type { Metadata } from "next";

const inter = Inter({ subsets: ["latin"] });
const queryClient = new QueryClient();
export const metadata: Metadata = {
title: "SD Core",
description: "SD Core NMS",
};

export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
const [backendAvailable, setBackendAvailable] = useState<null | boolean>(
null,
);

useEffect(() => {
const fetchData = async () => {
const isBackendAvailable = await checkBackendAvailable();
setBackendAvailable(isBackendAvailable);
};

fetchData();
}, []);

return (
<html lang="en">
<head>
<title>SD Core</title>
<link rel="shortcut icon" href="https://assets.ubuntu.com/v1/49a1a858-favicon-32x32.png" type="image/x-icon" />
</head>
<body className={inter.className}>
<div className="l-application" role="presentation">
<Navigation />
<main className="l-main">
<div className="p-panel">
{backendAvailable === null && (
<Loader text="Loading..." />
)}
{backendAvailable === false && (
<PageContent>
<Notification severity="negative" title="Error">
{"Backend not available"}
</Notification>
</PageContent>
)}
{backendAvailable === true && (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
)}
</div>
<footer className="l-footer--sticky p-strip--light">
<Row>
<p>
© 2023 Canonical Ltd. <a href="#">Ubuntu</a> and{" "}
<a href="#">Canonical</a> are registered trademarks of Canonical
Ltd.
</p>
<List
items={[
<a key="Legal information" href="https://ubuntu.com/legal">
Legal information
</a>,
]}
middot
/>
</Row>
</footer>
</main>
</div>
</body>
<body>{children}</body>
</html>
);
}
7 changes: 0 additions & 7 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1 @@
import type { Metadata } from "next";

export const metadata: Metadata = {
title: "SD Core",
description: "SD Core NMS",
};

export default function Page() {}
33 changes: 28 additions & 5 deletions components/Navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Logo from "./Logo";
import { usePathname } from "next/navigation";

const Navigation: FC = () => {
const pathname = usePathname()
const pathname = usePathname();
const [isCollapsed, setCollapsed] = useState(false);

const softToggleMenu = () => {
Expand Down Expand Up @@ -69,7 +69,11 @@ const Navigation: FC = () => {
className="p-side-navigation__link"
href="/network-configuration"
title="Network slices"
aria-current={pathname === "/network-configuration" ? "page" : undefined}
aria-current={
pathname === "/network-configuration"
? "page"
: undefined
}
>
<Icon
className="is-light p-side-navigation__icon"
Expand All @@ -83,7 +87,9 @@ const Navigation: FC = () => {
className="p-side-navigation__link"
href={`/subscribers`}
title={`Subscribers`}
aria-current={pathname === "/subscribers" ? "page" : undefined}
aria-current={
pathname === "/subscribers" ? "page" : undefined
}
>
<Icon
className="is-light p-side-navigation__icon"
Expand All @@ -94,6 +100,21 @@ const Navigation: FC = () => {
</li>
</ul>
<ul className="p-side-navigation__list sidenav-bottom-ul">
<li className="p-side-navigation__item">
<a
className="p-side-navigation__link"
href="/api"
target="_blank"
rel="noreferrer"
title="API"
>
<Icon
className="is-light p-side-navigation__icon"
name="code"
/>{" "}
API
</a>
</li>
<li className="p-side-navigation__item">
<a
className="p-side-navigation__link"
Expand All @@ -119,7 +140,7 @@ const Navigation: FC = () => {
>
<Icon
className="is-light p-side-navigation__icon"
name="code"
name="submit-bug"
/>{" "}
Report a bug
</a>
Expand All @@ -130,7 +151,9 @@ const Navigation: FC = () => {
<div className="sidenav-toggle-wrapper">
<Button
appearance="base"
aria-label={`${isCollapsed ? "expand" : "collapse"} main navigation`}
aria-label={`${
isCollapsed ? "expand" : "collapse"
} main navigation`}
hasIcon
dense
className="sidenav-toggle is-dark u-no-margin l-navigation-collapse-toggle u-hide--small"
Expand Down
Loading

0 comments on commit c704edd

Please sign in to comment.