Skip to content
Merged
2 changes: 1 addition & 1 deletion application/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ function App() {
<Routes>
<Route path="/" element={<Index />} />
<Route path="/login" element={<Login />} />
<Route path="/public/:pageId" element={<PublicStatusPage />} />
<Route path="/public/:slug" element={<PublicStatusPage />} />

{/* Protected Routes */}
<Route path="/dashboard" element={
Expand Down
16 changes: 8 additions & 8 deletions application/src/components/dashboard/sidebar/navigationData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,21 +41,21 @@ export const mainMenuItems = [
color: 'text-amber-400',
hasNavigation: true
},
{
id: 'reports',
path: null,
icon: LineChart,
translationKey: 'reports',
color: 'text-rose-400',
hasNavigation: false
},
{
id: 'regional-monitoring',
path: '/regional-monitoring',
icon: MapPin,
translationKey: 'regionalMonitoring',
color: 'text-indigo-400',
hasNavigation: true
},
{
id: 'reports',
path: null,
icon: LineChart,
translationKey: 'reports',
color: 'text-rose-400',
hasNavigation: false
}
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ export const OperationalPageContent = () => {
if (page.custom_domain) {
window.open(`https://${page.custom_domain}`, '_blank');
} else {
// Navigate to the public status page route
window.open(`/status/${page.slug}`, '_blank');
// Navigate to the public status page route using the correct format
window.open(`/public/${page.slug}`, '_blank');
}
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

import React, { useState } from "react";
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
Expand Down Expand Up @@ -54,19 +53,69 @@ export const AddRegionalAgentDialog: React.FC<AddRegionalAgentDialogProps> = ({
}
};

const copyToClipboard = async (text: string) => {
const copyToClipboard = async (text: string, description: string = "Content") => {
try {
await navigator.clipboard.writeText(text);
toast({
title: "Copied!",
description: "Installation script copied to clipboard.",
});
// Try the modern clipboard API first
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(text);
toast({
title: "Copied!",
description: `${description} copied to clipboard.`,
});
return;
}

// Fallback for older browsers or non-secure contexts (like localhost)
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();

const successful = document.execCommand('copy');
document.body.removeChild(textArea);

if (successful) {
toast({
title: "Copied!",
description: `${description} copied to clipboard.`,
});
} else {
throw new Error('execCommand failed');
}
} catch (error) {
console.error('Failed to copy to clipboard:', error);

// Show the text in a modal or alert as final fallback
const userAgent = navigator.userAgent.toLowerCase();
const isLocalhost = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1';

let errorMessage = "Failed to copy to clipboard.";
if (isLocalhost) {
errorMessage += " This is common in local development. Please manually copy the text.";
} else if (userAgent.includes('chrome')) {
errorMessage += " Try using HTTPS or enable clipboard permissions.";
}

toast({
title: "Copy failed",
description: "Failed to copy to clipboard.",
description: errorMessage,
variant: "destructive",
});

// As a last resort, select the text for manual copying
try {
const textarea = document.querySelector('textarea[readonly]') as HTMLTextAreaElement;
if (textarea) {
textarea.select();
textarea.setSelectionRange(0, 99999); // For mobile devices
}
} catch (selectError) {
console.error('Failed to select text:', selectError);
}
}
};

Expand All @@ -89,16 +138,6 @@ export const AddRegionalAgentDialog: React.FC<AddRegionalAgentDialogProps> = ({
});
};

const copyOneClickCommand = () => {
if (!installCommand) return;

const oneClickCommand = `curl -fsSL -H "User-Agent: CheckCle-Installer" \\
"data:text/plain;base64,$(echo '${installCommand.bash_script}' | base64 -w 0)" \\
| sudo bash`;

copyToClipboard(oneClickCommand);
};

const handleComplete = () => {
onAgentAdded();
setStep(1);
Expand Down Expand Up @@ -211,7 +250,7 @@ export const AddRegionalAgentDialog: React.FC<AddRegionalAgentDialogProps> = ({
<Textarea
readOnly
value={`# One-click installation command:
curl -fsSL https://github.com/operacle/checkcle/blob/main/scripts/regional-agent.sh | sudo bash -s -- \\
curl -fsSL https://raw.githubusercontent.com/checkcle/scripts/main/install.sh | sudo bash -s -- \\
--region-name="${regionName}" \\
--agent-id="${installCommand.agent_id}" \\
--agent-ip="${agentIp}" \\
Expand All @@ -223,7 +262,7 @@ curl -fsSL https://github.com/operacle/checkcle/blob/main/scripts/regional-agent
size="sm"
variant="outline"
className="absolute top-2 right-2"
onClick={() => copyToClipboard(`curl -fsSL https://github.com/operacle/checkcle/blob/main/scripts/regional-agent.sh | sudo bash -s -- --region-name="${regionName}" --agent-id="${installCommand.agent_id}" --agent-ip="${agentIp}" --token="${installCommand.token}" --pocketbase-url="${installCommand.api_endpoint}"`)}
onClick={() => copyToClipboard(`curl -fsSL https://raw.githubusercontent.com/checkcle/scripts/main/install.sh | sudo bash -s -- --region-name="${regionName}" --agent-id="${installCommand.agent_id}" --agent-ip="${agentIp}" --token="${installCommand.token}" --pocketbase-url="${installCommand.api_endpoint}"`, "Installation command")}
>
<Copy className="h-3 w-3" />
</Button>
Expand All @@ -236,14 +275,24 @@ curl -fsSL https://github.com/operacle/checkcle/blob/main/scripts/regional-agent
Download Complete Script
</Button>
<Button
onClick={() => copyToClipboard(installCommand.bash_script)}
onClick={() => copyToClipboard(installCommand.bash_script, "Installation script")}
variant="outline"
className="flex-1"
>
<Copy className="mr-2 h-4 w-4" />
Copy Full Script
</Button>
</div>

{/* Local development notice */}
{(window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') && (
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-3">
<p className="text-sm text-yellow-800">
<strong>Local Development Notice:</strong> If clipboard copying fails, this is normal in local development.
You can use the "Download Complete Script" button instead or manually copy the text.
</p>
</div>
)}
</CardContent>
</Card>
</TabsContent>
Expand All @@ -270,7 +319,7 @@ curl -fsSL https://github.com/operacle/checkcle/blob/main/scripts/regional-agent
size="sm"
variant="ghost"
className="absolute right-1 top-1/2 -translate-y-1/2 h-8 w-8 p-0 hover:bg-muted"
onClick={() => copyToClipboard(installCommand.agent_id)}
onClick={() => copyToClipboard(installCommand.agent_id, "Agent ID")}
>
<Copy className="h-3 w-3" />
</Button>
Expand Down Expand Up @@ -314,7 +363,7 @@ curl -fsSL https://github.com/operacle/checkcle/blob/main/scripts/regional-agent
size="sm"
variant="ghost"
className="absolute right-1 top-1/2 -translate-y-1/2 h-8 w-8 p-0 hover:bg-muted"
onClick={() => copyToClipboard(installCommand.token)}
onClick={() => copyToClipboard(installCommand.token, "Authentication token")}
>
<Copy className="h-3 w-3" />
</Button>
Expand Down
92 changes: 92 additions & 0 deletions application/src/components/services/RegionalAgentFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@

import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { useQuery } from "@tanstack/react-query";
import { regionalService } from "@/services/regionalService";
import { MapPin, Loader2, BarChart3 } from "lucide-react";

interface RegionalAgentFilterProps {
selectedAgent: string;
onAgentChange: (agent: string) => void;
}

export function RegionalAgentFilter({ selectedAgent, onAgentChange }: RegionalAgentFilterProps) {
const { data: regionalAgents = [], isLoading } = useQuery({
queryKey: ['regional-services'],
queryFn: regionalService.getRegionalServices,
});

// Filter only online agents
const onlineAgents = regionalAgents.filter(agent => agent.connection === 'online');

const getCurrentAgentDisplay = () => {
if (!selectedAgent || selectedAgent === "all") {
return "All Monitoring";
}

const [regionName] = selectedAgent.split("|");
const agent = onlineAgents.find(agent =>
`${agent.region_name}|${agent.agent_id}` === selectedAgent
);

if (agent) {
return `${agent.region_name} (${agent.agent_ip_address})`;
}

return regionName || "All Monitoring";
};

return (
<div className="w-64">
<label className="text-sm font-medium flex items-center gap-2 mb-2">
<MapPin className="h-4 w-4" />
Monitoring Source
</label>
<Select
onValueChange={onAgentChange}
value={selectedAgent || "all"}
disabled={isLoading}
>
<SelectTrigger>
<SelectValue placeholder={
isLoading
? "Loading agents..."
: getCurrentAgentDisplay()
} />
</SelectTrigger>
<SelectContent>
{isLoading ? (
<SelectItem value="loading" disabled>
<div className="flex items-center gap-2">
<Loader2 className="h-4 w-4 animate-spin" />
Loading agents...
</div>
</SelectItem>
) : (
<>
<SelectItem value="all">
<div className="flex items-center gap-2">
<BarChart3 className="h-4 w-4 text-purple-500" />
<span className="font-medium">All Monitoring</span>
</div>
</SelectItem>
{onlineAgents.length > 0 && onlineAgents.map((agent) => (
<SelectItem key={agent.id} value={`${agent.region_name}|${agent.agent_id}`}>
<div className="flex items-center gap-2">
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
<span className="font-medium">{agent.region_name}</span>
<span className="text-muted-foreground">({agent.agent_ip_address})</span>
</div>
</SelectItem>
))}
</>
)}
</SelectContent>
</Select>
{onlineAgents.length === 0 && !isLoading && (
<p className="text-xs text-amber-600 mt-1">
No regional agents available. Using default monitoring only.
</p>
)}
</div>
);
}
Loading