diff --git a/package-lock.json b/package-lock.json index 4bfaf39..005bd84 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "eslint-plugin-react-hooks": "^5.2.0", "gcode-preview": "^2.18.0", "keycloak-js": "^26.2.0", - "log4js": "^6.9.1", + "loglevel": "^1.9.2", "moment": "*", "prettier": "^3.5.3", "prettier-eslint": "^16.4.2", @@ -9986,15 +9986,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/date-format": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", - "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", - "license": "MIT", - "engines": { - "node": ">=4.0" - } - }, "node_modules/dayjs": { "version": "1.11.13", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", @@ -16008,27 +15999,10 @@ "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", "license": "MIT" }, - "node_modules/log4js": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", - "integrity": "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==", - "license": "Apache-2.0", - "dependencies": { - "date-format": "^4.0.14", - "debug": "^4.3.4", - "flatted": "^3.2.7", - "rfdc": "^1.3.0", - "streamroller": "^3.1.5" - }, - "engines": { - "node": ">=8.0" - } - }, "node_modules/loglevel": { "version": "1.9.2", "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz", "integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6.0" @@ -21486,12 +21460,6 @@ "node": ">=0.10.0" } }, - "node_modules/rfdc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "license": "MIT" - }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -22819,52 +22787,6 @@ "node": ">= 0.4" } }, - "node_modules/streamroller": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", - "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", - "license": "MIT", - "dependencies": { - "date-format": "^4.0.14", - "debug": "^4.3.4", - "fs-extra": "^8.1.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/streamroller/node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/streamroller/node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "license": "MIT", - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/streamroller/node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "license": "MIT", - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", diff --git a/package.json b/package.json index e1b964e..f7b0ca1 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "eslint-plugin-react-hooks": "^5.2.0", "gcode-preview": "^2.18.0", "keycloak-js": "^26.2.0", - "log4js": "^6.9.1", + "loglevel": "^1.9.2", "moment": "*", "prettier": "^3.5.3", "prettier-eslint": "^16.4.2", diff --git a/src/App.css b/src/App.css index 709352a..8c52b6d 100644 --- a/src/App.css +++ b/src/App.css @@ -22,11 +22,14 @@ .ant-input, .ant-input-number .ant-input-number-input, .ant-segmented-item-label, -.ant-badge-status-text { +.ant-badge-status-text, +.ant-tree-title, +.ant-select { font-family: 'DM Sans'; } -.ant-typography code { +.ant-typography code, +.ant-typography pre { font-family: 'DM Mono'; } @@ -115,6 +118,10 @@ code { padding: 0 !important; } +.ant-popover-inner:has(.keyboard-shortcut-tooltip) { + padding: 8px !important; +} + /* --- Start of src/index.css --- */ body { margin: 0; diff --git a/src/App.jsx b/src/App.jsx index 43b8132..d6e9d07 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -44,7 +44,7 @@ import StockAuditInfo from './components/Dashboard/Inventory/StockAudits/StockAu import Dashboard from './components/Dashboard/Dashboard.jsx' import PrivateRoute from './components/PrivateRoute' import './App.css' -import { SocketProvider } from './components/Dashboard/context/SocketContext.js' +import { PrintServerProvider } from './components/Dashboard/context/PrintServerContext.js' import { AuthProvider } from './components/Dashboard/context/AuthContext.js' import { SpotlightProvider } from './components/Dashboard/context/SpotlightContext.js' import StockEvents from './components/Dashboard/Inventory/StockEvents.jsx' @@ -61,8 +61,8 @@ import NoteTypes from './components/Dashboard/Management/NoteTypes.jsx' import NoteTypeInfo from './components/Dashboard/Management/NoteTypes/NoteTypeInfo.jsx' import SessionStorage from './components/Dashboard/Developer/SessionStorage.jsx' import AuthContextDebug from './components/Dashboard/Developer/AuthContextDebug.jsx' -import SocketContextDebug from './components/Dashboard/Developer/SocketContextDebug.jsx' -import { NotificationProvider } from './components/Dashboard/context/NotificationContext.js' +import PrintServerContextDebug from './components/Dashboard/Developer/PrintServerContextDebug.jsx' +import { ApiServerProvider } from './components/Dashboard/context/ApiServerContext.js' import Users from './components/Dashboard/Management/Users.jsx' import UserInfo from './components/Dashboard/Management/Users/UserInfo.jsx' @@ -74,8 +74,8 @@ const AppContent = () => { - - + + { element={} /> } + path='developer/printservercontextdebug' + element={} /> { /> - - + + diff --git a/src/assets/icons/lockicon.afdesign b/src/assets/icons/lockicon.afdesign new file mode 100644 index 0000000..5ed851b Binary files /dev/null and b/src/assets/icons/lockicon.afdesign differ diff --git a/src/assets/icons/lockicon.min.svg b/src/assets/icons/lockicon.min.svg new file mode 100644 index 0000000..f07fb04 --- /dev/null +++ b/src/assets/icons/lockicon.min.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/lockicon.svg b/src/assets/icons/lockicon.svg new file mode 100644 index 0000000..cd9f0bc --- /dev/null +++ b/src/assets/icons/lockicon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/components/Dashboard/Developer/DeveloperSidebar.jsx b/src/components/Dashboard/Developer/DeveloperSidebar.jsx index 1e359f8..c6c68a5 100644 --- a/src/components/Dashboard/Developer/DeveloperSidebar.jsx +++ b/src/components/Dashboard/Developer/DeveloperSidebar.jsx @@ -1,29 +1,35 @@ import React from 'react' import { useLocation } from 'react-router-dom' import DashboardSidebar from '../common/DashboardSidebar' +import { Typography } from 'antd' + +const { Text } = Typography const items = [ { key: 'sessionstorage', label: 'Session Storage', + icon: 🗃️, path: '/dashboard/developer/sessionstorage' }, { key: 'authcontextdebug', - label: 'Auth Context Debug', + label: 'Auth Debug', + icon: 🔐, path: '/dashboard/developer/authcontextdebug' }, { - key: 'socketcontextdebug', - label: 'Socket Context Debug', - path: '/dashboard/developer/socketcontextdebug' + key: 'printservercontextdebug', + label: 'Print Server Debug', + icon: 🖨️, + path: '/dashboard/developer/printservercontextdebug' } ] const routeKeyMap = { '/dashboard/developer/sessionstorage': 'sessionstorage', '/dashboard/developer/authcontext': 'authcontextdebug', - '/dashboard/developer/socketcontext': 'socketcontextdebug' + '/dashboard/developer/printservercontext': 'printservercontextdebug' } const DeveloperSidebar = (props) => { diff --git a/src/components/Dashboard/Developer/SocketContextDebug.jsx b/src/components/Dashboard/Developer/PrintServerContextDebug.jsx similarity index 82% rename from src/components/Dashboard/Developer/SocketContextDebug.jsx rename to src/components/Dashboard/Developer/PrintServerContextDebug.jsx index 54b0b53..e5f3b9b 100644 --- a/src/components/Dashboard/Developer/SocketContextDebug.jsx +++ b/src/components/Dashboard/Developer/PrintServerContextDebug.jsx @@ -9,13 +9,13 @@ import { message } from 'antd' import ReloadIcon from '../../Icons/ReloadIcon.jsx' -import { SocketContext } from '../context/SocketContext.js' +import { PrintServerContext } from '../context/PrintServerContext.js' import BoolDisplay from '../common/BoolDisplay.jsx' const { Text, Paragraph } = Typography -const SocketContextDebug = () => { - const { socket, error, connecting } = useContext(SocketContext) +const PrintServerContextDebug = () => { + const { printServer, error, connecting } = useContext(PrintServerContext) const [msgApi, contextHolder] = message.useMessage() const actionItems = { @@ -36,9 +36,9 @@ const SocketContextDebug = () => { // Helper to display socket info safely const getSocketInfo = () => { - if (!socket) return 'n/a' + if (!printServer) return 'n/a' // Only show safe properties - const { id, connected, disconnected, nsp } = socket + const { id, connected, disconnected, nsp } = printServer return JSON.stringify({ id, connected, disconnected, nsp }, null, 2) } @@ -55,7 +55,7 @@ const SocketContextDebug = () => {
- + @@ -74,4 +74,4 @@ const SocketContextDebug = () => { ) } -export default SocketContextDebug +export default PrintServerContextDebug diff --git a/src/components/Dashboard/Inventory/FilamentStocks.jsx b/src/components/Dashboard/Inventory/FilamentStocks.jsx index 5be217f..c9275a6 100644 --- a/src/components/Dashboard/Inventory/FilamentStocks.jsx +++ b/src/components/Dashboard/Inventory/FilamentStocks.jsx @@ -16,7 +16,7 @@ import { } from 'antd' import { AuthContext } from '../context/AuthContext' -import { SocketContext } from '../context/SocketContext' +import { PrintServerContext } from '../context/PrintServerContext' import NewFilamentStock from './FilamentStocks/NewFilamentStock' import IdText from '../common/IdText' @@ -41,7 +41,7 @@ const { Text } = Typography const FilamentStocks = () => { const [messageApi, contextHolder] = message.useMessage() const navigate = useNavigate() - const { socket } = useContext(SocketContext) + const { printServer } = useContext(PrintServerContext) const [initialized, setInitialized] = useState(false) const tableRef = useRef() @@ -213,9 +213,9 @@ const FilamentStocks = () => { ) React.useEffect(() => { - if (socket && !initialized) { + if (printServer && !initialized) { setInitialized(true) - socket.on('notify_filamentstock_update', (updateData) => { + printServer.on('notify_filamentstock_update', (updateData) => { console.log('Received filament stock update:', updateData) if (tableRef.current) { tableRef.current.updateData(updateData._id, updateData) @@ -224,12 +224,12 @@ const FilamentStocks = () => { } return () => { - if (socket && initialized) { + if (printServer && initialized) { console.log('Deregistering filament stock update listener') - socket.off('notify_filamentstock_update') + printServer.off('notify_filamentstock_update') } } - }, [socket, initialized]) + }, [printServer, initialized]) const getFilamentStockActionItems = (id) => { return { diff --git a/src/components/Dashboard/Inventory/FilamentStocks/FilamentStockInfo.jsx b/src/components/Dashboard/Inventory/FilamentStocks/FilamentStockInfo.jsx index 1e3a310..2f59597 100644 --- a/src/components/Dashboard/Inventory/FilamentStocks/FilamentStockInfo.jsx +++ b/src/components/Dashboard/Inventory/FilamentStocks/FilamentStockInfo.jsx @@ -19,7 +19,7 @@ import { } from 'antd' import { LoadingOutlined, CaretLeftOutlined } from '@ant-design/icons' import IdText from '../../common/IdText' -import { SocketContext } from '../../context/SocketContext' +import { PrintServerContext } from '../../context/PrintServerContext' import FilamentStockState from '../../common/FilamentStockState' import StockEventTable from '../../common/StockEventTable' import useCollapseState from '../../hooks/useCollapseState' @@ -48,7 +48,7 @@ const FilamentStockInfo = () => { 'filamentStockId' ) const [form] = Form.useForm() - const { socket } = useContext(SocketContext) + const { printServer } = useContext(PrintServerContext) const [collapseState, updateCollapseState] = useCollapseState( 'FilamentStockInfo', { @@ -75,9 +75,9 @@ const FilamentStockInfo = () => { // Add WebSocket event listener for real-time updates useEffect(() => { - if (socket && !initialized && filamentStockId) { + if (printServer && !initialized && filamentStockId) { setInitialized(true) - socket.on('notify_filamentstock_update', (statusUpdate) => { + printServer.on('notify_filamentstock_update', (statusUpdate) => { console.log('GOT FILAMENT STOCK UPDATE', statusUpdate) setFilamentStockData((prevData) => { if (statusUpdate?._id === filamentStockId) { @@ -91,12 +91,12 @@ const FilamentStockInfo = () => { }) } return () => { - if (socket && initialized) { + if (printServer && initialized) { console.log('Deregistering filament stock update listener') - socket.off('notify_filamentstock_update') + printServer.off('notify_filamentstock_update') } } - }, [socket, initialized, filamentStockId]) + }, [printServer, initialized, filamentStockId]) const fetchFilamentStockDetails = async () => { try { diff --git a/src/components/Dashboard/Inventory/FilamentStocks/LoadFilamentStock.jsx b/src/components/Dashboard/Inventory/FilamentStocks/LoadFilamentStock.jsx index cb3598d..2a7b7d8 100644 --- a/src/components/Dashboard/Inventory/FilamentStocks/LoadFilamentStock.jsx +++ b/src/components/Dashboard/Inventory/FilamentStocks/LoadFilamentStock.jsx @@ -11,7 +11,7 @@ import { } from 'antd' import { useMediaQuery } from 'react-responsive' import PropTypes from 'prop-types' -import { SocketContext } from '../../context/SocketContext' +import { PrintServerContext } from '../../context/PrintServerContext' import FilamentStockSelect from '../../common/FilamentStockSelect' import PrinterSelect from '../../common/PrinterSelect' @@ -38,7 +38,7 @@ const LoadFilamentStock = ({ filamentStockLoaded: PropTypes.bool } - const { socket } = useContext(SocketContext) + const { printServer } = useContext(PrintServerContext) const initialLoadFilamentStockForm = { printer: printer, @@ -93,16 +93,16 @@ const LoadFilamentStock = ({ console.log(statusUpdate) } - socket.emit('printer.objects.subscribe', params) - socket.emit('printer.objects.query', params) - socket.on('notify_status_update', notifyStatusUpdate) + printServer.emit('printer.objects.subscribe', params) + printServer.emit('printer.objects.query', params) + printServer.on('notify_status_update', notifyStatusUpdate) return () => { - socket.off('notify_status_update', notifyStatusUpdate) - socket.emit('printer.objects.unsubscribe', params) + printServer.off('notify_status_update', notifyStatusUpdate) + printServer.emit('printer.objects.unsubscribe', params) } } - }, [socket, loadFilamentStockFormValues.printer]) + }, [printServer, loadFilamentStockFormValues.printer]) React.useEffect(() => { loadFilamentStockForm @@ -170,7 +170,7 @@ const LoadFilamentStock = ({ try { // Set the extruder temperature - await socket.emit('printer.filamentstock.load', { + await printServer.emit('printer.filamentstock.load', { printerId: loadFilamentStockFormValues.printer._id, filamentStockId: loadFilamentStockFormValues.filamentStock._id }) diff --git a/src/components/Dashboard/Inventory/FilamentStocks/UnloadFilamentStock.jsx b/src/components/Dashboard/Inventory/FilamentStocks/UnloadFilamentStock.jsx index 215880f..dd1f3df 100644 --- a/src/components/Dashboard/Inventory/FilamentStocks/UnloadFilamentStock.jsx +++ b/src/components/Dashboard/Inventory/FilamentStocks/UnloadFilamentStock.jsx @@ -2,7 +2,7 @@ import React, { useState, useContext, useEffect } from 'react' import { Form, Button, Typography, Flex, Steps, Divider, Alert } from 'antd' import { useMediaQuery } from 'react-responsive' import PropTypes from 'prop-types' -import { SocketContext } from '../../context/SocketContext' +import { PrintServerContext } from '../../context/PrintServerContext' import PrinterSelect from '../../common/PrinterSelect' import PrinterTemperaturePanel from '../../common/PrinterTemperaturePanel' @@ -18,7 +18,7 @@ const UnloadFilamentStock = ({ onOk, reset, printer = null }) => { printer: PropTypes.object } - const { socket } = useContext(SocketContext) + const { printServer } = useContext(PrintServerContext) const isMobile = useMediaQuery({ maxWidth: 768 }) const initialUnloadFilamentStockForm = { @@ -66,16 +66,16 @@ const UnloadFilamentStock = ({ onOk, reset, printer = null }) => { } } - socket.emit('printer.objects.subscribe', params) - socket.emit('printer.objects.query', params) - socket.on('notify_status_update', notifyStatusUpdate) + printServer.emit('printer.objects.subscribe', params) + printServer.emit('printer.objects.query', params) + printServer.on('notify_status_update', notifyStatusUpdate) return () => { - socket.off('notify_status_update', notifyStatusUpdate) - socket.emit('printer.objects.unsubscribe', params) + printServer.off('notify_status_update', notifyStatusUpdate) + printServer.emit('printer.objects.unsubscribe', params) } } - }, [socket, unloadFilamentStockFormValues.printer]) + }, [printServer, unloadFilamentStockFormValues.printer]) React.useEffect(() => { if (reset) { @@ -109,7 +109,7 @@ const UnloadFilamentStock = ({ onOk, reset, printer = null }) => { const handleUnloadFilamentStock = async () => { setUnloadFilamentStockLoading(true) // Send G-code to retract the filament - await socket.emit('printer.gcode.script', { + await printServer.emit('printer.gcode.script', { printerId: unloadFilamentStockFormValues.printer._id, script: `_CLIENT_LINEAR_MOVE E=-200 F=1000` }) diff --git a/src/components/Dashboard/Inventory/StockAudits.jsx b/src/components/Dashboard/Inventory/StockAudits.jsx index 2febd96..28f0272 100644 --- a/src/components/Dashboard/Inventory/StockAudits.jsx +++ b/src/components/Dashboard/Inventory/StockAudits.jsx @@ -3,7 +3,7 @@ import { useNavigate } from 'react-router-dom' import { Button, Flex, Space, message, Dropdown, Typography } from 'antd' import { AuthContext } from '../context/AuthContext' -import { SocketContext } from '../context/SocketContext' +import { PrintServerContext } from '../context/PrintServerContext' import IdText from '../common/IdText' import StockAuditIcon from '../../Icons/StockAuditIcon' @@ -20,16 +20,16 @@ const { Text } = Typography const StockAudits = () => { const [messageApi, contextHolder] = message.useMessage() const navigate = useNavigate() - const { socket } = useContext(SocketContext) + const { printServer } = useContext(PrintServerContext) const [initialized, setInitialized] = useState(false) const tableRef = useRef() const { authenticated } = useContext(AuthContext) React.useEffect(() => { - if (socket && !initialized) { + if (printServer && !initialized) { setInitialized(true) - socket.on('notify_stockaudit_update', (updateData) => { + printServer.on('notify_stockaudit_update', (updateData) => { console.log('Received stock audit update:', updateData) if (tableRef.current) { tableRef.current.updateData(updateData._id, updateData) @@ -38,12 +38,12 @@ const StockAudits = () => { } return () => { - if (socket && initialized) { + if (printServer && initialized) { console.log('Deregistering stock audit update listener') - socket.off('notify_stockaudit_update') + printServer.off('notify_stockaudit_update') } } - }, [socket, initialized]) + }, [printServer, initialized]) const getStockAuditActionItems = (id) => { return { diff --git a/src/components/Dashboard/Inventory/StockEvents.jsx b/src/components/Dashboard/Inventory/StockEvents.jsx index 3431206..ce14d3a 100644 --- a/src/components/Dashboard/Inventory/StockEvents.jsx +++ b/src/components/Dashboard/Inventory/StockEvents.jsx @@ -11,7 +11,7 @@ import { } from 'antd' import { AuthContext } from '../context/AuthContext' -import { SocketContext } from '../context/SocketContext' +import { PrintServerContext } from '../context/PrintServerContext' import IdText from '../common/IdText' import TimeDisplay from '../common/TimeDisplay' import ReloadIcon from '../../Icons/ReloadIcon' @@ -31,7 +31,7 @@ import StockEventIcon from '../../Icons/StockEventIcon' const { Text } = Typography const StockEvents = () => { - const { socket } = useContext(SocketContext) + const { printServer } = useContext(PrintServerContext) const [initialized, setInitialized] = useState(false) const tableRef = useRef() const [viewMode, setViewMode] = useViewMode('StockEvents') @@ -225,9 +225,9 @@ const StockEvents = () => { React.useEffect(() => { // Add WebSocket event listener for real-time updates - if (socket && !initialized) { + if (printServer && !initialized) { setInitialized(true) - socket.on('notify_stockevent_update', (updateData) => { + printServer.on('notify_stockevent_update', (updateData) => { console.log('Received stock event update:', updateData) if (tableRef.current) { tableRef.current.updateData(updateData._id, updateData) @@ -236,12 +236,12 @@ const StockEvents = () => { } return () => { - if (socket && initialized) { + if (printServer && initialized) { console.log('Deregistering stock event update listener') - socket.off('notify_stockevent_update') + printServer.off('notify_stockevent_update') } } - }, [socket, initialized]) + }, [printServer, initialized]) const actionItems = { items: [ diff --git a/src/components/Dashboard/Management/Filaments/FilamentInfo.jsx b/src/components/Dashboard/Management/Filaments/FilamentInfo.jsx index d52089b..adf0696 100644 --- a/src/components/Dashboard/Management/Filaments/FilamentInfo.jsx +++ b/src/components/Dashboard/Management/Filaments/FilamentInfo.jsx @@ -1,6 +1,5 @@ -import React, { useState, useEffect } from 'react' +import React, { useState, useEffect, useContext, useCallback } from 'react' import { useLocation } from 'react-router-dom' -import axios from 'axios' import { Descriptions, Spin, @@ -15,13 +14,15 @@ import { InputNumber, ColorPicker, Select, - Collapse, Dropdown, Popover, Checkbox, - Card + Card, + Tag } from 'antd' -import { LoadingOutlined, CaretLeftOutlined } from '@ant-design/icons' +import { LoadingOutlined } from '@ant-design/icons' +import loglevel from 'loglevel' +import config from '../../../../config' import IdText from '../../common/IdText' import ReloadIcon from '../../../Icons/ReloadIcon' import EditIcon from '../../../Icons/EditIcon.jsx' @@ -32,19 +33,25 @@ import VendorSelect from '../../common/VendorSelect' import useCollapseState from '../../hooks/useCollapseState' import AuditLogTable from '../../common/AuditLogTable' import DashboardNotes from '../../common/DashboardNotes' +import InfoCollapse from '../../common/InfoCollapse' -import config from '../../../../config.js' import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx' import NoteIcon from '../../../Icons/NoteIcon.jsx' import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx' +import LockIcon from '../../../Icons/LockIcon.jsx' +import { ApiServerContext } from '../../context/ApiServerContext' -const { Title, Link, Text } = Typography +const log = loglevel.getLogger('FilamentInfo') +log.setLevel(config.logLevel) + +const { Link, Text } = Typography const FilamentInfo = () => { const [filamentData, setFilamentData] = useState(null) const [fetchLoading, setFetchLoading] = useState(true) const [editLoading, setEditLoading] = useState(false) - const [error, setError] = useState(null) + const [lockUser, setLockUser] = useState(null) + const [initialized, setInitialized] = useState(false) const location = useLocation() const [messageApi, contextHolder] = message.useMessage() const filamentId = new URLSearchParams(location.search).get('filamentId') @@ -59,12 +66,36 @@ const FilamentInfo = () => { auditLogs: true } ) + const { + apiServer, + fetchObjectInfo, + updateObjectInfo, + lockObject, + unlockObject, + onLockEvent, + onUpdateEvent, + fetchObjectLock, + showError + } = useContext(ApiServerContext) - useEffect(() => { - if (filamentId) { - fetchFilamentDetails() + // Define the event handler function + const lockEventHandler = useCallback((lockEvent) => { + if (lockEvent.locked === true) { + setLockUser(lockEvent.user) + } else { + setLockUser(null) } - }, [filamentId]) + }, []) + + // Cleanup effect for component unmount + useEffect(() => { + return () => { + if (filamentId) { + // Ensure any remaining locks are released when component unmounts + unlockObject(filamentId, 'filament') + } + } + }, [filamentId, unlockObject]) useEffect(() => { if (filamentData) { @@ -83,32 +114,84 @@ const FilamentInfo = () => { } }, [filamentData, form]) - const fetchFilamentDetails = async () => { + const fetchFilamentInfo = useCallback(async () => { try { setFetchLoading(true) - const response = await axios.get( - `${config.backendUrl}/filaments/${filamentId}`, - { - headers: { - Accept: 'application/json' - }, - withCredentials: true - } - ) - setFilamentData(response.data) - form.setFieldsValue(response.data) - setError(null) - } catch (err) { - setError('Failed to fetch filament details') - messageApi.error('Failed to fetch filament details') - } finally { + const data = await fetchObjectInfo(filamentId, 'filament') + const lockEvent = await fetchObjectLock(filamentId, 'filament') + setLockUser(lockEvent?.user || null) + setFilamentData(data) + form.setFieldsValue(data) setFetchLoading(false) + } catch (err) { + messageApi.error('Failed to fetch filament info') + // Show error modal with retry functionality + showError( + `Failed to fetch filament information. Message: ${err.message}. Code: ${err.code}`, + fetchFilamentInfo + ) } + }, [ + fetchObjectInfo, + fetchObjectLock, + filamentId, + form, + messageApi, + showError + ]) + + const updateFilamentInfo = async () => { + const values = form.getFieldsValue() + const updateValue = { + name: values.name, + vendor: values.vendor, + type: values.type, + cost: values.cost, + color: values.color, + diameter: values.diameter, + density: values.density, + url: values.url, + barcode: values.barcode, + emptySpoolWeight: values.emptySpoolWeight + } + await updateObjectInfo(filamentId, 'filament', updateValue) } + // Define the update event handler function + const updateEventHandler = useCallback( + (updateEvent) => { + log.debug('Update event received for filament:', updateEvent) + // Refresh the filament data when an update is received + fetchFilamentInfo() + }, + [fetchFilamentInfo] + ) + + useEffect(() => { + if (initialized == false && filamentId && apiServer?.connected === true) { + setInitialized(true) + fetchFilamentInfo() + } + }, [filamentId, apiServer?.connected, initialized, fetchFilamentInfo]) + + useEffect(() => { + if (filamentId) { + const cleanup = onLockEvent(filamentId, lockEventHandler) + return cleanup + } + }, [filamentId, onLockEvent, lockEventHandler]) + + useEffect(() => { + if (filamentId) { + const cleanup = onUpdateEvent(filamentId, updateEventHandler) + return cleanup + } + }, [filamentId, onUpdateEvent, updateEventHandler]) + const startEditing = () => { updateCollapseState('info', true) setIsEditing(true) + lockObject(filamentId, 'filament') } const cancelEditing = () => { @@ -128,34 +211,15 @@ const FilamentInfo = () => { }) } setIsEditing(false) + unlockObject(filamentId, 'filament') } - const updateFilamentInfo = async () => { + const handleUpdateFilamentInfo = async () => { try { const values = await form.validateFields() setEditLoading(true) - await axios.put( - `${config.backendUrl}/filaments/${filamentId}`, - { - name: values.name, - vendor: values.vendor, - type: values.type, - cost: values.cost, - color: values.color, - diameter: values.diameter, - density: values.density, - url: values.url, - barcode: values.barcode, - emptySpoolWeight: values.emptySpoolWeight - }, - { - headers: { - 'Content-Type': 'application/json' - }, - withCredentials: true - } - ) + await updateFilamentInfo() // Update the local state with the new values setFilamentData({ ...filamentData, ...values }) @@ -168,8 +232,13 @@ const FilamentInfo = () => { } console.error('Failed to update filament information:', err) messageApi.error('Failed to update filament information') + // Show error modal with retry functionality + showError( + `Failed to update filament information. Message: ${err.message}. Code: ${err.code}`, + () => handleUpdateFilamentInfo() + ) } finally { - fetchFilamentDetails() + fetchFilamentInfo() setEditLoading(false) } } @@ -184,7 +253,7 @@ const FilamentInfo = () => { ], onClick: ({ key }) => { if (key === 'reload') { - fetchFilamentDetails() + fetchFilamentInfo() } } } @@ -215,20 +284,6 @@ const FilamentInfo = () => { ) } - if (error) { - return ( - -

{error || 'Filament not found'}

- -
- ) - } - return ( <> {contextHolder} @@ -238,17 +293,34 @@ const FilamentInfo = () => { style={{ height: '100%', minHeight: 0 }} > - - - - - - - + + + + + + + + + + {lockUser && ( + + } + style={{ margin: 0 }} + color={'orange'} + /> + + + )} {isEditing ? ( @@ -256,7 +328,7 @@ const FilamentInfo = () => { - - ) : ( -
- - - updateCollapseState('info', keys.length > 0) - } - expandIcon={({ isActive }) => ( - - )} - className='no-h-padding-collapse no-t-padding-collapse' +
+ + } + active={collapseState.info} + onToggle={(expanded) => updateCollapseState('info', expanded)} + className='no-t-padding-collapse' + key='info' + > +
- - - - Filament Information - - - } - key='1' - > - } spinning={fetchLoading}> + - } - spinning={fetchLoading} - > - - - {filamentData?._id ? ( - - ) : ( - n/a - )} - - - {filamentData?.createdAt ? ( - - ) : ( - n/a - )} - + + {filamentData?._id ? ( + + ) : ( + n/a + )} + + + {filamentData?.createdAt ? ( + + ) : ( + n/a + )} + - - {isEditing ? ( - - - - ) : filamentData?.name ? ( - {filamentData.name} - ) : ( - n/a - )} - + + {isEditing ? ( + + + + ) : filamentData?.name ? ( + {filamentData.name} + ) : ( + n/a + )} + - - {filamentData?.updatedAt ? ( - - ) : ( - n/a - )} - + + {filamentData?.updatedAt ? ( + + ) : ( + n/a + )} + - - {isEditing ? ( - - - - ) : filamentData?.vendor?.name ? ( - {filamentData.vendor.name} - ) : ( - n/a - )} - + + {isEditing ? ( + + + + ) : filamentData?.vendor?.name ? ( + {filamentData.vendor.name} + ) : ( + n/a + )} + - - {filamentData?.vendor?.id ? ( - - ) : ( - n/a - )} - + + {filamentData?.vendor?.id ? ( + + ) : ( + n/a + )} + - - {isEditing ? ( - - - - ) : filamentData?.type ? ( - {filamentData.type} - ) : ( - n/a - )} - + + {isEditing ? ( + + + + ) : filamentData?.type ? ( + {filamentData.type} + ) : ( + n/a + )} + - - {isEditing ? ( - - - - ) : filamentData?.cost ? ( - {`£${filamentData.cost}/kg`} - ) : ( - n/a - )} - + + {isEditing ? ( + + + + ) : filamentData?.cost ? ( + {`£${filamentData.cost}/kg`} + ) : ( + n/a + )} + - - - {isEditing ? ( - { - return '#' + color.toHex() - }} - > - - - ) : filamentData?.color ? ( - - ) : ( - n/a - )} - - + + + {isEditing ? ( + { + return '#' + color.toHex() + }} + > + + + ) : filamentData?.color ? ( + + ) : ( + n/a + )} + + - - {isEditing ? ( - - - - ) : filamentData?.diameter ? ( - {`${filamentData.diameter}mm`} - ) : ( - n/a - )} - + + {isEditing ? ( + + + + ) : filamentData?.diameter ? ( + {`${filamentData.diameter}mm`} + ) : ( + n/a + )} + - - {isEditing ? ( - - - - ) : filamentData?.density ? ( - {`${filamentData.density}g/cm³`} - ) : ( - n/a - )} - + + {isEditing ? ( + + + + ) : filamentData?.density ? ( + {`${filamentData.density}g/cm³`} + ) : ( + n/a + )} + - - {isEditing ? ( - - - - ) : filamentData?.url ? ( - - {filamentData.url} - - ) : ( - n/a - )} - + + {isEditing ? ( + + + + ) : filamentData?.url ? ( + + {filamentData.url} + + ) : ( + n/a + )} + - - {isEditing ? ( - - - - ) : filamentData?.barcode ? ( - {filamentData.barcode} - ) : ( - n/a - )} - - - - -
- + + {isEditing ? ( + + + + ) : filamentData?.barcode ? ( + {filamentData.barcode} + ) : ( + n/a + )} + + + + +
- - updateCollapseState('notes', keys.length > 0) - } - expandIcon={({ isActive }) => ( - - )} - className='no-h-padding-collapse' - > - - - - Notes - -
- } - key='notes' - > - - - - - + } + active={collapseState.notes} + onToggle={(expanded) => updateCollapseState('notes', expanded)} + key='notes' + > + + + + - - updateCollapseState('auditLogs', keys.length > 0) - } - expandIcon={({ isActive }) => ( - - )} - className='no-h-padding-collapse' - > - - - - Audit Logs - - - } - key='auditLogs' - > - - - - -
- )} + } + active={collapseState.auditLogs} + onToggle={(expanded) => + updateCollapseState('auditLogs', expanded) + } + key='auditLogs' + > + + +
+
) diff --git a/src/components/Dashboard/Production/GCodeFiles/NewGCodeFile.jsx b/src/components/Dashboard/Production/GCodeFiles/NewGCodeFile.jsx index 3ef9fc3..5474888 100644 --- a/src/components/Dashboard/Production/GCodeFiles/NewGCodeFile.jsx +++ b/src/components/Dashboard/Production/GCodeFiles/NewGCodeFile.jsx @@ -72,7 +72,7 @@ const NewGCodeFile = ({ onOk, reset }) => { const { token, authenticated } = useContext(AuthContext) // eslint-disable-next-line - const fetchFilamentDetails = async () => { + const fetchFilamentInfo = async () => { if (!authenticated) { return } diff --git a/src/components/Dashboard/Production/Jobs.jsx b/src/components/Dashboard/Production/Jobs.jsx index be54fc8..e8ca155 100644 --- a/src/components/Dashboard/Production/Jobs.jsx +++ b/src/components/Dashboard/Production/Jobs.jsx @@ -17,7 +17,7 @@ import { } from 'antd' import { AuthContext } from '../context/AuthContext.js' -import { SocketContext } from '../context/SocketContext.js' +import { PrintServerContext } from '../context/PrintServerContext.js' import NewJob from './Jobs/NewJob.jsx' import JobState from '../common/JobState.jsx' import SubJobCounter from '../common/SubJobCounter.jsx' @@ -257,7 +257,7 @@ const Jobs = () => { ] const { authenticated } = useContext(AuthContext) - const { socket } = useContext(SocketContext) + const { printServer } = useContext(PrintServerContext) const [columnVisibility, updateColumnVisibility] = useColumnVisibility( 'Jobs', @@ -265,9 +265,9 @@ const Jobs = () => { ) const handleDeployJob = (jobId) => { - if (socket) { + if (printServer) { messageApi.info(`Print job ${jobId} deployment initiated`) - socket.emit('server.job_queue.deploy', { jobId }, (response) => { + printServer.emit('server.job_queue.deploy', { jobId }, (response) => { if (response == false) { notificationApi.error({ message: 'Print job deployment failed', diff --git a/src/components/Dashboard/Production/Jobs/JobInfo.jsx b/src/components/Dashboard/Production/Jobs/JobInfo.jsx index 36e2925..0aed559 100644 --- a/src/components/Dashboard/Production/Jobs/JobInfo.jsx +++ b/src/components/Dashboard/Production/Jobs/JobInfo.jsx @@ -21,7 +21,7 @@ import TimeDisplay from '../../common/TimeDisplay' import JobState from '../../common/JobState' import IdText from '../../common/IdText' import SubJobsTree from '../../common/SubJobsTree' -import { SocketContext } from '../../context/SocketContext' +import { PrintServerContext } from '../../context/PrintServerContext' import GCodeFileIcon from '../../../Icons/GCodeFileIcon' import ReloadIcon from '../../../Icons/ReloadIcon' import useCollapseState from '../../hooks/useCollapseState' @@ -42,7 +42,7 @@ const JobInfo = () => { const location = useLocation() const [messageApi] = message.useMessage() const jobId = new URLSearchParams(location.search).get('jobId') - const { socket } = useContext(SocketContext) + const { printServer } = useContext(PrintServerContext) const [collapseState, updateCollapseState] = useCollapseState('JobInfo', { info: true, subJobs: true, @@ -57,8 +57,8 @@ const JobInfo = () => { }, [jobId]) useEffect(() => { - if (socket && jobId) { - socket.on('notify_job_update', (updateData) => { + if (printServer && jobId) { + printServer.on('notify_job_update', (updateData) => { if (updateData._id === jobId) { setJobData((prevData) => { if (!prevData) return prevData @@ -73,11 +73,11 @@ const JobInfo = () => { } return () => { - if (socket) { - socket.off('notify_job_update') + if (printServer) { + printServer.off('notify_job_update') } } - }, [socket, jobId]) + }, [printServer, jobId]) const fetchJobDetails = async () => { try { diff --git a/src/components/Dashboard/Production/Printers/ControlPrinter.jsx b/src/components/Dashboard/Production/Printers/ControlPrinter.jsx index 3ba361f..8f8c689 100644 --- a/src/components/Dashboard/Production/Printers/ControlPrinter.jsx +++ b/src/components/Dashboard/Production/Printers/ControlPrinter.jsx @@ -22,7 +22,7 @@ import { } from 'antd' import { LoadingOutlined, CaretLeftOutlined } from '@ant-design/icons' -import { SocketContext } from '../../context/SocketContext' +import { PrintServerContext } from '../../context/PrintServerContext' import PrinterTemperaturePanel from '../../common/PrinterTemperaturePanel' import PrinterPositionPanel from '../../common/PrinterPositionPanel' @@ -106,7 +106,7 @@ const ControlPrinter = () => { ) }, [componentVisibility]) - const { socket } = useContext(SocketContext) + const { printServer } = useContext(PrintServerContext) const { authenticated } = useContext(AuthContext) // Fetch printer details when the component mounts @@ -143,9 +143,9 @@ const ControlPrinter = () => { // Add WebSocket event listener for real-time updates useEffect(() => { - if (socket && !initialized && printerId) { + if (printServer && !initialized && printerId) { setInitialized(true) - socket.on('notify_printer_update', (statusUpdate) => { + printServer.on('notify_printer_update', (statusUpdate) => { setPrinterData((prevData) => { if (statusUpdate?._id === printerId) { return { @@ -158,7 +158,7 @@ const ControlPrinter = () => { }) // Add WebSocket event listener for filament stock updates - socket.on('notify_filamentstock_update', (filamentStockUpdate) => { + printServer.on('notify_filamentstock_update', (filamentStockUpdate) => { setPrinterData((prevData) => { if (prevData?.currentFilamentStock) { if ( @@ -178,17 +178,17 @@ const ControlPrinter = () => { }) } return () => { - if (socket && initialized) { + if (printServer && initialized) { console.log('Deregistering') - socket.off('notify_printer_update') - socket.off('notify_filamentstock_update') + printServer.off('notify_printer_update') + printServer.off('notify_filamentstock_update') } } - }, [socket, initialized, printerId]) + }, [printServer, initialized, printerId]) function handleEmergencyStop() { console.log('Emergency stop button clicked') - socket.emit('printer.emergency_stop', { printerId }) + printServer.emit('printer.emergency_stop', { printerId }) } useEffect(() => { @@ -328,19 +328,19 @@ const ControlPrinter = () => { ], onClick: ({ key }) => { if (key === 'restartHost') { - socket.emit('printer.restart', { printerId }) + printServer.emit('printer.restart', { printerId }) } else if (key === 'restartFirmware') { - socket.emit('printer.firmware_restart', { printerId }) + printServer.emit('printer.firmware_restart', { printerId }) } else if (key === 'resumePrint') { - socket.emit('printer.print.resume', { printerId }) + printServer.emit('printer.print.resume', { printerId }) } else if (key === 'pausePrint') { - socket.emit('printer.print.pause', { printerId }) + printServer.emit('printer.print.pause', { printerId }) } else if (key === 'cancelPrint') { - socket.emit('printer.print.cancel', { printerId }) + printServer.emit('printer.print.cancel', { printerId }) } else if (key === 'startQueue') { - socket.emit('server.job_queue.start', { printerId }) + printServer.emit('server.job_queue.start', { printerId }) } else if (key === 'pauseQueue') { - socket.emit('server.job_queue.pause', { printerId }) + printServer.emit('server.job_queue.pause', { printerId }) } else if (key === 'loadFilamentStock') { setLoadFilamentStockModalOpen(true) } else if (key === 'unloadFilamentStock') { @@ -467,9 +467,9 @@ const ControlPrinter = () => { } onClick={() => { if (printerData?.state?.type === 'paused') { - socket.emit('printer.print.resume', { printerId }) + printServer.emit('printer.print.resume', { printerId }) } else { - socket.emit('printer.print.pause', { printerId }) + printServer.emit('printer.print.pause', { printerId }) } }} > @@ -482,7 +482,7 @@ const ControlPrinter = () => { printerData?.state?.type === 'error' } onClick={() => { - socket.emit('server.job_queue.start', { printerId }) + printServer.emit('server.job_queue.start', { printerId }) }} > @@ -933,7 +933,7 @@ const ControlPrinter = () => { key='firmwareRestart' icon={} onClick={() => { - socket.emit('printer.firmware_restart', { printerId }) + printServer.emit('printer.firmware_restart', { printerId }) setKlippyErrorModalOpen(false) }} > diff --git a/src/components/Dashboard/Production/Printers/NewPrinter.jsx b/src/components/Dashboard/Production/Printers/NewPrinter.jsx index e777570..a02cb19 100644 --- a/src/components/Dashboard/Production/Printers/NewPrinter.jsx +++ b/src/components/Dashboard/Production/Printers/NewPrinter.jsx @@ -21,7 +21,7 @@ import { } from 'antd' import { SearchOutlined, SettingOutlined } from '@ant-design/icons' import PropTypes from 'prop-types' -import { SocketContext } from '../../context/SocketContext' +import { PrintServerContext } from '../../context/PrintServerContext' import EditIcon from '../../../Icons/EditIcon.jsx' import config from '../../../../config.js' @@ -43,7 +43,7 @@ const NewPrinter = ({ onOk, reset }) => { reset: PropTypes.bool.isRequired } - const { socket } = useContext(SocketContext) + const { printServer } = useContext(PrintServerContext) const [messageApi, contextHolder] = message.useMessage() const [notificationApi, notificationContextHolder] = notification.useNotification() @@ -243,22 +243,22 @@ const NewPrinter = ({ onOk, reset }) => { setDiscovering(true) setDiscoveredPrinters([]) messageApi.info('Discovering printers...') - socket.off('notify_scan_network_found') - socket.off('notify_scan_network_progress') - socket.off('notify_scan_network_complete') + printServer.off('notify_scan_network_found') + printServer.off('notify_scan_network_progress') + printServer.off('notify_scan_network_complete') - socket.on('notify_scan_network_found', notifyScanNetworkFound) - socket.on('notify_scan_network_progress', notifyScanNetworkProgress) - socket.on('notify_scan_network_complete', notifyScanNetworkComplete) + printServer.on('notify_scan_network_found', notifyScanNetworkFound) + printServer.on('notify_scan_network_progress', notifyScanNetworkProgress) + printServer.on('notify_scan_network_complete', notifyScanNetworkComplete) - socket.emit('bridge.scan_network.start', { + printServer.emit('bridge.scan_network.start', { port: scanPort, protocol: scanProtocol }) } }, [ discovering, - socket, + printServer, scanPort, scanProtocol, messageApi, @@ -279,10 +279,10 @@ const NewPrinter = ({ onOk, reset }) => { setDiscovering(false) notificationApi.destroy('network-scan') messageApi.info('Stopping discovery...') - socket.off('notify_scan_network_found') - socket.off('notify_scan_network_progress') - socket.off('notify_scan_network_complete') - socket.emit('bridge.scan_network.stop', (response) => { + printServer.off('notify_scan_network_found') + printServer.off('notify_scan_network_progress') + printServer.off('notify_scan_network_complete') + printServer.emit('bridge.scan_network.stop', (response) => { if (response == false) { messageApi.error('Error stopping discovery!') } diff --git a/src/components/Dashboard/common/DashboardBreadcrumb.jsx b/src/components/Dashboard/common/DashboardBreadcrumb.jsx index 01db842..bee7906 100644 --- a/src/components/Dashboard/common/DashboardBreadcrumb.jsx +++ b/src/components/Dashboard/common/DashboardBreadcrumb.jsx @@ -46,7 +46,7 @@ const breadcrumbNameMap = { '/dashboard/inventory/stockaudits/info': 'Info', '/dashboard/developer/sessionstorage': 'Session Storage', '/dashboard/developer/authcontextdebug': 'Auth Context Debug', - '/dashboard/developer/socketcontextdebug': 'Socket Context Debug' + '/dashboard/developer/printservercontextdebug': 'Print Server Context Debug' } const DashboardBreadcrumb = () => { diff --git a/src/components/Dashboard/common/DashboardNavigation.jsx b/src/components/Dashboard/common/DashboardNavigation.jsx index 19acece..3e3b8a5 100644 --- a/src/components/Dashboard/common/DashboardNavigation.jsx +++ b/src/components/Dashboard/common/DashboardNavigation.jsx @@ -8,9 +8,8 @@ import { Dropdown, Button, Tooltip, - Typography, - Divider, - Badge + Badge, + Divider } from 'antd' import { LogoutOutlined, @@ -19,12 +18,13 @@ import { LoadingOutlined } from '@ant-design/icons' import { AuthContext } from '../context/AuthContext' -import { SocketContext } from '../context/SocketContext' +import { PrintServerContext } from '../context/PrintServerContext' import { SpotlightContext } from '../context/SpotlightContext' -import { NotificationContext } from '../context/NotificationContext' +import { ApiServerContext } from '../context/ApiServerContext' import { useNavigate, useLocation } from 'react-router-dom' import { Header } from 'antd/es/layout/layout' import { useMediaQuery } from 'react-responsive' +import KeyboardShortcut from './KeyboardShortcut' import FarmControlLogo from '../../Logos/FarmControlLogo' import FarmControlLogoSmall from '../../Logos/FarmControlLogoSmall' @@ -36,16 +36,16 @@ import BellIcon from '../../Icons/BellIcon' import SearchIcon from '../../Icons/SearchIcon' import SettingsIcon from '../../Icons/SettingsIcon' import DeveloperIcon from '../../Icons/DeveloperIcon' - -const { Text } = Typography +import PrinterIcon from '../../Icons/PrinterIcon' const DashboardNavigation = () => { const { logout, userProfile } = useContext(AuthContext) const { showSpotlight } = useContext(SpotlightContext) - const { toggleNotificationCenter, unreadCount } = - useContext(NotificationContext) - const { socket } = useContext(SocketContext) - const [socketState, setSocketState] = useState('disconnected') + const { toggleNotificationCenter, unreadCount } = useContext(ApiServerContext) + const { printServer } = useContext(PrintServerContext) + const { apiServer } = useContext(ApiServerContext) + const [printServerState, setPrintServerState] = useState('disconnected') + const [apiServerState, setApiServerState] = useState('disconnected') const navigate = useNavigate() const location = useLocation() const [selectedKey, setSelectedKey] = useState('production') @@ -59,14 +59,24 @@ const DashboardNavigation = () => { }, [location.pathname]) useEffect(() => { - if (socket?.connecting) { - setSocketState('connecting') - } else if (socket?.connected) { - setSocketState('connected') + if (printServer?.connecting) { + setPrintServerState('connecting') + } else if (printServer?.connected) { + setPrintServerState('connected') } else { - setSocketState('disconnected') + setPrintServerState('disconnected') } - }, [socket?.connecting, socket?.connected]) + }, [printServer?.connecting, printServer?.connected]) + + useEffect(() => { + if (apiServer?.connecting) { + setApiServerState('connecting') + } else if (apiServer?.connected) { + setApiServerState('connected') + } else { + setApiServerState('disconnected') + } + }, [apiServer?.connecting, apiServer?.connected]) const mainMenuItems = [ { @@ -165,54 +175,90 @@ const DashboardNavigation = () => { /> - ⌘ ⇧ P} arrow={false}> + showSpotlight()} + > - + /> + - + toggleNotificationCenter()} + > + + ]} + > + {errorModalContent} + + + ) +} + +ApiServerProvider.propTypes = { + children: PropTypes.node.isRequired +} + +export { ApiServerContext, ApiServerProvider } diff --git a/src/components/Dashboard/context/NotificationContext.js b/src/components/Dashboard/context/NotificationContext.js deleted file mode 100644 index 6a8cf70..0000000 --- a/src/components/Dashboard/context/NotificationContext.js +++ /dev/null @@ -1,212 +0,0 @@ -// src/contexts/NotificationContext.js -import React, { - createContext, - useEffect, - useState, - useContext, - useRef -} from 'react' -import io from 'socket.io-client' -import { message, notification, Drawer } from 'antd' -import PropTypes from 'prop-types' -import { AuthContext } from './AuthContext' -import config from '../../../config' -import NotificationCenter from '../common/NotificationCenter' - -const NotificationContext = createContext() - -const NotificationProvider = ({ children }) => { - const { token } = useContext(AuthContext) - const socketRef = useRef(null) - const [connecting, setConnecting] = useState(false) - const [error, setError] = useState(null) - const [notificationVisible, setNotificationVisible] = useState(false) - const [notifications, setNotifications] = useState([]) - const [messageApi, contextHolder] = message.useMessage() - const [notificationApi] = notification.useNotification() - - useEffect(() => { - if (token) { - console.log( - 'Token is available, connecting to notification web socket server...' - ) - - const newSocket = io( - config.notificationWsUrl || `${config.wsUrl}/notifications`, - { - reconnectionAttempts: 3, - timeout: 3000, - auth: { token: token } - } - ) - - setConnecting(true) - - newSocket.on('connect', () => { - console.log('Notification socket connected') - setConnecting(false) - setError(null) - }) - - newSocket.on('disconnect', () => { - console.log('Notification socket disconnected') - setError('Notification socket disconnected') - }) - - newSocket.on('connect_error', (err) => { - console.error('Notification socket connection error:', err) - messageApi.error('Notification socket connection error: ' + err.message) - setError('Notification socket connection error') - }) - - newSocket.on('notification.new', (data) => { - console.log('New notification received:', data) - // Add new notification to state - setNotifications((prev) => [data, ...prev]) - - // Show toast notification - notificationApi.info({ - message: data.title || 'New Notification', - description: data.message, - placement: 'topRight', - duration: 4.5 - }) - }) - - newSocket.on('notification.update', (data) => { - console.log('Notification updated:', data) - setNotifications((prev) => - prev.map((notification) => { - if (notification._id === data._id) { - return { ...notification, ...data } - } - return notification - }) - ) - }) - - newSocket.on('notification.delete', (data) => { - console.log('Notification deleted:', data) - setNotifications((prev) => - prev.filter((notification) => notification._id !== data._id) - ) - }) - - newSocket.on('error', (err) => { - console.error('Notification socket error:', err) - setError('Notification socket error') - }) - - socketRef.current = newSocket - - // Clean up function - return () => { - if (socketRef.current) { - console.log('Cleaning up notification socket connection...') - socketRef.current.disconnect() - socketRef.current = null - } - } - } else if (!token && socketRef.current) { - console.log('Token not available, disconnecting notification socket...') - socketRef.current.disconnect() - socketRef.current = null - } - }, [token, messageApi, notificationApi]) - - const showNotificationCenter = () => { - setNotificationVisible(true) - } - - const hideNotificationCenter = () => { - setNotificationVisible(false) - } - - const toggleNotificationCenter = () => { - setNotificationVisible((prev) => !prev) - } - - const updateNotifications = (newNotifications) => { - setNotifications(newNotifications) - } - - const addNotification = (notification) => { - setNotifications((prev) => [notification, ...prev]) - } - - const removeNotification = (notificationId) => { - setNotifications((prev) => - prev.filter((notification) => notification._id !== notificationId) - ) - } - - const markNotificationAsRead = (notificationId) => { - setNotifications((prev) => - prev.map((notification) => { - if (notification._id === notificationId) { - return { ...notification, read: true } - } - return notification - }) - ) - } - - const markAllNotificationsAsRead = () => { - setNotifications((prev) => - prev.map((notification) => ({ ...notification, read: true })) - ) - } - - const clearAllNotifications = () => { - setNotifications([]) - } - - const getUnreadCount = () => { - return notifications.filter((notification) => !notification.read).length - } - - return ( - - {contextHolder} - {children} - - {/* Notification Drawer */} - - - - - ) -} - -NotificationProvider.propTypes = { - children: PropTypes.node.isRequired -} - -export { NotificationContext, NotificationProvider } diff --git a/src/components/Dashboard/context/SocketContext.js b/src/components/Dashboard/context/PrintServerContext.js similarity index 59% rename from src/components/Dashboard/context/SocketContext.js rename to src/components/Dashboard/context/PrintServerContext.js index e6c603d..160a1d1 100644 --- a/src/components/Dashboard/context/SocketContext.js +++ b/src/components/Dashboard/context/PrintServerContext.js @@ -1,4 +1,4 @@ -// src/contexts/SocketContext.js +// src/contexts/PrintServerContext.js import React, { createContext, useEffect, @@ -11,10 +11,13 @@ import { message, notification } from 'antd' import PropTypes from 'prop-types' import { AuthContext } from './AuthContext' import config from '../../../config' +import loglevel from 'loglevel' +const log = loglevel.getLogger('Print Server') +log.setLevel(config.logLevel) -const SocketContext = createContext() +const PrintServerContext = createContext() -const SocketProvider = ({ children }) => { +const PrintServerProvider = ({ children }) => { const { token } = useContext(AuthContext) const socketRef = useRef(null) const [connecting, setConnecting] = useState(false) @@ -24,9 +27,9 @@ const SocketProvider = ({ children }) => { useEffect(() => { if (token) { - console.log('Token is available, connecting to web socket server...') + log.debug('Token is available, connecting to print server...') - const newSocket = io(config.wsUrl, { + const newSocket = io(config.printServerUrl, { reconnectionAttempts: 3, timeout: 3000, auth: { token: token } @@ -35,20 +38,20 @@ const SocketProvider = ({ children }) => { setConnecting(true) newSocket.on('connect', () => { - console.log('Socket connected') + log.debug('Print server connected') setConnecting(false) setError(null) }) newSocket.on('disconnect', () => { - console.log('Socket disconnected') - setError('Socket disconnected') + log.debug('Print server disconnected') + setError('Print server disconnected') }) newSocket.on('connect_error', (err) => { - console.error('Socket connection error:', err) - messageApi.error('Socket connection error: ' + err.message) - setError('Socket connection error') + log.error('Print server connection error:', err) + messageApi.error('Print server connection error: ' + err.message) + setError('Print server connection error') }) newSocket.on('bridge.notification', (data) => { @@ -59,8 +62,8 @@ const SocketProvider = ({ children }) => { }) newSocket.on('error', (err) => { - console.error('Socket error:', err) - setError('Socket error') + log.error('Print server error:', err) + setError('Print server error') }) socketRef.current = newSocket @@ -68,30 +71,30 @@ const SocketProvider = ({ children }) => { // Clean up function return () => { if (socketRef.current) { - console.log('Cleaning up socket connection...') + log.debug('Cleaning up socket connection...') socketRef.current.disconnect() socketRef.current = null } } } else if (!token && socketRef.current) { - console.log('Token not available, disconnecting socket...') + log.debug('Token not available, disconnecting socket...') socketRef.current.disconnect() socketRef.current = null } }, [token, messageApi]) return ( - {contextHolder} {children} - + ) } -SocketProvider.propTypes = { +PrintServerProvider.propTypes = { children: PropTypes.node.isRequired } -export { SocketContext, SocketProvider } +export { PrintServerContext, PrintServerProvider } diff --git a/src/components/Dashboard/context/SpotlightContext.js b/src/components/Dashboard/context/SpotlightContext.js index 849e552..d53090b 100644 --- a/src/components/Dashboard/context/SpotlightContext.js +++ b/src/components/Dashboard/context/SpotlightContext.js @@ -345,24 +345,6 @@ const SpotlightProvider = ({ children }) => { } } - // Add keyboard shortcut listener - useEffect(() => { - const handleKeyPress = (e) => { - if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key === 'p') { - e.preventDefault() // Prevent browser's default behavior - showSpotlight() - } - } - - // Add event listener - window.addEventListener('keydown', handleKeyPress) - - // Clean up - return () => { - window.removeEventListener('keydown', handleKeyPress) - } - }, []) - // Focus and select text in input when modal becomes visible useEffect(() => { if (showModal && inputRef.current) { diff --git a/src/components/Icons/LockIcon.jsx b/src/components/Icons/LockIcon.jsx new file mode 100644 index 0000000..be0660c --- /dev/null +++ b/src/components/Icons/LockIcon.jsx @@ -0,0 +1,7 @@ +import React from 'react' +import Icon from '@ant-design/icons' +import { ReactComponent as CustomIconSvg } from '../../assets/icons/lockicon.min.svg' + +const LockIcon = (props) => + +export default LockIcon diff --git a/src/config.js b/src/config.js index fec254e..fa55645 100644 --- a/src/config.js +++ b/src/config.js @@ -1,11 +1,15 @@ const config = { development: { backendUrl: 'http://192.168.68.53:8080', - wsUrl: 'ws://192.168.68.53:8081' + printServerUrl: 'ws://192.168.68.53:8081', + apiServerUrl: 'ws://192.168.68.53:9090', + logLevel: 'trace' }, production: { - backendUrl: 'http://192.168.68.53:8080', // Replace with your production backend URL - wsUrl: 'http://192.168.68.53:8081' // Replace with your production WebSocket URL + backendUrl: 'http://192.168.68.53:8080', + printServerUrl: 'ws://192.168.68.53:8081', + apiServerUrl: 'ws://192.168.68.53:9090', + logLevel: 'error' } }