- {" "}
{showUnreadOnly ? (
) : (
@@ -94,7 +90,7 @@ const NotificationCenterComponent = forwardRef(
: }
onClick={markAllRead}
disabled={!unreadCount}
diff --git a/client/src/contexts/SocketIO/useSocket.jsx b/client/src/contexts/SocketIO/useSocket.jsx
index b2f20e459..a68a03e7e 100644
--- a/client/src/contexts/SocketIO/useSocket.jsx
+++ b/client/src/contexts/SocketIO/useSocket.jsx
@@ -9,9 +9,10 @@ import {
GET_NOTIFICATIONS,
GET_UNREAD_COUNT,
MARK_ALL_NOTIFICATIONS_READ,
- MARK_NOTIFICATION_READ
+ MARK_NOTIFICATION_READ,
+ UPDATE_NOTIFICATIONS_READ_FRAGMENT
} from "../../graphql/notifications.queries.js";
-import { gql, useMutation } from "@apollo/client";
+import { useMutation } from "@apollo/client";
import { useTranslation } from "react-i18next";
const SocketContext = createContext(null);
@@ -290,7 +291,17 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser, scenarioNot
});
notification.info({
- message: t("notifications.labels.notification-popup-title", { ro_number: jobRoNumber }),
+ message: (
+ {
+ markNotificationRead({ variables: { id: notificationId } })
+ .then(() => navigate(`/manage/jobs/${jobId}`))
+ .catch((e) => console.error(`Error marking notification read: ${e?.message || ""}`));
+ }}
+ >
+ {t("notifications.labels.notification-popup-title", { ro_number: jobRoNumber })}
+
+ ),
description: (
+ "
+ aws --endpoint-url=http://localstack:4566 ses verify-domain-identity --domain imex.online --region ca-central-1
+ aws --endpoint-url=http://localstack:4566 ses verify-email-identity --email-address noreply@imex.online --region ca-central-1
+ aws --endpoint-url=http://localstack:4566 secretsmanager create-secret --name CHATTER_PRIVATE_KEY --secret-string file:///tmp/certs/io-ftp-test.key
+ aws --endpoint-url=http://localstack:4566 logs create-log-group --log-group-name development --region ca-central-1
+ aws --endpoint-url=http://localstack:4566 s3api create-bucket --bucket imex-large-log --create-bucket-configuration LocationConstraint=ca-central-1
+ "
+
+networks:
+ redis-cluster-net:
+ driver: bridge
+
+volumes:
+ node-app-npm-cache:
+ redis-node-1-data:
+ redis-node-2-data:
+ redis-node-3-data:
+ redis-lock:
diff --git a/hasura/metadata/tables.yaml b/hasura/metadata/tables.yaml
index 258a6860b..63a512bab 100644
--- a/hasura/metadata/tables.yaml
+++ b/hasura/metadata/tables.yaml
@@ -198,6 +198,14 @@
- name: user
using:
foreign_key_constraint_on: useremail
+ array_relationships:
+ - name: notifications
+ using:
+ foreign_key_constraint_on:
+ column: associationid
+ table:
+ name: notifications
+ schema: public
select_permissions:
- role: user
permission:
@@ -3484,6 +3492,13 @@
table:
name: notes
schema: public
+ - name: notifications
+ using:
+ foreign_key_constraint_on:
+ column: jobid
+ table:
+ name: notifications
+ schema: public
- name: parts_dispatches
using:
foreign_key_constraint_on:
@@ -6732,6 +6747,13 @@
table:
name: ioevents
schema: public
+ - name: job_watchers
+ using:
+ foreign_key_constraint_on:
+ column: user_email
+ table:
+ name: job_watchers
+ schema: public
- name: messages
using:
foreign_key_constraint_on:
diff --git a/nginx-websocket.conf b/nginx-websocket.conf
new file mode 100644
index 000000000..3f55e6491
--- /dev/null
+++ b/nginx-websocket.conf
@@ -0,0 +1,45 @@
+events {
+ worker_connections 1024;
+}
+
+http {
+ upstream node_app {
+ ip_hash; # Enables session persistence based on client IP
+ server node-app-1:4000;
+ server node-app-2:4000;
+ server node-app-3:4000;
+ }
+
+ # WebSocket upgrade configuration
+ map $http_upgrade $connection_upgrade {
+ default upgrade;
+ '' close;
+ }
+
+ server {
+ listen 80;
+
+ location / {
+ proxy_pass http://node_app;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+
+ # WebSocket headers
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection $connection_upgrade;
+ proxy_read_timeout 86400; # Keep WebSocket connections alive (24 hours)
+ }
+
+ # Health check endpoint
+ location /health {
+ proxy_pass http://node_app;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ }
+ }
+}
diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js
index 542f3b8bd..b1b62cd36 100644
--- a/server/graphql-client/queries.js
+++ b/server/graphql-client/queries.js
@@ -2758,6 +2758,7 @@ exports.INSERT_NOTIFICATIONS_MUTATION = ` mutation INSERT_NOTIFICATIONS($object
}
}`;
+// REMEMBER: Update the cache_bodyshop event in hasura to include any added fields
exports.GET_BODYSHOP_BY_ID = `
query GET_BODYSHOP_BY_ID($id: uuid!) {
bodyshops_by_pk(id: $id) {
diff --git a/server/routes/miscellaneousRoutes.js b/server/routes/miscellaneousRoutes.js
index 2fa04a552..a3f3ca281 100644
--- a/server/routes/miscellaneousRoutes.js
+++ b/server/routes/miscellaneousRoutes.js
@@ -142,4 +142,17 @@ router.post("/alertcheck", eventAuthorizationMiddleware, alertCheck);
// Redis Cache Routes
router.post("/bodyshop-cache", eventAuthorizationMiddleware, updateBodyshopCache);
+// Health Check for docker-compose-cluster load balancer, only available in development
+if (process.env.NODE_ENV === "development") {
+ router.get("/health", (req, res) => {
+ const healthStatus = {
+ status: "healthy",
+ timestamp: new Date().toISOString(),
+ environment: process.env.NODE_ENV || "unknown",
+ uptime: process.uptime()
+ };
+ res.status(200).json(healthStatus);
+ });
+}
+
module.exports = router;