diff --git a/frontend/src/views/playbook/ListPlaybook.jsx b/frontend/src/views/playbook/ListPlaybook.jsx
index 807df157..32b3ee5d 100644
--- a/frontend/src/views/playbook/ListPlaybook.jsx
+++ b/frontend/src/views/playbook/ListPlaybook.jsx
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
import { Card, Col, Row } from "react-bootstrap";
import CrudButton from "../../components/Button/CrudButton";
import TablePlaybook from "./components/TablePlaybook";
-import Search from "../../components/Search/Search";
+import ListViewHeader from "../../components/ListViewHeader/ListViewHeader";
import { getPlaybooks } from "../../api/services/playbooks";
import AdvancedPagination from "../../components/Pagination/AdvancedPagination";
import { getMinifiedTaxonomy } from "../../api/services/taxonomies";
@@ -22,11 +22,24 @@ const ListPlaybook = () => {
const [wordToSearch, setWordToSearch] = useState("");
const [order] = useState("");
+ const [refresh, setRefresh] = useState(true);
function updatePage(chosenPage) {
setCurrentPage(chosenPage);
}
+ const reloadPage = () => {
+ setLoading(true);
+ setRefresh((prev) => !prev);
+ };
+
+ const clearFilters = () => {
+ setLoading(true);
+ setWordToSearch("");
+ setCurrentPage(1);
+ setRefresh((prev) => !prev);
+ };
+
useEffect(() => {
getPlaybooks(currentPage, wordToSearch, order)
.then((response) => {
@@ -53,7 +66,7 @@ const ListPlaybook = () => {
});
setTaxonomyNames(dicTaxonomy);
});
- }, [countItems, currentPage, isModify, wordToSearch]);
+ }, [countItems, currentPage, isModify, wordToSearch, refresh]);
return (
@@ -61,14 +74,17 @@ const ListPlaybook = () => {
-
-
-
-
-
-
-
-
+
+
+
diff --git a/frontend/src/views/priority/ListPriority.jsx b/frontend/src/views/priority/ListPriority.jsx
index ba0cf0fa..3bea06b2 100644
--- a/frontend/src/views/priority/ListPriority.jsx
+++ b/frontend/src/views/priority/ListPriority.jsx
@@ -1,7 +1,7 @@
import React, { useEffect, useState } from "react";
import { Card, Col, Row } from "react-bootstrap";
import Alert from "../../components/Alert/Alert";
-import Search from "../../components/Search/Search";
+import ListViewHeader from "../../components/ListViewHeader/ListViewHeader";
import CrudButton from "../../components/Button/CrudButton";
import { getPriorities } from "../../api/services/priorities";
import TablePriorities from "./components/TablePriorities";
@@ -20,11 +20,24 @@ const ListPriorities = () => {
const { t } = useTranslation();
const [order, setOrder] = useState("name");
+ const [refresh, setRefresh] = useState(true);
function updatePage(chosenPage) {
setCurrentPage(chosenPage);
}
+ const reloadPage = () => {
+ setLoading(true);
+ setRefresh((prev) => !prev);
+ };
+
+ const clearFilters = () => {
+ setLoading(true);
+ setWordToSearch("");
+ setCurrentPage(1);
+ setRefresh((prev) => !prev);
+ };
+
useEffect(() => {
getPriorities(currentPage, wordToSearch, order)
.then((response) => {
@@ -43,24 +56,28 @@ const ListPriorities = () => {
setShowAlert(true);
setLoading(false);
});
- }, [currentPage, wordToSearch, order]);
+ }, [currentPage, wordToSearch, order, refresh]);
const resetShowAlert = () => {
setShowAlert(false);
};
return (
-
-
+
+
+
-
-
-
-
-
-
-
-
+
+
+
{
-
+
+
);
};
diff --git a/frontend/src/views/report/EditReport.jsx b/frontend/src/views/report/EditReport.jsx
index 64f06246..942e1c73 100644
--- a/frontend/src/views/report/EditReport.jsx
+++ b/frontend/src/views/report/EditReport.jsx
@@ -1,5 +1,5 @@
import React, { useEffect, useState } from "react";
-import { Card, Spinner } from "react-bootstrap";
+import { Card, Col, Row, Spinner } from "react-bootstrap";
import { useParams } from "react-router-dom";
import FormReport from "./components/FormReport";
import { putReport, getReport } from "../../api/services/reports";
@@ -62,8 +62,9 @@ const EditReport = () => {
};
return (
-
-
+
+
+
{t("ngen.report.edit")}
@@ -71,8 +72,9 @@ const EditReport = () => {
{loading && }
-
-
+
+
+
);
};
diff --git a/frontend/src/views/report/ListReport.jsx b/frontend/src/views/report/ListReport.jsx
index 484d39e8..1d3311c1 100644
--- a/frontend/src/views/report/ListReport.jsx
+++ b/frontend/src/views/report/ListReport.jsx
@@ -1,7 +1,7 @@
import React, { useEffect, useState } from "react";
import { getReports } from "../../api/services/reports";
import { Card, Col, Row } from "react-bootstrap";
-import Search from "../../components/Search/Search";
+import ListViewHeader from "../../components/ListViewHeader/ListViewHeader";
import CrudButton from "../../components/Button/CrudButton";
import TableReport from "./components/TableReport";
import { getMinifiedTaxonomy } from "../../api/services/taxonomies";
@@ -22,12 +22,25 @@ const ListReport = () => {
const [wordToSearch, setWordToSearch] = useState("");
const [order, setOrder] = useState("taxonomy__name");
+ const [refresh, setRefresh] = useState(true);
const { t } = useTranslation();
function updatePage(chosenPage) {
setCurrentPage(chosenPage);
}
+ const reloadPage = () => {
+ setLoading(true);
+ setRefresh((prev) => !prev);
+ };
+
+ const clearFilters = () => {
+ setLoading(true);
+ setWordToSearch("");
+ setCurrentPage(1);
+ setRefresh((prev) => !prev);
+ };
+
useEffect(() => {
getReports(currentPage, wordToSearch, order)
.then((response) => {
@@ -53,20 +66,24 @@ const ListReport = () => {
});
setTaxonomyNames(dicTaxonomy);
});
- }, [currentPage, wordToSearch, order]);
+ }, [currentPage, wordToSearch, order, refresh]);
return (
-
-
+
+
+
-
-
-
-
-
-
-
-
+
+
+
{
-
+
+
);
};
diff --git a/frontend/src/views/setting/EditSetting.jsx b/frontend/src/views/setting/EditSetting.jsx
index d2f8c047..909da6ca 100644
--- a/frontend/src/views/setting/EditSetting.jsx
+++ b/frontend/src/views/setting/EditSetting.jsx
@@ -305,8 +305,9 @@ const EditSetting = () => {
);
return (
-
-
+
+
+
{t("systemConfig")}
@@ -388,8 +389,9 @@ const EditSetting = () => {
)}
-
-
+
+
+
);
};
diff --git a/frontend/src/views/state/CreateState.jsx b/frontend/src/views/state/CreateState.jsx
index d9669abd..57d43df2 100644
--- a/frontend/src/views/state/CreateState.jsx
+++ b/frontend/src/views/state/CreateState.jsx
@@ -1,4 +1,5 @@
import React, { useState } from "react";
+import { Col, Row } from "react-bootstrap";
import FormState from "./components/FormState";
import Alert from "../../components/Alert/Alert";
import { postState, putState } from "../../api/services/states";
@@ -62,10 +63,12 @@ const AddState = () => {
};
return (
-
-
-
-
+
+
+
+
+
+
);
};
diff --git a/frontend/src/views/state/EditState.jsx b/frontend/src/views/state/EditState.jsx
index 65f1161d..0ce72669 100644
--- a/frontend/src/views/state/EditState.jsx
+++ b/frontend/src/views/state/EditState.jsx
@@ -1,5 +1,6 @@
import React, { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
+import { Col, Row } from "react-bootstrap";
import FormState from "./components/FormState";
import { putState, getState, getMinifiedState } from "../../api/services/states";
import ListEdge from "../edge/ListEdge";
@@ -68,10 +69,12 @@ const EditState = () => {
});
};
return (
-
-
-
-
+
+
+
+
+
+
);
};
export default EditState;
diff --git a/frontend/src/views/state/ListState.jsx b/frontend/src/views/state/ListState.jsx
index 27b98190..bd94824f 100644
--- a/frontend/src/views/state/ListState.jsx
+++ b/frontend/src/views/state/ListState.jsx
@@ -1,7 +1,7 @@
import React, { useEffect, useState } from "react";
import { getStates } from "../../api/services/states";
import { Card, Col, Row } from "react-bootstrap";
-import Search from "../../components/Search/Search";
+import ListViewHeader from "../../components/ListViewHeader/ListViewHeader";
import CrudButton from "../../components/Button/CrudButton";
import TableStates from "./components/TableStates";
import AdvancedPagination from "../../components/Pagination/AdvancedPagination";
@@ -20,6 +20,7 @@ const ListState = () => {
const [wordToSearch, setWordToSearch] = useState("");
const [order] = useState("");
+ const [refresh, setRefresh] = useState(true);
const [showAlert, setShowAlert] = useState(false);
@@ -27,6 +28,18 @@ const ListState = () => {
setCurrentPage(chosenPage);
}
+ const reloadPage = () => {
+ setLoading(true);
+ setRefresh((prev) => !prev);
+ };
+
+ const clearFilters = () => {
+ setLoading(true);
+ setWordToSearch("");
+ setCurrentPage(1);
+ setRefresh((prev) => !prev);
+ };
+
useEffect(() => {
getStates(currentPage, wordToSearch, order)
.then((response) => {
@@ -44,26 +57,24 @@ const ListState = () => {
setShowAlert(true);
setLoading(false);
});
- }, [currentPage, wordToSearch, order, isModify]);
+ }, [currentPage, wordToSearch, order, isModify, refresh]);
return (
-
-
+
+
+
-
-
-
-
-
-
-
-
+
+
+
@@ -84,7 +95,8 @@ const ListState = () => {
-
+
+
);
};
diff --git a/frontend/src/views/tag/ListTag.jsx b/frontend/src/views/tag/ListTag.jsx
index 3b49856d..216ae0f5 100644
--- a/frontend/src/views/tag/ListTag.jsx
+++ b/frontend/src/views/tag/ListTag.jsx
@@ -2,9 +2,9 @@ import React, { useEffect, useState } from "react";
import { Button, Card, Col, Row, Table } from "react-bootstrap";
import CrudButton from "components/Button/CrudButton";
import AdvancedPagination from "components/Pagination/AdvancedPagination";
-import Search from "components/Search/Search";
import { useTranslation } from "react-i18next";
import { getTags, deleteTag } from "api/services/tags";
+import ListViewHeader from "components/ListViewHeader/ListViewHeader";
import CreateTagModal from "./components/CreateTagModal";
import ModalConfirm from "components/Modal/ModalConfirm";
@@ -68,6 +68,13 @@ const ListTag = (props) => {
setIsModify(!isModify);
};
+ const clearFilters = () => {
+ setLoading(true);
+ setWordToSearch("");
+ setCurrentPage(1);
+ reloadPage();
+ };
+
const ifConfirm = (response) => {
setModalCreate(false);
reloadPage();
@@ -75,33 +82,20 @@ const ListTag = (props) => {
return (
-
+
+
+
-
-
-
-
-
-
-
+ {
@@ -112,8 +106,7 @@ const ListTag = (props) => {
}}
optionalPermissions={["create_tag", "create_tag_network_admin"]}
/>
-
-
+
@@ -207,7 +200,9 @@ const ListTag = (props) => {
reloadPage();
});
}}
- />
+ />
+
+
);
};
diff --git a/frontend/src/views/taxonomy/ListTaxonomies.jsx b/frontend/src/views/taxonomy/ListTaxonomies.jsx
index d79bb1b7..40424338 100644
--- a/frontend/src/views/taxonomy/ListTaxonomies.jsx
+++ b/frontend/src/views/taxonomy/ListTaxonomies.jsx
@@ -4,9 +4,8 @@ import CrudButton from "../../components/Button/CrudButton";
import Alert from "../../components/Alert/Alert";
import AdvancedPagination from "../../components/Pagination/AdvancedPagination";
import { getMinifiedTaxonomy, getTaxonomies } from "../../api/services/taxonomies";
-import Search from "../../components/Search/Search";
import TableTaxonomy from "./components/TableTaxonomy";
-import ButtonFilter from "../../components/Button/ButtonFilter";
+import ListViewHeader from "../../components/ListViewHeader/ListViewHeader";
import FilterSelectUrl from "../../components/Filter/FilterSelectUrl";
import FilterSelect from "../../components/Filter/FilterSelect";
import { useTranslation } from "react-i18next";
@@ -51,11 +50,38 @@ const ListTaxonomies = () => {
const [valueReportsFilter, setValueReportsFilter] = useState(null);
const [order, setOrder] = useState("name");
+ const [refresh, setRefresh] = useState(true);
function updatePage(chosenPage) {
setCurrentPage(chosenPage);
}
+ const reloadPage = () => {
+ setLoading(true);
+ setRefresh((prev) => !prev);
+ };
+
+ const clearFilters = () => {
+ setLoading(true);
+ setWordToSearch("");
+ setParentFilter("");
+ setValueParentFilter(null);
+ setTaxonomyGroupFilter("");
+ setValueGroupFilter(null);
+ setAliasFilter("");
+ setValueAliasFilter(null);
+ setTypeFilter("");
+ setValueTypeFilter(null);
+ setNeedsReviewFilter("");
+ setValueNeedsReviewFilter(null);
+ setActiveFilter("");
+ setValueActiveFilter(null);
+ setReportsFilter("");
+ setValueReportsFilter(null);
+ setCurrentPage(1);
+ setRefresh((prev) => !prev);
+ };
+
useEffect(() => {
getMinifiedTaxonomy().then((response) => {
setMinifiedTaxonomiesParent(
@@ -127,7 +153,7 @@ const ListTaxonomies = () => {
setShowAlert(true);
setLoading(false);
});
- }, [currentPage, isModify, order, wordToSearch, parentFilter, taxonomyGroupFilter, aliasFilter, typeFilter, activeFilter, needsReviewFilter, reportsFilter]);
+ }, [currentPage, isModify, order, wordToSearch, parentFilter, taxonomyGroupFilter, aliasFilter, typeFilter, activeFilter, needsReviewFilter, reportsFilter, refresh]);
const optionsTaxonomyType = [
{ value: "vulnerability", label: t("ngen.vulnerability") },
@@ -156,23 +182,19 @@ const ListTaxonomies = () => {
-
-
-
-
-
-
-
-
-
-
-
+
+
+
diff --git a/frontend/src/views/taxonomyGroup/ListTaxonomyGroups.jsx b/frontend/src/views/taxonomyGroup/ListTaxonomyGroups.jsx
index 606e4038..c801fc88 100644
--- a/frontend/src/views/taxonomyGroup/ListTaxonomyGroups.jsx
+++ b/frontend/src/views/taxonomyGroup/ListTaxonomyGroups.jsx
@@ -4,8 +4,8 @@ import { Link } from "react-router-dom";
import CrudButton from "../../components/Button/CrudButton";
import Alert from "../../components/Alert/Alert";
import AdvancedPagination from "../../components/Pagination/AdvancedPagination";
-import Search from "../../components/Search/Search";
import TableTaxonomyGroup from "./components/TableTaxonomyGroup";
+import ListViewHeader from "../../components/ListViewHeader/ListViewHeader";
import { useTranslation } from "react-i18next";
import { getMinifiedTaxonomyGroups, getTaxonomyGroups } from "../../api/services/taxonomyGroups";
@@ -24,11 +24,24 @@ const listTaxonomyGroups = () => {
const [wordToSearch, setWordToSearch] = useState("");
const [order, setOrder] = useState("name");
+ const [refresh, setRefresh] = useState(true);
function updatePage(chosenPage) {
setCurrentPage(chosenPage);
}
+ const reloadPage = () => {
+ setLoading(true);
+ setRefresh((prev) => !prev);
+ };
+
+ const clearFilters = () => {
+ setLoading(true);
+ setWordToSearch("");
+ setCurrentPage(1);
+ setRefresh((prev) => !prev);
+ };
+
useEffect(() => {
getTaxonomyGroups(currentPage, wordToSearch, order)
.then((response) => {
@@ -48,7 +61,7 @@ const listTaxonomyGroups = () => {
setLoading(false);
});
- }, [currentPage, isModify, order, wordToSearch]);
+ }, [currentPage, isModify, order, wordToSearch, refresh]);
return (
@@ -56,21 +69,17 @@ const listTaxonomyGroups = () => {
-
-
-
-
-
-
-
-
-
+
+
+
{
const [valueFeedFilter, setValueFeedFilter] = useState(null);
const [valueTaxonomyFilter, setValueTaxonomyFilter] = useState(null);
const [order, setOrder] = useState("event_feed__name");
+ const [refresh, setRefresh] = useState(true);
const [taxonomyNames, setTaxonomyNames] = useState({});
const [feedNames, setFeedNames] = useState({});
@@ -51,6 +51,22 @@ const ListTemplete = () => {
setCurrentPage(chosenPage);
}
+ const reloadPage = () => {
+ setLoading(true);
+ setRefresh((prev) => !prev);
+ };
+
+ const clearFilters = () => {
+ setLoading(true);
+ setWordToSearch("");
+ setTaxonomyFilter("");
+ setValueTaxonomyFilter(null);
+ setFeedFilter("");
+ setValueFeedFilter(null);
+ setCurrentPage(1);
+ setRefresh((prev) => !prev);
+ };
+
useEffect(() => {
getMinifiedTaxonomy().then((response) => {
let listTaxonomies = [];
@@ -124,7 +140,7 @@ const ListTemplete = () => {
setShowAlert(true);
setLoading(false);
});
- }, [currentPage, taxonomyFilter, feedFilter, wordToSearch, order, isModify]);
+ }, [currentPage, taxonomyFilter, feedFilter, wordToSearch, order, isModify, refresh]);
const resetShowAlert = () => {
setShowAlert(false);
@@ -136,17 +152,19 @@ const ListTemplete = () => {
-
-
-
-
-
-
-
-
-
-
-
+
+
+
diff --git a/frontend/src/views/tlp/ListTLP.jsx b/frontend/src/views/tlp/ListTLP.jsx
index a28a089a..ed6a73c6 100644
--- a/frontend/src/views/tlp/ListTLP.jsx
+++ b/frontend/src/views/tlp/ListTLP.jsx
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
import { Card, Col, Form, Row, Spinner, Table } from "react-bootstrap";
import Alert from "../../components/Alert/Alert";
import { getTLP } from "../../api/services/tlp";
-import Search from "../../components/Search/Search";
+import ListViewHeader from "../../components/ListViewHeader/ListViewHeader";
import Ordering from "../../components/Ordering/Ordering";
import { useTranslation } from "react-i18next";
@@ -14,6 +14,18 @@ const ListTLP = () => {
const [wordToSearch, setWordToSearch] = useState("");
const [order, setOrder] = useState("");
+ const [refresh, setRefresh] = useState(true);
+
+ const reloadPage = () => {
+ setLoading(true);
+ setRefresh((prev) => !prev);
+ };
+
+ const clearFilters = () => {
+ setLoading(true);
+ setWordToSearch("");
+ setRefresh((prev) => !prev);
+ };
const textareaStyle = {
resize: "none",
@@ -34,7 +46,7 @@ const ListTLP = () => {
.finally(() => {
setLoading(false);
});
- }, [wordToSearch, order]);
+ }, [wordToSearch, order, refresh]);
const resetShowAlert = () => {
setShowAlert(false);
@@ -48,18 +60,14 @@ const ListTLP = () => {
-
-
-
-
-
-
-
+
diff --git a/frontend/src/views/tools/lookup/ShowLookup.jsx b/frontend/src/views/tools/lookup/ShowLookup.jsx
index ae959265..8c68dbaa 100644
--- a/frontend/src/views/tools/lookup/ShowLookup.jsx
+++ b/frontend/src/views/tools/lookup/ShowLookup.jsx
@@ -1,5 +1,5 @@
import React, { useEffect, useState } from "react";
-import { Card } from "react-bootstrap";
+import { Card, Col, Row } from "react-bootstrap";
import { useLocation, useParams } from "react-router-dom";
import Alert from "components/Alert/Alert";
import FormLookup from "views/tools/lookup/FormLookup";
@@ -10,18 +10,20 @@ const ShowLookup = () => {
const { t } = useTranslation();
return (
- <>
-
-
-
- {t("ngen.lookup")}
-
-
-
-
-
-
- >
+
+
+
+
+
+ {t("ngen.lookup")}
+
+
+
+
+
+
+
+
);
};
diff --git a/frontend/src/views/user/CreateUser.jsx b/frontend/src/views/user/CreateUser.jsx
index 671aa1f7..e975196d 100644
--- a/frontend/src/views/user/CreateUser.jsx
+++ b/frontend/src/views/user/CreateUser.jsx
@@ -1,5 +1,5 @@
import React, { useEffect, useState } from "react";
-import { Card } from "react-bootstrap";
+import { Card, Col, Row } from "react-bootstrap";
import { postUser } from "../../api/services/users";
import { getMinifiedPriority } from "../../api/services/priorities";
import Alert from "../../components/Alert/Alert";
@@ -70,8 +70,9 @@ const AddUser = () => {
});
};
return (
- <>
-
+
+
+
{t("w.add")} {t("ngen.user")}
@@ -87,8 +88,9 @@ const AddUser = () => {
passwordRequired={true}
/>
-
- >
+
+
+
);
};
diff --git a/frontend/src/views/user/EditUser.jsx b/frontend/src/views/user/EditUser.jsx
index 07700099..41523dd4 100644
--- a/frontend/src/views/user/EditUser.jsx
+++ b/frontend/src/views/user/EditUser.jsx
@@ -1,5 +1,5 @@
import React, { useEffect, useState } from "react";
-import { Card } from "react-bootstrap";
+import { Card, Col, Row } from "react-bootstrap";
import { getUser, putUser } from "../../api/services/users";
import { useLocation, useParams } from "react-router-dom";
import Alert from "../../components/Alert/Alert";
@@ -76,8 +76,9 @@ const EditUser = () => {
});
};
return (
- <>
-
+
+
+
{t("w.edit")} {t("ngen.user")}
@@ -94,8 +95,9 @@ const EditUser = () => {
isEdit={true}
/>
-
- >
+
+
+
);
};
diff --git a/frontend/src/views/user/ListUser.jsx b/frontend/src/views/user/ListUser.jsx
index 81511568..f94f222f 100644
--- a/frontend/src/views/user/ListUser.jsx
+++ b/frontend/src/views/user/ListUser.jsx
@@ -1,7 +1,7 @@
import React, { useEffect, useState } from "react";
import { Card, Col, Row } from "react-bootstrap";
import TableUsers from "./components/TableUsers";
-import Search from "../../components/Search/Search";
+import ListViewHeader from "../../components/ListViewHeader/ListViewHeader";
import CrudButton from "../../components/Button/CrudButton";
import { getUsers } from "../../api/services/users";
import AdvancedPagination from "../../components/Pagination/AdvancedPagination";
@@ -25,11 +25,24 @@ function ListUser() {
const [wordToSearch, setWordToSearch] = useState("");
const [order, setOrder] = useState("");
+ const [refresh, setRefresh] = useState(true);
function updatePage(chosenPage) {
setCurrentPage(chosenPage);
}
+ const reloadPage = () => {
+ setLoading(true);
+ setRefresh((prev) => !prev);
+ };
+
+ const clearFilters = () => {
+ setLoading(true);
+ setWordToSearch("");
+ setCurrentPage(1);
+ setRefresh((prev) => !prev);
+ };
+
const resetShowAlert = () => {
setShowAlert(false);
};
@@ -51,31 +64,28 @@ function ListUser() {
setShowAlert(true);
setLoading(false);
});
- }, [currentPage, wordToSearch, order, isModify]);
+ }, [currentPage, wordToSearch, order, isModify, refresh]);
if (error) {
return {t("user.error.fetch")}
;
}
return (
-
-
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
-
+
+
);
}
diff --git a/ngen/tests/api/test_health.py b/ngen/tests/api/test_health.py
new file mode 100644
index 00000000..b48f471f
--- /dev/null
+++ b/ngen/tests/api/test_health.py
@@ -0,0 +1,27 @@
+from django.urls import reverse
+from rest_framework import status
+from rest_framework.test import APITestCase
+
+from ngen.models import User
+
+
+class HealthEndpointTestCase(APITestCase):
+ """Tests for the liveness endpoint used by the frontend connection indicator"""
+
+ fixtures = ["tests/priority.json"]
+
+ def setUp(self):
+ self.url = reverse("health")
+
+ def test_health_returns_200_for_anonymous_user(self):
+ response = self.client.get(self.url)
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.json(), {"status": "ok"})
+
+ def test_health_returns_200_for_authenticated_user(self):
+ User.objects.create_user(
+ username="testuser", password="password", email="test@ngen.test"
+ )
+ self.client.login(username="testuser", password="password")
+ response = self.client.get(self.url)
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
diff --git a/ngen/views/tools.py b/ngen/views/tools.py
index 648e1c42..c9d04989 100644
--- a/ngen/views/tools.py
+++ b/ngen/views/tools.py
@@ -35,6 +35,17 @@ def dispatch(self, request, *args, **kwargs):
return response
+class HealthView(APIView):
+ """Liveness endpoint used by the frontend connection indicator"""
+
+ permission_classes = [permissions.AllowAny]
+ authentication_classes = []
+ schema = None
+
+ def get(self, request):
+ return Response({"status": "ok"}, status=status.HTTP_200_OK)
+
+
class ContentTypeViewSet(viewsets.ReadOnlyModelViewSet):
queryset = ContentType.objects.all()
serializer_class = serializers.ContentTypeSerializer
diff --git a/project/urls.py b/project/urls.py
index 9e98800f..23f36fa7 100644
--- a/project/urls.py
+++ b/project/urls.py
@@ -164,6 +164,7 @@
# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API.
urlpatterns = [
+ path("api/health/", views.tools.HealthView.as_view(), name="health"),
path("api/admin/", admin.site.urls),
path("api/", include(router.urls)),
# path(