This commit is contained in:
Muhammad Ibrahim
2025-11-14 20:45:40 +00:00
parent ab700a3bc8
commit a37b479de6
10 changed files with 431 additions and 564 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -19,20 +19,20 @@ NC='\033[0m' # No Color
# Functions
error() {
printf "%b\n" "${RED}ERROR: $1${NC}" >&2
printf "%b\n" "${RED}ERROR: $1${NC}" >&2
exit 1
}
info() {
printf "%b\n" "${BLUE} $1${NC}"
printf "%b\n" "${BLUE}INFO: $1${NC}"
}
success() {
printf "%b\n" "${GREEN} $1${NC}"
printf "%b\n" "${GREEN}SUCCESS: $1${NC}"
}
warning() {
printf "%b\n" "${YELLOW}⚠️ $1${NC}"
printf "%b\n" "${YELLOW}WARNING: $1${NC}"
}
# Check if running as root
@@ -42,7 +42,7 @@ fi
# Verify system datetime and timezone
verify_datetime() {
info "🕐 Verifying system datetime and timezone..."
info "Verifying system datetime and timezone..."
# Get current system time
system_time=$(date)
@@ -50,7 +50,7 @@ verify_datetime() {
# Display current datetime info
echo ""
printf "%b\n" "${BLUE}📅 Current System Date/Time:${NC}"
printf "%b\n" "${BLUE}Current System Date/Time:${NC}"
echo " • Date/Time: $system_time"
echo " • Timezone: $timezone"
echo ""
@@ -62,26 +62,26 @@ verify_datetime() {
read -r response
case "$response" in
[Yy]*)
success "Date/time verification passed"
success "Date/time verification passed"
echo ""
return 0
;;
*)
echo ""
printf "%b\n" "${RED}Date/time verification failed${NC}"
printf "%b\n" "${RED}Date/time verification failed${NC}"
echo ""
printf "%b\n" "${YELLOW}💡 Please fix the date/time and re-run the installation script:${NC}"
printf "%b\n" "${YELLOW}Please fix the date/time and re-run the installation script:${NC}"
echo " sudo timedatectl set-time 'YYYY-MM-DD HH:MM:SS'"
echo " sudo timedatectl set-timezone 'America/New_York' # or your timezone"
echo " sudo timedatectl list-timezones # to see available timezones"
echo ""
printf "%b\n" "${BLUE} After fixing the date/time, re-run this installation script.${NC}"
printf "%b\n" "${BLUE}After fixing the date/time, re-run this installation script.${NC}"
error "Installation cancelled - please fix date/time and re-run"
;;
esac
else
# Non-interactive (piped from curl) - show warning and continue
printf "%b\n" "${YELLOW}⚠️ Non-interactive installation detected${NC}"
printf "%b\n" "${YELLOW}Non-interactive installation detected${NC}"
echo ""
echo "Please verify the date/time shown above is correct."
echo "If the date/time is incorrect, it may cause issues with:"
@@ -89,8 +89,8 @@ verify_datetime() {
echo " • Scheduled updates"
echo " • Data synchronization"
echo ""
printf "%b\n" "${GREEN}Continuing with installation...${NC}"
success "Date/time verification completed (assumed correct)"
printf "%b\n" "${GREEN}Continuing with installation...${NC}"
success "Date/time verification completed (assumed correct)"
echo ""
fi
}
@@ -159,7 +159,7 @@ if [ -z "$ARCHITECTURE" ]; then
ARCHITECTURE="arm"
;;
*)
warning "⚠️ Unknown architecture '$arch_raw', defaulting to amd64"
warning "Unknown architecture '$arch_raw', defaulting to amd64"
ARCHITECTURE="amd64"
;;
esac
@@ -177,31 +177,21 @@ case "$*" in
esac
if [ "$FORCE_INSTALL" = "true" ]; then
FORCE_INSTALL="true"
warning "⚠️ Force mode enabled - will bypass broken packages"
warning "Force mode enabled - will bypass broken packages"
fi
# Get unique machine ID for this host
MACHINE_ID=$(get_machine_id)
export MACHINE_ID
info "🚀 Starting PatchMon Agent Installation..."
info "📋 Server: $PATCHMON_URL"
info "🔑 API ID: $(echo "$API_ID" | cut -c1-16)..."
info "🆔 Machine ID: $(echo "$MACHINE_ID" | cut -c1-16)..."
info "🏗️ Architecture: $ARCHITECTURE"
# Display diagnostic information
echo ""
printf "%b\n" "${BLUE}🔧 Installation Diagnostics:${NC}"
echo " • URL: $PATCHMON_URL"
echo " • CURL FLAGS: $CURL_FLAGS"
echo " • API ID: $(echo "$API_ID" | cut -c1-16)..."
echo " • API Key: $(echo "$API_KEY" | cut -c1-16)..."
echo " • Architecture: $ARCHITECTURE"
echo ""
info "Starting PatchMon Agent Installation..."
info "Server: $PATCHMON_URL"
info "API ID: $(echo "$API_ID" | cut -c1-16)..."
info "Machine ID: $(echo "$MACHINE_ID" | cut -c1-16)..."
info "Architecture: $ARCHITECTURE"
# Install required dependencies
info "📦 Installing required dependencies..."
info "Installing required dependencies..."
echo ""
# Function to check if a command exists
@@ -417,7 +407,7 @@ if command -v apt-get >/dev/null 2>&1; then
if [ "$FORCE_INSTALL" = "true" ]; then
warning "Detected broken packages on system - force mode will work around them"
else
warning "⚠️ Broken packages detected on system"
warning "Broken packages detected on system"
warning "If installation fails, retry with: curl -s {URL}/api/v1/hosts/install --force -H ..."
fi
fi
@@ -466,88 +456,88 @@ success "Dependencies installation completed"
echo ""
# Step 1: Handle existing configuration directory
info "📁 Setting up configuration directory..."
info "Setting up configuration directory..."
# Check if configuration directory already exists
if [ -d "/etc/patchmon" ]; then
warning "⚠️ Configuration directory already exists at /etc/patchmon"
warning "⚠️ Preserving existing configuration files"
warning "Configuration directory already exists at /etc/patchmon"
warning "Preserving existing configuration files"
# List existing files for user awareness
info "📋 Existing files in /etc/patchmon:"
info "Existing files in /etc/patchmon:"
ls -la /etc/patchmon/ 2>/dev/null | grep -v "^total" | while read -r line; do
echo " $line"
done
else
info "📁 Creating new configuration directory..."
info "Creating new configuration directory..."
mkdir -p /etc/patchmon
fi
# Check if agent is already configured and working (before we overwrite anything)
info "🔍 Checking if agent is already configured..."
info "Checking if agent is already configured..."
if [ -f /etc/patchmon/config.yml ] && [ -f /etc/patchmon/credentials.yml ]; then
if [ -f /usr/local/bin/patchmon-agent ]; then
info "📋 Found existing agent configuration"
info "🧪 Testing existing configuration with ping..."
info "Found existing agent configuration"
info "Testing existing configuration with ping..."
if /usr/local/bin/patchmon-agent ping >/dev/null 2>&1; then
success "Agent is already configured and ping successful"
info "📋 Existing configuration is working - skipping installation"
success "Agent is already configured and ping successful"
info "Existing configuration is working - skipping installation"
info ""
info "If you want to reinstall, remove the configuration files first:"
info " sudo rm -f /etc/patchmon/config.yml /etc/patchmon/credentials.yml"
echo ""
exit 0
else
warning "⚠️ Agent configuration exists but ping failed"
warning "⚠️ Will move existing configuration and reinstall"
warning "Agent configuration exists but ping failed"
warning "Will move existing configuration and reinstall"
echo ""
fi
else
warning "⚠️ Configuration files exist but agent binary is missing"
warning "⚠️ Will move existing configuration and reinstall"
warning "Configuration files exist but agent binary is missing"
warning "Will move existing configuration and reinstall"
echo ""
fi
else
success "Agent not yet configured - proceeding with installation"
success "Agent not yet configured - proceeding with installation"
echo ""
fi
# Step 2: Create configuration files
info "🔐 Creating configuration files..."
info "Creating configuration files..."
# Check if config file already exists
if [ -f "/etc/patchmon/config.yml" ]; then
warning "⚠️ Config file already exists at /etc/patchmon/config.yml"
warning "⚠️ Moving existing file out of the way for fresh installation"
warning "Config file already exists at /etc/patchmon/config.yml"
warning "Moving existing file out of the way for fresh installation"
# Clean up old config backups (keep only last 3)
ls -t /etc/patchmon/config.yml.backup.* 2>/dev/null | tail -n +4 | xargs -r rm -f
# Move existing file out of the way
mv /etc/patchmon/config.yml /etc/patchmon/config.yml.backup.$(date +%Y%m%d_%H%M%S)
info "📋 Moved existing config to: /etc/patchmon/config.yml.backup.$(date +%Y%m%d_%H%M%S)"
info "Moved existing config to: /etc/patchmon/config.yml.backup.$(date +%Y%m%d_%H%M%S)"
fi
# Check if credentials file already exists
if [ -f "/etc/patchmon/credentials.yml" ]; then
warning "⚠️ Credentials file already exists at /etc/patchmon/credentials.yml"
warning "⚠️ Moving existing file out of the way for fresh installation"
warning "Credentials file already exists at /etc/patchmon/credentials.yml"
warning "Moving existing file out of the way for fresh installation"
# Clean up old credential backups (keep only last 3)
ls -t /etc/patchmon/credentials.yml.backup.* 2>/dev/null | tail -n +4 | xargs -r rm -f
# Move existing file out of the way
mv /etc/patchmon/credentials.yml /etc/patchmon/credentials.yml.backup.$(date +%Y%m%d_%H%M%S)
info "📋 Moved existing credentials to: /etc/patchmon/credentials.yml.backup.$(date +%Y%m%d_%H%M%S)"
info "Moved existing credentials to: /etc/patchmon/credentials.yml.backup.$(date +%Y%m%d_%H%M%S)"
fi
# Clean up old credentials file if it exists (from previous installations)
if [ -f "/etc/patchmon/credentials" ]; then
warning "⚠️ Found old credentials file, removing it..."
warning "Found old credentials file, removing it..."
rm -f /etc/patchmon/credentials
info "📋 Removed old credentials file"
info "Removed old credentials file"
fi
# Create main config file
@@ -574,29 +564,29 @@ chmod 600 /etc/patchmon/config.yml
chmod 600 /etc/patchmon/credentials.yml
# Step 3: Download the PatchMon agent binary using API credentials
info "📥 Downloading PatchMon agent binary..."
info "Downloading PatchMon agent binary..."
# Determine the binary filename based on architecture
BINARY_NAME="patchmon-agent-linux-${ARCHITECTURE}"
# Check if agent binary already exists
if [ -f "/usr/local/bin/patchmon-agent" ]; then
warning "⚠️ Agent binary already exists at /usr/local/bin/patchmon-agent"
warning "⚠️ Moving existing file out of the way for fresh installation"
warning "Agent binary already exists at /usr/local/bin/patchmon-agent"
warning "Moving existing file out of the way for fresh installation"
# Clean up old agent backups (keep only last 3)
ls -t /usr/local/bin/patchmon-agent.backup.* 2>/dev/null | tail -n +4 | xargs -r rm -f
# Move existing file out of the way
mv /usr/local/bin/patchmon-agent /usr/local/bin/patchmon-agent.backup.$(date +%Y%m%d_%H%M%S)
info "📋 Moved existing agent to: /usr/local/bin/patchmon-agent.backup.$(date +%Y%m%d_%H%M%S)"
info "Moved existing agent to: /usr/local/bin/patchmon-agent.backup.$(date +%Y%m%d_%H%M%S)"
fi
# Clean up old shell script if it exists (from previous installations)
if [ -f "/usr/local/bin/patchmon-agent.sh" ]; then
warning "⚠️ Found old shell script agent, removing it..."
warning "Found old shell script agent, removing it..."
rm -f /usr/local/bin/patchmon-agent.sh
info "📋 Removed old shell script agent"
info "Removed old shell script agent"
fi
# Download the binary
@@ -610,30 +600,30 @@ chmod +x /usr/local/bin/patchmon-agent
# Get the agent version from the binary
AGENT_VERSION=$(/usr/local/bin/patchmon-agent version 2>/dev/null || echo "Unknown")
info "📋 Agent version: $AGENT_VERSION"
info "Agent version: $AGENT_VERSION"
# Handle existing log files and create log directory
info "📁 Setting up log directory..."
info "Setting up log directory..."
# Create log directory if it doesn't exist
mkdir -p /etc/patchmon/logs
# Handle existing log files
if [ -f "/etc/patchmon/logs/patchmon-agent.log" ]; then
warning "⚠️ Existing log file found at /etc/patchmon/logs/patchmon-agent.log"
warning "⚠️ Rotating log file for fresh start"
warning "Existing log file found at /etc/patchmon/logs/patchmon-agent.log"
warning "Rotating log file for fresh start"
# Rotate the log file
mv /etc/patchmon/logs/patchmon-agent.log /etc/patchmon/logs/patchmon-agent.log.old.$(date +%Y%m%d_%H%M%S)
info "📋 Log file rotated to: /etc/patchmon/logs/patchmon-agent.log.old.$(date +%Y%m%d_%H%M%S)"
info "Log file rotated to: /etc/patchmon/logs/patchmon-agent.log.old.$(date +%Y%m%d_%H%M%S)"
fi
# Step 4: Test the configuration
info "🧪 Testing API credentials and connectivity..."
info "Testing API credentials and connectivity..."
if /usr/local/bin/patchmon-agent ping; then
success "TEST: API credentials are valid and server is reachable"
success "TEST: API credentials are valid and server is reachable"
else
error "Failed to validate API credentials or reach server"
error "Failed to validate API credentials or reach server"
fi
# Step 5: Setup service for WebSocket connection
@@ -641,16 +631,16 @@ fi
# Detect init system and create appropriate service
if command -v systemctl >/dev/null 2>&1; then
# Systemd is available
info "🔧 Setting up systemd service..."
info "Setting up systemd service..."
# Stop and disable existing service if it exists
if systemctl is-active --quiet patchmon-agent.service 2>/dev/null; then
warning "⚠️ Stopping existing PatchMon agent service..."
warning "Stopping existing PatchMon agent service..."
systemctl stop patchmon-agent.service
fi
if systemctl is-enabled --quiet patchmon-agent.service 2>/dev/null; then
warning "⚠️ Disabling existing PatchMon agent service..."
warning "Disabling existing PatchMon agent service..."
systemctl disable patchmon-agent.service
fi
@@ -680,9 +670,9 @@ EOF
# Clean up old crontab entries if they exist (from previous installations)
if crontab -l 2>/dev/null | grep -q "patchmon-agent"; then
warning "⚠️ Found old crontab entries, removing them..."
warning "Found old crontab entries, removing them..."
crontab -l 2>/dev/null | grep -v "patchmon-agent" | crontab -
info "📋 Removed old crontab entries"
info "Removed old crontab entries"
fi
# Reload systemd and enable/start the service
@@ -692,25 +682,25 @@ EOF
# Check if service started successfully
if systemctl is-active --quiet patchmon-agent.service; then
success "PatchMon Agent service started successfully"
info "🔗 WebSocket connection established"
success "PatchMon Agent service started successfully"
info "WebSocket connection established"
else
warning "⚠️ Service may have failed to start. Check status with: systemctl status patchmon-agent"
warning "Service may have failed to start. Check status with: systemctl status patchmon-agent"
fi
SERVICE_TYPE="systemd"
elif [ -d /etc/init.d ] && command -v rc-service >/dev/null 2>&1; then
# OpenRC is available (Alpine Linux)
info "🔧 Setting up OpenRC service..."
info "Setting up OpenRC service..."
# Stop and disable existing service if it exists
if rc-service patchmon-agent status >/dev/null 2>&1; then
warning "⚠️ Stopping existing PatchMon agent service..."
warning "Stopping existing PatchMon agent service..."
rc-service patchmon-agent stop
fi
if rc-update show default 2>/dev/null | grep -q "patchmon-agent"; then
warning "⚠️ Disabling existing PatchMon agent service..."
warning "Disabling existing PatchMon agent service..."
rc-update del patchmon-agent default
fi
@@ -737,9 +727,9 @@ EOF
# Clean up old crontab entries if they exist (from previous installations)
if crontab -l 2>/dev/null | grep -q "patchmon-agent"; then
warning "⚠️ Found old crontab entries, removing them..."
warning "Found old crontab entries, removing them..."
crontab -l 2>/dev/null | grep -v "patchmon-agent" | crontab -
info "📋 Removed old crontab entries"
info "Removed old crontab entries"
fi
# Enable and start the service
@@ -748,40 +738,40 @@ EOF
# Check if service started successfully
if rc-service patchmon-agent status >/dev/null 2>&1; then
success "PatchMon Agent service started successfully"
info "🔗 WebSocket connection established"
success "PatchMon Agent service started successfully"
info "WebSocket connection established"
else
warning "⚠️ Service may have failed to start. Check status with: rc-service patchmon-agent status"
warning "Service may have failed to start. Check status with: rc-service patchmon-agent status"
fi
SERVICE_TYPE="openrc"
else
# No init system detected, use crontab as fallback
warning "⚠️ No init system detected (systemd or OpenRC). Using crontab for service management."
warning "No init system detected (systemd or OpenRC). Using crontab for service management."
# Clean up old crontab entries if they exist
if crontab -l 2>/dev/null | grep -q "patchmon-agent"; then
warning "⚠️ Found old crontab entries, removing them..."
warning "Found old crontab entries, removing them..."
crontab -l 2>/dev/null | grep -v "patchmon-agent" | crontab -
info "📋 Removed old crontab entries"
info "Removed old crontab entries"
fi
# Add crontab entry to run the agent
(crontab -l 2>/dev/null; echo "@reboot /usr/local/bin/patchmon-agent serve >/dev/null 2>&1") | crontab -
info "📋 Added crontab entry for PatchMon agent"
info "Added crontab entry for PatchMon agent"
# Start the agent manually
/usr/local/bin/patchmon-agent serve >/dev/null 2>&1 &
success "PatchMon Agent started in background"
info "🔗 WebSocket connection established"
success "PatchMon Agent started in background"
info "WebSocket connection established"
SERVICE_TYPE="crontab"
fi
# Installation complete
success "🎉 PatchMon Agent installation completed successfully!"
success "PatchMon Agent installation completed successfully!"
echo ""
printf "%b\n" "${GREEN}📋 Installation Summary:${NC}"
printf "%b\n" "${GREEN}Installation Summary:${NC}"
echo " • Configuration directory: /etc/patchmon"
echo " • Agent binary installed: /usr/local/bin/patchmon-agent"
echo " • Architecture: $ARCHITECTURE"
@@ -801,16 +791,16 @@ echo " • Logs directory: /etc/patchmon/logs"
MOVED_FILES=$(ls /etc/patchmon/credentials.yml.backup.* /etc/patchmon/config.yml.backup.* /usr/local/bin/patchmon-agent.backup.* /etc/patchmon/logs/patchmon-agent.log.old.* /usr/local/bin/patchmon-agent.sh.backup.* /etc/patchmon/credentials.backup.* 2>/dev/null || true)
if [ -n "$MOVED_FILES" ]; then
echo ""
printf "%b\n" "${YELLOW}📋 Files Moved for Fresh Installation:${NC}"
printf "%b\n" "${YELLOW}Files Moved for Fresh Installation:${NC}"
echo "$MOVED_FILES" | while read -r moved_file; do
echo "$moved_file"
done
echo ""
printf "%b\n" "${BLUE}💡 Note: Old files are automatically cleaned up (keeping last 3)${NC}"
printf "%b\n" "${BLUE}Note: Old files are automatically cleaned up (keeping last 3)${NC}"
fi
echo ""
printf "%b\n" "${BLUE}🔧 Management Commands:${NC}"
printf "%b\n" "${BLUE}Management Commands:${NC}"
echo " • Test connection: /usr/local/bin/patchmon-agent ping"
echo " • Manual report: /usr/local/bin/patchmon-agent report"
echo " • Check status: /usr/local/bin/patchmon-agent diagnostics"
@@ -827,4 +817,4 @@ else
echo " • Restart service: pkill -f 'patchmon-agent serve' && /usr/local/bin/patchmon-agent serve &"
fi
echo ""
success "Your system is now being monitored by PatchMon!"
success "Your system is now being monitored by PatchMon!"

