mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
feat: add openapi and swagger support
This commit is contained in:
parent
f0eecf354b
commit
ad806437af
@ -98,6 +98,7 @@
|
||||
"node-pty": "1.0.0",
|
||||
"node-schedule": "2.1.1",
|
||||
"octokit": "3.1.2",
|
||||
"openapi-trpc": "^0.2.0",
|
||||
"otpauth": "^9.2.3",
|
||||
"postgres": "3.4.4",
|
||||
"public-ip": "6.0.2",
|
||||
@ -109,9 +110,11 @@
|
||||
"slugify": "^1.6.6",
|
||||
"sonner": "^1.4.0",
|
||||
"superjson": "^2.2.1",
|
||||
"swagger-ui-react": "^5.17.14",
|
||||
"tailwind-merge": "^2.2.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"tar-fs": "3.0.5",
|
||||
"trpc-openapi": "^1.2.0",
|
||||
"use-resize-observer": "9.1.0",
|
||||
"ws": "8.16.0",
|
||||
"xterm-addon-fit": "^0.8.0",
|
||||
@ -129,6 +132,7 @@
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@types/react": "^18.2.37",
|
||||
"@types/react-dom": "^18.2.15",
|
||||
"@types/swagger-ui-react": "^4.18.3",
|
||||
"@types/tar-fs": "2.0.4",
|
||||
"@types/ws": "8.5.10",
|
||||
"autoprefixer": "^10.4.14",
|
||||
|
@ -5,14 +5,14 @@ import { createTRPCContext } from "@/server/api/trpc";
|
||||
|
||||
// export API handler
|
||||
export default createNextApiHandler({
|
||||
router: appRouter,
|
||||
createContext: createTRPCContext,
|
||||
onError:
|
||||
process.env.NODE_ENV === "development"
|
||||
? ({ path, error }) => {
|
||||
console.error(
|
||||
`❌ tRPC failed on ${path ?? "<no-path>"}: ${error.message}`,
|
||||
);
|
||||
}
|
||||
: undefined,
|
||||
router: appRouter,
|
||||
createContext: createTRPCContext,
|
||||
onError:
|
||||
process.env.NODE_ENV === "development"
|
||||
? ({ path, error }) => {
|
||||
console.error(
|
||||
`❌ tRPC failed on ${path ?? "<no-path>"}: ${error.message}`,
|
||||
);
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
|
34
pages/swagger.tsx
Normal file
34
pages/swagger.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import { validateRequest } from "@/server/auth/auth";
|
||||
import { api } from "@/utils/api";
|
||||
import type { GetServerSidePropsContext, NextPage } from "next";
|
||||
import dynamic from "next/dynamic";
|
||||
import "swagger-ui-react/swagger-ui.css";
|
||||
|
||||
const SwaggerUI = dynamic(() => import("swagger-ui-react"), { ssr: false });
|
||||
|
||||
const Home: NextPage = () => {
|
||||
const { data } = api.settings.getOpenApiDocument.useQuery();
|
||||
console.log(data);
|
||||
|
||||
if (!data) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
return <SwaggerUI spec={data} />;
|
||||
};
|
||||
|
||||
export default Home;
|
||||
export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
const { user } = await validateRequest(context.req, context.res);
|
||||
|
||||
if (!user) {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: true,
|
||||
destination: "/",
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
props: {},
|
||||
};
|
||||
}
|
1612
pnpm-lock.yaml
1612
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -23,6 +23,8 @@ import { dockerRouter } from "./routers/docker";
|
||||
import { composeRouter } from "./routers/compose";
|
||||
import { registryRouter } from "./routers/registry";
|
||||
import { clusterRouter } from "./routers/cluster";
|
||||
import { generateOpenAPIDocumentFromTRPCRouter } from "openapi-trpc";
|
||||
|
||||
/**
|
||||
* This is the primary router for your server.
|
||||
*
|
||||
@ -39,6 +41,7 @@ export const appRouter = createTRPCRouter({
|
||||
redis: redisRouter,
|
||||
mongo: mongoRouter,
|
||||
mariadb: mariadbRouter,
|
||||
compose: composeRouter,
|
||||
user: userRouter,
|
||||
domain: domainRouter,
|
||||
destination: destinationRouter,
|
||||
@ -50,10 +53,15 @@ export const appRouter = createTRPCRouter({
|
||||
security: securityRouter,
|
||||
redirects: redirectsRouter,
|
||||
port: portRouter,
|
||||
compose: composeRouter,
|
||||
registry: registryRouter,
|
||||
cluster: clusterRouter,
|
||||
});
|
||||
|
||||
// export type definition of API
|
||||
export type AppRouter = typeof appRouter;
|
||||
export const doc = generateOpenAPIDocumentFromTRPCRouter(appRouter, {
|
||||
pathPrefix: "/api/trpc",
|
||||
processOperation(op) {
|
||||
op.security = [{ bearerAuth: [] }];
|
||||
},
|
||||
});
|
||||
|
@ -27,13 +27,15 @@ import { createAppAuth } from "@octokit/auth-app";
|
||||
import { haveGithubRequirements } from "@/server/utils/providers/github";
|
||||
|
||||
export const adminRouter = createTRPCRouter({
|
||||
one: adminProcedure.query(async () => {
|
||||
const { sshPrivateKey, ...rest } = await findAdmin();
|
||||
return {
|
||||
haveSSH: !!sshPrivateKey,
|
||||
...rest,
|
||||
};
|
||||
}),
|
||||
one: adminProcedure
|
||||
.meta({ openapi: { method: "GET", path: "/say-hello" } })
|
||||
.query(async () => {
|
||||
const { sshPrivateKey, ...rest } = await findAdmin();
|
||||
return {
|
||||
haveSSH: !!sshPrivateKey,
|
||||
...rest,
|
||||
};
|
||||
}),
|
||||
createUserInvitation: adminProcedure
|
||||
.input(apiCreateUserInvitation)
|
||||
.mutation(async ({ input }) => {
|
||||
|
@ -59,29 +59,6 @@ export const projectRouter = createTRPCRouter({
|
||||
}
|
||||
}),
|
||||
|
||||
createCLI: protectedProcedure
|
||||
.input(apiCreateProject)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
try {
|
||||
console.log(ctx);
|
||||
if (ctx.user.rol === "user") {
|
||||
await checkProjectAccess(ctx.user.authId, "create");
|
||||
}
|
||||
const project = await createProject(input);
|
||||
if (ctx.user.rol === "user") {
|
||||
await addNewProject(ctx.user.authId, project.projectId);
|
||||
}
|
||||
|
||||
return project;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Error to create the project",
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
}),
|
||||
one: protectedProcedure
|
||||
.input(apiFindOneProject)
|
||||
.query(async ({ input, ctx }) => {
|
||||
|
@ -41,6 +41,7 @@ import {
|
||||
} from "../services/settings";
|
||||
import { canAccessToTraefikFiles } from "../services/user";
|
||||
import { recreateDirectory } from "@/server/utils/filesystem/directory";
|
||||
import { doc } from "../root";
|
||||
|
||||
export const settingsRouter = createTRPCRouter({
|
||||
reloadServer: adminProcedure.mutation(async () => {
|
||||
@ -242,5 +243,22 @@ export const settingsRouter = createTRPCRouter({
|
||||
}
|
||||
return readConfigInPath(input.path);
|
||||
}),
|
||||
|
||||
getOpenApiDocument: protectedProcedure.query((): unknown => {
|
||||
doc.components = {
|
||||
securitySchemes: {
|
||||
bearerAuth: {
|
||||
type: "http",
|
||||
scheme: "bearer",
|
||||
bearerFormat: "JWT",
|
||||
},
|
||||
},
|
||||
};
|
||||
doc.info = {
|
||||
title: "Dokploy API",
|
||||
description: "Endpoints for dokploy",
|
||||
version: getDokployVersion(),
|
||||
};
|
||||
return doc;
|
||||
}),
|
||||
});
|
||||
// apt-get install apache2-utils
|
||||
|
@ -16,11 +16,15 @@ import { createTraefikConfig } from "@/server/utils/traefik/application";
|
||||
import { docker } from "@/server/constants";
|
||||
import { getAdvancedStats } from "@/server/monitoring/utilts";
|
||||
import { validUniqueServerAppName } from "./project";
|
||||
import { generatePassword } from "@/templates/utils";
|
||||
import { generateAppName } from "@/server/db/schema/utils";
|
||||
export type Application = typeof applications.$inferSelect;
|
||||
|
||||
export const createApplication = async (
|
||||
input: typeof apiCreateApplication._type,
|
||||
) => {
|
||||
input.appName =
|
||||
`${input.appName}-${generatePassword(6)}` || generateAppName("app");
|
||||
if (input.appName) {
|
||||
const valid = await validUniqueServerAppName(input.appName);
|
||||
|
||||
|
@ -14,10 +14,14 @@ import { COMPOSE_PATH } from "@/server/constants";
|
||||
import { cloneGithubRepository } from "@/server/utils/providers/github";
|
||||
import { cloneGitRepository } from "@/server/utils/providers/git";
|
||||
import { validUniqueServerAppName } from "./project";
|
||||
import { generateAppName } from "@/server/db/schema/utils";
|
||||
import { generatePassword } from "@/templates/utils";
|
||||
|
||||
export type Compose = typeof compose.$inferSelect;
|
||||
|
||||
export const createCompose = async (input: typeof apiCreateCompose._type) => {
|
||||
input.appName =
|
||||
`${input.appName}-${generatePassword(6)}` || generateAppName("compose");
|
||||
if (input.appName) {
|
||||
const valid = await validUniqueServerAppName(input.appName);
|
||||
|
||||
|
@ -6,10 +6,14 @@ import { pullImage } from "@/server/utils/docker/utils";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { eq, getTableColumns } from "drizzle-orm";
|
||||
import { validUniqueServerAppName } from "./project";
|
||||
import { generateAppName } from "@/server/db/schema/utils";
|
||||
import { generatePassword } from "@/templates/utils";
|
||||
|
||||
export type Mariadb = typeof mariadb.$inferSelect;
|
||||
|
||||
export const createMariadb = async (input: typeof apiCreateMariaDB._type) => {
|
||||
input.appName =
|
||||
`${input.appName}-${generatePassword(6)}` || generateAppName("mariadb");
|
||||
if (input.appName) {
|
||||
const valid = await validUniqueServerAppName(input.appName);
|
||||
|
||||
|
@ -6,10 +6,14 @@ import { pullImage } from "@/server/utils/docker/utils";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { eq, getTableColumns } from "drizzle-orm";
|
||||
import { validUniqueServerAppName } from "./project";
|
||||
import { generateAppName } from "@/server/db/schema/utils";
|
||||
import { generatePassword } from "@/templates/utils";
|
||||
|
||||
export type Mongo = typeof mongo.$inferSelect;
|
||||
|
||||
export const createMongo = async (input: typeof apiCreateMongo._type) => {
|
||||
input.appName =
|
||||
`${input.appName}-${generatePassword(6)}` || generateAppName("postgres");
|
||||
if (input.appName) {
|
||||
const valid = await validUniqueServerAppName(input.appName);
|
||||
|
||||
|
@ -6,10 +6,15 @@ import { pullImage } from "@/server/utils/docker/utils";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { eq, getTableColumns } from "drizzle-orm";
|
||||
import { validUniqueServerAppName } from "./project";
|
||||
import { generatePassword } from "@/templates/utils";
|
||||
import { generateAppName } from "@/server/db/schema/utils";
|
||||
|
||||
export type MySql = typeof mysql.$inferSelect;
|
||||
|
||||
export const createMysql = async (input: typeof apiCreateMySql._type) => {
|
||||
input.appName =
|
||||
`${input.appName}-${generatePassword(6)}` || generateAppName("mysql");
|
||||
|
||||
if (input.appName) {
|
||||
const valid = await validUniqueServerAppName(input.appName);
|
||||
|
||||
|
@ -6,10 +6,14 @@ import { pullImage } from "@/server/utils/docker/utils";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { eq, getTableColumns } from "drizzle-orm";
|
||||
import { validUniqueServerAppName } from "./project";
|
||||
import { generatePassword } from "@/templates/utils";
|
||||
import { generateAppName } from "@/server/db/schema/utils";
|
||||
|
||||
export type Postgres = typeof postgres.$inferSelect;
|
||||
|
||||
export const createPostgres = async (input: typeof apiCreatePostgres._type) => {
|
||||
input.appName =
|
||||
`${input.appName}-${generatePassword(6)}` || generateAppName("postgres");
|
||||
if (input.appName) {
|
||||
const valid = await validUniqueServerAppName(input.appName);
|
||||
|
||||
|
@ -6,11 +6,15 @@ import { pullImage } from "@/server/utils/docker/utils";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { validUniqueServerAppName } from "./project";
|
||||
import { generateAppName } from "@/server/db/schema/utils";
|
||||
import { generatePassword } from "@/templates/utils";
|
||||
|
||||
export type Redis = typeof redis.$inferSelect;
|
||||
|
||||
// https://github.com/drizzle-team/drizzle-orm/discussions/1483#discussioncomment-7523881
|
||||
export const createRedis = async (input: typeof apiCreateRedis._type) => {
|
||||
input.appName =
|
||||
`${input.appName}-${generatePassword(6)}` || generateAppName("redis");
|
||||
if (input.appName) {
|
||||
const valid = await validUniqueServerAppName(input.appName);
|
||||
|
||||
|
@ -15,6 +15,7 @@ import superjson from "superjson";
|
||||
import { ZodError } from "zod";
|
||||
import { validateBearerToken, validateRequest } from "../auth/auth";
|
||||
import type { Session, User } from "lucia";
|
||||
import type { OperationMeta } from "openapi-trpc";
|
||||
|
||||
/**
|
||||
* 1. CONTEXT
|
||||
@ -94,19 +95,22 @@ export const createTRPCContext = async (opts: CreateNextContextOptions) => {
|
||||
* errors on the backend.
|
||||
*/
|
||||
|
||||
const t = initTRPC.context<typeof createTRPCContext>().create({
|
||||
transformer: superjson,
|
||||
errorFormatter({ shape, error }) {
|
||||
return {
|
||||
...shape,
|
||||
data: {
|
||||
...shape.data,
|
||||
zodError:
|
||||
error.cause instanceof ZodError ? error.cause.flatten() : null,
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
const t = initTRPC
|
||||
.meta<OperationMeta>()
|
||||
.context<typeof createTRPCContext>()
|
||||
.create({
|
||||
transformer: superjson,
|
||||
errorFormatter({ shape, error }) {
|
||||
return {
|
||||
...shape,
|
||||
data: {
|
||||
...shape.data,
|
||||
zodError:
|
||||
error.cause instanceof ZodError ? error.cause.flatten() : null,
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 3. ROUTER & PROCEDURE (THE IMPORTANT BIT)
|
||||
|
@ -308,17 +308,12 @@ const createSchema = createInsertSchema(applications, {
|
||||
networkSwarm: NetworkSwarmSchema.nullable(),
|
||||
});
|
||||
|
||||
export const apiCreateApplication = createSchema
|
||||
.pick({
|
||||
name: true,
|
||||
appName: true,
|
||||
description: true,
|
||||
projectId: true,
|
||||
})
|
||||
.transform((data) => ({
|
||||
...data,
|
||||
appName: `${data.appName}-${generatePassword(6)}` || generateAppName("app"),
|
||||
}));
|
||||
export const apiCreateApplication = createSchema.pick({
|
||||
name: true,
|
||||
appName: true,
|
||||
description: true,
|
||||
projectId: true,
|
||||
});
|
||||
|
||||
export const apiFindOneApplication = createSchema
|
||||
.pick({
|
||||
|
@ -75,19 +75,13 @@ const createSchema = createInsertSchema(compose, {
|
||||
composeType: z.enum(["docker-compose", "stack"]).optional(),
|
||||
});
|
||||
|
||||
export const apiCreateCompose = createSchema
|
||||
.pick({
|
||||
name: true,
|
||||
description: true,
|
||||
projectId: true,
|
||||
composeType: true,
|
||||
appName: true,
|
||||
})
|
||||
.transform((data) => ({
|
||||
...data,
|
||||
appName:
|
||||
`${data.appName}-${generatePassword(6)}` || generateAppName("compose"),
|
||||
}));
|
||||
export const apiCreateCompose = createSchema.pick({
|
||||
name: true,
|
||||
description: true,
|
||||
projectId: true,
|
||||
composeType: true,
|
||||
appName: true,
|
||||
});
|
||||
|
||||
export const apiCreateComposeByTemplate = createSchema
|
||||
.pick({
|
||||
|
@ -89,12 +89,7 @@ export const apiCreateMariaDB = createSchema
|
||||
databaseUser: true,
|
||||
databasePassword: true,
|
||||
})
|
||||
.required()
|
||||
.transform((data) => ({
|
||||
...data,
|
||||
appName:
|
||||
`${data.appName}-${generatePassword(6)}` || generateAppName("mariadb"),
|
||||
}));
|
||||
.required();
|
||||
|
||||
export const apiFindOneMariaDB = createSchema
|
||||
.pick({
|
||||
|
@ -81,12 +81,7 @@ export const apiCreateMongo = createSchema
|
||||
databaseUser: true,
|
||||
databasePassword: true,
|
||||
})
|
||||
.required()
|
||||
.transform((data) => ({
|
||||
...data,
|
||||
appName:
|
||||
`${data.appName}-${generatePassword(6)}` || generateAppName("postgres"),
|
||||
}));
|
||||
.required();
|
||||
|
||||
export const apiFindOneMongo = createSchema
|
||||
.pick({
|
||||
|
@ -87,12 +87,7 @@ export const apiCreateMySql = createSchema
|
||||
databasePassword: true,
|
||||
databaseRootPassword: true,
|
||||
})
|
||||
.required()
|
||||
.transform((data) => ({
|
||||
...data,
|
||||
appName:
|
||||
`${data.appName}-${generatePassword(6)}` || generateAppName("mysql"),
|
||||
}));
|
||||
.required();
|
||||
|
||||
export const apiFindOneMySql = createSchema
|
||||
.pick({
|
||||
|
@ -83,12 +83,7 @@ export const apiCreatePostgres = createSchema
|
||||
projectId: true,
|
||||
description: true,
|
||||
})
|
||||
.required()
|
||||
.transform((data) => ({
|
||||
...data,
|
||||
appName:
|
||||
`${data.appName}-${generatePassword(6)}` || generateAppName("postgres"),
|
||||
}));
|
||||
.required();
|
||||
|
||||
export const apiFindOnePostgres = createSchema
|
||||
.pick({
|
||||
|
@ -53,11 +53,6 @@ export const apiCreateProject = createSchema.pick({
|
||||
description: true,
|
||||
});
|
||||
|
||||
export const apiCreateCLI = createSchema.pick({
|
||||
name: true,
|
||||
description: true,
|
||||
});
|
||||
|
||||
export const apiFindOneProject = createSchema
|
||||
.pick({
|
||||
projectId: true,
|
||||
|
@ -76,12 +76,7 @@ export const apiCreateRedis = createSchema
|
||||
projectId: true,
|
||||
description: true,
|
||||
})
|
||||
.required()
|
||||
.transform((data) => ({
|
||||
...data,
|
||||
appName:
|
||||
`${data.appName}-${generatePassword(6)}` || generateAppName("redis"),
|
||||
}));
|
||||
.required();
|
||||
|
||||
export const apiFindOneRedis = createSchema
|
||||
.pick({
|
||||
|
@ -22,6 +22,30 @@ import { initializePostgres } from "./setup/postgres-setup";
|
||||
import { migration } from "@/server/db/migration";
|
||||
import { setupDockerContainerLogsWebSocketServer } from "./wss/docker-container-logs";
|
||||
import { setupDockerContainerTerminalWebSocketServer } from "./wss/docker-container-terminal";
|
||||
import { generateOpenAPIDocumentFromTRPCRouter } from "openapi-trpc";
|
||||
import { appRouter } from "./api/root";
|
||||
import { getDokployVersion } from "./api/services/settings";
|
||||
|
||||
export const doc = generateOpenAPIDocumentFromTRPCRouter(appRouter, {
|
||||
pathPrefix: "/api/trpc",
|
||||
processOperation(op) {
|
||||
op.security = [{ bearerAuth: [] }];
|
||||
},
|
||||
});
|
||||
doc.components = {
|
||||
securitySchemes: {
|
||||
bearerAuth: {
|
||||
type: "http",
|
||||
scheme: "bearer",
|
||||
bearerFormat: "JWT",
|
||||
},
|
||||
},
|
||||
};
|
||||
doc.info = {
|
||||
title: "Dokploy API",
|
||||
description: "Endpoints for dokploy",
|
||||
version: getDokployVersion(),
|
||||
};
|
||||
|
||||
config({ path: ".env" });
|
||||
const PORT = Number.parseInt(process.env.PORT || "3000", 10);
|
||||
@ -31,6 +55,81 @@ const handle = app.getRequestHandler();
|
||||
void app.prepare().then(async () => {
|
||||
try {
|
||||
const server = http.createServer((req, res) => {
|
||||
if (req.method === "GET" && req.url === "/trpc.json") {
|
||||
res.setHeader("Content-Type", "application/json");
|
||||
res.end(JSON.stringify(doc)); // Asegúrate de definir `doc`
|
||||
return;
|
||||
}
|
||||
if (req.method === "GET" && req.url === "/trpc") {
|
||||
res.setHeader("Content-Type", "text/html");
|
||||
res.end(`<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="description" content="SwaggerUI" />
|
||||
<title>SwaggerUI</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@4.15.5/swagger-ui.min.css" rel="stylesheet" />
|
||||
<link href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@4.17.0/favicon-32x32.png" rel="icon" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="swagger-ui"></div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/swagger-ui-dist@4.15.5/swagger-ui-bundle.js"></script>
|
||||
<script>
|
||||
window.onload = () => {
|
||||
const ui = SwaggerUIBundle({
|
||||
url: '/trpc.json',
|
||||
dom_id: '#swagger-ui',
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis,
|
||||
SwaggerUIBundle.SwaggerUIStandalonePreset
|
||||
],
|
||||
layout: "BaseLayout",
|
||||
requestInterceptor: (request) => {
|
||||
const token = localStorage.getItem('bearerToken');
|
||||
if (token) {
|
||||
request.headers['Authorization'] = 'Bearer ' + token;
|
||||
}
|
||||
return request;
|
||||
}
|
||||
});
|
||||
|
||||
ui.initOAuth({
|
||||
persistAuthorization: true
|
||||
});
|
||||
|
||||
// Store the token in local storage when set in Swagger UI
|
||||
ui.authActions.authorize({
|
||||
bearerAuth: {
|
||||
name: "bearerAuth",
|
||||
value: localStorage.getItem('bearerToken') || '',
|
||||
schema: {
|
||||
type: "http",
|
||||
scheme: "bearer",
|
||||
bearerFormat: "JWT"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ui.authSelectors.oauth().authorize({
|
||||
bearerAuth: {
|
||||
token: localStorage.getItem('bearerToken') || ''
|
||||
}
|
||||
});
|
||||
|
||||
window.ui = ui;
|
||||
|
||||
// Save token to localStorage
|
||||
const tokenInput = document.querySelector('input[placeholder="Bearer token"]');
|
||||
tokenInput.addEventListener('change', (event) => {
|
||||
localStorage.setItem('bearerToken', event.target.value);
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>`);
|
||||
return;
|
||||
}
|
||||
handle(req, res);
|
||||
});
|
||||
|
||||
|
@ -158,3 +158,9 @@
|
||||
.animate-heartbeat {
|
||||
animation: heartbeat 2.5s infinite;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.swagger-ui {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user