mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-27 21:55:24 +00:00
* feat: shell controller, TabStage, layout and theme plugins with repair view * feat: move the classic layout into its own bundled plugin * feat: move color themes into a bundled themes plugin * fix: plugin tests and type checks work with a linked sdk * fix: home tab, layout switch and missing layout fallbacks for looks * chore: add classic layout and themes to the readme * chore: drop example plugin links from the plugin guide * feat: plugin filters by type, paged browse, hide plugins needing newer termix * feat: move desktop, status, import, audit and branding features into plugins * feat: host lock api so a plugin can load hosts from a config file (#1581) * chore: add hosts config to the plugin table * feat: login policy hook for plugins and optional https redirect (#1584) * chore: add login security to the plugin table * feat: api key socket tickets so a master can open remote sessions (#1654) * feat: sync moves to a plugin, desktop gets multiple accounts (#1580, #1654) * chore: add sync to the plugin table * feat: move the sync screens and wizard out of core into the plugin (#1580) * fix: add a neutral linked desktop hook for plugins (#1580) * fix: show the linked account's name and admin flag in the app (#1580) * feat: ssh transport providers, core ssh relay and plugin folders api * feat: support FIDO2 security keys through ssh agent * feat: add companion panels beside host tabs * feat: mTLS client certificates for desktop server links * feat: native web view for plugins in the desktop app * fix: detect the desktop link window by Electron's default user agent Fixes #1675 * fix: changelog for desktop link fix * fix: keep text primary keys in the plugin data export * fix: ship sharp binaries for Windows arm64 builds * feat: client cert on the sync link types (sdk 1.0.18) * chore: update translations * feat: themed title bar for the desktop app (#1690) * chore: changelog for the themed title bar * fix: draw the title bar in the sidebar color * feat: per host layout for the terminal panel bar * chore: sdk 1.0.19 for the panel bar layout * chore: bundle all 43 plugins for 26.10.3 * chore: update translations --------- Co-authored-by: SirHephaistos <60616553+SirHephaistos@users.noreply.github.com> Co-authored-by: Giovanny Aranda <garanda21@gmail.com>
241 lines
6.7 KiB
TypeScript
241 lines
6.7 KiB
TypeScript
import type { AuthenticatedRequest } from "../../../types/index.js";
|
|
import express, { type Request, type Response } from "express";
|
|
import { dashboardLogger } from "../../utils/logger.js";
|
|
import { AuthManager } from "../../utils/auth-manager.js";
|
|
import { DataCrypto } from "../../utils/data-crypto.js";
|
|
import { recentActivity } from "../../plugins/activity-provider.js";
|
|
|
|
const router = express.Router();
|
|
|
|
const authManager = AuthManager.getInstance();
|
|
const authenticateJWT = authManager.createAuthMiddleware();
|
|
|
|
const serverStartTime = Date.now();
|
|
|
|
function isUserDataUnlocked(userId: string): boolean {
|
|
return DataCrypto.getUserDataKey(userId) !== null;
|
|
}
|
|
|
|
/**
|
|
* @openapi
|
|
* /dashboard/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.
|
|
*/
|
|
router.get("/uptime", authenticateJWT, async (req: Request, res: Response) => {
|
|
try {
|
|
const uptimeMs = Date.now() - serverStartTime;
|
|
const uptimeSeconds = Math.floor(uptimeMs / 1000);
|
|
const days = Math.floor(uptimeSeconds / 86400);
|
|
const hours = Math.floor((uptimeSeconds % 86400) / 3600);
|
|
const minutes = Math.floor((uptimeSeconds % 3600) / 60);
|
|
|
|
res.json({
|
|
uptimeMs,
|
|
uptimeSeconds,
|
|
formatted: `${days}d ${hours}h ${minutes}m`,
|
|
});
|
|
} catch (err) {
|
|
dashboardLogger.error("Failed to get uptime", err);
|
|
res.status(500).json({ error: "Failed to get uptime" });
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @openapi
|
|
* /dashboard/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.
|
|
*/
|
|
router.get(
|
|
"/activity/recent",
|
|
authenticateJWT,
|
|
async (req: Request, res: Response) => {
|
|
try {
|
|
const userId = (req as AuthenticatedRequest).userId;
|
|
|
|
if (!isUserDataUnlocked(userId)) {
|
|
return res.status(401).json({
|
|
error: "Session expired - please log in again",
|
|
code: "SESSION_EXPIRED",
|
|
});
|
|
}
|
|
|
|
const limit = Number(req.query.limit) || 20;
|
|
|
|
const activities = await recentActivity.list(userId, limit);
|
|
|
|
res.json(activities);
|
|
} catch (err) {
|
|
dashboardLogger.error("Failed to get recent activity", err);
|
|
res.status(500).json({ error: "Failed to get recent activity" });
|
|
}
|
|
},
|
|
);
|
|
|
|
/**
|
|
* @openapi
|
|
* /dashboard/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
|
|
* description: An activity type a plugin's tab declares, lowercase letters, digits and underscores.
|
|
* 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.
|
|
*/
|
|
router.post(
|
|
"/activity/log",
|
|
authenticateJWT,
|
|
async (req: Request, res: Response) => {
|
|
try {
|
|
const userId = (req as AuthenticatedRequest).userId;
|
|
|
|
if (!isUserDataUnlocked(userId)) {
|
|
return res.status(401).json({
|
|
error: "Session expired - please log in again",
|
|
code: "SESSION_EXPIRED",
|
|
});
|
|
}
|
|
|
|
const { type, hostId, hostName } = req.body;
|
|
|
|
if (!type || !hostId || !hostName) {
|
|
return res.status(400).json({
|
|
error: "Missing required fields: type, hostId, hostName",
|
|
});
|
|
}
|
|
|
|
const result = await recentActivity.record(userId, {
|
|
type,
|
|
hostId,
|
|
hostName,
|
|
});
|
|
if (result.status === "invalid_type") {
|
|
return res.status(400).json({
|
|
error:
|
|
"Invalid activity type. Use lowercase letters, digits and underscores.",
|
|
});
|
|
}
|
|
if (result.status === "rate_limited") {
|
|
return res.json({
|
|
message: "Activity already logged recently (rate limited)",
|
|
});
|
|
}
|
|
if (result.status === "unavailable") {
|
|
return res
|
|
.status(404)
|
|
.json({ error: "Recent activity is not available" });
|
|
}
|
|
if (result.status === "denied") {
|
|
return res
|
|
.status(404)
|
|
.json({ error: "Host not found or access denied" });
|
|
}
|
|
|
|
res.json({ message: "Activity logged", id: result.id });
|
|
} catch (err) {
|
|
dashboardLogger.error("Failed to log activity", err);
|
|
res.status(500).json({ error: "Failed to log activity" });
|
|
}
|
|
},
|
|
);
|
|
|
|
/**
|
|
* @openapi
|
|
* /dashboard/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.
|
|
*/
|
|
router.delete(
|
|
"/activity/reset",
|
|
authenticateJWT,
|
|
async (req: Request, res: Response) => {
|
|
try {
|
|
const userId = (req as AuthenticatedRequest).userId;
|
|
|
|
await recentActivity.deleteByUserId(userId);
|
|
|
|
dashboardLogger.success("Recent activity cleared", {
|
|
operation: "reset_recent_activity",
|
|
userId,
|
|
});
|
|
|
|
res.json({ message: "Recent activity cleared" });
|
|
} catch (err) {
|
|
dashboardLogger.error("Failed to reset activity", err);
|
|
res.status(500).json({ error: "Failed to reset activity" });
|
|
}
|
|
},
|
|
);
|
|
|
|
export default router;
|