diff --git a/Client/src/Components/Alert/index.jsx b/Client/src/Components/Alert/index.jsx index eb5fbd13b..cafd58451 100644 --- a/Client/src/Components/Alert/index.jsx +++ b/Client/src/Components/Alert/index.jsx @@ -31,7 +31,7 @@ const icons = { const Alert = ({ variant, title, body, isToast, hasIcon = true, onClick }) => { const theme = useTheme(); - const { text, light, border } = theme.palette[variant]; + const { text, bg, border } = theme.palette[variant]; const icon = icons[variant]; return ( @@ -45,7 +45,7 @@ const Alert = ({ variant, title, body, isToast, hasIcon = true, onClick }) => { padding: hasIcon ? theme.spacing(8) : `${theme.spacing(4)} ${theme.spacing(8)}`, - backgroundColor: light, + backgroundColor: bg, border: `solid 1px ${border}`, borderRadius: theme.shape.borderRadius, }} diff --git a/Client/src/Components/Charts/ResponseTimeChart/index.css b/Client/src/Components/Charts/ResponseTimeChart/index.css deleted file mode 100644 index e69de29bb..000000000 diff --git a/Client/src/Components/Charts/ResponseTimeChart/index.jsx b/Client/src/Components/Charts/ResponseTimeChart/index.jsx deleted file mode 100644 index fd6c0f41e..000000000 --- a/Client/src/Components/Charts/ResponseTimeChart/index.jsx +++ /dev/null @@ -1,43 +0,0 @@ -import PropTypes from "prop-types"; -import { Stack } from "@mui/material"; -import { BarChart, Bar, ResponsiveContainer, Cell } from "recharts"; -import "./index.css"; - -const ResponseTimeChart = ({ checks = [] }) => { - return ( - - - - - {checks.map((check, index) => ( - - ))} - - - - - ); -}; - -ResponseTimeChart.propTypes = { - checks: PropTypes.array, -}; -export default ResponseTimeChart; diff --git a/Client/src/Pages/Monitors/Home/monitorData.jsx b/Client/src/Pages/Monitors/Home/monitorData.jsx index 28ff08756..d84496923 100644 --- a/Client/src/Pages/Monitors/Home/monitorData.jsx +++ b/Client/src/Pages/Monitors/Home/monitorData.jsx @@ -27,11 +27,22 @@ const data = { rows: [], }; +/** + * Builds table data for a list of monitors. + * + * @param {Array} monitors - An array of monitor objects containing information about each monitor. + * @param {boolean} isAdmin - Flag indicating if the current user is an admin. + * @param {Function} navigate - A function to navigate to the monitor detail page. + * @returns {Object} The data structure containing columns and rows for the table. + */ export const buildData = (monitors, isAdmin, navigate) => { const theme = useTheme(); data.rows = monitors.map((monitor, idx) => { let uptimePercentage = ""; + let percentageColor = theme.palette.percentage.uptimeExcellent; + + // Determine uptime percentage and color based on the monitor's uptimePercentage value if (monitor.uptimePercentage !== undefined) { uptimePercentage = monitor.uptimePercentage === 0 @@ -43,19 +54,11 @@ export const buildData = (monitors, isAdmin, navigate) => { url: monitor.url, title: monitor.name, percentage: uptimePercentage, - percentageColor: - monitor.status === true - ? theme.palette.success.main - : theme.palette.error.text, - status: - monitor.status === undefined - ? "unknown" - : monitor.status === true - ? "up" - : "down", + percentageColor, + status: monitor.status === true ? "up" : "down", }; - // Reverse checks so latest check is on the right + // Reverse checks so the latest check is on the right const reversedChecks = monitor.checks.slice().reverse(); return { diff --git a/Client/src/Utils/Theme/darkTheme.js b/Client/src/Utils/Theme/darkTheme.js index 15df7c1b8..008de87db 100644 --- a/Client/src/Utils/Theme/darkTheme.js +++ b/Client/src/Utils/Theme/darkTheme.js @@ -57,6 +57,12 @@ const darkTheme = createTheme({ bg: "#1E1E1E", border: "#e88c30", }, + percentage: { + uptimePoor: "#d32f2f", + uptimeFair: "#e88c30", + uptimeGood: "#ffd600", + uptimeExcellent: "#079455", + }, unresolved: { main: "#4e5ba6", light: "#e2eaf7", bg: "#f2f4f7" }, divider: border.light, other: { diff --git a/Client/src/Utils/Theme/lightTheme.js b/Client/src/Utils/Theme/lightTheme.js index bb83d09b2..6688a0ca4 100644 --- a/Client/src/Utils/Theme/lightTheme.js +++ b/Client/src/Utils/Theme/lightTheme.js @@ -54,6 +54,12 @@ const lightTheme = createTheme({ bg: "#ffecbc", border: "#fec84b", }, + percentage: { + uptimePoor: "#d32f2f", + uptimeFair: "#ec8013", + uptimeGood: "#ffb800", + uptimeExcellent: "#079455", + }, unresolved: { main: "#4e5ba6", light: "#e2eaf7", bg: "#f2f4f7" }, divider: border.light, other: { diff --git a/Server/openapi.json b/Server/openapi.json new file mode 100644 index 000000000..23c16521d --- /dev/null +++ b/Server/openapi.json @@ -0,0 +1,835 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "BlueWave Uptime", + "summary": "BlueWave Uptime OpenAPI Specifications", + "description": "BlueWave Uptime is an open source server monitoring application used to track the operational status and performance of servers and websites. It regularly checks whether a server/website is accessible and performs optimally, providing real-time alerts and reports on the monitored services' availability, downtime, and response time.", + "contact": { + "name": "API Support", + "url": "mailto:support@bluewavelabs.ca", + "email": "support@bluewavelabs.ca" + }, + "license": { + "name": "AGPLv3", + "url": "https://github.com/bluewave-labs/bluewave-uptime/tree/HEAD/LICENSE" + }, + "version": "1.0" + }, + "servers": [ + { + "url": "http://localhost:{PORT}/{API_PATH}", + "description": "Local Development Server", + "variables": { + "PORT": { + "description": "API Port", + "enum": ["5000", "3000", "8080"], + "default": "5000" + }, + "API_PATH": { + "description": "API Base Path", + "enum": ["api/v1", "api/v1.1", "api/v2"], + "default": "api/v1" + } + } + } + ], + "tags": [ + { + "name": "auth", + "description": "Authentication" + }, + { + "name": "invite", + "description": "Invite" + }, + { + "name": "monitors", + "description": "Monitors" + }, + { + "name": "checks", + "description": "Checks" + }, + { + "name": "alerts", + "description": "Alerts" + }, + { + "name": "pagespeed", + "description": "Page Speed" + }, + { + "name": "maintenance-window", + "description": "Maintenance window" + }, + { + "name": "healthy", + "description": "Health check" + }, + { + "name": "mail", + "description": "Mail service" + } + ], + "paths": { + "/auth/register": { + "post": { + "tags": ["auth"], + "description": "Register a new user", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["firstName", "lastName", "email", "password", "role", "teamId"], + "properties": { + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + }, + "password": { + "type": "string", + "format": "password" + }, + "profileImage": { + "type": "file", + "format": "file" + }, + "role": { + "type": "array", + "enum": [["user"], ["admin"], ["superadmin"]], + "default": ["superadmin"] + }, + "teamId": { + "type": "string", + "format": "uuid" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserSuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/auth/login": { + "post": { + "tags": ["auth"], + "description": "Login with credentials", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["email", "password"], + "properties": { + "email": { + "type": "string", + "format": "email" + }, + "password": { + "type": "string", + "format": "password" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserSuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/auth/user/:userId": { + "put": { + "tags": ["auth"], + "description": "Change user informations", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserSuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + }, + "delete": { + "tags": ["auth"], + "description": "Delete user", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserSuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/auth/users/admin": { + "get": { + "tags": ["auth"], + "description": "Checks to see if an admin account exists", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserSuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/auth/users": { + "get": { + "tags": ["auth"], + "description": "Get all users", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserSuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/auth/invite": { + "post": { + "tags": ["auth"], + "description": "Invite people to application", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserSuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/auth/invite/verify": { + "post": { + "tags": ["auth"], + "description": "Verify the invite", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserSuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/auth/recovery/request": { + "post": { + "tags": ["auth"], + "description": "Request a recovery token", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserSuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/auth/recovery/validate": { + "post": { + "tags": ["auth"], + "description": "Validate recovery token", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserSuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/auth/recovery/reset": { + "post": { + "tags": ["auth"], + "description": "Password reset", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserSuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/healthy": { + "get": { + "tags": ["healthy"], + "description": "Health check", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "ErrorResponse": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "msg": { + "type": "string" + } + } + }, + "SuccessResponse": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "msg": { + "type": "string" + }, + "data": { + "type": "object" + } + } + }, + "UserSuccessResponse": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "msg": { + "type": "string" + }, + "data": { + "type": "object", + "$ref": "#/components/schemas/UserDataType" + } + } + }, + "MonitorSuccessResponse": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "msg": { + "type": "string" + }, + "data": { + "type": "object", + "$ref": "#/components/schemas/MonitorDataType" + } + } + }, + "CheckDataSuccessResponse": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "msg": { + "type": "string" + }, + "data": { + "type": "object", + "$ref": "#/components/schemas/CheckDataType" + } + } + }, + "AlertDataSuccessResponse": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "msg": { + "type": "string" + }, + "data": { + "type": "object", + "$ref": "#/components/schemas/AlertDataType" + } + } + }, + "UserDataType": { + "type": "object", + "required": [ + "firstname", + "lastname", + "email", + "profilePicUrl", + "isActive", + "isVerified", + "updatedAt", + "createdAt" + ], + "properties": { + "firstname": { + "type": "string" + }, + "lastname": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + }, + "profilePicUrl": { + "type": "string" + }, + "isActive": { + "type": "boolean" + }, + "isVerified": { + "type": "boolean" + }, + "updatedAt": { + "type": "string" + }, + "createdAt": { + "type": "string" + } + } + }, + "MonitorDataType": { + "type": "object", + "required": [ + "userId", + "name", + "description", + "url", + "isActive", + "interval", + "updatedAt", + "createdAt" + ], + "properties": { + "userId": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "isActive": { + "type": "boolean" + }, + "interval": { + "type": "integer" + }, + "updatedAt": { + "type": "string" + }, + "createdAt": { + "type": "string" + } + } + }, + "CheckDataType": { + "type": "object", + "required": [ + "monitorId", + "status", + "responseTime", + "statusCode", + "message", + "updatedAt", + "createdAt" + ], + "properties": { + "monitorId": { + "type": "string" + }, + "status": { + "type": "string" + }, + "responseTime": { + "type": "integer" + }, + "statusCode": { + "type": "integer" + }, + "message": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "createdAt": { + "type": "string" + } + } + }, + "AlertDataType": { + "type": "object", + "required": [ + "checkId", + "monitorId", + "userId", + "status", + "message", + "notifiedStatus", + "acknowledgedStatus", + "updatedAt", + "createdAt" + ], + "properties": { + "checkId": { + "type": "string" + }, + "monitorId": { + "type": "string" + }, + "userId": { + "type": "string" + }, + "status": { + "type": "string" + }, + "message": { + "type": "string" + }, + "notifiedStatus": { + "type": "boolean" + }, + "acknowledgedStatus": { + "type": "boolean" + }, + "updatedAt": { + "type": "string" + }, + "createdAt": { + "type": "string" + } + } + } + } + } +}