OpenAPI spec at /api/docs with a Scalar viewer, opt-in via FEAT_API_DOCS

This commit is contained in:
jarek committed 2026-08-17 09:50:34 +02:00
1 parent ba8670af66
commit da26f7f764
251 files changed
+32497 -66

No files matched your search

+11
View File
@@ -120,6 +120,17 @@ async function getOwnContainerInfo(containerId: string | undefined): Promise<any
return null;
}
/**
* GET /api/system - Aggregated system, Docker, runtime and database info
*
* @openapi
* summary: Return aggregated system info — Docker daemon, host, Node.js runtime, database, and object counts for an environment
* query: env:integer ID of the environment to collect Docker info for (Docker fields are null when omitted or unreachable) (from GET /api/environments)
* resp-200: {docker:object, host:object, runtime:object!, database:object!, stats:object!}
* resp-200-example: {"docker":{"version":"27.0.3","apiVersion":"1.46"},"host":{"name":"docker-host","cpus":8,"memory":16777216000},"runtime":{"runtimeName":"Node.js","platform":"linux"},"database":{"type":"SQLite","schemaVersion":42},"stats":{"containers":{"total":10,"running":8,"stopped":2},"images":25,"volumes":5,"networks":4,"stacks":3}}
* resp-403: Permission denied, or (enterprise) no access to this environment
* resp-500: Failed to fetch system info
*/
export const GET: RequestHandler = async ({ url, cookies }) => {
const auth = await authorize(cookies);
+12
View File
@@ -8,6 +8,18 @@ const SKIP_DF_COLLECTION = process.env.SKIP_DF_COLLECTION === 'true' || process.
const DISK_USAGE_TIMEOUT = 15000; // 15 second timeout
/**
* GET /api/system/disk - Docker disk usage for an environment
*
* @openapi
* summary: Return Docker disk usage (df) for an environment, or null when collection is disabled, times out, or fails
* query: env:integer! ID of the environment to query disk usage for (from GET /api/environments)
* resp-200: {diskUsage:object}
* resp-200-desc: diskUsage is null when SKIP_DF_COLLECTION is set, or the query times out or errors
* resp-200-example: {"diskUsage":{"LayersSize":1420000000,"Images":[],"Containers":[],"Volumes":[]}}
* resp-400: Environment ID is required
* resp-403: Permission denied, or (enterprise) no access to this environment
*/
export const GET: RequestHandler = async ({ url, cookies }) => {
const auth = await authorize(cookies);
+19 -3
View File
@@ -18,7 +18,16 @@ export interface FileEntry {
* POST /api/system/files
* Create a directory
*
* Body: { path: string }
* @openapi
* summary: Create a directory on Dockhand's local filesystem (absolute path, no traversal, non-protected)
* body: {path:string!}
* body-example: {"path":"/docker/stacks/myapp"}
* resp-200: {success:boolean!, path:string!}
* resp-200-example: {"success":true,"path":"/docker/stacks/myapp"}
* resp-400: Path is missing, not absolute, or contains ".."
* resp-403: Permission denied, or the path is protected
* resp-409: Path already exists
* resp-500: Failed to create directory
*/
export const POST: RequestHandler = async ({ request, cookies }) => {
const auth = await authorize(cookies);
@@ -65,8 +74,15 @@ export const POST: RequestHandler = async ({ request, cookies }) => {
* GET /api/system/files
* Browse Dockhand's local filesystem (for mount browsing)
*
* Query params:
* - path: Directory path to list
* @openapi
* summary: List the entries of a directory on Dockhand's local filesystem (protected paths are hidden)
* query: path:string Absolute directory path to list (defaults to "/")
* resp-200: {path:string!, parent:string, entries:array<{name:string!, path:string!, type:string!, size:integer!, mtime:string!, mode:string!}>!}
* resp-200-example: {"path":"/docker","parent":"/","entries":[{"name":"stacks","path":"/docker/stacks","type":"directory","size":4096,"mtime":"2026-07-01T10:00:00.000Z","mode":"755"}]}
* resp-400: The path exists but is not a directory
* resp-403: Permission denied, or the path is protected
* resp-404: Path not found
* resp-500: Failed to list directory
*/
export const GET: RequestHandler = async ({ url, cookies }) => {
const auth = await authorize(cookies);
@@ -8,8 +8,15 @@ import { isProtectedPath } from '$lib/server/fs-guard';
* GET /api/system/files/content
* Read file content from Dockhand's local filesystem
*
* Query params:
* - path: File path to read
* @openapi
* summary: Read a text file from Dockhand's local filesystem (max 10MB, protected paths denied)
* query: path:string! Absolute path of the file to read
* resp-200: {path:string!, content:string!, size:integer!, mtime:string!}
* resp-200-example: {"path":"/docker/stacks/myapp/compose.yaml","content":"services:\n app:\n image: nginx","size":42,"mtime":"2026-07-01T10:00:00.000Z"}
* resp-400: Path is missing, points to a directory, or the file exceeds 10MB
* resp-403: Permission denied, or the path is protected
* resp-404: File not found
* resp-500: Failed to read file
*/
export const GET: RequestHandler = async ({ url, cookies }) => {
const auth = await authorize(cookies);