Refactor and enhance various components and routes in the Dashboard and MobileApp

- Improved formatting and readability in Kubernetes PodDetail and SideMenu components.
- Added service count fetching and loading/error handling in Logs, Metrics, and Traces pages.
- Updated Exception and Kubernetes routes for better readability.
- Enhanced Postgres migration scripts for KubernetesCluster and KubernetesClusterLabel.
- Minor formatting adjustments in MarkdownViewer and CriticalPath utility.
- Refactored EmptyState, MonitorSummaryView, and hooks in MobileApp for improved clarity.
- Fixed minor issues in MonitorDetailScreen and MonitorsScreen regarding status display.
This commit is contained in:
Nawaz Dhandala
2026-03-18 14:21:39 +00:00
parent 65a4132081
commit 305fa4a476
28 changed files with 581 additions and 361 deletions

View File

@@ -149,10 +149,9 @@ const KubernetesDocumentationCard: FunctionComponent<ComponentProps> = (
)}
value={
ingestionKeys
.filter(
(key: TelemetryIngestionKey) =>
key.id?.toString() === selectedKeyId,
)
.filter((key: TelemetryIngestionKey) => {
return key.id?.toString() === selectedKeyId;
})
.map((key: TelemetryIngestionKey): DropdownOption => {
return {
value: key.id?.toString() || "",

View File

@@ -17,7 +17,9 @@ import MetricsAggregationType from "Common/Types/Metrics/MetricsAggregationType"
export interface ComponentProps {
serviceIds?: Array<ObjectID> | undefined;
onFetchSuccess?: ((data: Array<MetricType>, totalCount: number) => void) | undefined;
onFetchSuccess?:
| ((data: Array<MetricType>, totalCount: number) => void)
| undefined;
}
const MetricsTable: FunctionComponent<ComponentProps> = (

View File

@@ -622,8 +622,7 @@ const SpanViewer: FunctionComponent<ComponentProps> = (
</code>
</div>
</div>
{link.attributes &&
Object.keys(link.attributes).length > 0 ? (
{link.attributes && Object.keys(link.attributes).length > 0 ? (
<div>
<div className="text-xs text-gray-500 font-medium mb-1">
Attributes

View File

@@ -816,42 +816,39 @@ const TelemetryDocumentation: FunctionComponent<ComponentProps> = (
loadIngestionKeys().catch(() => {});
}, []);
const loadIngestionKeys: () => Promise<void> =
async (): Promise<void> => {
try {
setIsLoadingKeys(true);
setKeyError("");
const result: ListResult<TelemetryIngestionKey> =
await ModelAPI.getList<TelemetryIngestionKey>({
modelType: TelemetryIngestionKey,
query: {
projectId: ProjectUtil.getCurrentProjectId()!,
},
limit: 50,
skip: 0,
select: {
_id: true,
name: true,
secretKey: true,
description: true,
},
sort: {},
});
const loadIngestionKeys: () => Promise<void> = async (): Promise<void> => {
try {
setIsLoadingKeys(true);
setKeyError("");
const result: ListResult<TelemetryIngestionKey> =
await ModelAPI.getList<TelemetryIngestionKey>({
modelType: TelemetryIngestionKey,
query: {
projectId: ProjectUtil.getCurrentProjectId()!,
},
limit: 50,
skip: 0,
select: {
_id: true,
name: true,
secretKey: true,
description: true,
},
sort: {},
});
setIngestionKeys(result.data);
setIngestionKeys(result.data);
// Auto-select the first key if available and none selected
if (result.data.length > 0 && !selectedKeyId) {
setSelectedKeyId(
result.data[0]!.id?.toString() || "",
);
}
} catch (err) {
setKeyError(API.getFriendlyErrorMessage(err as Error));
} finally {
setIsLoadingKeys(false);
// Auto-select the first key if available and none selected
if (result.data.length > 0 && !selectedKeyId) {
setSelectedKeyId(result.data[0]!.id?.toString() || "");
}
};
} catch (err) {
setKeyError(API.getFriendlyErrorMessage(err as Error));
} finally {
setIsLoadingKeys(false);
}
};
// Get the selected key object
const selectedKey: TelemetryIngestionKey | undefined = useMemo(() => {
@@ -944,9 +941,7 @@ const TelemetryDocumentation: FunctionComponent<ComponentProps> = (
<div className="w-9 h-9 rounded-full bg-indigo-50 border-2 border-indigo-500 text-indigo-600 flex items-center justify-center text-sm font-bold z-10">
{stepNumber}
</div>
{!isLast && (
<div className="w-0.5 flex-1 bg-gray-200 mt-2 mb-0" />
)}
{!isLast && <div className="w-0.5 flex-1 bg-gray-200 mt-2 mb-0" />}
</div>
{/* Step content */}
<div className={`flex-1 min-w-0 ${isLast ? "pb-0" : "pb-8"}`}>
@@ -1023,15 +1018,19 @@ const TelemetryDocumentation: FunctionComponent<ComponentProps> = (
<div className="flex items-center gap-2 mb-3">
<div className="flex-1">
<Dropdown
options={ingestionKeys.map((key: TelemetryIngestionKey): DropdownOption => {
return {
value: key.id?.toString() || "",
label: key.name || "Unnamed Key",
};
})}
options={ingestionKeys.map(
(key: TelemetryIngestionKey): DropdownOption => {
return {
value: key.id?.toString() || "",
label: key.name || "Unnamed Key",
};
},
)}
value={
ingestionKeys
.filter((key: TelemetryIngestionKey) => key.id?.toString() === selectedKeyId)
.filter((key: TelemetryIngestionKey) => {
return key.id?.toString() === selectedKeyId;
})
.map((key: TelemetryIngestionKey): DropdownOption => {
return {
value: key.id?.toString() || "",
@@ -1039,7 +1038,9 @@ const TelemetryDocumentation: FunctionComponent<ComponentProps> = (
};
})[0]
}
onChange={(value: DropdownValue | Array<DropdownValue> | null) => {
onChange={(
value: DropdownValue | Array<DropdownValue> | null,
) => {
if (value) {
setSelectedKeyId(value.toString());
}
@@ -1195,7 +1196,11 @@ const TelemetryDocumentation: FunctionComponent<ComponentProps> = (
{renderStep(
2,
"Install Dependencies",
`Install the OpenTelemetry SDK and exporters for ${languages.find((l: LanguageOption) => { return l.key === selectedLanguage; })?.label || selectedLanguage}.`,
`Install the OpenTelemetry SDK and exporters for ${
languages.find((l: LanguageOption) => {
return l.key === selectedLanguage;
})?.label || selectedLanguage
}.`,
<CodeBlock
code={installSnippet.code}
language={installSnippet.language}
@@ -1283,10 +1288,7 @@ const TelemetryDocumentation: FunctionComponent<ComponentProps> = (
4,
"Run FluentBit",
"Start FluentBit with your configuration file.",
<CodeBlock
code="fluent-bit -c fluent-bit.conf"
language="bash"
/>,
<CodeBlock code="fluent-bit -c fluent-bit.conf" language="bash" />,
true,
)}
</div>
@@ -1340,10 +1342,7 @@ const TelemetryDocumentation: FunctionComponent<ComponentProps> = (
4,
"Run Fluentd",
"Start Fluentd with your configuration.",
<CodeBlock
code="fluentd -c fluentd.conf"
language="bash"
/>,
<CodeBlock code="fluentd -c fluentd.conf" language="bash" />,
true,
)}
</div>

View File

@@ -37,7 +37,8 @@ const FlameGraph: FunctionComponent<FlameGraphProps> = (
const [hoveredSpanId, setHoveredSpanId] = React.useState<string | null>(null);
const [focusedSpanId, setFocusedSpanId] = React.useState<string | null>(null);
const containerRef: React.RefObject<HTMLDivElement | null> = React.useRef<HTMLDivElement>(null);
const containerRef: React.RefObject<HTMLDivElement | null> =
React.useRef<HTMLDivElement>(null);
// Build span data for critical path utility
const spanDataList: SpanData[] = React.useMemo(() => {
@@ -90,9 +91,7 @@ const FlameGraph: FunctionComponent<FlameGraphProps> = (
}
}
const getServiceInfo = (
span: Span,
): { color: Color; name: string } => {
const getServiceInfo = (span: Span): { color: Color; name: string } => {
const service: Service | undefined = telemetryServices.find(
(s: Service) => {
return s._id?.toString() === span.serviceId?.toString();
@@ -123,7 +122,9 @@ const FlameGraph: FunctionComponent<FlameGraphProps> = (
startTimeUnixNano: span.startTimeUnixNano!,
endTimeUnixNano: span.endTimeUnixNano!,
durationUnixNano: span.durationUnixNano!,
selfTimeUnixNano: selfTime ? selfTime.selfTimeUnixNano : span.durationUnixNano!,
selfTimeUnixNano: selfTime
? selfTime.selfTimeUnixNano
: span.durationUnixNano!,
serviceColor: serviceInfo.color,
serviceName: serviceInfo.name,
};
@@ -220,13 +221,9 @@ const FlameGraph: FunctionComponent<FlameGraphProps> = (
}
const leftPercent: number =
totalDuration > 0
? ((nodeStart - viewStart) / totalDuration) * 100
: 0;
totalDuration > 0 ? ((nodeStart - viewStart) / totalDuration) * 100 : 0;
const widthPercent: number =
totalDuration > 0
? ((nodeEnd - nodeStart) / totalDuration) * 100
: 0;
totalDuration > 0 ? ((nodeEnd - nodeStart) / totalDuration) * 100 : 0;
const isHovered: boolean = hoveredSpanId === node.span.spanId;
const isSelected: boolean = selectedSpanId === node.span.spanId;

View File

@@ -712,7 +712,13 @@ const TraceExplorer: FunctionComponent<ComponentProps> = (
});
}
return filtered;
}, [spans, showErrorsOnly, selectedServiceIds, spanSearchText, telemetryServices]);
}, [
spans,
showErrorsOnly,
selectedServiceIds,
spanSearchText,
telemetryServices,
]);
// Search match count for display
const searchMatchCount: number = React.useMemo(() => {
@@ -1288,11 +1294,9 @@ const TraceExplorer: FunctionComponent<ComponentProps> = (
<button
type="button"
onClick={() => {
setShowCriticalPath(
(prev: boolean) => {
return !prev;
},
);
setShowCriticalPath((prev: boolean) => {
return !prev;
});
}}
className={`text-xs font-medium px-3 py-1.5 rounded-md border transition-all flex items-center space-x-1 ${
showCriticalPath
@@ -1428,17 +1432,14 @@ const TraceExplorer: FunctionComponent<ComponentProps> = (
</div>
<div className="space-y-1.5">
{serviceBreakdown.map((breakdown: ServiceBreakdown) => {
const service: Service | undefined =
telemetryServices.find((s: Service) => {
return (
s._id?.toString() === breakdown.serviceId
);
});
const serviceName: string =
service?.name || "Unknown";
const service: Service | undefined = telemetryServices.find(
(s: Service) => {
return s._id?.toString() === breakdown.serviceId;
},
);
const serviceName: string = service?.name || "Unknown";
const serviceColor: string = String(
(service?.serviceColor as unknown as string) ||
"#6366f1",
(service?.serviceColor as unknown as string) || "#6366f1",
);
const percent: number = Math.min(
breakdown.percentOfTrace,
@@ -1446,7 +1447,10 @@ const TraceExplorer: FunctionComponent<ComponentProps> = (
);
return (
<div key={breakdown.serviceId} className="flex items-center space-x-2">
<div
key={breakdown.serviceId}
className="flex items-center space-x-2"
>
<span
className="h-2.5 w-2.5 rounded-sm ring-1 ring-black/10 flex-shrink-0"
style={{
@@ -1468,8 +1472,7 @@ const TraceExplorer: FunctionComponent<ComponentProps> = (
</div>
<span className="text-[10px] text-gray-500 w-20 text-right">
{SpanUtil.getSpanDurationAsString({
spanDurationInUnixNano:
breakdown.selfTimeUnixNano,
spanDurationInUnixNano: breakdown.selfTimeUnixNano,
divisibilityFactor: divisibilityFactor,
})}{" "}
({percent.toFixed(1)}%)
@@ -1526,9 +1529,7 @@ const TraceExplorer: FunctionComponent<ComponentProps> = (
setSelectedSpans([spanId]);
}}
selectedSpanId={
selectedSpans.length > 0
? selectedSpans[0]
: undefined
selectedSpans.length > 0 ? selectedSpans[0] : undefined
}
/>
</div>

View File

@@ -135,8 +135,10 @@ const TraceServiceMap: FunctionComponent<TraceServiceMapProps> = (
);
}
// Layout: arrange nodes in a topological order based on edges
// Simple layout: find entry nodes and lay out left-to-right
/*
* Layout: arrange nodes in a topological order based on edges
* Simple layout: find entry nodes and lay out left-to-right
*/
const { nodePositions, layoutWidth, layoutHeight } = React.useMemo(() => {
// Build adjacency list
const adjList: Map<string, string[]> = new Map();
@@ -151,10 +153,7 @@ const TraceServiceMap: FunctionComponent<TraceServiceMapProps> = (
const neighbors: string[] = adjList.get(edge.fromServiceId) || [];
neighbors.push(edge.toServiceId);
adjList.set(edge.fromServiceId, neighbors);
inDegree.set(
edge.toServiceId,
(inDegree.get(edge.toServiceId) || 0) + 1,
);
inDegree.set(edge.toServiceId, (inDegree.get(edge.toServiceId) || 0) + 1);
}
// Topological sort using BFS (Kahn's algorithm)
@@ -333,18 +332,16 @@ const TraceServiceMap: FunctionComponent<TraceServiceMapProps> = (
refY="3"
orient="auto"
>
<polygon
points="0 0, 8 3, 0 6"
fill="#9ca3af"
/>
<polygon points="0 0, 8 3, 0 6" fill="#9ca3af" />
</marker>
</defs>
</svg>
{/* Render nodes */}
{nodes.map((node: ServiceNode) => {
const pos: { x: number; y: number } | undefined =
nodePositions.get(node.serviceId);
const pos: { x: number; y: number } | undefined = nodePositions.get(
node.serviceId,
);
if (!pos) {
return null;
}
@@ -355,9 +352,7 @@ const TraceServiceMap: FunctionComponent<TraceServiceMapProps> = (
<div
key={node.serviceId}
className={`absolute rounded-lg border-2 bg-white shadow-sm p-3 ${
hasErrors
? "border-red-300"
: "border-gray-200"
hasErrors ? "border-red-300" : "border-gray-200"
}`}
style={{
left: `${pos.x}px`,

View File

@@ -44,7 +44,9 @@ export interface ComponentProps {
spanQuery?: Query<Span> | undefined;
isMinimalTable?: boolean | undefined;
noItemsMessage?: string | undefined;
onFetchSuccess?: ((data: Array<Span>, totalCount: number) => void) | undefined;
onFetchSuccess?:
| ((data: Array<Span>, totalCount: number) => void)
| undefined;
}
const TraceTable: FunctionComponent<ComponentProps> = (
@@ -312,12 +314,9 @@ const TraceTable: FunctionComponent<ComponentProps> = (
);
}
return Promise.resolve(
RouteUtil.populateRouteParams(
RouteMap[PageMap.TRACE_VIEW]!,
{
modelId: span.traceId!.toString(),
},
),
RouteUtil.populateRouteParams(RouteMap[PageMap.TRACE_VIEW]!, {
modelId: span.traceId!.toString(),
}),
);
}}
filters={[

View File

@@ -4,11 +4,15 @@ import TelemetryDocumentation from "../../Components/Telemetry/Documentation";
import React, {
FunctionComponent,
ReactElement,
useCallback,
useEffect,
useState,
} from "react";
import ExceptionsTable from "../../Components/Exceptions/ExceptionsTable";
import TelemetryException from "Common/Models/DatabaseModels/TelemetryException";
import Service from "Common/Models/DatabaseModels/Service";
import ModelAPI from "Common/UI/Utils/ModelAPI/ModelAPI";
import API from "Common/UI/Utils/API/API";
import PageLoader from "Common/UI/Components/Loader/PageLoader";
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
const UnresolvedExceptionsPage: FunctionComponent<PageComponentProps> = (
props: PageComponentProps,
@@ -16,17 +20,29 @@ const UnresolvedExceptionsPage: FunctionComponent<PageComponentProps> = (
const disableTelemetryForThisProject: boolean =
props.currentProject?.reseller?.enableTelemetryFeatures === false;
const [hasData, setHasData] = useState<boolean | undefined>(undefined);
const [serviceCount, setServiceCount] = useState<number | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(true);
const [error, setError] = useState<string>("");
const handleFetchSuccess: (
data: Array<TelemetryException>,
totalCount: number,
) => void = useCallback(
(_data: Array<TelemetryException>, totalCount: number) => {
setHasData(totalCount > 0);
},
[],
);
const fetchServiceCount: PromiseVoidFunction = async (): Promise<void> => {
setIsLoading(true);
try {
const count: number = await ModelAPI.count({
modelType: Service,
query: {},
});
setServiceCount(count);
} catch (err) {
setError(API.getFriendlyMessage(err));
}
setIsLoading(false);
};
useEffect(() => {
fetchServiceCount().catch((err: Error) => {
setError(API.getFriendlyMessage(err));
});
}, []);
if (disableTelemetryForThisProject) {
return (
@@ -34,7 +50,15 @@ const UnresolvedExceptionsPage: FunctionComponent<PageComponentProps> = (
);
}
if (hasData === false) {
if (isLoading) {
return <PageLoader isVisible={true} />;
}
if (error) {
return <ErrorMessage message={error} />;
}
if (serviceCount === 0) {
return <TelemetryDocumentation telemetryType="exceptions" />;
}
@@ -46,7 +70,6 @@ const UnresolvedExceptionsPage: FunctionComponent<PageComponentProps> = (
}}
title="Unresolved Exceptions"
description="All the exceptions that have not been resolved."
onFetchSuccess={handleFetchSuccess}
/>
);
};

View File

@@ -65,26 +65,25 @@ const KubernetesClusterEvents: FunctionComponent<
const endDate: Date = OneUptimeDate.getCurrentDate();
const startDate: Date = OneUptimeDate.addRemoveHours(endDate, -24);
const listResult: ListResult<Log> =
await AnalyticsModelAPI.getList<Log>({
modelType: Log,
query: {
projectId: ProjectUtil.getCurrentProjectId()!.toString(),
time: new InBetween<Date>(startDate, endDate),
},
limit: 200,
skip: 0,
select: {
time: true,
body: true,
severityText: true,
attributes: true,
},
sort: {
time: SortOrder.Descending,
},
requestOptions: {},
});
const listResult: ListResult<Log> = await AnalyticsModelAPI.getList<Log>({
modelType: Log,
query: {
projectId: ProjectUtil.getCurrentProjectId()!.toString(),
time: new InBetween<Date>(startDate, endDate),
},
limit: 200,
skip: 0,
select: {
time: true,
body: true,
severityText: true,
attributes: true,
},
sort: {
time: SortOrder.Descending,
},
requestOptions: {},
});
// Helper to extract a string value from OTLP kvlistValue
const getKvValue = (
@@ -94,7 +93,9 @@ const KubernetesClusterEvents: FunctionComponent<
if (!kvList) {
return "";
}
const values = (kvList as JSONObject)["values"] as Array<JSONObject> | undefined;
const values = (kvList as JSONObject)["values"] as
| Array<JSONObject>
| undefined;
if (!values) {
return "";
}
@@ -128,7 +129,9 @@ const KubernetesClusterEvents: FunctionComponent<
if (!kvList) {
return "";
}
const values = (kvList as JSONObject)["values"] as Array<JSONObject> | undefined;
const values = (kvList as JSONObject)["values"] as
| Array<JSONObject>
| undefined;
if (!values) {
return "";
}
@@ -193,10 +196,14 @@ const KubernetesClusterEvents: FunctionComponent<
const note: string = getKvValue(objectKvList, "note") || "";
// Get object details from "regarding" sub-object
const objectKind: string = getNestedKvValue(objectKvList, "regarding", "kind") || "";
const objectName: string = getNestedKvValue(objectKvList, "regarding", "name") || "";
const namespace: string = getNestedKvValue(objectKvList, "regarding", "namespace") ||
getNestedKvValue(objectKvList, "metadata", "namespace") || "";
const objectKind: string =
getNestedKvValue(objectKvList, "regarding", "kind") || "";
const objectName: string =
getNestedKvValue(objectKvList, "regarding", "name") || "";
const namespace: string =
getNestedKvValue(objectKvList, "regarding", "namespace") ||
getNestedKvValue(objectKvList, "metadata", "namespace") ||
"";
if (eventType || reason) {
k8sEvents.push({
@@ -275,47 +282,40 @@ const KubernetesClusterEvents: FunctionComponent<
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{events.map(
(event: KubernetesEvent, index: number) => {
const isWarning: boolean =
event.type.toLowerCase() === "warning";
return (
<tr
key={index}
className={
isWarning ? "bg-yellow-50" : ""
}
>
<td className="px-4 py-3 whitespace-nowrap text-sm text-gray-500">
{event.timestamp}
</td>
<td className="px-4 py-3 whitespace-nowrap text-sm">
<span
className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${
isWarning
? "bg-yellow-100 text-yellow-800"
: "bg-green-100 text-green-800"
}`}
>
{event.type}
</span>
</td>
<td className="px-4 py-3 whitespace-nowrap text-sm text-gray-900">
{event.reason}
</td>
<td className="px-4 py-3 whitespace-nowrap text-sm text-gray-900">
{event.objectKind}/{event.objectName}
</td>
<td className="px-4 py-3 whitespace-nowrap text-sm text-gray-500">
{event.namespace}
</td>
<td className="px-4 py-3 text-sm text-gray-500 max-w-md truncate">
{event.message}
</td>
</tr>
);
},
)}
{events.map((event: KubernetesEvent, index: number) => {
const isWarning: boolean =
event.type.toLowerCase() === "warning";
return (
<tr key={index} className={isWarning ? "bg-yellow-50" : ""}>
<td className="px-4 py-3 whitespace-nowrap text-sm text-gray-500">
{event.timestamp}
</td>
<td className="px-4 py-3 whitespace-nowrap text-sm">
<span
className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${
isWarning
? "bg-yellow-100 text-yellow-800"
: "bg-green-100 text-green-800"
}`}
>
{event.type}
</span>
</td>
<td className="px-4 py-3 whitespace-nowrap text-sm text-gray-900">
{event.reason}
</td>
<td className="px-4 py-3 whitespace-nowrap text-sm text-gray-900">
{event.objectKind}/{event.objectName}
</td>
<td className="px-4 py-3 whitespace-nowrap text-sm text-gray-500">
{event.namespace}
</td>
<td className="px-4 py-3 text-sm text-gray-500 max-w-md truncate">
{event.message}
</td>
</tr>
);
})}
</tbody>
</table>
</div>

View File

@@ -205,7 +205,12 @@ const KubernetesClusterPodDetail: FunctionComponent<
onChange={(data: MetricViewData) => {
setMetricViewData({
...data,
queryConfigs: [podCpuQuery, podMemoryQuery, cpuQuery, memoryQuery],
queryConfigs: [
podCpuQuery,
podMemoryQuery,
cpuQuery,
memoryQuery,
],
formulaConfigs: [],
});
}}

View File

@@ -32,9 +32,7 @@ const KubernetesClusterSideMenu: FunctionComponent<ComponentProps> = (
link={{
title: "Documentation",
to: RouteUtil.populateRouteParams(
RouteMap[
PageMap.KUBERNETES_CLUSTER_VIEW_DOCUMENTATION
] as Route,
RouteMap[PageMap.KUBERNETES_CLUSTER_VIEW_DOCUMENTATION] as Route,
{ modelId: props.modelId },
),
}}
@@ -80,9 +78,7 @@ const KubernetesClusterSideMenu: FunctionComponent<ComponentProps> = (
link={{
title: "Control Plane",
to: RouteUtil.populateRouteParams(
RouteMap[
PageMap.KUBERNETES_CLUSTER_VIEW_CONTROL_PLANE
] as Route,
RouteMap[PageMap.KUBERNETES_CLUSTER_VIEW_CONTROL_PLANE] as Route,
{ modelId: props.modelId },
),
}}

View File

@@ -3,11 +3,16 @@ import ErrorMessage from "Common/UI/Components/ErrorMessage/ErrorMessage";
import React, {
FunctionComponent,
ReactElement,
useCallback,
useEffect,
useState,
} from "react";
import DashboardLogsViewer from "../../Components/Logs/LogsViewer";
import TelemetryDocumentation from "../../Components/Telemetry/Documentation";
import Service from "Common/Models/DatabaseModels/Service";
import ModelAPI from "Common/UI/Utils/ModelAPI/ModelAPI";
import API from "Common/UI/Utils/API/API";
import PageLoader from "Common/UI/Components/Loader/PageLoader";
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
const LogsPage: FunctionComponent<PageComponentProps> = (
props: PageComponentProps,
@@ -15,15 +20,30 @@ const LogsPage: FunctionComponent<PageComponentProps> = (
const disableTelemetryForThisProject: boolean =
props.currentProject?.reseller?.enableTelemetryFeatures === false;
const [hasData, setHasData] = useState<boolean | undefined>(undefined);
const [serviceCount, setServiceCount] = useState<number | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(true);
const [error, setError] = useState<string>("");
const [showDocs, setShowDocs] = useState<boolean>(false);
const handleCountChange: (count: number) => void = useCallback(
(count: number) => {
setHasData(count > 0);
},
[],
);
const fetchServiceCount: PromiseVoidFunction = async (): Promise<void> => {
setIsLoading(true);
try {
const count: number = await ModelAPI.count({
modelType: Service,
query: {},
});
setServiceCount(count);
} catch (err) {
setError(API.getFriendlyMessage(err));
}
setIsLoading(false);
};
useEffect(() => {
fetchServiceCount().catch((err: Error) => {
setError(API.getFriendlyMessage(err));
});
}, []);
if (disableTelemetryForThisProject) {
return (
@@ -31,6 +51,14 @@ const LogsPage: FunctionComponent<PageComponentProps> = (
);
}
if (isLoading) {
return <PageLoader isVisible={true} />;
}
if (error) {
return <ErrorMessage message={error} />;
}
if (showDocs) {
return (
<TelemetryDocumentation
@@ -42,7 +70,7 @@ const LogsPage: FunctionComponent<PageComponentProps> = (
);
}
if (hasData === false) {
if (serviceCount === 0) {
return <TelemetryDocumentation telemetryType="logs" />;
}
@@ -53,7 +81,6 @@ const LogsPage: FunctionComponent<PageComponentProps> = (
limit={100}
enableRealtime={true}
id="logs"
onCountChange={handleCountChange}
onShowDocumentation={() => {
setShowDocs(true);
}}

View File

@@ -4,11 +4,15 @@ import TelemetryDocumentation from "../../Components/Telemetry/Documentation";
import React, {
FunctionComponent,
ReactElement,
useCallback,
useEffect,
useState,
} from "react";
import MetricsTable from "../../Components/Metrics/MetricsTable";
import MetricType from "Common/Models/DatabaseModels/MetricType";
import Service from "Common/Models/DatabaseModels/Service";
import ModelAPI from "Common/UI/Utils/ModelAPI/ModelAPI";
import API from "Common/UI/Utils/API/API";
import PageLoader from "Common/UI/Components/Loader/PageLoader";
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
const MetricsPage: FunctionComponent<PageComponentProps> = (
props: PageComponentProps,
@@ -16,17 +20,29 @@ const MetricsPage: FunctionComponent<PageComponentProps> = (
const disableTelemetryForThisProject: boolean =
props.currentProject?.reseller?.enableTelemetryFeatures === false;
const [hasData, setHasData] = useState<boolean | undefined>(undefined);
const [serviceCount, setServiceCount] = useState<number | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(true);
const [error, setError] = useState<string>("");
const handleFetchSuccess: (
data: Array<MetricType>,
totalCount: number,
) => void = useCallback(
(_data: Array<MetricType>, totalCount: number) => {
setHasData(totalCount > 0);
},
[],
);
const fetchServiceCount: PromiseVoidFunction = async (): Promise<void> => {
setIsLoading(true);
try {
const count: number = await ModelAPI.count({
modelType: Service,
query: {},
});
setServiceCount(count);
} catch (err) {
setError(API.getFriendlyMessage(err));
}
setIsLoading(false);
};
useEffect(() => {
fetchServiceCount().catch((err: Error) => {
setError(API.getFriendlyMessage(err));
});
}, []);
if (disableTelemetryForThisProject) {
return (
@@ -34,11 +50,19 @@ const MetricsPage: FunctionComponent<PageComponentProps> = (
);
}
if (hasData === false) {
if (isLoading) {
return <PageLoader isVisible={true} />;
}
if (error) {
return <ErrorMessage message={error} />;
}
if (serviceCount === 0) {
return <TelemetryDocumentation telemetryType="metrics" />;
}
return <MetricsTable onFetchSuccess={handleFetchSuccess} />;
return <MetricsTable />;
};
export default MetricsPage;

View File

@@ -4,11 +4,15 @@ import TelemetryDocumentation from "../../Components/Telemetry/Documentation";
import React, {
FunctionComponent,
ReactElement,
useCallback,
useEffect,
useState,
} from "react";
import TraceTable from "../../Components/Traces/TraceTable";
import Span from "Common/Models/AnalyticsModels/Span";
import Service from "Common/Models/DatabaseModels/Service";
import ModelAPI from "Common/UI/Utils/ModelAPI/ModelAPI";
import API from "Common/UI/Utils/API/API";
import PageLoader from "Common/UI/Components/Loader/PageLoader";
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
const TracesPage: FunctionComponent<PageComponentProps> = (
props: PageComponentProps,
@@ -16,12 +20,29 @@ const TracesPage: FunctionComponent<PageComponentProps> = (
const disableTelemetryForThisProject: boolean =
props.currentProject?.reseller?.enableTelemetryFeatures === false;
const [hasData, setHasData] = useState<boolean | undefined>(undefined);
const [serviceCount, setServiceCount] = useState<number | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(true);
const [error, setError] = useState<string>("");
const handleFetchSuccess: (data: Array<Span>, totalCount: number) => void =
useCallback((_data: Array<Span>, totalCount: number) => {
setHasData(totalCount > 0);
}, []);
const fetchServiceCount: PromiseVoidFunction = async (): Promise<void> => {
setIsLoading(true);
try {
const count: number = await ModelAPI.count({
modelType: Service,
query: {},
});
setServiceCount(count);
} catch (err) {
setError(API.getFriendlyMessage(err));
}
setIsLoading(false);
};
useEffect(() => {
fetchServiceCount().catch((err: Error) => {
setError(API.getFriendlyMessage(err));
});
}, []);
if (disableTelemetryForThisProject) {
return (
@@ -29,11 +50,19 @@ const TracesPage: FunctionComponent<PageComponentProps> = (
);
}
if (hasData === false) {
if (isLoading) {
return <PageLoader isVisible={true} />;
}
if (error) {
return <ErrorMessage message={error} />;
}
if (serviceCount === 0) {
return <TelemetryDocumentation telemetryType="traces" />;
}
return <TraceTable onFetchSuccess={handleFetchSuccess} />;
return <TraceTable />;
};
export default TracesPage;

View File

@@ -64,9 +64,7 @@ const ExceptionsRoutes: FunctionComponent<ComponentProps> = (
element={
<ExceptionsDocumentationPage
{...props}
pageRoute={
RouteMap[PageMap.EXCEPTIONS_DOCUMENTATION] as Route
}
pageRoute={RouteMap[PageMap.EXCEPTIONS_DOCUMENTATION] as Route}
/>
}
/>

View File

@@ -61,81 +61,113 @@ const KubernetesRoutes: FunctionComponent<ComponentProps> = (
/>
<PageRoute
path={RouteUtil.getLastPathForKey(PageMap.KUBERNETES_CLUSTER_VIEW_PODS)}
path={RouteUtil.getLastPathForKey(
PageMap.KUBERNETES_CLUSTER_VIEW_PODS,
)}
element={
<KubernetesClusterViewPods
{...props}
pageRoute={RouteMap[PageMap.KUBERNETES_CLUSTER_VIEW_PODS] as Route}
pageRoute={
RouteMap[PageMap.KUBERNETES_CLUSTER_VIEW_PODS] as Route
}
/>
}
/>
<PageRoute
path={RouteUtil.getLastPathForKey(PageMap.KUBERNETES_CLUSTER_VIEW_POD_DETAIL)}
path={RouteUtil.getLastPathForKey(
PageMap.KUBERNETES_CLUSTER_VIEW_POD_DETAIL,
)}
element={
<KubernetesClusterViewPodDetail
{...props}
pageRoute={RouteMap[PageMap.KUBERNETES_CLUSTER_VIEW_POD_DETAIL] as Route}
pageRoute={
RouteMap[PageMap.KUBERNETES_CLUSTER_VIEW_POD_DETAIL] as Route
}
/>
}
/>
<PageRoute
path={RouteUtil.getLastPathForKey(PageMap.KUBERNETES_CLUSTER_VIEW_NODES)}
path={RouteUtil.getLastPathForKey(
PageMap.KUBERNETES_CLUSTER_VIEW_NODES,
)}
element={
<KubernetesClusterViewNodes
{...props}
pageRoute={RouteMap[PageMap.KUBERNETES_CLUSTER_VIEW_NODES] as Route}
pageRoute={
RouteMap[PageMap.KUBERNETES_CLUSTER_VIEW_NODES] as Route
}
/>
}
/>
<PageRoute
path={RouteUtil.getLastPathForKey(PageMap.KUBERNETES_CLUSTER_VIEW_NODE_DETAIL)}
path={RouteUtil.getLastPathForKey(
PageMap.KUBERNETES_CLUSTER_VIEW_NODE_DETAIL,
)}
element={
<KubernetesClusterViewNodeDetail
{...props}
pageRoute={RouteMap[PageMap.KUBERNETES_CLUSTER_VIEW_NODE_DETAIL] as Route}
pageRoute={
RouteMap[PageMap.KUBERNETES_CLUSTER_VIEW_NODE_DETAIL] as Route
}
/>
}
/>
<PageRoute
path={RouteUtil.getLastPathForKey(PageMap.KUBERNETES_CLUSTER_VIEW_EVENTS)}
path={RouteUtil.getLastPathForKey(
PageMap.KUBERNETES_CLUSTER_VIEW_EVENTS,
)}
element={
<KubernetesClusterViewEvents
{...props}
pageRoute={RouteMap[PageMap.KUBERNETES_CLUSTER_VIEW_EVENTS] as Route}
pageRoute={
RouteMap[PageMap.KUBERNETES_CLUSTER_VIEW_EVENTS] as Route
}
/>
}
/>
<PageRoute
path={RouteUtil.getLastPathForKey(PageMap.KUBERNETES_CLUSTER_VIEW_CONTROL_PLANE)}
path={RouteUtil.getLastPathForKey(
PageMap.KUBERNETES_CLUSTER_VIEW_CONTROL_PLANE,
)}
element={
<KubernetesClusterViewControlPlane
{...props}
pageRoute={RouteMap[PageMap.KUBERNETES_CLUSTER_VIEW_CONTROL_PLANE] as Route}
pageRoute={
RouteMap[PageMap.KUBERNETES_CLUSTER_VIEW_CONTROL_PLANE] as Route
}
/>
}
/>
<PageRoute
path={RouteUtil.getLastPathForKey(PageMap.KUBERNETES_CLUSTER_VIEW_DELETE)}
path={RouteUtil.getLastPathForKey(
PageMap.KUBERNETES_CLUSTER_VIEW_DELETE,
)}
element={
<KubernetesClusterViewDelete
{...props}
pageRoute={RouteMap[PageMap.KUBERNETES_CLUSTER_VIEW_DELETE] as Route}
pageRoute={
RouteMap[PageMap.KUBERNETES_CLUSTER_VIEW_DELETE] as Route
}
/>
}
/>
<PageRoute
path={RouteUtil.getLastPathForKey(PageMap.KUBERNETES_CLUSTER_VIEW_DOCUMENTATION)}
path={RouteUtil.getLastPathForKey(
PageMap.KUBERNETES_CLUSTER_VIEW_DOCUMENTATION,
)}
element={
<KubernetesClusterViewDocumentation
{...props}
pageRoute={RouteMap[PageMap.KUBERNETES_CLUSTER_VIEW_DOCUMENTATION] as Route}
pageRoute={
RouteMap[PageMap.KUBERNETES_CLUSTER_VIEW_DOCUMENTATION] as Route
}
/>
}
/>

View File

@@ -1,54 +1,137 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class MigrationName1773761409952 implements MigrationInterface {
name = 'MigrationName1773761409952'
name = "MigrationName1773761409952";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "KubernetesCluster" DROP CONSTRAINT "FK_kubernetes_cluster_projectId"`);
await queryRunner.query(`ALTER TABLE "KubernetesCluster" DROP CONSTRAINT "FK_kubernetes_cluster_createdByUserId"`);
await queryRunner.query(`ALTER TABLE "KubernetesCluster" DROP CONSTRAINT "FK_kubernetes_cluster_deletedByUserId"`);
await queryRunner.query(`ALTER TABLE "KubernetesClusterLabel" DROP CONSTRAINT "FK_kubernetes_cluster_label_clusterId"`);
await queryRunner.query(`ALTER TABLE "KubernetesClusterLabel" DROP CONSTRAINT "FK_kubernetes_cluster_label_labelId"`);
await queryRunner.query(`DROP INDEX "public"."IDX_kubernetes_cluster_projectId"`);
await queryRunner.query(`DROP INDEX "public"."IDX_kubernetes_cluster_clusterIdentifier"`);
await queryRunner.query(`DROP INDEX "public"."IDX_kubernetes_cluster_slug"`);
await queryRunner.query(`DROP INDEX "public"."IDX_kubernetes_cluster_label_clusterId"`);
await queryRunner.query(`DROP INDEX "public"."IDX_kubernetes_cluster_label_labelId"`);
await queryRunner.query(`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type":"Recurring","value":{"intervalType":"Day","intervalCount":{"_type":"PositiveNumber","value":1}}}'`);
await queryRunner.query(`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type":"RestrictionTimes","value":{"restictionType":"None","dayRestrictionTimes":null,"weeklyRestrictionTimes":[]}}'`);
await queryRunner.query(`CREATE INDEX "IDX_5ae5bbb0c93c048b0b76b1d426" ON "KubernetesCluster" ("projectId") `);
await queryRunner.query(`CREATE INDEX "IDX_b9259f6741a7965a518e258f61" ON "KubernetesCluster" ("clusterIdentifier") `);
await queryRunner.query(`CREATE INDEX "IDX_ed1b53bd041aa21b44ca8cdab5" ON "KubernetesClusterLabel" ("kubernetesClusterId") `);
await queryRunner.query(`CREATE INDEX "IDX_2ec82ad068e84cf762c32ad7c7" ON "KubernetesClusterLabel" ("labelId") `);
await queryRunner.query(`ALTER TABLE "KubernetesCluster" ADD CONSTRAINT "FK_5ae5bbb0c93c048b0b76b1d4268" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "KubernetesCluster" ADD CONSTRAINT "FK_1bee392c44b1aebe754932133a8" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "KubernetesCluster" ADD CONSTRAINT "FK_b0f6c98aac521060f8b68fe5c87" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "KubernetesClusterLabel" ADD CONSTRAINT "FK_ed1b53bd041aa21b44ca8cdab5e" FOREIGN KEY ("kubernetesClusterId") REFERENCES "KubernetesCluster"("_id") ON DELETE CASCADE ON UPDATE CASCADE`);
await queryRunner.query(`ALTER TABLE "KubernetesClusterLabel" ADD CONSTRAINT "FK_2ec82ad068e84cf762c32ad7c76" FOREIGN KEY ("labelId") REFERENCES "Label"("_id") ON DELETE CASCADE ON UPDATE CASCADE`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "KubernetesClusterLabel" DROP CONSTRAINT "FK_2ec82ad068e84cf762c32ad7c76"`);
await queryRunner.query(`ALTER TABLE "KubernetesClusterLabel" DROP CONSTRAINT "FK_ed1b53bd041aa21b44ca8cdab5e"`);
await queryRunner.query(`ALTER TABLE "KubernetesCluster" DROP CONSTRAINT "FK_b0f6c98aac521060f8b68fe5c87"`);
await queryRunner.query(`ALTER TABLE "KubernetesCluster" DROP CONSTRAINT "FK_1bee392c44b1aebe754932133a8"`);
await queryRunner.query(`ALTER TABLE "KubernetesCluster" DROP CONSTRAINT "FK_5ae5bbb0c93c048b0b76b1d4268"`);
await queryRunner.query(`DROP INDEX "public"."IDX_2ec82ad068e84cf762c32ad7c7"`);
await queryRunner.query(`DROP INDEX "public"."IDX_ed1b53bd041aa21b44ca8cdab5"`);
await queryRunner.query(`DROP INDEX "public"."IDX_b9259f6741a7965a518e258f61"`);
await queryRunner.query(`DROP INDEX "public"."IDX_5ae5bbb0c93c048b0b76b1d426"`);
await queryRunner.query(`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type": "RestrictionTimes", "value": {"restictionType": "None", "dayRestrictionTimes": null, "weeklyRestrictionTimes": []}}'`);
await queryRunner.query(`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type": "Recurring", "value": {"intervalType": "Day", "intervalCount": {"_type": "PositiveNumber", "value": 1}}}'`);
await queryRunner.query(`CREATE INDEX "IDX_kubernetes_cluster_label_labelId" ON "KubernetesClusterLabel" ("labelId") `);
await queryRunner.query(`CREATE INDEX "IDX_kubernetes_cluster_label_clusterId" ON "KubernetesClusterLabel" ("kubernetesClusterId") `);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_kubernetes_cluster_slug" ON "KubernetesCluster" ("slug") `);
await queryRunner.query(`CREATE INDEX "IDX_kubernetes_cluster_clusterIdentifier" ON "KubernetesCluster" ("clusterIdentifier") `);
await queryRunner.query(`CREATE INDEX "IDX_kubernetes_cluster_projectId" ON "KubernetesCluster" ("projectId") `);
await queryRunner.query(`ALTER TABLE "KubernetesClusterLabel" ADD CONSTRAINT "FK_kubernetes_cluster_label_labelId" FOREIGN KEY ("labelId") REFERENCES "Label"("_id") ON DELETE CASCADE ON UPDATE CASCADE`);
await queryRunner.query(`ALTER TABLE "KubernetesClusterLabel" ADD CONSTRAINT "FK_kubernetes_cluster_label_clusterId" FOREIGN KEY ("kubernetesClusterId") REFERENCES "KubernetesCluster"("_id") ON DELETE CASCADE ON UPDATE CASCADE`);
await queryRunner.query(`ALTER TABLE "KubernetesCluster" ADD CONSTRAINT "FK_kubernetes_cluster_deletedByUserId" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "KubernetesCluster" ADD CONSTRAINT "FK_kubernetes_cluster_createdByUserId" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "KubernetesCluster" ADD CONSTRAINT "FK_kubernetes_cluster_projectId" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`);
}
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "KubernetesCluster" DROP CONSTRAINT "FK_kubernetes_cluster_projectId"`,
);
await queryRunner.query(
`ALTER TABLE "KubernetesCluster" DROP CONSTRAINT "FK_kubernetes_cluster_createdByUserId"`,
);
await queryRunner.query(
`ALTER TABLE "KubernetesCluster" DROP CONSTRAINT "FK_kubernetes_cluster_deletedByUserId"`,
);
await queryRunner.query(
`ALTER TABLE "KubernetesClusterLabel" DROP CONSTRAINT "FK_kubernetes_cluster_label_clusterId"`,
);
await queryRunner.query(
`ALTER TABLE "KubernetesClusterLabel" DROP CONSTRAINT "FK_kubernetes_cluster_label_labelId"`,
);
await queryRunner.query(
`DROP INDEX "public"."IDX_kubernetes_cluster_projectId"`,
);
await queryRunner.query(
`DROP INDEX "public"."IDX_kubernetes_cluster_clusterIdentifier"`,
);
await queryRunner.query(
`DROP INDEX "public"."IDX_kubernetes_cluster_slug"`,
);
await queryRunner.query(
`DROP INDEX "public"."IDX_kubernetes_cluster_label_clusterId"`,
);
await queryRunner.query(
`DROP INDEX "public"."IDX_kubernetes_cluster_label_labelId"`,
);
await queryRunner.query(
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type":"Recurring","value":{"intervalType":"Day","intervalCount":{"_type":"PositiveNumber","value":1}}}'`,
);
await queryRunner.query(
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type":"RestrictionTimes","value":{"restictionType":"None","dayRestrictionTimes":null,"weeklyRestrictionTimes":[]}}'`,
);
await queryRunner.query(
`CREATE INDEX "IDX_5ae5bbb0c93c048b0b76b1d426" ON "KubernetesCluster" ("projectId") `,
);
await queryRunner.query(
`CREATE INDEX "IDX_b9259f6741a7965a518e258f61" ON "KubernetesCluster" ("clusterIdentifier") `,
);
await queryRunner.query(
`CREATE INDEX "IDX_ed1b53bd041aa21b44ca8cdab5" ON "KubernetesClusterLabel" ("kubernetesClusterId") `,
);
await queryRunner.query(
`CREATE INDEX "IDX_2ec82ad068e84cf762c32ad7c7" ON "KubernetesClusterLabel" ("labelId") `,
);
await queryRunner.query(
`ALTER TABLE "KubernetesCluster" ADD CONSTRAINT "FK_5ae5bbb0c93c048b0b76b1d4268" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "KubernetesCluster" ADD CONSTRAINT "FK_1bee392c44b1aebe754932133a8" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "KubernetesCluster" ADD CONSTRAINT "FK_b0f6c98aac521060f8b68fe5c87" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "KubernetesClusterLabel" ADD CONSTRAINT "FK_ed1b53bd041aa21b44ca8cdab5e" FOREIGN KEY ("kubernetesClusterId") REFERENCES "KubernetesCluster"("_id") ON DELETE CASCADE ON UPDATE CASCADE`,
);
await queryRunner.query(
`ALTER TABLE "KubernetesClusterLabel" ADD CONSTRAINT "FK_2ec82ad068e84cf762c32ad7c76" FOREIGN KEY ("labelId") REFERENCES "Label"("_id") ON DELETE CASCADE ON UPDATE CASCADE`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "KubernetesClusterLabel" DROP CONSTRAINT "FK_2ec82ad068e84cf762c32ad7c76"`,
);
await queryRunner.query(
`ALTER TABLE "KubernetesClusterLabel" DROP CONSTRAINT "FK_ed1b53bd041aa21b44ca8cdab5e"`,
);
await queryRunner.query(
`ALTER TABLE "KubernetesCluster" DROP CONSTRAINT "FK_b0f6c98aac521060f8b68fe5c87"`,
);
await queryRunner.query(
`ALTER TABLE "KubernetesCluster" DROP CONSTRAINT "FK_1bee392c44b1aebe754932133a8"`,
);
await queryRunner.query(
`ALTER TABLE "KubernetesCluster" DROP CONSTRAINT "FK_5ae5bbb0c93c048b0b76b1d4268"`,
);
await queryRunner.query(
`DROP INDEX "public"."IDX_2ec82ad068e84cf762c32ad7c7"`,
);
await queryRunner.query(
`DROP INDEX "public"."IDX_ed1b53bd041aa21b44ca8cdab5"`,
);
await queryRunner.query(
`DROP INDEX "public"."IDX_b9259f6741a7965a518e258f61"`,
);
await queryRunner.query(
`DROP INDEX "public"."IDX_5ae5bbb0c93c048b0b76b1d426"`,
);
await queryRunner.query(
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "restrictionTimes" SET DEFAULT '{"_type": "RestrictionTimes", "value": {"restictionType": "None", "dayRestrictionTimes": null, "weeklyRestrictionTimes": []}}'`,
);
await queryRunner.query(
`ALTER TABLE "OnCallDutyPolicyScheduleLayer" ALTER COLUMN "rotation" SET DEFAULT '{"_type": "Recurring", "value": {"intervalType": "Day", "intervalCount": {"_type": "PositiveNumber", "value": 1}}}'`,
);
await queryRunner.query(
`CREATE INDEX "IDX_kubernetes_cluster_label_labelId" ON "KubernetesClusterLabel" ("labelId") `,
);
await queryRunner.query(
`CREATE INDEX "IDX_kubernetes_cluster_label_clusterId" ON "KubernetesClusterLabel" ("kubernetesClusterId") `,
);
await queryRunner.query(
`CREATE UNIQUE INDEX "IDX_kubernetes_cluster_slug" ON "KubernetesCluster" ("slug") `,
);
await queryRunner.query(
`CREATE INDEX "IDX_kubernetes_cluster_clusterIdentifier" ON "KubernetesCluster" ("clusterIdentifier") `,
);
await queryRunner.query(
`CREATE INDEX "IDX_kubernetes_cluster_projectId" ON "KubernetesCluster" ("projectId") `,
);
await queryRunner.query(
`ALTER TABLE "KubernetesClusterLabel" ADD CONSTRAINT "FK_kubernetes_cluster_label_labelId" FOREIGN KEY ("labelId") REFERENCES "Label"("_id") ON DELETE CASCADE ON UPDATE CASCADE`,
);
await queryRunner.query(
`ALTER TABLE "KubernetesClusterLabel" ADD CONSTRAINT "FK_kubernetes_cluster_label_clusterId" FOREIGN KEY ("kubernetesClusterId") REFERENCES "KubernetesCluster"("_id") ON DELETE CASCADE ON UPDATE CASCADE`,
);
await queryRunner.query(
`ALTER TABLE "KubernetesCluster" ADD CONSTRAINT "FK_kubernetes_cluster_deletedByUserId" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "KubernetesCluster" ADD CONSTRAINT "FK_kubernetes_cluster_createdByUserId" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "KubernetesCluster" ADD CONSTRAINT "FK_kubernetes_cluster_projectId" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
}
}

View File

@@ -539,5 +539,5 @@ export default [
MigrationName1773402621107,
MigrationName1773676206197,
MigrationName1774000000000,
MigrationName1773761409952
MigrationName1773761409952,
];

View File

@@ -241,7 +241,10 @@ const init: InitFunction = async (
},
);
app.use(`/${appName}`, ExpressStatic(path.resolve(process.cwd(), "public")));
app.use(
`/${appName}`,
ExpressStatic(path.resolve(process.cwd(), "public")),
);
app.get(
`/${appName}/dist/Index.js`,

View File

@@ -352,9 +352,11 @@ const MarkdownViewer: FunctionComponent<ComponentProps> = (
return <>{children}</>;
}
// If the child is a custom component (CodeBlock, MermaidDiagram, etc.)
// rather than a plain HTML element like <code>, skip pre styling.
// Checking typeof type !== "string" is minification-safe unlike checking type.name.
/*
* If the child is a custom component (CodeBlock, MermaidDiagram, etc.)
* rather than a plain HTML element like <code>, skip pre styling.
* Checking typeof type !== "string" is minification-safe unlike checking type.name.
*/
const isCustomComponent: boolean =
React.isValidElement(children) &&
typeof (children as any).type !== "string";
@@ -439,10 +441,7 @@ const MarkdownViewer: FunctionComponent<ComponentProps> = (
},
tr: ({ ...props }: any) => {
return (
<tr
className="hover:bg-gray-50 transition-colors"
{...props}
/>
<tr className="hover:bg-gray-50 transition-colors" {...props} />
);
},
th: ({ ...props }: any) => {
@@ -455,10 +454,7 @@ const MarkdownViewer: FunctionComponent<ComponentProps> = (
},
td: ({ ...props }: any) => {
return (
<td
className="px-4 py-2.5 text-sm text-gray-700"
{...props}
/>
<td className="px-4 py-2.5 text-sm text-gray-700" {...props} />
);
},
hr: ({ ...props }: any) => {
@@ -482,11 +478,11 @@ const MarkdownViewer: FunctionComponent<ComponentProps> = (
}
const isMultiline: boolean = content.includes("\n");
const hasLanguage: boolean = !!(
const hasLanguage: boolean = Boolean(
match &&
match?.filter((item: string) => {
return item.includes("language-");
}).length > 0
match?.filter((item: string) => {
return item.includes("language-");
}).length > 0,
);
// Multiline code blocks (with or without language) get the full CodeBlock treatment

View File

@@ -1,5 +1,7 @@
// Critical Path Analysis for distributed traces
// Computes self-time, critical path, and bottleneck identification
/*
* Critical Path Analysis for distributed traces
* Computes self-time, critical path, and bottleneck identification
*/
export interface SpanData {
spanId: string;
@@ -71,9 +73,10 @@ export default class CriticalPathUtil {
selfTimeUnixNano,
childTimeUnixNano,
totalTimeUnixNano: span.durationUnixNano,
selfTimePercent: span.durationUnixNano > 0
? (selfTimeUnixNano / span.durationUnixNano) * 100
: 0,
selfTimePercent:
span.durationUnixNano > 0
? (selfTimeUnixNano / span.durationUnixNano) * 100
: 0,
});
}
@@ -110,9 +113,14 @@ export default class CriticalPathUtil {
}
// Sort by start time
intervals.sort((a: { start: number; end: number }, b: { start: number; end: number }) => {
return a.start - b.start;
});
intervals.sort(
(
a: { start: number; end: number },
b: { start: number; end: number },
) => {
return a.start - b.start;
},
);
// Merge overlapping intervals
let mergedDuration: number = 0;
@@ -275,9 +283,7 @@ export default class CriticalPathUtil {
/**
* Compute latency breakdown by service.
*/
public static computeServiceBreakdown(
spans: SpanData[],
): ServiceBreakdown[] {
public static computeServiceBreakdown(spans: SpanData[]): ServiceBreakdown[] {
const selfTimes: Map<string, SpanSelfTime> =
CriticalPathUtil.computeSelfTimes(spans);
@@ -302,12 +308,15 @@ export default class CriticalPathUtil {
for (const span of spans) {
const serviceId: string = span.serviceId || "unknown";
const entry: { totalDuration: number; selfTime: number; spanCount: number } =
serviceMap.get(serviceId) || {
totalDuration: 0,
selfTime: 0,
spanCount: 0,
};
const entry: {
totalDuration: number;
selfTime: number;
spanCount: number;
} = serviceMap.get(serviceId) || {
totalDuration: 0,
selfTime: 0,
spanCount: 0,
};
entry.totalDuration += span.durationUnixNano;
const selfTime: SpanSelfTime | undefined = selfTimes.get(span.spanId);
@@ -329,11 +338,9 @@ export default class CriticalPathUtil {
}
// Sort by self-time descending (biggest contributors first)
result.sort(
(a: ServiceBreakdown, b: ServiceBreakdown) => {
return b.selfTimeUnixNano - a.selfTimeUnixNano;
},
);
result.sort((a: ServiceBreakdown, b: ServiceBreakdown) => {
return b.selfTimeUnixNano - a.selfTimeUnixNano;
});
return result;
}

View File

@@ -4,7 +4,13 @@ import { Ionicons } from "@expo/vector-icons";
import { useTheme } from "../theme";
import GradientButton from "./GradientButton";
type EmptyIcon = "incidents" | "alerts" | "episodes" | "notes" | "monitors" | "default";
type EmptyIcon =
| "incidents"
| "alerts"
| "episodes"
| "notes"
| "monitors"
| "default";
interface EmptyStateProps {
title: string;

View File

@@ -21,7 +21,10 @@ function toDisplayString(val: unknown): string {
if (typeof obj.value === "string") {
return obj.value;
}
if (typeof obj.toString === "function" && obj.toString !== Object.prototype.toString) {
if (
typeof obj.toString === "function" &&
obj.toString !== Object.prototype.toString
) {
return obj.toString();
}
try {

View File

@@ -142,13 +142,12 @@ export function useAllProjectCounts(): UseAllProjectCountsResult {
0,
);
const inoperationalMonitorCount: number =
inoperationalMonitorQueries.reduce(
(sum: number, q: UseQueryResult<ListResponse<MonitorItem>, Error>) => {
return sum + (q.data?.count ?? 0);
},
0,
);
const inoperationalMonitorCount: number = inoperationalMonitorQueries.reduce(
(sum: number, q: UseQueryResult<ListResponse<MonitorItem>, Error>) => {
return sum + (q.data?.count ?? 0);
},
0,
);
const isLoading: boolean =
incidentQuery.isPending ||

View File

@@ -57,8 +57,9 @@ export function useAllProjectMonitors(): UseAllProjectMonitorsResult {
const items: ProjectMonitorItem[] = useMemo(() => {
const allItems: ProjectMonitorItem[] = [];
for (let i: number = 0; i < queries.length; i++) {
const query: UseQueryResult<ListResponse<MonitorItem>, Error> =
queries[i]!;
const query: UseQueryResult<ListResponse<MonitorItem>, Error> = queries[
i
]!;
const projectId: string = projectList[i]?._id ?? "";
if (query.data) {
for (const item of query.data.data) {
@@ -75,11 +76,9 @@ export function useAllProjectMonitors(): UseAllProjectMonitorsResult {
const refetch: () => Promise<void> = async (): Promise<void> => {
await Promise.all(
queries.map(
(q: UseQueryResult<ListResponse<MonitorItem>, Error>) => {
return q.refetch();
},
),
queries.map((q: UseQueryResult<ListResponse<MonitorItem>, Error>) => {
return q.refetch();
}),
);
};

View File

@@ -334,7 +334,7 @@ export default function MonitorDetailScreen({
>
{isDisabled
? "Disabled"
: (monitor.currentMonitorStatus?.name ?? "Unknown")}
: monitor.currentMonitorStatus?.name ?? "Unknown"}
</Text>
</View>

View File

@@ -254,8 +254,7 @@ export default function MonitorsScreen(): React.JSX.Element {
for (const wrapped of allMonitors) {
const statusName: string =
wrapped.item.currentMonitorStatus?.name?.toLowerCase() ?? "";
const isDisabled: boolean =
wrapped.item.disableActiveMonitoring === true;
const isDisabled: boolean = wrapped.item.disableActiveMonitoring === true;
if (isDisabled) {
disabledCount++;