mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 00:32:12 +02:00
222 lines
6.3 KiB
TypeScript
222 lines
6.3 KiB
TypeScript
import LocalFile from "Common/Server/Utils/LocalFile";
|
|
import logger from "Common/Server/Utils/Logger";
|
|
import ObjectID from "Common/Types/ObjectID";
|
|
import path from "path";
|
|
import os from "os";
|
|
|
|
export interface WorkspaceInfo {
|
|
workspacePath: string;
|
|
taskId: string;
|
|
createdAt: Date;
|
|
}
|
|
|
|
export default class WorkspaceManager {
|
|
private static readonly BASE_TEMP_DIR: string = path.join(
|
|
os.tmpdir(),
|
|
"oneuptime-ai-agent",
|
|
);
|
|
|
|
// Create a new workspace for a task
|
|
public static async createWorkspace(taskId: string): Promise<WorkspaceInfo> {
|
|
const timestamp: number = Date.now();
|
|
const uniqueId: string = ObjectID.generate().toString().substring(0, 8);
|
|
const workspaceName: string = `task-${taskId}-${timestamp}-${uniqueId}`;
|
|
const workspacePath: string = path.join(this.BASE_TEMP_DIR, workspaceName);
|
|
|
|
logger.debug(`Creating workspace: ${workspacePath}`);
|
|
|
|
// Create the workspace directory
|
|
await LocalFile.makeDirectory(workspacePath);
|
|
|
|
return {
|
|
workspacePath,
|
|
taskId,
|
|
createdAt: new Date(),
|
|
};
|
|
}
|
|
|
|
// Create a subdirectory within a workspace
|
|
public static async createSubdirectory(
|
|
workspacePath: string,
|
|
subdirectoryName: string,
|
|
): Promise<string> {
|
|
const subdirectoryPath: string = path.join(workspacePath, subdirectoryName);
|
|
await LocalFile.makeDirectory(subdirectoryPath);
|
|
return subdirectoryPath;
|
|
}
|
|
|
|
// Check if workspace exists
|
|
public static async workspaceExists(workspacePath: string): Promise<boolean> {
|
|
try {
|
|
await LocalFile.readDirectory(workspacePath);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Delete a workspace and all its contents
|
|
public static async deleteWorkspace(workspacePath: string): Promise<void> {
|
|
logger.debug(`Deleting workspace: ${workspacePath}`);
|
|
|
|
try {
|
|
// Verify the path is within our temp directory to prevent accidental deletion
|
|
const normalizedPath: string = path.normalize(workspacePath);
|
|
const normalizedBase: string = path.normalize(this.BASE_TEMP_DIR);
|
|
|
|
if (!normalizedPath.startsWith(normalizedBase)) {
|
|
throw new Error(
|
|
`Security error: Cannot delete path outside workspace base: ${workspacePath}`,
|
|
);
|
|
}
|
|
|
|
await LocalFile.deleteDirectory(workspacePath);
|
|
logger.debug(`Workspace deleted: ${workspacePath}`);
|
|
} catch (error) {
|
|
logger.error(`Error deleting workspace ${workspacePath}:`);
|
|
logger.error(error);
|
|
}
|
|
}
|
|
|
|
// Write a file to workspace
|
|
public static async writeFile(
|
|
workspacePath: string,
|
|
relativePath: string,
|
|
content: string,
|
|
): Promise<string> {
|
|
const filePath: string = path.join(workspacePath, relativePath);
|
|
|
|
// Ensure parent directory exists
|
|
const parentDir: string = path.dirname(filePath);
|
|
await LocalFile.makeDirectory(parentDir);
|
|
|
|
await LocalFile.write(filePath, content);
|
|
|
|
return filePath;
|
|
}
|
|
|
|
// Read a file from workspace
|
|
public static async readFile(
|
|
workspacePath: string,
|
|
relativePath: string,
|
|
): Promise<string> {
|
|
const filePath: string = path.join(workspacePath, relativePath);
|
|
return LocalFile.read(filePath);
|
|
}
|
|
|
|
// Check if a file exists in workspace
|
|
public static async fileExists(
|
|
workspacePath: string,
|
|
relativePath: string,
|
|
): Promise<boolean> {
|
|
try {
|
|
const filePath: string = path.join(workspacePath, relativePath);
|
|
await LocalFile.read(filePath);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Delete a file from workspace
|
|
public static async deleteFile(
|
|
workspacePath: string,
|
|
relativePath: string,
|
|
): Promise<void> {
|
|
const filePath: string = path.join(workspacePath, relativePath);
|
|
await LocalFile.deleteFile(filePath);
|
|
}
|
|
|
|
// List files in workspace directory
|
|
public static async listFiles(workspacePath: string): Promise<Array<string>> {
|
|
const entries: Array<{ name: string; isDirectory(): boolean }> =
|
|
await LocalFile.readDirectory(workspacePath);
|
|
return entries.map((entry: { name: string }) => {
|
|
return entry.name;
|
|
});
|
|
}
|
|
|
|
// Get the full path for a relative path in workspace
|
|
public static getFullPath(
|
|
workspacePath: string,
|
|
relativePath: string,
|
|
): string {
|
|
return path.join(workspacePath, relativePath);
|
|
}
|
|
|
|
// Clean up old workspaces (older than specified hours)
|
|
public static async cleanupOldWorkspaces(
|
|
maxAgeHours: number = 24,
|
|
): Promise<number> {
|
|
logger.debug(`Cleaning up workspaces older than ${maxAgeHours} hours`);
|
|
|
|
let cleanedCount: number = 0;
|
|
|
|
try {
|
|
// Ensure base directory exists
|
|
try {
|
|
await LocalFile.readDirectory(this.BASE_TEMP_DIR);
|
|
} catch {
|
|
// Base directory doesn't exist, nothing to clean
|
|
return 0;
|
|
}
|
|
|
|
const entries: Array<{ name: string; isDirectory(): boolean }> =
|
|
await LocalFile.readDirectory(this.BASE_TEMP_DIR);
|
|
|
|
const maxAge: number = maxAgeHours * 60 * 60 * 1000; // Convert to milliseconds
|
|
const now: number = Date.now();
|
|
|
|
for (const entry of entries) {
|
|
if (!entry.isDirectory()) {
|
|
continue;
|
|
}
|
|
|
|
const workspacePath: string = path.join(this.BASE_TEMP_DIR, entry.name);
|
|
|
|
/*
|
|
* Try to extract timestamp from directory name
|
|
* Format: task-{taskId}-{timestamp}-{uniqueId}
|
|
*/
|
|
const match: RegExpMatchArray | null = entry.name.match(
|
|
/task-[^-]+-(\d+)-[^-]+/,
|
|
);
|
|
|
|
if (match) {
|
|
const timestamp: number = parseInt(match[1] || "0", 10);
|
|
|
|
if (now - timestamp > maxAge) {
|
|
await this.deleteWorkspace(workspacePath);
|
|
cleanedCount++;
|
|
}
|
|
}
|
|
}
|
|
} catch (error) {
|
|
logger.error("Error during workspace cleanup:");
|
|
logger.error(error);
|
|
}
|
|
|
|
logger.debug(`Cleaned up ${cleanedCount} old workspaces`);
|
|
|
|
return cleanedCount;
|
|
}
|
|
|
|
// Initialize workspace manager (create base directory if needed)
|
|
public static async initialize(): Promise<void> {
|
|
try {
|
|
await LocalFile.makeDirectory(this.BASE_TEMP_DIR);
|
|
logger.debug(
|
|
`Workspace base directory initialized: ${this.BASE_TEMP_DIR}`,
|
|
);
|
|
} catch (error) {
|
|
logger.error("Error initializing workspace manager:");
|
|
logger.error(error);
|
|
}
|
|
}
|
|
|
|
// Get the base temp directory path
|
|
public static getBaseTempDir(): string {
|
|
return this.BASE_TEMP_DIR;
|
|
}
|
|
}
|