feat: fix network stats merge and add openapi jsdocs comments
This commit is contained in:
@@ -58,6 +58,31 @@ app.use(express.json({ limit: "1mb" }));
|
||||
|
||||
app.use(authManager.createAuthMiddleware());
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /uptime:
|
||||
* get:
|
||||
* summary: Get server uptime
|
||||
* description: Returns the uptime of the server in various formats.
|
||||
* tags:
|
||||
* - Dashboard
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Server uptime information.
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* uptimeMs:
|
||||
* type: number
|
||||
* uptimeSeconds:
|
||||
* type: number
|
||||
* formatted:
|
||||
* type: string
|
||||
* 500:
|
||||
* description: Failed to get uptime.
|
||||
*/
|
||||
app.get("/uptime", async (req, res) => {
|
||||
try {
|
||||
const uptimeMs = Date.now() - serverStartTime;
|
||||
@@ -77,6 +102,28 @@ app.get("/uptime", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /activity/recent:
|
||||
* get:
|
||||
* summary: Get recent activity
|
||||
* description: Fetches the most recent activities for the authenticated user.
|
||||
* tags:
|
||||
* - Dashboard
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: limit
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: The maximum number of activities to return.
|
||||
* responses:
|
||||
* 200:
|
||||
* description: A list of recent activities.
|
||||
* 401:
|
||||
* description: Session expired.
|
||||
* 500:
|
||||
* description: Failed to get recent activity.
|
||||
*/
|
||||
app.get("/activity/recent", async (req, res) => {
|
||||
try {
|
||||
const userId = (req as AuthenticatedRequest).userId;
|
||||
@@ -108,6 +155,40 @@ app.get("/activity/recent", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /activity/log:
|
||||
* post:
|
||||
* summary: Log a new activity
|
||||
* description: Logs a new user activity, such as accessing a terminal or file manager. This endpoint is rate-limited.
|
||||
* tags:
|
||||
* - Dashboard
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* type:
|
||||
* type: string
|
||||
* enum: [terminal, file_manager, server_stats, tunnel, docker]
|
||||
* hostId:
|
||||
* type: integer
|
||||
* hostName:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Activity logged successfully or rate-limited.
|
||||
* 400:
|
||||
* description: Invalid request body.
|
||||
* 401:
|
||||
* description: Session expired.
|
||||
* 404:
|
||||
* description: Host not found or access denied.
|
||||
* 500:
|
||||
* description: Failed to log activity.
|
||||
*/
|
||||
app.post("/activity/log", async (req, res) => {
|
||||
try {
|
||||
const userId = (req as AuthenticatedRequest).userId;
|
||||
@@ -224,6 +305,22 @@ app.post("/activity/log", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /activity/reset:
|
||||
* delete:
|
||||
* summary: Reset recent activity
|
||||
* description: Clears all recent activity for the authenticated user.
|
||||
* tags:
|
||||
* - Dashboard
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Recent activity cleared.
|
||||
* 401:
|
||||
* description: Session expired.
|
||||
* 500:
|
||||
* description: Failed to reset activity.
|
||||
*/
|
||||
app.delete("/activity/reset", async (req, res) => {
|
||||
try {
|
||||
const userId = (req as AuthenticatedRequest).userId;
|
||||
|
||||
@@ -206,10 +206,46 @@ app.use(bodyParser.urlencoded({ limit: "1gb", extended: true }));
|
||||
app.use(bodyParser.raw({ limit: "5gb", type: "application/octet-stream" }));
|
||||
app.use(cookieParser());
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /health:
|
||||
* get:
|
||||
* summary: Health check
|
||||
* description: Returns the health status of the server.
|
||||
* tags:
|
||||
* - General
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Server is healthy.
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* status:
|
||||
* type: string
|
||||
* example: ok
|
||||
*/
|
||||
app.get("/health", (req, res) => {
|
||||
res.json({ status: "ok" });
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /version:
|
||||
* get:
|
||||
* summary: Get version information
|
||||
* description: Returns the local and remote version of the application.
|
||||
* tags:
|
||||
* - General
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Version information.
|
||||
* 404:
|
||||
* description: Local version not set.
|
||||
* 500:
|
||||
* description: Fetch error.
|
||||
*/
|
||||
app.get("/version", authenticateJWT, async (req, res) => {
|
||||
let localVersion = process.env.VERSION;
|
||||
|
||||
@@ -308,6 +344,31 @@ app.get("/version", authenticateJWT, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /releases/rss:
|
||||
* get:
|
||||
* summary: Get releases in RSS format
|
||||
* description: Returns the latest releases from the GitHub repository in an RSS-like JSON format.
|
||||
* tags:
|
||||
* - General
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: page
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: The page number of the releases to fetch.
|
||||
* - in: query
|
||||
* name: per_page
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: The number of releases to fetch per page.
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Releases in RSS format.
|
||||
* 500:
|
||||
* description: Failed to generate RSS format.
|
||||
*/
|
||||
app.get("/releases/rss", authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page as string) || 1;
|
||||
@@ -364,6 +425,20 @@ app.get("/releases/rss", authenticateJWT, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /encryption/status:
|
||||
* get:
|
||||
* summary: Get encryption status
|
||||
* description: Returns the security status of the application.
|
||||
* tags:
|
||||
* - Encryption
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Security status.
|
||||
* 500:
|
||||
* description: Failed to get security status.
|
||||
*/
|
||||
app.get("/encryption/status", requireAdmin, async (req, res) => {
|
||||
try {
|
||||
const securityStatus = {
|
||||
@@ -385,6 +460,20 @@ app.get("/encryption/status", requireAdmin, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /encryption/initialize:
|
||||
* post:
|
||||
* summary: Initialize security system
|
||||
* description: Initializes the security system for the application.
|
||||
* tags:
|
||||
* - Encryption
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Security system initialized successfully.
|
||||
* 500:
|
||||
* description: Failed to initialize security system.
|
||||
*/
|
||||
app.post("/encryption/initialize", requireAdmin, async (req, res) => {
|
||||
try {
|
||||
const authManager = AuthManager.getInstance();
|
||||
@@ -408,6 +497,20 @@ app.post("/encryption/initialize", requireAdmin, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /encryption/regenerate:
|
||||
* post:
|
||||
* summary: Regenerate JWT secret
|
||||
* description: Regenerates the system JWT secret. This will invalidate all existing JWT tokens.
|
||||
* tags:
|
||||
* - Encryption
|
||||
* responses:
|
||||
* 200:
|
||||
* description: System JWT secret regenerated.
|
||||
* 500:
|
||||
* description: Failed to regenerate JWT secret.
|
||||
*/
|
||||
app.post("/encryption/regenerate", requireAdmin, async (req, res) => {
|
||||
try {
|
||||
apiLogger.warn("System JWT secret regenerated via API", {
|
||||
@@ -429,6 +532,20 @@ app.post("/encryption/regenerate", requireAdmin, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /encryption/regenerate-jwt:
|
||||
* post:
|
||||
* summary: Regenerate JWT secret
|
||||
* description: Regenerates the JWT secret. This will invalidate all existing JWT tokens.
|
||||
* tags:
|
||||
* - Encryption
|
||||
* responses:
|
||||
* 200:
|
||||
* description: New JWT secret generated.
|
||||
* 500:
|
||||
* description: Failed to regenerate JWT secret.
|
||||
*/
|
||||
app.post("/encryption/regenerate-jwt", requireAdmin, async (req, res) => {
|
||||
try {
|
||||
apiLogger.warn("JWT secret regenerated via API", {
|
||||
@@ -449,6 +566,33 @@ app.post("/encryption/regenerate-jwt", requireAdmin, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /database/export:
|
||||
* post:
|
||||
* summary: Export user data
|
||||
* description: Exports the user's data as a SQLite database file.
|
||||
* tags:
|
||||
* - Database
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* password:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: User data exported successfully.
|
||||
* 400:
|
||||
* description: Password required for export.
|
||||
* 401:
|
||||
* description: Invalid password.
|
||||
* 500:
|
||||
* description: Failed to export user data.
|
||||
*/
|
||||
app.post("/database/export", authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
const userId = (req as AuthenticatedRequest).userId;
|
||||
@@ -899,6 +1043,36 @@ app.post("/database/export", authenticateJWT, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /database/import:
|
||||
* post:
|
||||
* summary: Import user data
|
||||
* description: Imports user data from a SQLite database file.
|
||||
* tags:
|
||||
* - Database
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* multipart/form-data:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* file:
|
||||
* type: string
|
||||
* format: binary
|
||||
* password:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Incremental import completed successfully.
|
||||
* 400:
|
||||
* description: No file uploaded or password required for import.
|
||||
* 401:
|
||||
* description: Invalid password.
|
||||
* 500:
|
||||
* description: Failed to import SQLite data.
|
||||
*/
|
||||
app.post(
|
||||
"/database/import",
|
||||
authenticateJWT,
|
||||
@@ -1363,6 +1537,31 @@ app.post(
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /database/export/preview:
|
||||
* post:
|
||||
* summary: Preview user data export
|
||||
* description: Generates a preview of the user data export, including statistics about the data.
|
||||
* tags:
|
||||
* - Database
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* scope:
|
||||
* type: string
|
||||
* includeCredentials:
|
||||
* type: boolean
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Export preview generated successfully.
|
||||
* 500:
|
||||
* description: Failed to generate export preview.
|
||||
*/
|
||||
app.post("/database/export/preview", authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
const userId = (req as AuthenticatedRequest).userId;
|
||||
@@ -1398,6 +1597,33 @@ app.post("/database/export/preview", authenticateJWT, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /database/restore:
|
||||
* post:
|
||||
* summary: Restore database from backup
|
||||
* description: Restores the database from an encrypted backup file.
|
||||
* tags:
|
||||
* - Database
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* backupPath:
|
||||
* type: string
|
||||
* targetPath:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Database restored successfully.
|
||||
* 400:
|
||||
* description: Backup path is required or invalid encrypted backup file.
|
||||
* 500:
|
||||
* description: Database restore failed.
|
||||
*/
|
||||
app.post("/database/restore", requireAdmin, async (req, res) => {
|
||||
try {
|
||||
const { backupPath, targetPath } = req.body;
|
||||
@@ -1479,6 +1705,20 @@ async function initializeSecurity() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /database/migration/status:
|
||||
* get:
|
||||
* summary: Get database migration status
|
||||
* description: Returns the status of the database migration.
|
||||
* tags:
|
||||
* - Database
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Migration status.
|
||||
* 500:
|
||||
* description: Failed to get migration status.
|
||||
*/
|
||||
app.get(
|
||||
"/database/migration/status",
|
||||
authenticateJWT,
|
||||
@@ -1532,6 +1772,20 @@ app.get(
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /database/migration/history:
|
||||
* get:
|
||||
* summary: Get database migration history
|
||||
* description: Returns the history of database migrations.
|
||||
* tags:
|
||||
* - Database
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Migration history.
|
||||
* 500:
|
||||
* description: Failed to get migration history.
|
||||
*/
|
||||
app.get(
|
||||
"/database/migration/history",
|
||||
authenticateJWT,
|
||||
|
||||
@@ -671,6 +671,13 @@ const migrateSchema = () => {
|
||||
`);
|
||||
} catch (createError) {
|
||||
databaseLogger.warn("Failed to create network_topology table", {
|
||||
operation: "schema_migration",
|
||||
error: createError,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
sqlite.prepare("SELECT id FROM host_access LIMIT 1").get();
|
||||
} catch {
|
||||
try {
|
||||
|
||||
@@ -301,6 +301,14 @@ export const networkTopology = sqliteTable("network_topology", {
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
topology: text("topology"),
|
||||
createdAt: text("created_at")
|
||||
.notNull()
|
||||
.default(sql`CURRENT_TIMESTAMP`),
|
||||
updatedAt: text("updated_at")
|
||||
.notNull()
|
||||
.default(sql`CURRENT_TIMESTAMP`),
|
||||
});
|
||||
|
||||
export const hostAccess = sqliteTable("host_access", {
|
||||
id: integer("id").primaryKey({ autoIncrement: true }),
|
||||
hostId: integer("host_id")
|
||||
|
||||
@@ -99,8 +99,20 @@ const router = express.Router();
|
||||
const authManager = AuthManager.getInstance();
|
||||
const authenticateJWT = authManager.createAuthMiddleware();
|
||||
|
||||
// Route: Get alerts for the authenticated user (excluding dismissed ones)
|
||||
// GET /alerts
|
||||
/**
|
||||
* @openapi
|
||||
* /alerts:
|
||||
* get:
|
||||
* summary: Get active alerts
|
||||
* description: Fetches active alerts for the authenticated user, excluding those that have been dismissed.
|
||||
* tags:
|
||||
* - Alerts
|
||||
* responses:
|
||||
* 200:
|
||||
* description: A list of active alerts.
|
||||
* 500:
|
||||
* description: Failed to fetch alerts.
|
||||
*/
|
||||
router.get("/", authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
const userId = (req as AuthenticatedRequest).userId;
|
||||
@@ -131,8 +143,33 @@ router.get("/", authenticateJWT, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Route: Dismiss an alert for the authenticated user
|
||||
// POST /alerts/dismiss
|
||||
/**
|
||||
* @openapi
|
||||
* /alerts/dismiss:
|
||||
* post:
|
||||
* summary: Dismiss an alert
|
||||
* description: Marks an alert as dismissed for the authenticated user.
|
||||
* tags:
|
||||
* - Alerts
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* alertId:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Alert dismissed successfully.
|
||||
* 400:
|
||||
* description: Alert ID is required.
|
||||
* 409:
|
||||
* description: Alert already dismissed.
|
||||
* 500:
|
||||
* description: Failed to dismiss alert.
|
||||
*/
|
||||
router.post("/dismiss", authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
const { alertId } = req.body;
|
||||
@@ -170,8 +207,20 @@ router.post("/dismiss", authenticateJWT, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Route: Get dismissed alerts for a user
|
||||
// GET /alerts/dismissed/:userId
|
||||
/**
|
||||
* @openapi
|
||||
* /alerts/dismissed:
|
||||
* get:
|
||||
* summary: Get dismissed alerts
|
||||
* description: Fetches a list of alerts that have been dismissed by the authenticated user.
|
||||
* tags:
|
||||
* - Alerts
|
||||
* responses:
|
||||
* 200:
|
||||
* description: A list of dismissed alerts.
|
||||
* 500:
|
||||
* description: Failed to fetch dismissed alerts.
|
||||
*/
|
||||
router.get("/dismissed", authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
const userId = (req as AuthenticatedRequest).userId;
|
||||
@@ -194,8 +243,33 @@ router.get("/dismissed", authenticateJWT, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Route: Undismiss an alert for the authenticated user (remove from dismissed list)
|
||||
// DELETE /alerts/dismiss
|
||||
/**
|
||||
* @openapi
|
||||
* /alerts/dismiss:
|
||||
* delete:
|
||||
* summary: Undismiss an alert
|
||||
* description: Removes an alert from the dismissed list for the authenticated user.
|
||||
* tags:
|
||||
* - Alerts
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* alertId:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Alert undismissed successfully.
|
||||
* 400:
|
||||
* description: Alert ID is required.
|
||||
* 404:
|
||||
* description: Dismissed alert not found.
|
||||
* 500:
|
||||
* description: Failed to undismiss alert.
|
||||
*/
|
||||
router.delete("/dismiss", authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
const { alertId } = req.body;
|
||||
|
||||
@@ -84,8 +84,52 @@ const authManager = AuthManager.getInstance();
|
||||
const authenticateJWT = authManager.createAuthMiddleware();
|
||||
const requireDataAccess = authManager.createDataAccessMiddleware();
|
||||
|
||||
// Create a new credential
|
||||
// POST /credentials
|
||||
/**
|
||||
* @openapi
|
||||
* /credentials:
|
||||
* post:
|
||||
* summary: Create a new credential
|
||||
* description: Creates a new SSH credential for the authenticated user.
|
||||
* tags:
|
||||
* - Credentials
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* name:
|
||||
* type: string
|
||||
* description:
|
||||
* type: string
|
||||
* folder:
|
||||
* type: string
|
||||
* tags:
|
||||
* type: array
|
||||
* items:
|
||||
* type: string
|
||||
* authType:
|
||||
* type: string
|
||||
* enum: [password, key]
|
||||
* username:
|
||||
* type: string
|
||||
* password:
|
||||
* type: string
|
||||
* key:
|
||||
* type: string
|
||||
* keyPassword:
|
||||
* type: string
|
||||
* keyType:
|
||||
* type: string
|
||||
* responses:
|
||||
* 201:
|
||||
* description: Credential created successfully.
|
||||
* 400:
|
||||
* description: Invalid request body.
|
||||
* 500:
|
||||
* description: Failed to create credential.
|
||||
*/
|
||||
router.post(
|
||||
"/",
|
||||
authenticateJWT,
|
||||
@@ -231,8 +275,22 @@ router.post(
|
||||
},
|
||||
);
|
||||
|
||||
// Get all credentials for the authenticated user
|
||||
// GET /credentials
|
||||
/**
|
||||
* @openapi
|
||||
* /credentials:
|
||||
* get:
|
||||
* summary: Get all credentials
|
||||
* description: Retrieves all SSH credentials for the authenticated user.
|
||||
* tags:
|
||||
* - Credentials
|
||||
* responses:
|
||||
* 200:
|
||||
* description: A list of credentials.
|
||||
* 400:
|
||||
* description: Invalid userId.
|
||||
* 500:
|
||||
* description: Failed to fetch credentials.
|
||||
*/
|
||||
router.get(
|
||||
"/",
|
||||
authenticateJWT,
|
||||
@@ -264,8 +322,22 @@ router.get(
|
||||
},
|
||||
);
|
||||
|
||||
// Get all unique credential folders for the authenticated user
|
||||
// GET /credentials/folders
|
||||
/**
|
||||
* @openapi
|
||||
* /credentials/folders:
|
||||
* get:
|
||||
* summary: Get credential folders
|
||||
* description: Retrieves all unique credential folders for the authenticated user.
|
||||
* tags:
|
||||
* - Credentials
|
||||
* responses:
|
||||
* 200:
|
||||
* description: A list of folder names.
|
||||
* 400:
|
||||
* description: Invalid userId.
|
||||
* 500:
|
||||
* description: Failed to fetch credential folders.
|
||||
*/
|
||||
router.get(
|
||||
"/folders",
|
||||
authenticateJWT,
|
||||
@@ -302,8 +374,30 @@ router.get(
|
||||
},
|
||||
);
|
||||
|
||||
// Get a specific credential by ID (with plain text secrets)
|
||||
// GET /credentials/:id
|
||||
/**
|
||||
* @openapi
|
||||
* /credentials/{id}:
|
||||
* get:
|
||||
* summary: Get a specific credential
|
||||
* description: Retrieves a specific credential by its ID, including secrets.
|
||||
* tags:
|
||||
* - Credentials
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The requested credential.
|
||||
* 400:
|
||||
* description: Invalid request.
|
||||
* 404:
|
||||
* description: Credential not found.
|
||||
* 500:
|
||||
* description: Failed to fetch credential.
|
||||
*/
|
||||
router.get(
|
||||
"/:id",
|
||||
authenticateJWT,
|
||||
@@ -366,8 +460,41 @@ router.get(
|
||||
},
|
||||
);
|
||||
|
||||
// Update a credential
|
||||
// PUT /credentials/:id
|
||||
/**
|
||||
* @openapi
|
||||
* /credentials/{id}:
|
||||
* put:
|
||||
* summary: Update a credential
|
||||
* description: Updates a specific credential by its ID.
|
||||
* tags:
|
||||
* - Credentials
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* name:
|
||||
* type: string
|
||||
* description:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The updated credential.
|
||||
* 400:
|
||||
* description: Invalid request.
|
||||
* 404:
|
||||
* description: Credential not found.
|
||||
* 500:
|
||||
* description: Failed to update credential.
|
||||
*/
|
||||
router.put(
|
||||
"/:id",
|
||||
authenticateJWT,
|
||||
@@ -510,8 +637,30 @@ router.put(
|
||||
},
|
||||
);
|
||||
|
||||
// Delete a credential
|
||||
// DELETE /credentials/:id
|
||||
/**
|
||||
* @openapi
|
||||
* /credentials/{id}:
|
||||
* delete:
|
||||
* summary: Delete a credential
|
||||
* description: Deletes a specific credential by its ID.
|
||||
* tags:
|
||||
* - Credentials
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Credential deleted successfully.
|
||||
* 400:
|
||||
* description: Invalid request.
|
||||
* 404:
|
||||
* description: Credential not found.
|
||||
* 500:
|
||||
* description: Failed to delete credential.
|
||||
*/
|
||||
router.delete(
|
||||
"/:id",
|
||||
authenticateJWT,
|
||||
@@ -626,8 +775,35 @@ router.delete(
|
||||
},
|
||||
);
|
||||
|
||||
// Apply a credential to an SSH host (for quick application)
|
||||
// POST /credentials/:id/apply-to-host/:hostId
|
||||
/**
|
||||
* @openapi
|
||||
* /credentials/{id}/apply-to-host/{hostId}:
|
||||
* post:
|
||||
* summary: Apply a credential to a host
|
||||
* description: Applies a credential to an SSH host for quick application.
|
||||
* tags:
|
||||
* - Credentials
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* - in: path
|
||||
* name: hostId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Credential applied to host successfully.
|
||||
* 400:
|
||||
* description: Invalid request.
|
||||
* 404:
|
||||
* description: Credential not found.
|
||||
* 500:
|
||||
* description: Failed to apply credential to host.
|
||||
*/
|
||||
router.post(
|
||||
"/:id/apply-to-host/:hostId",
|
||||
authenticateJWT,
|
||||
@@ -705,8 +881,28 @@ router.post(
|
||||
},
|
||||
);
|
||||
|
||||
// Get hosts using a specific credential
|
||||
// GET /credentials/:id/hosts
|
||||
/**
|
||||
* @openapi
|
||||
* /credentials/{id}/hosts:
|
||||
* get:
|
||||
* summary: Get hosts using a credential
|
||||
* description: Retrieves a list of hosts that are using a specific credential.
|
||||
* tags:
|
||||
* - Credentials
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 200:
|
||||
* description: A list of hosts.
|
||||
* 400:
|
||||
* description: Invalid request.
|
||||
* 500:
|
||||
* description: Failed to fetch hosts using credential.
|
||||
*/
|
||||
router.get(
|
||||
"/:id/hosts",
|
||||
authenticateJWT,
|
||||
@@ -800,8 +996,33 @@ function formatSSHHostOutput(
|
||||
};
|
||||
}
|
||||
|
||||
// Rename a credential folder
|
||||
// PUT /credentials/folders/rename
|
||||
/**
|
||||
* @openapi
|
||||
* /credentials/folders/rename:
|
||||
* put:
|
||||
* summary: Rename a credential folder
|
||||
* description: Renames a credential folder for the authenticated user.
|
||||
* tags:
|
||||
* - Credentials
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* oldName:
|
||||
* type: string
|
||||
* newName:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Folder renamed successfully.
|
||||
* 400:
|
||||
* description: Both oldName and newName are required.
|
||||
* 500:
|
||||
* description: Failed to rename folder.
|
||||
*/
|
||||
router.put(
|
||||
"/folders/rename",
|
||||
authenticateJWT,
|
||||
@@ -840,8 +1061,33 @@ router.put(
|
||||
},
|
||||
);
|
||||
|
||||
// Detect SSH key type endpoint
|
||||
// POST /credentials/detect-key-type
|
||||
/**
|
||||
* @openapi
|
||||
* /credentials/detect-key-type:
|
||||
* post:
|
||||
* summary: Detect SSH key type
|
||||
* description: Detects the type of an SSH private key.
|
||||
* tags:
|
||||
* - Credentials
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* privateKey:
|
||||
* type: string
|
||||
* keyPassword:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Key type detection result.
|
||||
* 400:
|
||||
* description: Private key is required.
|
||||
* 500:
|
||||
* description: Failed to detect key type.
|
||||
*/
|
||||
router.post(
|
||||
"/detect-key-type",
|
||||
authenticateJWT,
|
||||
@@ -874,8 +1120,31 @@ router.post(
|
||||
},
|
||||
);
|
||||
|
||||
// Detect SSH public key type endpoint
|
||||
// POST /credentials/detect-public-key-type
|
||||
/**
|
||||
* @openapi
|
||||
* /credentials/detect-public-key-type:
|
||||
* post:
|
||||
* summary: Detect SSH public key type
|
||||
* description: Detects the type of an SSH public key.
|
||||
* tags:
|
||||
* - Credentials
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* publicKey:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Key type detection result.
|
||||
* 400:
|
||||
* description: Public key is required.
|
||||
* 500:
|
||||
* description: Failed to detect public key type.
|
||||
*/
|
||||
router.post(
|
||||
"/detect-public-key-type",
|
||||
authenticateJWT,
|
||||
@@ -909,8 +1178,35 @@ router.post(
|
||||
},
|
||||
);
|
||||
|
||||
// Validate SSH key pair endpoint
|
||||
// POST /credentials/validate-key-pair
|
||||
/**
|
||||
* @openapi
|
||||
* /credentials/validate-key-pair:
|
||||
* post:
|
||||
* summary: Validate SSH key pair
|
||||
* description: Validates if a given SSH private key and public key match.
|
||||
* tags:
|
||||
* - Credentials
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* privateKey:
|
||||
* type: string
|
||||
* publicKey:
|
||||
* type: string
|
||||
* keyPassword:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Key pair validation result.
|
||||
* 400:
|
||||
* description: Private key and public key are required.
|
||||
* 500:
|
||||
* description: Failed to validate key pair.
|
||||
*/
|
||||
router.post(
|
||||
"/validate-key-pair",
|
||||
authenticateJWT,
|
||||
@@ -953,8 +1249,32 @@ router.post(
|
||||
},
|
||||
);
|
||||
|
||||
// Generate new SSH key pair endpoint
|
||||
// POST /credentials/generate-key-pair
|
||||
/**
|
||||
* @openapi
|
||||
* /credentials/generate-key-pair:
|
||||
* post:
|
||||
* summary: Generate new SSH key pair
|
||||
* description: Generates a new SSH key pair.
|
||||
* tags:
|
||||
* - Credentials
|
||||
* requestBody:
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* keyType:
|
||||
* type: string
|
||||
* keySize:
|
||||
* type: integer
|
||||
* passphrase:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The new key pair.
|
||||
* 500:
|
||||
* description: Failed to generate SSH key pair.
|
||||
*/
|
||||
router.post(
|
||||
"/generate-key-pair",
|
||||
authenticateJWT,
|
||||
@@ -996,8 +1316,33 @@ router.post(
|
||||
},
|
||||
);
|
||||
|
||||
// Generate public key from private key endpoint
|
||||
// POST /credentials/generate-public-key
|
||||
/**
|
||||
* @openapi
|
||||
* /credentials/generate-public-key:
|
||||
* post:
|
||||
* summary: Generate public key from private key
|
||||
* description: Generates a public key from a given private key.
|
||||
* tags:
|
||||
* - Credentials
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* privateKey:
|
||||
* type: string
|
||||
* keyPassword:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The generated public key.
|
||||
* 400:
|
||||
* description: Private key is required.
|
||||
* 500:
|
||||
* description: Failed to generate public key.
|
||||
*/
|
||||
router.post(
|
||||
"/generate-public-key",
|
||||
authenticateJWT,
|
||||
@@ -1283,7 +1628,7 @@ async function deploySSHKeyToHost(
|
||||
.replace(/'/g, "'\\''");
|
||||
|
||||
conn.exec(
|
||||
`printf '%s\\n' '${escapedKey} ${credData.name}@Termix' >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys`,
|
||||
`printf '%s\n' '${escapedKey} ${credData.name}@Termix' >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys`,
|
||||
(err, stream) => {
|
||||
if (err) {
|
||||
clearTimeout(addTimeout);
|
||||
@@ -1502,8 +1847,41 @@ async function deploySSHKeyToHost(
|
||||
});
|
||||
}
|
||||
|
||||
// Deploy SSH Key to Host endpoint
|
||||
// POST /credentials/:id/deploy-to-host
|
||||
/**
|
||||
* @openapi
|
||||
* /credentials/{id}/deploy-to-host:
|
||||
* post:
|
||||
* summary: Deploy SSH key to a host
|
||||
* description: Deploys an SSH public key to a target host's authorized_keys file.
|
||||
* tags:
|
||||
* - Credentials
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* targetHostId:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 200:
|
||||
* description: SSH key deployed successfully.
|
||||
* 400:
|
||||
* description: Credential ID and target host ID are required.
|
||||
* 401:
|
||||
* description: Authentication required.
|
||||
* 404:
|
||||
* description: Credential or target host not found.
|
||||
* 500:
|
||||
* description: Failed to deploy SSH key.
|
||||
*/
|
||||
router.post(
|
||||
"/:id/deploy-to-host",
|
||||
authenticateJWT,
|
||||
|
||||
@@ -9,6 +9,22 @@ const router = express.Router();
|
||||
const authManager = AuthManager.getInstance();
|
||||
const authenticateJWT = authManager.createAuthMiddleware();
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /network-topology:
|
||||
* get:
|
||||
* summary: Get network topology
|
||||
* description: Retrieves the network topology for the authenticated user.
|
||||
* tags:
|
||||
* - Network Topology
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The network topology.
|
||||
* 401:
|
||||
* description: User not authenticated.
|
||||
* 500:
|
||||
* description: Failed to fetch network topology.
|
||||
*/
|
||||
router.get(
|
||||
"/",
|
||||
authenticateJWT,
|
||||
@@ -24,7 +40,7 @@ router.get(
|
||||
.select()
|
||||
.from(networkTopology)
|
||||
.where(eq(networkTopology.userId, userId));
|
||||
|
||||
|
||||
if (result.length > 0) {
|
||||
const topologyStr = result[0].topology;
|
||||
const topology = topologyStr ? JSON.parse(topologyStr) : null;
|
||||
@@ -34,11 +50,43 @@ router.get(
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching network topology:", error);
|
||||
return res.status(500).json({ error: "Failed to fetch network topology", details: (error as Error).message });
|
||||
return res
|
||||
.status(500)
|
||||
.json({
|
||||
error: "Failed to fetch network topology",
|
||||
details: (error as Error).message,
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /network-topology:
|
||||
* post:
|
||||
* summary: Save network topology
|
||||
* description: Saves the network topology for the authenticated user.
|
||||
* tags:
|
||||
* - Network Topology
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* topology:
|
||||
* type: object
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Network topology saved successfully.
|
||||
* 400:
|
||||
* description: Topology data is required.
|
||||
* 401:
|
||||
* description: User not authenticated.
|
||||
* 500:
|
||||
* description: Failed to save network topology.
|
||||
*/
|
||||
router.post(
|
||||
"/",
|
||||
authenticateJWT,
|
||||
@@ -55,15 +103,16 @@ router.post(
|
||||
}
|
||||
|
||||
const db = getDb();
|
||||
|
||||
|
||||
// Ensure topology is a string
|
||||
const topologyStr = typeof topology === 'string' ? topology : JSON.stringify(topology);
|
||||
|
||||
const topologyStr =
|
||||
typeof topology === "string" ? topology : JSON.stringify(topology);
|
||||
|
||||
const existing = await db
|
||||
.select()
|
||||
.from(networkTopology)
|
||||
.where(eq(networkTopology.userId, userId));
|
||||
|
||||
|
||||
if (existing.length > 0) {
|
||||
// Update existing record
|
||||
await db
|
||||
@@ -76,11 +125,16 @@ router.post(
|
||||
.insert(networkTopology)
|
||||
.values({ userId, topology: topologyStr });
|
||||
}
|
||||
|
||||
|
||||
return res.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error("Error saving network topology:", error);
|
||||
return res.status(500).json({ error: "Failed to save network topology", details: (error as Error).message });
|
||||
return res
|
||||
.status(500)
|
||||
.json({
|
||||
error: "Failed to save network topology",
|
||||
details: (error as Error).message,
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -27,8 +27,51 @@ function isNonEmptyString(value: unknown): value is string {
|
||||
return typeof value === "string" && value.trim().length > 0;
|
||||
}
|
||||
|
||||
//Share a host with a user or role
|
||||
//POST /rbac/host/:id/share
|
||||
/**
|
||||
* @openapi
|
||||
* /rbac/host/{id}/share:
|
||||
* post:
|
||||
* summary: Share a host
|
||||
* description: Shares a host with a user or a role.
|
||||
* tags:
|
||||
* - RBAC
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* targetType:
|
||||
* type: string
|
||||
* enum: [user, role]
|
||||
* targetUserId:
|
||||
* type: string
|
||||
* targetRoleId:
|
||||
* type: integer
|
||||
* durationHours:
|
||||
* type: number
|
||||
* permissionLevel:
|
||||
* type: string
|
||||
* enum: [view]
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Host shared successfully.
|
||||
* 400:
|
||||
* description: Invalid request body.
|
||||
* 403:
|
||||
* description: Not host owner.
|
||||
* 404:
|
||||
* description: Target user or role not found.
|
||||
* 500:
|
||||
* description: Failed to share host.
|
||||
*/
|
||||
router.post(
|
||||
"/host/:id/share",
|
||||
authenticateJWT,
|
||||
@@ -227,8 +270,35 @@ router.post(
|
||||
},
|
||||
);
|
||||
|
||||
// Revoke host access
|
||||
// DELETE /rbac/host/:id/access/:accessId
|
||||
/**
|
||||
* @openapi
|
||||
* /rbac/host/{id}/access/{accessId}:
|
||||
* delete:
|
||||
* summary: Revoke host access
|
||||
* description: Revokes a user's or role's access to a host.
|
||||
* tags:
|
||||
* - RBAC
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* - in: path
|
||||
* name: accessId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Access revoked successfully.
|
||||
* 400:
|
||||
* description: Invalid ID.
|
||||
* 403:
|
||||
* description: Not host owner.
|
||||
* 500:
|
||||
* description: Failed to revoke access.
|
||||
*/
|
||||
router.delete(
|
||||
"/host/:id/access/:accessId",
|
||||
authenticateJWT,
|
||||
@@ -267,8 +337,30 @@ router.delete(
|
||||
},
|
||||
);
|
||||
|
||||
// Get host access list
|
||||
// GET /rbac/host/:id/access
|
||||
/**
|
||||
* @openapi
|
||||
* /rbac/host/{id}/access:
|
||||
* get:
|
||||
* summary: Get host access list
|
||||
* description: Retrieves the list of users and roles that have access to a host.
|
||||
* tags:
|
||||
* - RBAC
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The access list for the host.
|
||||
* 400:
|
||||
* description: Invalid host ID.
|
||||
* 403:
|
||||
* description: Not host owner.
|
||||
* 500:
|
||||
* description: Failed to get access list.
|
||||
*/
|
||||
router.get(
|
||||
"/host/:id/access",
|
||||
authenticateJWT,
|
||||
@@ -338,8 +430,20 @@ router.get(
|
||||
},
|
||||
);
|
||||
|
||||
// Get user's shared hosts (hosts shared WITH this user)
|
||||
// GET /rbac/shared-hosts
|
||||
/**
|
||||
* @openapi
|
||||
* /rbac/shared-hosts:
|
||||
* get:
|
||||
* summary: Get shared hosts
|
||||
* description: Retrieves the list of hosts that have been shared with the authenticated user.
|
||||
* tags:
|
||||
* - RBAC
|
||||
* responses:
|
||||
* 200:
|
||||
* description: A list of shared hosts.
|
||||
* 500:
|
||||
* description: Failed to get shared hosts.
|
||||
*/
|
||||
router.get(
|
||||
"/shared-hosts",
|
||||
authenticateJWT,
|
||||
@@ -385,8 +489,20 @@ router.get(
|
||||
},
|
||||
);
|
||||
|
||||
// Get all roles
|
||||
// GET /rbac/roles
|
||||
/**
|
||||
* @openapi
|
||||
* /rbac/roles:
|
||||
* get:
|
||||
* summary: Get all roles
|
||||
* description: Retrieves a list of all roles.
|
||||
* tags:
|
||||
* - RBAC
|
||||
* responses:
|
||||
* 200:
|
||||
* description: A list of roles.
|
||||
* 500:
|
||||
* description: Failed to get roles.
|
||||
*/
|
||||
router.get(
|
||||
"/roles",
|
||||
authenticateJWT,
|
||||
@@ -413,8 +529,20 @@ router.get(
|
||||
},
|
||||
);
|
||||
|
||||
// Get all roles
|
||||
// GET /rbac/roles
|
||||
/**
|
||||
* @openapi
|
||||
* /rbac/roles:
|
||||
* get:
|
||||
* summary: Get all roles
|
||||
* description: Retrieves a list of all roles.
|
||||
* tags:
|
||||
* - RBAC
|
||||
* responses:
|
||||
* 200:
|
||||
* description: A list of roles.
|
||||
* 500:
|
||||
* description: Failed to get roles.
|
||||
*/
|
||||
router.get(
|
||||
"/roles",
|
||||
authenticateJWT,
|
||||
@@ -443,8 +571,37 @@ router.get(
|
||||
},
|
||||
);
|
||||
|
||||
// Create new role
|
||||
// POST /rbac/roles
|
||||
/**
|
||||
* @openapi
|
||||
* /rbac/roles:
|
||||
* post:
|
||||
* summary: Create a new role
|
||||
* description: Creates a new role.
|
||||
* tags:
|
||||
* - RBAC
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* name:
|
||||
* type: string
|
||||
* displayName:
|
||||
* type: string
|
||||
* description:
|
||||
* type: string
|
||||
* responses:
|
||||
* 201:
|
||||
* description: Role created successfully.
|
||||
* 400:
|
||||
* description: Invalid request body.
|
||||
* 409:
|
||||
* description: A role with this name already exists.
|
||||
* 500:
|
||||
* description: Failed to create role.
|
||||
*/
|
||||
router.post(
|
||||
"/roles",
|
||||
authenticateJWT,
|
||||
@@ -503,8 +660,41 @@ router.post(
|
||||
},
|
||||
);
|
||||
|
||||
// Update role
|
||||
// PUT /rbac/roles/:id
|
||||
/**
|
||||
* @openapi
|
||||
* /rbac/roles/{id}:
|
||||
* put:
|
||||
* summary: Update a role
|
||||
* description: Updates a role by its ID.
|
||||
* tags:
|
||||
* - RBAC
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* displayName:
|
||||
* type: string
|
||||
* description:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Role updated successfully.
|
||||
* 400:
|
||||
* description: Invalid request body or role ID.
|
||||
* 404:
|
||||
* description: Role not found.
|
||||
* 500:
|
||||
* description: Failed to update role.
|
||||
*/
|
||||
router.put(
|
||||
"/roles/:id",
|
||||
authenticateJWT,
|
||||
@@ -570,8 +760,32 @@ router.put(
|
||||
},
|
||||
);
|
||||
|
||||
// Delete role
|
||||
// DELETE /rbac/roles/:id
|
||||
/**
|
||||
* @openapi
|
||||
* /rbac/roles/{id}:
|
||||
* delete:
|
||||
* summary: Delete a role
|
||||
* description: Deletes a role by its ID.
|
||||
* tags:
|
||||
* - RBAC
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Role deleted successfully.
|
||||
* 400:
|
||||
* description: Invalid role ID.
|
||||
* 403:
|
||||
* description: Cannot delete system roles.
|
||||
* 404:
|
||||
* description: Role not found.
|
||||
* 500:
|
||||
* description: Failed to delete role.
|
||||
*/
|
||||
router.delete(
|
||||
"/roles/:id",
|
||||
authenticateJWT,
|
||||
@@ -634,8 +848,43 @@ router.delete(
|
||||
},
|
||||
);
|
||||
|
||||
// Assign role to user
|
||||
// POST /rbac/users/:userId/roles
|
||||
/**
|
||||
* @openapi
|
||||
* /rbac/users/{userId}/roles:
|
||||
* post:
|
||||
* summary: Assign a role to a user
|
||||
* description: Assigns a role to a user.
|
||||
* tags:
|
||||
* - RBAC
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: userId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* roleId:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Role assigned successfully.
|
||||
* 400:
|
||||
* description: Role ID is required.
|
||||
* 403:
|
||||
* description: System roles cannot be manually assigned.
|
||||
* 404:
|
||||
* description: User or role not found.
|
||||
* 409:
|
||||
* description: Role already assigned.
|
||||
* 500:
|
||||
* description: Failed to assign role.
|
||||
*/
|
||||
router.post(
|
||||
"/users/:userId/roles",
|
||||
authenticateJWT,
|
||||
@@ -746,8 +995,37 @@ router.post(
|
||||
},
|
||||
);
|
||||
|
||||
// Remove role from user
|
||||
// DELETE /rbac/users/:userId/roles/:roleId
|
||||
/**
|
||||
* @openapi
|
||||
* /rbac/users/{userId}/roles/{roleId}:
|
||||
* delete:
|
||||
* summary: Remove a role from a user
|
||||
* description: Removes a role from a user.
|
||||
* tags:
|
||||
* - RBAC
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: userId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* - in: path
|
||||
* name: roleId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Role removed successfully.
|
||||
* 400:
|
||||
* description: Invalid role ID.
|
||||
* 403:
|
||||
* description: System roles cannot be removed.
|
||||
* 404:
|
||||
* description: Role not found.
|
||||
* 500:
|
||||
* description: Failed to remove role.
|
||||
*/
|
||||
router.delete(
|
||||
"/users/:userId/roles/:roleId",
|
||||
authenticateJWT,
|
||||
@@ -805,8 +1083,28 @@ router.delete(
|
||||
},
|
||||
);
|
||||
|
||||
// Get user's roles
|
||||
// GET /rbac/users/:userId/roles
|
||||
/**
|
||||
* @openapi
|
||||
* /rbac/users/{userId}/roles:
|
||||
* get:
|
||||
* summary: Get user's roles
|
||||
* description: Retrieves a list of roles for a specific user.
|
||||
* tags:
|
||||
* - RBAC
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: userId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: A list of roles.
|
||||
* 403:
|
||||
* description: Access denied.
|
||||
* 500:
|
||||
* description: Failed to get user roles.
|
||||
*/
|
||||
router.get(
|
||||
"/users/:userId/roles",
|
||||
authenticateJWT,
|
||||
|
||||
@@ -17,8 +17,22 @@ const authManager = AuthManager.getInstance();
|
||||
const authenticateJWT = authManager.createAuthMiddleware();
|
||||
const requireDataAccess = authManager.createDataAccessMiddleware();
|
||||
|
||||
// Get all snippet folders
|
||||
// GET /snippets/folders
|
||||
/**
|
||||
* @openapi
|
||||
* /snippets/folders:
|
||||
* get:
|
||||
* summary: Get all snippet folders
|
||||
* description: Retrieves all snippet folders for the authenticated user.
|
||||
* tags:
|
||||
* - Snippets
|
||||
* responses:
|
||||
* 200:
|
||||
* description: A list of snippet folders.
|
||||
* 400:
|
||||
* description: Invalid userId.
|
||||
* 500:
|
||||
* description: Failed to fetch snippet folders.
|
||||
*/
|
||||
router.get(
|
||||
"/folders",
|
||||
authenticateJWT,
|
||||
@@ -46,8 +60,37 @@ router.get(
|
||||
},
|
||||
);
|
||||
|
||||
// Create a new snippet folder
|
||||
// POST /snippets/folders
|
||||
/**
|
||||
* @openapi
|
||||
* /snippets/folders:
|
||||
* post:
|
||||
* summary: Create a new snippet folder
|
||||
* description: Creates a new snippet folder for the authenticated user.
|
||||
* tags:
|
||||
* - Snippets
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* name:
|
||||
* type: string
|
||||
* color:
|
||||
* type: string
|
||||
* icon:
|
||||
* type: string
|
||||
* responses:
|
||||
* 201:
|
||||
* description: Snippet folder created successfully.
|
||||
* 400:
|
||||
* description: Folder name is required.
|
||||
* 409:
|
||||
* description: Folder with this name already exists.
|
||||
* 500:
|
||||
* description: Failed to create snippet folder.
|
||||
*/
|
||||
router.post(
|
||||
"/folders",
|
||||
authenticateJWT,
|
||||
@@ -110,8 +153,41 @@ router.post(
|
||||
},
|
||||
);
|
||||
|
||||
// Update snippet folder metadata (color, icon)
|
||||
// PUT /snippets/folders/:name/metadata
|
||||
/**
|
||||
* @openapi
|
||||
* /snippets/folders/{name}/metadata:
|
||||
* put:
|
||||
* summary: Update snippet folder metadata
|
||||
* description: Updates the metadata (color, icon) of a snippet folder.
|
||||
* tags:
|
||||
* - Snippets
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: name
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* color:
|
||||
* type: string
|
||||
* icon:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Snippet folder metadata updated successfully.
|
||||
* 400:
|
||||
* description: Invalid request.
|
||||
* 404:
|
||||
* description: Folder not found.
|
||||
* 500:
|
||||
* description: Failed to update snippet folder metadata.
|
||||
*/
|
||||
router.put(
|
||||
"/folders/:name/metadata",
|
||||
authenticateJWT,
|
||||
@@ -194,8 +270,37 @@ router.put(
|
||||
},
|
||||
);
|
||||
|
||||
// Rename snippet folder
|
||||
// PUT /snippets/folders/rename
|
||||
/**
|
||||
* @openapi
|
||||
* /snippets/folders/rename:
|
||||
* put:
|
||||
* summary: Rename a snippet folder
|
||||
* description: Renames a snippet folder for the authenticated user.
|
||||
* tags:
|
||||
* - Snippets
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* oldName:
|
||||
* type: string
|
||||
* newName:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Folder renamed successfully.
|
||||
* 400:
|
||||
* description: Invalid request.
|
||||
* 404:
|
||||
* description: Folder not found.
|
||||
* 409:
|
||||
* description: Folder with new name already exists.
|
||||
* 500:
|
||||
* description: Failed to rename snippet folder.
|
||||
*/
|
||||
router.put(
|
||||
"/folders/rename",
|
||||
authenticateJWT,
|
||||
@@ -282,8 +387,28 @@ router.put(
|
||||
},
|
||||
);
|
||||
|
||||
// Delete snippet folder
|
||||
// DELETE /snippets/folders/:name
|
||||
/**
|
||||
* @openapi
|
||||
* /snippets/folders/{name}:
|
||||
* delete:
|
||||
* summary: Delete a snippet folder
|
||||
* description: Deletes a snippet folder and moves its snippets to the root.
|
||||
* tags:
|
||||
* - Snippets
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: name
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Snippet folder deleted successfully.
|
||||
* 400:
|
||||
* description: Invalid request.
|
||||
* 500:
|
||||
* description: Failed to delete snippet folder.
|
||||
*/
|
||||
router.delete(
|
||||
"/folders/:name",
|
||||
authenticateJWT,
|
||||
@@ -338,8 +463,40 @@ router.delete(
|
||||
},
|
||||
);
|
||||
|
||||
// Reorder snippets (bulk update)
|
||||
// PUT /snippets/reorder
|
||||
/**
|
||||
* @openapi
|
||||
* /snippets/reorder:
|
||||
* put:
|
||||
* summary: Reorder snippets
|
||||
* description: Bulk updates the order and folder of snippets.
|
||||
* tags:
|
||||
* - Snippets
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* snippets:
|
||||
* type: array
|
||||
* items:
|
||||
* type: object
|
||||
* properties:
|
||||
* id:
|
||||
* type: integer
|
||||
* order:
|
||||
* type: integer
|
||||
* folder:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Snippets reordered successfully.
|
||||
* 400:
|
||||
* description: Invalid request.
|
||||
* 500:
|
||||
* description: Failed to reorder snippets.
|
||||
*/
|
||||
router.put(
|
||||
"/reorder",
|
||||
authenticateJWT,
|
||||
@@ -405,8 +562,35 @@ router.put(
|
||||
},
|
||||
);
|
||||
|
||||
// Execute a snippet on a host
|
||||
// POST /snippets/execute
|
||||
/**
|
||||
* @openapi
|
||||
* /snippets/execute:
|
||||
* post:
|
||||
* summary: Execute a snippet on a host
|
||||
* description: Executes a snippet on a specified host.
|
||||
* tags:
|
||||
* - Snippets
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* snippetId:
|
||||
* type: integer
|
||||
* hostId:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Snippet executed successfully.
|
||||
* 400:
|
||||
* description: Snippet ID and Host ID are required.
|
||||
* 404:
|
||||
* description: Snippet or host not found.
|
||||
* 500:
|
||||
* description: Failed to execute snippet.
|
||||
*/
|
||||
router.post(
|
||||
"/execute",
|
||||
authenticateJWT,
|
||||
@@ -662,8 +846,22 @@ router.post(
|
||||
},
|
||||
);
|
||||
|
||||
// Get all snippets for the authenticated user
|
||||
// GET /snippets
|
||||
/**
|
||||
* @openapi
|
||||
* /snippets:
|
||||
* get:
|
||||
* summary: Get all snippets
|
||||
* description: Retrieves all snippets for the authenticated user.
|
||||
* tags:
|
||||
* - Snippets
|
||||
* responses:
|
||||
* 200:
|
||||
* description: A list of snippets.
|
||||
* 400:
|
||||
* description: Invalid userId.
|
||||
* 500:
|
||||
* description: Failed to fetch snippets.
|
||||
*/
|
||||
router.get(
|
||||
"/",
|
||||
authenticateJWT,
|
||||
@@ -696,8 +894,30 @@ router.get(
|
||||
},
|
||||
);
|
||||
|
||||
// Get a specific snippet by ID
|
||||
// GET /snippets/:id
|
||||
/**
|
||||
* @openapi
|
||||
* /snippets/{id}:
|
||||
* get:
|
||||
* summary: Get a specific snippet
|
||||
* description: Retrieves a specific snippet by its ID.
|
||||
* tags:
|
||||
* - Snippets
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The requested snippet.
|
||||
* 400:
|
||||
* description: Invalid request parameters.
|
||||
* 404:
|
||||
* description: Snippet not found.
|
||||
* 500:
|
||||
* description: Failed to fetch snippet.
|
||||
*/
|
||||
router.get(
|
||||
"/:id",
|
||||
authenticateJWT,
|
||||
@@ -735,8 +955,39 @@ router.get(
|
||||
},
|
||||
);
|
||||
|
||||
// Create a new snippet
|
||||
// POST /snippets
|
||||
/**
|
||||
* @openapi
|
||||
* /snippets:
|
||||
* post:
|
||||
* summary: Create a new snippet
|
||||
* description: Creates a new snippet for the authenticated user.
|
||||
* tags:
|
||||
* - Snippets
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* name:
|
||||
* type: string
|
||||
* content:
|
||||
* type: string
|
||||
* description:
|
||||
* type: string
|
||||
* folder:
|
||||
* type: string
|
||||
* order:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 201:
|
||||
* description: Snippet created successfully.
|
||||
* 400:
|
||||
* description: Name and content are required.
|
||||
* 500:
|
||||
* description: Failed to create snippet.
|
||||
*/
|
||||
router.post(
|
||||
"/",
|
||||
authenticateJWT,
|
||||
@@ -806,8 +1057,47 @@ router.post(
|
||||
},
|
||||
);
|
||||
|
||||
// Update a snippet
|
||||
// PUT /snippets/:id
|
||||
/**
|
||||
* @openapi
|
||||
* /snippets/{id}:
|
||||
* put:
|
||||
* summary: Update a snippet
|
||||
* description: Updates a specific snippet by its ID.
|
||||
* tags:
|
||||
* - Snippets
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* name:
|
||||
* type: string
|
||||
* content:
|
||||
* type: string
|
||||
* description:
|
||||
* type: string
|
||||
* folder:
|
||||
* type: string
|
||||
* order:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The updated snippet.
|
||||
* 400:
|
||||
* description: Invalid request.
|
||||
* 404:
|
||||
* description: Snippet not found.
|
||||
* 500:
|
||||
* description: Failed to update snippet.
|
||||
*/
|
||||
router.put(
|
||||
"/:id",
|
||||
authenticateJWT,
|
||||
@@ -883,8 +1173,30 @@ router.put(
|
||||
},
|
||||
);
|
||||
|
||||
// Delete a snippet
|
||||
// DELETE /snippets/:id
|
||||
/**
|
||||
* @openapi
|
||||
* /snippets/{id}:
|
||||
* delete:
|
||||
* summary: Delete a snippet
|
||||
* description: Deletes a specific snippet by its ID.
|
||||
* tags:
|
||||
* - Snippets
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Snippet deleted successfully.
|
||||
* 400:
|
||||
* description: Invalid request.
|
||||
* 404:
|
||||
* description: Snippet not found.
|
||||
* 500:
|
||||
* description: Failed to delete snippet.
|
||||
*/
|
||||
router.delete(
|
||||
"/:id",
|
||||
authenticateJWT,
|
||||
|
||||
@@ -53,6 +53,22 @@ const permissionManager = PermissionManager.getInstance();
|
||||
const authenticateJWT = authManager.createAuthMiddleware();
|
||||
const requireDataAccess = authManager.createDataAccessMiddleware();
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/db/host/internal:
|
||||
* get:
|
||||
* summary: Get internal SSH host data
|
||||
* description: Returns internal SSH host data for autostart tunnels. Requires internal auth token.
|
||||
* tags:
|
||||
* - SSH
|
||||
* responses:
|
||||
* 200:
|
||||
* description: A list of autostart hosts.
|
||||
* 403:
|
||||
* description: Forbidden.
|
||||
* 500:
|
||||
* description: Failed to fetch autostart SSH data.
|
||||
*/
|
||||
router.get("/db/host/internal", async (req: Request, res: Response) => {
|
||||
try {
|
||||
const internalToken = req.headers["x-internal-auth-token"];
|
||||
@@ -135,6 +151,22 @@ router.get("/db/host/internal", async (req: Request, res: Response) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/db/host/internal/all:
|
||||
* get:
|
||||
* summary: Get all internal SSH host data
|
||||
* description: Returns all internal SSH host data. Requires internal auth token.
|
||||
* tags:
|
||||
* - SSH
|
||||
* responses:
|
||||
* 200:
|
||||
* description: A list of all hosts.
|
||||
* 401:
|
||||
* description: Invalid or missing internal authentication token.
|
||||
* 500:
|
||||
* description: Failed to fetch all hosts.
|
||||
*/
|
||||
router.get("/db/host/internal/all", async (req: Request, res: Response) => {
|
||||
try {
|
||||
const internalToken = req.headers["x-internal-auth-token"];
|
||||
@@ -194,8 +226,22 @@ router.get("/db/host/internal/all", async (req: Request, res: Response) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Route: Create SSH data (requires JWT)
|
||||
// POST /ssh/host
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/db/host:
|
||||
* post:
|
||||
* summary: Create SSH host
|
||||
* description: Creates a new SSH host configuration.
|
||||
* tags:
|
||||
* - SSH
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Host created successfully.
|
||||
* 400:
|
||||
* description: Invalid SSH data.
|
||||
* 500:
|
||||
* description: Failed to save SSH data.
|
||||
*/
|
||||
router.post(
|
||||
"/db/host",
|
||||
authenticateJWT,
|
||||
@@ -438,8 +484,32 @@ router.post(
|
||||
},
|
||||
);
|
||||
|
||||
// Route: Update SSH data (requires JWT)
|
||||
// PUT /ssh/host/:id
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/db/host/{id}:
|
||||
* put:
|
||||
* summary: Update SSH host
|
||||
* description: Updates an existing SSH host configuration.
|
||||
* tags:
|
||||
* - SSH
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Host updated successfully.
|
||||
* 400:
|
||||
* description: Invalid SSH data.
|
||||
* 403:
|
||||
* description: Access denied.
|
||||
* 404:
|
||||
* description: Host not found.
|
||||
* 500:
|
||||
* description: Failed to update SSH data.
|
||||
*/
|
||||
router.put(
|
||||
"/db/host/:id",
|
||||
authenticateJWT,
|
||||
@@ -785,8 +855,22 @@ router.put(
|
||||
},
|
||||
);
|
||||
|
||||
// Route: Get SSH data for the authenticated user (requires JWT)
|
||||
// GET /ssh/host
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/db/host:
|
||||
* get:
|
||||
* summary: Get all SSH hosts
|
||||
* description: Retrieves all SSH hosts for the authenticated user.
|
||||
* tags:
|
||||
* - SSH
|
||||
* responses:
|
||||
* 200:
|
||||
* description: A list of SSH hosts.
|
||||
* 400:
|
||||
* description: Invalid userId.
|
||||
* 500:
|
||||
* description: Failed to fetch SSH data.
|
||||
*/
|
||||
router.get(
|
||||
"/db/host",
|
||||
authenticateJWT,
|
||||
@@ -966,8 +1050,30 @@ router.get(
|
||||
},
|
||||
);
|
||||
|
||||
// Route: Get SSH host by ID (requires JWT)
|
||||
// GET /ssh/host/:id
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/db/host/{id}:
|
||||
* get:
|
||||
* summary: Get SSH host by ID
|
||||
* description: Retrieves a specific SSH host by its ID.
|
||||
* tags:
|
||||
* - SSH
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The requested SSH host.
|
||||
* 400:
|
||||
* description: Invalid userId or hostId.
|
||||
* 404:
|
||||
* description: SSH host not found.
|
||||
* 500:
|
||||
* description: Failed to fetch SSH host.
|
||||
*/
|
||||
router.get(
|
||||
"/db/host/:id",
|
||||
authenticateJWT,
|
||||
@@ -1041,8 +1147,30 @@ router.get(
|
||||
},
|
||||
);
|
||||
|
||||
// Route: Export SSH host with decrypted credentials (requires data access)
|
||||
// GET /ssh/db/host/:id/export
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/db/host/{id}/export:
|
||||
* get:
|
||||
* summary: Export SSH host
|
||||
* description: Exports a specific SSH host with decrypted credentials.
|
||||
* tags:
|
||||
* - SSH
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The exported SSH host.
|
||||
* 400:
|
||||
* description: Invalid userId or hostId.
|
||||
* 404:
|
||||
* description: SSH host not found.
|
||||
* 500:
|
||||
* description: Failed to export SSH host.
|
||||
*/
|
||||
router.get(
|
||||
"/db/host/:id/export",
|
||||
authenticateJWT,
|
||||
@@ -1121,8 +1249,30 @@ router.get(
|
||||
},
|
||||
);
|
||||
|
||||
// Route: Delete SSH host by id (requires JWT)
|
||||
// DELETE /ssh/host/:id
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/db/host/{id}:
|
||||
* delete:
|
||||
* summary: Delete SSH host
|
||||
* description: Deletes an SSH host by its ID.
|
||||
* tags:
|
||||
* - SSH
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 200:
|
||||
* description: SSH host deleted successfully.
|
||||
* 400:
|
||||
* description: Invalid userId or id.
|
||||
* 404:
|
||||
* description: SSH host not found.
|
||||
* 500:
|
||||
* description: Failed to delete SSH host.
|
||||
*/
|
||||
router.delete(
|
||||
"/db/host/:id",
|
||||
authenticateJWT,
|
||||
@@ -1237,8 +1387,28 @@ router.delete(
|
||||
},
|
||||
);
|
||||
|
||||
// Route: Get recent files (requires JWT)
|
||||
// GET /ssh/file_manager/recent
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/file_manager/recent:
|
||||
* get:
|
||||
* summary: Get recent files
|
||||
* description: Retrieves a list of recent files for a specific host.
|
||||
* tags:
|
||||
* - SSH
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: hostId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 200:
|
||||
* description: A list of recent files.
|
||||
* 400:
|
||||
* description: Invalid userId or hostId.
|
||||
* 500:
|
||||
* description: Failed to fetch recent files.
|
||||
*/
|
||||
router.get(
|
||||
"/file_manager/recent",
|
||||
authenticateJWT,
|
||||
@@ -1279,8 +1449,35 @@ router.get(
|
||||
},
|
||||
);
|
||||
|
||||
// Route: Add recent file (requires JWT)
|
||||
// POST /ssh/file_manager/recent
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/file_manager/recent:
|
||||
* post:
|
||||
* summary: Add recent file
|
||||
* description: Adds a file to the list of recent files for a host.
|
||||
* tags:
|
||||
* - SSH
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* hostId:
|
||||
* type: integer
|
||||
* path:
|
||||
* type: string
|
||||
* name:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Recent file added.
|
||||
* 400:
|
||||
* description: Invalid data.
|
||||
* 500:
|
||||
* description: Failed to add recent file.
|
||||
*/
|
||||
router.post(
|
||||
"/file_manager/recent",
|
||||
authenticateJWT,
|
||||
@@ -1328,8 +1525,33 @@ router.post(
|
||||
},
|
||||
);
|
||||
|
||||
// Route: Remove recent file (requires JWT)
|
||||
// DELETE /ssh/file_manager/recent
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/file_manager/recent:
|
||||
* delete:
|
||||
* summary: Remove recent file
|
||||
* description: Removes a file from the list of recent files for a host.
|
||||
* tags:
|
||||
* - SSH
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* hostId:
|
||||
* type: integer
|
||||
* path:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Recent file removed.
|
||||
* 400:
|
||||
* description: Invalid data.
|
||||
* 500:
|
||||
* description: Failed to remove recent file.
|
||||
*/
|
||||
router.delete(
|
||||
"/file_manager/recent",
|
||||
authenticateJWT,
|
||||
@@ -1361,8 +1583,28 @@ router.delete(
|
||||
},
|
||||
);
|
||||
|
||||
// Route: Get pinned files (requires JWT)
|
||||
// GET /ssh/file_manager/pinned
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/file_manager/pinned:
|
||||
* get:
|
||||
* summary: Get pinned files
|
||||
* description: Retrieves a list of pinned files for a specific host.
|
||||
* tags:
|
||||
* - SSH
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: hostId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 200:
|
||||
* description: A list of pinned files.
|
||||
* 400:
|
||||
* description: Invalid userId or hostId.
|
||||
* 500:
|
||||
* description: Failed to fetch pinned files.
|
||||
*/
|
||||
router.get(
|
||||
"/file_manager/pinned",
|
||||
authenticateJWT,
|
||||
@@ -1402,8 +1644,37 @@ router.get(
|
||||
},
|
||||
);
|
||||
|
||||
// Route: Add pinned file (requires JWT)
|
||||
// POST /ssh/file_manager/pinned
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/file_manager/pinned:
|
||||
* post:
|
||||
* summary: Add pinned file
|
||||
* description: Adds a file to the list of pinned files for a host.
|
||||
* tags:
|
||||
* - SSH
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* hostId:
|
||||
* type: integer
|
||||
* path:
|
||||
* type: string
|
||||
* name:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: File pinned.
|
||||
* 400:
|
||||
* description: Invalid data.
|
||||
* 409:
|
||||
* description: File already pinned.
|
||||
* 500:
|
||||
* description: Failed to pin file.
|
||||
*/
|
||||
router.post(
|
||||
"/file_manager/pinned",
|
||||
authenticateJWT,
|
||||
@@ -1448,8 +1719,33 @@ router.post(
|
||||
},
|
||||
);
|
||||
|
||||
// Route: Remove pinned file (requires JWT)
|
||||
// DELETE /ssh/file_manager/pinned
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/file_manager/pinned:
|
||||
* delete:
|
||||
* summary: Remove pinned file
|
||||
* description: Removes a file from the list of pinned files for a host.
|
||||
* tags:
|
||||
* - SSH
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* hostId:
|
||||
* type: integer
|
||||
* path:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Pinned file removed.
|
||||
* 400:
|
||||
* description: Invalid data.
|
||||
* 500:
|
||||
* description: Failed to remove pinned file.
|
||||
*/
|
||||
router.delete(
|
||||
"/file_manager/pinned",
|
||||
authenticateJWT,
|
||||
@@ -1481,8 +1777,28 @@ router.delete(
|
||||
},
|
||||
);
|
||||
|
||||
// Route: Get shortcuts (requires JWT)
|
||||
// GET /ssh/file_manager/shortcuts
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/file_manager/shortcuts:
|
||||
* get:
|
||||
* summary: Get shortcuts
|
||||
* description: Retrieves a list of shortcuts for a specific host.
|
||||
* tags:
|
||||
* - SSH
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: hostId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 200:
|
||||
* description: A list of shortcuts.
|
||||
* 400:
|
||||
* description: Invalid userId or hostId.
|
||||
* 500:
|
||||
* description: Failed to fetch shortcuts.
|
||||
*/
|
||||
router.get(
|
||||
"/file_manager/shortcuts",
|
||||
authenticateJWT,
|
||||
@@ -1522,8 +1838,37 @@ router.get(
|
||||
},
|
||||
);
|
||||
|
||||
// Route: Add shortcut (requires JWT)
|
||||
// POST /ssh/file_manager/shortcuts
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/file_manager/shortcuts:
|
||||
* post:
|
||||
* summary: Add shortcut
|
||||
* description: Adds a shortcut for a specific host.
|
||||
* tags:
|
||||
* - SSH
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* hostId:
|
||||
* type: integer
|
||||
* path:
|
||||
* type: string
|
||||
* name:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Shortcut added.
|
||||
* 400:
|
||||
* description: Invalid data.
|
||||
* 409:
|
||||
* description: Shortcut already exists.
|
||||
* 500:
|
||||
* description: Failed to add shortcut.
|
||||
*/
|
||||
router.post(
|
||||
"/file_manager/shortcuts",
|
||||
authenticateJWT,
|
||||
@@ -1568,8 +1913,33 @@ router.post(
|
||||
},
|
||||
);
|
||||
|
||||
// Route: Remove shortcut (requires JWT)
|
||||
// DELETE /ssh/file_manager/shortcuts
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/file_manager/shortcuts:
|
||||
* delete:
|
||||
* summary: Remove shortcut
|
||||
* description: Removes a shortcut for a specific host.
|
||||
* tags:
|
||||
* - SSH
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* hostId:
|
||||
* type: integer
|
||||
* path:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Shortcut removed.
|
||||
* 400:
|
||||
* description: Invalid data.
|
||||
* 500:
|
||||
* description: Failed to remove shortcut.
|
||||
*/
|
||||
router.delete(
|
||||
"/file_manager/shortcuts",
|
||||
authenticateJWT,
|
||||
@@ -1601,8 +1971,28 @@ router.delete(
|
||||
},
|
||||
);
|
||||
|
||||
// Route: Get command history for a host
|
||||
// GET /ssh/command-history/:hostId
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/command-history/{hostId}:
|
||||
* get:
|
||||
* summary: Get command history
|
||||
* description: Retrieves the command history for a specific host.
|
||||
* tags:
|
||||
* - SSH
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: hostId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 200:
|
||||
* description: A list of commands.
|
||||
* 400:
|
||||
* description: Invalid userId or hostId.
|
||||
* 500:
|
||||
* description: Failed to fetch command history.
|
||||
*/
|
||||
router.get(
|
||||
"/command-history/:hostId",
|
||||
authenticateJWT,
|
||||
@@ -1647,8 +2037,33 @@ router.get(
|
||||
},
|
||||
);
|
||||
|
||||
// Route: Delete command from history
|
||||
// DELETE /ssh/command-history
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/command-history:
|
||||
* delete:
|
||||
* summary: Delete command from history
|
||||
* description: Deletes a specific command from the history of a host.
|
||||
* tags:
|
||||
* - SSH
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* hostId:
|
||||
* type: integer
|
||||
* command:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Command deleted from history.
|
||||
* 400:
|
||||
* description: Invalid data.
|
||||
* 500:
|
||||
* description: Failed to delete command.
|
||||
*/
|
||||
router.delete(
|
||||
"/command-history",
|
||||
authenticateJWT,
|
||||
@@ -1700,9 +2115,8 @@ async function resolveHostCredentials(
|
||||
|
||||
if (requestingUserId && requestingUserId !== ownerId) {
|
||||
try {
|
||||
const { SharedCredentialManager } = await import(
|
||||
"../../utils/shared-credential-manager.js"
|
||||
);
|
||||
const { SharedCredentialManager } =
|
||||
await import("../../utils/shared-credential-manager.js");
|
||||
const sharedCredManager = SharedCredentialManager.getInstance();
|
||||
const sharedCred = await sharedCredManager.getSharedCredentialForUser(
|
||||
host.id as number,
|
||||
@@ -1790,8 +2204,33 @@ async function resolveHostCredentials(
|
||||
}
|
||||
}
|
||||
|
||||
// Route: Rename folder (requires JWT)
|
||||
// PUT /ssh/db/folders/rename
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/folders/rename:
|
||||
* put:
|
||||
* summary: Rename folder
|
||||
* description: Renames a folder for SSH hosts and credentials.
|
||||
* tags:
|
||||
* - SSH
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* oldName:
|
||||
* type: string
|
||||
* newName:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Folder renamed successfully.
|
||||
* 400:
|
||||
* description: Old name and new name are required.
|
||||
* 500:
|
||||
* description: Failed to rename folder.
|
||||
*/
|
||||
router.put(
|
||||
"/folders/rename",
|
||||
authenticateJWT,
|
||||
@@ -1865,8 +2304,22 @@ router.put(
|
||||
},
|
||||
);
|
||||
|
||||
// Route: Get all folders with metadata (requires JWT)
|
||||
// GET /ssh/db/folders
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/folders:
|
||||
* get:
|
||||
* summary: Get all folders
|
||||
* description: Retrieves all folders for the authenticated user.
|
||||
* tags:
|
||||
* - SSH
|
||||
* responses:
|
||||
* 200:
|
||||
* description: A list of folders.
|
||||
* 400:
|
||||
* description: Invalid user ID.
|
||||
* 500:
|
||||
* description: Failed to fetch folders.
|
||||
*/
|
||||
router.get("/folders", authenticateJWT, async (req: Request, res: Response) => {
|
||||
const userId = (req as AuthenticatedRequest).userId;
|
||||
|
||||
@@ -1890,8 +2343,35 @@ router.get("/folders", authenticateJWT, async (req: Request, res: Response) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Route: Update folder metadata (requires JWT)
|
||||
// PUT /ssh/db/folders/metadata
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/folders/metadata:
|
||||
* put:
|
||||
* summary: Update folder metadata
|
||||
* description: Updates the metadata (color, icon) of a folder.
|
||||
* tags:
|
||||
* - SSH
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* name:
|
||||
* type: string
|
||||
* color:
|
||||
* type: string
|
||||
* icon:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Folder metadata updated successfully.
|
||||
* 400:
|
||||
* description: Folder name is required.
|
||||
* 500:
|
||||
* description: Failed to update folder metadata.
|
||||
*/
|
||||
router.put(
|
||||
"/folders/metadata",
|
||||
authenticateJWT,
|
||||
@@ -1944,8 +2424,28 @@ router.put(
|
||||
},
|
||||
);
|
||||
|
||||
// Route: Delete all hosts in folder (requires JWT)
|
||||
// DELETE /ssh/db/folders/:name/hosts
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/folders/{name}/hosts:
|
||||
* delete:
|
||||
* summary: Delete all hosts in folder
|
||||
* description: Deletes all SSH hosts within a specific folder.
|
||||
* tags:
|
||||
* - SSH
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: name
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Hosts deleted successfully.
|
||||
* 400:
|
||||
* description: Invalid folder name.
|
||||
* 500:
|
||||
* description: Failed to delete hosts in folder.
|
||||
*/
|
||||
router.delete(
|
||||
"/folders/:name/hosts",
|
||||
authenticateJWT,
|
||||
@@ -2063,8 +2563,31 @@ router.delete(
|
||||
},
|
||||
);
|
||||
|
||||
// Route: Bulk import SSH hosts (requires JWT)
|
||||
// POST /ssh/bulk-import
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/bulk-import:
|
||||
* post:
|
||||
* summary: Bulk import SSH hosts
|
||||
* description: Bulk imports multiple SSH hosts.
|
||||
* tags:
|
||||
* - SSH
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* hosts:
|
||||
* type: array
|
||||
* items:
|
||||
* type: object
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Import completed.
|
||||
* 400:
|
||||
* description: Invalid request body.
|
||||
*/
|
||||
router.post(
|
||||
"/bulk-import",
|
||||
authenticateJWT,
|
||||
@@ -2221,8 +2744,33 @@ router.post(
|
||||
},
|
||||
);
|
||||
|
||||
// Route: Enable autostart for SSH configuration (requires JWT)
|
||||
// POST /ssh/autostart/enable
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/autostart/enable:
|
||||
* post:
|
||||
* summary: Enable autostart for SSH configuration
|
||||
* description: Enables autostart for a specific SSH configuration.
|
||||
* tags:
|
||||
* - SSH
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* sshConfigId:
|
||||
* type: number
|
||||
* responses:
|
||||
* 200:
|
||||
* description: AutoStart enabled successfully.
|
||||
* 400:
|
||||
* description: Valid sshConfigId is required.
|
||||
* 404:
|
||||
* description: SSH configuration not found.
|
||||
* 500:
|
||||
* description: Internal server error.
|
||||
*/
|
||||
router.post(
|
||||
"/autostart/enable",
|
||||
authenticateJWT,
|
||||
@@ -2375,8 +2923,31 @@ router.post(
|
||||
},
|
||||
);
|
||||
|
||||
// Route: Disable autostart for SSH configuration (requires JWT)
|
||||
// DELETE /ssh/autostart/disable
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/autostart/disable:
|
||||
* delete:
|
||||
* summary: Disable autostart for SSH configuration
|
||||
* description: Disables autostart for a specific SSH configuration.
|
||||
* tags:
|
||||
* - SSH
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* sshConfigId:
|
||||
* type: number
|
||||
* responses:
|
||||
* 200:
|
||||
* description: AutoStart disabled successfully.
|
||||
* 400:
|
||||
* description: Valid sshConfigId is required.
|
||||
* 500:
|
||||
* description: Internal server error.
|
||||
*/
|
||||
router.delete(
|
||||
"/autostart/disable",
|
||||
authenticateJWT,
|
||||
@@ -2421,8 +2992,20 @@ router.delete(
|
||||
},
|
||||
);
|
||||
|
||||
// Route: Get autostart status for user's SSH configurations (requires JWT)
|
||||
// GET /ssh/autostart/status
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/autostart/status:
|
||||
* get:
|
||||
* summary: Get autostart status
|
||||
* description: Retrieves the autostart status for the user's SSH configurations.
|
||||
* tags:
|
||||
* - SSH
|
||||
* responses:
|
||||
* 200:
|
||||
* description: A list of autostart configurations.
|
||||
* 500:
|
||||
* description: Internal server error.
|
||||
*/
|
||||
router.get(
|
||||
"/autostart/status",
|
||||
authenticateJWT,
|
||||
|
||||
@@ -17,8 +17,33 @@ const authManager = AuthManager.getInstance();
|
||||
const authenticateJWT = authManager.createAuthMiddleware();
|
||||
const requireDataAccess = authManager.createDataAccessMiddleware();
|
||||
|
||||
// Save command to history
|
||||
// POST /terminal/command_history
|
||||
/**
|
||||
* @openapi
|
||||
* /terminal/command_history:
|
||||
* post:
|
||||
* summary: Save command to history
|
||||
* description: Saves a command to the command history for a specific host.
|
||||
* tags:
|
||||
* - Terminal
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* hostId:
|
||||
* type: integer
|
||||
* command:
|
||||
* type: string
|
||||
* responses:
|
||||
* 201:
|
||||
* description: Command saved successfully.
|
||||
* 400:
|
||||
* description: Missing required parameters.
|
||||
* 500:
|
||||
* description: Failed to save command.
|
||||
*/
|
||||
router.post(
|
||||
"/command_history",
|
||||
authenticateJWT,
|
||||
@@ -59,8 +84,28 @@ router.post(
|
||||
},
|
||||
);
|
||||
|
||||
// Get command history for a specific host
|
||||
// GET /terminal/command_history/:hostId
|
||||
/**
|
||||
* @openapi
|
||||
* /terminal/command_history/{hostId}:
|
||||
* get:
|
||||
* summary: Get command history
|
||||
* description: Retrieves the command history for a specific host.
|
||||
* tags:
|
||||
* - Terminal
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: hostId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 200:
|
||||
* description: A list of commands.
|
||||
* 400:
|
||||
* description: Invalid request parameters.
|
||||
* 500:
|
||||
* description: Failed to fetch history.
|
||||
*/
|
||||
router.get(
|
||||
"/command_history/:hostId",
|
||||
authenticateJWT,
|
||||
@@ -107,8 +152,33 @@ router.get(
|
||||
},
|
||||
);
|
||||
|
||||
// Delete a specific command from history
|
||||
// POST /terminal/command_history/delete
|
||||
/**
|
||||
* @openapi
|
||||
* /terminal/command_history/delete:
|
||||
* post:
|
||||
* summary: Delete a specific command from history
|
||||
* description: Deletes a specific command from the history of a host.
|
||||
* tags:
|
||||
* - Terminal
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* hostId:
|
||||
* type: integer
|
||||
* command:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Command deleted successfully.
|
||||
* 400:
|
||||
* description: Missing required parameters.
|
||||
* 500:
|
||||
* description: Failed to delete command.
|
||||
*/
|
||||
router.post(
|
||||
"/command_history/delete",
|
||||
authenticateJWT,
|
||||
@@ -150,8 +220,28 @@ router.post(
|
||||
},
|
||||
);
|
||||
|
||||
// Clear command history for a specific host (optional feature)
|
||||
// DELETE /terminal/command_history/:hostId
|
||||
/**
|
||||
* @openapi
|
||||
* /terminal/command_history/{hostId}:
|
||||
* delete:
|
||||
* summary: Clear command history
|
||||
* description: Clears the entire command history for a specific host.
|
||||
* tags:
|
||||
* - Terminal
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: hostId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Command history cleared successfully.
|
||||
* 400:
|
||||
* description: Invalid request.
|
||||
* 500:
|
||||
* description: Failed to clear history.
|
||||
*/
|
||||
router.delete(
|
||||
"/command_history/:hostId",
|
||||
authenticateJWT,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -365,7 +365,34 @@ app.use(express.urlencoded({ limit: "100mb", extended: true }));
|
||||
const authManager = AuthManager.getInstance();
|
||||
app.use(authManager.createAuthMiddleware());
|
||||
|
||||
// POST /docker/ssh/connect - Establish SSH session
|
||||
/**
|
||||
* @openapi
|
||||
* /docker/ssh/connect:
|
||||
* post:
|
||||
* summary: Establish SSH session for Docker
|
||||
* description: Establishes an SSH session to a host for Docker operations.
|
||||
* tags:
|
||||
* - Docker
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* responses:
|
||||
* 200:
|
||||
* description: SSH connection established.
|
||||
* 400:
|
||||
* description: Missing sessionId or hostId.
|
||||
* 401:
|
||||
* description: Authentication required.
|
||||
* 403:
|
||||
* description: Docker is not enabled for this host.
|
||||
* 404:
|
||||
* description: Host not found.
|
||||
* 500:
|
||||
* description: SSH connection failed.
|
||||
*/
|
||||
app.post("/docker/ssh/connect", async (req, res) => {
|
||||
const {
|
||||
sessionId,
|
||||
@@ -929,7 +956,29 @@ app.post("/docker/ssh/connect", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// POST /docker/ssh/disconnect - Close SSH session
|
||||
/**
|
||||
* @openapi
|
||||
* /docker/ssh/disconnect:
|
||||
* post:
|
||||
* summary: Disconnect SSH session
|
||||
* description: Closes an active SSH session for Docker operations.
|
||||
* tags:
|
||||
* - Docker
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* sessionId:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: SSH session disconnected.
|
||||
* 400:
|
||||
* description: Session ID is required.
|
||||
*/
|
||||
app.post("/docker/ssh/disconnect", async (req, res) => {
|
||||
const { sessionId } = req.body;
|
||||
|
||||
@@ -942,7 +991,35 @@ app.post("/docker/ssh/disconnect", async (req, res) => {
|
||||
res.json({ success: true, message: "SSH session disconnected" });
|
||||
});
|
||||
|
||||
// POST /docker/ssh/connect-totp - Verify TOTP and complete connection
|
||||
/**
|
||||
* @openapi
|
||||
* /docker/ssh/connect-totp:
|
||||
* post:
|
||||
* summary: Verify TOTP and complete connection
|
||||
* description: Verifies the TOTP code and completes the SSH connection.
|
||||
* tags:
|
||||
* - Docker
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* sessionId:
|
||||
* type: string
|
||||
* totpCode:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: TOTP verified, SSH connection established.
|
||||
* 400:
|
||||
* description: Session ID and TOTP code required.
|
||||
* 401:
|
||||
* description: Invalid TOTP code.
|
||||
* 404:
|
||||
* description: TOTP session expired.
|
||||
*/
|
||||
app.post("/docker/ssh/connect-totp", async (req, res) => {
|
||||
const { sessionId, totpCode } = req.body;
|
||||
const userId = (req as any).userId;
|
||||
@@ -1105,7 +1182,29 @@ app.post("/docker/ssh/connect-totp", async (req, res) => {
|
||||
session.finish(responses);
|
||||
});
|
||||
|
||||
// POST /docker/ssh/keepalive - Keep session alive
|
||||
/**
|
||||
* @openapi
|
||||
* /docker/ssh/keepalive:
|
||||
* post:
|
||||
* summary: Keep SSH session alive
|
||||
* description: Keeps an active SSH session alive.
|
||||
* tags:
|
||||
* - Docker
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* sessionId:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Session keepalive successful.
|
||||
* 400:
|
||||
* description: Session ID is required or session not found.
|
||||
*/
|
||||
app.post("/docker/ssh/keepalive", async (req, res) => {
|
||||
const { sessionId } = req.body;
|
||||
|
||||
@@ -1133,7 +1232,26 @@ app.post("/docker/ssh/keepalive", async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
// GET /docker/ssh/status - Check session status
|
||||
/**
|
||||
* @openapi
|
||||
* /docker/ssh/status:
|
||||
* get:
|
||||
* summary: Check SSH session status
|
||||
* description: Checks the status of an active SSH session.
|
||||
* tags:
|
||||
* - Docker
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: sessionId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Session status.
|
||||
* 400:
|
||||
* description: Session ID is required.
|
||||
*/
|
||||
app.get("/docker/ssh/status", async (req, res) => {
|
||||
const sessionId = req.query.sessionId as string;
|
||||
|
||||
@@ -1146,7 +1264,28 @@ app.get("/docker/ssh/status", async (req, res) => {
|
||||
res.json({ success: true, connected: isConnected });
|
||||
});
|
||||
|
||||
// GET /docker/validate/:sessionId - Validate Docker availability
|
||||
/**
|
||||
* @openapi
|
||||
* /docker/validate/{sessionId}:
|
||||
* get:
|
||||
* summary: Validate Docker availability
|
||||
* description: Validates if Docker is available on the host.
|
||||
* tags:
|
||||
* - Docker
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: sessionId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Docker availability status.
|
||||
* 400:
|
||||
* description: SSH session not found or not connected.
|
||||
* 500:
|
||||
* description: Validation failed.
|
||||
*/
|
||||
app.get("/docker/validate/:sessionId", async (req, res) => {
|
||||
const { sessionId } = req.params;
|
||||
const userId = (req as any).userId;
|
||||
@@ -1236,7 +1375,32 @@ app.get("/docker/validate/:sessionId", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// GET /docker/containers/:sessionId - List all containers
|
||||
/**
|
||||
* @openapi
|
||||
* /docker/containers/{sessionId}:
|
||||
* get:
|
||||
* summary: List all containers
|
||||
* description: Lists all Docker containers on the host.
|
||||
* tags:
|
||||
* - Docker
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: sessionId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* - in: query
|
||||
* name: all
|
||||
* schema:
|
||||
* type: boolean
|
||||
* responses:
|
||||
* 200:
|
||||
* description: A list of containers.
|
||||
* 400:
|
||||
* description: SSH session not found or not connected.
|
||||
* 500:
|
||||
* description: Failed to list containers.
|
||||
*/
|
||||
app.get("/docker/containers/:sessionId", async (req, res) => {
|
||||
const { sessionId } = req.params;
|
||||
const all = req.query.all !== "false";
|
||||
@@ -1297,7 +1461,35 @@ app.get("/docker/containers/:sessionId", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// GET /docker/containers/:sessionId/:containerId - Get container details
|
||||
/**
|
||||
* @openapi
|
||||
* /docker/containers/{sessionId}/{containerId}:
|
||||
* get:
|
||||
* summary: Get container details
|
||||
* description: Retrieves detailed information about a specific container.
|
||||
* tags:
|
||||
* - Docker
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: sessionId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* - in: path
|
||||
* name: containerId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Container details.
|
||||
* 400:
|
||||
* description: SSH session not found or not connected.
|
||||
* 404:
|
||||
* description: Container not found.
|
||||
* 500:
|
||||
* description: Failed to get container details.
|
||||
*/
|
||||
app.get("/docker/containers/:sessionId/:containerId", async (req, res) => {
|
||||
const { sessionId, containerId } = req.params;
|
||||
const userId = (req as any).userId;
|
||||
@@ -1356,7 +1548,35 @@ app.get("/docker/containers/:sessionId/:containerId", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// POST /docker/containers/:sessionId/:containerId/start - Start container
|
||||
/**
|
||||
* @openapi
|
||||
* /docker/containers/{sessionId}/{containerId}/start:
|
||||
* post:
|
||||
* summary: Start container
|
||||
* description: Starts a specific container.
|
||||
* tags:
|
||||
* - Docker
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: sessionId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* - in: path
|
||||
* name: containerId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Container started successfully.
|
||||
* 400:
|
||||
* description: SSH session not found or not connected.
|
||||
* 404:
|
||||
* description: Container not found.
|
||||
* 500:
|
||||
* description: Failed to start container.
|
||||
*/
|
||||
app.post(
|
||||
"/docker/containers/:sessionId/:containerId/start",
|
||||
async (req, res) => {
|
||||
@@ -1414,7 +1634,35 @@ app.post(
|
||||
},
|
||||
);
|
||||
|
||||
// POST /docker/containers/:sessionId/:containerId/stop - Stop container
|
||||
/**
|
||||
* @openapi
|
||||
* /docker/containers/{sessionId}/{containerId}/stop:
|
||||
* post:
|
||||
* summary: Stop container
|
||||
* description: Stops a specific container.
|
||||
* tags:
|
||||
* - Docker
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: sessionId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* - in: path
|
||||
* name: containerId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Container stopped successfully.
|
||||
* 400:
|
||||
* description: SSH session not found or not connected.
|
||||
* 404:
|
||||
* description: Container not found.
|
||||
* 500:
|
||||
* description: Failed to stop container.
|
||||
*/
|
||||
app.post(
|
||||
"/docker/containers/:sessionId/:containerId/stop",
|
||||
async (req, res) => {
|
||||
@@ -1472,7 +1720,35 @@ app.post(
|
||||
},
|
||||
);
|
||||
|
||||
// POST /docker/containers/:sessionId/:containerId/restart - Restart container
|
||||
/**
|
||||
* @openapi
|
||||
* /docker/containers/{sessionId}/{containerId}/restart:
|
||||
* post:
|
||||
* summary: Restart container
|
||||
* description: Restarts a specific container.
|
||||
* tags:
|
||||
* - Docker
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: sessionId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* - in: path
|
||||
* name: containerId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Container restarted successfully.
|
||||
* 400:
|
||||
* description: SSH session not found or not connected.
|
||||
* 404:
|
||||
* description: Container not found.
|
||||
* 500:
|
||||
* description: Failed to restart container.
|
||||
*/
|
||||
app.post(
|
||||
"/docker/containers/:sessionId/:containerId/restart",
|
||||
async (req, res) => {
|
||||
@@ -1530,7 +1806,35 @@ app.post(
|
||||
},
|
||||
);
|
||||
|
||||
// POST /docker/containers/:sessionId/:containerId/pause - Pause container
|
||||
/**
|
||||
* @openapi
|
||||
* /docker/containers/{sessionId}/{containerId}/pause:
|
||||
* post:
|
||||
* summary: Pause container
|
||||
* description: Pauses a specific container.
|
||||
* tags:
|
||||
* - Docker
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: sessionId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* - in: path
|
||||
* name: containerId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Container paused successfully.
|
||||
* 400:
|
||||
* description: SSH session not found or not connected.
|
||||
* 404:
|
||||
* description: Container not found.
|
||||
* 500:
|
||||
* description: Failed to pause container.
|
||||
*/
|
||||
app.post(
|
||||
"/docker/containers/:sessionId/:containerId/pause",
|
||||
async (req, res) => {
|
||||
@@ -1588,7 +1892,35 @@ app.post(
|
||||
},
|
||||
);
|
||||
|
||||
// POST /docker/containers/:sessionId/:containerId/unpause - Unpause container
|
||||
/**
|
||||
* @openapi
|
||||
* /docker/containers/{sessionId}/{containerId}/unpause:
|
||||
* post:
|
||||
* summary: Unpause container
|
||||
* description: Unpauses a specific container.
|
||||
* tags:
|
||||
* - Docker
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: sessionId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* - in: path
|
||||
* name: containerId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Container unpaused successfully.
|
||||
* 400:
|
||||
* description: SSH session not found or not connected.
|
||||
* 404:
|
||||
* description: Container not found.
|
||||
* 500:
|
||||
* description: Failed to unpause container.
|
||||
*/
|
||||
app.post(
|
||||
"/docker/containers/:sessionId/:containerId/unpause",
|
||||
async (req, res) => {
|
||||
@@ -1646,7 +1978,39 @@ app.post(
|
||||
},
|
||||
);
|
||||
|
||||
// DELETE /docker/containers/:sessionId/:containerId/remove - Remove container
|
||||
/**
|
||||
* @openapi
|
||||
* /docker/containers/{sessionId}/{containerId}/remove:
|
||||
* delete:
|
||||
* summary: Remove container
|
||||
* description: Removes a specific container.
|
||||
* tags:
|
||||
* - Docker
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: sessionId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* - in: path
|
||||
* name: containerId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* - in: query
|
||||
* name: force
|
||||
* schema:
|
||||
* type: boolean
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Container removed successfully.
|
||||
* 400:
|
||||
* description: SSH session not found or not connected, or cannot remove a running container.
|
||||
* 404:
|
||||
* description: Container not found.
|
||||
* 500:
|
||||
* description: Failed to remove container.
|
||||
*/
|
||||
app.delete(
|
||||
"/docker/containers/:sessionId/:containerId/remove",
|
||||
async (req, res) => {
|
||||
@@ -1718,7 +2082,51 @@ app.delete(
|
||||
},
|
||||
);
|
||||
|
||||
// GET /docker/containers/:sessionId/:containerId/logs - Get container logs
|
||||
/**
|
||||
* @openapi
|
||||
* /docker/containers/{sessionId}/{containerId}/logs:
|
||||
* get:
|
||||
* summary: Get container logs
|
||||
* description: Retrieves logs for a specific container.
|
||||
* tags:
|
||||
* - Docker
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: sessionId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* - in: path
|
||||
* name: containerId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* - in: query
|
||||
* name: tail
|
||||
* schema:
|
||||
* type: integer
|
||||
* - in: query
|
||||
* name: timestamps
|
||||
* schema:
|
||||
* type: boolean
|
||||
* - in: query
|
||||
* name: since
|
||||
* schema:
|
||||
* type: string
|
||||
* - in: query
|
||||
* name: until
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Container logs.
|
||||
* 400:
|
||||
* description: SSH session not found or not connected.
|
||||
* 404:
|
||||
* description: Container not found.
|
||||
* 500:
|
||||
* description: Failed to get container logs.
|
||||
*/
|
||||
app.get("/docker/containers/:sessionId/:containerId/logs", async (req, res) => {
|
||||
const { sessionId, containerId } = req.params;
|
||||
const tail = req.query.tail ? parseInt(req.query.tail as string) : 100;
|
||||
@@ -1795,7 +2203,35 @@ app.get("/docker/containers/:sessionId/:containerId/logs", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// GET /docker/containers/:sessionId/:containerId/stats - Get container stats
|
||||
/**
|
||||
* @openapi
|
||||
* /docker/containers/{sessionId}/{containerId}/stats:
|
||||
* get:
|
||||
* summary: Get container stats
|
||||
* description: Retrieves stats for a specific container.
|
||||
* tags:
|
||||
* - Docker
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: sessionId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* - in: path
|
||||
* name: containerId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Container stats.
|
||||
* 400:
|
||||
* description: SSH session not found or not connected.
|
||||
* 404:
|
||||
* description: Container not found.
|
||||
* 500:
|
||||
* description: Failed to get container stats.
|
||||
*/
|
||||
app.get(
|
||||
"/docker/containers/:sessionId/:containerId/stats",
|
||||
async (req, res) => {
|
||||
|
||||
@@ -416,6 +416,24 @@ function detectBinary(buffer: Buffer): boolean {
|
||||
return nullBytes / sampleSize > 0.01;
|
||||
}
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/file_manager/ssh/connect:
|
||||
* post:
|
||||
* summary: Connect to SSH for file manager
|
||||
* description: Establishes an SSH connection for file manager operations.
|
||||
* tags:
|
||||
* - File Manager
|
||||
* responses:
|
||||
* 200:
|
||||
* description: SSH connection established.
|
||||
* 400:
|
||||
* description: Missing SSH connection parameters.
|
||||
* 401:
|
||||
* description: Authentication required.
|
||||
* 500:
|
||||
* description: SSH connection failed.
|
||||
*/
|
||||
app.post("/ssh/file_manager/ssh/connect", async (req, res) => {
|
||||
const {
|
||||
sessionId,
|
||||
@@ -986,6 +1004,26 @@ app.post("/ssh/file_manager/ssh/connect", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/file_manager/ssh/connect-totp:
|
||||
* post:
|
||||
* summary: Verify TOTP and complete connection
|
||||
* description: Verifies the TOTP code and completes the SSH connection for file manager.
|
||||
* tags:
|
||||
* - File Manager
|
||||
* responses:
|
||||
* 200:
|
||||
* description: TOTP verified, SSH connection established.
|
||||
* 400:
|
||||
* description: Session ID and TOTP code required.
|
||||
* 401:
|
||||
* description: Invalid TOTP code or authentication required.
|
||||
* 404:
|
||||
* description: TOTP session expired.
|
||||
* 408:
|
||||
* description: TOTP session timeout.
|
||||
*/
|
||||
app.post("/ssh/file_manager/ssh/connect-totp", async (req, res) => {
|
||||
const { sessionId, totpCode } = req.body;
|
||||
|
||||
@@ -1149,18 +1187,71 @@ app.post("/ssh/file_manager/ssh/connect-totp", async (req, res) => {
|
||||
session.finish(responses);
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/file_manager/ssh/disconnect:
|
||||
* post:
|
||||
* summary: Disconnect from SSH
|
||||
* description: Closes an active SSH connection for file manager.
|
||||
* tags:
|
||||
* - File Manager
|
||||
* responses:
|
||||
* 200:
|
||||
* description: SSH connection disconnected.
|
||||
*/
|
||||
app.post("/ssh/file_manager/ssh/disconnect", (req, res) => {
|
||||
const { sessionId } = req.body;
|
||||
cleanupSession(sessionId);
|
||||
res.json({ status: "success", message: "SSH connection disconnected" });
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/file_manager/ssh/status:
|
||||
* get:
|
||||
* summary: Get SSH connection status
|
||||
* description: Checks the status of an SSH connection for file manager.
|
||||
* tags:
|
||||
* - File Manager
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: sessionId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: SSH connection status.
|
||||
*/
|
||||
app.get("/ssh/file_manager/ssh/status", (req, res) => {
|
||||
const sessionId = req.query.sessionId as string;
|
||||
const isConnected = !!sshSessions[sessionId]?.isConnected;
|
||||
res.json({ status: "success", connected: isConnected });
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/file_manager/ssh/keepalive:
|
||||
* post:
|
||||
* summary: Keep SSH session alive
|
||||
* description: Keeps an active SSH session for file manager alive.
|
||||
* tags:
|
||||
* - File Manager
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* sessionId:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Session keepalive successful.
|
||||
* 400:
|
||||
* description: Session ID is required or session not found.
|
||||
*/
|
||||
app.post("/ssh/file_manager/ssh/keepalive", (req, res) => {
|
||||
const { sessionId } = req.body;
|
||||
|
||||
@@ -1188,6 +1279,33 @@ app.post("/ssh/file_manager/ssh/keepalive", (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/file_manager/ssh/listFiles:
|
||||
* get:
|
||||
* summary: List files in a directory
|
||||
* description: Lists the files and directories in a given path on the remote host.
|
||||
* tags:
|
||||
* - File Manager
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: sessionId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* - in: query
|
||||
* name: path
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: A list of files and directories.
|
||||
* 400:
|
||||
* description: Session ID is required or SSH connection not established.
|
||||
* 500:
|
||||
* description: Failed to list files.
|
||||
*/
|
||||
app.get("/ssh/file_manager/ssh/listFiles", (req, res) => {
|
||||
const sessionId = req.query.sessionId as string;
|
||||
const sshConn = sshSessions[sessionId];
|
||||
@@ -1387,6 +1505,33 @@ app.get("/ssh/file_manager/ssh/listFiles", (req, res) => {
|
||||
trySFTP();
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/file_manager/ssh/identifySymlink:
|
||||
* get:
|
||||
* summary: Identify symbolic link
|
||||
* description: Identifies the target of a symbolic link.
|
||||
* tags:
|
||||
* - File Manager
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: sessionId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* - in: query
|
||||
* name: path
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Symbolic link information.
|
||||
* 400:
|
||||
* description: Missing required parameters or SSH connection not established.
|
||||
* 500:
|
||||
* description: Failed to identify symbolic link.
|
||||
*/
|
||||
app.get("/ssh/file_manager/ssh/identifySymlink", (req, res) => {
|
||||
const sessionId = req.query.sessionId as string;
|
||||
const sshConn = sshSessions[sessionId];
|
||||
@@ -1454,6 +1599,35 @@ app.get("/ssh/file_manager/ssh/identifySymlink", (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/file_manager/ssh/readFile:
|
||||
* get:
|
||||
* summary: Read a file
|
||||
* description: Reads the content of a file from the remote host.
|
||||
* tags:
|
||||
* - File Manager
|
||||
* parameters:
|
||||
* - in: query
|
||||
* name: sessionId
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* - in: query
|
||||
* name: path
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The content of the file.
|
||||
* 400:
|
||||
* description: Missing required parameters or file too large.
|
||||
* 404:
|
||||
* description: File not found.
|
||||
* 500:
|
||||
* description: Failed to read file.
|
||||
*/
|
||||
app.get("/ssh/file_manager/ssh/readFile", (req, res) => {
|
||||
const sessionId = req.query.sessionId as string;
|
||||
const sshConn = sshSessions[sessionId];
|
||||
@@ -1592,6 +1766,35 @@ app.get("/ssh/file_manager/ssh/readFile", (req, res) => {
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/file_manager/ssh/writeFile:
|
||||
* post:
|
||||
* summary: Write to a file
|
||||
* description: Writes content to a file on the remote host.
|
||||
* tags:
|
||||
* - File Manager
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* sessionId:
|
||||
* type: string
|
||||
* path:
|
||||
* type: string
|
||||
* content:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: File written successfully.
|
||||
* 400:
|
||||
* description: Missing required parameters or SSH connection not established.
|
||||
* 500:
|
||||
* description: Failed to write file.
|
||||
*/
|
||||
app.post("/ssh/file_manager/ssh/writeFile", async (req, res) => {
|
||||
const { sessionId, path: filePath, content } = req.body;
|
||||
const sshConn = sshSessions[sessionId];
|
||||
@@ -1737,10 +1940,15 @@ app.post("/ssh/file_manager/ssh/writeFile", async (req, res) => {
|
||||
if (err) {
|
||||
fileLogger.error("Fallback write command failed:", err);
|
||||
if (!res.headersSent) {
|
||||
return res.status(500).json({
|
||||
error: `Write failed: ${err.message}`,
|
||||
toast: { type: "error", message: `Write failed: ${err.message}` },
|
||||
});
|
||||
return res
|
||||
.status(500)
|
||||
.json({
|
||||
error: `Write failed: ${err.message}`,
|
||||
toast: {
|
||||
type: "error",
|
||||
message: `Write failed: ${err.message}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -1803,6 +2011,37 @@ app.post("/ssh/file_manager/ssh/writeFile", async (req, res) => {
|
||||
trySFTP();
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/file_manager/ssh/uploadFile:
|
||||
* post:
|
||||
* summary: Upload a file
|
||||
* description: Uploads a file to the remote host.
|
||||
* tags:
|
||||
* - File Manager
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* sessionId:
|
||||
* type: string
|
||||
* path:
|
||||
* type: string
|
||||
* content:
|
||||
* type: string
|
||||
* fileName:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: File uploaded successfully.
|
||||
* 400:
|
||||
* description: Missing required parameters or SSH connection not established.
|
||||
* 500:
|
||||
* description: Failed to upload file.
|
||||
*/
|
||||
app.post("/ssh/file_manager/ssh/uploadFile", async (req, res) => {
|
||||
const { sessionId, path: filePath, content, fileName } = req.body;
|
||||
const sshConn = sshSessions[sessionId];
|
||||
@@ -2103,6 +2342,37 @@ app.post("/ssh/file_manager/ssh/uploadFile", async (req, res) => {
|
||||
trySFTP();
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/file_manager/ssh/createFile:
|
||||
* post:
|
||||
* summary: Create a file
|
||||
* description: Creates an empty file on the remote host.
|
||||
* tags:
|
||||
* - File Manager
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* sessionId:
|
||||
* type: string
|
||||
* path:
|
||||
* type: string
|
||||
* fileName:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: File created successfully.
|
||||
* 400:
|
||||
* description: Missing required parameters or SSH connection not established.
|
||||
* 403:
|
||||
* description: Permission denied.
|
||||
* 500:
|
||||
* description: Failed to create file.
|
||||
*/
|
||||
app.post("/ssh/file_manager/ssh/createFile", async (req, res) => {
|
||||
const { sessionId, path: filePath, fileName } = req.body;
|
||||
const sshConn = sshSessions[sessionId];
|
||||
@@ -2204,6 +2474,37 @@ app.post("/ssh/file_manager/ssh/createFile", async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/file_manager/ssh/createFolder:
|
||||
* post:
|
||||
* summary: Create a folder
|
||||
* description: Creates a new folder on the remote host.
|
||||
* tags:
|
||||
* - File Manager
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* sessionId:
|
||||
* type: string
|
||||
* path:
|
||||
* type: string
|
||||
* folderName:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Folder created successfully.
|
||||
* 400:
|
||||
* description: Missing required parameters or SSH connection not established.
|
||||
* 403:
|
||||
* description: Permission denied.
|
||||
* 500:
|
||||
* description: Failed to create folder.
|
||||
*/
|
||||
app.post("/ssh/file_manager/ssh/createFolder", async (req, res) => {
|
||||
const { sessionId, path: folderPath, folderName } = req.body;
|
||||
const sshConn = sshSessions[sessionId];
|
||||
@@ -2305,6 +2606,37 @@ app.post("/ssh/file_manager/ssh/createFolder", async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/file_manager/ssh/deleteItem:
|
||||
* delete:
|
||||
* summary: Delete a file or directory
|
||||
* description: Deletes a file or directory on the remote host.
|
||||
* tags:
|
||||
* - File Manager
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* sessionId:
|
||||
* type: string
|
||||
* path:
|
||||
* type: string
|
||||
* isDirectory:
|
||||
* type: boolean
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Item deleted successfully.
|
||||
* 400:
|
||||
* description: Missing required parameters or SSH connection not established.
|
||||
* 403:
|
||||
* description: Permission denied.
|
||||
* 500:
|
||||
* description: Failed to delete item.
|
||||
*/
|
||||
app.delete("/ssh/file_manager/ssh/deleteItem", async (req, res) => {
|
||||
const { sessionId, path: itemPath, isDirectory } = req.body;
|
||||
const sshConn = sshSessions[sessionId];
|
||||
@@ -2407,6 +2739,37 @@ app.delete("/ssh/file_manager/ssh/deleteItem", async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/file_manager/ssh/renameItem:
|
||||
* put:
|
||||
* summary: Rename a file or directory
|
||||
* description: Renames a file or directory on the remote host.
|
||||
* tags:
|
||||
* - File Manager
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* sessionId:
|
||||
* type: string
|
||||
* oldPath:
|
||||
* type: string
|
||||
* newName:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Item renamed successfully.
|
||||
* 400:
|
||||
* description: Missing required parameters or SSH connection not established.
|
||||
* 403:
|
||||
* description: Permission denied.
|
||||
* 500:
|
||||
* description: Failed to rename item.
|
||||
*/
|
||||
app.put("/ssh/file_manager/ssh/renameItem", async (req, res) => {
|
||||
const { sessionId, oldPath, newName } = req.body;
|
||||
const sshConn = sshSessions[sessionId];
|
||||
@@ -2515,6 +2878,39 @@ app.put("/ssh/file_manager/ssh/renameItem", async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/file_manager/ssh/moveItem:
|
||||
* put:
|
||||
* summary: Move a file or directory
|
||||
* description: Moves a file or directory on the remote host.
|
||||
* tags:
|
||||
* - File Manager
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* sessionId:
|
||||
* type: string
|
||||
* oldPath:
|
||||
* type: string
|
||||
* newPath:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Item moved successfully.
|
||||
* 400:
|
||||
* description: Missing required parameters or SSH connection not established.
|
||||
* 403:
|
||||
* description: Permission denied.
|
||||
* 408:
|
||||
* description: Move operation timed out.
|
||||
* 500:
|
||||
* description: Failed to move item.
|
||||
*/
|
||||
app.put("/ssh/file_manager/ssh/moveItem", async (req, res) => {
|
||||
const { sessionId, oldPath, newPath } = req.body;
|
||||
const sshConn = sshSessions[sessionId];
|
||||
@@ -2640,6 +3036,37 @@ app.put("/ssh/file_manager/ssh/moveItem", async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/file_manager/ssh/downloadFile:
|
||||
* post:
|
||||
* summary: Download a file
|
||||
* description: Downloads a file from the remote host.
|
||||
* tags:
|
||||
* - File Manager
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* sessionId:
|
||||
* type: string
|
||||
* path:
|
||||
* type: string
|
||||
* hostId:
|
||||
* type: integer
|
||||
* userId:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The file content.
|
||||
* 400:
|
||||
* description: Missing required parameters or file too large.
|
||||
* 500:
|
||||
* description: Failed to download file.
|
||||
*/
|
||||
app.post("/ssh/file_manager/ssh/downloadFile", async (req, res) => {
|
||||
const { sessionId, path: filePath, hostId, userId } = req.body;
|
||||
|
||||
@@ -2741,6 +3168,39 @@ app.post("/ssh/file_manager/ssh/downloadFile", async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/file_manager/ssh/copyItem:
|
||||
* post:
|
||||
* summary: Copy a file or directory
|
||||
* description: Copies a file or directory on the remote host.
|
||||
* tags:
|
||||
* - File Manager
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* sessionId:
|
||||
* type: string
|
||||
* sourcePath:
|
||||
* type: string
|
||||
* targetDir:
|
||||
* type: string
|
||||
* hostId:
|
||||
* type: integer
|
||||
* userId:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Item copied successfully.
|
||||
* 400:
|
||||
* description: Missing required parameters or SSH connection not established.
|
||||
* 500:
|
||||
* description: Failed to copy item.
|
||||
*/
|
||||
app.post("/ssh/file_manager/ssh/copyItem", async (req, res) => {
|
||||
const { sessionId, sourcePath, targetDir, hostId, userId } = req.body;
|
||||
|
||||
@@ -2904,6 +3364,33 @@ app.post("/ssh/file_manager/ssh/copyItem", async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/file_manager/ssh/executeFile:
|
||||
* post:
|
||||
* summary: Execute a file
|
||||
* description: Executes a file on the remote host.
|
||||
* tags:
|
||||
* - File Manager
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* sessionId:
|
||||
* type: string
|
||||
* filePath:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: File execution result.
|
||||
* 400:
|
||||
* description: Missing required parameters or SSH connection not available.
|
||||
* 500:
|
||||
* description: Failed to execute file.
|
||||
*/
|
||||
app.post("/ssh/file_manager/ssh/executeFile", async (req, res) => {
|
||||
const { sessionId, filePath } = req.body;
|
||||
const sshConn = sshSessions[sessionId];
|
||||
@@ -3002,6 +3489,37 @@ app.post("/ssh/file_manager/ssh/executeFile", async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/file_manager/ssh/changePermissions:
|
||||
* post:
|
||||
* summary: Change file permissions
|
||||
* description: Changes the permissions of a file on the remote host.
|
||||
* tags:
|
||||
* - File Manager
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* sessionId:
|
||||
* type: string
|
||||
* path:
|
||||
* type: string
|
||||
* permissions:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Permissions changed successfully.
|
||||
* 400:
|
||||
* description: Missing required parameters or SSH connection not available.
|
||||
* 408:
|
||||
* description: Permission change timed out.
|
||||
* 500:
|
||||
* description: Failed to change permissions.
|
||||
*/
|
||||
app.post("/ssh/file_manager/ssh/changePermissions", async (req, res) => {
|
||||
const { sessionId, path, permissions } = req.body;
|
||||
const sshConn = sshSessions[sessionId];
|
||||
@@ -3319,8 +3837,39 @@ app.post("/ssh/file_manager/ssh/extractArchive", async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
// Route: Compress files/folders (requires JWT)
|
||||
// POST /ssh/file_manager/ssh/compressFiles
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/file_manager/ssh/compressFiles:
|
||||
* post:
|
||||
* summary: Compress files
|
||||
* description: Compresses files and/or directories on the remote host.
|
||||
* tags:
|
||||
* - File Manager
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* sessionId:
|
||||
* type: string
|
||||
* paths:
|
||||
* type: array
|
||||
* items:
|
||||
* type: string
|
||||
* archiveName:
|
||||
* type: string
|
||||
* format:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Files compressed successfully.
|
||||
* 400:
|
||||
* description: Missing required parameters or unsupported compression format.
|
||||
* 500:
|
||||
* description: Failed to compress files.
|
||||
*/
|
||||
app.post("/ssh/file_manager/ssh/compressFiles", async (req, res) => {
|
||||
const { sessionId, paths, archiveName, format } = req.body;
|
||||
|
||||
|
||||
@@ -1803,6 +1803,10 @@ async function collectMetrics(host: SSHHostWithCredentials): Promise<{
|
||||
} catch (e) {
|
||||
statsLogger.debug("Failed to collect ports metrics", {
|
||||
operation: "ports_metrics_failed",
|
||||
error: e instanceof Error ? e.message : String(e),
|
||||
});
|
||||
}
|
||||
|
||||
let firewall: {
|
||||
type: "iptables" | "nftables" | "none";
|
||||
status: "active" | "inactive" | "unknown";
|
||||
@@ -1920,6 +1924,20 @@ function tcpPing(
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /status:
|
||||
* get:
|
||||
* summary: Get all host statuses
|
||||
* description: Retrieves the status of all hosts for the authenticated user.
|
||||
* tags:
|
||||
* - Server Stats
|
||||
* responses:
|
||||
* 200:
|
||||
* description: A map of host IDs to their status entries.
|
||||
* 401:
|
||||
* description: Session expired - please log in again.
|
||||
*/
|
||||
app.get("/status", async (req, res) => {
|
||||
const userId = (req as AuthenticatedRequest).userId;
|
||||
|
||||
@@ -1942,6 +1960,28 @@ app.get("/status", async (req, res) => {
|
||||
res.json(result);
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /status/{id}:
|
||||
* get:
|
||||
* summary: Get host status by ID
|
||||
* description: Retrieves the status of a specific host by its ID.
|
||||
* tags:
|
||||
* - Server Stats
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Host status entry.
|
||||
* 401:
|
||||
* description: Session expired - please log in again.
|
||||
* 404:
|
||||
* description: Status not available.
|
||||
*/
|
||||
app.get("/status/:id", validateHostId, async (req, res) => {
|
||||
const id = Number(req.params.id);
|
||||
const userId = (req as AuthenticatedRequest).userId;
|
||||
@@ -1966,6 +2006,20 @@ app.get("/status/:id", validateHostId, async (req, res) => {
|
||||
res.json(statusEntry);
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /clear-connections:
|
||||
* post:
|
||||
* summary: Clear all SSH connections
|
||||
* description: Clears all SSH connections from the connection pool.
|
||||
* tags:
|
||||
* - Server Stats
|
||||
* responses:
|
||||
* 200:
|
||||
* description: All SSH connections cleared.
|
||||
* 401:
|
||||
* description: Session expired - please log in again.
|
||||
*/
|
||||
app.post("/clear-connections", async (req, res) => {
|
||||
const userId = (req as AuthenticatedRequest).userId;
|
||||
|
||||
@@ -1980,6 +2034,20 @@ app.post("/clear-connections", async (req, res) => {
|
||||
res.json({ message: "All SSH connections cleared" });
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /refresh:
|
||||
* post:
|
||||
* summary: Refresh polling
|
||||
* description: Clears all SSH connections and refreshes host polling.
|
||||
* tags:
|
||||
* - Server Stats
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Polling refreshed.
|
||||
* 401:
|
||||
* description: Session expired - please log in again.
|
||||
*/
|
||||
app.post("/refresh", async (req, res) => {
|
||||
const userId = (req as AuthenticatedRequest).userId;
|
||||
|
||||
@@ -1996,6 +2064,35 @@ app.post("/refresh", async (req, res) => {
|
||||
res.json({ message: "Polling refreshed" });
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /host-updated:
|
||||
* post:
|
||||
* summary: Start polling for updated host
|
||||
* description: Starts polling for a specific host after it has been updated.
|
||||
* tags:
|
||||
* - Server Stats
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* hostId:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Host polling started.
|
||||
* 400:
|
||||
* description: Invalid hostId.
|
||||
* 401:
|
||||
* description: Session expired - please log in again.
|
||||
* 404:
|
||||
* description: Host not found.
|
||||
* 500:
|
||||
* description: Failed to start polling.
|
||||
*/
|
||||
app.post("/host-updated", async (req, res) => {
|
||||
const userId = (req as AuthenticatedRequest).userId;
|
||||
const { hostId } = req.body;
|
||||
@@ -2031,6 +2128,33 @@ app.post("/host-updated", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /host-deleted:
|
||||
* post:
|
||||
* summary: Stop polling for deleted host
|
||||
* description: Stops polling for a specific host after it has been deleted.
|
||||
* tags:
|
||||
* - Server Stats
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* hostId:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Host polling stopped.
|
||||
* 400:
|
||||
* description: Invalid hostId.
|
||||
* 401:
|
||||
* description: Session expired - please log in again.
|
||||
* 500:
|
||||
* description: Failed to stop polling.
|
||||
*/
|
||||
app.post("/host-deleted", async (req, res) => {
|
||||
const userId = (req as AuthenticatedRequest).userId;
|
||||
const { hostId } = req.body;
|
||||
@@ -2059,6 +2183,28 @@ app.post("/host-deleted", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /metrics/{id}:
|
||||
* get:
|
||||
* summary: Get host metrics
|
||||
* description: Retrieves current metrics for a specific host including CPU, memory, disk, network, processes, and system information.
|
||||
* tags:
|
||||
* - Server Stats
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Host metrics data.
|
||||
* 401:
|
||||
* description: Session expired - please log in again.
|
||||
* 404:
|
||||
* description: Metrics not available.
|
||||
*/
|
||||
app.get("/metrics/:id", validateHostId, async (req, res) => {
|
||||
const id = Number(req.params.id);
|
||||
const userId = (req as AuthenticatedRequest).userId;
|
||||
@@ -2096,6 +2242,30 @@ app.get("/metrics/:id", validateHostId, async (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /metrics/start/{id}:
|
||||
* post:
|
||||
* summary: Start metrics collection
|
||||
* description: Establishes an SSH connection and starts collecting metrics for a specific host.
|
||||
* tags:
|
||||
* - Server Stats
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Metrics collection started successfully, or TOTP required.
|
||||
* 401:
|
||||
* description: Session expired - please log in again.
|
||||
* 404:
|
||||
* description: Host not found.
|
||||
* 500:
|
||||
* description: Failed to start metrics collection.
|
||||
*/
|
||||
app.post("/metrics/start/:id", validateHostId, async (req, res) => {
|
||||
const id = Number(req.params.id);
|
||||
const userId = (req as AuthenticatedRequest).userId;
|
||||
@@ -2275,6 +2445,37 @@ app.post("/metrics/start/:id", validateHostId, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /metrics/stop/{id}:
|
||||
* post:
|
||||
* summary: Stop metrics collection
|
||||
* description: Stops metrics collection for a specific host and cleans up the SSH session.
|
||||
* tags:
|
||||
* - Server Stats
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* requestBody:
|
||||
* required: false
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* viewerSessionId:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Metrics collection stopped successfully.
|
||||
* 401:
|
||||
* description: Session expired - please log in again.
|
||||
* 500:
|
||||
* description: Failed to stop metrics collection.
|
||||
*/
|
||||
app.post("/metrics/stop/:id", validateHostId, async (req, res) => {
|
||||
const id = Number(req.params.id);
|
||||
const userId = (req as AuthenticatedRequest).userId;
|
||||
@@ -2317,6 +2518,37 @@ app.post("/metrics/stop/:id", validateHostId, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /metrics/connect-totp:
|
||||
* post:
|
||||
* summary: Complete TOTP verification for metrics
|
||||
* description: Verifies the TOTP code and completes the metrics SSH connection.
|
||||
* tags:
|
||||
* - Server Stats
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* sessionId:
|
||||
* type: string
|
||||
* totpCode:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: TOTP verified, metrics connection established.
|
||||
* 400:
|
||||
* description: Missing sessionId or totpCode.
|
||||
* 401:
|
||||
* description: Session expired or invalid TOTP code.
|
||||
* 404:
|
||||
* description: TOTP session not found or expired.
|
||||
* 500:
|
||||
* description: Failed to verify TOTP.
|
||||
*/
|
||||
app.post("/metrics/connect-totp", async (req, res) => {
|
||||
const { sessionId, totpCode } = req.body;
|
||||
const userId = (req as AuthenticatedRequest).userId;
|
||||
@@ -2452,6 +2684,35 @@ app.post("/metrics/connect-totp", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /metrics/heartbeat:
|
||||
* post:
|
||||
* summary: Update viewer heartbeat
|
||||
* description: Updates the heartbeat timestamp for a metrics viewer session to keep it alive.
|
||||
* tags:
|
||||
* - Server Stats
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* viewerSessionId:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Heartbeat updated successfully.
|
||||
* 400:
|
||||
* description: Invalid viewerSessionId.
|
||||
* 401:
|
||||
* description: Session expired - please log in again.
|
||||
* 404:
|
||||
* description: Viewer session not found.
|
||||
* 500:
|
||||
* description: Failed to update heartbeat.
|
||||
*/
|
||||
app.post("/metrics/heartbeat", async (req, res) => {
|
||||
const { viewerSessionId } = req.body;
|
||||
const userId = (req as AuthenticatedRequest).userId;
|
||||
@@ -2484,6 +2745,33 @@ app.post("/metrics/heartbeat", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /metrics/register-viewer:
|
||||
* post:
|
||||
* summary: Register metrics viewer
|
||||
* description: Registers a new viewer session for a host to track who is viewing metrics.
|
||||
* tags:
|
||||
* - Server Stats
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* hostId:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Viewer registered successfully.
|
||||
* 400:
|
||||
* description: Invalid hostId.
|
||||
* 401:
|
||||
* description: Session expired - please log in again.
|
||||
* 500:
|
||||
* description: Failed to register viewer.
|
||||
*/
|
||||
app.post("/metrics/register-viewer", async (req, res) => {
|
||||
const { hostId } = req.body;
|
||||
const userId = (req as AuthenticatedRequest).userId;
|
||||
@@ -2514,6 +2802,35 @@ app.post("/metrics/register-viewer", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /metrics/unregister-viewer:
|
||||
* post:
|
||||
* summary: Unregister metrics viewer
|
||||
* description: Unregisters a viewer session when they stop viewing metrics for a host.
|
||||
* tags:
|
||||
* - Server Stats
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* hostId:
|
||||
* type: integer
|
||||
* viewerSessionId:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Viewer unregistered successfully.
|
||||
* 400:
|
||||
* description: Invalid hostId or viewerSessionId.
|
||||
* 401:
|
||||
* description: Session expired - please log in again.
|
||||
* 500:
|
||||
* description: Failed to unregister viewer.
|
||||
*/
|
||||
app.post("/metrics/unregister-viewer", async (req, res) => {
|
||||
const { hostId, viewerSessionId } = req.body;
|
||||
const userId = (req as AuthenticatedRequest).userId;
|
||||
|
||||
@@ -1450,10 +1450,42 @@ async function killRemoteTunnelByMarker(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/tunnel/status:
|
||||
* get:
|
||||
* summary: Get all tunnel statuses
|
||||
* description: Retrieves the status of all SSH tunnels.
|
||||
* tags:
|
||||
* - SSH Tunnels
|
||||
* responses:
|
||||
* 200:
|
||||
* description: A list of all tunnel statuses.
|
||||
*/
|
||||
app.get("/ssh/tunnel/status", (req, res) => {
|
||||
res.json(getAllTunnelStatus());
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/tunnel/status/{tunnelName}:
|
||||
* get:
|
||||
* summary: Get tunnel status by name
|
||||
* description: Retrieves the status of a specific SSH tunnel by its name.
|
||||
* tags:
|
||||
* - SSH Tunnels
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: tunnelName
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Tunnel status.
|
||||
* 404:
|
||||
* description: Tunnel not found.
|
||||
*/
|
||||
app.get("/ssh/tunnel/status/:tunnelName", (req, res) => {
|
||||
const { tunnelName } = req.params;
|
||||
const status = connectionStatus.get(tunnelName);
|
||||
@@ -1465,6 +1497,39 @@ app.get("/ssh/tunnel/status/:tunnelName", (req, res) => {
|
||||
res.json({ name: tunnelName, status });
|
||||
});
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/tunnel/connect:
|
||||
* post:
|
||||
* summary: Connect SSH tunnel
|
||||
* description: Establishes an SSH tunnel connection with the specified configuration.
|
||||
* tags:
|
||||
* - SSH Tunnels
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* name:
|
||||
* type: string
|
||||
* sourceHostId:
|
||||
* type: integer
|
||||
* tunnelIndex:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Connection request received.
|
||||
* 400:
|
||||
* description: Invalid tunnel configuration.
|
||||
* 401:
|
||||
* description: Authentication required.
|
||||
* 403:
|
||||
* description: Access denied to this host.
|
||||
* 500:
|
||||
* description: Failed to connect tunnel.
|
||||
*/
|
||||
app.post(
|
||||
"/ssh/tunnel/connect",
|
||||
authenticateJWT,
|
||||
@@ -1619,6 +1684,35 @@ app.post(
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/tunnel/disconnect:
|
||||
* post:
|
||||
* summary: Disconnect SSH tunnel
|
||||
* description: Disconnects an active SSH tunnel.
|
||||
* tags:
|
||||
* - SSH Tunnels
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* tunnelName:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Disconnect request received.
|
||||
* 400:
|
||||
* description: Tunnel name required.
|
||||
* 401:
|
||||
* description: Authentication required.
|
||||
* 403:
|
||||
* description: Access denied.
|
||||
* 500:
|
||||
* description: Failed to disconnect tunnel.
|
||||
*/
|
||||
app.post(
|
||||
"/ssh/tunnel/disconnect",
|
||||
authenticateJWT,
|
||||
@@ -1683,6 +1777,35 @@ app.post(
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /ssh/tunnel/cancel:
|
||||
* post:
|
||||
* summary: Cancel tunnel retry
|
||||
* description: Cancels the retry mechanism for a failed SSH tunnel connection.
|
||||
* tags:
|
||||
* - SSH Tunnels
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* tunnelName:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Cancel request received.
|
||||
* 400:
|
||||
* description: Tunnel name required.
|
||||
* 401:
|
||||
* description: Authentication required.
|
||||
* 403:
|
||||
* description: Access denied.
|
||||
* 500:
|
||||
* description: Failed to cancel tunnel retry.
|
||||
*/
|
||||
app.post(
|
||||
"/ssh/tunnel/cancel",
|
||||
authenticateJWT,
|
||||
|
||||
@@ -194,7 +194,7 @@ class UserDataImport {
|
||||
continue;
|
||||
}
|
||||
|
||||
const newHostData = {
|
||||
const newHostData: any = {
|
||||
...host,
|
||||
userId: targetUserId,
|
||||
updatedAt: new Date().toISOString(),
|
||||
@@ -204,7 +204,7 @@ class UserDataImport {
|
||||
newHostData.createdAt = new Date().toISOString();
|
||||
}
|
||||
|
||||
let processedHostData = newHostData;
|
||||
let processedHostData: any = newHostData;
|
||||
if (options.userDataKey) {
|
||||
processedHostData = DataCrypto.encryptRecord(
|
||||
"ssh_data",
|
||||
@@ -275,7 +275,7 @@ class UserDataImport {
|
||||
continue;
|
||||
}
|
||||
|
||||
const newCredentialData = {
|
||||
const newCredentialData: any = {
|
||||
...credential,
|
||||
userId: targetUserId,
|
||||
updatedAt: new Date().toISOString(),
|
||||
@@ -287,7 +287,7 @@ class UserDataImport {
|
||||
newCredentialData.createdAt = new Date().toISOString();
|
||||
}
|
||||
|
||||
let processedCredentialData = newCredentialData;
|
||||
let processedCredentialData: any = newCredentialData;
|
||||
if (options.userDataKey) {
|
||||
processedCredentialData = DataCrypto.encryptRecord(
|
||||
"ssh_credentials",
|
||||
|
||||
@@ -1740,6 +1740,7 @@
|
||||
"state": "State",
|
||||
"process": "Process",
|
||||
"noData": "No listening ports data"
|
||||
},
|
||||
"firewall": {
|
||||
"title": "Firewall",
|
||||
"active": "Active",
|
||||
|
||||
@@ -7,7 +7,8 @@ export type WidgetType =
|
||||
| "processes"
|
||||
| "system"
|
||||
| "login_stats"
|
||||
| "ports";
|
||||
| "ports"
|
||||
| "firewall";
|
||||
|
||||
export interface ListeningPort {
|
||||
protocol: "tcp" | "udp";
|
||||
@@ -21,7 +22,7 @@ export interface ListeningPort {
|
||||
export interface PortsMetrics {
|
||||
source: "ss" | "netstat" | "none";
|
||||
ports: ListeningPort[];
|
||||
| "firewall";
|
||||
}
|
||||
|
||||
export interface FirewallRule {
|
||||
chain: string;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { HostManager } from "@/ui/desktop/apps/host-manager/HostManager";
|
||||
import { HostManager } from "@/ui/desktop/apps/host-manager/hosts/HostManager";
|
||||
import React from "react";
|
||||
|
||||
const HostManagerApp: React.FC = () => {
|
||||
|
||||
@@ -270,7 +270,8 @@ export function ServerStats({
|
||||
case "ports":
|
||||
return (
|
||||
<PortsWidget metrics={metrics} metricsHistory={metricsHistory} />
|
||||
|
||||
);
|
||||
|
||||
case "firewall":
|
||||
return (
|
||||
<FirewallWidget metrics={metrics} metricsHistory={metricsHistory} />
|
||||
|
||||
@@ -361,6 +361,7 @@ export function AppView({
|
||||
rightSidebarOpen={rightSidebarOpen}
|
||||
rightSidebarWidth={rightSidebarWidth}
|
||||
isStandalone={true}
|
||||
/>
|
||||
) : t.type === "tunnel" ? (
|
||||
<TunnelManager
|
||||
hostConfig={t.hostConfig}
|
||||
|
||||
Reference in New Issue
Block a user