diff --git a/client/src/Pages/Incidents/Components/ActiveIncidentsPanel/index.jsx b/client/src/Pages/Incidents/Components/ActiveIncidentsPanel/index.jsx deleted file mode 100644 index fda257695..000000000 --- a/client/src/Pages/Incidents/Components/ActiveIncidentsPanel/index.jsx +++ /dev/null @@ -1,134 +0,0 @@ -import PropTypes from "prop-types"; -import { Box, Stack, Typography } from "@mui/material"; -import { useTheme } from "@emotion/react"; -import { useTranslation } from "react-i18next"; -import PanelSkeleton from "../IncidentsSummaryPanel/skeleton.jsx"; -import Icon from "@/Components/v1/Icon"; -import SummaryCard from "../SummaryCard/index.jsx"; - -/** - * ActiveIncidentsPanel Component - * - * Displays a quick overview of currently active incidents. - * Shows only essential information: monitor name, status badge, and duration. - * - * @param {Object} props - * @param {Array} props.incidents - Array of active incidents - * @param {number} props.totalCount - Total count of active incidents (for badge) - * @param {boolean} props.isLoading - Loading state - * @param {Object} props.error - Error object if any - * @param {Function} props.onIncidentClick - Callback when incident is clicked (optional) - */ -const ActiveIncidentsPanel = ({ totalCount = 0, isLoading = false, error = null }) => { - const theme = useTheme(); - const { t } = useTranslation(); - - if (isLoading) { - return ; - } - - if (error) { - return ( - - - {t("incidentsPage.incidentsActivePanelError")} - - - ); - } - - if (!totalCount || totalCount === 0) { - return ( - - - - - - - - {t("incidentsPage.allSystemsAreOperational")} - - - - ); - } - - return ( - - - - - - - - {totalCount} - - - - ); -}; - -ActiveIncidentsPanel.propTypes = { - totalCount: PropTypes.number, - isLoading: PropTypes.bool, - error: PropTypes.object, - onIncidentClick: PropTypes.func, -}; - -export default ActiveIncidentsPanel; diff --git a/client/src/Pages/Incidents/Components/CardSummary.tsx b/client/src/Pages/Incidents/Components/CardSummary.tsx index 12d1f87cb..60b13bde7 100644 --- a/client/src/Pages/Incidents/Components/CardSummary.tsx +++ b/client/src/Pages/Incidents/Components/CardSummary.tsx @@ -5,13 +5,45 @@ import Grid from "@mui/material/Grid"; import { BaseBox, ValueLabel } from "@/Components/v2/design-elements"; import { CircleCheck, TriangleAlert, Bell, Wrench, Globe } from "lucide-react"; import Box from "@mui/material/Box"; -import { SummaryItem } from "@/Pages/Incidents/Components/IncidentItem"; import { useTranslation } from "react-i18next"; import { useTheme } from "@mui/material"; import type { IncidentSummary, IncidentSummaryItem } from "@/Types/Incident"; import { getIncidentsDuration } from "@/Pages/Incidents/utils"; +interface SummaryItemProps { + icon: React.ReactNode; + label: string; + value: string | number; +} + +const SummaryItem = ({ icon, label, value }: SummaryItemProps) => { + const theme = useTheme(); + return ( + + + {icon} + {label} + + + {value} + + + ); +}; + interface SummaryCardProps { title: string; sx?: React.CSSProperties; @@ -92,6 +124,7 @@ export const SummaryCardActiveIncidents = ({ summary }: SummaryCardActiveInciden const SummaryIncidentItem = ({ incident }: { incident: IncidentSummaryItem }) => { const theme = useTheme(); + const { t } = useTranslation(); const duration = getIncidentsDuration(incident); return ( fontWeight={500} noWrap > - {incident.monitorName ?? "N/A"} + {incident.monitorName ?? t("common.labels.na")} @@ -130,7 +163,7 @@ const SummaryIncidentItem = ({ incident }: { incident: IncidentSummaryItem }) => > @@ -182,8 +215,8 @@ export const SummaryCardStats = ({ summary }: SummaryCardStatsProps) => { if (!summary) return null; const mostAffected = !summary.total || summary.total === 0 - ? "N/A" - : summary.topMonitor?.monitorName || t("N/A"); + ? t("common.labels.na") + : summary.topMonitor?.monitorName || t("common.labels.na"); return ( { } label={t("pages.incidents.summaryCard.incidentStats.avgResolutionTime")} - value={summary.total > 0 ? `${summary.avgResolutionTimeHours || 0} hours` : "N/A"} + value={summary.total > 0 ? `${summary.avgResolutionTimeHours || 0} hours` : t("common.labels.na")} /> ); diff --git a/client/src/Pages/Incidents/Components/HeaderIncidents.tsx b/client/src/Pages/Incidents/Components/HeaderIncidents.tsx deleted file mode 100644 index e69de29bb..000000000 diff --git a/client/src/Pages/Incidents/Components/IncidentDetailsModal/index.jsx b/client/src/Pages/Incidents/Components/IncidentDetailsModal/index.jsx deleted file mode 100644 index 34efd632e..000000000 --- a/client/src/Pages/Incidents/Components/IncidentDetailsModal/index.jsx +++ /dev/null @@ -1,413 +0,0 @@ -// Components -import { Stack, Typography, Grid, Box, Button, Divider } from "@mui/material"; -import { GenericDialog } from "@/Components/v1/Dialog/genericDialog.jsx"; -import GenericFallback from "@/Components/v1/GenericFallback/index.jsx"; -import SummaryCard from "../SummaryCard/index.jsx"; -import ResolveIncidentDialog from "../ResolveIncidentDialog/index.jsx"; - -// Utils -import { useEffect, useState } from "react"; -import { useTheme } from "@emotion/react"; -import { useTranslation } from "react-i18next"; -import { formatDateWithTz } from "@/Utils/timeUtilsLegacy.js"; -import { useSelector } from "react-redux"; -import PropTypes from "prop-types"; - -// Hooks -import useFetchIncidents from "../../hooks/useFetchIncidents.js"; -import useGetIncidentsDuration from "../../hooks/useGetIncidentsDuration.js"; - -const IncidentDetailsModal = ({ open, incidentId, onClose, onResolved }) => { - const theme = useTheme(); - const { t } = useTranslation(); - const { fetchIncidentById, resolveIncident } = useFetchIncidents(); - const [incidentData, setIncidentData] = useState(null); - const [isLoading, setIsLoading] = useState(false); - const [isResolveDialogOpen, setIsResolveDialogOpen] = useState(false); - const uiTimezone = useSelector((state) => state.ui.timezone); - const isActive = incidentData?.incident?.status === true; - const duration = useGetIncidentsDuration(incidentData?.incident || null, isActive); - - const statusColor = isActive ? theme.palette.error.main : theme.palette.success.main; - const toCapitalLetter = (text) => - typeof text === "string" && text.length > 0 - ? text.charAt(0).toUpperCase() + text.slice(1) - : text; - - const KeyValueRow = ({ label, value, sx = {} }) => ( - - - - {label} - - - - - {value ?? "-"} - - - - ); - - useEffect(() => { - const loadIncident = async () => { - if (!incidentId || !open) { - setIncidentData(null); - setIsLoading(false); - return; - } - - setIsLoading(true); - try { - const incidentData = await fetchIncidentById(incidentId); - setIncidentData(incidentData); - } catch (error) { - console.error("Error fetching incident:", error); - setIncidentData(null); - } finally { - setIsLoading(false); - } - }; - - loadIncident(); - }, [incidentId, open, fetchIncidentById]); - - const openResolveDialog = () => { - setIsResolveDialogOpen(true); - }; - - const closeResolveDialog = () => { - setIsResolveDialogOpen(false); - }; - - const handleAfterResolve = async () => { - if (!incidentId) return; - const updatedIncident = await fetchIncidentById(incidentId); - if (updatedIncident) { - setIncidentData(updatedIncident); - } - }; - - const renderContent = () => { - if (isLoading) { - return ( - - ); - } - - if (!incidentData) { - return ( - - ); - } - - return ( - - - - - - - - {isActive ? t("incidentsPage.active") : t("incidentsPage.resolved")} - - - - - - {t("incidentsPage.incidentItemMonitor")} - - - {incidentData?.monitor?.name || t("incidentsPage.unknownMonitor")} - - - - - {t("incidentsPage.URL")} - - - {incidentData?.monitor?.url || "-"} - - - - - - - - - - - - - - {t("incidentsPage.timeline")} - - - - - {!isActive && ( - - )} - - - - - - - - - - - {isActive - ? t("incidentsPage.errorDetails") - : t("incidentsPage.resolutionDetails")} - - - - - - - - - {!isActive && ( - <> - - {incidentData?.incident?.resolutionType === "manual" && ( - - )} - - )} - - - - - - - - ); - }; - - return ( - - - {renderContent()} - {!isActive && - incidentData?.incident?.resolutionType === "manual" && - incidentData?.user?.email && ( - - {t("incidentsPage.resolvedBy")}: {incidentData.user.email} - - )} - - {isActive && ( - - )} - - - - - - - ); -}; - -IncidentDetailsModal.propTypes = { - open: PropTypes.bool.isRequired, - incidentId: PropTypes.string, - onClose: PropTypes.func.isRequired, - onResolved: PropTypes.func, -}; - -export default IncidentDetailsModal; diff --git a/client/src/Pages/Incidents/Components/IncidentItem.tsx b/client/src/Pages/Incidents/Components/IncidentItem.tsx deleted file mode 100644 index 13bff2a77..000000000 --- a/client/src/Pages/Incidents/Components/IncidentItem.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import Stack from "@mui/material/Stack"; -import Box from "@mui/material/Box"; -import Divider from "@mui/material/Divider"; -import Grid from "@mui/material/Grid"; -import Typography from "@mui/material/Typography"; -import { ValueLabel } from "@/Components/v2/design-elements"; -import { Globe } from "lucide-react"; - -import type { Incident } from "@/Types/Incident"; -import type { Monitor } from "@/Types/Monitor"; -import { useTheme } from "@mui/material"; -import { getIncidentsDuration } from "@/Pages/Incidents/utils"; - -export const IncidentItem = ({ - incident, - monitor, -}: { - incident: Incident; - monitor?: Monitor | null; -}) => { - const theme = useTheme(); - const duration = getIncidentsDuration(incident); - return ( - - - - - {monitor ? monitor.name : "N/A"} - - - - - - - - - {duration} - - - ); -}; - -interface SummaryItemProps { - icon: React.ReactNode; - label: string; - value: string | number; -} - -export const SummaryItem = ({ icon, label, value }: SummaryItemProps) => { - const theme = useTheme(); - return ( - - - {icon} - - {label}: {value} - - - - - ); -}; diff --git a/client/src/Pages/Incidents/Components/IncidentTable/IncidentActionsMenu.jsx b/client/src/Pages/Incidents/Components/IncidentTable/IncidentActionsMenu.jsx deleted file mode 100644 index d125a9fdb..000000000 --- a/client/src/Pages/Incidents/Components/IncidentTable/IncidentActionsMenu.jsx +++ /dev/null @@ -1,156 +0,0 @@ -// Components -import IconButton from "@mui/material/IconButton"; -import Menu from "@mui/material/Menu"; -import MenuItem from "@mui/material/MenuItem"; -import Icon from "@/Components/v1/Icon/index.jsx"; -import ResolveIncidentDialog from "../ResolveIncidentDialog/index.jsx"; - -// Utils -import { useState } from "react"; -import { useTheme } from "@emotion/react"; -import { useTranslation } from "react-i18next"; -import { TypeToPathMap } from "@/Utils/monitorUtilsLegacy.js"; -import { useNavigate } from "react-router-dom"; -import PropTypes from "prop-types"; - -const IncidentActionsMenu = ({ incident, monitor, onResolve, onOpenDetails }) => { - const [anchorEl, setAnchorEl] = useState(null); - const [isDialogOpen, setIsDialogOpen] = useState(false); - const theme = useTheme(); - const { t } = useTranslation(); - const navigate = useNavigate(); - const openMenu = (event) => { - event.preventDefault(); - event.stopPropagation(); - setAnchorEl(event.currentTarget); - }; - - const closeMenu = (e) => { - if (e) { - e.stopPropagation(); - } - setAnchorEl(null); - }; - - const openResolveDialog = (e) => { - closeMenu(e); - setIsDialogOpen(true); - }; - - const closeDialog = (e) => { - if (e) { - e.stopPropagation(); - } - setIsDialogOpen(false); - }; - - const isActive = incident.status === true; - const handleGoToDetails = (e) => { - e.stopPropagation(); - closeMenu(e); - if (onOpenDetails) { - onOpenDetails(incident.id); - } - }; - - const handleGoToMonitor = async (e) => { - e.stopPropagation(); - closeMenu(e); - try { - if (monitor) { - const path = TypeToPathMap[monitor.type]; - if (path && monitor.id) { - navigate(`/${path}/${monitor.id}`); - } else { - console.error(`Monitor type not found`); - } - } else { - console.error("Monitor information not found in incident data"); - } - } catch (error) { - console.error("Error fetching incident details:", error); - } - }; - - return ( - <> - { - event.stopPropagation(); - openMenu(event); - }} - sx={{ - "&:focus": { - outline: "none", - }, - "& svg path": { - stroke: theme.palette.primary.contrastTextTertiary, - }, - }} - > - - - - - - {t("incidentsPage.incidentsTableActionDetails", "Details")} - - - {t("incidentsPage.incidentsTableActionGoToMonitor", "Go to monitor")} - - {isActive && ( - { - e.stopPropagation(); - openResolveDialog(e); - }} - > - {t("incidentsPage.incidentsTableActionResolveManually")} - - )} - - - - ); -}; - -IncidentActionsMenu.propTypes = { - incident: PropTypes.shape({ - id: PropTypes.string.isRequired, - status: PropTypes.bool.isRequired, - }).isRequired, - monitor: PropTypes.shape({ - id: PropTypes.string.isRequired, - type: PropTypes.string.isRequired, - }).isRequired, - onResolve: PropTypes.func.isRequired, - onOpenDetails: PropTypes.func, -}; - -export default IncidentActionsMenu; diff --git a/client/src/Pages/Incidents/Components/IncidentTable/index.jsx b/client/src/Pages/Incidents/Components/IncidentTable/index.jsx deleted file mode 100644 index 2a03a8822..000000000 --- a/client/src/Pages/Incidents/Components/IncidentTable/index.jsx +++ /dev/null @@ -1,193 +0,0 @@ -//Components -import DataTable from "@/Components/v1/Table/index.jsx"; -import TableSkeleton from "@/Components/v1/Table/skeleton.jsx"; -import Pagination from "@/Components/v1/Table/TablePagination/index.jsx"; -import { StatusLabel } from "@/Components/v1/Label/index.jsx"; -import { HttpStatusLabel } from "@/Components/v1/HttpStatusLabel/index.jsx"; -import GenericFallback from "@/Components/v1/GenericFallback/index.jsx"; -import NetworkError from "@/Components/v1/GenericFallback/NetworkError.jsx"; -import IncidentActionsMenu from "./IncidentActionsMenu.jsx"; - -//Utils -import { formatDateWithTz } from "../../../../Utils/timeUtilsLegacy.js"; -import { useSelector } from "react-redux"; -import PropTypes from "prop-types"; -import { useTranslation } from "react-i18next"; -import { Typography, useTheme } from "@mui/material"; - -const IncidentTable = ({ - monitors = [], - incidents = [], - incidentsCount = 0, - isLoading = false, - networkError = false, - page = 0, - rowsPerPage = 10, - handleChangePage, - handleChangeRowsPerPage, - resolveIncident, - handleUpdateTrigger, - onOpenDetails, -}) => { - const uiTimezone = useSelector((state) => state.ui.timezone); - - const { t } = useTranslation(); - const theme = useTheme(); - - const handleResolveIncident = async (incidentId, options = {}) => { - try { - await resolveIncident(incidentId, options); - handleUpdateTrigger(); - } catch (error) { - console.error(t("incidentsPage.errorResolvingIncident"), error); - } - }; - - const headers = [ - { - id: "monitorName", - content: t("incidentsTableMonitorName"), - render: (row) => { - const monitor = monitors.find((monitor) => monitor.id === row.monitorId); - return monitor ? monitor.name : "N/A"; - }, - }, - { - id: "status", - content: t("incidentsTableStatus"), - render: (row) => { - const status = row.status === true ? "down" : "up"; - const statusText = - row.status === true ? t("incidentsPage.active") : t("incidentsPage.resolved"); - return ( - - ); - }, - }, - { - id: "startTime", - content: t("incidentsPage.startTime"), - render: (row) => { - const formattedDate = formatDateWithTz( - row.startTime || row.createdAt, - "YYYY-MM-DD HH:mm:ss A", - uiTimezone - ); - return formattedDate; - }, - }, - { - id: "endTime", - content: t("incidentsPage.endTime"), - render: (row) => { - if (row.endTime) { - return formatDateWithTz(row.endTime, "YYYY-MM-DD HH:mm:ss A", uiTimezone); - } - return "-"; - }, - }, - { - id: "resolutionType", - content: t("incidentsPage.resolutionType"), - render: (row) => { - if (row.resolutionType) { - return ( - - {row.resolutionType} - - ); - } - return "-"; - }, - }, - { - id: "statusCode", - content: t("incidentsTableStatusCode"), - render: (row) => , - }, - { - id: "message", - content: t("incidentsTableMessage"), - render: (row) => row.message || "-", - }, - { - id: "action", - content: t("actions"), - render: (row) => { - return ( - monitor.id === row.monitorId)} - onResolve={handleResolveIncident} - onOpenDetails={onOpenDetails} - /> - ); - }, - }, - ]; - - if (isLoading) return ; - - if (networkError) { - return ( - - - - ); - } - - if (!isLoading && !networkError && incidents?.length === 0) { - return ( - - {t("incidentsTableNoIncidents", "No incidents found")} - - ); - } - - const incidentsData = Array.isArray(incidents) ? incidents : []; - - return ( - <> - - - - ); -}; - -IncidentTable.propTypes = { - incidents: PropTypes.array.isRequired, // Array of incident objects - incidentsCount: PropTypes.number.isRequired, // Total count for pagination - isLoading: PropTypes.bool.isRequired, // Loading state - networkError: PropTypes.bool, // Network error object - page: PropTypes.number.isRequired, // Current page number - rowsPerPage: PropTypes.number.isRequired, // Number of rows per page - handleChangePage: PropTypes.func.isRequired, // Handler for page change - handleChangeRowsPerPage: PropTypes.func.isRequired, // Handler for rows per page change - resolveIncident: PropTypes.func.isRequired, - handleUpdateTrigger: PropTypes.func.isRequired, - onOpenDetails: PropTypes.func, -}; - -export default IncidentTable; diff --git a/client/src/Pages/Incidents/Components/IncidentsSummaryPanel/index.jsx b/client/src/Pages/Incidents/Components/IncidentsSummaryPanel/index.jsx deleted file mode 100644 index 33ba21cd1..000000000 --- a/client/src/Pages/Incidents/Components/IncidentsSummaryPanel/index.jsx +++ /dev/null @@ -1,100 +0,0 @@ -import { useEffect, useState } from "react"; -// Hooks -import useFetchIncidents from "../../hooks/useFetchIncidents.js"; -import PropTypes from "prop-types"; -// Components -import { Grid } from "@mui/material"; -import ActiveIncidentsPanel from "../ActiveIncidentsPanel/index.jsx"; -import LatestIncidentsPanel from "../LatestIncidentsPanel/index.jsx"; -import StatisticsPanel from "../StatisticsPanel/index.jsx"; - -const IncidentsSummaryPanel = ({ updateTrigger }) => { - const { fetchIncidentSummary } = useFetchIncidents(); - const [summary, setSummary] = useState({ - totalActive: 0, - avgResolutionTimeHours: 0, - total: 0, - topMonitor: null, - totalManualResolutions: 0, - totalAutomaticResolutions: 0, - latestIncidents: [], - }); - - const [isLoadingSummary, setIsLoadingSummary] = useState(false); - const [summaryError, setSummaryError] = useState(null); - - useEffect(() => { - const fetchSummary = async () => { - setIsLoadingSummary(true); - setSummaryError(null); - - try { - const summaryData = await fetchIncidentSummary({ - limit: 3, - }); - - if (summaryData) { - setSummary(summaryData); - } else { - setSummaryError(new Error("Failed to fetch summary")); - } - } catch (error) { - console.error("Error fetching incident summary:", error); - setSummaryError(error); - } finally { - setIsLoadingSummary(false); - } - }; - - fetchSummary(); - }, [fetchIncidentSummary, updateTrigger]); - - return ( - <> - - - - - - - - - - - - - - ); -}; - -IncidentsSummaryPanel.propTypes = { - updateTrigger: PropTypes.bool.isRequired, -}; -export default IncidentsSummaryPanel; diff --git a/client/src/Pages/Incidents/Components/IncidentsSummaryPanel/skeleton.jsx b/client/src/Pages/Incidents/Components/IncidentsSummaryPanel/skeleton.jsx deleted file mode 100644 index 153cd7c25..000000000 --- a/client/src/Pages/Incidents/Components/IncidentsSummaryPanel/skeleton.jsx +++ /dev/null @@ -1,20 +0,0 @@ -import { Skeleton } from "@mui/material"; -import SummaryCard from "../SummaryCard/index.jsx"; - -/** - * Simple skeleton loader for summary panels - * Reusable for ActiveIncidentsPanel, LatestIncidentsPanel, and StatisticsPanel - */ -const PanelSkeleton = () => { - return ( - - - - ); -}; - -export default PanelSkeleton; diff --git a/client/src/Pages/Incidents/Components/LatestIncidentsPanel/IncidentItem.jsx b/client/src/Pages/Incidents/Components/LatestIncidentsPanel/IncidentItem.jsx deleted file mode 100644 index e8001087b..000000000 --- a/client/src/Pages/Incidents/Components/LatestIncidentsPanel/IncidentItem.jsx +++ /dev/null @@ -1,115 +0,0 @@ -import PropTypes from "prop-types"; -import { Box, Stack, Typography } from "@mui/material"; -import { useTheme } from "@emotion/react"; -import { StatusLabel } from "@/Components/v1/Label/index.jsx"; -import { getHumanReadableDuration } from "@/Utils/timeUtilsLegacy.js"; -import Icon from "@/Components/v1/Icon"; -import { useTranslation } from "react-i18next"; -import useGetIncidentsDuration from "../../hooks/useGetIncidentsDuration"; -import { Grid } from "@mui/material"; - -const IncidentItem = ({ incident }) => { - const { t } = useTranslation(); - const theme = useTheme(); - const isActive = incident.status === true; - const duration = useGetIncidentsDuration(incident, isActive); - const iconWrapperStyle = { - display: "flex", - justifyContent: "center", - mx: theme.spacing(2), - color: theme.palette.primary.contrastTextTertiary, - "& svg": { - width: 18, - height: 18, - }, - "& svg path": { - stroke: "currentColor", - strokeWidth: 1.5, - }, - }; - - return ( - - - - - - - {incident.monitorName || t("incidentsPage.unknownMonitor")} - - - - - - - - - {duration} - - - ); -}; - -IncidentItem.propTypes = { - incident: PropTypes.shape({ - _id: PropTypes.string, - id: PropTypes.string, - monitorId: PropTypes.string, - monitorName: PropTypes.string, - status: PropTypes.bool, - startTime: PropTypes.oneOfType([PropTypes.string, PropTypes.instanceOf(Date)]), - endTime: PropTypes.oneOfType([PropTypes.string, PropTypes.instanceOf(Date)]), - resolutionType: PropTypes.oneOf(["automatic", "manual"]), - }), - onClick: PropTypes.func, -}; - -export default IncidentItem; diff --git a/client/src/Pages/Incidents/Components/LatestIncidentsPanel/index.jsx b/client/src/Pages/Incidents/Components/LatestIncidentsPanel/index.jsx deleted file mode 100644 index ff1ef9417..000000000 --- a/client/src/Pages/Incidents/Components/LatestIncidentsPanel/index.jsx +++ /dev/null @@ -1,114 +0,0 @@ -import PropTypes from "prop-types"; -import { Box, Stack, Typography, Divider } from "@mui/material"; -import { useTheme } from "@emotion/react"; -import { useTranslation } from "react-i18next"; -import PanelSkeleton from "../IncidentsSummaryPanel/skeleton.jsx"; -import IncidentItem from "./IncidentItem.jsx"; -import SummaryCard from "../SummaryCard/index.jsx"; -import Icon from "@/Components/v1/Icon"; - -/** - * LatestIncidentsPanel Component - * - * Displays a quick overview of recent incidents (both active and resolved). - * Shows only essential information: monitor name, status, duration, and resolution type. - * - * @param {Object} props - * @param {Array} props.incidents - Array of recent incidents - * @param {boolean} props.isLoading - Loading state - * @param {Object} props.error - Error object if any - */ -const LatestIncidentsPanel = ({ incidents = [], isLoading = false, error = null }) => { - const theme = useTheme(); - const { t } = useTranslation(); - - if (isLoading) { - return ; - } - - if (error) { - return ( - - - - {t("incidentsPage.incidentsLatestPanelError")} - - - - ); - } - - return ( - - {!incidents || incidents.length === 0 ? ( - - - - - - - {t("incidentsPage.incidentsLatestPanelEmpty")} - - - ) : ( - - {incidents.map((incident, index) => ( - - - {index < incidents.length - 1 && } - - ))} - - )} - - ); -}; - -LatestIncidentsPanel.propTypes = { - incidents: PropTypes.arrayOf( - PropTypes.shape({ - _id: PropTypes.string, - monitorId: PropTypes.string, - monitorName: PropTypes.string, - status: PropTypes.bool, - duration: PropTypes.string, - resolutionType: PropTypes.oneOf(["automatic", "manual"]), - }) - ), - isLoading: PropTypes.bool, - error: PropTypes.object, - onIncidentClick: PropTypes.func, -}; - -export default LatestIncidentsPanel; diff --git a/client/src/Pages/Incidents/Components/OptionsHeader/index.jsx b/client/src/Pages/Incidents/Components/OptionsHeader/index.jsx deleted file mode 100644 index 8d7439576..000000000 --- a/client/src/Pages/Incidents/Components/OptionsHeader/index.jsx +++ /dev/null @@ -1,139 +0,0 @@ -// Components -import { Stack, Typography, Button, ButtonGroup } from "@mui/material"; -import Select from "@/Components/v1/Inputs/Select/index.jsx"; -import PropTypes from "prop-types"; - -//Utils -import { useTheme } from "@emotion/react"; -import { useTranslation } from "react-i18next"; - -const OptionsHeader = ({ - shouldRender, - selectedMonitor = 0, - setSelectedMonitor, - monitors, - filter = "all", - setFilter, - dateRange = "all", - setDateRange, -}) => { - const theme = useTheme(); - const { t } = useTranslation(); - const monitorNames = typeof monitors !== "undefined" ? Object.values(monitors) : []; - - const filterOptions = [ - { _id: "all", name: t("incidentsPage.incidentsOptionsHeaderFilterAll") }, - { _id: "active", name: t("incidentsPage.incidentsOptionsHeaderFilterActive") }, - { _id: "resolved", name: t("incidentsPage.incidentsOptionsHeaderFilterResolved") }, - { _id: "manual", name: t("incidentsPage.incidentsOptionsHeaderFilterManual") }, - { _id: "automatic", name: t("incidentsPage.incidentsOptionsHeaderFilterAutomatic") }, - ]; - - const stackStyles = { - direction: "row", - alignItems: "center", - gap: theme.spacing(6), - }; - - if (!shouldRender) return; - - return ( - - - - {t("incidentsOptionsHeader")} - - setFilter(e.target.value)} - items={filterOptions} - sx={{ - backgroundColor: theme.palette.primary.main, - color: theme.palette.primary.contrastTextSecondary, - }} - /> - - - - {t("incidentsOptionsHeaderShow")} - - - - - - - - - - ); -}; - -OptionsHeader.propTypes = { - shouldRender: PropTypes.bool, - selectedMonitor: PropTypes.string, - setSelectedMonitor: PropTypes.func, - monitors: PropTypes.object, - filter: PropTypes.string, - setFilter: PropTypes.func, - dateRange: PropTypes.string, - setDateRange: PropTypes.func, -}; - -export default OptionsHeader; diff --git a/client/src/Pages/Incidents/Components/ResolveIncidentDialog/index.jsx b/client/src/Pages/Incidents/Components/ResolveIncidentDialog/index.jsx deleted file mode 100644 index 3e868b2dc..000000000 --- a/client/src/Pages/Incidents/Components/ResolveIncidentDialog/index.jsx +++ /dev/null @@ -1,101 +0,0 @@ -import { useEffect, useState } from "react"; -import PropTypes from "prop-types"; -import { Button, Stack } from "@mui/material"; -import { useTranslation } from "react-i18next"; -import { useTheme } from "@emotion/react"; -import { GenericDialog } from "@/Components/v1/Dialog/genericDialog.jsx"; -import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx"; - -const ResolveIncidentDialog = ({ - open, - incidentId, - onClose, - onResolve, - onAfterResolve, - onResolved, - isLoading = false, -}) => { - const { t } = useTranslation(); - const theme = useTheme(); - const [comment, setComment] = useState(""); - const [isSubmitting, setIsSubmitting] = useState(false); - - useEffect(() => { - if (open) { - setComment(""); - } - }, [open]); - - const handleConfirm = async () => { - if (!incidentId) return; - const trimmed = comment.trim(); - try { - setIsSubmitting(true); - await onResolve(incidentId, trimmed ? { comment: trimmed } : {}); - if (onAfterResolve) { - await onAfterResolve(); - } - onResolved?.(); - onClose(); - } catch (error) { - console.error("Error resolving incident:", error); - } finally { - setIsSubmitting(false); - } - }; - - return ( - - - setComment(e.target.value)} - maxWidth="100%" - /> - - - - - - - ); -}; - -ResolveIncidentDialog.propTypes = { - open: PropTypes.bool.isRequired, - incidentId: PropTypes.string, - onClose: PropTypes.func.isRequired, - onResolve: PropTypes.func.isRequired, - onAfterResolve: PropTypes.func, - isLoading: PropTypes.bool, - onResolved: PropTypes.func, -}; - -export default ResolveIncidentDialog; diff --git a/client/src/Pages/Incidents/Components/StatisticsPanel/index.jsx b/client/src/Pages/Incidents/Components/StatisticsPanel/index.jsx deleted file mode 100644 index 7f07f688f..000000000 --- a/client/src/Pages/Incidents/Components/StatisticsPanel/index.jsx +++ /dev/null @@ -1,168 +0,0 @@ -import PropTypes from "prop-types"; -import { Box, Stack, Typography, Divider } from "@mui/material"; -import { useTheme } from "@emotion/react"; -import PanelSkeleton from "../IncidentsSummaryPanel/skeleton.jsx"; -import { useTranslation } from "react-i18next"; -import Icon from "@/Components/v1/Icon"; -import SummaryCard from "../SummaryCard/index.jsx"; - -/** - * StatisticsPanel Component - * - * Displays key metrics and statistics about incidents. - * - * @param {Object} props - * @param {Object} props.statistics - Statistics data object - * @param {number} props.statistics.total - Total incidents count - * @param {number} props.statistics.totalTrend - Trend percentage for total (optional) - * @param {string} props.statistics.mttr - Mean Time To Resolve (formatted string, e.g., "2h 15m") - * @param {number} props.statistics.mttrTrend - Trend percentage for MTTR (optional) - * @param {Object} props.statistics.topMonitor - Top monitor with most incidents - * @param {string} props.statistics.topMonitor.name - Monitor name - * @param {number} props.statistics.topMonitor.count - Incident count - * @param {number} props.statistics.topMonitor.percentage - Percentage of total - * @param {string} props.statistics.uptimeAffected - Total downtime (formatted string, e.g., "12h 45m") - * @param {number} props.statistics.availability - Availability percentage - * @param {boolean} props.isLoading - Loading state - * @param {Object} props.error - Error object if any - */ -const StatisticsPanel = ({ isLoading = false, error = null, summary = {} }) => { - const theme = useTheme(); - const { t } = useTranslation(); - const totalResolutions = - (summary?.totalManualResolutions || 0) + (summary?.totalAutomaticResolutions || 0); - - const iconWrapperStyle = { - display: "flex", - justifyContent: "center", - mx: theme.spacing(2), - color: theme.palette.primary.contrastTextTertiary, - "& svg": { - width: 18, - height: 18, - }, - "& svg path": { - stroke: "currentColor", - strokeWidth: 1.5, - }, - }; - if (isLoading) { - return ; - } - - if (error) { - return ( - - - - {t("incidentsPage.incidentsStatisticsPanelError")} - - - - ); - } - - const getMostAffectedMonitor = () => { - if (!summary.total || summary.total === 0) { - return t("incidentsPage.none"); - } - return summary.topMonitor?.monitorName || t("incidentsPage.none"); - }; - - const rowStyle = { - py: theme.spacing(0.5), - minHeight: 32, - }; - - return ( - - - - - - - - - {t("incidentsPage.totalIncidents")}: {summary.total || 0} - - - - - - - - - - - {t("incidentsPage.mostAffectedMonitor")}: {getMostAffectedMonitor()} - - - - - - - - - - {t("incidentsPage.avgResolutionTime")}:{" "} - {summary.total > 0 - ? `${summary.avgResolutionTimeHours || 0} ${t("incidentsPage.hours")}` - : "N/A"} - - - - - ); -}; - -StatisticsPanel.propTypes = { - summary: PropTypes.object, - isLoading: PropTypes.bool, - error: PropTypes.object, -}; - -export default StatisticsPanel; diff --git a/client/src/Pages/Incidents/Components/SummaryCard/index.jsx b/client/src/Pages/Incidents/Components/SummaryCard/index.jsx deleted file mode 100644 index 3a8589dc1..000000000 --- a/client/src/Pages/Incidents/Components/SummaryCard/index.jsx +++ /dev/null @@ -1,64 +0,0 @@ -import { Paper } from "@mui/material"; -import { useTheme } from "@mui/material/styles"; -import PropTypes from "prop-types"; -import { Box, Stack, Typography, Divider } from "@mui/material"; -const SummaryCard = ({ children, isHighPriority = false, sx = {}, title = null }) => { - const theme = useTheme(); - return ( - - {title && ( - - - - {title} - - - - - )} - - {children} - - - ); -}; -SummaryCard.propTypes = { - children: PropTypes.node, - isHighPriority: PropTypes.bool, - sx: PropTypes.object, - title: PropTypes.string, -}; -export default SummaryCard; diff --git a/client/src/Pages/Incidents/hooks/useFetchIncidents.js b/client/src/Pages/Incidents/hooks/useFetchIncidents.js deleted file mode 100644 index e308c9319..000000000 --- a/client/src/Pages/Incidents/hooks/useFetchIncidents.js +++ /dev/null @@ -1,215 +0,0 @@ -import { useState, useCallback } from "react"; -import { networkService } from "../../../main.jsx"; -import { createToast } from "../../../Utils/toastUtils.jsx"; -import { useTranslation } from "react-i18next"; -/** - * Hook to fetch and manage incidents - * - * Provides multiple functions to fetch incidents with different filters. - * - * @returns {Object} { - * incidents, - * incidentsCount, - * isLoading, - * networkError, - * fetchIncidents, - * fetchActiveIncidents, - * fetchResolvedIncidents, - * fetchIncidentsByTeam, - * fetchIncidentById, - * resolveIncident, - * fetchIncidentSummary - * } - */ -const useFetchIncidents = () => { - const [incidents, setIncidents] = useState(null); - const [incidentsCount, setIncidentsCount] = useState(null); - const [isLoading, setIsLoading] = useState(false); - const [networkError, setNetworkError] = useState(false); - const { t } = useTranslation(); - /** - * Generic function to fetch incidents by team with custom config - * - * @param {Object} config - Configuration object - * @param {string} [config.monitorId] - Filter by monitor ID - * @param {boolean} [config.status] - Filter by status (true=active, false=resolved) - * @param {string} [config.resolutionType] - Filter by resolution type (automatic/manual) - * @param {string} [config.sortOrder] - Sort order (asc/desc) - * @param {string} [config.dateRange] - Date range filter - * @param {number} [config.page] - Page number - * @param {number} [config.rowsPerPage] - Rows per page - */ - const fetchIncidentsByTeam = useCallback(async (config = {}) => { - try { - setIsLoading(true); - setNetworkError(false); - - const res = await networkService.getIncidentsByTeam(config); - setIncidents(res.data?.data?.incidents || []); - setIncidentsCount(res.data?.data?.count || 0); - } catch (error) { - setNetworkError(true); - console.error(t("incidentsPage.errorFetchingIncidents"), error); - createToast({ body: error.message || t("incidentsPage.errorFetchingIncidents") }); - } finally { - setIsLoading(false); - } - }, []); - - /** - * Fetch active (open) incidents - Default function - * - * @param {Object} config - Additional configuration - */ - const fetchActiveIncidents = useCallback( - async (config = {}) => { - await fetchIncidentsByTeam({ - status: true, - sortOrder: "desc", - ...config, - }); - }, - [fetchIncidentsByTeam] - ); - - const fetchIncidentsByResolutionType = useCallback( - async (resolutionType, config = {}) => { - await fetchIncidentsByTeam({ - resolutionType, - sortOrder: "desc", - ...config, - }); - }, - [fetchIncidentsByTeam] - ); - /** - * Fetch resolved incidents - * - * @param {Object} config - Additional configuration - */ - const fetchResolvedIncidents = useCallback( - async (config = {}) => { - await fetchIncidentsByTeam({ - status: false, - sortOrder: "desc", - ...config, - }); - }, - [fetchIncidentsByTeam] - ); - - /** - * Fetch incidents with custom filters (alias for fetchIncidentsByTeam) - * - * @param {Object} config - Configuration object - */ - const fetchIncidents = useCallback( - async (config = {}) => { - await fetchIncidentsByTeam(config); - }, - [fetchIncidentsByTeam] - ); - - /** - * Fetch a single incident by ID - * - * @param {string} incidentId - The ID of the incident to fetch - * @returns {Promise} The incident object or null if not found - */ - const fetchIncidentById = useCallback(async (incidentId) => { - if (!incidentId) { - console.error(t("incidentsPage.noIncidentIdProvided")); - return null; - } - - try { - setIsLoading(true); - setNetworkError(false); - - const res = await networkService.getIncidentById(incidentId); - return res.data?.data || null; - } catch (error) { - setNetworkError(true); - console.error(t("incidentsPage.errorFetchingIncident"), error); - createToast({ body: error.message || t("incidentsPage.errorFetchingIncident") }); - return null; - } finally { - setIsLoading(false); - } - }, []); - - /** - * Resolve an incident manually - * - * @param {string} incidentId - The ID of the incident to resolve - * @param {Object} options - Resolution options - * @param {string} [options.comment] - Optional comment about the resolution - * @param {Function} [onSuccess] - Callback function to call on success - * @param {Function} [onError] - Callback function to call on error - */ - const resolveIncident = useCallback(async (incidentId, options = {}) => { - if (!incidentId) { - console.error(t("incidentsPage.noIncidentIdProvided")); - return; - } - - try { - setIsLoading(true); - setNetworkError(false); - - const res = await networkService.resolveIncidentManually(incidentId, options); - if (res.data?.success) { - createToast({ body: t("incidentsPage.incidentResolvedSuccessfully") }); - } else { - createToast({ body: t("incidentsPage.errorResolvingIncident") }); - } - } catch (error) { - setNetworkError(true); - console.error(t("incidentsPage.errorResolvingIncident"), error); - const errorMessage = - error.response?.data?.message || - error.message || - t("incidentsPage.errorResolvingIncident"); - createToast({ body: errorMessage }); - } finally { - setIsLoading(false); - } - }, []); - - /** - * Fetch incident summary - * - * @param {Object} config - Configuration object - * @param {number} [config.limit=10] - Number of latest incidents to return - * @returns {Promise} The summary object or null if error - */ - const fetchIncidentSummary = useCallback(async (config = {}) => { - try { - const res = await networkService.getIncidentSummary(config); - return res.data?.data || null; - } catch (error) { - console.error(t("incidentsPage.errorFetchingIncidentSummary"), error); - createToast({ - body: error.message || t("incidentsPage.errorFetchingIncidentSummary"), - }); - return null; - } - }, []); - - return { - incidents, - incidentsCount, - isLoading, - networkError, - fetchIncidents, - fetchActiveIncidents, - fetchResolvedIncidents, - fetchIncidentsByTeam, - fetchIncidentById, - resolveIncident, - fetchIncidentSummary, - fetchIncidentsByResolutionType, - }; -}; - -export default useFetchIncidents; diff --git a/client/src/Pages/Incidents/hooks/useGetIncidentsDuration.js b/client/src/Pages/Incidents/hooks/useGetIncidentsDuration.js deleted file mode 100644 index a4f434a80..000000000 --- a/client/src/Pages/Incidents/hooks/useGetIncidentsDuration.js +++ /dev/null @@ -1,25 +0,0 @@ -import { getHumanReadableDuration } from "@/Utils/timeUtilsLegacy.js"; -const useGetIncidentsDuration = (incident, isActive) => { - if (!incident?.startTime) { - return "-"; - } - const startTime = new Date(incident?.startTime); - const endTime = isActive - ? new Date() - : incident?.endTime - ? new Date(incident.endTime) - : null; - - if (!endTime) { - return "-"; - } - - const durationMs = endTime.getTime() - startTime.getTime(); - - if (durationMs < 0) { - return "-"; - } - - return getHumanReadableDuration(durationMs); -}; -export default useGetIncidentsDuration; diff --git a/client/src/Pages/Incidents/old.jsx b/client/src/Pages/Incidents/old.jsx deleted file mode 100644 index 0377405b7..000000000 --- a/client/src/Pages/Incidents/old.jsx +++ /dev/null @@ -1,178 +0,0 @@ -// Components -import { Stack } from "@mui/material"; -import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; -import GenericFallback from "@/Components/v1/GenericFallback/index.jsx"; -import IncidentTable from "./Components/IncidentTable/index.jsx"; -import OptionsHeader from "./Components/OptionsHeader/index.jsx"; -import IncidentsSummaryPanel from "./Components/IncidentsSummaryPanel/index.jsx"; -import IncidentDetailsModal from "./Components/IncidentDetailsModal/index.jsx"; -//Utils -import { useTheme } from "@emotion/react"; -import { useState, useEffect } from "react"; -import NetworkError from "@/Components/v1/GenericFallback/NetworkError.jsx"; -import { useTranslation } from "react-i18next"; -import { useParams } from "react-router-dom"; - -// Hooks -import useFetchIncidents from "./hooks/useFetchIncidents.js"; -import { useFetchMonitorsByTeamId } from "../../Hooks/monitorHooks.js"; - -const Incidents2 = () => { - const { monitorId } = useParams(); - const { t } = useTranslation(); - - const BREADCRUMBS = [ - { name: t("incidentsPageTitle", "Incidents"), path: "/incidents" }, - ]; - - const [selectedMonitor, setSelectedMonitor] = useState(monitorId || "0"); - const [filter, setFilter] = useState("all"); - const [dateRange, setDateRange] = useState("all"); - const [page, setPage] = useState(0); - const [rowsPerPage, setRowsPerPage] = useState(10); - const [monitorLookup, setMonitorLookup] = useState(undefined); - const [updateTrigger, setUpdateTrigger] = useState(false); - const [isDetailsOpen, setIsDetailsOpen] = useState(false); - const [selectedIncidentId, setSelectedIncidentId] = useState(null); - const handleUpdateTrigger = () => { - setUpdateTrigger((prev) => !prev); - }; - - const [monitors, isLoadingMonitors, monitorsNetworkError] = useFetchMonitorsByTeamId( - {} - ); - - const { - incidents, - incidentsCount, - isLoading: isLoadingIncidents, - networkError: incidentsNetworkError, - fetchIncidents, - fetchActiveIncidents, - fetchResolvedIncidents, - resolveIncident, - fetchIncidentsByResolutionType, - } = useFetchIncidents(); - - const networkError = monitorsNetworkError || incidentsNetworkError; - - const theme = useTheme(); - - useEffect(() => { - setPage(0); - }, [selectedMonitor, filter, dateRange]); - - useEffect(() => { - const config = { - monitorId: selectedMonitor !== "0" ? selectedMonitor : undefined, - sortOrder: "desc", - dateRange, - page, - rowsPerPage, - }; - - if (filter === "active") { - fetchActiveIncidents(config); - } else if (filter === "resolved") { - fetchResolvedIncidents(config); - } else if (filter === "manual") { - fetchIncidentsByResolutionType("manual", config); - } else if (filter === "automatic") { - fetchIncidentsByResolutionType("automatic", config); - } else { - fetchIncidents(config); - } - }, [ - selectedMonitor, - filter, - dateRange, - page, - rowsPerPage, - updateTrigger, - fetchActiveIncidents, - fetchResolvedIncidents, - fetchIncidents, - fetchIncidentsByResolutionType, - ]); - - useEffect(() => { - const lookup = monitors?.reduce((acc, monitor) => { - acc[monitor.id] = { - id: monitor.id, - name: monitor.name, - type: monitor.type, - }; - return acc; - }, {}); - setMonitorLookup(lookup); - }, [monitors]); - - if (networkError) { - return ( - - - - ); - } - - const handleChangePage = (_, newPage) => { - setPage(newPage); - }; - - const handleChangeRowsPerPage = (event) => { - setRowsPerPage(parseInt(event.target.value, 10)); - setPage(0); - }; - const handleOpenDetails = (id) => { - if (!id) return; - setSelectedIncidentId(id); - setIsDetailsOpen(true); - }; - - const handleCloseDetails = () => { - setIsDetailsOpen(false); - setSelectedIncidentId(null); - }; - - return ( - - - - - - - - - - - ); -}; - -export default Incidents2; diff --git a/client/src/locales/en.json b/client/src/locales/en.json index b64d2b3bb..da604b395 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -8,7 +8,8 @@ "labels": { "responseTime": "Response time", "active": "Active", - "resolved": "Resolved" + "resolved": "Resolved", + "na": "N/A" }, "buttons": { "toggleTheme": "Toggles light & dark", @@ -406,7 +407,8 @@ }, "status": { "active": "Active", - "resolved": "Resolved" + "resolved": "Resolved", + "na": "N/A" }, "actions": { "details": "Details", @@ -452,20 +454,6 @@ } } }, - "incidentsTableNoIncidents": "No incidents recorded", - "incidentsTablePaginationLabel": "incidents", - "incidentsTableMonitorName": "Monitor Name", - "incidentsTableStatus": "Status", - "incidentsTableStatusCode": "Status Code", - "incidentsTableMessage": "Message", - "incidentsOptionsHeader": "Incidents for:", - "incidentsOptionsHeaderFilterBy": "Filter by:", - "incidentsOptionsHeaderFilterAll": "All", - "incidentsOptionsHeaderShow": "Show:", - "incidentsOptionsHeaderLastHour": "Last hour", - "incidentsOptionsHeaderLastDay": "Last day", - "incidentsOptionsHeaderLastWeek": "Last week", - "incidentsOptionsPlaceholderAllServers": "All servers", "used": "Used", "total": "Total", "status": "Status", @@ -648,7 +636,6 @@ "statusBreadCrumbsDetails": "Details", "commonSaving": "Saving...", "navControls": "Controls", - "incidentsPageTitle": "Incidents", "passwordPanel": { "currentPassword": "Current password", "newPassword": "New password", @@ -1002,61 +989,9 @@ }, "chooseGame": "Choose game", "incidentsPage": { - "resolved": "Resolved", - "active": "Active", - "startTime": "Start Time", - "endTime": "End Time", - "resolutionType": "Resolution Type", - "incidentsActivePanelError": "Error loading active incidents", - "allSystemsAreOperational": "All Systems Are Operational", - "incidentsActivePanelTitle": "Total Active Incidents", - "incidentsLatestPanelError": "Error loading latest incidents", - "incidentsLatestPanelTitle": "Latest Incidents", - "incidentsLatestPanelEmpty": "No recent incidents", - "incidentItemMonitor": "Monitor", - "unknownMonitor": "Unknown Monitor", - "incidentsStatisticsPanelError": "Error loading statistics", - "incidentsStatisticsPanelTitle": "General Statistics", - "totalIncidents": "Total Incidents", - "mostAffectedMonitor": "Most Affected Monitor", - "avgResolutionTime": "Avg. Resolution Time", - "incidentResolvedSuccessfully": "Incident resolved successfully", - "errorFetchingIncidents": "Error fetching incidents", - "errorFetchingIncident": "Error fetching incident", - "noIncidentIdProvided": "No incident ID provided", - "errorFetchingIncidentSummary": "Error fetching incident summary", - "incidentsOptionsHeaderFilterManual": "Manual", - "incidentsOptionsHeaderFilterAutomatic": "Automatic", - "incidentsOptionsHeaderFilterAll": "All", - "incidentsOptionsHeaderFilterActive": "Active", - "incidentsOptionsHeaderFilterResolved": "Resolved", - "incidentsTableActionResolveManually": "Resolve Manually", - "incidentsTableActionDetails": "Details", - "incidentsTableActionGoToMonitor": "Go to monitor", "resolveIncidentDialogTitle": "Resolve Incident", - "resolveIncidentDialogDescription": "Are you sure you want to resolve this incident manually?", "resolveIncidentDialogCommentLabel": "Comment (optional)", "resolveIncidentDialogCommentPlaceholder": "Add a comment about the resolution...", - "resolveIncidentDialogConfirm": "Resolve", - "hours": "hours", - "incidentNotFound": "Incident not found", - "incidentDetails": "INCIDENT DETAILS", - "overview": "OVERVIEW", - "URL": "URL", - "incidentAnalysis": "INCIDENT ANALYSIS", - "timeline": "Timeline", - "startedAt": "Started at", - "endedAt": "Ended at", - "downtime": "Downtime", - "errorDetails": "Error details", - "resolutionDetails": "Resolution details", - "statusCode": "Status code", - "resolutionMethod": "Resolution method", - "resolvedBy": "Resolved by", - "comment": "Comment", - "noCommentProvided": "No comment provided", - "loadingIncidentDetails": "Loading incident details...", - "none": "None", - "message": "Message" + "resolveIncidentDialogConfirm": "Resolve" } }