Merge pull request #61 from operacle/develop
Fix: Server Historical Performance Metrics date filter and improved the container entrypoint script pipeline
This commit is contained in:
@@ -11,8 +11,12 @@ export const SidebarHeader: React.FC<SidebarHeaderProps> = ({ collapsed }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`p-4 ${theme === 'dark' ? 'border-[#1e1e1e]' : 'border-sidebar-border'} border-b flex items-center ${collapsed ? 'justify-center' : ''}`}>
|
<div className={`p-4 ${theme === 'dark' ? 'border-[#1e1e1e]' : 'border-sidebar-border'} border-b flex items-center ${collapsed ? 'justify-center' : ''}`}>
|
||||||
<div className="h-8 w-8 bg-green-500 rounded flex items-center justify-center mr-2">
|
<div className="h-8 w-8 bg-gray-600 rounded flex items-center justify-center mr-2">
|
||||||
<span className="text-white font-bold">C</span>
|
<img
|
||||||
|
src="/favicon.ico"
|
||||||
|
alt="CheckCle"
|
||||||
|
className="h-6 w-6"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{!collapsed && <h1 className="text-xl font-semibold">CheckCle App</h1>}
|
{!collapsed && <h1 className="text-xl font-semibold">CheckCle App</h1>}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
import { Globe, Boxes, Radar, Calendar, BarChart2, LineChart, MapPin, Settings, User, Bell, Database, Info, BookOpen } from "lucide-react";
|
import { Globe, Boxes, Layers, Calendar, BarChart2, LineChart, MapPin, Settings, User, Bell, Database, Info, BookOpen } from "lucide-react";
|
||||||
|
|
||||||
export const mainMenuItems = [
|
export const mainMenuItems = [
|
||||||
{
|
{
|
||||||
@@ -21,7 +21,7 @@ export const mainMenuItems = [
|
|||||||
{
|
{
|
||||||
id: 'ssl-domain',
|
id: 'ssl-domain',
|
||||||
path: '/ssl-domain',
|
path: '/ssl-domain',
|
||||||
icon: Radar,
|
icon: Layers,
|
||||||
translationKey: 'sslDomain',
|
translationKey: 'sslDomain',
|
||||||
color: 'text-cyan-400',
|
color: 'text-cyan-400',
|
||||||
hasNavigation: true
|
hasNavigation: true
|
||||||
|
|||||||
@@ -35,38 +35,6 @@ export const DockerRowActions = ({ container, containerStatus, onContainerAction
|
|||||||
<Eye className="mr-2 h-4 w-4" />
|
<Eye className="mr-2 h-4 w-4" />
|
||||||
View Details
|
View Details
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuSeparator className="bg-border" />
|
|
||||||
<DropdownMenuItem
|
|
||||||
onClick={() => onContainerAction(containerStatus === 'running' ? 'stop' : 'start', container.id, container.name)}
|
|
||||||
className="cursor-pointer hover:bg-muted"
|
|
||||||
>
|
|
||||||
{containerStatus === 'running' ? (
|
|
||||||
<>
|
|
||||||
<Square className="mr-2 h-4 w-4" />
|
|
||||||
Stop Container
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Play className="mr-2 h-4 w-4" />
|
|
||||||
Start Container
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem
|
|
||||||
onClick={() => onContainerAction('restart', container.id, container.name)}
|
|
||||||
className="cursor-pointer hover:bg-muted"
|
|
||||||
>
|
|
||||||
<RefreshCw className="mr-2 h-4 w-4" />
|
|
||||||
Restart Container
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuSeparator className="bg-border" />
|
|
||||||
<DropdownMenuItem
|
|
||||||
onClick={() => onContainerAction('delete', container.id, container.name)}
|
|
||||||
className="cursor-pointer hover:bg-muted text-destructive focus:text-destructive"
|
|
||||||
>
|
|
||||||
<Trash2 className="mr-2 h-4 w-4" />
|
|
||||||
Remove Container
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ export const DockerTableRow = ({ container, onRowClick, onContainerAction, onVie
|
|||||||
<div className="font-semibold text-sm sm:text-base text-foreground">{container.name}</div>
|
<div className="font-semibold text-sm sm:text-base text-foreground">{container.name}</div>
|
||||||
<div className="text-xs sm:text-sm text-muted-foreground">
|
<div className="text-xs sm:text-sm text-muted-foreground">
|
||||||
<div className="font-mono">{container.docker_id}</div>
|
<div className="font-mono">{container.docker_id}</div>
|
||||||
<div className="font-mono">{container.hostname}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|||||||
@@ -28,9 +28,10 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
|||||||
} = useQuery({
|
} = useQuery({
|
||||||
queryKey: ['server-metrics-history', serverId, timeRange],
|
queryKey: ['server-metrics-history', serverId, timeRange],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
console.log('ServerHistoryCharts: Fetching metrics for serverId:', serverId);
|
console.log('ServerHistoryCharts: Fetching metrics for serverId:', serverId, 'timeRange:', timeRange);
|
||||||
const result = await serverService.getServerMetrics(serverId, timeRange);
|
const result = await serverService.getServerMetrics(serverId, timeRange);
|
||||||
console.log('ServerHistoryCharts: Raw metrics result:', result);
|
console.log('ServerHistoryCharts: Raw metrics result for timeRange', timeRange, ':', result?.length || 0, 'records');
|
||||||
|
console.log('ServerHistoryCharts: First 3 records:', result?.slice(0, 3));
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
enabled: !!serverId,
|
enabled: !!serverId,
|
||||||
@@ -47,7 +48,9 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
|||||||
timeRange
|
timeRange
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log('ServerHistoryCharts: About to format chart data with', metrics?.length || 0, 'metrics for timeRange:', timeRange);
|
||||||
const chartData = formatChartData(metrics, timeRange);
|
const chartData = formatChartData(metrics, timeRange);
|
||||||
|
console.log('ServerHistoryCharts: After formatting, got', chartData?.length || 0, 'chart data points');
|
||||||
|
|
||||||
const getGridColor = () => theme === 'dark' ? '#374151' : '#e5e7eb';
|
const getGridColor = () => theme === 'dark' ? '#374151' : '#e5e7eb';
|
||||||
const getAxisColor = () => theme === 'dark' ? '#9ca3af' : '#6b7280';
|
const getAxisColor = () => theme === 'dark' ? '#9ca3af' : '#6b7280';
|
||||||
|
|||||||
@@ -55,11 +55,24 @@ export const filterMetricsByTimeRange = (metrics: any[], timeRange: TimeRange):
|
|||||||
if (!selectedRange) return metrics;
|
if (!selectedRange) return metrics;
|
||||||
|
|
||||||
const cutoffTime = new Date(now.getTime() - (selectedRange.hours * 60 * 60 * 1000));
|
const cutoffTime = new Date(now.getTime() - (selectedRange.hours * 60 * 60 * 1000));
|
||||||
|
console.log('filterMetricsByTimeRange: timeRange:', timeRange, 'cutoffTime:', cutoffTime.toISOString(), 'now:', now.toISOString());
|
||||||
|
|
||||||
return metrics.filter(metric => {
|
const filtered = metrics.filter(metric => {
|
||||||
const metricTime = new Date(metric.created || metric.timestamp);
|
const metricTime = new Date(metric.created || metric.timestamp);
|
||||||
return metricTime >= cutoffTime;
|
const isValid = metricTime >= cutoffTime;
|
||||||
|
if (!isValid && metrics.indexOf(metric) < 3) {
|
||||||
|
console.log('filterMetricsByTimeRange: Filtered out metric:', {
|
||||||
|
metricTime: metricTime.toISOString(),
|
||||||
|
cutoffTime: cutoffTime.toISOString(),
|
||||||
|
created: metric.created,
|
||||||
|
timestamp: metric.timestamp
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return isValid;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log('filterMetricsByTimeRange: Filtered', metrics.length, 'to', filtered.length, 'metrics');
|
||||||
|
return filtered;
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatTimestamp = (timestamp: string, timeRange: TimeRange): string => {
|
const formatTimestamp = (timestamp: string, timeRange: TimeRange): string => {
|
||||||
@@ -95,7 +108,19 @@ const formatTimestamp = (timestamp: string, timeRange: TimeRange): string => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const formatChartData = (metrics: any[], timeRange: TimeRange) => {
|
export const formatChartData = (metrics: any[], timeRange: TimeRange) => {
|
||||||
|
console.log('formatChartData: Input metrics count:', metrics?.length || 0, 'timeRange:', timeRange);
|
||||||
|
|
||||||
const filteredMetrics = filterMetricsByTimeRange(metrics, timeRange);
|
const filteredMetrics = filterMetricsByTimeRange(metrics, timeRange);
|
||||||
|
console.log('formatChartData: After time filtering:', filteredMetrics?.length || 0, 'metrics');
|
||||||
|
|
||||||
|
if (filteredMetrics.length > 0) {
|
||||||
|
console.log('formatChartData: First filtered metric:', filteredMetrics[0]);
|
||||||
|
console.log('formatChartData: Sample timestamps:', filteredMetrics.slice(0, 3).map(m => ({
|
||||||
|
created: m.created,
|
||||||
|
timestamp: m.timestamp,
|
||||||
|
both: new Date(m.created || m.timestamp).toISOString()
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
|
||||||
return filteredMetrics.slice(0, 100).reverse().map((metric, index) => {
|
return filteredMetrics.slice(0, 100).reverse().map((metric, index) => {
|
||||||
const cpuUsage = typeof metric.cpu_usage === 'string' ?
|
const cpuUsage = typeof metric.cpu_usage === 'string' ?
|
||||||
|
|||||||
@@ -35,16 +35,21 @@ export const serverService = {
|
|||||||
console.log('serverService.getServerMetrics: Could not fetch server details:', error);
|
console.log('serverService.getServerMetrics: Could not fetch server details:', error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the server's server_id field if available, otherwise use the serverId
|
// Try multiple filter strategies to find data
|
||||||
|
let filter = '';
|
||||||
let metricsServerId = serverId;
|
let metricsServerId = serverId;
|
||||||
|
|
||||||
|
// Strategy 1: Use server.server_id if available
|
||||||
if (server && server.server_id) {
|
if (server && server.server_id) {
|
||||||
metricsServerId = server.server_id;
|
metricsServerId = server.server_id;
|
||||||
console.log('serverService.getServerMetrics: Using server.server_id for metrics:', metricsServerId);
|
filter = `server_id = "${metricsServerId}"`;
|
||||||
|
console.log('serverService.getServerMetrics: Strategy 1 - Using server.server_id for metrics:', metricsServerId);
|
||||||
|
} else {
|
||||||
|
// Strategy 2: Use the serverId directly
|
||||||
|
filter = `server_id = "${serverId}"`;
|
||||||
|
console.log('serverService.getServerMetrics: Strategy 2 - Using serverId directly for metrics:', serverId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build filter for server_id and time range
|
|
||||||
let filter = `server_id = "${metricsServerId}"`;
|
|
||||||
|
|
||||||
// Add agent_id filter if available in server data
|
// Add agent_id filter if available in server data
|
||||||
if (server && server.agent_id) {
|
if (server && server.agent_id) {
|
||||||
filter += ` && agent_id = "${server.agent_id}"`;
|
filter += ` && agent_id = "${server.agent_id}"`;
|
||||||
@@ -78,19 +83,121 @@ export const serverService = {
|
|||||||
|
|
||||||
const cutoffISO = cutoffTime.toISOString();
|
const cutoffISO = cutoffTime.toISOString();
|
||||||
filter += ` && created >= "${cutoffISO}"`;
|
filter += ` && created >= "${cutoffISO}"`;
|
||||||
console.log('serverService.getServerMetrics: Using time filter:', cutoffISO);
|
console.log('serverService.getServerMetrics: Using time filter from:', cutoffISO, 'to now');
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('serverService.getServerMetrics: Final filter:', filter);
|
console.log('serverService.getServerMetrics: Final filter:', filter);
|
||||||
|
|
||||||
// Fetch filtered records with proper sorting
|
// Fetch filtered records with proper sorting
|
||||||
const records = await pb.collection('server_metrics').getFullList({
|
let records = await pb.collection('server_metrics').getFullList({
|
||||||
filter: filter,
|
filter: filter,
|
||||||
sort: '-created',
|
sort: '-created',
|
||||||
requestKey: null
|
requestKey: null
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('serverService.getServerMetrics: Found', records.length, 'records with filter');
|
console.log('serverService.getServerMetrics: Found', records.length, 'records with primary filter');
|
||||||
|
|
||||||
|
// If no records found with primary strategy, try fallback strategies
|
||||||
|
if (records.length === 0) {
|
||||||
|
console.log('serverService.getServerMetrics: No records found, trying fallback strategies...');
|
||||||
|
|
||||||
|
// Fallback 1: Try without agent_id filter
|
||||||
|
let fallbackFilter = `server_id = "${metricsServerId}"`;
|
||||||
|
if (timeRange) {
|
||||||
|
const now = new Date();
|
||||||
|
let cutoffTime;
|
||||||
|
|
||||||
|
switch (timeRange) {
|
||||||
|
case '60m':
|
||||||
|
cutoffTime = new Date(now.getTime() - (60 * 60 * 1000));
|
||||||
|
break;
|
||||||
|
case '1d':
|
||||||
|
cutoffTime = new Date(now.getTime() - (24 * 60 * 60 * 1000));
|
||||||
|
break;
|
||||||
|
case '7d':
|
||||||
|
cutoffTime = new Date(now.getTime() - (7 * 24 * 60 * 60 * 1000));
|
||||||
|
break;
|
||||||
|
case '1m':
|
||||||
|
cutoffTime = new Date(now.getTime() - (30 * 24 * 60 * 60 * 1000));
|
||||||
|
break;
|
||||||
|
case '3m':
|
||||||
|
cutoffTime = new Date(now.getTime() - (90 * 24 * 60 * 60 * 1000));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
cutoffTime = new Date(now.getTime() - (24 * 60 * 60 * 1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
const cutoffISO = cutoffTime.toISOString();
|
||||||
|
fallbackFilter += ` && created >= "${cutoffISO}"`;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('serverService.getServerMetrics: Trying fallback filter without agent_id:', fallbackFilter);
|
||||||
|
records = await pb.collection('server_metrics').getFullList({
|
||||||
|
filter: fallbackFilter,
|
||||||
|
sort: '-created',
|
||||||
|
requestKey: null
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('serverService.getServerMetrics: Fallback found', records.length, 'records');
|
||||||
|
|
||||||
|
// Fallback 2: Try with different server_id strategies
|
||||||
|
if (records.length === 0) {
|
||||||
|
const alternativeIds = [serverId, server?.server_id, server?.id].filter(Boolean);
|
||||||
|
console.log('serverService.getServerMetrics: Trying alternative server IDs:', alternativeIds);
|
||||||
|
|
||||||
|
for (const altId of alternativeIds) {
|
||||||
|
if (altId && altId !== metricsServerId) {
|
||||||
|
let altFilter = `server_id = "${altId}"`;
|
||||||
|
if (timeRange) {
|
||||||
|
const now = new Date();
|
||||||
|
let cutoffTime;
|
||||||
|
|
||||||
|
switch (timeRange) {
|
||||||
|
case '60m':
|
||||||
|
cutoffTime = new Date(now.getTime() - (60 * 60 * 1000));
|
||||||
|
break;
|
||||||
|
case '1d':
|
||||||
|
cutoffTime = new Date(now.getTime() - (24 * 60 * 60 * 1000));
|
||||||
|
break;
|
||||||
|
case '7d':
|
||||||
|
cutoffTime = new Date(now.getTime() - (7 * 24 * 60 * 60 * 1000));
|
||||||
|
break;
|
||||||
|
case '1m':
|
||||||
|
cutoffTime = new Date(now.getTime() - (30 * 24 * 60 * 60 * 1000));
|
||||||
|
break;
|
||||||
|
case '3m':
|
||||||
|
cutoffTime = new Date(now.getTime() - (90 * 24 * 60 * 60 * 1000));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
cutoffTime = new Date(now.getTime() - (24 * 60 * 60 * 1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
const cutoffISO = cutoffTime.toISOString();
|
||||||
|
altFilter += ` && created >= "${cutoffISO}"`;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('serverService.getServerMetrics: Trying alternative ID filter:', altFilter);
|
||||||
|
const altRecords = await pb.collection('server_metrics').getFullList({
|
||||||
|
filter: altFilter,
|
||||||
|
sort: '-created',
|
||||||
|
requestKey: null
|
||||||
|
});
|
||||||
|
|
||||||
|
if (altRecords.length > 0) {
|
||||||
|
console.log('serverService.getServerMetrics: Alternative ID found', altRecords.length, 'records');
|
||||||
|
records = altRecords;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('serverService.getServerMetrics: Final result:', records.length, 'records found');
|
||||||
|
if (records.length > 0) {
|
||||||
|
console.log('serverService.getServerMetrics: Sample record:', records[0]);
|
||||||
|
}
|
||||||
|
|
||||||
return records;
|
return records;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching server metrics:', error);
|
console.error('Error fetching server metrics:', error);
|
||||||
|
|||||||
@@ -8,21 +8,22 @@ if [ ! -f /mnt/pb_data/data.db ] && [ -d /app/pb_data ] && [ "$(ls -A /app/pb_da
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Start PocketBase in the background
|
# Start PocketBase in the background
|
||||||
echo "Starting PocketBase..."
|
echo "Starting CheckCle Application..."
|
||||||
/app/pocketbase serve --http=0.0.0.0:8090 --dir /mnt/pb_data 2>&1 | grep -vE 'REST API|Dashboard' &
|
/app/pocketbase serve --http=0.0.0.0:8090 --dir /mnt/pb_data 2>&1 | grep -vE 'REST API|Dashboard' &
|
||||||
|
|
||||||
# Wait for PocketBase to become available
|
# Wait for PocketBase to become available
|
||||||
echo "Waiting for PocketBase to become available..."
|
echo "Waiting for Backend Server to become available..."
|
||||||
until curl -s http://localhost:8090/api/health >/dev/null; do
|
until curl -s http://localhost:8090/api/health >/dev/null; do
|
||||||
echo "Waiting on http://localhost:8090..."
|
echo "Waiting on http://localhost:8090..."
|
||||||
sleep 1
|
sleep 1
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "PocketBase is up!"
|
echo "Backend Server is up!"
|
||||||
|
|
||||||
# Start Go service
|
# Start Go service
|
||||||
echo "Starting Go service..."
|
echo "Starting Go Operational service..."
|
||||||
/app/service-operation &
|
/app/service-operation &
|
||||||
|
|
||||||
|
echo "Default Access: admin@example.com/Admin123456"
|
||||||
# Keep container alive
|
# Keep container alive
|
||||||
wait
|
wait
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
echo "Running on architecture: $(uname -m)"
|
||||||
|
|
||||||
|
# Copy PocketBase data if empty
|
||||||
|
if [ ! -f /mnt/pb_data/data.db ] && [ -d /app/pb_data ] && [ "$(ls -A /app/pb_data)" ]; then
|
||||||
|
cp -a /app/pb_data/. /mnt/pb_data/
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Start PocketBase in the background with no admin UI and filtered logs
|
||||||
|
echo "Starting CheckCle Application..."
|
||||||
|
(
|
||||||
|
/app/pocketbase serve \
|
||||||
|
--http=0.0.0.0:8090 \
|
||||||
|
--dir /mnt/pb_data \
|
||||||
|
--no-admin \
|
||||||
|
2>&1 | grep -vE 'REST API|Dashboard'
|
||||||
|
) &
|
||||||
|
|
||||||
|
# Wait for PocketBase to become available
|
||||||
|
echo "Waiting for Backend Server to become available..."
|
||||||
|
until curl -s http://localhost:8090/api/health >/dev/null; do
|
||||||
|
echo "Waiting on http://localhost:8090..."
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Backend Server is up!"
|
||||||
|
|
||||||
|
# Start Go service
|
||||||
|
echo "Starting Go Operational service..."
|
||||||
|
/app/service-operation &
|
||||||
|
|
||||||
|
echo "Default Access: admin@example.com/Admin123456"
|
||||||
|
|
||||||
|
# Keep container alive
|
||||||
|
wait
|
||||||
Reference in New Issue
Block a user