diff --git a/keep-ui/.gitignore b/keep-ui/.gitignore
index 42daa1e2a..6eca4f651 100644
--- a/keep-ui/.gitignore
+++ b/keep-ui/.gitignore
@@ -8,6 +8,8 @@ pids
*.pid
*.seed
+!lib
+
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
diff --git a/keep-ui/app/error.tsx b/keep-ui/app/error.tsx
index 3f0c0ad7d..532fd8622 100644
--- a/keep-ui/app/error.tsx
+++ b/keep-ui/app/error.tsx
@@ -6,21 +6,22 @@
"use client";
import Image from "next/image";
import "./error.css";
-import {useEffect} from "react";
-import {Title, Subtitle} from "@tremor/react";
-import {Button, Text} from "@tremor/react";
+import { useEffect } from "react";
+import { Title, Subtitle } from "@tremor/react";
+import { Button, Text } from "@tremor/react";
import { signOut } from "next-auth/react";
+import { KeepApiError } from "@/shared/lib/KeepApiError";
export default function ErrorComponent({
error,
reset,
}: {
- error: Error | KeepApiError
- reset: () => void
+ error: Error | KeepApiError;
+ reset: () => void;
}) {
useEffect(() => {
- console.error(error)
- }, [error])
+ console.error(error);
+ }, [error]);
return (
@@ -29,16 +30,16 @@ export default function ErrorComponent({
{error instanceof KeepApiError && (
- Status Code: {error.statusCode}
+ Status Code: {error.statusCode}
+
Message: {error.message}
)}
- {error instanceof KeepApiError && error.proposedResolution && (
- {error.proposedResolution}
- )
- }
+ {error instanceof KeepApiError && error.proposedResolution && (
+ {error.proposedResolution}
+ )}
@@ -48,36 +49,23 @@ export default function ErrorComponent({
onClick={() => signOut()}
color="orange"
variant="secondary"
- className="mt-4 border border-orange-500 text-orange-500">
+ className="mt-4 border border-orange-500 text-orange-500"
+ >
Sign Out
) : (
)}
- )
-}
-
-// Custom Error Class
-export class KeepApiError extends Error {
- url: string;
- proposedResolution: string;
- statusCode: number | undefined;
-
- constructor(message: string, url: string, proposedResolution: string, statusCode?: number) {
- super(message);
- this.name = "KeepApiError";
- this.url = url;
- this.proposedResolution = proposedResolution;
- this.statusCode = statusCode;
- }
+ );
}
diff --git a/keep-ui/app/incidents/incident-list.tsx b/keep-ui/app/incidents/incident-list.tsx
index f0b3951e7..f94b48f9f 100644
--- a/keep-ui/app/incidents/incident-list.tsx
+++ b/keep-ui/app/incidents/incident-list.tsx
@@ -2,7 +2,7 @@
import { Card, Title, Subtitle, Button, Badge } from "@tremor/react";
import Loading from "app/loading";
import React, { useState } from "react";
-import { IncidentDto } from "./models";
+import { IncidentDto, PaginatedIncidentsDto } from "@/app/incidents/models";
import CreateOrUpdateIncident from "./create-or-update-incident";
import IncidentsTable from "./incidents-table";
import { useIncidents, usePollIncidents } from "utils/hooks/useIncidents";
@@ -28,7 +28,11 @@ interface Filters {
affected_services: string[];
}
-export default function IncidentList() {
+export default function IncidentList({
+ initialData,
+}: {
+ initialData?: PaginatedIncidentsDto;
+}) {
const [incidentsPagination, setIncidentsPagination] = useState({
limit: 20,
offset: 0,
@@ -65,7 +69,12 @@ export default function IncidentList() {
incidentsPagination.limit,
incidentsPagination.offset,
incidentsSorting[0],
- filters
+ filters,
+ {
+ revalidateOnFocus: false,
+ revalidateOnMount: !initialData,
+ fallbackData: initialData,
+ }
);
const {
data: predictedIncidents,
diff --git a/keep-ui/app/incidents/page.tsx b/keep-ui/app/incidents/page.tsx
index b311caaac..b50c55b82 100644
--- a/keep-ui/app/incidents/page.tsx
+++ b/keep-ui/app/incidents/page.tsx
@@ -1,7 +1,32 @@
+import { getServerSession } from "next-auth/next";
import IncidentList from "./incident-list";
+import { getApiURL } from "@/utils/apiUrl";
+import { authOptions } from "@/pages/api/auth/[...nextauth]";
+import {
+ getIncidents,
+ GetIncidentsParams,
+} from "@/entities/incidents/api/incidents";
+import { PaginatedIncidentsDto } from "./models";
-export default function Page() {
- return ;
+const defaultIncidentsParams: GetIncidentsParams = {
+ confirmed: true,
+ limit: 20,
+ offset: 0,
+ sorting: { id: "creation_time", desc: true },
+ filters: {},
+};
+
+export default async function Page() {
+ let incidents: PaginatedIncidentsDto | null = null;
+ try {
+ const session = await getServerSession(authOptions);
+ const apiUrl = getApiURL();
+
+ incidents = await getIncidents(apiUrl, session, defaultIncidentsParams);
+ } catch (error) {
+ console.log(error);
+ }
+ return ;
}
export const metadata = {
diff --git a/keep-ui/app/providers/page.client.tsx b/keep-ui/app/providers/page.client.tsx
index 68ce8aab8..258df36a7 100644
--- a/keep-ui/app/providers/page.client.tsx
+++ b/keep-ui/app/providers/page.client.tsx
@@ -1,7 +1,7 @@
"use client";
import { defaultProvider, Provider } from "./providers";
import { useSession } from "next-auth/react";
-import { KeepApiError } from "../error";
+import { KeepApiError } from "@/shared/lib/KeepApiError";
import { useApiUrl } from "utils/hooks/useConfig";
import ProvidersTiles from "./providers-tiles";
import React, { useState, useEffect } from "react";
diff --git a/keep-ui/app/workflows/builder/builder-card.tsx b/keep-ui/app/workflows/builder/builder-card.tsx
index 5f534e312..49e565461 100644
--- a/keep-ui/app/workflows/builder/builder-card.tsx
+++ b/keep-ui/app/workflows/builder/builder-card.tsx
@@ -5,7 +5,7 @@ import { useEffect, useState } from "react";
import { useApiUrl } from "utils/hooks/useConfig";
import Loader from "./loader";
import { Provider } from "../../providers/providers";
-import { KeepApiError } from "../../error";
+import { KeepApiError } from "@/shared/lib/KeepApiError";
import { useProviders } from "utils/hooks/useProviders";
const Builder = dynamic(() => import("./builder"), {
diff --git a/keep-ui/entities/incidents/api/incidents.ts b/keep-ui/entities/incidents/api/incidents.ts
new file mode 100644
index 000000000..0b70e14ec
--- /dev/null
+++ b/keep-ui/entities/incidents/api/incidents.ts
@@ -0,0 +1,52 @@
+import { PaginatedIncidentsDto } from "@/app/incidents/models";
+import { fetcher } from "@/utils/fetcher";
+import { Session } from "next-auth";
+
+interface Filters {
+ status: string[];
+ severity: string[];
+ assignees: string[];
+ sources: string[];
+ affected_services: string[];
+}
+
+export type GetIncidentsParams = {
+ confirmed: boolean;
+ limit: number;
+ offset: number;
+ sorting: { id: string; desc: boolean };
+ filters: Filters | {};
+};
+
+export function buildIncidentsUrl(apiUrl: string, params: GetIncidentsParams) {
+ const filtersParams = new URLSearchParams();
+
+ Object.entries(params.filters).forEach(([key, value]) => {
+ if (value.length == 0) {
+ filtersParams.delete(key as string);
+ } else {
+ value.forEach((s: string) => {
+ filtersParams.append(key, s);
+ });
+ }
+ });
+
+ return `${apiUrl}/incidents?confirmed=${params.confirmed}&limit=${params.limit}&offset=${params.offset}&sorting=${
+ params.sorting.desc ? "-" : ""
+ }${params.sorting.id}&${filtersParams.toString()}`;
+}
+
+export async function getIncidents(
+ apiUrl: string,
+ session: Session | null,
+ params: GetIncidentsParams
+) {
+ if (!session) {
+ return null;
+ }
+ const url = buildIncidentsUrl(apiUrl, params);
+ return (await fetcher(
+ url,
+ session.accessToken
+ )) as Promise;
+}
diff --git a/keep-ui/shared/lib/KeepApiError.ts b/keep-ui/shared/lib/KeepApiError.ts
new file mode 100644
index 000000000..92727a309
--- /dev/null
+++ b/keep-ui/shared/lib/KeepApiError.ts
@@ -0,0 +1,24 @@
+// Custom Error Class
+
+export class KeepApiError extends Error {
+ url: string;
+ proposedResolution: string;
+ statusCode: number | undefined;
+
+ constructor(
+ message: string,
+ url: string,
+ proposedResolution: string,
+ statusCode?: number
+ ) {
+ super(message);
+ this.name = "KeepApiError";
+ this.url = url;
+ this.proposedResolution = proposedResolution;
+ this.statusCode = statusCode;
+ }
+
+ toString() {
+ return `${this.name}: ${this.message} - ${this.url} - ${this.proposedResolution} - ${this.statusCode}`;
+ }
+}
diff --git a/keep-ui/tailwind.config.js b/keep-ui/tailwind.config.js
index 3e6a5b5f5..da660de41 100644
--- a/keep-ui/tailwind.config.js
+++ b/keep-ui/tailwind.config.js
@@ -5,6 +5,7 @@ module.exports = {
"./components/**/*.{js,ts,jsx,tsx}",
"./entities/**/*.{js,ts,jsx,tsx}",
"./features/**/*.{js,ts,jsx,tsx}",
+ "./shared/**/*.{js,ts,jsx,tsx}",
"./node_modules/@tremor/**/*.{js,ts,jsx,tsx}",
],
darkMode: "class",
diff --git a/keep-ui/tsconfig.json b/keep-ui/tsconfig.json
index 6d7822423..302b30407 100644
--- a/keep-ui/tsconfig.json
+++ b/keep-ui/tsconfig.json
@@ -27,7 +27,8 @@
"@/pages/*": ["./pages/*"],
"@/utils/*": ["./utils/*"],
"@/entities/*": ["./entities/*"],
- "@/features/*": ["./features/*"]
+ "@/features/*": ["./features/*"],
+ "@/shared/*": ["./shared/*"]
}
},
"include": [
diff --git a/keep-ui/utils/fetcher.ts b/keep-ui/utils/fetcher.ts
index 1b1446b1b..6a0287675 100644
--- a/keep-ui/utils/fetcher.ts
+++ b/keep-ui/utils/fetcher.ts
@@ -1,4 +1,4 @@
-import { KeepApiError } from '../app/error';
+import { KeepApiError } from "@/shared/lib/KeepApiError";
export const fetcher = async (
url: string,
@@ -17,7 +17,7 @@ export const fetcher = async (
// if the response has detail field, throw the detail field
if (response.headers.get("content-type")?.includes("application/json")) {
const data = await response.json();
- if(response.status === 401) {
+ if (response.status === 401) {
throw new KeepApiError(
`${data.message || data.detail}`,
url,
diff --git a/keep-ui/utils/hooks/useIncidents.ts b/keep-ui/utils/hooks/useIncidents.ts
index 4c4bb3bb4..09c1f91cf 100644
--- a/keep-ui/utils/hooks/useIncidents.ts
+++ b/keep-ui/utils/hooks/useIncidents.ts
@@ -63,7 +63,9 @@ export const useIncidents = (
return {
...swrValue,
- isLoading: swrValue.isLoading || sessionStatus === "loading",
+ isLoading:
+ swrValue.isLoading ||
+ (!options.fallbackData && sessionStatus === "loading"),
};
};