mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-23 03:33:53 +00:00
feat: Add PBS docs, increase recent task limit, clean up logs
This commit is contained in:
@@ -14,6 +14,7 @@ A lightweight monitoring application for Proxmox VE that displays real-time stat
|
||||
- [Configuration](#️-configuration)
|
||||
- [Environment Variables](#environment-variables)
|
||||
- [Creating a Proxmox API Token](#creating-a-proxmox-api-token)
|
||||
- [Creating a Proxmox Backup Server API Token](#creating-a-proxmox-backup-server-api-token)
|
||||
- [Required Permissions](#required-permissions)
|
||||
- [Installation](#-installation)
|
||||
- [Running the Application](#-running-the-application)
|
||||
@@ -62,6 +63,36 @@ A lightweight monitoring application for Proxmox VE that displays real-time stat
|
||||
|
||||
If you only need to monitor a single Proxmox cluster or node, you only need to set the primary variables (`PROXMOX_HOST`, `PROXMOX_TOKEN_ID`, `PROXMOX_TOKEN_SECRET`).
|
||||
|
||||
**Proxmox Backup Server (PBS) Configuration (Optional):**
|
||||
|
||||
Pulse can also monitor backup status information from a Proxmox Backup Server instance. If you want to enable this feature, configure the following environment variables:
|
||||
|
||||
- `PBS_HOST`: URL of your Proxmox Backup Server (e.g., `https://your-pbs-ip-or-hostname:8007`).
|
||||
- `PBS_TOKEN_ID`: Your PBS API Token ID (e.g., `user@pam!tokenid`). Create this in the PBS UI (see below).
|
||||
- `PBS_TOKEN_SECRET`: Your PBS API Token Secret.
|
||||
- `PBS_NODE_NAME`: (Potentially Required) The internal hostname of your PBS server. **Crucially, this might be different from the hostname used in `PBS_HOST`**. See detailed explanation below.
|
||||
- `PBS_ALLOW_SELF_SIGNED_CERTS`: (Optional) Set to `true` if your PBS server uses self-signed SSL certificates. Defaults to `false`.
|
||||
- `PBS_PORT`: (Optional) Port for the PBS API. Defaults to `8007`.
|
||||
|
||||
*Note: Currently, Pulse only supports monitoring a single PBS instance. Numbered variables like `PBS_HOST_2` are not yet supported.*
|
||||
|
||||
**Why `PBS_NODE_NAME` is Important:**
|
||||
|
||||
Pulse needs to query task lists specific to the PBS node (e.g., `/api2/json/nodes/{nodeName}/tasks`). While Pulse attempts to discover this node name automatically, this can fail due to API limitations or specific permission issues (like accessing `/api2/json/nodes`, which is not a standard PBS management endpoint).
|
||||
|
||||
If Pulse cannot fetch task data correctly (which might result in recent backups showing as 'stale'), you likely need to set `PBS_NODE_NAME` manually.
|
||||
|
||||
**How to find your PBS Node Name:**
|
||||
1. **SSH:** Log into your PBS server via SSH and run the command `hostname`. The output is the value needed for `PBS_NODE_NAME`.
|
||||
2. **UI:** Log into the PBS web interface. The hostname is typically displayed on the main Dashboard under Server Status.
|
||||
3. **MOTD:** The hostname is often shown in the Message of the Day when you log in via SSH.
|
||||
|
||||
Example: If your PBS connects via `https://minipc-pbs.lan:8007` but its internal hostname is `proxmox-backup-server`, you would set:
|
||||
```
|
||||
PBS_HOST=https://minipc-pbs.lan:8007
|
||||
PBS_NODE_NAME=proxmox-backup-server
|
||||
```
|
||||
|
||||
### Creating a Proxmox API Token
|
||||
|
||||
An API token is recommended for connecting Pulse to Proxmox.
|
||||
@@ -103,6 +134,34 @@ An API token is recommended for connecting Pulse to Proxmox.
|
||||
|
||||
5. **Update your `server/.env` file** with the `Token ID` (which looks like `user@realm!tokenid`, e.g., `pulse-monitor@pam!pulse`) and the `Secret` you saved.
|
||||
|
||||
### Creating a Proxmox Backup Server API Token
|
||||
|
||||
If you are configuring PBS monitoring, you need a separate API token created within PBS.
|
||||
|
||||
1. **Log in to the Proxmox Backup Server web interface**
|
||||
2. **Create a dedicated user** (optional but recommended)
|
||||
* Go to `Configuration` → `Access Control` → `User Management`.
|
||||
* Click `Add`.
|
||||
* Enter a `User ID` (e.g., "pulse-monitor@pam"), set other fields as needed, and click `Add`.
|
||||
3. **Create an API token**
|
||||
* Under `Configuration` → `Access Control`, select `API Token`.
|
||||
* Click `Add`.
|
||||
* Select the `User` (e.g., "pulse-monitor@pam") or `root@pam`.
|
||||
* Enter a `Token Name` (e.g., "pulse").
|
||||
* Leave `Privilege Separation` checked.
|
||||
* Click `Add`.
|
||||
* **Important:** Copy the displayed `Secret` value immediately.
|
||||
4. **Assign permissions**
|
||||
* Under `Configuration` → `Access Control`, select `Permissions`.
|
||||
* Click `Add` → `API Token Permission`.
|
||||
* Path: `/datastore` (This grants access to view datastores and their contents, including backup snapshots).
|
||||
* API Token: Select the token you created (e.g., "pulse-monitor@pam!pulse").
|
||||
* Role: `DatastoreAudit` (Provides read-only access to datastore contents and backups).
|
||||
* Ensure `Propagate` is checked.
|
||||
* Click `Add`.
|
||||
* *(Note: While `DatastoreAudit` on `/datastore` is usually sufficient for backup status, if you still encounter issues fetching task lists, you might need broader permissions like `Audit` on path `/` for the token, although this has shown inconsistent behaviour with API tokens vs user sessions in some PBS versions).*
|
||||
5. **Update your `server/.env` file** with the PBS `Token ID` (`PBS_TOKEN_ID`) and the `Secret` (`PBS_TOKEN_SECRET`).
|
||||
|
||||
### Required Permissions
|
||||
|
||||
The `PVEAuditor` role is recommended as it provides the necessary read-only permissions for Pulse to monitor your Proxmox environment:
|
||||
|
||||
+32
-29
@@ -301,7 +301,7 @@ async function initializeAllPbsClients() {
|
||||
});
|
||||
|
||||
clientData = { client: pbsAxiosInstance, config: config };
|
||||
console.log(`INFO: [PBS Init] Successfully initialized client for instance '${config.name}' (Token Auth)`);
|
||||
console.log(`INFO: Successfully initialized client for instance '${config.name}' (Token Auth)`);
|
||||
} else {
|
||||
// This case should not be reachable anymore if loadPbsConfig only creates 'token' authMethod configs
|
||||
console.error(`ERROR: Unexpected authMethod '${config.authMethod}' found during PBS client initialization for: ${config.name}`);
|
||||
@@ -311,12 +311,12 @@ async function initializeAllPbsClients() {
|
||||
pbsApiClients[config.id] = clientData; // Store successful client keyed by config ID
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`ERROR: Unhandled exception during PBS client initialization for ${config.name}: ${error.message}`);
|
||||
console.error(`ERROR: Unhandled exception during PBS client initialization for ${config.name}: ${error.message}`, error.stack);
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.allSettled(initPromises);
|
||||
console.log(`INFO: [PBS Init] Finished initialization. ${Object.keys(pbsApiClients).length} / ${pbsConfigs.length} PBS clients initialized successfully.`);
|
||||
console.log(`INFO: Finished initialization. ${Object.keys(pbsApiClients).length} / ${pbsConfigs.length} PBS clients initialized successfully.`);
|
||||
}
|
||||
|
||||
if (Object.keys(apiClients).length === 0 && pbsConfigs.length === 0) {
|
||||
@@ -1205,7 +1205,7 @@ async function fetchPbsNodeName(pbsClient) {
|
||||
return 'localhost'; // Fallback
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`ERROR: Failed to fetch PBS nodes list: ${error.message}`);
|
||||
console.error(`ERROR: Failed to fetch PBS nodes list for ${pbsClient.config.name}: ${error.message}`, error.stack);
|
||||
return 'localhost'; // Fallback on error
|
||||
}
|
||||
}
|
||||
@@ -1225,21 +1225,22 @@ async function fetchAllPbsTasksForProcessing(pbsClient, nodeName) {
|
||||
return { tasks: null, error: true };
|
||||
}
|
||||
try {
|
||||
const sinceTimestamp = Math.floor((Date.now() - 7 * 24 * 60 * 60 * 1000) / 1000);
|
||||
const response = await pbsClient.client.get(`/nodes/${nodeName}/tasks`, {
|
||||
params: {
|
||||
since: sinceTimestamp,
|
||||
limit: 1000, // Fetch a larger number to cover 7 days of various tasks
|
||||
errors: 1,
|
||||
}
|
||||
});
|
||||
const sinceTimestamp = Math.floor((Date.now() - 7 * 24 * 60 * 60 * 1000) / 1000); // RESTORED
|
||||
const requestUrl = `/nodes/${nodeName}/tasks`;
|
||||
const requestParams = {
|
||||
since: sinceTimestamp, // RESTORED
|
||||
limit: 1000, // Kept (was 1000 after increasing from 20)
|
||||
errors: 1, // RESTORED
|
||||
};
|
||||
const paramsToSend = Object.keys(requestParams).length > 0 ? { params: requestParams } : {};
|
||||
const response = await pbsClient.client.get(requestUrl, paramsToSend);
|
||||
const allTasks = response.data?.data ?? [];
|
||||
console.log(`INFO: Fetched ${allTasks.length} tasks from PBS for processing.`);
|
||||
return { tasks: allTasks, error: false };
|
||||
} catch (error) {
|
||||
console.error(`ERROR: Failed to fetch PBS task list for node ${nodeName}: ${error.message}`);
|
||||
if (error.response?.status === 401) console.error("ERROR: PBS API authentication failed (401).");
|
||||
else if (error.response?.status === 403) console.error("ERROR: PBS API authorization failed (403).");
|
||||
console.error(`ERROR: Failed to fetch PBS task list for node ${nodeName} (${pbsClient.config.name}): ${error.message}`, error.stack);
|
||||
if (error.response) { // Log more detail if available
|
||||
console.error(`Error details: Status=${error.response.status}, Data=${JSON.stringify(error.response.data)}`);
|
||||
}
|
||||
return { tasks: null, error: true };
|
||||
}
|
||||
}
|
||||
@@ -1305,10 +1306,10 @@ function processPbsTasks(allTasks) {
|
||||
// Process and sort recent tasks for each category
|
||||
const sortTasksDesc = (a, b) => (b.startTime || 0) - (a.startTime || 0);
|
||||
|
||||
const recentBackupTasks = taskResults.backup.list.map(createDetailedTask).sort(sortTasksDesc).slice(0, 20);
|
||||
const recentVerifyTasks = taskResults.verify.list.map(createDetailedTask).sort(sortTasksDesc).slice(0, 20);
|
||||
const recentSyncTasks = taskResults.sync.list.map(createDetailedTask).sort(sortTasksDesc).slice(0, 20);
|
||||
const recentPruneGcTasks = taskResults.pruneGc.list.map(createDetailedTask).sort(sortTasksDesc).slice(0, 20);
|
||||
const recentBackupTasks = taskResults.backup.list.map(createDetailedTask).sort(sortTasksDesc).slice(0, 100);
|
||||
const recentVerifyTasks = taskResults.verify.list.map(createDetailedTask).sort(sortTasksDesc).slice(0, 100);
|
||||
const recentSyncTasks = taskResults.sync.list.map(createDetailedTask).sort(sortTasksDesc).slice(0, 100);
|
||||
const recentPruneGcTasks = taskResults.pruneGc.list.map(createDetailedTask).sort(sortTasksDesc).slice(0, 100);
|
||||
|
||||
console.log(`INFO: Processed PBS Tasks - Backup: ${taskResults.backup.list.length} (OK: ${taskResults.backup.ok}, Fail: ${taskResults.backup.failed}), Verify: ${taskResults.verify.list.length} (OK: ${taskResults.verify.ok}, Fail: ${taskResults.verify.failed}), Sync: ${taskResults.sync.list.length} (OK: ${taskResults.sync.ok}, Fail: ${taskResults.sync.failed}), Prune/GC: ${taskResults.pruneGc.list.length} (OK: ${taskResults.pruneGc.ok}, Fail: ${taskResults.pruneGc.failed})`);
|
||||
|
||||
@@ -1366,11 +1367,13 @@ async function fetchPbsTaskSummaryByType(pbsClient, nodeName, taskTypes) {
|
||||
|
||||
async function fetchPbsDatastoreData(pbsClient) {
|
||||
// Fetches datastore usage details from PBS using the /status/datastore-usage endpoint
|
||||
console.log("INFO: Fetching PBS datastore data...");
|
||||
console.log(`INFO: Fetching PBS datastore data for ${pbsClient.config.name}...`);
|
||||
let datastores = [];
|
||||
const primaryUrl = '/status/datastore-usage';
|
||||
const fallbackUrl = '/config/datastore';
|
||||
try {
|
||||
// Fetch usage stats for all datastores at once
|
||||
const usageResponse = await pbsClient.client.get('/status/datastore-usage');
|
||||
const usageResponse = await pbsClient.client.get(primaryUrl);
|
||||
const usageData = usageResponse.data?.data ?? [];
|
||||
|
||||
if (usageData.length > 0) {
|
||||
@@ -1391,14 +1394,15 @@ async function fetchPbsDatastoreData(pbsClient) {
|
||||
}
|
||||
|
||||
} catch (usageError) {
|
||||
console.error(`ERROR: Failed to fetch PBS datastore usage via /status/datastore-usage: ${usageError.message}. Trying fallback /config/datastore.`);
|
||||
console.error(`ERROR: Failed to fetch PBS datastore usage via ${primaryUrl} for ${pbsClient.config.name}: ${usageError.message}. Trying fallback ${fallbackUrl}.`, usageError.stack);
|
||||
// --- Fallback Logic ---
|
||||
try {
|
||||
const configResponse = await pbsClient.client.get('/config/datastore');
|
||||
const configResponse = await pbsClient.client.get(fallbackUrl);
|
||||
const datastoresConfig = configResponse.data?.data ?? [];
|
||||
if (datastoresConfig.length > 0) {
|
||||
console.log(`INFO: Fetched config for ${datastoresConfig.length} PBS datastores (fallback). Status unavailable.`);
|
||||
datastores = datastoresConfig.map(dsConfig => ({
|
||||
// Map the received data to the expected format
|
||||
datastores = datastoresConfig.map(dsConfig => ({
|
||||
name: dsConfig.name,
|
||||
path: dsConfig.path,
|
||||
total: null, // Usage/Status info unavailable from config
|
||||
@@ -1410,13 +1414,12 @@ async function fetchPbsDatastoreData(pbsClient) {
|
||||
console.warn("WARN: Fallback fetch of PBS datastore config also returned empty data.");
|
||||
}
|
||||
} catch (configError) {
|
||||
console.error(`ERROR: Fallback fetch of PBS datastore config failed: ${configError.message}`);
|
||||
if (configError.response?.status === 401 || configError.response?.status === 403) {
|
||||
console.error("ERROR: PBS API authentication/authorization failed for datastore config access.");
|
||||
console.error(`ERROR: Fallback fetch of PBS datastore config (${fallbackUrl}) for ${pbsClient.config.name}: ${configError.message}`, configError.stack);
|
||||
if (configError.response) { // Log more detail if available
|
||||
console.error(`Fallback error details: Status=${configError.response.status}, Data=${JSON.stringify(configError.response.data)}`);
|
||||
}
|
||||
// Keep datastores as empty array if both primary and fallback attempts fail
|
||||
}
|
||||
// --- End Fallback ---
|
||||
}
|
||||
|
||||
console.log(`INFO: Finished fetching PBS datastore data. Found ${datastores.length} datastores.`);
|
||||
|
||||
Reference in New Issue
Block a user