From 34ca3a65d6389a484c14e6ca3164eeb3d30253ea Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 17 Jun 2025 10:13:58 +0530 Subject: [PATCH 1/2] Add bulk selection for status page servers --- client/src/Components/Inputs/Search/index.jsx | 88 ++++++++++++++++++- 1 file changed, 85 insertions(+), 3 deletions(-) diff --git a/client/src/Components/Inputs/Search/index.jsx b/client/src/Components/Inputs/Search/index.jsx index e5654faeb..618ae327f 100644 --- a/client/src/Components/Inputs/Search/index.jsx +++ b/client/src/Components/Inputs/Search/index.jsx @@ -1,7 +1,16 @@ import PropTypes from "prop-types"; -import { Box, ListItem, Autocomplete, TextField, Stack, Typography } from "@mui/material"; +import { + Box, + ListItem, + Autocomplete, + TextField, + Stack, + Typography, + Checkbox, +} from "@mui/material"; import { useTheme } from "@emotion/react"; import SearchIcon from "../../../assets/icons/search.svg?react"; +import React, { useEffect } from "react"; /** * Search component using Material UI's Autocomplete. @@ -60,18 +69,67 @@ const Search = ({ onBlur, }) => { const theme = useTheme(); + const [selectAll, setSelectAll] = React.useState(false); + useEffect(() => { + setSelectAll( + multiple && + isAdorned && + Array.isArray(value) && + Array.isArray(options) && + options.length > 0 && + value.length === options.length + ); + }, [multiple, isAdorned, options, value]); + const [open, setOpen] = React.useState(false); + const enhancedOptions = React.useMemo(() => { + return multiple && isAdorned + ? [{ [filteredBy]: "Select All", isSelectAll: true, _id: "select_all" }, ...options] + : options; + }, [multiple, isAdorned, options, filteredBy]); + const isOptionSelected = (option) => { + if (!multiple && !isAdorned) return false; + if (Array.isArray(value)) { + return value.some((item) => item._id === option._id); + } + return false; + }; + const handleSelectAll = (isSelectAll) => { + const newValue = isSelectAll ? [...options] : []; + handleChange(newValue); + setSelectAll(isSelectAll); + setOpen(!isSelectAll); + }; return ( setOpen(true)} + onClose={(event, reason) => { + if (reason === "blur" || reason === "escape") { + setOpen(false); + } + }} inputValue={inputValue} onInputChange={(_, newValue) => { handleInputChange(newValue); }} onChange={(_, newValue) => { - handleChange(newValue); + if (multiple && isAdorned) { + const hasSelectAllSelected = + Array.isArray(newValue) && newValue.some((item) => item.isSelectAll); + if (hasSelectAllSelected) { + handleSelectAll(!selectAll); + } else { + handleChange(newValue); + setSelectAll(Array.isArray(newValue) && newValue.length === options.length); + } + } else { + handleChange(newValue); + setOpen(false); + } }} fullWidth freeSolo @@ -120,6 +178,9 @@ const Search = ({ )} filterOptions={(options, { inputValue }) => { + if (inputValue.trim() === "" && multiple && isAdorned) { + return enhancedOptions; + } const filtered = options.filter((option) => option[filteredBy].toLowerCase().includes(inputValue.toLowerCase()) ); @@ -136,6 +197,7 @@ const Search = ({ const { key, ...optionProps } = props; const hasSecondaryLabel = secondaryLabel && option[secondaryLabel] !== undefined; const port = option["port"]; + const selected = isOptionSelected(option); return ( + {multiple && isAdorned && ( + + )} {option[filteredBy] + (hasSecondaryLabel ? ` (${option[secondaryLabel]}${port ? `: ${port}` : ""})` From 3cacda687c44fe36d14d33ad83bba9e0214fbd82 Mon Sep 17 00:00:00 2001 From: myusername Date: Wed, 18 Jun 2025 19:05:04 +0530 Subject: [PATCH 2/2] fix more changes for bulk selection on status page server --- client/src/Components/Inputs/Search/index.jsx | 29 +- client/src/locales/en.json | 257 ++++++++++++++++++ 2 files changed, 285 insertions(+), 1 deletion(-) diff --git a/client/src/Components/Inputs/Search/index.jsx b/client/src/Components/Inputs/Search/index.jsx index 618ae327f..9689e38c1 100644 --- a/client/src/Components/Inputs/Search/index.jsx +++ b/client/src/Components/Inputs/Search/index.jsx @@ -11,6 +11,10 @@ import { import { useTheme } from "@emotion/react"; import SearchIcon from "../../../assets/icons/search.svg?react"; import React, { useEffect } from "react"; +<<<<<<< Updated upstream +======= +import { useTranslation } from "react-i18next"; +>>>>>>> Stashed changes /** * Search component using Material UI's Autocomplete. @@ -70,6 +74,7 @@ const Search = ({ }) => { const theme = useTheme(); const [selectAll, setSelectAll] = React.useState(false); +<<<<<<< Updated upstream useEffect(() => { setSelectAll( multiple && @@ -84,6 +89,21 @@ const Search = ({ const enhancedOptions = React.useMemo(() => { return multiple && isAdorned ? [{ [filteredBy]: "Select All", isSelectAll: true, _id: "select_all" }, ...options] +======= + const { t } = useTranslation(); + useEffect(() => { + const allSelected = + Array.isArray(value) && Array.isArray(options) && value.length === options.length; + if (selectAll !== allSelected) setSelectAll(allSelected); + }, [value, options]); + const [open, setOpen] = React.useState(false); + const enhancedOptions = React.useMemo(() => { + return multiple && isAdorned + ? [ + { [filteredBy]: t("selectAll"), isSelectAll: true, _id: "select_all" }, + ...options, + ] +>>>>>>> Stashed changes : options; }, [multiple, isAdorned, options, filteredBy]); const isOptionSelected = (option) => { @@ -97,7 +117,10 @@ const Search = ({ const newValue = isSelectAll ? [...options] : []; handleChange(newValue); setSelectAll(isSelectAll); +<<<<<<< Updated upstream setOpen(!isSelectAll); +======= +>>>>>>> Stashed changes }; return ( option[filteredBy]} isOptionEqualToValue={(option, value) => option._id === value._id} // Compare by unique identifier renderInput={(params) => ( @@ -219,7 +242,11 @@ const Search = ({ : {} } > +<<<<<<< Updated upstream {multiple && isAdorned && ( +======= + {multiple && isAdorned && !option.noOptions && ( +>>>>>>> Stashed changes to get the full container ID.", "port": "Enter the URL or IP of the server, the port number and a clear display name that appears on the dashboard." +<<<<<<< Updated upstream } +======= + }, + "common": { + "appName": "Checkmate", + "monitoringAgentName": "Capture", + "buttons": { + "toggleTheme": "Toggles light & dark" + }, + "toasts": { + "networkError": "Network error", + "checkConnection": "Please check your connection", + "unknownError": "Unknown error" + } + }, + "auth": { + "common": { + "navigation": { + "continue": "Continue", + "back": "Back" + }, + "inputs": { + "email": { + "label": "Email", + "placeholder": "jordan.ellis@domain.com", + "errors": { + "empty": "To continue, please enter your email address", + "invalid": "Please recheck validity of entered email address" + } + }, + "password": { + "label": "Password", + "rules": { + "length": { + "beginning": "Must be at least", + "highlighted": "8 characters long" + }, + "special": { + "beginning": "Must contain at least", + "highlighted": "one special character" + }, + "number": { + "beginning": "Must contain at least", + "highlighted": "one number" + }, + "uppercase": { + "beginning": "Must contain at least", + "highlighted": "one upper character" + }, + "lowercase": { + "beginning": "Must contain at least", + "highlighted": "one lower character" + }, + "match": { + "beginning": "Confirm password and password", + "highlighted": "must match" + } + }, + "errors": { + "empty": "Please enter your password", + "length": "Password must be at least 8 characters long", + "uppercase": "Password must contain at least 1 uppercase letter", + "lowercase": "Password must contain at least 1 lowercase letter", + "number": "Password must contain at least 1 number", + "special": "Password must contain at least 1 special character", + "incorrect": "The password you provided does not match our records" + } + }, + "passwordConfirm": { + "label": "Confirm password", + "placeholder": "Re-enter password to confirm", + "errors": { + "empty": "Please enter your password again for confirmation (helps with typos)", + "different": "Entered passwords don't match, so one of them is probably mistyped" + } + }, + "firstName": { + "label": "Name", + "placeholder": "Jordan", + "errors": { + "empty": "Please enter your name", + "length": "Name must be less than 50 characters", + "pattern": "Name must contain only letters, spaces, apostrophes, or hyphens" + } + }, + "lastName": { + "label": "Surname", + "placeholder": "Ellis", + "errors": { + "empty": "Please enter your surname", + "length": "Surname must be less than 50 characters", + "pattern": "Surname must contain only letters, spaces, apostrophes, or hyphens" + } + } + }, + "errors": { + "validation": "Error validating data." + } + }, + "login": { + "heading": "Log In", + "subheadings": { + "stepOne": "Enter your email", + "stepTwo": "Enter your password" + }, + "links": { + "forgotPassword": "Forgot password?", + "register": "Do not have an account?", + "forgotPasswordLink": "Reset password", + "registerLink": "Register here" + }, + "toasts": { + "success": "Welcome back! You're successfully logged in.", + "incorrectPassword": "Incorrect password" + }, + "errors": { + "password": { + "incorrect": "The password you provided does not match our records" + } + } + }, + "registration": { + "heading": { + "superAdmin": "Create super admin", + "user": "Sign Up" + }, + "subheadings": { + "stepOne": "Enter your personal details", + "stepTwo": "Enter your email", + "stepThree": "Create your password" + }, + "description": { + "superAdmin": "Create your super admin account to get started", + "user": "Sign up as a user and ask super admin for access to your monitors" + }, + "gettingStartedButton": { + "superAdmin": "Create super admin account", + "user": "Sign up regular user" + }, + "termsAndPolicies": "By creating an account, you agree to our Terms of Service and Privacy Policy.", + "links": { + "login": "Already have an account? Log In" + }, + "toasts": { + "success": "Welcome! Your account was created successfully." + } + }, + "forgotPassword": { + "heading": "Forgot password?", + "subheadings": { + "stepOne": "No worries, we'll send you reset instructions.", + "stepTwo": "We sent a password reset link to ", + "stepThree": "Your new password must be different from previously used passwords.", + "stepFour": "Your password has been successfully reset. Click below to log in magically." + }, + "buttons": { + "openEmail": "Open email app", + "resetPassword": "Reset password" + }, + "imageAlts": { + "passwordKey": "Password key icon", + "email": "Email icon", + "lock": "Lock icon", + "passwordConfirm": "Password confirm icon" + }, + "links": { + "login": "Go back to Log In", + "resend": "Didn't receive the email? Click to resend" + }, + "toasts": { + "sent": "Instructions sent to .", + "emailNotFound": "Email not found.", + "redirect": "Redirecting in ...", + "success": "Your password was reset successfully.", + "error": "Unable to reset password. Please try again later or contact support." + } + } + }, + "errorPages": { + "serverUnreachable": { + "toasts": { + "reconnected": "Successfully reconnected to the server.", + "stillUnreachable": "Server is still unreachable. Please try again later." + }, + "alertBox": "Server Connection Error", + "description": "We're unable to connect to the server. Please check your internet connection or verify your deployment configuration if the problem persists.", + "retryButton": { + "default": "Retry connection", + "processing": "Connecting..." + } + } + }, + "createNotifications": { + "title": "Create notification channel", + "nameSettings": { + "title": "Name", + "description": "A descriptive name for your integration.", + "nameLabel": "Name", + "namePlaceholder": "e.g. Slack notifications" + }, + "typeSettings": { + "title": "Type", + "description": "Select the type of notification channel you want to create.", + "typeLabel": "Type" + }, + "emailSettings": { + "title": "Email", + "description": "Destination email addresses.", + "emailLabel": "Email address", + "emailPlaceholder": "e.g. john@example.com" + }, + "slackSettings": { + "title": "Slack", + "description": "Configure your Slack webhook here", + "webhookLabel": "Slack webhook URL", + "webhookPlaceholder": "https://hooks.slack.com/services/..." + }, + "pagerdutySettings": { + "title": "PagerDuty", + "description": "Configure your PagerDuty integration here", + "integrationKeyLabel": "Integration key", + "integrationKeyPlaceholder": "1234567890" + }, + "genericWebhookSettings": { + "title": "Webhook", + "description": "Configure your webhook here", + "webhookLabel": "Webhook URL", + "webhookPlaceholder": "https://your-server.com/webhook" + }, + "discordSettings": { + "title": "Discord", + "description": "Configure your Discord webhook here", + "webhookLabel": "Discord Webhook URL", + "webhookPlaceholder": "https://your-server.com/webhook" + } + }, + "notificationConfig": { + "title": "Notifications", + "description": "Select the notifications channels you want to use" + }, + "monitorStatus": { + "checkingEvery": "Checking every {{interval}}", + "withCaptureAgent": "with Capture agent {{version}}", + "up": "up", + "down": "down", + "paused": "paused" + }, + "advancedMatching": "Advanced matching", + "settingsEmailTLSServername": "TLS Servername - Optional Hostname for TLS Validation when host is an IP", + "settingsEmailIgnoreTLS": "Ignore TLS - Disable STARTTLS", + "settingsEmailRequireTLS": "Require TLS - Force STARTTLS", + "settingsEmailRejectUnauthorized": "Reject Unauthorized", + "settingsEmailSecure": "Secure - Use SSL", + "settingsEmailPool": "Pool - Enable connection pooling", + "sendTestNotifications": "Send test notifications", + "selectAll": "Select all" +>>>>>>> Stashed changes }