Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
35ee4d3bcb | ||
|
|
162b081858 | ||
|
|
6c0aea46a7 | ||
|
|
f1a6afb860 | ||
|
|
5c52a208b9 | ||
|
|
4fdc0bde5c | ||
|
|
3d4ebe9588 | ||
|
|
62ea7b0e5b | ||
|
|
8747c38f40 | ||
|
|
d633fc5ccb | ||
|
|
0d7a35367f | ||
|
|
af141ec772 | ||
|
|
5784fec3d1 | ||
|
|
95cd33ac06 | ||
|
|
2a591ee503 | ||
|
|
f5aa24d77e | ||
|
|
7874439e4c | ||
|
|
7140d436a5 | ||
|
|
ba0c89fc6c | ||
|
|
b5caabed20 | ||
|
|
8814596d6e | ||
|
|
c60d07c3a0 | ||
|
|
54ecf02d9a | ||
|
|
1ab31d19f3 | ||
|
|
0f764ca084 | ||
|
|
ed80837810 | ||
|
|
cf67c82f5e | ||
|
|
5a7b51bfec |
No files matched your search
@@ -0,0 +1,145 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
Checkmate is an open-source uptime and infrastructure monitoring application. It monitors server hardware, uptime, response times, and incidents with real-time alerts. The companion agent [Capture](https://github.com/bluewave-labs/capture) provides infrastructure metrics (CPU, RAM, disk, temperature).
|
||||
|
||||
## Development Commands
|
||||
|
||||
### Client (React/Vite)
|
||||
```bash
|
||||
cd client
|
||||
npm install
|
||||
npm run dev # Start dev server at http://localhost:5173
|
||||
npm run build # TypeScript check + production build
|
||||
npm run lint # ESLint (strict, max-warnings 0)
|
||||
npm run format # Prettier formatting
|
||||
npm run format-check # Check formatting
|
||||
```
|
||||
|
||||
### Server (Node.js/Express)
|
||||
```bash
|
||||
cd server
|
||||
npm install
|
||||
npm run dev # Start with hot-reload (nodemon + tsx) at http://localhost:52345
|
||||
npm run build # TypeScript compile + path alias resolution
|
||||
npm run test # Run Mocha tests with c8 coverage
|
||||
npm run lint # ESLint v9
|
||||
npm run lint-fix # Auto-fix lint issues
|
||||
npm run format # Prettier formatting
|
||||
```
|
||||
|
||||
### Docker Development
|
||||
```bash
|
||||
cd docker/dev
|
||||
./build_images.sh
|
||||
docker run -d -p 27017:27017 -v uptime_mongo_data:/data/db --name uptime_database_mongo mongo:6.0
|
||||
```
|
||||
|
||||
## Environment Setup
|
||||
|
||||
### Server `.env` (minimum required)
|
||||
```env
|
||||
CLIENT_HOST="http://localhost:5173"
|
||||
JWT_SECRET="my_secret_key_change_this"
|
||||
DB_CONNECTION_STRING="mongodb://localhost:27017/uptime_db"
|
||||
TOKEN_TTL="99d"
|
||||
ORIGIN="localhost"
|
||||
LOG_LEVEL="debug"
|
||||
```
|
||||
|
||||
### Client `.env`
|
||||
```env
|
||||
VITE_APP_API_BASE_URL="http://localhost:52345/api/v1"
|
||||
VITE_APP_LOG_LEVEL="debug"
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
### Monorepo Structure
|
||||
- `/client` - React 18 + TypeScript + Vite + MUI frontend
|
||||
- `/server` - Node.js 20+ + Express + TypeScript backend
|
||||
- `/docker` - Multi-environment Docker configs (dev, staging, prod, arm, mono)
|
||||
|
||||
### Backend Layers
|
||||
```
|
||||
server/src/
|
||||
├── controllers/ # Route handlers (authController, monitorController, etc.)
|
||||
├── service/ # Business logic
|
||||
│ ├── business/ # Core monitoring logic
|
||||
│ ├── infrastructure/ # Server/system utilities
|
||||
│ └── system/ # App-level settings
|
||||
├── db/
|
||||
│ ├── models/ # Mongoose schemas (Monitor, Check, Incident, User, etc.)
|
||||
│ ├── migration/ # Database migrations (run on startup)
|
||||
│ └── modules/ # Database-specific modules
|
||||
├── middleware/v1/ # verifyJWT, rateLimiter, sanitization, responseHandler
|
||||
├── routes/v1/ # API route definitions
|
||||
├── validation/ # Joi input validation schemas
|
||||
└── repositories/ # Data access layer
|
||||
```
|
||||
|
||||
### Frontend Structure
|
||||
```
|
||||
client/src/
|
||||
├── Components/ # Reusable UI components
|
||||
├── Pages/ # Page components (Auth, Uptime, Infrastructure, Incidents, etc.)
|
||||
├── Features/ # Redux slices (Auth, UI)
|
||||
├── Hooks/ # Custom React hooks
|
||||
├── Utils/ # Utilities (NetworkService.js is main API client)
|
||||
├── Validation/ # Input validation
|
||||
└── locales/ # i18n translations
|
||||
```
|
||||
|
||||
### API
|
||||
- Base URL: `/api/v1`
|
||||
- Documentation: `http://localhost:52345/api-docs` (Swagger UI)
|
||||
- OpenAPI spec: `/server/openapi.json`
|
||||
|
||||
### Key Technologies
|
||||
- **State Management**: Redux Toolkit + Redux-Persist
|
||||
- **Data Fetching**: SWR + Axios
|
||||
- **Database**: MongoDB with Mongoose ODM
|
||||
- **Queue/Cache**: Redis + BullMQ + Pulse (cron scheduling)
|
||||
- **i18n**: i18next + react-i18next (translations via PoEditor)
|
||||
|
||||
## Code Conventions
|
||||
|
||||
### Internationalization
|
||||
All user-facing strings must use the translation function:
|
||||
```javascript
|
||||
t('your.key') // Never hardcode UI strings
|
||||
```
|
||||
|
||||
### Branching
|
||||
- Always branch from `develop` (not master)
|
||||
- Use descriptive names: `feat/add-alerts`, `fix/login-error`
|
||||
- PRs target `develop` branch
|
||||
|
||||
### Formatting
|
||||
- **Client**: Prettier with `printWidth: 90`, tabs, double quotes
|
||||
- **Server**: Prettier with `printWidth: 150`, tabs, double quotes
|
||||
- Both use ESLint with strict settings
|
||||
|
||||
### Testing
|
||||
Server tests use Mocha + Chai + Sinon:
|
||||
```bash
|
||||
npm test # Run all tests with coverage
|
||||
npm test -- --grep "pattern" # Run specific tests
|
||||
```
|
||||
Test files: `server/tests/**/*.test.js`
|
||||
|
||||
## Database Models
|
||||
|
||||
Key Mongoose models in `/server/src/db/models/`:
|
||||
- **Monitor** - Monitoring configuration (website, infrastructure, port, etc.)
|
||||
- **Check** - Individual monitoring check results
|
||||
- **Incident** - Downtime incidents
|
||||
- **User** - User accounts
|
||||
- **Team** - Team/workspace management
|
||||
- **StatusPage** - Public status pages
|
||||
- **Notification** - Alert configuration (email, Discord, Slack, webhooks)
|
||||
- **MaintenanceWindow** - Scheduled maintenance periods
|
||||
- **AppSettings** - Global application settings
|
||||
@@ -12,7 +12,6 @@
|
||||
"@emotion/styled": "^11.13.0",
|
||||
"@hello-pangea/dnd": "^18.0.0",
|
||||
"@hookform/resolvers": "5.2.2",
|
||||
"@mui/icons-material": "6.4.11",
|
||||
"@mui/lab": "6.0.0-dev.240424162023-9968b4889d",
|
||||
"@mui/material": "6.4.11",
|
||||
"@mui/x-charts": "^7.5.1",
|
||||
@@ -25,6 +24,7 @@
|
||||
"human-interval": "2.0.1",
|
||||
"i18next": "25.4.2",
|
||||
"joi": "17.13.3",
|
||||
"lucide-react": "^0.562.0",
|
||||
"mui-color-input": "^6.0.0",
|
||||
"pretty-ms": "9.3.0",
|
||||
"react": "18.3.1",
|
||||
@@ -1189,32 +1189,6 @@
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/icons-material": {
|
||||
"version": "6.4.11",
|
||||
"resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.4.11.tgz",
|
||||
"integrity": "sha512-+jjJGIrB1awNbMv4ZVPPdN/p7O1UKFZ+xqRvNIQ8B1KnlID5hPMPBLM6UUbRF4bu3UDCbu79rn9Nye5LGNzmeA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.26.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@mui/material": "^6.4.11",
|
||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/lab": {
|
||||
"version": "6.0.0-dev.240424162023-9968b4889d",
|
||||
"resolved": "https://registry.npmjs.org/@mui/lab/-/lab-6.0.0-dev.240424162023-9968b4889d.tgz",
|
||||
@@ -2050,9 +2024,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@remix-run/router": {
|
||||
"version": "1.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz",
|
||||
"integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==",
|
||||
"version": "1.23.2",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz",
|
||||
"integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
@@ -5297,9 +5271,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1"
|
||||
@@ -5464,6 +5438,15 @@
|
||||
"yallist": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/lucide-react": {
|
||||
"version": "0.562.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.562.0.tgz",
|
||||
"integrity": "sha512-82hOAu7y0dbVuFfmO4bYF1XEwYk/mEbM5E+b1jgci/udUBEE/R7LF5Ip0CCEmXe8AybRM8L+04eP+LGZeDvkiw==",
|
||||
"license": "ISC",
|
||||
"peerDependencies": {
|
||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
@@ -6129,12 +6112,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "6.30.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.1.tgz",
|
||||
"integrity": "sha512-X1m21aEmxGXqENEPG3T6u0Th7g0aS4ZmoNynhbs+Cn+q+QGTLt+d5IQ2bHAXKzKcxGJjxACpVbnYQSCRcfxHlQ==",
|
||||
"version": "6.30.3",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz",
|
||||
"integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@remix-run/router": "1.23.0"
|
||||
"@remix-run/router": "1.23.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
@@ -6144,13 +6127,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-router-dom": {
|
||||
"version": "6.30.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.1.tgz",
|
||||
"integrity": "sha512-llKsgOkZdbPU1Eg3zK8lCn+sjD9wMRZZPuzmdWWX5SUs8OFkN5HnFVC0u5KMeMaC9aoancFI/KoLuKPqN+hxHw==",
|
||||
"version": "6.30.3",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz",
|
||||
"integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@remix-run/router": "1.23.0",
|
||||
"react-router": "6.30.1"
|
||||
"@remix-run/router": "1.23.2",
|
||||
"react-router": "6.30.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
"@emotion/styled": "^11.13.0",
|
||||
"@hello-pangea/dnd": "^18.0.0",
|
||||
"@hookform/resolvers": "5.2.2",
|
||||
"@mui/icons-material": "6.4.11",
|
||||
"@mui/lab": "6.0.0-dev.240424162023-9968b4889d",
|
||||
"@mui/material": "6.4.11",
|
||||
"@mui/x-charts": "^7.5.1",
|
||||
@@ -30,6 +29,7 @@
|
||||
"human-interval": "2.0.1",
|
||||
"i18next": "25.4.2",
|
||||
"joi": "17.13.3",
|
||||
"lucide-react": "^0.562.0",
|
||||
"mui-color-input": "^6.0.0",
|
||||
"pretty-ms": "9.3.0",
|
||||
"react": "18.3.1",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import Menu from "@mui/material/Menu";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import Settings from "../../../assets/icons/settings-bold.svg?react";
|
||||
import Icon from "../Icon";
|
||||
import Dialog from "../Dialog/index.jsx";
|
||||
|
||||
// Utils
|
||||
@@ -84,7 +84,10 @@ const ActionsMenu = ({
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Settings />
|
||||
<Icon
|
||||
name="Settings"
|
||||
size={20}
|
||||
/>
|
||||
</IconButton>
|
||||
|
||||
<Menu
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import PropTypes from "prop-types";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { Box, Button, IconButton, Stack, Typography } from "@mui/material";
|
||||
import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
|
||||
import ErrorOutlineOutlinedIcon from "@mui/icons-material/ErrorOutlineOutlined";
|
||||
import WarningAmberOutlinedIcon from "@mui/icons-material/WarningAmberOutlined";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import Icon from "../Icon";
|
||||
import "./index.css";
|
||||
|
||||
/**
|
||||
@@ -13,9 +10,24 @@ import "./index.css";
|
||||
*/
|
||||
|
||||
const icons = {
|
||||
info: <InfoOutlinedIcon />,
|
||||
error: <ErrorOutlineOutlinedIcon />,
|
||||
warning: <WarningAmberOutlinedIcon />,
|
||||
info: (
|
||||
<Icon
|
||||
name="Info"
|
||||
size={24}
|
||||
/>
|
||||
),
|
||||
error: (
|
||||
<Icon
|
||||
name="AlertCircle"
|
||||
size={24}
|
||||
/>
|
||||
),
|
||||
warning: (
|
||||
<Icon
|
||||
name="AlertTriangle"
|
||||
size={24}
|
||||
/>
|
||||
),
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -97,10 +109,9 @@ const Alert = ({ variant, title, body, isToast, hasIcon = true, onClick }) => {
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CloseIcon
|
||||
sx={{
|
||||
fontSize: "20px",
|
||||
}}
|
||||
<Icon
|
||||
name="X"
|
||||
size={20}
|
||||
/>
|
||||
</IconButton>
|
||||
)}
|
||||
|
||||
@@ -1,27 +1,28 @@
|
||||
import LeftArrow from "../../../assets/icons/left-arrow.svg?react";
|
||||
import LeftArrowDouble from "../../../assets/icons/left-arrow-double.svg?react";
|
||||
import LeftArrowLong from "../../../assets/icons/left-arrow-long.svg?react";
|
||||
import Icon from "../Icon";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const ArrowLeft = ({ type, color = "#667085", ...props }) => {
|
||||
if (type === "double") {
|
||||
return (
|
||||
<LeftArrowDouble
|
||||
style={{ color }}
|
||||
<Icon
|
||||
name="ChevronsLeft"
|
||||
color={color}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
} else if (type === "long") {
|
||||
return (
|
||||
<LeftArrowLong
|
||||
style={{ color }}
|
||||
<Icon
|
||||
name="ArrowLeft"
|
||||
color={color}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<LeftArrow
|
||||
style={{ color }}
|
||||
<Icon
|
||||
name="ChevronLeft"
|
||||
color={color}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import RightArrow from "../../../assets/icons/right-arrow.svg?react";
|
||||
import RightArrowDouble from "../../../assets/icons/right-arrow-double.svg?react";
|
||||
import Icon from "../Icon";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const ArrowRight = ({ type, color = "#667085", ...props }) => {
|
||||
if (type === "double") {
|
||||
return (
|
||||
<RightArrowDouble
|
||||
style={{ color }}
|
||||
<Icon
|
||||
name="ChevronsRight"
|
||||
color={color}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<RightArrow
|
||||
style={{ color }}
|
||||
<Icon
|
||||
name="ChevronRight"
|
||||
color={color}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import "./check.css";
|
||||
import PropTypes from "prop-types";
|
||||
import CheckGrey from "../../../assets/icons/check.svg?react";
|
||||
import CheckOutlined from "../../../assets/icons/check-outlined.svg?react";
|
||||
import Icon from "../Icon";
|
||||
import { Box, Stack, Typography } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
|
||||
@@ -35,16 +34,16 @@ const Check = ({ text, noHighlightText, variant = "info", outlined = false }) =>
|
||||
alignItems="center"
|
||||
>
|
||||
{outlined ? (
|
||||
<CheckOutlined alt="check" />
|
||||
<Icon
|
||||
name="CheckCircle"
|
||||
size={20}
|
||||
/>
|
||||
) : (
|
||||
<Box
|
||||
lineHeight={0}
|
||||
sx={{
|
||||
"& svg > path": { fill: colors[variant] },
|
||||
}}
|
||||
>
|
||||
<CheckGrey alt="form checks" />
|
||||
</Box>
|
||||
<Icon
|
||||
name="Check"
|
||||
size={16}
|
||||
color={colors[variant]}
|
||||
/>
|
||||
)}
|
||||
<Typography
|
||||
component="span"
|
||||
|
||||
@@ -8,8 +8,7 @@ import IconButton from "@mui/material/IconButton";
|
||||
import Container from "@mui/material/Container";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import Drawer from "@mui/material/Drawer";
|
||||
import MenuIcon from "@mui/icons-material/Menu";
|
||||
import CloseRoundedIcon from "@mui/icons-material/CloseRounded";
|
||||
import Icon from "../Icon";
|
||||
import ThemeSwitch from "../ThemeSwitch/index.jsx";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { useSelector } from "react-redux";
|
||||
@@ -153,7 +152,11 @@ const AppAppBar = () => {
|
||||
aria-label="Menu button"
|
||||
onClick={toggleDrawer(true)}
|
||||
>
|
||||
<MenuIcon sx={{ color: theme.palette.text.primary }} />
|
||||
<Icon
|
||||
name="Menu"
|
||||
size={24}
|
||||
color={theme.palette.text.primary}
|
||||
/>
|
||||
</IconButton>
|
||||
<Drawer
|
||||
anchor="top"
|
||||
@@ -176,7 +179,11 @@ const AppAppBar = () => {
|
||||
}}
|
||||
>
|
||||
<IconButton onClick={toggleDrawer(false)}>
|
||||
<CloseRoundedIcon sx={{ color: theme.palette.text.primary }} />
|
||||
<Icon
|
||||
name="X"
|
||||
size={24}
|
||||
color={theme.palette.text.primary}
|
||||
/>
|
||||
</IconButton>
|
||||
</Box>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Checkbox, FormControl, ListItemText, MenuItem, Select } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import PropTypes from "prop-types";
|
||||
import AddCircleOutlineIcon from "@mui/icons-material/AddCircleOutline";
|
||||
import Icon from "../Icon";
|
||||
|
||||
/**
|
||||
* A reusable filter header component that displays a dropdown menu with selectable options.
|
||||
@@ -29,9 +29,10 @@ const FilterHeader = ({ header, options, value, onChange, multiple = true }) =>
|
||||
<Select
|
||||
multiple={multiple}
|
||||
IconComponent={(props) => (
|
||||
<AddCircleOutlineIcon
|
||||
<Icon
|
||||
{...props}
|
||||
sx={{ fontSize: "medium" }}
|
||||
name="PlusCircle"
|
||||
size={18}
|
||||
/>
|
||||
)}
|
||||
displayEmpty
|
||||
|
||||
@@ -23,12 +23,12 @@ const Host = ({ url, title, percentageColor, percentage, showURL, status }) => {
|
||||
direction="row"
|
||||
position="relative"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(5)}
|
||||
gap={theme.spacing(4)}
|
||||
>
|
||||
<Typography
|
||||
variant="h6"
|
||||
component="span"
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
@@ -38,9 +38,9 @@ const Host = ({ url, title, percentageColor, percentage, showURL, status }) => {
|
||||
<>
|
||||
<Dot />
|
||||
<Typography
|
||||
variant="h6"
|
||||
component="span"
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
fontWeight: 500,
|
||||
color: percentageColor,
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* Maps legacy icon names to Lucide icon names
|
||||
* Use: import { iconMap } from './iconMap'; LucideIcons[iconMap['monitors']]
|
||||
*/
|
||||
export const iconMap = {
|
||||
// Navigation/Sidebar icons
|
||||
monitors: "Globe",
|
||||
incidents: "AlertTriangle",
|
||||
integrations: "Link",
|
||||
"page-speed": "Gauge",
|
||||
settings: "Settings",
|
||||
notifications: "Bell",
|
||||
maintenance: "Wrench",
|
||||
"status-pages": "Wifi",
|
||||
docs: "FileText",
|
||||
discussions: "MessageCircle",
|
||||
support: "HelpCircle",
|
||||
changeLog: "Code",
|
||||
logs: "Database",
|
||||
|
||||
// User icons
|
||||
user: "User",
|
||||
"user-two": "Users",
|
||||
"user-edit": "UserPen",
|
||||
groups: "Users",
|
||||
lock: "Lock",
|
||||
key: "Key",
|
||||
logout: "LogOut",
|
||||
|
||||
// Action icons
|
||||
edit: "Pencil",
|
||||
"trash-bin": "Trash2",
|
||||
search: "Search",
|
||||
"close-icon": "X",
|
||||
check: "Check",
|
||||
"check-icon": "CheckCircle",
|
||||
"check-outlined": "CheckCircle",
|
||||
|
||||
// Arrow icons
|
||||
"left-arrow": "ChevronLeft",
|
||||
"right-arrow": "ChevronRight",
|
||||
"up-arrow": "ChevronUp",
|
||||
"down-arrow": "ChevronDown",
|
||||
"left-arrow-double": "ChevronsLeft",
|
||||
"right-arrow-double": "ChevronsRight",
|
||||
"left-arrow-long": "ArrowLeft",
|
||||
"top-right-arrow": "ArrowUpRight",
|
||||
"open-in-new-page": "ExternalLink",
|
||||
"selector-vertical": "ChevronsUpDown",
|
||||
|
||||
// Status/Alert icons
|
||||
"alert-icon": "Bell",
|
||||
"warning-icon": "AlertCircle",
|
||||
"history-icon": "History",
|
||||
|
||||
// Data/Chart icons
|
||||
dashboard: "LayoutGrid",
|
||||
"monitor-graph-line": "TrendingUp",
|
||||
"response-time-icon": "TrendingUp",
|
||||
"uptime-icon": "Activity",
|
||||
"speedometer-icon": "Gauge",
|
||||
"spedometer-icon": "Gauge",
|
||||
"ruler-icon": "Ruler",
|
||||
"performance-report": "Layers",
|
||||
"average-response-icon": "BarChart3",
|
||||
"distributed-uptime": "Network",
|
||||
|
||||
// Misc icons
|
||||
calendar: "Calendar",
|
||||
"calendar-check": "CalendarCheck",
|
||||
"clock-snooze": "Clock",
|
||||
mail: "Mail",
|
||||
email: "Mail",
|
||||
folder: "Folder",
|
||||
"dots-vertical": "MoreVertical",
|
||||
"pause-icon": "PauseCircle",
|
||||
"resume-icon": "PlayCircle",
|
||||
"checkbox-filled": "CheckSquare",
|
||||
"checkbox-outline": "Square",
|
||||
"radio-checked": "Circle",
|
||||
"cpu-chip": "Cpu",
|
||||
certificate: "Award",
|
||||
"interval-check": "RefreshCw",
|
||||
};
|
||||
|
||||
/**
|
||||
* MUI icon to Lucide mapping
|
||||
*/
|
||||
export const muiToLucideMap = {
|
||||
AddCircleOutline: "PlusCircle",
|
||||
ArrowDownwardRounded: "ArrowDown",
|
||||
ArrowUpwardRounded: "ArrowUp",
|
||||
ArrowDropDown: "ChevronDown",
|
||||
ArrowOutward: "ExternalLink",
|
||||
CheckCircle: "CheckCircle2",
|
||||
Clear: "X",
|
||||
Close: "X",
|
||||
CloseRounded: "X",
|
||||
CloudUpload: "Upload",
|
||||
Delete: "Trash2",
|
||||
DeleteOutlineRounded: "Trash2",
|
||||
Email: "Mail",
|
||||
ErrorOutline: "AlertTriangle",
|
||||
ErrorOutlineOutlined: "AlertTriangle",
|
||||
Image: "ImageIcon",
|
||||
InfoOutlined: "Info",
|
||||
KeyboardArrowDown: "ChevronDown",
|
||||
Menu: "Menu",
|
||||
PauseCircleOutline: "PauseCircle",
|
||||
PauseOutlined: "Pause",
|
||||
PlayArrowOutlined: "Play",
|
||||
PlayCircleOutlineRounded: "PlayCircle",
|
||||
RadioButtonChecked: "Circle",
|
||||
ReorderRounded: "GripVertical",
|
||||
SettingsOutlined: "Settings",
|
||||
Share: "Share2",
|
||||
Visibility: "Eye",
|
||||
VisibilityOff: "EyeOff",
|
||||
WarningAmberOutlined: "AlertTriangle",
|
||||
};
|
||||
@@ -0,0 +1,196 @@
|
||||
import { useTheme } from "@mui/material";
|
||||
import PropTypes from "prop-types";
|
||||
import {
|
||||
Activity,
|
||||
AlertCircle,
|
||||
AlertTriangle,
|
||||
ArrowDown,
|
||||
ArrowLeft,
|
||||
ArrowUp,
|
||||
ArrowUpRight,
|
||||
Bell,
|
||||
Calendar,
|
||||
Check,
|
||||
CheckCircle,
|
||||
CheckSquare,
|
||||
ChevronDown,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
ChevronsLeft,
|
||||
ChevronsRight,
|
||||
ChevronsUpDown,
|
||||
Circle,
|
||||
CircleDot,
|
||||
Clock,
|
||||
Code,
|
||||
Cpu,
|
||||
Database,
|
||||
ExternalLink,
|
||||
Eye,
|
||||
EyeOff,
|
||||
FileText,
|
||||
Gauge,
|
||||
Globe,
|
||||
GripVertical,
|
||||
HelpCircle,
|
||||
History,
|
||||
Image as ImageIcon,
|
||||
Info,
|
||||
Key,
|
||||
Layers,
|
||||
Link,
|
||||
Lock,
|
||||
LogOut,
|
||||
Mail,
|
||||
Menu,
|
||||
MessageCircle,
|
||||
MoreVertical,
|
||||
Pause,
|
||||
PauseCircle,
|
||||
Play,
|
||||
PlayCircle,
|
||||
PlusCircle,
|
||||
RefreshCw,
|
||||
Ruler,
|
||||
Search,
|
||||
Settings,
|
||||
Square,
|
||||
Trash2,
|
||||
TrendingUp,
|
||||
Upload,
|
||||
User,
|
||||
Users,
|
||||
Wifi,
|
||||
Wrench,
|
||||
X,
|
||||
} from "lucide-react";
|
||||
|
||||
/**
|
||||
* Map of icon names to Lucide icon components.
|
||||
* Only icons explicitly imported here will be included in the bundle (tree-shaking).
|
||||
* To add a new icon: import it above and add it to this object.
|
||||
*/
|
||||
const iconComponents = {
|
||||
Activity,
|
||||
AlertCircle,
|
||||
AlertTriangle,
|
||||
ArrowDown,
|
||||
ArrowLeft,
|
||||
ArrowUp,
|
||||
ArrowUpRight,
|
||||
Bell,
|
||||
Calendar,
|
||||
Check,
|
||||
CheckCircle,
|
||||
CheckSquare,
|
||||
ChevronDown,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
ChevronsLeft,
|
||||
ChevronsRight,
|
||||
ChevronsUpDown,
|
||||
Circle,
|
||||
CircleDot,
|
||||
Clock,
|
||||
Code,
|
||||
Cpu,
|
||||
Database,
|
||||
ExternalLink,
|
||||
Eye,
|
||||
EyeOff,
|
||||
FileText,
|
||||
Gauge,
|
||||
Globe,
|
||||
GripVertical,
|
||||
HelpCircle,
|
||||
History,
|
||||
Image: ImageIcon,
|
||||
Info,
|
||||
Key,
|
||||
Layers,
|
||||
Link,
|
||||
Lock,
|
||||
LogOut,
|
||||
Mail,
|
||||
Menu,
|
||||
MessageCircle,
|
||||
MoreVertical,
|
||||
Pause,
|
||||
PauseCircle,
|
||||
Play,
|
||||
PlayCircle,
|
||||
PlusCircle,
|
||||
RefreshCw,
|
||||
Ruler,
|
||||
Search,
|
||||
Settings,
|
||||
Square,
|
||||
Trash2,
|
||||
TrendingUp,
|
||||
Upload,
|
||||
User,
|
||||
Users,
|
||||
Wifi,
|
||||
Wrench,
|
||||
X,
|
||||
};
|
||||
|
||||
/**
|
||||
* Theme-aware icon component wrapping Lucide icons
|
||||
*
|
||||
* @param {string} name - Lucide icon name (e.g., "Bell", "Check", "ArrowLeft")
|
||||
* @param {number} size - Icon size in pixels (default: 20)
|
||||
* @param {string} color - Direct color value OR theme path like "primary.contrastText"
|
||||
* @param {number} strokeWidth - Stroke width (default: 1.5)
|
||||
* @param {string} fill - Fill color (default: "none")
|
||||
*/
|
||||
const Icon = ({ name, size = 20, color, strokeWidth = 1.5, fill = "none", ...props }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
// Resolve color from theme path or use direct value
|
||||
const resolveColor = (colorValue) => {
|
||||
if (!colorValue) {
|
||||
return theme.palette.primary.contrastTextTertiary; // Default icon color
|
||||
}
|
||||
|
||||
// If it's a theme path like "primary.contrastText"
|
||||
if (typeof colorValue === "string" && colorValue.includes(".")) {
|
||||
const parts = colorValue.split(".");
|
||||
let resolved = theme.palette;
|
||||
for (const part of parts) {
|
||||
resolved = resolved?.[part];
|
||||
}
|
||||
return resolved || colorValue;
|
||||
}
|
||||
|
||||
// Direct color value
|
||||
return colorValue;
|
||||
};
|
||||
|
||||
const LucideIcon = iconComponents[name];
|
||||
|
||||
if (!LucideIcon) {
|
||||
console.warn(`Icon "${name}" not found in Icon component`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<LucideIcon
|
||||
size={size}
|
||||
color={resolveColor(color)}
|
||||
strokeWidth={strokeWidth}
|
||||
fill={fill}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
Icon.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
size: PropTypes.number,
|
||||
color: PropTypes.string,
|
||||
strokeWidth: PropTypes.number,
|
||||
fill: PropTypes.string,
|
||||
};
|
||||
|
||||
export default Icon;
|
||||
@@ -1,8 +1,7 @@
|
||||
import PropTypes from "prop-types";
|
||||
import { FormControlLabel, Checkbox as MuiCheckbox } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import CheckboxOutline from "../../../../assets/icons/checkbox-outline.svg?react";
|
||||
import CheckboxFilled from "../../../../assets/icons/checkbox-filled.svg?react";
|
||||
import Icon from "../../Icon";
|
||||
|
||||
/**
|
||||
* Checkbox Component
|
||||
@@ -65,8 +64,8 @@ const Checkbox = ({
|
||||
name={name}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
icon={<CheckboxOutline />}
|
||||
checkedIcon={<CheckboxFilled />}
|
||||
icon={<Icon name="Square" />}
|
||||
checkedIcon={<Icon name="CheckSquare" />}
|
||||
inputProps={{
|
||||
"aria-label": "controlled checkbox",
|
||||
id: id,
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
// Components
|
||||
import { Box, Stack, Typography } from "@mui/material";
|
||||
import CloudUploadIcon from "@mui/icons-material/CloudUpload";
|
||||
import Icon from "../../Icon";
|
||||
import Image from "../../Image/index.jsx";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import ProgressUpload from "../../ProgressBars/index.jsx";
|
||||
import ImageIcon from "@mui/icons-material/Image";
|
||||
|
||||
// Utils
|
||||
import PropTypes from "prop-types";
|
||||
@@ -176,7 +175,10 @@ const ImageUpload = ({
|
||||
boxShadow: theme.shape.boxShadow,
|
||||
}}
|
||||
>
|
||||
<CloudUploadIcon />
|
||||
<Icon
|
||||
name="Upload"
|
||||
size={24}
|
||||
/>
|
||||
</IconButton>
|
||||
<Typography
|
||||
component="h2"
|
||||
@@ -203,7 +205,12 @@ const ImageUpload = ({
|
||||
</Box>
|
||||
{(localError || progress.isLoading || progress.value !== 0) && (
|
||||
<ProgressUpload
|
||||
icon={<ImageIcon />}
|
||||
icon={
|
||||
<Icon
|
||||
name="Image"
|
||||
size={20}
|
||||
/>
|
||||
}
|
||||
label={file?.name || "Upload failed"}
|
||||
size={file?.size}
|
||||
progress={progress.value}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import PropTypes from "prop-types";
|
||||
import { FormControlLabel, Radio as MUIRadio, Typography } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import RadioChecked from "../../../../assets/icons/radio-checked.svg?react";
|
||||
import Icon from "../../Icon";
|
||||
import "./index.css";
|
||||
|
||||
/**
|
||||
@@ -47,7 +47,12 @@ const Radio = ({
|
||||
<MUIRadio
|
||||
id={id}
|
||||
size={size}
|
||||
checkedIcon={<RadioChecked />}
|
||||
checkedIcon={
|
||||
<Icon
|
||||
name="CircleDot"
|
||||
size={16}
|
||||
/>
|
||||
}
|
||||
sx={{
|
||||
color: "transparent",
|
||||
width: 16,
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
Checkbox,
|
||||
} from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import SearchIcon from "../../../../assets/icons/search.svg?react";
|
||||
import Icon from "../../Icon";
|
||||
import React, { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import FieldWrapper from "../FieldWrapper/index.jsx";
|
||||
@@ -31,20 +31,13 @@ import FieldWrapper from "../FieldWrapper/index.jsx";
|
||||
const SearchAdornment = () => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Box
|
||||
height={16}
|
||||
sx={{
|
||||
"& svg": {
|
||||
width: 16,
|
||||
height: 16,
|
||||
"& path": {
|
||||
stroke: theme.palette.primary.contrastTextTertiary,
|
||||
strokeWidth: 1.2,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<SearchIcon />
|
||||
<Box height={16}>
|
||||
<Icon
|
||||
name="Search"
|
||||
size={16}
|
||||
color="primary.contrastTextTertiary"
|
||||
strokeWidth={1.2}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import PropTypes from "prop-types";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { MenuItem, Select as MuiSelect, Stack, Typography } from "@mui/material";
|
||||
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
|
||||
import Icon from "../../Icon";
|
||||
import FieldWrapper from "../FieldWrapper/index.jsx";
|
||||
|
||||
import "./index.css";
|
||||
@@ -98,7 +98,13 @@ const Select = ({
|
||||
error={error}
|
||||
name={name}
|
||||
inputProps={{ id: id }}
|
||||
IconComponent={KeyboardArrowDownIcon}
|
||||
IconComponent={(props) => (
|
||||
<Icon
|
||||
name="ChevronDown"
|
||||
size={20}
|
||||
{...props}
|
||||
/>
|
||||
)}
|
||||
MenuProps={{ disableScrollLock: true }}
|
||||
sx={{
|
||||
fontSize: 13,
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { Stack, Typography, InputAdornment, IconButton } from "@mui/material";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import PropTypes from "prop-types";
|
||||
import VisibilityOff from "@mui/icons-material/VisibilityOff";
|
||||
import Visibility from "@mui/icons-material/Visibility";
|
||||
import ReorderRoundedIcon from "@mui/icons-material/ReorderRounded";
|
||||
import DeleteIcon from "../../../../../assets/icons/trash-bin.svg?react";
|
||||
import Icon from "../../../Icon";
|
||||
|
||||
export const HttpAdornment = ({ https }) => {
|
||||
const theme = useTheme();
|
||||
@@ -56,7 +53,17 @@ export const PasswordEndAdornment = ({ fieldType, setFieldType }) => {
|
||||
},
|
||||
}}
|
||||
>
|
||||
{fieldType === "password" ? <VisibilityOff /> : <Visibility />}
|
||||
{fieldType === "password" ? (
|
||||
<Icon
|
||||
name="EyeOff"
|
||||
size={20}
|
||||
/>
|
||||
) : (
|
||||
<Icon
|
||||
name="Eye"
|
||||
size={20}
|
||||
/>
|
||||
)}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
);
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
min-height: 100vh;
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
overflow-x: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* TODO go for this approach for responsiveness. The aside needs to be taken care of */
|
||||
@@ -30,4 +32,6 @@
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from "react";
|
||||
import Button from "@mui/material/Button";
|
||||
import ButtonGroup from "@mui/material/ButtonGroup";
|
||||
import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
|
||||
import Icon from "../Icon";
|
||||
import ClickAwayListener from "@mui/material/ClickAwayListener";
|
||||
import Grow from "@mui/material/Grow";
|
||||
import Paper from "@mui/material/Paper";
|
||||
@@ -70,7 +70,10 @@ const MonitorActions = ({ isLoading }) => {
|
||||
aria-haspopup="menu"
|
||||
onClick={handleToggle}
|
||||
>
|
||||
<ArrowDropDownIcon />
|
||||
<Icon
|
||||
name="ChevronDown"
|
||||
size={20}
|
||||
/>
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<Popper
|
||||
|
||||
@@ -3,10 +3,7 @@ import Status from "./status.jsx";
|
||||
import Skeleton from "./skeleton.jsx";
|
||||
import Button from "@mui/material/Button";
|
||||
import { Tooltip } from "@mui/material";
|
||||
import SettingsOutlinedIcon from "@mui/icons-material/SettingsOutlined";
|
||||
import PauseOutlinedIcon from "@mui/icons-material/PauseOutlined";
|
||||
import PlayArrowOutlinedIcon from "@mui/icons-material/PlayArrowOutlined";
|
||||
import EmailIcon from "@mui/icons-material/Email";
|
||||
import Icon from "../Icon";
|
||||
|
||||
// Utils
|
||||
import PropTypes from "prop-types";
|
||||
@@ -75,7 +72,12 @@ const MonitorDetailsControlHeader = ({
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
loading={isSending}
|
||||
startIcon={<EmailIcon />}
|
||||
startIcon={
|
||||
<Icon
|
||||
name="Mail"
|
||||
size={18}
|
||||
/>
|
||||
}
|
||||
disabled={isTestNotificationsDisabled}
|
||||
onClick={() => {
|
||||
testAllNotifications({ monitorId: monitor?.id });
|
||||
@@ -103,7 +105,17 @@ const MonitorDetailsControlHeader = ({
|
||||
color="secondary"
|
||||
loading={isPausing}
|
||||
startIcon={
|
||||
monitor?.isActive ? <PauseOutlinedIcon /> : <PlayArrowOutlinedIcon />
|
||||
monitor?.isActive ? (
|
||||
<Icon
|
||||
name="Pause"
|
||||
size={18}
|
||||
/>
|
||||
) : (
|
||||
<Icon
|
||||
name="Play"
|
||||
size={18}
|
||||
/>
|
||||
)
|
||||
}
|
||||
onClick={() => {
|
||||
pauseMonitor({
|
||||
@@ -119,7 +131,12 @@ const MonitorDetailsControlHeader = ({
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
startIcon={<SettingsOutlinedIcon />}
|
||||
startIcon={
|
||||
<Icon
|
||||
name="Settings"
|
||||
size={18}
|
||||
/>
|
||||
}
|
||||
onClick={() => navigate(`/${path}/configure/${monitor.id}`)}
|
||||
>
|
||||
Configure
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Divider from "@mui/material/Divider";
|
||||
import DeleteOutlineRoundedIcon from "@mui/icons-material/DeleteOutlineRounded";
|
||||
import Icon from "../Icon";
|
||||
import Search from "../Inputs/Search/index.jsx";
|
||||
|
||||
// Utils
|
||||
@@ -102,11 +102,13 @@ const NotificationConfig = ({
|
||||
>
|
||||
{notification.notificationName}
|
||||
</Typography>
|
||||
<DeleteOutlineRoundedIcon
|
||||
<Icon
|
||||
name="Trash2"
|
||||
size={20}
|
||||
onClick={() => {
|
||||
handleDelete(notification._id);
|
||||
}}
|
||||
sx={{ cursor: "pointer" }}
|
||||
style={{ cursor: "pointer" }}
|
||||
/>
|
||||
{index < selectedNotifications.length - 1 && <Divider />}
|
||||
</Stack>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { useTheme } from "@emotion/react";
|
||||
import PropTypes from "prop-types";
|
||||
import { Box, IconButton, LinearProgress, Stack, Typography } from "@mui/material";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import ErrorOutlineOutlinedIcon from "@mui/icons-material/ErrorOutlineOutlined";
|
||||
import Icon from "../Icon";
|
||||
import "./index.css";
|
||||
|
||||
/**
|
||||
@@ -51,7 +50,10 @@ const ProgressUpload = ({ icon, label, size, progress = 0, onClick, error }) =>
|
||||
alignItems={error ? "center" : "flex-start"}
|
||||
>
|
||||
{error ? (
|
||||
<ErrorOutlineOutlinedIcon />
|
||||
<Icon
|
||||
name="AlertTriangle"
|
||||
size={20}
|
||||
/>
|
||||
) : icon ? (
|
||||
<Box
|
||||
sx={{
|
||||
@@ -127,10 +129,9 @@ const ProgressUpload = ({ icon, label, size, progress = 0, onClick, error }) =>
|
||||
}
|
||||
}
|
||||
>
|
||||
<CloseIcon
|
||||
sx={{
|
||||
fontSize: "20px",
|
||||
}}
|
||||
<Icon
|
||||
name="X"
|
||||
size={20}
|
||||
/>
|
||||
</IconButton>
|
||||
</Stack>
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
import React from "react";
|
||||
import { Step, Stepper, StepLabel, Typography } from "@mui/material";
|
||||
import RadioButtonCheckedIcon from "@mui/icons-material/RadioButtonChecked";
|
||||
import CheckCircle from "@mui/icons-material/CheckCircle";
|
||||
import Icon from "../Icon";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const CustomStepIcon = (props) => {
|
||||
const { completed, active } = props;
|
||||
return completed ? (
|
||||
<CheckCircle color="accent" />
|
||||
<Icon
|
||||
name="CheckCircle2"
|
||||
size={24}
|
||||
color="accent.main"
|
||||
/>
|
||||
) : (
|
||||
<RadioButtonCheckedIcon color={active ? "accent" : "disabled"} />
|
||||
<Icon
|
||||
name="Circle"
|
||||
size={24}
|
||||
color={active ? "accent.main" : "text.disabled"}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import html2canvas from "html2canvas";
|
||||
import ShareIcon from "@mui/icons-material/Share";
|
||||
import Icon from "../Icon";
|
||||
import { Button } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
|
||||
@@ -55,7 +55,13 @@ const ShareComponent = ({ elementToCapture, fileName = "screenshot" }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="outlined"
|
||||
startIcon={<ShareIcon sx={{ color: theme.palette.success.main }} />}
|
||||
startIcon={
|
||||
<Icon
|
||||
name="Share2"
|
||||
size={18}
|
||||
color="success.main"
|
||||
/>
|
||||
}
|
||||
color="success"
|
||||
onClick={captureAndShare}
|
||||
>
|
||||
|
||||
@@ -7,8 +7,7 @@ import Avatar from "../../Avatar/index.jsx";
|
||||
import Menu from "@mui/material/Menu";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import Divider from "@mui/material/Divider";
|
||||
import DotsVertical from "../../../../assets/icons/dots-vertical.svg?react";
|
||||
import LogoutSvg from "../../../../assets/icons/logout.svg?react";
|
||||
import Icon from "../../Icon";
|
||||
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useSelector } from "react-redux";
|
||||
@@ -81,6 +80,10 @@ const AuthFooter = ({ collapsed, accountMenuItems }) => {
|
||||
gap: theme.spacing(2),
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
pl: theme.spacing(4),
|
||||
color: theme.palette.primary.contrastTextTertiary,
|
||||
"& svg": {
|
||||
stroke: theme.palette.primary.contrastTextTertiary,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{item.icon}
|
||||
@@ -160,19 +163,14 @@ const AuthFooter = ({ collapsed, accountMenuItems }) => {
|
||||
ml: "50px",
|
||||
"&:focus": { outline: "none" },
|
||||
alignSelf: "center",
|
||||
|
||||
"& svg": {
|
||||
width: "22px",
|
||||
height: "22px",
|
||||
},
|
||||
"& svg path": {
|
||||
/* Vertical three dots */
|
||||
stroke: theme.palette.primary.contrastTextTertiary,
|
||||
},
|
||||
}}
|
||||
onClick={(event) => openPopup(event)}
|
||||
>
|
||||
<DotsVertical />
|
||||
<Icon
|
||||
name="MoreVertical"
|
||||
size={22}
|
||||
color="primary.contrastTextTertiary"
|
||||
/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
@@ -256,12 +254,13 @@ const AuthFooter = ({ collapsed, accountMenuItems }) => {
|
||||
gap: theme.spacing(4),
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
pl: theme.spacing(4),
|
||||
"& svg path": {
|
||||
stroke: theme.palette.primary.contrastTextTertiary,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<LogoutSvg />
|
||||
<Icon
|
||||
name="LogOut"
|
||||
size={20}
|
||||
color="primary.contrastTextTertiary"
|
||||
/>
|
||||
{t("menu.logOut", "Log out")}
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
|
||||
@@ -52,12 +52,11 @@ const NavItem = ({ item, collapsed, selected, onClick }) => {
|
||||
<ListItemIcon
|
||||
sx={{
|
||||
minWidth: 0,
|
||||
color: iconStroke,
|
||||
"& svg": {
|
||||
height: 20,
|
||||
width: 20,
|
||||
opacity: 0.81,
|
||||
},
|
||||
"& svg path": {
|
||||
stroke: iconStroke,
|
||||
},
|
||||
}}
|
||||
|
||||
@@ -8,22 +8,7 @@ import NavItem from "./components/navItem.jsx";
|
||||
import AuthFooter from "./components/authFooter.jsx";
|
||||
|
||||
import StarPrompt from "../StarPrompt/index.jsx";
|
||||
import LockSvg from "../../../assets/icons/lock.svg?react";
|
||||
import UserSvg from "../../../assets/icons/user.svg?react";
|
||||
import TeamSvg from "../../../assets/icons/user-two.svg?react";
|
||||
import Support from "../../../assets/icons/support.svg?react";
|
||||
import Maintenance from "../../../assets/icons/maintenance.svg?react";
|
||||
import Monitors from "../../../assets/icons/monitors.svg?react";
|
||||
import Incidents from "../../../assets/icons/incidents.svg?react";
|
||||
import Integrations from "../../../assets/icons/integrations.svg?react";
|
||||
import PageSpeed from "../../../assets/icons/page-speed.svg?react";
|
||||
import Settings from "../../../assets/icons/settings.svg?react";
|
||||
import ChangeLog from "../../../assets/icons/changeLog.svg?react";
|
||||
import Docs from "../../../assets/icons/docs.svg?react";
|
||||
import StatusPages from "../../../assets/icons/status-pages.svg?react";
|
||||
import Discussions from "../../../assets/icons/discussions.svg?react";
|
||||
import Notifications from "../../../assets/icons/notifications.svg?react";
|
||||
import Logs from "../../../assets/icons/logs.svg?react";
|
||||
import Icon from "../Icon";
|
||||
|
||||
// Utils
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
@@ -39,44 +24,41 @@ const URL_MAP = {
|
||||
};
|
||||
|
||||
const getMenu = (t) => [
|
||||
{ name: t("menu.uptime"), path: "uptime", icon: <Monitors /> },
|
||||
{ name: t("menu.pagespeed"), path: "pagespeed", icon: <PageSpeed /> },
|
||||
|
||||
{ name: t("menu.infrastructure"), path: "infrastructure", icon: <Integrations /> },
|
||||
{ name: t("menu.uptime"), path: "uptime", icon: <Icon name="Globe" /> },
|
||||
{ name: t("menu.pagespeed"), path: "pagespeed", icon: <Icon name="Gauge" /> },
|
||||
{ name: t("menu.infrastructure"), path: "infrastructure", icon: <Icon name="Link" /> },
|
||||
{
|
||||
name: t("menu.notifications"),
|
||||
path: "notifications",
|
||||
icon: <Notifications />,
|
||||
icon: <Icon name="Bell" />,
|
||||
},
|
||||
{ name: t("menu.checks"), path: "checks", icon: <Docs /> },
|
||||
{ name: t("menu.incidents"), path: "incidents", icon: <Incidents /> },
|
||||
|
||||
{ name: t("menu.statusPages"), path: "status", icon: <StatusPages /> },
|
||||
{ name: t("menu.maintenance"), path: "maintenance", icon: <Maintenance /> },
|
||||
{ name: t("menu.logs"), path: "logs", icon: <Logs /> },
|
||||
|
||||
{ name: t("menu.checks"), path: "checks", icon: <Icon name="FileText" /> },
|
||||
{ name: t("menu.incidents"), path: "incidents", icon: <Icon name="AlertTriangle" /> },
|
||||
{ name: t("menu.statusPages"), path: "status", icon: <Icon name="Wifi" /> },
|
||||
{ name: t("menu.maintenance"), path: "maintenance", icon: <Icon name="Wrench" /> },
|
||||
{ name: t("menu.logs"), path: "logs", icon: <Icon name="Database" /> },
|
||||
{
|
||||
name: t("menu.settings"),
|
||||
icon: <Settings />,
|
||||
icon: <Icon name="Settings" />,
|
||||
path: "settings",
|
||||
},
|
||||
];
|
||||
|
||||
const getOtherMenuItems = (t) => [
|
||||
{ name: t("menu.support"), path: "support", icon: <Support /> },
|
||||
{ name: t("menu.support"), path: "support", icon: <Icon name="HelpCircle" /> },
|
||||
{
|
||||
name: t("menu.discussions"),
|
||||
path: "discussions",
|
||||
icon: <Discussions />,
|
||||
icon: <Icon name="MessageCircle" />,
|
||||
},
|
||||
{ name: t("menu.docs"), path: "docs", icon: <Docs /> },
|
||||
{ name: t("menu.changelog"), path: "changelog", icon: <ChangeLog /> },
|
||||
{ name: t("menu.docs"), path: "docs", icon: <Icon name="FileText" /> },
|
||||
{ name: t("menu.changelog"), path: "changelog", icon: <Icon name="Code" /> },
|
||||
];
|
||||
|
||||
const getAccountMenuItems = (t) => [
|
||||
{ name: t("menu.profile"), path: "account/profile", icon: <UserSvg /> },
|
||||
{ name: t("menu.password"), path: "account/password", icon: <LockSvg /> },
|
||||
{ name: t("menu.team"), path: "account/team", icon: <TeamSvg /> },
|
||||
{ name: t("menu.profile"), path: "account/profile", icon: <Icon name="User" /> },
|
||||
{ name: t("menu.password"), path: "account/password", icon: <Icon name="Lock" /> },
|
||||
{ name: t("menu.team"), path: "account/team", icon: <Icon name="Users" /> },
|
||||
];
|
||||
|
||||
const Sidebar = () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { Typography, IconButton, Stack, Box } from "@mui/material";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import Icon from "../Icon";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -67,7 +67,10 @@ const StarPrompt = ({ repoUrl = "https://github.com/bluewave-labs/checkmate" })
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CloseIcon sx={{ fontSize: "1.25rem" }} />
|
||||
<Icon
|
||||
name="X"
|
||||
size={20}
|
||||
/>
|
||||
</IconButton>
|
||||
</Stack>
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import PropTypes from "prop-types";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { Stack, TablePagination, Typography } from "@mui/material";
|
||||
import { TablePaginationActions } from "./Actions/index.jsx";
|
||||
import SelectorVertical from "../../../../assets/icons/selector-vertical.svg?react";
|
||||
import Icon from "../../Icon";
|
||||
|
||||
Pagination.propTypes = {
|
||||
paginationLabel: PropTypes.string, // Label for the pagination.
|
||||
@@ -100,7 +100,12 @@ function Pagination({
|
||||
},
|
||||
},
|
||||
inputProps: { id: "pagination-dropdown" },
|
||||
IconComponent: SelectorVertical,
|
||||
IconComponent: () => (
|
||||
<Icon
|
||||
name="ChevronsUpDown"
|
||||
size={16}
|
||||
/>
|
||||
),
|
||||
sx: {
|
||||
ml: theme.spacing(4),
|
||||
mr: theme.spacing(12),
|
||||
|
||||
@@ -3,19 +3,31 @@ import IconButton from "@mui/material/IconButton";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Button from "@mui/material/Button";
|
||||
import ToastBody from "./body.jsx";
|
||||
import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
|
||||
import ErrorOutlineOutlinedIcon from "@mui/icons-material/ErrorOutlineOutlined";
|
||||
import WarningAmberOutlinedIcon from "@mui/icons-material/WarningAmberOutlined";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import Icon from "../Icon";
|
||||
|
||||
// Utils
|
||||
import { useTheme } from "@emotion/react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const icons = {
|
||||
info: <InfoOutlinedIcon />,
|
||||
error: <ErrorOutlineOutlinedIcon />,
|
||||
warning: <WarningAmberOutlinedIcon />,
|
||||
info: (
|
||||
<Icon
|
||||
name="Info"
|
||||
size={24}
|
||||
/>
|
||||
),
|
||||
error: (
|
||||
<Icon
|
||||
name="AlertCircle"
|
||||
size={24}
|
||||
/>
|
||||
),
|
||||
warning: (
|
||||
<Icon
|
||||
name="AlertTriangle"
|
||||
size={24}
|
||||
/>
|
||||
),
|
||||
};
|
||||
|
||||
const Toast = ({ variant, title, body, onClick, hasDismiss, hasIcon }) => {
|
||||
@@ -50,7 +62,10 @@ const Toast = ({ variant, title, body, onClick, hasDismiss, hasIcon }) => {
|
||||
)}
|
||||
{title && (
|
||||
<IconButton onClick={onClick}>
|
||||
<CloseIcon />
|
||||
<Icon
|
||||
name="X"
|
||||
size={20}
|
||||
/>
|
||||
</IconButton>
|
||||
)}
|
||||
</Stack>
|
||||
@@ -63,7 +78,10 @@ const Toast = ({ variant, title, body, onClick, hasDismiss, hasIcon }) => {
|
||||
<ToastBody body={body} />
|
||||
{!title && (
|
||||
<IconButton onClick={onClick}>
|
||||
<CloseIcon />
|
||||
<Icon
|
||||
name="X"
|
||||
size={20}
|
||||
/>
|
||||
</IconButton>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
@@ -3,7 +3,7 @@ import Button from "@mui/material/Button";
|
||||
import Menu from "@mui/material/Menu";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
|
||||
import Icon from "@/Components/v1/Icon";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Proptypes from "prop-types";
|
||||
|
||||
@@ -24,7 +24,13 @@ const AddMemberMenu = ({ handleInviteOpen, handleIsRegisterOpen }) => {
|
||||
<Button
|
||||
variant="contained"
|
||||
color="accent"
|
||||
endIcon={<ArrowDropDownIcon sx={{ color: theme.palette.secondary.light }} />}
|
||||
endIcon={
|
||||
<Icon
|
||||
name="ChevronDown"
|
||||
size={20}
|
||||
color="secondary.light"
|
||||
/>
|
||||
}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{t("teamPanel.addTeamMember.addMemberMenu")}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Typography from "@mui/material/Typography";
|
||||
import DataTable from "@/Components/v1/Table/index.jsx";
|
||||
import DeleteOutlineRoundedIcon from "@mui/icons-material/DeleteOutlineRounded";
|
||||
import Icon from "@/Components/v1/Icon";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ROLES } from "../../../../Utils/roleUtils.js";
|
||||
@@ -21,11 +21,13 @@ const RoleTable = ({ roles, handleDeleteRole }) => {
|
||||
render: (row) => {
|
||||
if (row === ROLES.SUPERADMIN) return null;
|
||||
return (
|
||||
<DeleteOutlineRoundedIcon
|
||||
<Icon
|
||||
name="Trash2"
|
||||
size={20}
|
||||
onClick={() => {
|
||||
handleDeleteRole(row);
|
||||
}}
|
||||
sx={{ cursor: "pointer" }}
|
||||
style={{ cursor: "pointer" }}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -7,9 +7,9 @@ import { createToast } from "../../Utils/toastUtils.jsx";
|
||||
import { forgotPassword } from "../../Features/Auth/authSlice.js";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import Background from "@/assets/Images/background-grid.svg?react";
|
||||
import EmailIcon from "@/assets/icons/email.svg?react";
|
||||
import Logo from "@/assets/icons/checkmate-icon.svg?react";
|
||||
import IconBox from "@/Components/v1/IconBox/index.jsx";
|
||||
import Icon from "@/Components/v1/Icon";
|
||||
import "./index.css";
|
||||
|
||||
const CheckEmail = () => {
|
||||
@@ -160,7 +160,7 @@ const CheckEmail = () => {
|
||||
svgHeight={24}
|
||||
mb={theme.spacing(4)}
|
||||
>
|
||||
<EmailIcon alt={t("auth.forgotPassword.imageAlts.email")} />
|
||||
<Icon name="Mail" />
|
||||
</IconBox>
|
||||
</Stack>
|
||||
<Typography component="h1">{t("auth.forgotPassword.heading")}</Typography>
|
||||
|
||||
@@ -8,9 +8,9 @@ import { newOrChangedCredentials } from "../../Validation/validation.js";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx";
|
||||
import Logo from "@/assets/icons/checkmate-icon.svg?react";
|
||||
import Key from "@/assets/icons/key.svg?react";
|
||||
import Background from "@/assets/Images/background-grid.svg?react";
|
||||
import IconBox from "@/Components/v1/IconBox/index.jsx";
|
||||
import Icon from "@/Components/v1/Icon";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import "./index.css";
|
||||
|
||||
@@ -165,7 +165,7 @@ const ForgotPassword = () => {
|
||||
svgHeight={24}
|
||||
mb={theme.spacing(4)}
|
||||
>
|
||||
<Key alt={t("auth.forgotPassword.imageAlts.passwordKey")} />
|
||||
<Icon name="Key" />
|
||||
</IconBox>
|
||||
</Stack>
|
||||
<Typography component="h1">{t("auth.forgotPassword.heading")}</Typography>
|
||||
|
||||
@@ -4,9 +4,9 @@ import { useNavigate } from "react-router-dom";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { clearAuthState } from "../../Features/Auth/authSlice.js";
|
||||
import Background from "@/assets/Images/background-grid.svg?react";
|
||||
import ConfirmIcon from "@/assets/icons/check-outlined.svg?react";
|
||||
import Logo from "@/assets/icons/checkmate-icon.svg?react";
|
||||
import IconBox from "@/Components/v1/IconBox/index.jsx";
|
||||
import Icon from "@/Components/v1/Icon";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import "./index.css";
|
||||
|
||||
@@ -94,7 +94,7 @@ const NewPasswordConfirmed = () => {
|
||||
svgHeight={24}
|
||||
mb={theme.spacing(4)}
|
||||
>
|
||||
<ConfirmIcon alt={t("auth.forgotPassword.imageAlts.passwordConfirm")} />
|
||||
<Icon name="CheckCircle" />
|
||||
</IconBox>
|
||||
</Stack>
|
||||
<Typography component="h1">{t("auth.forgotPassword.heading")}</Typography>
|
||||
|
||||
@@ -11,9 +11,9 @@ import Check from "@/Components/v1/Check/Check.jsx";
|
||||
import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx";
|
||||
import { PasswordEndAdornment } from "@/Components/v1/Inputs/TextInput/Adornments/index.jsx";
|
||||
import IconBox from "@/Components/v1/IconBox/index.jsx";
|
||||
import LockIcon from "@/assets/icons/lock.svg?react";
|
||||
import Logo from "@/assets/icons/checkmate-icon.svg?react";
|
||||
import Background from "@/assets/Images/background-grid.svg?react";
|
||||
import Icon from "@/Components/v1/Icon";
|
||||
import "./index.css";
|
||||
import { useValidatePassword } from "./hooks/useValidatePassword.jsx";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
@@ -140,7 +140,7 @@ const SetNewPassword = () => {
|
||||
svgHeight={24}
|
||||
mb={theme.spacing(4)}
|
||||
>
|
||||
<LockIcon alt={t("auth.forgotPassword.imageAlts.lock")} />
|
||||
<Icon name="Lock" />
|
||||
</IconBox>
|
||||
</Stack>
|
||||
<Typography component="h1">{t("auth.forgotPassword.heading")}</Typography>
|
||||
|
||||
@@ -2,10 +2,7 @@ import PropTypes from "prop-types";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { Box, Stack, Typography } from "@mui/material";
|
||||
import Background from "@/assets/Images/background-grid.svg?react";
|
||||
import AlertIcon from "@/assets/icons/alert-icon.svg?react";
|
||||
import CheckIcon from "@/assets/icons/check-icon.svg?react";
|
||||
import CloseIcon from "@/assets/icons/close-icon.svg?react";
|
||||
import WarningIcon from "@/assets/icons/warning-icon.svg?react";
|
||||
import Icon from "@/Components/v1/Icon";
|
||||
|
||||
const StatusBox = ({ title, value, status }) => {
|
||||
const theme = useTheme();
|
||||
@@ -26,28 +23,40 @@ const StatusBox = ({ title, value, status }) => {
|
||||
color = theme.palette.success.lowContrast;
|
||||
icon = (
|
||||
<Box sx={{ ...sharedStyles, top: theme.spacing(6), right: theme.spacing(6) }}>
|
||||
<CheckIcon />
|
||||
<Icon
|
||||
name="CheckCircle"
|
||||
size={20}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
} else if (status === "down") {
|
||||
color = theme.palette.error.lowContrast;
|
||||
icon = (
|
||||
<Box sx={{ ...sharedStyles, top: theme.spacing(6), right: theme.spacing(6) }}>
|
||||
<CloseIcon />
|
||||
<Icon
|
||||
name="X"
|
||||
size={20}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
} else if (status === "paused") {
|
||||
color = theme.palette.warning.lowContrast;
|
||||
icon = (
|
||||
<Box sx={{ ...sharedStyles, top: theme.spacing(6), right: theme.spacing(6) }}>
|
||||
<WarningIcon />
|
||||
<Icon
|
||||
name="AlertTriangle"
|
||||
size={20}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
} else {
|
||||
color = theme.palette.accent.main;
|
||||
icon = (
|
||||
<Box sx={{ ...sharedStyles, top: theme.spacing(6), right: theme.spacing(6) }}>
|
||||
<AlertIcon />
|
||||
<Icon
|
||||
name="Bell"
|
||||
size={20}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,8 +3,7 @@ import { Box, Stack, Typography } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import PanelSkeleton from "../IncidentsSummaryPanel/skeleton.jsx";
|
||||
import Incidents from "@/assets/icons/incidents.svg?react";
|
||||
import CheckIcon from "@/assets/icons/check-icon.svg?react";
|
||||
import Icon from "@/Components/v1/Icon";
|
||||
import SummaryCard from "../SummaryCard/index.jsx";
|
||||
|
||||
/**
|
||||
@@ -43,39 +42,39 @@ const ActiveIncidentsPanel = ({ totalCount = 0, isLoading = false, error = null
|
||||
|
||||
if (!totalCount || totalCount === 0) {
|
||||
return (
|
||||
<SummaryCard>
|
||||
<SummaryCard title={t("incidentsPage.incidentsActivePanelTitle")}>
|
||||
<Stack
|
||||
direction="column"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
padding={theme.spacing(10)}
|
||||
gap={theme.spacing(4)}
|
||||
padding={theme.spacing(6)}
|
||||
gap={theme.spacing(2)}
|
||||
sx={{ flex: 1 }}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
color: theme.palette.success.main,
|
||||
"& svg": {
|
||||
width: 60,
|
||||
height: 60,
|
||||
|
||||
width: 24,
|
||||
height: 24,
|
||||
"& path": { stroke: "currentColor", strokeWidth: 2 },
|
||||
},
|
||||
mb: theme.spacing(2),
|
||||
mb: theme.spacing(1),
|
||||
}}
|
||||
>
|
||||
<CheckIcon />
|
||||
<Icon
|
||||
name="CheckCircle"
|
||||
size={60}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Typography
|
||||
variant="h1"
|
||||
color={theme.palette.primary.contrastTextSecondary}
|
||||
sx={{
|
||||
fontSize: 13,
|
||||
textTransform: "uppercase",
|
||||
fontWeight: 600,
|
||||
fontWeight: 500,
|
||||
textAlign: "center",
|
||||
color: theme.palette.success.lowContrast,
|
||||
letterSpacing: theme.spacing(0.4),
|
||||
}}
|
||||
>
|
||||
{t("incidentsPage.allSystemsAreOperational")}
|
||||
@@ -86,50 +85,40 @@ const ActiveIncidentsPanel = ({ totalCount = 0, isLoading = false, error = null
|
||||
}
|
||||
|
||||
return (
|
||||
<SummaryCard isHighPriority={true}>
|
||||
<SummaryCard title={t("incidentsPage.incidentsActivePanelTitle")}>
|
||||
<Stack
|
||||
direction="column"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
spacing={theme.spacing(4)}
|
||||
gap={theme.spacing(2)}
|
||||
sx={{ flex: 1 }}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
color: theme.palette.error.lowContrast,
|
||||
padding: theme.spacing(2),
|
||||
"& svg": {
|
||||
width: 60,
|
||||
height: 60,
|
||||
|
||||
width: 24,
|
||||
height: 24,
|
||||
"& path": { stroke: "currentColor", strokeWidth: 2 },
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Incidents />
|
||||
<Icon
|
||||
name="AlertTriangle"
|
||||
size={60}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Typography
|
||||
variant="h1"
|
||||
sx={{
|
||||
fontSize: `calc(${theme.typography.h1.fontSize} * 2.5)`,
|
||||
fontWeight: 700,
|
||||
fontSize: 32,
|
||||
fontWeight: 600,
|
||||
color: theme.palette.error.lowContrast,
|
||||
lineHeight: 1,
|
||||
}}
|
||||
>
|
||||
{totalCount}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="h2"
|
||||
sx={{
|
||||
textTransform: "uppercase",
|
||||
fontWeight: 700,
|
||||
letterSpacing: theme.spacing(0.4),
|
||||
paddingTop: theme.spacing(3),
|
||||
}}
|
||||
>
|
||||
{t("incidentsPage.incidentsActivePanelTitle")}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</SummaryCard>
|
||||
);
|
||||
|
||||
@@ -53,7 +53,7 @@ const IncidentsSummaryPanel = ({ updateTrigger }) => {
|
||||
<>
|
||||
<Grid
|
||||
container
|
||||
spacing={3}
|
||||
spacing={"16px"}
|
||||
>
|
||||
<Grid
|
||||
item
|
||||
|
||||
@@ -3,8 +3,7 @@ import { Box, Stack, Typography } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { StatusLabel } from "@/Components/v1/Label/index.jsx";
|
||||
import { getHumanReadableDuration } from "@/Utils/timeUtils.js";
|
||||
import Monitors from "@/assets/icons/monitors.svg?react";
|
||||
import AverageResponseIcon from "@/assets/icons/status-pages.svg?react";
|
||||
import Icon from "@/Components/v1/Icon";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const IncidentItem = ({ incident }) => {
|
||||
@@ -37,101 +36,68 @@ const IncidentItem = ({ incident }) => {
|
||||
|
||||
const duration = calculateDuration();
|
||||
const iconWrapperStyle = {
|
||||
px: theme.spacing(2),
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
mx: theme.spacing(2),
|
||||
color: theme.palette.primary.contrastTextTertiary,
|
||||
"& svg": {
|
||||
width: 18,
|
||||
height: 18,
|
||||
},
|
||||
"& svg path": {
|
||||
stroke: "currentColor",
|
||||
strokeWidth: 1.5,
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
display: "grid",
|
||||
gridTemplateColumns: "1fr 100px 80px",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing(2),
|
||||
width: "100%",
|
||||
py: theme.spacing(0.5),
|
||||
"&:hover": { opacity: 0.8 },
|
||||
}}
|
||||
>
|
||||
<Stack
|
||||
direction="column"
|
||||
gap={theme.spacing(1.5)}
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(2)}
|
||||
>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(1)}
|
||||
<Box sx={iconWrapperStyle}>
|
||||
<Icon
|
||||
name="Globe"
|
||||
size={18}
|
||||
/>
|
||||
</Box>
|
||||
<Typography
|
||||
variant="body1"
|
||||
fontWeight={500}
|
||||
noWrap
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
...iconWrapperStyle,
|
||||
}}
|
||||
>
|
||||
<Monitors />
|
||||
</Box>
|
||||
|
||||
<Stack
|
||||
direction="row"
|
||||
gap={theme.spacing(4)}
|
||||
alignItems="baseline"
|
||||
>
|
||||
<Typography variant="body1">
|
||||
{t("incidentsPage.incidentItemMonitor")}:
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
fontWeight={600}
|
||||
>
|
||||
{incident.monitorName || t("incidentsPage.unknownMonitor")}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
width="100%"
|
||||
marginTop={theme.spacing(1)}
|
||||
>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(3)}
|
||||
>
|
||||
<Box sx={{ ...iconWrapperStyle }}>
|
||||
<AverageResponseIcon />
|
||||
</Box>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
spacing={2}
|
||||
>
|
||||
<Typography variant="body1">
|
||||
{t("incidentsPage.incidentItemStatus")}:
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
||||
<StatusLabel
|
||||
status={isActive ? "down" : "up"}
|
||||
text={isActive ? t("incidentsPage.active") : t("incidentsPage.resolved")}
|
||||
customStyles={{
|
||||
textTransform: "capitalize",
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
<Box sx={{ flexGrow: 1 }} />
|
||||
|
||||
<Typography
|
||||
variant="body1"
|
||||
fontWeight={500}
|
||||
>
|
||||
{duration}
|
||||
</Typography>
|
||||
</Stack>
|
||||
{incident.monitorName || t("incidentsPage.unknownMonitor")}
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
||||
<Box sx={{ display: "flex", justifyContent: "center" }}>
|
||||
<StatusLabel
|
||||
status={isActive ? "down" : "up"}
|
||||
text={isActive ? t("incidentsPage.active") : t("incidentsPage.resolved")}
|
||||
customStyles={{
|
||||
textTransform: "capitalize",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Typography
|
||||
variant="body1"
|
||||
fontWeight={500}
|
||||
textAlign="right"
|
||||
>
|
||||
{duration}
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next";
|
||||
import PanelSkeleton from "../IncidentsSummaryPanel/skeleton.jsx";
|
||||
import IncidentItem from "./IncidentItem.jsx";
|
||||
import SummaryCard from "../SummaryCard/index.jsx";
|
||||
import Icon from "@/Components/v1/Icon";
|
||||
|
||||
/**
|
||||
* LatestIncidentsPanel Component
|
||||
@@ -51,21 +52,35 @@ const LatestIncidentsPanel = ({ incidents = [], isLoading = false, error = null
|
||||
return (
|
||||
<SummaryCard title={t("incidentsPage.incidentsLatestPanelTitle")}>
|
||||
{!incidents || incidents.length === 0 ? (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
flexGrow: 1,
|
||||
}}
|
||||
<Stack
|
||||
direction="column"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
padding={theme.spacing(6)}
|
||||
gap={theme.spacing(2)}
|
||||
sx={{ flex: 1 }}
|
||||
>
|
||||
<Box sx={{ mb: theme.spacing(1) }}>
|
||||
<Icon
|
||||
name="Check"
|
||||
size={24}
|
||||
color={theme.palette.success.main}
|
||||
strokeWidth={2}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Typography
|
||||
variant="body2"
|
||||
textAlign="center"
|
||||
sx={{
|
||||
fontSize: 13,
|
||||
textTransform: "uppercase",
|
||||
fontWeight: 500,
|
||||
textAlign: "center",
|
||||
color: theme.palette.success.lowContrast,
|
||||
}}
|
||||
>
|
||||
{t("incidentsPage.incidentsLatestPanelEmpty")}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
) : (
|
||||
<Stack gap={theme.spacing(4)}>
|
||||
{incidents.map((incident, index) => (
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import PropTypes from "prop-types";
|
||||
import { Box, Stack, Typography } from "@mui/material";
|
||||
import { Box, Stack, Typography, Divider } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import PanelSkeleton from "../IncidentsSummaryPanel/skeleton.jsx";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Divider } from "@mui/material";
|
||||
import Clock from "@/assets/icons/maintenance.svg?react";
|
||||
import Incidents from "@/assets/icons/incidents.svg?react";
|
||||
import ResolutionItem from "@/assets/icons/interval-check.svg?react";
|
||||
import NotificationIcon from "@/assets/icons/notifications.svg?react";
|
||||
import Icon from "@/Components/v1/Icon";
|
||||
import SummaryCard from "../SummaryCard/index.jsx";
|
||||
|
||||
/**
|
||||
@@ -30,23 +26,36 @@ import SummaryCard from "../SummaryCard/index.jsx";
|
||||
* @param {boolean} props.isLoading - Loading state
|
||||
* @param {Object} props.error - Error object if any
|
||||
*/
|
||||
const formatResolutionTime = (hours) => {
|
||||
if (!hours || hours === 0) return "0m";
|
||||
|
||||
const totalMinutes = Math.round(hours * 60);
|
||||
const h = Math.floor(totalMinutes / 60);
|
||||
const m = totalMinutes % 60;
|
||||
|
||||
if (h === 0) return `${m}m`;
|
||||
if (m === 0) return `${h}h`;
|
||||
return `${h}h ${m}m`;
|
||||
};
|
||||
|
||||
const StatisticsPanel = ({ isLoading = false, error = null, summary = {} }) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const totalResolutions =
|
||||
(summary?.totalManualResolutions || 0) + (summary?.totalAutomaticResolutions || 0);
|
||||
const automaticPercentage =
|
||||
totalResolutions > 0
|
||||
? (summary?.totalAutomaticResolutions / totalResolutions) * 100
|
||||
: 0;
|
||||
|
||||
const iconWrapperStyle = {
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
mx: theme.spacing(3),
|
||||
mx: theme.spacing(2),
|
||||
color: theme.palette.primary.contrastTextTertiary,
|
||||
"& svg": {
|
||||
width: 18,
|
||||
height: 18,
|
||||
},
|
||||
"& svg path": {
|
||||
stroke: "currentColor",
|
||||
strokeWidth: 1.5,
|
||||
},
|
||||
};
|
||||
if (isLoading) {
|
||||
@@ -76,150 +85,87 @@ const StatisticsPanel = ({ isLoading = false, error = null, summary = {} }) => {
|
||||
);
|
||||
}
|
||||
|
||||
const getMostAffectedMonitor = () => {
|
||||
if (!summary.total || summary.total === 0) {
|
||||
return t("incidentsPage.none");
|
||||
}
|
||||
return summary.topMonitor?.monitorName || t("incidentsPage.none");
|
||||
};
|
||||
|
||||
const rowStyle = {
|
||||
py: theme.spacing(0.5),
|
||||
minHeight: 32,
|
||||
};
|
||||
|
||||
return (
|
||||
<SummaryCard title={t("incidentsPage.incidentsStatisticsPanelTitle")}>
|
||||
<Stack gap={theme.spacing(4)}>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(3)}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
...iconWrapperStyle,
|
||||
}}
|
||||
>
|
||||
<NotificationIcon />
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{
|
||||
fontWeight: 500,
|
||||
lineHeight: 1.2,
|
||||
}}
|
||||
>
|
||||
{t("incidentsPage.totalIncidents")} : {summary.total || 0}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Divider />
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(2)}
|
||||
>
|
||||
<Box sx={iconWrapperStyle}>
|
||||
<Incidents />
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{
|
||||
fontWeight: 500,
|
||||
lineHeight: 1.2,
|
||||
}}
|
||||
>
|
||||
{t("incidentsPage.mostAffectedMonitor")} :{" "}
|
||||
{summary.topMonitor?.monitorName || t("incidentsPage.unknownMonitor")}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Divider />
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(2)}
|
||||
>
|
||||
<Box sx={iconWrapperStyle}>
|
||||
<Clock />
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
{t("incidentsPage.avgResolutionTime")} :{" "}
|
||||
{summary.avgResolutionTimeHours || 0} {t("incidentsPage.hours")}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Divider />
|
||||
|
||||
<Box padding={theme.spacing(2)}>
|
||||
<Box>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(2)}
|
||||
mb={theme.spacing(1.5)}
|
||||
sx={rowStyle}
|
||||
>
|
||||
<Box sx={iconWrapperStyle}>
|
||||
<ResolutionItem />
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{
|
||||
fontWeight: 500,
|
||||
lineHeight: 1.2,
|
||||
}}
|
||||
>
|
||||
{t("incidentsPage.resolutions")}: {totalResolutions}
|
||||
</Typography>
|
||||
<Icon
|
||||
name="Bell"
|
||||
size={18}
|
||||
/>
|
||||
</Box>
|
||||
<Typography
|
||||
variant="body1"
|
||||
fontWeight={500}
|
||||
>
|
||||
{t("incidentsPage.totalIncidents")}: {summary.total || 0}
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
height: 8,
|
||||
borderRadius: 4,
|
||||
overflow: "hidden",
|
||||
bgcolor: theme.palette.accent.main,
|
||||
width: "100%",
|
||||
marginTop: theme.spacing(4),
|
||||
}}
|
||||
<Divider sx={{ mt: theme.spacing(2) }} />
|
||||
</Box>
|
||||
<Box>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(2)}
|
||||
sx={rowStyle}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: `${automaticPercentage}%`,
|
||||
|
||||
bgcolor: theme.palette.warningSecondary.lowContrast,
|
||||
height: "100%",
|
||||
}}
|
||||
<Box sx={iconWrapperStyle}>
|
||||
<Icon
|
||||
name="AlertTriangle"
|
||||
size={18}
|
||||
/>
|
||||
</Box>
|
||||
<Typography
|
||||
variant="body1"
|
||||
fontWeight={500}
|
||||
>
|
||||
{t("incidentsPage.mostAffectedMonitor")}: {getMostAffectedMonitor()}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Divider sx={{ mt: theme.spacing(2) }} />
|
||||
</Box>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(2)}
|
||||
sx={rowStyle}
|
||||
>
|
||||
<Box sx={iconWrapperStyle}>
|
||||
<Icon
|
||||
name="Wrench"
|
||||
size={18}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
paddingTop: theme.spacing(4),
|
||||
width: "100%",
|
||||
}}
|
||||
<Typography
|
||||
variant="body1"
|
||||
fontWeight={500}
|
||||
>
|
||||
{summary.totalAutomaticResolutions > 0 && (
|
||||
<Typography
|
||||
variant="body2"
|
||||
fontWeight={500}
|
||||
color={theme.palette.warningSecondary.contrastText}
|
||||
>
|
||||
{t("incidentsPage.automatic")} ({summary.totalAutomaticResolutions})
|
||||
</Typography>
|
||||
)}
|
||||
{summary.totalManualResolutions > 0 && (
|
||||
<Typography
|
||||
variant="body2"
|
||||
fontWeight={500}
|
||||
color={theme.palette.accent.main}
|
||||
>
|
||||
{t("incidentsPage.manual")} ({summary.totalManualResolutions})
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
{t("incidentsPage.avgResolutionTime")}:{" "}
|
||||
{summary.total > 0
|
||||
? formatResolutionTime(summary.avgResolutionTimeHours)
|
||||
: "N/A"}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</SummaryCard>
|
||||
);
|
||||
|
||||
@@ -8,17 +8,14 @@ const SummaryCard = ({ children, isHighPriority = false, sx = {}, title = null }
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
padding: theme.spacing(4),
|
||||
borderRadius: 3,
|
||||
padding: "16px",
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
border: isHighPriority
|
||||
? `${theme.spacing(1.5)} solid ${theme.palette.error.lowContrast}`
|
||||
: `${theme.spacing(1)} solid ${theme.palette.divider}`,
|
||||
boxShadow: theme.palette.tertiary.cardShadow,
|
||||
border: `1px solid ${theme.palette.primary.lowContrast}`,
|
||||
color: theme.palette.primary.contrastTextTertiary,
|
||||
fontSize: theme.typography.body1.fontSize,
|
||||
...sx,
|
||||
@@ -33,13 +30,12 @@ const SummaryCard = ({ children, isHighPriority = false, sx = {}, title = null }
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="h6"
|
||||
component="h2"
|
||||
sx={{
|
||||
textTransform: "uppercase",
|
||||
fontWeight: 700,
|
||||
fontSize: theme.typography.h2.fontSize,
|
||||
|
||||
letterSpacing: theme.spacing(0.5),
|
||||
fontWeight: 500,
|
||||
fontSize: 13,
|
||||
color: theme.palette.primary.contrastTextSecondary,
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
@@ -49,9 +45,9 @@ const SummaryCard = ({ children, isHighPriority = false, sx = {}, title = null }
|
||||
</Box>
|
||||
)}
|
||||
<Stack
|
||||
mt={theme.spacing(4)}
|
||||
paddingTop={theme.spacing(5)}
|
||||
gap={theme.spacing(4)}
|
||||
mt={theme.spacing(2)}
|
||||
paddingTop={theme.spacing(2)}
|
||||
gap={theme.spacing(2)}
|
||||
sx={{ height: "100%" }}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -8,7 +8,7 @@ import IncidentsSummaryPanel from "./Components/IncidentsSummaryPanel/index.jsx"
|
||||
|
||||
//Utils
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import NetworkError from "@/Components/v1/GenericFallback/NetworkError.jsx";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
@@ -53,16 +53,33 @@ const Incidents2 = () => {
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
useEffect(() => {
|
||||
setPage(0);
|
||||
}, [selectedMonitor, filter, dateRange]);
|
||||
// Track previous filter values to detect changes and reset page
|
||||
const prevFiltersRef = useRef({ selectedMonitor, filter, dateRange });
|
||||
|
||||
useEffect(() => {
|
||||
const prevFilters = prevFiltersRef.current;
|
||||
const filtersChanged =
|
||||
prevFilters.selectedMonitor !== selectedMonitor ||
|
||||
prevFilters.filter !== filter ||
|
||||
prevFilters.dateRange !== dateRange;
|
||||
|
||||
// Determine the effective page - reset to 0 if filters changed
|
||||
const effectivePage = filtersChanged ? 0 : page;
|
||||
|
||||
// Update the ref for next comparison
|
||||
if (filtersChanged) {
|
||||
prevFiltersRef.current = { selectedMonitor, filter, dateRange };
|
||||
// Also update the page state (for pagination component)
|
||||
if (page !== 0) {
|
||||
setPage(0);
|
||||
}
|
||||
}
|
||||
|
||||
const config = {
|
||||
monitorId: selectedMonitor !== "0" ? selectedMonitor : undefined,
|
||||
sortOrder: "desc",
|
||||
dateRange,
|
||||
page,
|
||||
page: effectivePage,
|
||||
rowsPerPage,
|
||||
};
|
||||
|
||||
@@ -87,8 +104,8 @@ const Incidents2 = () => {
|
||||
|
||||
useEffect(() => {
|
||||
const lookup = monitors?.reduce((acc, monitor) => {
|
||||
acc[monitor._id] = {
|
||||
_id: monitor._id,
|
||||
acc[monitor.id] = {
|
||||
_id: monitor.id,
|
||||
name: monitor.name,
|
||||
type: monitor.type,
|
||||
};
|
||||
|
||||
@@ -2,8 +2,7 @@ import { useState } from "react";
|
||||
import { Box, Button } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import PauseCircleOutlineIcon from "@mui/icons-material/PauseCircleOutline";
|
||||
import PlayCircleOutlineRoundedIcon from "@mui/icons-material/PlayCircleOutlineRounded";
|
||||
import Icon from "@/Components/v1/Icon";
|
||||
import Dialog from "@/Components/v1/Dialog/index.jsx";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
@@ -36,12 +35,18 @@ const MonitorActionButtons = ({ monitor, isBusy, handlePause, handleRemove }) =>
|
||||
>
|
||||
{monitor?.isActive ? (
|
||||
<>
|
||||
<PauseCircleOutlineIcon />
|
||||
<Icon
|
||||
name="PauseCircle"
|
||||
size={20}
|
||||
/>
|
||||
{t("pause")}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<PlayCircleOutlineRoundedIcon />
|
||||
<Icon
|
||||
name="PlayCircle"
|
||||
size={20}
|
||||
/>
|
||||
{t("resume")}
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -61,7 +61,7 @@ const useInfrastructureSubmit = () => {
|
||||
};
|
||||
|
||||
const finalForm = {
|
||||
...(isCreate ? {} : { _id: monitorId }),
|
||||
...(isCreate ? {} : { id: monitorId }),
|
||||
...rest,
|
||||
description: form.name,
|
||||
type: "hardware",
|
||||
|
||||
@@ -22,9 +22,9 @@ import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
useDeleteMonitor,
|
||||
useFetchGlobalSettings,
|
||||
useFetchHardwareMonitorById,
|
||||
useFetchMonitorById,
|
||||
usePauseMonitor,
|
||||
} from "../../../Hooks/monitorHooks.js";
|
||||
} from "@/Hooks/monitorHooks.js";
|
||||
import useInfrastructureMonitorForm from "./hooks/useInfrastructureMonitorForm.jsx";
|
||||
import useValidateInfrastructureForm from "./hooks/useValidateInfrastructureForm.jsx";
|
||||
import useInfrastructureSubmit from "./hooks/useInfrastructureSubmit.jsx";
|
||||
@@ -37,14 +37,16 @@ const CreateInfrastructureMonitor = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
// State
|
||||
const [monitor, setMonitor] = useState(null);
|
||||
const [https, setHttps] = useState(false);
|
||||
const [updateTrigger, setUpdateTrigger] = useState(false);
|
||||
const [availableDisks, setAvailableDisks] = useState([]);
|
||||
|
||||
// Fetch monitor details if editing
|
||||
const [monitor, isLoading] = useFetchHardwareMonitorById({
|
||||
const [isLoading] = useFetchMonitorById({
|
||||
monitorId,
|
||||
updateTrigger,
|
||||
setMonitor,
|
||||
updateTrigger: true,
|
||||
});
|
||||
const [deleteMonitor, isDeleting] = useDeleteMonitor();
|
||||
const [globalSettings, globalSettingsLoading] = useFetchGlobalSettings();
|
||||
@@ -150,8 +152,7 @@ const CreateInfrastructureMonitor = () => {
|
||||
|
||||
const handleRemove = async (event) => {
|
||||
event.preventDefault();
|
||||
const TEMP_MONITOR = { id: monitor._id };
|
||||
await deleteMonitor({ monitor: TEMP_MONITOR, redirect: "/infrastructure" });
|
||||
await deleteMonitor({ monitor, redirect: "/infrastructure" });
|
||||
};
|
||||
|
||||
const isBusy =
|
||||
|
||||
@@ -3,7 +3,7 @@ import PropTypes from "prop-types";
|
||||
import FilterHeader from "@/Components/v1/FilterHeader/index.jsx";
|
||||
import { useMemo } from "react";
|
||||
import { Box, Button } from "@mui/material";
|
||||
import ClearIcon from "@mui/icons-material/Clear";
|
||||
import Icon from "@/Components/v1/Icon";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
/**
|
||||
@@ -68,7 +68,12 @@ const Filter = ({
|
||||
color={theme.palette.primary.contrastText}
|
||||
onClick={handleReset}
|
||||
variant="contained"
|
||||
endIcon={<ClearIcon />}
|
||||
endIcon={
|
||||
<Icon
|
||||
name="X"
|
||||
size={18}
|
||||
/>
|
||||
}
|
||||
sx={{
|
||||
visibility: isFilterActive ? "visible" : "hidden",
|
||||
}}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Stack } from "@mui/material";
|
||||
import { InfrastructureMenu } from "../MonitorsTableMenu/index.jsx";
|
||||
import LoadingSpinner from "../../../../Uptime/Monitors/Components/LoadingSpinner/index.jsx";
|
||||
// Assets
|
||||
import CPUChipIcon from "@/assets/icons/cpu-chip.svg?react";
|
||||
import Icon from "@/Components/v1/Icon";
|
||||
import CustomGauge from "@/Components/v1/Charts/CustomGauge/index.jsx";
|
||||
|
||||
// Utils
|
||||
@@ -67,9 +67,9 @@ const MonitorsTable = ({
|
||||
alignItems={"center"}
|
||||
gap=".25rem"
|
||||
>
|
||||
<CPUChipIcon
|
||||
width={20}
|
||||
height={20}
|
||||
<Icon
|
||||
name="Cpu"
|
||||
size={20}
|
||||
/>
|
||||
{row.processor}
|
||||
</Stack>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useTheme } from "@emotion/react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { createToast } from "@/Utils/toastUtils.jsx";
|
||||
import { IconButton, Menu, MenuItem } from "@mui/material";
|
||||
import Settings from "@/assets/icons/settings-bold.svg?react";
|
||||
import Icon from "@/Components/v1/Icon";
|
||||
import PropTypes from "prop-types";
|
||||
import Dialog from "@/Components/v1/Dialog/index.jsx";
|
||||
import { networkService } from "@/Utils/NetworkService.js";
|
||||
@@ -100,7 +100,10 @@ const InfrastructureMenu = ({ monitor, isAdmin, updateCallback }) => {
|
||||
}}
|
||||
ref={anchor}
|
||||
>
|
||||
<Settings />
|
||||
<Icon
|
||||
name="Settings"
|
||||
size={20}
|
||||
/>
|
||||
</IconButton>
|
||||
|
||||
<Menu
|
||||
|
||||
@@ -12,13 +12,16 @@ import { Box } from "@mui/material";
|
||||
const BaseContainer = ({children}) => {
|
||||
const theme = useTheme()
|
||||
return(
|
||||
<Box
|
||||
<Box
|
||||
sx={{
|
||||
padding: theme.spacing(3),
|
||||
borderRadius: theme.spacing(2),
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
minWidth: 250,
|
||||
width: "fit-content",
|
||||
borderRadius: 4,
|
||||
border: `1px solid ${theme.palette.primary.lowContrast}`,
|
||||
minWidth: 200,
|
||||
width: `calc(25% - (3 * ${theme.spacing(8)} / 4))`,
|
||||
[theme.breakpoints.down("md")]: {
|
||||
width: `calc(50% - (1 * ${theme.spacing(8)} / 2))`,
|
||||
},
|
||||
}}>
|
||||
{children}
|
||||
</Box>
|
||||
@@ -99,7 +102,7 @@ const Gauges = ({ diagnostics, isLoading }) => {
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
spacing={theme.spacing(8)}
|
||||
gap={theme.spacing(8)}
|
||||
flexWrap="wrap"
|
||||
>
|
||||
<InfrastructureStyleGauge
|
||||
|
||||
@@ -2,14 +2,17 @@ import Stack from "@mui/material/Stack";
|
||||
import Box from "@mui/material/Box";
|
||||
import Select from "@/Components/v1/Inputs/Select/index.jsx";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Divider from "@mui/material/Divider";
|
||||
import DataTable from "@/Components/v1/Table/index.jsx";
|
||||
import Pagination from "@/Components/v1/Table/TablePagination/index.jsx";
|
||||
|
||||
import { useFetchLogs } from "../../../Hooks/logHooks.js";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const formatLog = (theme, log, idx) => {
|
||||
const LevelBadge = ({ level }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const levelColors = {
|
||||
info: theme.palette.success.main,
|
||||
warn: theme.palette.warning.main,
|
||||
@@ -17,31 +20,38 @@ const formatLog = (theme, log, idx) => {
|
||||
debug: theme.palette.accent.main,
|
||||
};
|
||||
|
||||
const color = levelColors[log.level] || theme.palette.primary.contrastText;
|
||||
const color = levelColors[level] || theme.palette.primary.contrastText;
|
||||
|
||||
return (
|
||||
<span key={idx}>
|
||||
<span>[{log.timestamp}]</span>{" "}
|
||||
<span style={{ color, fontWeight: "bold" }}>{log.level.toUpperCase()}</span>
|
||||
{": "}
|
||||
{`(${log.service})`}
|
||||
{`(${log.method})`}
|
||||
{": "}
|
||||
{log.message}
|
||||
<br />
|
||||
</span>
|
||||
<Box
|
||||
component="span"
|
||||
sx={{
|
||||
color: color,
|
||||
fontWeight: 600,
|
||||
textTransform: "uppercase",
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
{level}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const Logs = () => {
|
||||
// Local state
|
||||
const [logLevel, setLogLevel] = useState("all");
|
||||
const formatTimestamp = (timestamp) => {
|
||||
if (!timestamp) return "-";
|
||||
const date = new Date(timestamp);
|
||||
return date.toLocaleString();
|
||||
};
|
||||
|
||||
const Logs = () => {
|
||||
const [logLevel, setLogLevel] = useState("all");
|
||||
const [page, setPage] = useState(0);
|
||||
const [rowsPerPage, setRowsPerPage] = useState(15);
|
||||
|
||||
// Hooks
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const [logs, isLoading, error] = useFetchLogs();
|
||||
// Setup
|
||||
|
||||
const LOG_LEVELS = [
|
||||
{ _id: "all", name: t("logsPage.logLevelSelect.values.all") },
|
||||
{ _id: "info", name: t("logsPage.logLevelSelect.values.info") },
|
||||
@@ -49,52 +59,116 @@ const Logs = () => {
|
||||
{ _id: "error", name: t("logsPage.logLevelSelect.values.error") },
|
||||
{ _id: "debug", name: t("logsPage.logLevelSelect.values.debug") },
|
||||
];
|
||||
|
||||
const headers = [
|
||||
{
|
||||
id: "timestamp",
|
||||
content: t("logsPage.table.timestamp"),
|
||||
render: (row) => (
|
||||
<Typography sx={{ fontSize: 13, fontFamily: "monospace" }}>
|
||||
{formatTimestamp(row.timestamp)}
|
||||
</Typography>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "level",
|
||||
content: t("logsPage.table.level"),
|
||||
render: (row) => <LevelBadge level={row.level} />,
|
||||
},
|
||||
{
|
||||
id: "service",
|
||||
content: t("logsPage.table.service"),
|
||||
render: (row) => (
|
||||
<Typography sx={{ fontSize: 13 }}>{row.service || "-"}</Typography>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "method",
|
||||
content: t("logsPage.table.method"),
|
||||
render: (row) => (
|
||||
<Typography sx={{ fontSize: 13, fontFamily: "monospace" }}>{row.method || "-"}</Typography>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "message",
|
||||
content: t("logsPage.table.message"),
|
||||
render: (row) => (
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: 13,
|
||||
maxWidth: 400,
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
{row.message || "-"}
|
||||
</Typography>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const filteredLogs = logs
|
||||
?.filter((log) => {
|
||||
if (logLevel === "all") return true;
|
||||
return log.level === logLevel;
|
||||
})
|
||||
.reverse()
|
||||
.map((log, idx) => ({ ...log, id: idx }));
|
||||
|
||||
const paginatedLogs = filteredLogs?.slice(
|
||||
page * rowsPerPage,
|
||||
page * rowsPerPage + rowsPerPage
|
||||
);
|
||||
|
||||
const handleChangePage = (event, newPage) => {
|
||||
setPage(newPage);
|
||||
};
|
||||
|
||||
const handleChangeRowsPerPage = (event) => {
|
||||
setRowsPerPage(parseInt(event.target.value, 10));
|
||||
setPage(0);
|
||||
};
|
||||
|
||||
const handleLogLevelChange = (e) => {
|
||||
setLogLevel(e.target.value);
|
||||
setPage(0);
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack gap={theme.spacing(4)}>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(4)}
|
||||
sx={{
|
||||
position: "sticky",
|
||||
top: theme.spacing(17),
|
||||
backdropFilter: "blur(10px)",
|
||||
paddingTop: theme.spacing(4),
|
||||
paddingLeft: theme.spacing(6),
|
||||
}}
|
||||
>
|
||||
<Typography>{t("logsPage.logLevelSelect.title")}</Typography>
|
||||
<Select
|
||||
items={LOG_LEVELS}
|
||||
value={logLevel}
|
||||
onChange={(e) => {
|
||||
setLogLevel(e.target.value);
|
||||
}}
|
||||
onChange={handleLogLevelChange}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
<Box
|
||||
component="pre"
|
||||
sx={{
|
||||
fontFamily: "monospace",
|
||||
color: theme.palette.primary.contrastText,
|
||||
padding: 2,
|
||||
borderRadius: 1,
|
||||
overflowX: "auto",
|
||||
whiteSpace: "pre-wrap",
|
||||
wordWrap: "break-word",
|
||||
<DataTable
|
||||
shouldRender={!isLoading}
|
||||
headers={headers}
|
||||
data={paginatedLogs || []}
|
||||
config={{
|
||||
emptyView: t("logsPage.noLogs"),
|
||||
}}
|
||||
>
|
||||
<code>
|
||||
{logs
|
||||
?.filter((log) => {
|
||||
if (logLevel === "all") return true;
|
||||
return log.level === logLevel;
|
||||
})
|
||||
.reverse()
|
||||
.map((log, idx) => formatLog(theme, log, idx))}
|
||||
</code>
|
||||
</Box>
|
||||
/>
|
||||
|
||||
{filteredLogs?.length > 0 && (
|
||||
<Pagination
|
||||
paginationLabel={t("logsPage.table.logs")}
|
||||
itemCount={filteredLogs?.length || 0}
|
||||
page={page}
|
||||
rowsPerPage={rowsPerPage}
|
||||
handleChangePage={handleChangePage}
|
||||
handleChangeRowsPerPage={handleChangeRowsPerPage}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Components
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import Icon from "@/Components/v1/Icon";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
@@ -14,8 +14,10 @@ const MonitorListItem = ({ monitor, onDelete }) => {
|
||||
width="100%"
|
||||
>
|
||||
<Typography flexGrow={1}>{monitor.name}</Typography>
|
||||
<DeleteIcon
|
||||
sx={{ cursor: "pointer" }}
|
||||
<Icon
|
||||
name="Trash2"
|
||||
size={20}
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={() => onDelete(monitor)}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
@@ -16,7 +16,7 @@ import useMaintenanceActions from "./hooks/useMaintenanceActions.jsx";
|
||||
|
||||
import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx";
|
||||
import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx";
|
||||
import CalendarIcon from "@/assets/icons/calendar.svg?react";
|
||||
import Icon from "@/Components/v1/Icon";
|
||||
import "./index.css";
|
||||
import Search from "@/Components/v1/Inputs/Search/index.jsx";
|
||||
import { logger } from "@/Utils/Logger.js";
|
||||
@@ -217,7 +217,14 @@ const CreateMaintenance = () => {
|
||||
disablePast
|
||||
disableHighlightToday
|
||||
value={form.startDate}
|
||||
slots={{ openPickerIcon: () => <CalendarIcon /> }}
|
||||
slots={{
|
||||
openPickerIcon: () => (
|
||||
<Icon
|
||||
name="Calendar"
|
||||
size={20}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
slotProps={{
|
||||
switchViewButton: { sx: { display: "none" } },
|
||||
nextIconButton: { sx: { ml: theme.spacing(2) } },
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useNavigate } from "react-router-dom";
|
||||
import { useSelector } from "react-redux";
|
||||
import { IconButton, Menu, MenuItem } from "@mui/material";
|
||||
import { logger } from "@/Utils/Logger.js";
|
||||
import Settings from "@/assets/icons/settings-bold.svg?react";
|
||||
import Icon from "@/Components/v1/Icon";
|
||||
import PropTypes from "prop-types";
|
||||
import { networkService } from "../../../../main.jsx";
|
||||
import { createToast } from "@/Utils/toastUtils.jsx";
|
||||
@@ -98,7 +98,10 @@ const ActionsMenu = ({ /* isAdmin, */ maintenanceWindow, updateCallback }) => {
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Settings />
|
||||
<Icon
|
||||
name="Settings"
|
||||
size={20}
|
||||
/>
|
||||
</IconButton>
|
||||
|
||||
<Menu
|
||||
|
||||
@@ -2,8 +2,7 @@ import PropTypes from "prop-types";
|
||||
import { Box } from "@mui/material";
|
||||
import DataTable from "@/Components/v1/Table/index.jsx";
|
||||
import Pagination from "@/Components/v1/Table/TablePagination/index.jsx";
|
||||
import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded";
|
||||
import ArrowUpwardRoundedIcon from "@mui/icons-material/ArrowUpwardRounded";
|
||||
import Icon from "@/Components/v1/Icon";
|
||||
import ActionsMenu from "./ActionsMenu/index.jsx";
|
||||
import { memo } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
@@ -68,9 +67,15 @@ const MaintenanceTable = ({
|
||||
}}
|
||||
>
|
||||
{sort.order === "asc" ? (
|
||||
<ArrowUpwardRoundedIcon />
|
||||
<Icon
|
||||
name="ArrowUp"
|
||||
size={18}
|
||||
/>
|
||||
) : (
|
||||
<ArrowDownwardRoundedIcon />
|
||||
<Icon
|
||||
name="ArrowDown"
|
||||
size={18}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
</Box>
|
||||
@@ -89,9 +94,15 @@ const MaintenanceTable = ({
|
||||
}}
|
||||
>
|
||||
{sort.order === "asc" ? (
|
||||
<ArrowUpwardRoundedIcon />
|
||||
<Icon
|
||||
name="ArrowUp"
|
||||
size={18}
|
||||
/>
|
||||
) : (
|
||||
<ArrowDownwardRoundedIcon />
|
||||
<Icon
|
||||
name="ArrowDown"
|
||||
size={18}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
</Box>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Components
|
||||
import Menu from "@mui/material/Menu";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import SettingsOutlinedIcon from "@mui/icons-material/SettingsOutlined";
|
||||
import Icon from "@/Components/v1/Icon";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
|
||||
// Utils
|
||||
@@ -49,7 +49,10 @@ const ActionMenu = ({ notification, onDelete }) => {
|
||||
onClick={handleClick}
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
>
|
||||
<SettingsOutlinedIcon />
|
||||
<Icon
|
||||
name="Settings"
|
||||
size={20}
|
||||
/>
|
||||
</IconButton>
|
||||
|
||||
<Menu
|
||||
|
||||
@@ -3,10 +3,9 @@ import { Box, Stack, Tooltip, Typography, Button, ButtonGroup } from "@mui/mater
|
||||
import ConfigBox from "@/Components/v1/ConfigBox/index.jsx";
|
||||
import Select from "@/Components/v1/Inputs/Select/index.jsx";
|
||||
import TextInput from "@/Components/v1/Inputs/TextInput/index.jsx";
|
||||
import PauseCircleOutlineIcon from "@mui/icons-material/PauseCircleOutline";
|
||||
import Icon from "@/Components/v1/Icon";
|
||||
import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx";
|
||||
import PulseDot from "@/Components/v1/Animated/PulseDot.jsx";
|
||||
import PlayCircleOutlineRoundedIcon from "@mui/icons-material/PlayCircleOutlineRounded";
|
||||
import SkeletonLayout from "./skeleton.jsx";
|
||||
import NotificationsConfig from "@/Components/v1/NotificationConfig/index.jsx";
|
||||
import Dialog from "@/Components/v1/Dialog/index.jsx";
|
||||
@@ -178,8 +177,7 @@ const PageSpeedSetup = () => {
|
||||
|
||||
const handleRemove = async (event) => {
|
||||
event.preventDefault();
|
||||
const TEMP_MONITOR = { id: monitor._id };
|
||||
await deleteMonitor({ monitor: TEMP_MONITOR, redirect: "/pagespeed" });
|
||||
await deleteMonitor({ monitor: { id: monitorId }, redirect: "/pagespeed" });
|
||||
};
|
||||
|
||||
const isBusy = isLoading || isCreating || isDeleting || isUpdating || isPausing;
|
||||
@@ -314,12 +312,18 @@ const PageSpeedSetup = () => {
|
||||
>
|
||||
{monitor?.isActive ? (
|
||||
<>
|
||||
<PauseCircleOutlineIcon />
|
||||
<Icon
|
||||
name="PauseCircle"
|
||||
size={20}
|
||||
/>
|
||||
{t("pause")}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<PlayCircleOutlineRoundedIcon />
|
||||
<Icon
|
||||
name="PlayCircle"
|
||||
size={20}
|
||||
/>
|
||||
{t("resume")}
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Box, Typography, Divider } from "@mui/material";
|
||||
import Checkbox from "@/Components/v1/Inputs/Checkbox/index.jsx";
|
||||
import MetricsIcon from "@/assets/icons/ruler-icon.svg?react";
|
||||
import Icon from "@/Components/v1/Icon";
|
||||
import LegendBox from "@/Components/v1/Charts/LegendBox/index.jsx";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useTheme } from "@emotion/react";
|
||||
@@ -10,7 +10,12 @@ const AreaChartLegend = ({ metrics, handleMetrics }) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<LegendBox
|
||||
icon={<MetricsIcon />}
|
||||
icon={
|
||||
<Icon
|
||||
name="Ruler"
|
||||
size={20}
|
||||
/>
|
||||
}
|
||||
header="Metrics"
|
||||
>
|
||||
<Box>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Stack, Box, Typography } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import LegendBox from "@/Components/v1/Charts/LegendBox/index.jsx";
|
||||
import SpeedometerIcon from "@/assets/icons/speedometer-icon.svg?react";
|
||||
import Icon from "@/Components/v1/Icon";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const PieChartLegend = ({ audits }) => {
|
||||
@@ -9,7 +9,12 @@ const PieChartLegend = ({ audits }) => {
|
||||
|
||||
return (
|
||||
<LegendBox
|
||||
icon={<SpeedometerIcon />}
|
||||
icon={
|
||||
<Icon
|
||||
name="Gauge"
|
||||
size={20}
|
||||
/>
|
||||
}
|
||||
header="Performance metrics"
|
||||
sx={{ flex: 1 }}
|
||||
>
|
||||
|
||||
@@ -2,7 +2,7 @@ import ChartBox from "@/Components/v1/Charts/ChartBox/index.jsx";
|
||||
import AreaChart from "../Charts/AreaChart.jsx";
|
||||
import AreaChartLegend from "../Charts/AreaChartLegend.jsx";
|
||||
import SkeletonLayout from "./skeleton.jsx";
|
||||
import ScoreIcon from "@/assets/icons/monitor-graph-line.svg?react";
|
||||
import Icon from "@/Components/v1/Icon";
|
||||
import { Stack } from "@mui/material";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
@@ -24,7 +24,12 @@ const PageSpeedAreaChart = ({ shouldRender, monitor, metrics, handleMetrics }) =
|
||||
>
|
||||
<ChartBox
|
||||
justifyContent="flex-start"
|
||||
icon={<ScoreIcon />}
|
||||
icon={
|
||||
<Icon
|
||||
name="TrendingUp"
|
||||
size={20}
|
||||
/>
|
||||
}
|
||||
header="Score history"
|
||||
height="100%"
|
||||
borderRadiusRight={16}
|
||||
|
||||
@@ -4,9 +4,21 @@ import { Typography } from "@mui/material";
|
||||
import { getHumanReadableDuration } from "../../../../../Utils/timeUtils.js";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const PageSpeedStatusBoxes = ({ shouldRender, monitor }) => {
|
||||
const uptimeDuration = getHumanReadableDuration(monitor?.uptimeDuration);
|
||||
const time = getHumanReadableDuration(monitor?.lastChecked);
|
||||
const PageSpeedStatusBoxes = ({ shouldRender, monitorStats }) => {
|
||||
const lastChecked = monitorStats?.lastCheckTimestamp
|
||||
? Date.now() - monitorStats.lastCheckTimestamp
|
||||
: 0;
|
||||
|
||||
// Determine time since last failure
|
||||
const timeOfLastFailure = monitorStats?.timeOfLastFailure;
|
||||
const timeSinceLastFailure =
|
||||
timeOfLastFailure > 0
|
||||
? Date.now() - timeOfLastFailure
|
||||
: Date.now() - new Date(monitorStats?.createdAt);
|
||||
|
||||
const streakTime = getHumanReadableDuration(timeSinceLastFailure);
|
||||
|
||||
const time = getHumanReadableDuration(lastChecked);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -16,7 +28,7 @@ const PageSpeedStatusBoxes = ({ shouldRender, monitor }) => {
|
||||
heading="checks since"
|
||||
subHeading={
|
||||
<>
|
||||
{uptimeDuration}
|
||||
{streakTime}
|
||||
<Typography component="span">{t("ago")}</Typography>
|
||||
</>
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import ChartBox from "@/Components/v1/Charts/ChartBox/index.jsx";
|
||||
import PerformanceIcon from "@/assets/icons/performance-report.svg?react";
|
||||
import Icon from "@/Components/v1/Icon";
|
||||
import PieChart from "../Charts/PieChart.jsx";
|
||||
import { Typography } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
@@ -17,7 +17,12 @@ const PerformanceReport = ({ shouldRender, audits }) => {
|
||||
|
||||
return (
|
||||
<ChartBox
|
||||
icon={<PerformanceIcon />}
|
||||
icon={
|
||||
<Icon
|
||||
name="Layers"
|
||||
size={20}
|
||||
/>
|
||||
}
|
||||
header="Performance report"
|
||||
Legend={<PieChartLegend audits={audits} />}
|
||||
borderRadiusRight={16}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import PropTypes from "prop-types";
|
||||
// import PageSpeedIcon from "../../../../assets/icons/page-speed.svg?react";
|
||||
import PageSpeedIcon from "@/assets/icons/page-speed.svg?react";
|
||||
import Icon from "@/Components/v1/Icon";
|
||||
import { StatusLabel } from "@/Components/v1/Label/index.jsx";
|
||||
import { Box, Grid, Stack, Typography } from "@mui/material";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
@@ -241,7 +240,10 @@ const Card = ({ monitor }) => {
|
||||
}}
|
||||
>
|
||||
<IconBox>
|
||||
<PageSpeedIcon />
|
||||
<Icon
|
||||
name="Gauge"
|
||||
size={20}
|
||||
/>
|
||||
</IconBox>
|
||||
<Typography
|
||||
component="h2"
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Components
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
import ReorderRoundedIcon from "@mui/icons-material/ReorderRounded";
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import Icon from "@/Components/v1/Icon";
|
||||
|
||||
// Utils
|
||||
import { DragDropContext, Droppable, Draggable } from "@hello-pangea/dnd";
|
||||
@@ -28,10 +27,15 @@ const MonitorListItem = ({
|
||||
justifyContent={"start"}
|
||||
border={`1px solid ${theme.palette.primary.lowContrast}`}
|
||||
>
|
||||
<ReorderRoundedIcon />
|
||||
<Icon
|
||||
name="GripVertical"
|
||||
size={20}
|
||||
/>
|
||||
<Typography>{monitor.name}</Typography>
|
||||
<DeleteIcon
|
||||
sx={{ marginLeft: "auto" }}
|
||||
<Icon
|
||||
name="Trash2"
|
||||
size={20}
|
||||
style={{ marginLeft: "auto", cursor: "pointer" }}
|
||||
onClick={() => {
|
||||
onDelete(monitor);
|
||||
}}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Button, Box } from "@mui/material";
|
||||
import ProgressUpload from "@/Components/v1/ProgressBars/index.jsx";
|
||||
import ImageIcon from "@mui/icons-material/Image";
|
||||
import Icon from "@/Components/v1/Icon";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { formatBytes } from "../../../../../Utils/fileUtils.js";
|
||||
const Progress = ({ isLoading, progressValue, logo, logoType, removeLogo, errors }) => {
|
||||
@@ -8,7 +8,12 @@ const Progress = ({ isLoading, progressValue, logo, logoType, removeLogo, errors
|
||||
if (isLoading) {
|
||||
return (
|
||||
<ProgressUpload
|
||||
icon={<ImageIcon />}
|
||||
icon={
|
||||
<Icon
|
||||
name="Image"
|
||||
size={20}
|
||||
/>
|
||||
}
|
||||
label={logo?.name}
|
||||
size={formatBytes(logo?.size)}
|
||||
progress={progressValue}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
// Components
|
||||
import { Box, Stack, Typography, Button } from "@mui/material";
|
||||
import Image from "@/Components/v1/Image/index.jsx";
|
||||
import SettingsIcon from "@/assets/icons/settings-bold.svg?react";
|
||||
import Icon from "@/Components/v1/Icon";
|
||||
import ThemeSwitch from "@/Components/v1/ThemeSwitch/index.jsx";
|
||||
import ArrowOutwardIcon from "@mui/icons-material/ArrowOutward";
|
||||
//Utils
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
@@ -46,7 +45,12 @@ const Controls = ({ url, type }) => {
|
||||
},
|
||||
}}
|
||||
>
|
||||
<SettingsIcon /> {t("configure")}
|
||||
<Icon
|
||||
name="Settings"
|
||||
size={18}
|
||||
style={{ marginRight: "8px" }}
|
||||
/>{" "}
|
||||
{t("configure")}
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
@@ -111,7 +115,10 @@ const ControlsHeader = ({ statusPage, isPublic, url, type = "uptime" }) => {
|
||||
}}
|
||||
>
|
||||
<Typography>{t("publicLink")}</Typography>
|
||||
<ArrowOutwardIcon />
|
||||
<Icon
|
||||
name="ExternalLink"
|
||||
size={18}
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Components
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
import CheckCircleIcon from "@mui/icons-material/CheckCircle";
|
||||
import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline";
|
||||
import Icon from "@/Components/v1/Icon";
|
||||
|
||||
// Utils
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
@@ -10,8 +9,10 @@ import PropTypes from "prop-types";
|
||||
const getMonitorStatus = (monitors, theme) => {
|
||||
const monitorsStatus = {
|
||||
icon: (
|
||||
<ErrorOutlineIcon
|
||||
sx={{ color: theme.palette.primary.contrastTextSecondaryDarkBg }}
|
||||
<Icon
|
||||
name="AlertTriangle"
|
||||
size={24}
|
||||
color="primary.contrastTextSecondaryDarkBg"
|
||||
/>
|
||||
),
|
||||
};
|
||||
@@ -19,8 +20,10 @@ const getMonitorStatus = (monitors, theme) => {
|
||||
monitorsStatus.msg = "All systems operational";
|
||||
monitorsStatus.color = theme.palette.success.lowContrast;
|
||||
monitorsStatus.icon = (
|
||||
<CheckCircleIcon
|
||||
sx={{ color: theme.palette.primary.contrastTextSecondaryDarkBg }}
|
||||
<Icon
|
||||
name="CheckCircle2"
|
||||
size={24}
|
||||
color="primary.contrastTextSecondaryDarkBg"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import DataTable from "@/Components/v1/Table/index.jsx";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { StatusLabel } from "@/Components/v1/Label/index.jsx";
|
||||
import ArrowOutwardIcon from "@mui/icons-material/ArrowOutward";
|
||||
import Icon from "@/Components/v1/Icon";
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
import { useTranslation } from "react-i18next";
|
||||
const StatusPagesTable = ({ data }) => {
|
||||
@@ -48,7 +48,12 @@ const StatusPagesTable = ({ data }) => {
|
||||
}}
|
||||
>
|
||||
<Typography>{content}</Typography>
|
||||
{row.isPublished && <ArrowOutwardIcon />}
|
||||
{row.isPublished && (
|
||||
<Icon
|
||||
name="ExternalLink"
|
||||
size={18}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -28,10 +28,7 @@ import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { monitorValidation } from "../../../Validation/validation.js";
|
||||
import { createToast } from "../../../Utils/toastUtils.jsx";
|
||||
import {
|
||||
PauseOutlined as PauseOutlinedIcon,
|
||||
PlayArrowOutlined as PlayArrowOutlinedIcon,
|
||||
} from "@mui/icons-material";
|
||||
import Icon from "@/Components/v1/Icon";
|
||||
import { useMonitorUtils } from "../../../Hooks/useMonitorUtils.js";
|
||||
import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications.js";
|
||||
import { useParams } from "react-router-dom";
|
||||
@@ -429,7 +426,17 @@ const UptimeCreate = ({ isClone = false }) => {
|
||||
color="secondary"
|
||||
loading={isBusy}
|
||||
startIcon={
|
||||
monitor?.isActive ? <PauseOutlinedIcon /> : <PlayArrowOutlinedIcon />
|
||||
monitor?.isActive ? (
|
||||
<Icon
|
||||
name="Pause"
|
||||
size={18}
|
||||
/>
|
||||
) : (
|
||||
<Icon
|
||||
name="Play"
|
||||
size={18}
|
||||
/>
|
||||
)
|
||||
}
|
||||
onClick={handlePause}
|
||||
>
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
// Components
|
||||
import { Stack, Typography, Box } from "@mui/material";
|
||||
import ChartBox from "@/Components/v1/Charts/ChartBox/index.jsx";
|
||||
import UptimeIcon from "@/assets/icons/uptime-icon.svg?react";
|
||||
import IncidentsIcon from "@/assets/icons/incidents.svg?react";
|
||||
import AverageResponseIcon from "@/assets/icons/average-response-icon.svg?react";
|
||||
import Icon from "@/Components/v1/Icon";
|
||||
import UpBarChart from "../Charts/UpBarChart.jsx";
|
||||
import DownBarChart from "../Charts/DownBarChart.jsx";
|
||||
import ResponseGaugeChart from "../Charts/ResponseGaugeChart.jsx";
|
||||
@@ -38,7 +36,12 @@ const ChartBoxes = ({
|
||||
gap={theme.spacing(8)}
|
||||
>
|
||||
<ChartBox
|
||||
icon={<UptimeIcon />}
|
||||
icon={
|
||||
<Icon
|
||||
name="Activity"
|
||||
size={20}
|
||||
/>
|
||||
}
|
||||
header="Uptime"
|
||||
isEmpty={monitorData?.groupedUpChecks?.length === 0}
|
||||
>
|
||||
@@ -89,7 +92,12 @@ const ChartBoxes = ({
|
||||
/>
|
||||
</ChartBox>
|
||||
<ChartBox
|
||||
icon={<IncidentsIcon />}
|
||||
icon={
|
||||
<Icon
|
||||
name="AlertTriangle"
|
||||
size={20}
|
||||
/>
|
||||
}
|
||||
header="Incidents"
|
||||
noDataMessage={noIncidentsMessage}
|
||||
isEmpty={monitorData?.groupedDownChecks?.length === 0}
|
||||
@@ -123,7 +131,12 @@ const ChartBoxes = ({
|
||||
/>
|
||||
</ChartBox>
|
||||
<ChartBox
|
||||
icon={<AverageResponseIcon />}
|
||||
icon={
|
||||
<Icon
|
||||
name="BarChart3"
|
||||
size={20}
|
||||
/>
|
||||
}
|
||||
header="Average Response Time"
|
||||
>
|
||||
<ResponseGaugeChart avgResponseTime={monitorData?.groupedAvgResponseTime ?? 0} />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import ChartBox from "@/Components/v1/Charts/ChartBox/index.jsx";
|
||||
import MonitorDetailsAreaChart from "@/Components/v1/Charts/MonitorDetailsAreaChart/index.jsx";
|
||||
import ResponseTimeIcon from "@/assets/icons/response-time-icon.svg?react";
|
||||
import Icon from "@/Components/v1/Icon";
|
||||
import SkeletonLayout from "./ResponseTimeChartSkeleton.jsx";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
@@ -11,7 +11,12 @@ const ResponseTImeChart = ({ isLoading = false, groupedChecks = [], dateRange })
|
||||
|
||||
return (
|
||||
<ChartBox
|
||||
icon={<ResponseTimeIcon />}
|
||||
icon={
|
||||
<Icon
|
||||
name="TrendingUp"
|
||||
size={20}
|
||||
/>
|
||||
}
|
||||
header="Response Times"
|
||||
>
|
||||
<MonitorDetailsAreaChart
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import ChartBox from "@/Components/v1/Charts/ChartBox/index.jsx";
|
||||
import PropTypes from "prop-types";
|
||||
import HistoryIcon from "@/assets/icons/history-icon.svg?react";
|
||||
import Icon from "@/Components/v1/Icon";
|
||||
import Table from "@/Components/v1/Table/index.jsx";
|
||||
import TablePagination from "@/Components/v1/Table/TablePagination/index.jsx";
|
||||
import { StatusLabel } from "@/Components/v1/Label/index.jsx";
|
||||
@@ -184,7 +184,12 @@ const ResponseTable = ({
|
||||
|
||||
return (
|
||||
<ChartBox
|
||||
icon={<HistoryIcon />}
|
||||
icon={
|
||||
<Icon
|
||||
name="History"
|
||||
size={20}
|
||||
/>
|
||||
}
|
||||
header="Response Times"
|
||||
height="100%"
|
||||
>
|
||||
|
||||
@@ -3,7 +3,7 @@ import PropTypes from "prop-types";
|
||||
import FilterHeader from "@/Components/v1/FilterHeader/index.jsx";
|
||||
import { useMemo } from "react";
|
||||
import { Box, Button } from "@mui/material";
|
||||
import ClearIcon from "@mui/icons-material/Clear";
|
||||
import Icon from "@/Components/v1/Icon";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
/**
|
||||
@@ -131,7 +131,12 @@ const Filter = ({
|
||||
color={theme.palette.primary.contrastText}
|
||||
onClick={handleReset}
|
||||
variant="contained"
|
||||
endIcon={<ClearIcon />}
|
||||
endIcon={
|
||||
<Icon
|
||||
name="X"
|
||||
size={18}
|
||||
/>
|
||||
}
|
||||
sx={{
|
||||
visibility: isFilterActive ? "visible" : "hidden",
|
||||
}}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import PropTypes from "prop-types";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { Box, Stack, Typography } from "@mui/material";
|
||||
import Arrow from "@/assets/icons/top-right-arrow.svg?react";
|
||||
import Icon from "@/Components/v1/Icon";
|
||||
import Background from "@/assets/Images/background-grid.svg?react";
|
||||
import ClockSnooze from "@/assets/icons/clock-snooze.svg?react";
|
||||
|
||||
const StatusBox = ({ title, value, status }) => {
|
||||
const theme = useTheme();
|
||||
@@ -20,21 +19,30 @@ const StatusBox = ({ title, value, status }) => {
|
||||
color = theme.palette.success.lowContrast;
|
||||
icon = (
|
||||
<Box sx={{ ...sharedStyles, top: theme.spacing(4) }}>
|
||||
<Arrow />
|
||||
<Icon
|
||||
name="ArrowUpRight"
|
||||
size={20}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
} else if (status === "down") {
|
||||
color = theme.palette.error.lowContrast;
|
||||
icon = (
|
||||
<Box sx={{ ...sharedStyles, transform: "rotate(180deg)", top: theme.spacing(2) }}>
|
||||
<Arrow />
|
||||
<Icon
|
||||
name="ArrowUpRight"
|
||||
size={20}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
} else if (status === "paused") {
|
||||
color = theme.palette.warning.lowContrast;
|
||||
icon = (
|
||||
<Box sx={{ ...sharedStyles, top: theme.spacing(6), right: theme.spacing(6) }}>
|
||||
<ClockSnooze />
|
||||
<Icon
|
||||
name="Clock"
|
||||
size={20}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
// Components
|
||||
import { Box, Stack } from "@mui/material";
|
||||
import DataTable from "@/Components/v1/Table/index.jsx";
|
||||
import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded";
|
||||
import ArrowUpwardRoundedIcon from "@mui/icons-material/ArrowUpwardRounded";
|
||||
import Icon from "@/Components/v1/Icon";
|
||||
import Host from "@/Components/v1/Host/index.jsx";
|
||||
import { StatusLabel } from "@/Components/v1/Label/index.jsx";
|
||||
import BarChart from "@/Components/v1/Charts/BarChart/index.jsx";
|
||||
@@ -92,9 +91,15 @@ const UptimeDataTable = ({
|
||||
}}
|
||||
>
|
||||
{sort?.order === "asc" ? (
|
||||
<ArrowUpwardRoundedIcon />
|
||||
<Icon
|
||||
name="ArrowUp"
|
||||
size={18}
|
||||
/>
|
||||
) : (
|
||||
<ArrowDownwardRoundedIcon />
|
||||
<Icon
|
||||
name="ArrowDown"
|
||||
size={18}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
</Stack>
|
||||
@@ -128,9 +133,15 @@ const UptimeDataTable = ({
|
||||
}}
|
||||
>
|
||||
{sort?.order === "asc" ? (
|
||||
<ArrowUpwardRoundedIcon fontSize="18px" />
|
||||
<Icon
|
||||
name="ArrowUp"
|
||||
size={18}
|
||||
/>
|
||||
) : (
|
||||
<ArrowDownwardRoundedIcon fontSize="18px" />
|
||||
<Icon
|
||||
name="ArrowDown"
|
||||
size={18}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.35395 21C10.0591 21.6224 10.9853 22 11.9998 22C13.0142 22 13.9405 21.6224 14.6456 21M17.9998 8C17.9998 6.4087 17.3676 4.88258 16.2424 3.75736C15.1172 2.63214 13.5911 2 11.9998 2C10.4085 2 8.88235 2.63214 7.75713 3.75736C6.63192 4.88258 5.99977 6.4087 5.99977 8C5.99977 11.0902 5.22024 13.206 4.34944 14.6054C3.6149 15.7859 3.24763 16.3761 3.2611 16.5408C3.27601 16.7231 3.31463 16.7926 3.46155 16.9016C3.59423 17 4.19237 17 5.38863 17H18.6109C19.8072 17 20.4053 17 20.538 16.9016C20.6849 16.7926 20.7235 16.7231 20.7384 16.5408C20.7519 16.3761 20.3846 15.7859 19.6501 14.6054C18.7793 13.206 17.9998 11.0902 17.9998 8Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 817 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M20 20V13M12 20V10M4 20L4 16M13.4067 5.0275L18.5751 6.96567M10.7988 5.40092L5.20023 9.59983M21.0607 6.43934C21.6464 7.02513 21.6464 7.97487 21.0607 8.56066C20.4749 9.14645 19.5251 9.14645 18.9393 8.56066C18.3536 7.97487 18.3536 7.02513 18.9393 6.43934C19.5251 5.85355 20.4749 5.85355 21.0607 6.43934ZM5.06066 9.43934C5.64645 10.0251 5.64645 10.9749 5.06066 11.5607C4.47487 12.1464 3.52513 12.1464 2.93934 11.5607C2.35355 10.9749 2.35355 10.0251 2.93934 9.43934C3.52513 8.85355 4.47487 8.85355 5.06066 9.43934ZM13.0607 3.43934C13.6464 4.02513 13.6464 4.97487 13.0607 5.56066C12.4749 6.14645 11.5251 6.14645 10.9393 5.56066C10.3536 4.97487 10.3536 4.02513 10.9393 3.43934C11.5251 2.85355 12.4749 2.85355 13.0607 3.43934Z" stroke="black" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 915 B |
@@ -1,4 +0,0 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="32" height="32" fill="#1570EF"/>
|
||||
<path d="M4.43466 24V9.45455H10.0028C11.054 9.45455 11.9276 9.62026 12.6236 9.9517C13.3243 10.2784 13.8475 10.7259 14.1932 11.294C14.5436 11.8622 14.7188 12.5062 14.7188 13.2259C14.7188 13.8177 14.6051 14.3243 14.3778 14.7457C14.1506 15.1624 13.8452 15.5009 13.4616 15.7614C13.0781 16.0218 12.6496 16.2088 12.1761 16.3224V16.4645C12.6922 16.4929 13.187 16.6515 13.6605 16.9403C14.1387 17.2244 14.5294 17.6269 14.8324 18.1477C15.1354 18.6686 15.2869 19.2983 15.2869 20.0369C15.2869 20.7898 15.1046 21.4669 14.7401 22.0682C14.3755 22.6648 13.8262 23.1359 13.0923 23.4815C12.3584 23.8272 11.4351 24 10.3224 24H4.43466ZM7.0696 21.7983H9.90341C10.8598 21.7983 11.5488 21.616 11.9702 21.2514C12.3963 20.8821 12.6094 20.4086 12.6094 19.831C12.6094 19.4001 12.5028 19.0118 12.2898 18.6662C12.0767 18.3158 11.7737 18.0412 11.3807 17.8423C10.9877 17.6387 10.5189 17.5369 9.97443 17.5369H7.0696V21.7983ZM7.0696 15.6406H9.67614C10.1307 15.6406 10.5402 15.5578 10.9048 15.392C11.2694 15.2216 11.5559 14.9825 11.7642 14.6747C11.9773 14.3622 12.0838 13.9929 12.0838 13.5668C12.0838 13.0033 11.8849 12.5393 11.4872 12.1747C11.0942 11.8101 10.5095 11.6278 9.73295 11.6278H7.0696V15.6406ZM25.5482 9.45455H28.1832V18.9574C28.1832 19.9991 27.937 20.9152 27.4445 21.706C26.9568 22.4967 26.2703 23.1146 25.3849 23.5597C24.4995 24 23.4649 24.2202 22.2812 24.2202C21.0927 24.2202 20.0558 24 19.1704 23.5597C18.285 23.1146 17.5984 22.4967 17.1107 21.706C16.623 20.9152 16.3792 19.9991 16.3792 18.9574V9.45455H19.0141V18.7372C19.0141 19.3433 19.1467 19.883 19.4119 20.3565C19.6817 20.83 20.0605 21.2017 20.5482 21.4716C21.0359 21.7367 21.6136 21.8693 22.2812 21.8693C22.9488 21.8693 23.5264 21.7367 24.0141 21.4716C24.5066 21.2017 24.8853 20.83 25.1505 20.3565C25.4156 19.883 25.5482 19.3433 25.5482 18.7372V9.45455Z" fill="white"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.9 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg width="20" height="22" viewBox="0 0 20 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19 10.5V7.8C19 6.11984 19 5.27976 18.673 4.63803C18.3854 4.07354 17.9265 3.6146 17.362 3.32698C16.7202 3 15.8802 3 14.2 3H5.8C4.11984 3 3.27976 3 2.63803 3.32698C2.07354 3.6146 1.6146 4.07354 1.32698 4.63803C1 5.27976 1 6.11984 1 7.8V16.2C1 17.8802 1 18.7202 1.32698 19.362C1.6146 19.9265 2.07354 20.3854 2.63803 20.673C3.27976 21 4.11984 21 5.8 21H10.5M19 9H1M14 1V5M6 1V5M16 20V14M13 17H19" stroke="#667085" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 591 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="18" height="20" viewBox="0 0 18 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16.5 8.33342H1.5M12.3333 1.66675V5.00008M5.66667 1.66675V5.00008M5.5 18.3334H12.5C13.9001 18.3334 14.6002 18.3334 15.135 18.0609C15.6054 17.8212 15.9878 17.4388 16.2275 16.9684C16.5 16.4336 16.5 15.7335 16.5 14.3334V7.33342C16.5 5.93328 16.5 5.23322 16.2275 4.69844C15.9878 4.22803 15.6054 3.84558 15.135 3.6059C14.6002 3.33341 13.9001 3.33341 12.5 3.33341H5.5C4.09987 3.33341 3.3998 3.33341 2.86502 3.6059C2.39462 3.84558 2.01217 4.22803 1.77248 4.69844C1.5 5.23322 1.5 5.93328 1.5 7.33341V14.3334C1.5 15.7335 1.5 16.4336 1.77248 16.9684C2.01217 17.4388 2.39462 17.8212 2.86502 18.0609C3.3998 18.3334 4.09987 18.3334 5.5 18.3334Z" stroke="#344054" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 830 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.5 20H5C3.89543 20 3 19.1046 3 18V4C3 2.89543 3.89543 2 5 2H19C20.1046 2 21 2.89543 21 4V18C21 19.1046 20.1046 20 19 20H17.5M12 19C13.6569 19 15 17.6569 15 16C15 14.3431 13.6569 13 12 13C10.3431 13 9 14.3431 9 16C9 17.6569 10.3431 19 12 19ZM12 19L12.0214 18.9998L8.82867 22.1926L6.00024 19.3641L9.01965 16.3447M12 19L15.1928 22.1926L18.0212 19.3641L15.0018 16.3447M9 6H15M7 9.5H17" stroke="#475466" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 578 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17 17L22 12L17 7M7 7L2 12L7 17M14 3L10 21" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 237 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.5 12L10.5 15L16.5 9M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 332 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.24935 10.0001L8.74935 12.5001L13.7493 7.50008M18.3327 10.0001C18.3327 14.6025 14.6017 18.3334 9.99935 18.3334C5.39698 18.3334 1.66602 14.6025 1.66602 10.0001C1.66602 5.39771 5.39698 1.66675 9.99935 1.66675C14.6017 1.66675 18.3327 5.39771 18.3327 10.0001Z" stroke="#1570EF" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 456 B |
@@ -1,4 +0,0 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 10C0 4.47715 4.47715 0 10 0C15.5228 0 20 4.47715 20 10C20 15.5228 15.5228 20 10 20C4.47715 20 0 15.5228 0 10Z" fill="#D0D5DD"/>
|
||||
<path d="M6.25 10L8.75 12.5L13.75 7.5" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 363 B |
@@ -1,4 +0,0 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 6C0 2.68629 2.68629 0 6 0H14C17.3137 0 20 2.68629 20 6V14C20 17.3137 17.3137 20 14 20H6C2.68629 20 0 17.3137 0 14V6Z" fill="#1570EF"/>
|
||||
<path d="M14.6668 6.5L8.25016 12.9167L5.3335 10" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 380 B |
@@ -1,5 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="12" cy="12" r="12" fill="#079455"/>
|
||||
<circle cx="12" cy="12" r="8" fill="#17B26A"/>
|
||||
<circle cx="12" cy="12" r="3" fill="white"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 243 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0.5 6C0.5 2.96243 2.96243 0.5 6 0.5H14C17.0376 0.5 19.5 2.96243 19.5 6V14C19.5 17.0376 17.0376 19.5 14 19.5H6C2.96243 19.5 0.5 17.0376 0.5 14V6Z" stroke="#848b97"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 278 B |
@@ -1,5 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="12" cy="12" r="12" fill="#D92D20"/>
|
||||
<circle cx="12" cy="12" r="8" fill="#F04438"/>
|
||||
<circle cx="12" cy="12" r="3" fill="white"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 243 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16.5 17H21.5L16.5 22H21.5M21.9506 13C21.9833 12.6711 22 12.3375 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22C12.1677 22 12.3344 21.9959 12.5 21.9877C12.6678 21.9795 12.8345 21.9671 13 21.9506M12 6V12L15.7384 13.8692" stroke="#667085" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 454 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15 9L9 15M9 9L15 15M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 330 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 2V4M15 2V4M9 20V22M15 20V22M20 9H22M20 14H22M2 9H4M2 14H4M8.8 20H15.2C16.8802 20 17.7202 20 18.362 19.673C18.9265 19.3854 19.3854 18.9265 19.673 18.362C20 17.7202 20 16.8802 20 15.2V8.8C20 7.11984 20 6.27976 19.673 5.63803C19.3854 5.07354 18.9265 4.6146 18.362 4.32698C17.7202 4 16.8802 4 15.2 4H8.8C7.11984 4 6.27976 4 5.63803 4.32698C5.07354 4.6146 4.6146 5.07354 4.32698 5.63803C4 6.27976 4 7.11984 4 8.8V15.2C4 16.8802 4 17.7202 4.32698 18.362C4.6146 18.9265 5.07354 19.3854 5.63803 19.673C6.27976 20 7.11984 20 8.8 20ZM10.6 15H13.4C13.9601 15 14.2401 15 14.454 14.891C14.6422 14.7951 14.7951 14.6422 14.891 14.454C15 14.2401 15 13.9601 15 13.4V10.6C15 10.0399 15 9.75992 14.891 9.54601C14.7951 9.35785 14.6422 9.20487 14.454 9.10899C14.2401 9 13.9601 9 13.4 9H10.6C10.0399 9 9.75992 9 9.54601 9.10899C9.35785 9.20487 9.20487 9.35785 9.10899 9.54601C9 9.75992 9 10.0399 9 10.6V13.4C9 13.9601 9 14.2401 9.10899 14.454C9.20487 14.6422 9.35785 14.7951 9.54601 14.891C9.75992 15 10.0399 15 10.6 15Z" stroke="#667085" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 17H16M11.0177 2.764L4.23539 8.03912C3.78202 8.39175 3.55534 8.56806 3.39203 8.78886C3.24737 8.98444 3.1396 9.20478 3.07403 9.43905C3 9.70352 3 9.9907 3 10.5651V17.8C3 18.9201 3 19.4801 3.21799 19.908C3.40973 20.2843 3.71569 20.5903 4.09202 20.782C4.51984 21 5.07989 21 6.2 21H17.8C18.9201 21 19.4802 21 19.908 20.782C20.2843 20.5903 20.5903 20.2843 20.782 19.908C21 19.4801 21 18.9201 21 17.8V10.5651C21 9.9907 21 9.70352 20.926 9.43905C20.8604 9.20478 20.7526 8.98444 20.608 8.78886C20.4447 8.56806 20.218 8.39175 19.7646 8.03913L12.9823 2.764C12.631 2.49075 12.4553 2.35412 12.2613 2.3016C12.0902 2.25526 11.9098 2.25526 11.7387 2.3016C11.5447 2.35412 11.369 2.49075 11.0177 2.764Z" stroke="#667085" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 885 B |
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24" height="24" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11 20C15.9706 20 20 15.9706 20 11C20 6.02944 15.9706 2 11 2C6.02944 2 2 6.02944 2 11C2 12.9021 2.5901 14.6665 3.59721 16.1199C3.70168 16.2707 3.7226 16.4653 3.64529 16.6317L2.42556 19.2519C2.23082 19.6399 2.57262 20.0754 2.97992 19.9193L6.0988 18.7595C6.25727 18.7007 6.44031 18.7151 6.58838 18.7985C7.9427 19.5534 9.4987 20 11 20Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M7 8.5H17" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M7 12.5H13" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M7 16.5H11" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 923 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11 4.5H18.3C19.4201 4.5 19.9802 4.5 20.408 4.71799C20.7843 4.90973 21.0903 5.21569 21.282 5.59202C21.5 6.01984 21.5 6.57989 21.5 7.7V9C21.5 9.93188 21.5 10.3978 21.3478 10.7654C21.1448 11.2554 20.7554 11.6448 20.2654 11.8478C19.8978 12 19.4319 12 18.5 12M13 19.5H5.7C4.5799 19.5 4.01984 19.5 3.59202 19.282C3.21569 19.0903 2.90973 18.7843 2.71799 18.408C2.5 17.9802 2.5 17.4201 2.5 16.3V15C2.5 14.0681 2.5 13.6022 2.65224 13.2346C2.85523 12.7446 3.24458 12.3552 3.73463 12.1522C4.10218 12 4.56812 12 5.5 12M10.3 14.5H13.7C13.98 14.5 14.12 14.5 14.227 14.4455C14.3211 14.3976 14.3976 14.3211 14.4455 14.227C14.5 14.12 14.5 13.98 14.5 13.7V10.3C14.5 10.02 14.5 9.87996 14.4455 9.773C14.3976 9.67892 14.3211 9.60243 14.227 9.5545C14.12 9.5 13.98 9.5 13.7 9.5H10.3C10.02 9.5 9.87996 9.5 9.773 9.5545C9.67892 9.60243 9.60243 9.67892 9.5545 9.773C9.5 9.87996 9.5 10.02 9.5 10.3V13.7C9.5 13.98 9.5 14.12 9.5545 14.227C9.60243 14.3211 9.67892 14.3976 9.773 14.4455C9.87996 14.5 10.02 14.5 10.3 14.5ZM17.8 22H21.2C21.48 22 21.62 22 21.727 21.9455C21.8211 21.8976 21.8976 21.8211 21.9455 21.727C22 21.62 22 21.48 22 21.2V17.8C22 17.52 22 17.38 21.9455 17.273C21.8976 17.1789 21.8211 17.1024 21.727 17.0545C21.62 17 21.48 17 21.2 17H17.8C17.52 17 17.38 17 17.273 17.0545C17.1789 17.1024 17.1024 17.1789 17.0545 17.273C17 17.38 17 17.52 17 17.8V21.2C17 21.48 17 21.62 17.0545 21.727C17.1024 21.8211 17.1789 21.8976 17.273 21.9455C17.38 22 17.52 22 17.8 22ZM2.8 7H6.2C6.48003 7 6.62004 7 6.727 6.9455C6.82108 6.89757 6.89757 6.82108 6.9455 6.727C7 6.62004 7 6.48003 7 6.2V2.8C7 2.51997 7 2.37996 6.9455 2.273C6.89757 2.17892 6.82108 2.10243 6.727 2.0545C6.62004 2 6.48003 2 6.2 2H2.8C2.51997 2 2.37996 2 2.273 2.0545C2.17892 2.10243 2.10243 2.17892 2.0545 2.273C2 2.37996 2 2.51997 2 2.8V6.2C2 6.48003 2 6.62004 2.0545 6.727C2.10243 6.82108 2.17892 6.89757 2.273 6.9455C2.37996 7 2.51997 7 2.8 7Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.0 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14 11H8M10 15H8M16 7H8M20 6.8V17.2C20 18.8802 20 19.7202 19.673 20.362C19.3854 20.9265 18.9265 21.3854 18.362 21.673C17.7202 22 16.8802 22 15.2 22H8.8C7.11984 22 6.27976 22 5.63803 21.673C5.07354 21.3854 4.6146 20.9265 4.32698 20.362C4 19.7202 4 18.8802 4 17.2V6.8C4 5.11984 4 4.27976 4.32698 3.63803C4.6146 3.07354 5.07354 2.6146 5.63803 2.32698C6.27976 2 7.11984 2 8.8 2H15.2C16.8802 2 17.7202 2 18.362 2.32698C18.9265 2.6146 19.3854 3.07354 19.673 3.63803C20 4.27976 20 5.11984 20 6.8Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 684 B |
@@ -1,5 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 13C12.5523 13 13 12.5523 13 12C13 11.4477 12.5523 11 12 11C11.4477 11 11 11.4477 11 12C11 12.5523 11.4477 13 12 13Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12 6C12.5523 6 13 5.55228 13 5C13 4.44772 12.5523 4 12 4C11.4477 4 11 4.44772 11 5C11 5.55228 11.4477 6 12 6Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12 20C12.5523 20 13 19.5523 13 19C13 18.4477 12.5523 18 12 18C11.4477 18 11 18.4477 11 19C11 19.5523 11.4477 20 12 20Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 733 B |