Added Opensearch index control and rollover config control
This commit is contained in:
@@ -5,7 +5,7 @@ go 1.24.0
|
|||||||
toolchain go1.24.3
|
toolchain go1.24.3
|
||||||
|
|
||||||
//replace github.com/frikky/schemaless => ../../../schemaless
|
//replace github.com/frikky/schemaless => ../../../schemaless
|
||||||
replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared
|
//replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared
|
||||||
|
|
||||||
//replace github.com/frikky/kin-openapi => ../../../../git/kin-openapi
|
//replace github.com/frikky/kin-openapi => ../../../../git/kin-openapi
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ require (
|
|||||||
github.com/gorilla/mux v1.8.1
|
github.com/gorilla/mux v1.8.1
|
||||||
github.com/h2non/filetype v1.1.3
|
github.com/h2non/filetype v1.1.3
|
||||||
github.com/satori/go.uuid v1.2.0
|
github.com/satori/go.uuid v1.2.0
|
||||||
github.com/shuffle/shuffle-shared v0.8.99
|
github.com/shuffle/shuffle-shared v0.9.0
|
||||||
github.com/shuffle/singul v0.0.15
|
github.com/shuffle/singul v0.0.15
|
||||||
golang.org/x/crypto v0.38.0
|
golang.org/x/crypto v0.38.0
|
||||||
google.golang.org/api v0.236.0
|
google.golang.org/api v0.236.0
|
||||||
|
|||||||
@@ -359,8 +359,8 @@ github.com/sendgrid/sendgrid-go v3.16.1+incompatible h1:zWhTmB0Y8XCDzeWIm2/BIt1G
|
|||||||
github.com/sendgrid/sendgrid-go v3.16.1+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
|
github.com/sendgrid/sendgrid-go v3.16.1+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
|
||||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
||||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||||
github.com/shuffle/shuffle-shared v0.8.91 h1:ZcxSKhns96w1GQG/EjEJSLBvuEA3KVjvORAetV3W4bM=
|
github.com/shuffle/shuffle-shared v0.9.0 h1:hUNtW3qHkji2cUVyR1cIXAz6qxpAHHP5hBmSAe3pIhs=
|
||||||
github.com/shuffle/shuffle-shared v0.8.91/go.mod h1:PFLG4eaxz8zQc9Lbc0Ok/nG8rNsaBkZ0TNar+VU+J/g=
|
github.com/shuffle/shuffle-shared v0.9.0/go.mod h1:PFLG4eaxz8zQc9Lbc0Ok/nG8rNsaBkZ0TNar+VU+J/g=
|
||||||
github.com/shuffle/singul v0.0.15 h1:a5Qro1BKs9a+mWE289oYTkDeVFoKEd/H9qu9XJGsVxo=
|
github.com/shuffle/singul v0.0.15 h1:a5Qro1BKs9a+mWE289oYTkDeVFoKEd/H9qu9XJGsVxo=
|
||||||
github.com/shuffle/singul v0.0.15/go.mod h1:z1RRZsymTYxsB8WMQwwswypruy4j5EugGPFEZspTEA4=
|
github.com/shuffle/singul v0.0.15/go.mod h1:z1RRZsymTYxsB8WMQwwswypruy4j5EugGPFEZspTEA4=
|
||||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
|
|||||||
@@ -1,22 +1,93 @@
|
|||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Bar } from 'react-chartjs-2';
|
|
||||||
|
|
||||||
const HealthBarChart = (props) => {
|
const HealthBarChart = (props) => {
|
||||||
const { globalUrl, filteredData, options, onBarClick } = props;
|
const { filteredData, onBarClick } = props;
|
||||||
|
const [hoveredBar, setHoveredBar] = useState(null);
|
||||||
return (
|
|
||||||
<Bar
|
if (!filteredData || !Array.isArray(filteredData)) {
|
||||||
data={filteredData}
|
console.error('Invalid chart data format');
|
||||||
options={options}
|
return null;
|
||||||
height="35.5rem"
|
}
|
||||||
width={filteredData.width}
|
|
||||||
getElementAtEvent={(elements) => {
|
const formatData = () => {
|
||||||
if (elements && elements.length > 0) {
|
return filteredData.map((item) => ({
|
||||||
onBarClick(elements);
|
label: item.date,
|
||||||
}
|
value: item.avgRunFinished,
|
||||||
|
color: item.color,
|
||||||
|
executionIds: item.executionIds,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBarClick = (data) => {
|
||||||
|
if (onBarClick) {
|
||||||
|
onBarClick(data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const chartData = formatData();
|
||||||
|
|
||||||
|
const barWidth = `calc((100% - ${(chartData.length - 1) * 5}px) / ${chartData.length})`;
|
||||||
|
const barGap = '5px';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
height: '5rem',
|
||||||
|
width: '100%',
|
||||||
|
position: 'relative',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center', // Center the content vertically
|
||||||
|
justifyContent: 'center', // Center the content horizontally
|
||||||
|
padding: '0.5rem 0' // Add some padding for spacing
|
||||||
|
}}>
|
||||||
|
{/* Chart Container */}
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
height: '80%', // Take up 80% of the parent height
|
||||||
|
width: '100%',
|
||||||
|
alignItems: 'center', // Center the bars vertically
|
||||||
|
gap: barGap,
|
||||||
|
}}>
|
||||||
|
{chartData.map((item, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
style={{
|
||||||
|
width: barWidth,
|
||||||
|
height: '70%', // 70% of the container height
|
||||||
|
backgroundColor: item.color,
|
||||||
|
cursor: 'pointer',
|
||||||
|
borderRadius: '2px',
|
||||||
}}
|
}}
|
||||||
/>
|
onMouseEnter={() => setHoveredBar(item)}
|
||||||
);
|
onMouseLeave={() => setHoveredBar(null)}
|
||||||
|
onClick={() => handleBarClick(item)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Tooltip */}
|
||||||
|
{hoveredBar && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '-3rem', // Position above the chart
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translateX(-50%)',
|
||||||
|
backgroundColor: '#333333',
|
||||||
|
color: '#ffffff',
|
||||||
|
padding: '8px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
fontSize: '12px',
|
||||||
|
boxShadow: '0 2px 4px rgba(0, 0, 0, 0.5)',
|
||||||
|
zIndex: 1000,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>Date: {hoveredBar.label}</div>
|
||||||
|
<div>Uptime: {hoveredBar.value}%</div>
|
||||||
|
<div>Executions: {hoveredBar.executionIds.length}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default HealthBarChart;
|
export default HealthBarChart;
|
||||||
|
|||||||
@@ -1,31 +1,35 @@
|
|||||||
import React, { useEffect, useState, useCallback } from 'react';
|
import React, { useEffect, useState, useCallback } from 'react';
|
||||||
import { Bar } from 'react-chartjs-2';
|
import { toast } from "react-toastify";
|
||||||
//import healthData1 from '../healthstats1.json';
|
import {
|
||||||
import { toast } from "react-toastify"
|
CheckOutlined as CheckOutlinedIcon,
|
||||||
import CheckOutlinedIcon from '@mui/icons-material/CheckOutlined';
|
} from '@mui/icons-material';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
ButtonGroup,
|
ButtonGroup,
|
||||||
Typography,
|
Typography,
|
||||||
|
LinearProgress,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
|
|
||||||
import HealthBarChart from '../components/HealthBarChart.jsx';
|
import HealthBarChart from '../components/HealthBarChart.jsx';
|
||||||
|
import LiveExecutionsChart from '../components/LiveExecutionsGraph.jsx';
|
||||||
|
|
||||||
const HealthPage = (props) => {
|
const HealthPage = (props) => {
|
||||||
const { globalUrl, userdata } = props;
|
const { userdata, globalUrl } = props;
|
||||||
const [healthData, setHealthData] = useState(null);
|
const [healthData, setHealthData] = useState(null);
|
||||||
const [selectedRange, setSelectedRange] = useState('30d');
|
const [selectedRange, setSelectedRange] = useState('30d');
|
||||||
|
const [liveExecutionsData, setLiveExecutionsData] = useState([]);
|
||||||
const [filteredData, setFilteredData] = useState([]);
|
const [filteredData, setFilteredData] = useState([]);
|
||||||
const [averageUptime, setAverageUptime] = useState(0);
|
const [averageUptime, setAverageUptime] = useState(0);
|
||||||
|
const [liveExecutionsRange, setLiveExecutionsRange] = useState('1h'); // Default to 1h
|
||||||
|
const [isHealthLoading, setIsHealthLoading] = useState(false); // Loading state for HealthBarChart
|
||||||
|
const [isLiveExecutionsLoading, setIsLiveExecutionsLoading] = useState(false); // Loading state for LiveExecutionsChart
|
||||||
|
|
||||||
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
|
const isCloud = (window.location.host === "localhost:3002" || window.location.host === "shuffler.io") ? true : (process.env.IS_SSR === "true");
|
||||||
//const globalUrl = `https://shuffler.io`
|
|
||||||
|
|
||||||
console.log("HEALTHPAGE 1")
|
|
||||||
|
|
||||||
const fetchHealthStats = useCallback(async () => {
|
const fetchHealthStats = useCallback(async () => {
|
||||||
//const after = new Date().getTime() - 90 * 24 * 60 * 60
|
setIsHealthLoading(true); // Start loading for HealthBarChart
|
||||||
try {
|
try {
|
||||||
//const response = await fetch(`${globalUrl}/api/v1/health/stats?after=${after}`, {
|
|
||||||
const response = await fetch(`${globalUrl}/api/v1/health/stats`, {
|
const response = await fetch(`${globalUrl}/api/v1/health/stats`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -42,31 +46,118 @@ const HealthPage = (props) => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching health stats:", error);
|
console.error("Error fetching health stats:", error);
|
||||||
toast.error("Failed loading health stats");
|
toast.error("Failed loading health stats");
|
||||||
|
} finally {
|
||||||
|
setIsHealthLoading(false); // Stop loading for HealthBarChart
|
||||||
|
}
|
||||||
|
}, [globalUrl]);
|
||||||
|
|
||||||
|
const fetchLiveExecutions = useCallback(async (range = '1h') => {
|
||||||
|
setIsLiveExecutionsLoading(true); // Start loading for LiveExecutionsChart
|
||||||
|
try {
|
||||||
|
const fetchOptions = {
|
||||||
|
method: "GET",
|
||||||
|
credentials: "include",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (window.location.host !== "localhost:3002") {
|
||||||
|
fetchOptions.headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = Math.floor(Date.now() / 1000);
|
||||||
|
// let after = now - 3600; // Default to 1h
|
||||||
|
let mode = ""
|
||||||
|
|
||||||
|
switch (range) {
|
||||||
|
case '1h':
|
||||||
|
mode = "1h"
|
||||||
|
break;
|
||||||
|
case '7h':
|
||||||
|
mode = "7h"
|
||||||
|
break;
|
||||||
|
case '1d':
|
||||||
|
mode = "1d"
|
||||||
|
break;
|
||||||
|
case '7d':
|
||||||
|
mode = "7d"
|
||||||
|
break;
|
||||||
|
case 'month':
|
||||||
|
mode = "month"
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
mode = "1h"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!userdata.support_access) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
`${globalUrl}/api/v1/health/executions/live?mode=${mode}`,
|
||||||
|
fetchOptions
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Failed to fetch live executions");
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
console.log("Raw live executions data:", data);
|
||||||
|
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
const formattedData = data
|
||||||
|
.map(item => ({
|
||||||
|
...item,
|
||||||
|
// failed: Number(item.failed) || 0,
|
||||||
|
executing: Number(item.executing) || 0,
|
||||||
|
finished: Number(item.finished) || 0,
|
||||||
|
aborted: Number(item.aborted) || 0,
|
||||||
|
created_at: Number(item.created_at) || 0
|
||||||
|
}))
|
||||||
|
.sort((a, b) => a.created_at - b.created_at);
|
||||||
|
|
||||||
|
console.log("Formatted live executions data:", formattedData);
|
||||||
|
setLiveExecutionsData(formattedData);
|
||||||
|
} else {
|
||||||
|
console.error("Received invalid data format:", data);
|
||||||
|
setLiveExecutionsData([]);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching live executions:", error);
|
||||||
|
toast.error("Failed loading live executions data");
|
||||||
|
} finally {
|
||||||
|
setIsLiveExecutionsLoading(false); // Stop loading for LiveExecutionsChart
|
||||||
}
|
}
|
||||||
}, [globalUrl]);
|
}, [globalUrl]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchHealthStats();
|
fetchHealthStats();
|
||||||
}, [fetchHealthStats]);
|
fetchLiveExecutions(liveExecutionsRange);
|
||||||
|
|
||||||
|
const interval = setInterval(() => fetchLiveExecutions(liveExecutionsRange), 60000);
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, [fetchHealthStats, fetchLiveExecutions, liveExecutionsRange]);
|
||||||
|
|
||||||
const extractRunFinished = (data, range) => {
|
const extractRunFinished = (data, range) => {
|
||||||
if (!data || !Array.isArray(data)) return [];
|
if (!data || !Array.isArray(data)) return [];
|
||||||
|
|
||||||
const currentDate = new Date().getTime();
|
const currentDate = new Date().getTime();
|
||||||
const rangeInMillis = {
|
const rangeInMillis = {
|
||||||
'24hr': 24 * 60 * 60 * 1000,
|
'24hr': 24 * 60 * 60 * 1000,
|
||||||
'7day': 7 * 24 * 60 * 60 * 1000,
|
'7day': 7 * 24 * 60 * 60 * 1000,
|
||||||
'30d': 30 * 24 * 60 * 60 * 1000,
|
'30d': 30 * 24 * 60 * 60 * 1000,
|
||||||
'90d': 90 * 24 * 60 * 60 * 1000
|
'90d': 90 * 24 * 60 * 60 * 1000
|
||||||
}
|
};
|
||||||
const filteredData = data.filter(item => currentDate - item.updated * 1000 <= rangeInMillis[range])
|
const filteredData = data.filter(item => currentDate - item.updated * 1000 <= rangeInMillis[range]);
|
||||||
|
|
||||||
const aggregatedData = new Map()
|
const aggregatedData = new Map();
|
||||||
|
|
||||||
filteredData.forEach(item => {
|
filteredData.forEach(item => {
|
||||||
const timestamp = item.updated * 1000; // Convert Unix timestamp to milliseconds
|
const timestamp = item.updated * 1000; // Convert Unix timestamp to milliseconds
|
||||||
let key;
|
let key;
|
||||||
|
|
||||||
switch (range) {
|
switch (range) {
|
||||||
case '24hr':
|
case '24hr':
|
||||||
const date = new Date(timestamp);
|
const date = new Date(timestamp);
|
||||||
@@ -83,7 +174,7 @@ const HealthPage = (props) => {
|
|||||||
default:
|
default:
|
||||||
key = new Date(timestamp).toLocaleDateString();
|
key = new Date(timestamp).toLocaleDateString();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if date already exists in the map
|
// Check if date already exists in the map
|
||||||
if (aggregatedData.has(key)) {
|
if (aggregatedData.has(key)) {
|
||||||
// Update aggregated values
|
// Update aggregated values
|
||||||
@@ -100,13 +191,13 @@ const HealthPage = (props) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Calculate averages and assign colors
|
// Calculate averages and assign colors
|
||||||
const result = Array.from(aggregatedData.entries()).map(([key, { totalEntries, totalRunFinished, executionIds }]) => {
|
const result = Array.from(aggregatedData.entries()).map(([key, { totalEntries, totalRunFinished, executionIds }]) => {
|
||||||
const avg = totalEntries > 0 ? totalRunFinished / totalEntries : 0;
|
const avg = totalEntries > 0 ? totalRunFinished / totalEntries : 0;
|
||||||
const FinalAvg = avg * 100;
|
const FinalAvg = avg * 100;
|
||||||
let color;
|
let color;
|
||||||
|
|
||||||
if (FinalAvg >= 100) {
|
if (FinalAvg >= 100) {
|
||||||
color = '#00F670';
|
color = '#00F670';
|
||||||
} else if (FinalAvg >= 98.50 && FinalAvg <= 99.99) {
|
} else if (FinalAvg >= 98.50 && FinalAvg <= 99.99) {
|
||||||
@@ -114,7 +205,7 @@ const HealthPage = (props) => {
|
|||||||
} else if (FinalAvg <= 98.49) {
|
} else if (FinalAvg <= 98.49) {
|
||||||
color = '#FF354C';
|
color = '#FF354C';
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
date: range === '24hr' ? `${key}:00` : key,
|
date: range === '24hr' ? `${key}:00` : key,
|
||||||
avgRunFinished: FinalAvg,
|
avgRunFinished: FinalAvg,
|
||||||
@@ -122,11 +213,10 @@ const HealthPage = (props) => {
|
|||||||
executionIds
|
executionIds
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (healthData) {
|
if (healthData) {
|
||||||
const newData = extractRunFinished(healthData, selectedRange);
|
const newData = extractRunFinished(healthData, selectedRange);
|
||||||
@@ -160,19 +250,19 @@ const HealthPage = (props) => {
|
|||||||
const labels = filteredData.map((value, i) => {
|
const labels = filteredData.map((value, i) => {
|
||||||
if (selectedRange === '24hr') {
|
if (selectedRange === '24hr') {
|
||||||
const [datePart, hourPart] = value.date.split(' ');
|
const [datePart, hourPart] = value.date.split(' ');
|
||||||
const [day, month, year] = datePart.split('/');
|
const [month, day, year] = datePart.split('/');
|
||||||
const monthIndex = parseInt(month, 10) - 1;
|
const monthIndex = parseInt(month, 10) - 1;
|
||||||
const date = new Date(year, monthIndex, day);
|
const date = new Date(year, monthIndex, day);
|
||||||
let formattedDate = `${date.toLocaleString('en-US', { month: 'short', day: '2-digit' })}`;
|
let formattedDate = `${date.toLocaleString('en-US', { month: 'short', day: '2-digit' })}`;
|
||||||
|
|
||||||
// Add hour part if available
|
// Add hour part if available
|
||||||
if (hourPart) {
|
if (hourPart) {
|
||||||
formattedDate += `, ${hourPart.split(':').slice(0, 2).join(':')}`;
|
formattedDate += `, ${hourPart.split(':').slice(0, 2).join(':')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${formattedDate} \nUptime: ${value.avgRunFinished.toFixed(2)}%`;
|
return `${formattedDate} \nUptime: ${value.avgRunFinished.toFixed(2)}%`;
|
||||||
} else if (selectedRange === '7day') {
|
} else if (selectedRange === '7day') {
|
||||||
const [datePart, hourPart] = value.date.split(' ');
|
const [datePart, hourPart] = value.date.split(' ');
|
||||||
const [day, month, year] = datePart.split('/');
|
const [month, day, year] = datePart.split('/');
|
||||||
const monthIndex = parseInt(month, 10) - 1;
|
const monthIndex = parseInt(month, 10) - 1;
|
||||||
const date = new Date(year, monthIndex, day);
|
const date = new Date(year, monthIndex, day);
|
||||||
let formattedDate = `${date.toLocaleString('en-US', { month: 'short', day: '2-digit' })}`;
|
let formattedDate = `${date.toLocaleString('en-US', { month: 'short', day: '2-digit' })}`;
|
||||||
@@ -185,12 +275,12 @@ const HealthPage = (props) => {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const dateParts = value.date.split('/'); // Assuming the date format is "DD/MM/YYYY"
|
const dateParts = value.date.split('/'); // Assuming the date format is "DD/MM/YYYY"
|
||||||
const date = new Date(`${dateParts[2]}-${dateParts[1]}-${dateParts[0]}`); // Reformat the date string to "YYYY-MM-DD"
|
const date = new Date(`${dateParts[2]}-${dateParts[0]}-${dateParts[1]}`); // Reformat the date string to "YYYY-MM-DD"
|
||||||
|
|
||||||
return `${date.toLocaleDateString('en-US', { month: 'short', day: '2-digit', year: 'numeric' })} \nUptime: ${value.avgRunFinished.toFixed(2)}%`;
|
return `${date.toLocaleDateString('en-US', { month: 'short', day: '2-digit', year: 'numeric' })} \nUptime: ${value.avgRunFinished.toFixed(2)}%`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
if (selectedRange === '24hr') {
|
if (selectedRange === '24hr') {
|
||||||
barThickness = 35;
|
barThickness = 35;
|
||||||
}
|
}
|
||||||
@@ -200,12 +290,6 @@ const HealthPage = (props) => {
|
|||||||
else if (selectedRange === '30d') {
|
else if (selectedRange === '30d') {
|
||||||
barThickness = 25;
|
barThickness = 25;
|
||||||
}
|
}
|
||||||
// let width = 800; // Default chart width
|
|
||||||
// if (selectedRange === '7day') {
|
|
||||||
// width = 400; // Adjust chart width for 7 days
|
|
||||||
// } else if (selectedRange === '30d') {
|
|
||||||
// width = 600; // Adjust chart width for 30 days
|
|
||||||
// }
|
|
||||||
|
|
||||||
const datasets = [{
|
const datasets = [{
|
||||||
label: "",
|
label: "",
|
||||||
@@ -213,14 +297,11 @@ const HealthPage = (props) => {
|
|||||||
backgroundColor: filteredData.map(item => item.color),
|
backgroundColor: filteredData.map(item => item.color),
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
barThickness: barThickness,
|
barThickness: barThickness,
|
||||||
// barPercentage: barPercentage,
|
|
||||||
}];
|
}];
|
||||||
|
|
||||||
return { labels, datasets };
|
return { labels, datasets };
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log("HEALTHPAGE 2")
|
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
legend: {
|
legend: {
|
||||||
display: false
|
display: false
|
||||||
@@ -253,12 +334,11 @@ const HealthPage = (props) => {
|
|||||||
},
|
},
|
||||||
afterLabel: function (tooltipItem, data) {
|
afterLabel: function (tooltipItem, data) {
|
||||||
const label = data.labels[tooltipItem.index];
|
const label = data.labels[tooltipItem.index];
|
||||||
// console.log(label)
|
|
||||||
const uptime = label.match(/Uptime:\s*(\d+(?:\.\d+)?)/)[1]; // Extract uptime value using regex
|
const uptime = label.match(/Uptime:\s*(\d+(?:\.\d+)?)/)[1]; // Extract uptime value using regex
|
||||||
return `Success Rate: ${uptime}%`; // Customize the uptime display
|
return `Test-Workflow Health: ${uptime}%`; // Customize the uptime display
|
||||||
},
|
},
|
||||||
title: function () {
|
title: function () {
|
||||||
return 'Fully Oprational'; // Hide the tooltip title
|
return 'Fully Operational'; // Hide the tooltip title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -284,32 +364,118 @@ const HealthPage = (props) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const healthBarData = updateChartData()
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: 30, width: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center' }}>
|
<div style={{ paddingTop: 30, width: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center' }}>
|
||||||
|
{/* Health Bar Chart Section */}
|
||||||
<ButtonGroup style={{ display: 'flex', margin: "auto", marginBottom: 10, width: 300, borderRadius: 30, background: "#000000" }}>
|
<ButtonGroup style={{ display: 'flex', margin: "auto", marginBottom: 10, width: 300, borderRadius: 30, background: "#000000" }}>
|
||||||
<Button variant="contained" style={{ flex: 1, borderBottom: selectedRange === '24hr' ? '2px solid #FF8444' : 'none', background: "#000000", color: selectedRange === '24hr' ? '#FF8444' : '#cfd8dc', textTransform: 'none' }} onClick={() => filterDataByRange('24hr')}>24h</Button>
|
<Button variant="contained" style={{ flex: 1, borderBottom: selectedRange === '24hr' ? '2px solid #FF8444' : 'none', background: "#000000", color: selectedRange === '24hr' ? '#FF8444' : '#cfd8dc', textTransform: 'none' }} onClick={() => filterDataByRange('24hr')} disabled={isHealthLoading}>24h</Button>
|
||||||
<Button variant="contained" style={{ flex: 1, borderBottom: selectedRange === '7day' ? '2px solid #FF8444' : 'none', background: "#000000", color: selectedRange === '7day' ? '#FF8444' : '#cfd8dc', textTransform: 'none' }} onClick={() => filterDataByRange('7day')}>7d</Button>
|
<Button variant="contained" style={{ flex: 1, borderBottom: selectedRange === '7day' ? '2px solid #FF8444' : 'none', background: "#000000", color: selectedRange === '7day' ? '#FF8444' : '#cfd8dc', textTransform: 'none' }} onClick={() => filterDataByRange('7day')} disabled={isHealthLoading}>7d</Button>
|
||||||
<Button variant="contained" style={{ flex: 1, borderBottom: selectedRange === '30d' ? '2px solid #FF8444' : 'none', background: "#000000", color: selectedRange === '30d' ? '#FF8444' : '#cfd8dc', textTransform: 'none' }} onClick={() => filterDataByRange('30d')}>30d</Button>
|
<Button variant="contained" style={{ flex: 1, borderBottom: selectedRange === '30d' ? '2px solid #FF8444' : 'none', background: "#000000", color: selectedRange === '30d' ? '#FF8444' : '#cfd8dc', textTransform: 'none' }} onClick={() => filterDataByRange('30d')} disabled={isHealthLoading}>30d</Button>
|
||||||
<Button disabled variant="contained" style={{ flex: 1, borderBottom: selectedRange === '90d' ? '2px solid #FF8444' : 'none', background: "#000000", color: false === false ? "grey" : selectedRange === '90d' ? '#FF8444' : '#cfd8dc', textTransform: 'none' }} onClick={() => filterDataByRange('90d')}>90d</Button>
|
<Button disabled variant="contained" style={{ flex: 1, borderBottom: selectedRange === '90d' ? '2px solid #FF8444' : 'none', background: "#000000", color: false === false ? "grey" : selectedRange === '90d' ? '#FF8444' : '#cfd8dc', textTransform: 'none' }} onClick={() => filterDataByRange('90d')} disabled={isHealthLoading}>90d</Button>
|
||||||
<Button disabled variant="contained" style={{ flex: 1, borderBottom: selectedRange === '180d' ? '2px solid #FF8444' : 'none', background: "#000000", color: false === false ? "grey" : selectedRange === '180d' ? '#FF8444' : '#cfd8dc', textTransform: 'none' }} onClick={() => filterDataByRange('180d')}>180d</Button>
|
<Button disabled variant="contained" style={{ flex: 1, borderBottom: selectedRange === '180d' ? '2px solid #FF8444' : 'none', background: "#000000", color: false === false ? "grey" : selectedRange === '180d' ? '#FF8444' : '#cfd8dc', textTransform: 'none' }} onClick={() => filterDataByRange('180d')} disabled={isHealthLoading}>180d</Button>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
|
|
||||||
<div style={{ margin: '0 auto', padding: 20, width: 1000, justifyContent: "center", color: '#ffffff', backgroundColor: '#000000', fontSize: '16px', borderRadius: '16px' }}>
|
{/* Loading Bar for HealthBarChart */}
|
||||||
<div style={{ display: "flex", alignItems: "center" }}>
|
{isHealthLoading && (
|
||||||
<CheckOutlinedIcon style={{ borderRadius: 20, fontSize: 24, backgroundColor: '#00e600', marginLeft: 25 }} />
|
<LinearProgress style={{ width: '100%', marginBottom: 10 }} />
|
||||||
<div>
|
)}
|
||||||
<Typography style={{ marginLeft: 10 }}>Workflow Health</Typography>
|
|
||||||
<Typography style={{ marginLeft: 10, fontWeight: 100, fontSize: 13, color: "#00FF00" }}>Operational</Typography>
|
<div style={{ margin: '0 auto', padding: 10, width: 800, backgroundColor: '#000000', borderRadius: '16px' }}>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '10px' }}>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
<CheckOutlinedIcon style={{ borderRadius: 20, fontSize: 24, backgroundColor: '#00e600', marginRight: 10 }} />
|
||||||
|
<div>
|
||||||
|
<Typography style={{ color: '#ffffff', fontSize: 16 }}>Workflow Health</Typography>
|
||||||
|
<Typography style={{ color: '#00FF00', fontSize: 12, fontWeight: 100 }}>Operational</Typography>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ marginLeft: 720 }}>
|
<div>
|
||||||
<Typography style={{}}>{averageUptime.toFixed(2)}%</Typography>
|
<Typography style={{ color: '#ffffff', fontSize: 16, textAlign: 'end' }}>{averageUptime.toFixed(2)}%</Typography>
|
||||||
<Typography style={{ fontWeight: 100, fontSize: 13, textAlign: "end" }}>Success Rate</Typography>
|
<Typography style={{ color: '#ffffff', fontSize: 12, fontWeight: 100 }}>Success Rate</Typography>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<HealthBarChart filteredData={updateChartData()} options={options} onBarClick={handleBarClick} />
|
<HealthBarChart
|
||||||
|
filteredData={filteredData}
|
||||||
|
onBarClick={handleBarClick}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{userdata.support_access && (
|
||||||
|
<div style={{ margin: '20px auto', padding: 20, width: 1000, justifyContent: "center", color: '#ffffff', backgroundColor: '#000000', fontSize: '16px', borderRadius: '16px' }}>
|
||||||
|
<div style={{ display: "flex", alignItems: "center", marginBottom: 20 }}>
|
||||||
|
<Typography variant="h6" style={{ marginLeft: 10 }}>Live Executions</Typography>
|
||||||
|
<ButtonGroup style={{ marginLeft: 'auto', borderRadius: 30, background: "#000000" }}>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
borderBottom: liveExecutionsRange === '1h' ? '2px solid #FF8444' : 'none',
|
||||||
|
background: "#000000",
|
||||||
|
color: liveExecutionsRange === '1h' ? '#FF8444' : '#cfd8dc',
|
||||||
|
textTransform: 'none'
|
||||||
|
}}
|
||||||
|
onClick={() => setLiveExecutionsRange('1h')}
|
||||||
|
disabled={isLiveExecutionsLoading}
|
||||||
|
>
|
||||||
|
1h
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
borderBottom: liveExecutionsRange === '7h' ? '2px solid #FF8444' : 'none',
|
||||||
|
background: "#000000",
|
||||||
|
color: liveExecutionsRange === '7h' ? '#FF8444' : '#cfd8dc',
|
||||||
|
textTransform: 'none'
|
||||||
|
}}
|
||||||
|
onClick={() => setLiveExecutionsRange('7h')}
|
||||||
|
disabled={isLiveExecutionsLoading}
|
||||||
|
>
|
||||||
|
7h
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
borderBottom: liveExecutionsRange === '1d' ? '2px solid #FF8444' : 'none',
|
||||||
|
background: "#000000",
|
||||||
|
color: liveExecutionsRange === '1d' ? '#FF8444' : '#cfd8dc',
|
||||||
|
textTransform: 'none'
|
||||||
|
}}
|
||||||
|
onClick={() => setLiveExecutionsRange('1d')}
|
||||||
|
disabled={isLiveExecutionsLoading}
|
||||||
|
>
|
||||||
|
1d
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
borderBottom: liveExecutionsRange === '7d' ? '2px solid #FF8444' : 'none',
|
||||||
|
background: "#000000",
|
||||||
|
color: liveExecutionsRange === '7d' ? '#FF8444' : '#cfd8dc',
|
||||||
|
textTransform: 'none'
|
||||||
|
}}
|
||||||
|
onClick={() => setLiveExecutionsRange('7d')}
|
||||||
|
disabled={isLiveExecutionsLoading}
|
||||||
|
>
|
||||||
|
7d
|
||||||
|
</Button>
|
||||||
|
{/* <Button variant="contained" style={{ flex: 1, borderBottom: liveExecutionsRange === 'month' ? '2px solid #FF8444' : 'none', background: "#000000", color: liveExecutionsRange === 'month' ? '#FF8444' : '#cfd8dc', textTransform: 'none' }} onClick={() => setLiveExecutionsRange('month')} disabled={isLiveExecutionsLoading}>Month</Button> */}
|
||||||
|
</ButtonGroup>
|
||||||
|
</div>
|
||||||
|
{/* Loading Bar for LiveExecutionsChart */}
|
||||||
|
{isLiveExecutionsLoading && (
|
||||||
|
<LinearProgress style={{ width: '100%', marginBottom: 10 }} />
|
||||||
|
)}
|
||||||
|
<LiveExecutionsChart data={liveExecutionsData} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user