This commit is contained in:
Jarek Krochmalski committed 2026-01-11 09:01:42 +01:00
1 parent c15355e159
commit 851e56bc57
23 files changed
+8923

No files matched your search

+80
View File
@@ -0,0 +1,80 @@
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { readdirSync, statSync, existsSync } from 'node:fs';
import { join, basename } from 'node:path';
import { authorize } from '$lib/server/authorize';
export interface FileEntry {
name: string;
path: string;
type: 'file' | 'directory' | 'symlink';
size: number;
mtime: string;
mode: string;
}
/**
* GET /api/system/files
* Browse Dockhand's local filesystem (for mount browsing)
*
* Query params:
* - path: Directory path to list
*/
export const GET: RequestHandler = async ({ url, cookies }) => {
const auth = await authorize(cookies);
if (auth.authEnabled && !await auth.can('stacks', 'edit')) {
return json({ error: 'Permission denied' }, { status: 403 });
}
const path = url.searchParams.get('path') || '/';
try {
if (!existsSync(path)) {
return json({ error: `Path not found: ${path}` }, { status: 404 });
}
const stat = statSync(path);
if (!stat.isDirectory()) {
return json({ error: `Not a directory: ${path}` }, { status: 400 });
}
const entries: FileEntry[] = [];
const dirEntries = readdirSync(path, { withFileTypes: true });
for (const entry of dirEntries) {
try {
const fullPath = join(path, entry.name);
const entryStat = statSync(fullPath);
entries.push({
name: entry.name,
path: fullPath,
type: entry.isDirectory() ? 'directory' : entry.isSymbolicLink() ? 'symlink' : 'file',
size: entryStat.size,
mtime: entryStat.mtime.toISOString(),
mode: (entryStat.mode & 0o777).toString(8).padStart(3, '0')
});
} catch {
// Skip entries we can't stat (permission issues, etc.)
}
}
// Sort: directories first, then alphabetically
entries.sort((a, b) => {
if (a.type === 'directory' && b.type !== 'directory') return -1;
if (a.type !== 'directory' && b.type === 'directory') return 1;
return a.name.localeCompare(b.name);
});
return json({
path,
parent: path === '/' ? null : join(path, '..'),
entries
});
} catch (error) {
console.error('Error listing directory:', error);
const message = error instanceof Error ? error.message : 'Unknown error';
return json({ error: `Failed to list directory: ${message}` }, { status: 500 });
}
};
@@ -0,0 +1,55 @@
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { readFileSync, existsSync, statSync } from 'node:fs';
import { authorize } from '$lib/server/authorize';
/**
* GET /api/system/files/content
* Read file content from Dockhand's local filesystem
*
* Query params:
* - path: File path to read
*/
export const GET: RequestHandler = async ({ url, cookies }) => {
const auth = await authorize(cookies);
if (auth.authEnabled && !await auth.can('stacks', 'edit')) {
return json({ error: 'Permission denied' }, { status: 403 });
}
const path = url.searchParams.get('path');
if (!path) {
return json({ error: 'Path is required' }, { status: 400 });
}
try {
if (!existsSync(path)) {
return json({ error: `File not found: ${path}` }, { status: 404 });
}
const stat = statSync(path);
if (stat.isDirectory()) {
return json({ error: `Cannot read directory as file: ${path}` }, { status: 400 });
}
// Limit file size to 10MB
const maxSize = 10 * 1024 * 1024;
if (stat.size > maxSize) {
return json({ error: `File too large (max ${maxSize / 1024 / 1024}MB)` }, { status: 400 });
}
const content = readFileSync(path, 'utf-8');
return json({
path,
content,
size: stat.size,
mtime: stat.mtime.toISOString()
});
} catch (error) {
console.error('Error reading file:', error);
const message = error instanceof Error ? error.message : 'Unknown error';
return json({ error: `Failed to read file: ${message}` }, { status: 500 });
}
};