View File

@@ -1,6 +1,6 @@
{
"name": "patchmon-backend",
"version": "1.3.3",
"version": "1.3.4",
"description": "Backend API for Linux Patch Monitoring System",
"license": "AGPL-3.0",
"main": "src/server.js",

View File

@@ -6,5 +6,5 @@ VITE_API_URL=http://localhost:3001/api/v1
# Application Metadata
VITE_APP_NAME=PatchMon
VITE_APP_VERSION=1.3.1
VITE_APP_VERSION=1.3.4

View File

@@ -1,7 +1,7 @@
{
"name": "patchmon-frontend",
"private": true,
"version": "1.3.3",
"version": "1.3.4",
"license": "AGPL-3.0",
"type": "module",
"scripts": {

View File

@@ -23,7 +23,7 @@ const Integrations = () => {
const token_base64_id = useId();
const gethomepage_config_id = useId();
const [activeTab, setActiveTab] = useState("proxmox");
const [activeTab, setActiveTab] = useState("auto-enrollment");
const [tokens, setTokens] = useState([]);
const [host_groups, setHostGroups] = useState([]);
const [loading, setLoading] = useState(true);
@@ -34,6 +34,7 @@ const Integrations = () => {
const [show_secret, setShowSecret] = useState(false);
const [server_url, setServerUrl] = useState("");
const [force_proxmox_install, setForceProxmoxInstall] = useState(false);
const [usage_type, setUsageType] = useState("proxmox-lxc");
// Form state
const [form_data, setFormData] = useState({
@@ -120,12 +121,13 @@ const Integrations = () => {
e.preventDefault();
try {
// Determine integration type based on active tab
// Determine integration type based on active tab or usage_type
let integration_type = "proxmox-lxc";
if (activeTab === "gethomepage") {
integration_type = "gethomepage";
} else if (activeTab === "api") {
integration_type = "api";
} else if (activeTab === "auto-enrollment") {
// Use the usage_type selected in the modal
integration_type = usage_type;
}
const data = {
@@ -148,7 +150,7 @@ const Integrations = () => {
}
// Add scopes for API credentials
if (activeTab === "api" && form_data.scopes) {
if (usage_type === "api" && form_data.scopes) {
data.scopes = form_data.scopes;
}
@@ -156,6 +158,7 @@ const Integrations = () => {
setNewToken(response.data.token);
setShowCreateModal(false);
load_tokens();
// Keep usage_type so the success modal can use it
// Reset form
setFormData({
@@ -338,14 +341,14 @@ const Integrations = () => {
<div className="border-b border-secondary-200 dark:border-secondary-600 flex">
<button
type="button"
onClick={() => handleTabChange("proxmox")}
onClick={() => handleTabChange("auto-enrollment")}
className={`px-6 py-3 text-sm font-medium ${
activeTab === "proxmox"
activeTab === "auto-enrollment"
? "text-primary-600 dark:text-primary-400 border-b-2 border-primary-500 bg-primary-50 dark:bg-primary-900/20"
: "text-secondary-500 dark:text-secondary-400 hover:text-secondary-700 dark:hover:text-secondary-300 hover:bg-secondary-50 dark:hover:bg-secondary-700/50"
}`}
>
Proxmox LXC
Auto-Enrollment & API
</button>
<button
type="button"
@@ -358,17 +361,6 @@ const Integrations = () => {
>
GetHomepage
</button>
<button
type="button"
onClick={() => handleTabChange("api")}
className={`px-6 py-3 text-sm font-medium ${
activeTab === "api"
? "text-primary-600 dark:text-primary-400 border-b-2 border-primary-500 bg-primary-50 dark:bg-primary-900/20"
: "text-secondary-500 dark:text-secondary-400 hover:text-secondary-700 dark:hover:text-secondary-300 hover:bg-secondary-50 dark:hover:bg-secondary-700/50"
}`}
>
API
</button>
<button
type="button"
onClick={() => handleTabChange("docker")}
@@ -385,8 +377,8 @@ const Integrations = () => {
{/* Tab Content */}
<div className="p-6">
{/* Proxmox Tab */}
{activeTab === "proxmox" && (
{/* Auto-Enrollment & API Tab */}
{activeTab === "auto-enrollment" && (
<div className="space-y-6">
{/* Header with New Token Button */}
<div className="flex items-center justify-between">
@@ -396,11 +388,11 @@ const Integrations = () => {
</div>
<div>
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white">
Proxmox LXC Auto-Enrollment
Auto-Enrollment & API Credentials
</h3>
<p className="text-sm text-secondary-600 dark:text-secondary-400">
Automatically discover and enroll LXC containers from
Proxmox hosts
Manage tokens for Proxmox LXC auto-enrollment and API
access
</p>
</div>
</div>
@@ -419,171 +411,229 @@ const Integrations = () => {
<div className="text-center py-8">
<div className="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-primary-600" />
</div>
) : tokens.length === 0 ? (
) : tokens.filter(
(token) =>
token.metadata?.integration_type === "proxmox-lxc" ||
token.metadata?.integration_type === "api",
).length === 0 ? (
<div className="text-center py-8 text-secondary-600 dark:text-secondary-400">
<p>No auto-enrollment tokens created yet.</p>
<p>No auto-enrollment or API tokens created yet.</p>
<p className="text-sm mt-2">
Create a token to enable automatic host enrollment from
Proxmox.
Create a token to enable Proxmox auto-enrollment or API
access.
</p>
</div>
) : (
<div className="space-y-3">
{tokens.map((token) => (
<div
key={token.id}
className="border border-secondary-200 dark:border-secondary-600 rounded-lg p-4 hover:border-primary-300 dark:hover:border-primary-700 transition-colors"
>
<div className="flex justify-between items-start">
<div className="flex-1">
<div className="flex items-center gap-2 flex-wrap">
<h4 className="font-medium text-secondary-900 dark:text-white">
{token.token_name}
</h4>
<span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200">
Proxmox LXC
</span>
{token.is_active ? (
<span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200">
Active
</span>
) : (
<span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-secondary-100 text-secondary-800 dark:bg-secondary-700 dark:text-secondary-200">
Inactive
</span>
)}
{tokens
.filter(
(token) =>
token.metadata?.integration_type === "proxmox-lxc" ||
token.metadata?.integration_type === "api",
)
.map((token) => (
<div
key={token.id}
className="border border-secondary-200 dark:border-secondary-600 rounded-lg p-4 hover:border-primary-300 dark:hover:border-primary-700 transition-colors"
>
<div className="flex justify-between items-start">
<div className="flex-1">
<div className="flex items-center gap-2 flex-wrap">
<h4 className="font-medium text-secondary-900 dark:text-white">
{token.token_name}
</h4>
{token.metadata?.integration_type ===
"proxmox-lxc" ? (
<span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200">
Proxmox LXC
</span>
) : (
<span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200">
API
</span>
)}
{token.is_active ? (
<span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200">
Active
</span>
) : (
<span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-secondary-100 text-secondary-800 dark:bg-secondary-700 dark:text-secondary-200">
Inactive
</span>
)}
</div>
<div className="mt-2 space-y-1 text-sm text-secondary-600 dark:text-secondary-400">
<div className="flex items-center gap-2">
<span className="font-mono text-xs bg-secondary-100 dark:bg-secondary-700 px-2 py-1 rounded">
{token.token_key}
</span>
<button
type="button"
onClick={() =>
copy_to_clipboard(
token.token_key,
`key-${token.id}`,
)
}
className="text-primary-600 hover:text-primary-700 dark:text-primary-400"
>
{copy_success[`key-${token.id}`] ? (
<CheckCircle className="h-4 w-4" />
) : (
<Copy className="h-4 w-4" />
)}
</button>
</div>
{token.metadata?.integration_type ===
"proxmox-lxc" && (
<p>
Usage: {token.hosts_created_today}/
{token.max_hosts_per_day} hosts today
</p>
)}
{token.metadata?.integration_type ===
"proxmox-lxc" &&
token.host_groups && (
<p>
Default Group:{" "}
<span
className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium"
style={{
backgroundColor: `${token.host_groups.color}20`,
color: token.host_groups.color,
}}
>
{token.host_groups.name}
</span>
</p>
)}
{token.metadata?.integration_type === "api" &&
token.scopes && (
<p>
Scopes:{" "}
{Object.entries(token.scopes)
.map(
([resource, actions]) =>
`${resource}: ${Array.isArray(actions) ? actions.join(", ") : actions}`,
)
.join(" | ")}
</p>
)}
{token.allowed_ip_ranges?.length > 0 && (
<p>
Allowed IPs:{" "}
{token.allowed_ip_ranges.join(", ")}
</p>
)}
<p>Created: {format_date(token.created_at)}</p>
{token.last_used_at && (
<p>
Last Used: {format_date(token.last_used_at)}
</p>
)}
{token.expires_at && (
<p>
Expires: {format_date(token.expires_at)}
{new Date(token.expires_at) <
new Date() && (
<span className="ml-2 text-red-600 dark:text-red-400">
(Expired)
</span>
)}
</p>
)}
</div>
</div>
<div className="mt-2 space-y-1 text-sm text-secondary-600 dark:text-secondary-400">
<div className="flex items-center gap-2">
<span className="font-mono text-xs bg-secondary-100 dark:bg-secondary-700 px-2 py-1 rounded">
{token.token_key}
</span>
<div className="flex items-center gap-2">
{token.metadata?.integration_type === "api" && (
<button
type="button"
onClick={() =>
copy_to_clipboard(
token.token_key,
`key-${token.id}`,
)
}
className="text-primary-600 hover:text-primary-700 dark:text-primary-400"
onClick={() => open_edit_modal(token)}
className="px-3 py-1 text-sm rounded bg-blue-100 text-blue-700 hover:bg-blue-200 dark:bg-blue-900 dark:text-blue-300"
>
{copy_success[`key-${token.id}`] ? (
<CheckCircle className="h-4 w-4" />
) : (
<Copy className="h-4 w-4" />
)}
Edit
</button>
</div>
<p>
Usage: {token.hosts_created_today}/
{token.max_hosts_per_day} hosts today
</p>
{token.host_groups && (
<p>
Default Group:{" "}
<span
className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium"
style={{
backgroundColor: `${token.host_groups.color}20`,
color: token.host_groups.color,
}}
>
{token.host_groups.name}
</span>
</p>
)}
{token.allowed_ip_ranges?.length > 0 && (
<p>
Allowed IPs:{" "}
{token.allowed_ip_ranges.join(", ")}
</p>
)}
<p>Created: {format_date(token.created_at)}</p>
{token.last_used_at && (
<p>
Last Used: {format_date(token.last_used_at)}
</p>
)}
{token.expires_at && (
<p>
Expires: {format_date(token.expires_at)}
{new Date(token.expires_at) < new Date() && (
<span className="ml-2 text-red-600 dark:text-red-400">
(Expired)
</span>
)}
</p>
)}
<button
type="button"
onClick={() =>
toggle_token_active(token.id, token.is_active)
}
className={`px-3 py-1 text-sm rounded ${
token.is_active
? "bg-secondary-100 text-secondary-700 hover:bg-secondary-200 dark:bg-secondary-700 dark:text-secondary-300"
: "bg-green-100 text-green-700 hover:bg-green-200 dark:bg-green-900 dark:text-green-300"
}`}
>
{token.is_active ? "Disable" : "Enable"}
</button>
<button
type="button"
onClick={() =>
delete_token(token.id, token.token_name)
}
className="text-red-600 hover:text-red-800 dark:text-red-400 p-2"
>
<Trash2 className="h-4 w-4" />
</button>
</div>
</div>
<div className="flex items-center gap-2">
<button
type="button"
onClick={() =>
toggle_token_active(token.id, token.is_active)
}
className={`px-3 py-1 text-sm rounded ${
token.is_active
? "bg-secondary-100 text-secondary-700 hover:bg-secondary-200 dark:bg-secondary-700 dark:text-secondary-300"
: "bg-green-100 text-green-700 hover:bg-green-200 dark:bg-green-900 dark:text-green-300"
}`}
>
{token.is_active ? "Disable" : "Enable"}
</button>
<button
type="button"
onClick={() =>
delete_token(token.id, token.token_name)
}
className="text-red-600 hover:text-red-800 dark:text-red-400 p-2"
>
<Trash2 className="h-4 w-4" />
</button>
</div>
</div>
</div>
))}
))}
</div>
)}
{/* Documentation Section */}
<div className="bg-primary-50 dark:bg-primary-900/20 border border-primary-200 dark:border-primary-800 rounded-lg p-6">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-primary-900 dark:text-primary-200">
How to Use Auto-Enrollment
</h3>
<a
href="https://docs.patchmon.net/books/patchmon-application-documentation/page/proxmox-lxc-auto-enrollment-guide"
target="_blank"
rel="noopener noreferrer"
className="px-4 py-2 bg-primary-600 hover:bg-primary-700 dark:bg-primary-500 dark:hover:bg-primary-600 text-white rounded-lg flex items-center gap-2 transition-colors"
>
<BookOpen className="h-4 w-4" />
Documentation
</a>
</div>
<ol className="list-decimal list-inside space-y-2 text-sm text-primary-800 dark:text-primary-300">
<li>
Create a new auto-enrollment token using the button above
</li>
<li>
Copy the one-line installation command shown in the
success dialog
</li>
<li>SSH into your Proxmox host as root</li>
<li>
Paste and run the command - it will automatically discover
and enroll all running LXC containers
</li>
<li>View enrolled containers in the Hosts page</li>
</ol>
<div className="mt-4 p-3 bg-primary-100 dark:bg-primary-900/40 rounded border border-primary-200 dark:border-primary-700">
<p className="text-xs text-primary-800 dark:text-primary-300">
<strong>💡 Tip:</strong> You can run the same command
multiple times safely - already enrolled containers will
be automatically skipped.
</p>
<h3 className="text-lg font-semibold text-primary-900 dark:text-primary-200 mb-4">
Documentation
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* Proxmox Documentation */}
<div className="border border-primary-200 dark:border-primary-700 rounded-lg p-4 bg-white dark:bg-secondary-800">
<div className="flex items-center gap-2 mb-3">
<Server className="h-5 w-5 text-blue-600 dark:text-blue-400" />
<h4 className="font-semibold text-secondary-900 dark:text-white">
Proxmox LXC Auto-Enrollment
</h4>
</div>
<p className="text-sm text-secondary-600 dark:text-secondary-400 mb-3">
Automatically discover and enroll LXC containers from
Proxmox hosts.
</p>
<a
href="https://docs.patchmon.net/books/patchmon-application-documentation/page/proxmox-lxc-auto-enrollment-guide"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 px-3 py-2 bg-primary-600 hover:bg-primary-700 dark:bg-primary-500 dark:hover:bg-primary-600 text-white rounded-lg text-sm transition-colors"
>
<BookOpen className="h-4 w-4" />
View Guide
</a>
</div>
{/* API Documentation */}
<div className="border border-primary-200 dark:border-primary-700 rounded-lg p-4 bg-white dark:bg-secondary-800">
<div className="flex items-center gap-2 mb-3">
<Server className="h-5 w-5 text-green-600 dark:text-green-400" />
<h4 className="font-semibold text-secondary-900 dark:text-white">
API Credentials
</h4>
</div>
<p className="text-sm text-secondary-600 dark:text-secondary-400 mb-3">
Programmatic access to PatchMon data using Basic
Authentication.
</p>
<a
href="https://docs.patchmon.net/books/patchmon-application-documentation/page/integration-api-documentation"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 px-3 py-2 bg-primary-600 hover:bg-primary-700 dark:bg-primary-500 dark:hover:bg-primary-600 text-white rounded-lg text-sm transition-colors"
>
<BookOpen className="h-4 w-4" />
View Guide
</a>
</div>
</div>
</div>
</div>
@@ -849,214 +899,6 @@ const Integrations = () => {
</div>
)}
{/* API Tab */}
{activeTab === "api" && (
<div className="space-y-6">
{/* Header with New Credential Button */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-primary-100 dark:bg-primary-900 rounded-lg flex items-center justify-center">
<Server className="h-5 w-5 text-primary-600 dark:text-primary-400" />
</div>
<div>
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white">
API Credentials
</h3>
<p className="text-sm text-secondary-600 dark:text-secondary-400">
Manage API credentials for programmatic access to
PatchMon data
</p>
</div>
</div>
<button
type="button"
onClick={() => setShowCreateModal(true)}
className="btn-primary flex items-center gap-2"
>
<Plus className="h-4 w-4" />
New Credential
</button>
</div>
{/* API Credentials List */}
{loading ? (
<div className="text-center py-8">
<div className="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-primary-600" />
</div>
) : tokens.filter(
(token) => token.metadata?.integration_type === "api",
).length === 0 ? (
<div className="text-center py-8 text-secondary-600 dark:text-secondary-400">
<p>No API credentials created yet.</p>
<p className="text-sm mt-2">
Create a credential to enable programmatic access to
PatchMon.
</p>
</div>
) : (
<div className="space-y-3">
{tokens
.filter(
(token) => token.metadata?.integration_type === "api",
)
.map((token) => (
<div
key={token.id}
className="border border-secondary-200 dark:border-secondary-600 rounded-lg p-4 hover:border-primary-300 dark:hover:border-primary-700 transition-colors"
>
<div className="flex justify-between items-start">
<div className="flex-1">
<div className="flex items-center gap-2 flex-wrap">
<h4 className="font-medium text-secondary-900 dark:text-white">
{token.token_name}
</h4>
<span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200">
API
</span>
{token.is_active ? (
<span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200">
Active
</span>
) : (
<span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-secondary-100 text-secondary-800 dark:bg-secondary-700 dark:text-secondary-200">
Inactive
</span>
)}
</div>
<div className="mt-2 space-y-1 text-sm text-secondary-600 dark:text-secondary-400">
<div className="flex items-center gap-2">
<span className="font-mono text-xs bg-secondary-100 dark:bg-secondary-700 px-2 py-1 rounded">
{token.token_key}
</span>
<button
type="button"
onClick={() =>
copy_to_clipboard(
token.token_key,
`key-${token.id}`,
)
}
className="text-primary-600 hover:text-primary-700 dark:text-primary-400"
>
{copy_success[`key-${token.id}`] ? (
<CheckCircle className="h-4 w-4" />
) : (
<Copy className="h-4 w-4" />
)}
</button>
</div>
{token.scopes && (
<p>
Scopes:{" "}
{Object.entries(token.scopes)
.map(
([resource, actions]) =>
`${resource}: ${Array.isArray(actions) ? actions.join(", ") : actions}`,
)
.join(" | ")}
</p>
)}
{token.allowed_ip_ranges?.length > 0 && (
<p>
Allowed IPs:{" "}
{token.allowed_ip_ranges.join(", ")}
</p>
)}
<p>Created: {format_date(token.created_at)}</p>
{token.last_used_at && (
<p>
Last Used: {format_date(token.last_used_at)}
</p>
)}
{token.expires_at && (
<p>
Expires: {format_date(token.expires_at)}
{new Date(token.expires_at) <
new Date() && (
<span className="ml-2 text-red-600 dark:text-red-400">
(Expired)
</span>
)}
</p>
)}
</div>
</div>
<div className="flex items-center gap-2">
<button
type="button"
onClick={() => open_edit_modal(token)}
className="px-3 py-1 text-sm rounded bg-blue-100 text-blue-700 hover:bg-blue-200 dark:bg-blue-900 dark:text-blue-300"
>
Edit
</button>
<button
type="button"
onClick={() =>
toggle_token_active(token.id, token.is_active)
}
className={`px-3 py-1 text-sm rounded ${
token.is_active
? "bg-secondary-100 text-secondary-700 hover:bg-secondary-200 dark:bg-secondary-700 dark:text-secondary-300"
: "bg-green-100 text-green-700 hover:bg-green-200 dark:bg-green-900 dark:text-green-300"
}`}
>
{token.is_active ? "Disable" : "Enable"}
</button>
<button
type="button"
onClick={() =>
delete_token(token.id, token.token_name)
}
className="text-red-600 hover:text-red-800 dark:text-red-400 p-2"
>
<Trash2 className="h-4 w-4" />
</button>
</div>
</div>
</div>
))}
</div>
)}
{/* Documentation Section */}
<div className="bg-primary-50 dark:bg-primary-900/20 border border-primary-200 dark:border-primary-800 rounded-lg p-6">
<h3 className="text-lg font-semibold text-primary-900 dark:text-primary-200 mb-4">
Using API Credentials
</h3>
<div className="space-y-4 text-sm text-primary-800 dark:text-primary-300">
<p>
API credentials allow you to programmatically access
PatchMon data using Basic Authentication.
</p>
<div>
<p className="font-semibold mb-2">
Example cURL Request:
</p>
<div className="bg-primary-100 dark:bg-primary-900/40 p-3 rounded border border-primary-200 dark:border-primary-700 font-mono text-xs overflow-x-auto">
curl -u "YOUR_API_KEY:YOUR_API_SECRET" \<br />
&nbsp;&nbsp;{server_url}/api/v1/api/hosts
</div>
</div>
<div>
<p className="font-semibold mb-2">
Query Hosts by Group:
</p>
<div className="bg-primary-100 dark:bg-primary-900/40 p-3 rounded border border-primary-200 dark:border-primary-700 font-mono text-xs overflow-x-auto">
curl -u "YOUR_API_KEY:YOUR_API_SECRET" \<br />
&nbsp;&nbsp;"{server_url}
/api/v1/api/hosts?hostgroup=Production,Development"
</div>
</div>
<p className="text-xs">
<strong>💡 Tip:</strong> You can filter by host group
names or UUIDs. Multiple groups can be specified as a
comma-separated list.
</p>
</div>
</div>
</div>
)}
{/* Docker Tab */}
{activeTab === "docker" && (
<div className="space-y-6">
@@ -1206,13 +1048,14 @@ const Integrations = () => {
<h2 className="text-xl font-bold text-secondary-900 dark:text-white">
{activeTab === "gethomepage"
? "Create GetHomepage API Key"
: activeTab === "api"
? "Create API Credential"
: "Create Auto-Enrollment Token"}
: "Create Token"}
</h2>
<button
type="button"
onClick={() => setShowCreateModal(false)}
onClick={() => {
setShowCreateModal(false);
setUsageType("proxmox-lxc");
}}
className="text-secondary-400 hover:text-secondary-600 dark:hover:text-secondary-200"
>
<X className="h-6 w-6" />
@@ -1220,6 +1063,27 @@ const Integrations = () => {
</div>
<form onSubmit={create_token} className="space-y-4">
{activeTab === "auto-enrollment" && (
<label className="block">
<span className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">
Usage Type *
</span>
<select
value={usage_type}
onChange={(e) => setUsageType(e.target.value)}
className="w-full px-3 py-2 border border-secondary-300 dark:border-secondary-600 rounded-md bg-white dark:bg-secondary-700 text-secondary-900 dark:text-white"
>
<option value="proxmox-lxc">
Proxmox LXC Auto-Enrollment
</option>
<option value="api">API Credentials</option>
</select>
<p className="mt-1 text-xs text-secondary-500 dark:text-secondary-400">
Select the type of token you want to create
</p>
</label>
)}
<label className="block">
<span className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">
Token Name *
@@ -1234,7 +1098,7 @@ const Integrations = () => {
placeholder={
activeTab === "gethomepage"
? "e.g., GetHomepage Widget"
: activeTab === "api"
: usage_type === "api"
? "e.g., Ansible Inventory"
: "e.g., Proxmox Production"
}
@@ -1242,60 +1106,61 @@ const Integrations = () => {
/>
</label>
{activeTab === "proxmox" && (
<>
<label className="block">
<span className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">
Max Hosts Per Day
</span>
<input
type="number"
min="1"
max="1000"
value={form_data.max_hosts_per_day}
onChange={(e) =>
setFormData({
...form_data,
max_hosts_per_day: e.target.value,
})
}
className="w-full px-3 py-2 border border-secondary-300 dark:border-secondary-600 rounded-md bg-white dark:bg-secondary-700 text-secondary-900 dark:text-white"
/>
<p className="mt-1 text-xs text-secondary-500 dark:text-secondary-400">
Maximum number of hosts that can be enrolled per day
using this token
</p>
</label>
{usage_type === "proxmox-lxc" &&
activeTab === "auto-enrollment" && (
<>
<label className="block">
<span className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">
Max Hosts Per Day
</span>
<input
type="number"
min="1"
max="1000"
value={form_data.max_hosts_per_day}
onChange={(e) =>
setFormData({
...form_data,
max_hosts_per_day: e.target.value,
})
}
className="w-full px-3 py-2 border border-secondary-300 dark:border-secondary-600 rounded-md bg-white dark:bg-secondary-700 text-secondary-900 dark:text-white"
/>
<p className="mt-1 text-xs text-secondary-500 dark:text-secondary-400">
Maximum number of hosts that can be enrolled per day
using this token
</p>
</label>
<label className="block">
<span className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">
Default Host Group (Optional)
</span>
<select
value={form_data.default_host_group_id}
onChange={(e) =>
setFormData({
...form_data,
default_host_group_id: e.target.value,
})
}
className="w-full px-3 py-2 border border-secondary-300 dark:border-secondary-600 rounded-md bg-white dark:bg-secondary-700 text-secondary-900 dark:text-white"
>
<option value="">No default group</option>
{host_groups.map((group) => (
<option key={group.id} value={group.id}>
{group.name}
</option>
))}
</select>
<p className="mt-1 text-xs text-secondary-500 dark:text-secondary-400">
Auto-enrolled hosts will be assigned to this group
</p>
</label>
</>
)}
<label className="block">
<span className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1">
Default Host Group (Optional)
</span>
<select
value={form_data.default_host_group_id}
onChange={(e) =>
setFormData({
...form_data,
default_host_group_id: e.target.value,
})
}
className="w-full px-3 py-2 border border-secondary-300 dark:border-secondary-600 rounded-md bg-white dark:bg-secondary-700 text-secondary-900 dark:text-white"
>
<option value="">No default group</option>
{host_groups.map((group) => (
<option key={group.id} value={group.id}>
{group.name}
</option>
))}
</select>
<p className="mt-1 text-xs text-secondary-500 dark:text-secondary-400">
Auto-enrolled hosts will be assigned to this group
</p>
</label>
</>
)}
{activeTab === "api" && (
{usage_type === "api" && activeTab === "auto-enrollment" && (
<div className="block">
<span className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-2">
Scopes *
@@ -1390,7 +1255,10 @@ const Integrations = () => {
</button>
<button
type="button"
onClick={() => setShowCreateModal(false)}
onClick={() => {
setShowCreateModal(false);
setUsageType("proxmox-lxc");
}}
className="flex-1 bg-secondary-100 dark:bg-secondary-700 text-secondary-700 dark:text-secondary-300 py-2 px-4 rounded-md hover:bg-secondary-200 dark:hover:bg-secondary-600"
>
Cancel
@@ -1411,9 +1279,11 @@ const Integrations = () => {
<div className="flex items-center gap-2">
<CheckCircle className="h-6 w-6 text-green-600 dark:text-green-400" />
<h2 className="text-lg font-bold text-secondary-900 dark:text-white">
{activeTab === "gethomepage"
{new_token.metadata?.integration_type === "gethomepage" ||
activeTab === "gethomepage"
? "API Key Created Successfully"
: activeTab === "api"
: new_token.metadata?.integration_type === "api" ||
usage_type === "api"
? "API Credential Created Successfully"
: "Token Created Successfully"}
</h2>
@@ -1423,6 +1293,7 @@ const Integrations = () => {
onClick={() => {
setNewToken(null);
setShowSecret(false);
setUsageType("proxmox-lxc");
}}
className="text-secondary-400 hover:text-secondary-600 dark:hover:text-secondary-200"
>
@@ -1538,31 +1409,34 @@ const Integrations = () => {
</div>
</div>
{activeTab === "api" && new_token.scopes && (
<div className="mt-4">
<div className="block text-xs font-medium text-secondary-700 dark:text-secondary-300 mb-2">
Granted Scopes
{(new_token.metadata?.integration_type === "api" ||
usage_type === "api") &&
new_token.scopes && (
<div className="mt-4">
<div className="block text-xs font-medium text-secondary-700 dark:text-secondary-300 mb-2">
Granted Scopes
</div>
<div className="bg-secondary-50 dark:bg-secondary-900 border border-secondary-300 dark:border-secondary-600 rounded-md p-3">
{Object.entries(new_token.scopes).map(
([resource, actions]) => (
<div key={resource} className="text-sm">
<span className="font-semibold text-secondary-800 dark:text-secondary-200 capitalize">
{resource}:
</span>{" "}
<span className="text-secondary-600 dark:text-secondary-400">
{Array.isArray(actions)
? actions.join(", ").toUpperCase()
: actions}
</span>
</div>
),
)}
</div>
</div>
<div className="bg-secondary-50 dark:bg-secondary-900 border border-secondary-300 dark:border-secondary-600 rounded-md p-3">
{Object.entries(new_token.scopes).map(
([resource, actions]) => (
<div key={resource} className="text-sm">
<span className="font-semibold text-secondary-800 dark:text-secondary-200 capitalize">
{resource}:
</span>{" "}
<span className="text-secondary-600 dark:text-secondary-400">
{Array.isArray(actions)
? actions.join(", ").toUpperCase()
: actions}
</span>
</div>
),
)}
</div>
</div>
)}
)}
{activeTab === "api" && (
{(new_token.metadata?.integration_type === "api" ||
usage_type === "api") && (
<div className="mt-6">
<div className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-2">
Usage Examples
@@ -1635,7 +1509,8 @@ const Integrations = () => {
</div>
)}
{activeTab === "proxmox" && (
{(new_token.metadata?.integration_type === "proxmox-lxc" ||
usage_type === "proxmox-lxc") && (
<div className="mt-6">
<div className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-2">
One-Line Installation Command
@@ -1703,7 +1578,8 @@ const Integrations = () => {
</div>
)}
{activeTab === "gethomepage" && (
{(new_token.metadata?.integration_type === "gethomepage" ||
activeTab === "gethomepage") && (
<div className="mt-3 space-y-3">
<div>
<label
@@ -1835,6 +1711,7 @@ const Integrations = () => {
onClick={() => {
setNewToken(null);
setShowSecret(false);
setUsageType("proxmox-lxc");
}}
className="w-full btn-primary py-2 px-4 rounded-md"
>

View File

@@ -1,6 +1,6 @@
{
"name": "patchmon",
"version": "1.3.3",
"version": "1.3.4",
"description": "Linux Patch Monitoring System",
"license": "AGPL-3.0",
"private": true,