mirror of
https://github.com/9technologygroup/patchmon.net.git
synced 2025-11-09 16:37:29 +00:00
fix(frontend): variable unused
This commit is contained in:
@@ -9,11 +9,8 @@ import {
|
|||||||
ChevronDown,
|
ChevronDown,
|
||||||
Clock,
|
Clock,
|
||||||
Columns,
|
Columns,
|
||||||
Copy,
|
|
||||||
ExternalLink,
|
ExternalLink,
|
||||||
Eye,
|
|
||||||
Eye as EyeIcon,
|
Eye as EyeIcon,
|
||||||
EyeOff,
|
|
||||||
EyeOff as EyeOffIcon,
|
EyeOff as EyeOffIcon,
|
||||||
Filter,
|
Filter,
|
||||||
GripVertical,
|
GripVertical,
|
||||||
@@ -35,7 +32,6 @@ import {
|
|||||||
dashboardAPI,
|
dashboardAPI,
|
||||||
formatRelativeTime,
|
formatRelativeTime,
|
||||||
hostGroupsAPI,
|
hostGroupsAPI,
|
||||||
settingsAPI,
|
|
||||||
} from "../utils/api";
|
} from "../utils/api";
|
||||||
import { OSIcon } from "../utils/osIcons.jsx";
|
import { OSIcon } from "../utils/osIcons.jsx";
|
||||||
|
|
||||||
@@ -232,565 +228,6 @@ const AddHostModal = ({ isOpen, onClose, onSuccess }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Credentials Modal Component
|
|
||||||
const CredentialsModal = ({ host, isOpen, onClose }) => {
|
|
||||||
const apiIdId = useId();
|
|
||||||
const apiKeyId = useId();
|
|
||||||
const [showApiKey, setShowApiKey] = useState(false);
|
|
||||||
const [activeTab, setActiveTab] = useState(
|
|
||||||
host?.isNewHost ? "quick" : "credentials",
|
|
||||||
);
|
|
||||||
|
|
||||||
// Update active tab when host changes
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (host?.isNewHost) {
|
|
||||||
setActiveTab("quick");
|
|
||||||
} else {
|
|
||||||
setActiveTab("credentials");
|
|
||||||
}
|
|
||||||
}, [host?.isNewHost]);
|
|
||||||
|
|
||||||
const copyToClipboard = async (text, label) => {
|
|
||||||
try {
|
|
||||||
// Try modern clipboard API first
|
|
||||||
if (navigator.clipboard && window.isSecureContext) {
|
|
||||||
await navigator.clipboard.writeText(text);
|
|
||||||
alert(`${label} copied to clipboard!`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback for older browsers or non-secure contexts
|
|
||||||
const textArea = document.createElement("textarea");
|
|
||||||
textArea.value = text;
|
|
||||||
textArea.style.position = "fixed";
|
|
||||||
textArea.style.left = "-999999px";
|
|
||||||
textArea.style.top = "-999999px";
|
|
||||||
document.body.appendChild(textArea);
|
|
||||||
textArea.focus();
|
|
||||||
textArea.select();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const successful = document.execCommand("copy");
|
|
||||||
if (successful) {
|
|
||||||
alert(`${label} copied to clipboard!`);
|
|
||||||
} else {
|
|
||||||
throw new Error("Copy command failed");
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// If all else fails, show the text in a prompt
|
|
||||||
prompt(`Copy this ${label.toLowerCase()}:`, text);
|
|
||||||
} finally {
|
|
||||||
document.body.removeChild(textArea);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Failed to copy to clipboard:", err);
|
|
||||||
// Show the text in a prompt as last resort
|
|
||||||
prompt(`Copy this ${label.toLowerCase()}:`, text);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fetch server URL from settings
|
|
||||||
const { data: settings } = useQuery({
|
|
||||||
queryKey: ["settings"],
|
|
||||||
queryFn: () => settingsAPI.get().then((res) => res.data),
|
|
||||||
enabled: isOpen, // Only fetch when modal is open
|
|
||||||
});
|
|
||||||
|
|
||||||
const serverUrl =
|
|
||||||
settings?.server_url || window.location.origin.replace(":3000", ":3001");
|
|
||||||
|
|
||||||
const getSetupCommands = () => {
|
|
||||||
// Get current time for crontab scheduling
|
|
||||||
const now = new Date();
|
|
||||||
const currentMinute = now.getMinutes();
|
|
||||||
const currentHour = now.getHours();
|
|
||||||
|
|
||||||
return {
|
|
||||||
oneLine: `curl -sSL ${serverUrl}/api/v1/hosts/install | bash -s -- ${serverUrl} "${host?.api_id}" "${host?.api_key}"`,
|
|
||||||
|
|
||||||
download: `# Download and setup PatchMon agent
|
|
||||||
curl -o /tmp/patchmon-agent.sh ${serverUrl}/api/v1/hosts/agent/download
|
|
||||||
sudo mkdir -p /etc/patchmon
|
|
||||||
sudo mv /tmp/patchmon-agent.sh /usr/local/bin/patchmon-agent.sh
|
|
||||||
sudo chmod +x /usr/local/bin/patchmon-agent.sh`,
|
|
||||||
|
|
||||||
configure: `# Configure API credentials
|
|
||||||
sudo /usr/local/bin/patchmon-agent.sh configure "${host?.api_id}" "${host?.api_key}"`,
|
|
||||||
|
|
||||||
test: `# Test the configuration
|
|
||||||
sudo /usr/local/bin/patchmon-agent.sh test`,
|
|
||||||
|
|
||||||
initialUpdate: `# Send initial package data
|
|
||||||
sudo /usr/local/bin/patchmon-agent.sh update`,
|
|
||||||
|
|
||||||
crontab: `# Add to crontab for hourly updates starting at current time (run as root)
|
|
||||||
echo "${currentMinute} * * * * /usr/local/bin/patchmon-agent.sh update >/dev/null 2>&1" | sudo crontab -`,
|
|
||||||
|
|
||||||
fullSetup: `#!/bin/bash
|
|
||||||
# Complete PatchMon Agent Setup Script
|
|
||||||
# Run this on the target host: ${host?.friendly_name}
|
|
||||||
|
|
||||||
echo "🔄 Setting up PatchMon agent..."
|
|
||||||
|
|
||||||
# Download and install agent
|
|
||||||
echo "📥 Downloading agent script..."
|
|
||||||
curl -o /tmp/patchmon-agent.sh ${serverUrl}/api/v1/hosts/agent/download
|
|
||||||
sudo mkdir -p /etc/patchmon
|
|
||||||
sudo mv /tmp/patchmon-agent.sh /usr/local/bin/patchmon-agent.sh
|
|
||||||
sudo chmod +x /usr/local/bin/patchmon-agent.sh
|
|
||||||
|
|
||||||
# Configure credentials
|
|
||||||
echo "🔑 Configuring API credentials..."
|
|
||||||
sudo /usr/local/bin/patchmon-agent.sh configure "${host?.api_id}" "${host?.api_key}"
|
|
||||||
|
|
||||||
# Test configuration
|
|
||||||
echo "🧪 Testing configuration..."
|
|
||||||
sudo /usr/local/bin/patchmon-agent.sh test
|
|
||||||
|
|
||||||
# Send initial update
|
|
||||||
echo "📊 Sending initial package data..."
|
|
||||||
sudo /usr/local/bin/patchmon-agent.sh update
|
|
||||||
|
|
||||||
# Setup crontab starting at current time
|
|
||||||
echo "⏰ Setting up hourly crontab starting at ${currentHour.toString().padStart(2, "0")}:${currentMinute.toString().padStart(2, "0")}..."
|
|
||||||
echo "${currentMinute} * * * * /usr/local/bin/patchmon-agent.sh update >/dev/null 2>&1" | sudo crontab -
|
|
||||||
|
|
||||||
echo "✅ PatchMon agent setup complete!"
|
|
||||||
echo " - Agent installed: /usr/local/bin/patchmon-agent.sh"
|
|
||||||
echo " - Config directory: /etc/patchmon/"
|
|
||||||
echo " - Updates: Every hour via crontab (starting at ${currentHour.toString().padStart(2, "0")}:${currentMinute.toString().padStart(2, "0")})"
|
|
||||||
echo " - View logs: tail -f /var/log/patchmon-agent.log"`,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!isOpen || !host) return null;
|
|
||||||
|
|
||||||
const commands = getSetupCommands();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
|
||||||
<div className="bg-white rounded-lg p-6 w-full max-w-4xl max-h-[90vh] overflow-y-auto">
|
|
||||||
<div className="flex justify-between items-center mb-4">
|
|
||||||
<h3 className="text-lg font-medium text-secondary-900">
|
|
||||||
Host Setup - {host.friendly_name}
|
|
||||||
</h3>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={onClose}
|
|
||||||
className="text-secondary-400 hover:text-secondary-600"
|
|
||||||
>
|
|
||||||
<X className="h-5 w-5" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Tabs */}
|
|
||||||
<div className="flex space-x-1 mb-6 bg-secondary-100 p-1 rounded-lg">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setActiveTab("quick")}
|
|
||||||
className={`px-4 py-2 rounded-md text-sm font-medium transition-colors ${
|
|
||||||
activeTab === "quick"
|
|
||||||
? "bg-white text-secondary-900 shadow-sm"
|
|
||||||
: "text-secondary-600 hover:text-secondary-900"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
Quick Install
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setActiveTab("credentials")}
|
|
||||||
className={`px-4 py-2 rounded-md text-sm font-medium transition-colors ${
|
|
||||||
activeTab === "credentials"
|
|
||||||
? "bg-white text-secondary-900 shadow-sm"
|
|
||||||
: "text-secondary-600 hover:text-secondary-900"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
API Credentials
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setActiveTab("setup")}
|
|
||||||
className={`px-4 py-2 rounded-md text-sm font-medium transition-colors ${
|
|
||||||
activeTab === "setup"
|
|
||||||
? "bg-white text-secondary-900 shadow-sm"
|
|
||||||
: "text-secondary-600 hover:text-secondary-900"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
Setup Instructions
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setActiveTab("script")}
|
|
||||||
className={`px-4 py-2 rounded-md text-sm font-medium transition-colors ${
|
|
||||||
activeTab === "script"
|
|
||||||
? "bg-white text-secondary-900 shadow-sm"
|
|
||||||
: "text-secondary-600 hover:text-secondary-900"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
Auto-Setup Script
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Tab Content */}
|
|
||||||
{activeTab === "quick" && (
|
|
||||||
<div className="space-y-6">
|
|
||||||
<div className="bg-green-50 border border-green-200 rounded-md p-4">
|
|
||||||
<h4 className="text-sm font-medium text-green-800 mb-2">
|
|
||||||
🚀 One-Line Installation
|
|
||||||
</h4>
|
|
||||||
<p className="text-sm text-green-700">
|
|
||||||
Copy and paste this single command on{" "}
|
|
||||||
<strong>{host.friendly_name}</strong> to install and configure
|
|
||||||
the PatchMon agent automatically.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="border border-secondary-200 rounded-lg p-4">
|
|
||||||
<h5 className="font-medium text-secondary-900 mb-3">
|
|
||||||
Installation Command
|
|
||||||
</h5>
|
|
||||||
<div className="bg-secondary-900 text-secondary-100 p-3 rounded font-mono text-sm">
|
|
||||||
<div className="flex justify-between items-start">
|
|
||||||
<code className="flex-1 whitespace-pre-wrap break-all">
|
|
||||||
{commands.oneLine}
|
|
||||||
</code>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() =>
|
|
||||||
copyToClipboard(commands.oneLine, "Installation command")
|
|
||||||
}
|
|
||||||
className="ml-2 text-secondary-400 hover:text-secondary-200 flex-shrink-0"
|
|
||||||
>
|
|
||||||
<Copy className="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p className="text-xs text-secondary-500 mt-2">
|
|
||||||
This command will download, install, configure, and set up
|
|
||||||
automatic updates for the PatchMon agent.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="bg-blue-50 border border-blue-200 rounded-md p-4">
|
|
||||||
<h4 className="text-sm font-medium text-blue-800 mb-2">
|
|
||||||
📋 What This Command Does
|
|
||||||
</h4>
|
|
||||||
<ul className="text-sm text-blue-700 space-y-1">
|
|
||||||
<li>• Downloads the PatchMon installation script</li>
|
|
||||||
<li>
|
|
||||||
• Installs the agent to{" "}
|
|
||||||
<code>/usr/local/bin/patchmon-agent.sh</code>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
• Configures API credentials for{" "}
|
|
||||||
<strong>{host.friendly_name}</strong>
|
|
||||||
</li>
|
|
||||||
<li>• Tests the connection to PatchMon server</li>
|
|
||||||
<li>• Sends initial package data</li>
|
|
||||||
<li>• Sets up hourly automatic updates via crontab</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="bg-amber-50 border border-amber-200 rounded-md p-4">
|
|
||||||
<h4 className="text-sm font-medium text-amber-800 mb-2">
|
|
||||||
⚠️ Requirements
|
|
||||||
</h4>
|
|
||||||
<ul className="text-sm text-amber-700 space-y-1">
|
|
||||||
<li>
|
|
||||||
• Must be run as root (use <code>sudo</code>)
|
|
||||||
</li>
|
|
||||||
<li>• Requires internet connection to download agent</li>
|
|
||||||
<li>
|
|
||||||
• Requires <code>curl</code> and <code>bash</code> to be
|
|
||||||
installed
|
|
||||||
</li>
|
|
||||||
<li>• Host must be able to reach the PatchMon server</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{activeTab === "credentials" && (
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div>
|
|
||||||
<label
|
|
||||||
htmlFor={apiIdId}
|
|
||||||
className="block text-sm font-medium text-secondary-700 mb-2"
|
|
||||||
>
|
|
||||||
API ID
|
|
||||||
</label>
|
|
||||||
<div className="flex">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id={apiIdId}
|
|
||||||
readOnly
|
|
||||||
value={host.apiId}
|
|
||||||
className="flex-1 block w-full border-secondary-300 rounded-l-md shadow-sm bg-secondary-50"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => copyToClipboard(host.apiId, "API ID")}
|
|
||||||
className="px-3 py-2 border border-l-0 border-secondary-300 rounded-r-md bg-secondary-50 hover:bg-secondary-100"
|
|
||||||
>
|
|
||||||
<Copy className="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label
|
|
||||||
htmlFor={apiKeyId}
|
|
||||||
className="block text-sm font-medium text-secondary-700 mb-2"
|
|
||||||
>
|
|
||||||
API Key
|
|
||||||
</label>
|
|
||||||
<div className="flex">
|
|
||||||
<input
|
|
||||||
type={showApiKey ? "text" : "password"}
|
|
||||||
id={apiKeyId}
|
|
||||||
readOnly
|
|
||||||
value={host.apiKey}
|
|
||||||
className="flex-1 block w-full border-secondary-300 rounded-l-md shadow-sm bg-secondary-50"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setShowApiKey(!showApiKey)}
|
|
||||||
className="px-3 py-2 border border-l-0 border-r-0 border-secondary-300 bg-secondary-50 hover:bg-secondary-100"
|
|
||||||
>
|
|
||||||
{showApiKey ? (
|
|
||||||
<EyeOff className="h-4 w-4" />
|
|
||||||
) : (
|
|
||||||
<Eye className="h-4 w-4" />
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => copyToClipboard(host.apiKey, "API Key")}
|
|
||||||
className="px-3 py-2 border border-l-0 border-secondary-300 rounded-r-md bg-secondary-50 hover:bg-secondary-100"
|
|
||||||
>
|
|
||||||
<Copy className="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="bg-amber-50 border border-amber-200 rounded-md p-4">
|
|
||||||
<h4 className="text-sm font-medium text-amber-800 mb-2">
|
|
||||||
⚠️ Security Note
|
|
||||||
</h4>
|
|
||||||
<p className="text-sm text-amber-700">
|
|
||||||
Keep these credentials secure. They provide access to update
|
|
||||||
package information for <strong>{host.friendly_name}</strong>{" "}
|
|
||||||
only.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{activeTab === "setup" && (
|
|
||||||
<div className="space-y-6">
|
|
||||||
<div className="bg-blue-50 border border-blue-200 rounded-md p-4">
|
|
||||||
<h4 className="text-sm font-medium text-blue-800 mb-2">
|
|
||||||
📋 Step-by-Step Setup
|
|
||||||
</h4>
|
|
||||||
<p className="text-sm text-blue-700">
|
|
||||||
Follow these commands on <strong>{host.friendly_name}</strong>{" "}
|
|
||||||
to install and configure the PatchMon agent.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Step 1: Download & Install */}
|
|
||||||
<div className="border border-secondary-200 rounded-lg p-4">
|
|
||||||
<h5 className="font-medium text-secondary-900 mb-3">
|
|
||||||
Step 1: Download & Install Agent
|
|
||||||
</h5>
|
|
||||||
<div className="bg-secondary-900 text-secondary-100 p-3 rounded font-mono text-sm">
|
|
||||||
<div className="flex justify-between items-start">
|
|
||||||
<code className="flex-1 whitespace-pre-wrap">
|
|
||||||
{commands.download}
|
|
||||||
</code>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() =>
|
|
||||||
copyToClipboard(commands.download, "Download commands")
|
|
||||||
}
|
|
||||||
className="ml-2 text-secondary-400 hover:text-secondary-200 flex-shrink-0"
|
|
||||||
>
|
|
||||||
<Copy className="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Step 2: Configure */}
|
|
||||||
<div className="border border-secondary-200 rounded-lg p-4">
|
|
||||||
<h5 className="font-medium text-secondary-900 mb-3">
|
|
||||||
Step 2: Configure API Credentials
|
|
||||||
</h5>
|
|
||||||
<div className="bg-secondary-900 text-secondary-100 p-3 rounded font-mono text-sm">
|
|
||||||
<div className="flex justify-between items-start">
|
|
||||||
<code className="flex-1">{commands.configure}</code>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() =>
|
|
||||||
copyToClipboard(commands.configure, "Configure command")
|
|
||||||
}
|
|
||||||
className="ml-2 text-secondary-400 hover:text-secondary-200"
|
|
||||||
>
|
|
||||||
<Copy className="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Step 3: Test */}
|
|
||||||
<div className="border border-secondary-200 rounded-lg p-4">
|
|
||||||
<h5 className="font-medium text-secondary-900 mb-3">
|
|
||||||
Step 3: Test Configuration
|
|
||||||
</h5>
|
|
||||||
<div className="bg-secondary-900 text-secondary-100 p-3 rounded font-mono text-sm">
|
|
||||||
<div className="flex justify-between items-start">
|
|
||||||
<code className="flex-1">{commands.test}</code>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() =>
|
|
||||||
copyToClipboard(commands.test, "Test command")
|
|
||||||
}
|
|
||||||
className="ml-2 text-secondary-400 hover:text-secondary-200"
|
|
||||||
>
|
|
||||||
<Copy className="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Step 4: Initial Update */}
|
|
||||||
<div className="border border-secondary-200 rounded-lg p-4">
|
|
||||||
<h5 className="font-medium text-secondary-900 mb-3">
|
|
||||||
Step 4: Send Initial Package Data
|
|
||||||
</h5>
|
|
||||||
<p className="text-sm text-secondary-600 mb-3">
|
|
||||||
This will automatically detect and send system information (OS,
|
|
||||||
IP, architecture) along with package data.
|
|
||||||
</p>
|
|
||||||
<div className="bg-secondary-900 text-secondary-100 p-3 rounded font-mono text-sm">
|
|
||||||
<div className="flex justify-between items-start">
|
|
||||||
<code className="flex-1">{commands.initialUpdate}</code>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() =>
|
|
||||||
copyToClipboard(
|
|
||||||
commands.initialUpdate,
|
|
||||||
"Initial update command",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
className="ml-2 text-secondary-400 hover:text-secondary-200"
|
|
||||||
>
|
|
||||||
<Copy className="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Step 5: Crontab */}
|
|
||||||
<div className="border border-secondary-200 rounded-lg p-4">
|
|
||||||
<h5 className="font-medium text-secondary-900 mb-3">
|
|
||||||
Step 5: Setup Hourly Updates
|
|
||||||
</h5>
|
|
||||||
<div className="bg-secondary-900 text-secondary-100 p-3 rounded font-mono text-sm">
|
|
||||||
<div className="flex justify-between items-start">
|
|
||||||
<code className="flex-1">{commands.crontab}</code>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() =>
|
|
||||||
copyToClipboard(commands.crontab, "Crontab command")
|
|
||||||
}
|
|
||||||
className="ml-2 text-secondary-400 hover:text-secondary-200"
|
|
||||||
>
|
|
||||||
<Copy className="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p className="text-xs text-secondary-500 mt-2">
|
|
||||||
This sets up automatic package updates every hour at the top of
|
|
||||||
the hour.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{activeTab === "script" && (
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="bg-green-50 border border-green-200 rounded-md p-4">
|
|
||||||
<h4 className="text-sm font-medium text-green-800 mb-2">
|
|
||||||
🚀 Automated Setup
|
|
||||||
</h4>
|
|
||||||
<p className="text-sm text-green-700">
|
|
||||||
Copy this complete setup script to{" "}
|
|
||||||
<strong>{host.friendly_name}</strong> and run it to
|
|
||||||
automatically install and configure everything.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="border border-secondary-200 rounded-lg p-4">
|
|
||||||
<div className="flex justify-between items-center mb-3">
|
|
||||||
<h5 className="font-medium text-secondary-900">
|
|
||||||
Complete Setup Script
|
|
||||||
</h5>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() =>
|
|
||||||
copyToClipboard(commands.fullSetup, "Complete setup script")
|
|
||||||
}
|
|
||||||
className="px-3 py-1 bg-primary-600 text-white rounded text-sm hover:bg-primary-700 flex items-center gap-2"
|
|
||||||
>
|
|
||||||
<Copy className="h-4 w-4" />
|
|
||||||
Copy Script
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className="bg-secondary-900 text-secondary-100 p-4 rounded font-mono text-xs overflow-x-auto">
|
|
||||||
<pre className="whitespace-pre-wrap">{commands.fullSetup}</pre>
|
|
||||||
</div>
|
|
||||||
<div className="mt-3 text-sm text-secondary-600">
|
|
||||||
<p>
|
|
||||||
<strong>Usage:</strong>
|
|
||||||
</p>
|
|
||||||
<p>1. Copy the script above</p>
|
|
||||||
<p>
|
|
||||||
2. Save it to a file on {host.friendly_name} (e.g.,{" "}
|
|
||||||
<code>setup-patchmon.sh</code>)
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
3. Run:{" "}
|
|
||||||
<code>
|
|
||||||
chmod +x setup-patchmon.sh && sudo ./setup-patchmon.sh
|
|
||||||
</code>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="mt-6 flex justify-end space-x-3">
|
|
||||||
<a
|
|
||||||
href={`${serverUrl}/api/v1/hosts/agent/download`}
|
|
||||||
download="patchmon-agent.sh"
|
|
||||||
className="px-4 py-2 text-sm font-medium text-primary-700 bg-primary-50 border border-primary-200 rounded-md hover:bg-primary-100"
|
|
||||||
>
|
|
||||||
Download Agent Script
|
|
||||||
</a>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={onClose}
|
|
||||||
className="px-4 py-2 text-sm font-medium text-secondary-700 bg-white border border-secondary-300 rounded-md hover:bg-secondary-50"
|
|
||||||
>
|
|
||||||
Close
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const Hosts = () => {
|
const Hosts = () => {
|
||||||
const hostGroupFilterId = useId();
|
const hostGroupFilterId = useId();
|
||||||
const statusFilterId = useId();
|
const statusFilterId = useId();
|
||||||
|
|||||||
Reference in New Issue
Block a user