Merge branch 'develop' into settings-zod-schema

This commit is contained in:
karenvicent committed 2026-02-12 14:25:05 -05:00
commit 6195650dc1
19 files changed
+179 -464

No files matched your search

@@ -1,53 +0,0 @@
.wallet-adapter-dropdown {
width: 100%;
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
gap: var(--env-var-spacing-1);
}
.wallet-adapter-button {
display: flex;
align-items: center;
justify-content: center;
width: auto;
height: var(--env-var-height-2) !important;
font-size: var(--env-var-font-size-medium-plus) !important;
font-weight: 500 !important;
margin: 0;
padding: calc(
(var(--env-var-height-2) - var(--env-var-font-size-medium-plus) * 1.2) / 2
)
var(--env-var-spacing-1);
white-space: nowrap;
}
.wallet-adapter-modal-title {
font-size: var(--font-size-h5) !important;
}
/* Separator styling */
.wallet-adapter-modal-divider {
background-color: var(--border-color) !important;
margin: var(--spacing-md) 0 !important;
}
/* Responsive fixes */
@media (max-width: 1200px) {
.wallet-adapter-button {
font-size: var(--env-var-font-size-medium) !important;
padding: calc((var(--env-var-height-2) - var(--env-var-font-size-medium) * 1.2) / 2)
var(--env-var-spacing-1-minus) !important;
}
}
@media (max-width: 900px) {
.wallet-adapter-modal-wrapper {
flex-direction: column !important;
}
.wallet-adapter-modal-divider {
margin: var(--spacing-sm) 0 !important;
}
}
@@ -1,50 +0,0 @@
// import { useMemo } from "react";
// import { ConnectionProvider, WalletProvider } from "@solana/wallet-adapter-react";
// import { WalletAdapterNetwork } from "@solana/wallet-adapter-base";
// import {
// UnsafeBurnerWalletAdapter,
// PhantomWalletAdapter,
// } from "@solana/wallet-adapter-wallets";
// import { WalletModalProvider } from "@solana/wallet-adapter-react-ui";
// import { clusterApiUrl } from "@solana/web3.js";
// import PropTypes from "prop-types";
// import "./index.css";
// // Default styles that can be overridden by your app
// import "@solana/wallet-adapter-react-ui/styles.css";
// export const Wallet = ({ children }) => {
// // The network can be set to 'devnet', 'testnet', or 'mainnet-beta'.
// const network = WalletAdapterNetwork.Mainnet;
// // You can also provide a custom RPC endpoint.
// const endpoint = useMemo(() => clusterApiUrl(network), [network]);
// const wallets = useMemo(
// () => [new PhantomWalletAdapter()],
// // eslint-disable-next-line react-hooks/exhaustive-deps
// [network]
// );
// return (
// <ConnectionProvider endpoint={endpoint}>
// <WalletProvider
// wallets={wallets}
// autoConnect
// >
// <WalletModalProvider>{children}</WalletModalProvider>
// </WalletProvider>
// </ConnectionProvider>
// );
// };
// Wallet.propTypes = {
// children: PropTypes.node,
// };
const Wallet = ({ children }) => {
return children;
};
export default Wallet;
@@ -6,7 +6,6 @@
min-height: 16px;
}
.MuiBreadcrumbs-root .MuiBreadcrumbs-li a {
font-size: var(--env-var-font-size-medium);
font-weight: 400;
}
.MuiBreadcrumbs-root .MuiBreadcrumbs-li:not(:last-child) {
@@ -64,7 +64,6 @@ const Select = ({
const theme = useTheme();
const getItemValue = (item) => item?._id ?? item?.id;
const itemStyles = {
fontSize: "var(--env-var-font-size-medium)",
color: theme.palette.primary.contrastTextTertiary,
borderRadius: theme.shape.borderRadius,
margin: theme.spacing(2),
@@ -18,7 +18,6 @@ export const HttpAdornment = ({ https }) => {
>
<Typography
component="h5"
paddingRight={"var(--env-var-spacing-1-minus)"}
color={theme.palette.primary.contrastTextSecondary}
sx={{ lineHeight: 1, opacity: 0.8 }}
>
@@ -23,7 +23,6 @@ export const MonitorStatus = ({ monitor }: { monitor: Monitor }) => {
overflow={"hidden"}
textOverflow={"ellipsis"}
whiteSpace={"nowrap"}
maxWidth={isSmall ? "100%" : "calc((100vw - var(--env-var-width-2)) / 2)"}
>
{monitor.name}
</Typography>
@@ -40,7 +39,6 @@ export const MonitorStatus = ({ monitor }: { monitor: Monitor }) => {
overflow={"hidden"}
textOverflow={"ellipsis"}
whiteSpace={"nowrap"}
maxWidth={isSmall ? "100%" : "calc((100vw - var(--env-var-width-2)) / 2)"}
>
{formatUrl(monitor?.url)}
</Typography>
@@ -1,4 +1,17 @@
import { Box, Stack, useTheme } from "@mui/material";
import Box from "@mui/material/Box";
import Stack from "@mui/material/Stack";
import { useTheme, keyframes } from "@mui/material";
const ripple = keyframes`
from {
opacity: 1;
transform: scale(0);
}
to {
opacity: 0;
transform: scale(2);
}
`;
interface PulseDotProps {
color: string;
@@ -28,7 +41,7 @@ export const PulseDot = ({ color }: PulseDotProps) => {
height: "100%",
backgroundColor: "inherit",
borderRadius: "50%",
animation: "ripple 1.8s ease-out infinite",
animation: `${ripple} 1.8s ease-out infinite`,
},
"&::after": {
content: `""`,
-98
View File
@@ -1,98 +0,0 @@
import { createSlice } from "@reduxjs/toolkit";
const initialMode = window?.matchMedia?.("(prefers-color-scheme: dark)")?.matches
? "dark"
: "light";
const initialState = {
monitors: {
rowsPerPage: 10,
},
team: {
rowsPerPage: 5,
},
maintenance: {
rowsPerPage: 5,
},
infrastructure: {
rowsPerPage: 5,
},
logs: {
rowsPerPage: 15,
},
sidebar: {
collapsed: false,
},
mode: initialMode,
showURL: false,
greeting: { index: 0, lastUpdate: null },
timezone: "America/Toronto",
distributedUptimeEnabled: false,
language: "en",
starPromptOpen: true,
chartType: "histogram",
};
const uiSlice = createSlice({
name: "ui",
initialState,
reducers: {
setDistributedUptimeEnabled: (state, action) => {
state.distributedUptimeEnabled = action.payload;
},
setRowsPerPage: (state, action) => {
const { table, value } = action.payload;
if (!state[table]) {
state[table] = {};
}
state[table].rowsPerPage = value;
},
toggleSidebar: (state) => {
state.sidebar.collapsed = !state.sidebar.collapsed;
},
setCollapsed: (state, action) => {
const { collapsed } = action.payload;
state.sidebar.collapsed = collapsed;
},
setMode: (state, action) => {
state.mode = action.payload;
},
setShowURL: (state, action) => {
state.showURL = action.payload;
},
setGreeting(state, action) {
if (!state.greeting) {
state.greeting = { index: 0, lastUpdate: null };
}
state.greeting.index = action.payload.index;
state.greeting.lastUpdate = action.payload.lastUpdate;
},
setTimezone(state, action) {
state.timezone = action.payload.timezone;
},
setLanguage: (state, action) => {
state.language = action.payload;
},
setStarPromptOpen: (state, action) => {
state.starPromptOpen = action.payload;
},
setChartType: (state, action) => {
state.chartType = action.payload;
},
},
});
export default uiSlice.reducer;
export const {
setRowsPerPage,
toggleSidebar,
setCollapsed,
setMode,
setShowURL,
setGreeting,
setTimezone,
setDistributedUptimeEnabled,
setLanguage,
setStarPromptOpen,
setChartType,
} = uiSlice.actions;
+133
View File
@@ -0,0 +1,133 @@
import { createSlice, type PayloadAction } from "@reduxjs/toolkit";
type ThemeMode = "light" | "dark";
type ChartType = "histogram" | "line";
type TableName = "monitors" | "team" | "maintenance" | "infrastructure" | "logs";
interface TableState {
rowsPerPage: number;
}
interface SidebarState {
collapsed: boolean;
}
interface GreetingState {
index: number;
lastUpdate: string | null;
}
interface UIState {
monitors: TableState;
team: TableState;
maintenance: TableState;
infrastructure: TableState;
logs: TableState;
sidebar: SidebarState;
mode: ThemeMode;
showURL: boolean;
greeting: GreetingState;
timezone: string;
distributedUptimeEnabled: boolean;
language: string;
starPromptOpen: boolean;
chartType: ChartType;
}
const initialMode: ThemeMode = window?.matchMedia?.("(prefers-color-scheme: dark)")
?.matches
? "dark"
: "light";
const initialState: UIState = {
monitors: {
rowsPerPage: 10,
},
team: {
rowsPerPage: 5,
},
maintenance: {
rowsPerPage: 5,
},
infrastructure: {
rowsPerPage: 5,
},
logs: {
rowsPerPage: 15,
},
sidebar: {
collapsed: false,
},
mode: initialMode,
showURL: false,
greeting: { index: 0, lastUpdate: null },
timezone: "America/Toronto",
distributedUptimeEnabled: false,
language: "en",
starPromptOpen: true,
chartType: "histogram",
};
const uiSlice = createSlice({
name: "ui",
initialState,
reducers: {
setDistributedUptimeEnabled: (state, action: PayloadAction<boolean>) => {
state.distributedUptimeEnabled = action.payload;
},
setRowsPerPage: (
state,
action: PayloadAction<{ table: TableName; value: number }>
) => {
const { table, value } = action.payload;
state[table].rowsPerPage = value;
},
toggleSidebar: (state) => {
state.sidebar.collapsed = !state.sidebar.collapsed;
},
setCollapsed: (state, action: PayloadAction<{ collapsed: boolean }>) => {
state.sidebar.collapsed = action.payload.collapsed;
},
setMode: (state, action: PayloadAction<ThemeMode>) => {
state.mode = action.payload;
},
setShowURL: (state, action: PayloadAction<boolean>) => {
state.showURL = action.payload;
},
setGreeting: (
state,
action: PayloadAction<{ index: number; lastUpdate: string | null }>
) => {
state.greeting.index = action.payload.index;
state.greeting.lastUpdate = action.payload.lastUpdate;
},
setTimezone: (state, action: PayloadAction<{ timezone: string }>) => {
state.timezone = action.payload.timezone;
},
setLanguage: (state, action: PayloadAction<string>) => {
state.language = action.payload;
},
setStarPromptOpen: (state, action: PayloadAction<boolean>) => {
state.starPromptOpen = action.payload;
},
setChartType: (state, action: PayloadAction<ChartType>) => {
state.chartType = action.payload;
},
},
});
export type { UIState, ThemeMode, ChartType, TableName };
export default uiSlice.reducer;
export const {
setRowsPerPage,
toggleSidebar,
setCollapsed,
setMode,
setShowURL,
setGreeting,
setTimezone,
setDistributedUptimeEnabled,
setLanguage,
setStarPromptOpen,
setChartType,
} = uiSlice.actions;
-10
View File
@@ -1,10 +0,0 @@
.integrations h1.MuiTypography-root {
font-size: var(--env-var-font-size-large);
font-weight: 600;
}
.integrations p.MuiTypography-root {
font-size: var(--env-var-font-size-medium);
}
.integrations button {
height: var(--env-var-height-2);
}
-156
View File
@@ -1,156 +0,0 @@
import PropTypes from "prop-types";
import { Stack, Typography, Grid, Button } from "@mui/material";
import { useTheme } from "@emotion/react";
import Discord from "../../../assets/icons/discord-icon.svg?react";
import Slack from "../../../assets/icons/slack-icon.svg?react";
import Zapier from "../../../assets/icons/zapier-icon.svg?react";
import { useTranslation } from "react-i18next";
import "./index.css";
/**
* Integrations component
* @param {Object} props - Props for the IntegrationsComponent.
* @param {string} props.icon - The icon for the integration image.
* @param {string} props.header - The header for the integration.
* @param {string} props.info - Information about the integration.
* @param {Function} props.onClick - The onClick handler for the integration button.
* @returns {JSX.Element} The JSX representation of the IntegrationsComponent.
*/
const IntegrationsComponent = ({ icon, header, info, onClick }) => {
const theme = useTheme();
const { t } = useTranslation();
return (
<Grid
item
lg={6}
flexGrow={1}
>
<Stack
direction="row"
justifyContent="space-between"
gap={theme.spacing(12)}
p={theme.spacing(8)}
pl={theme.spacing(12)}
height="100%"
border={1}
borderColor={theme.palette.primary.lowContrast}
borderRadius={theme.shape.borderRadius}
backgroundColor={theme.palette.primary.main}
>
{icon}
<Stack
gap={theme.spacing(2)}
flex={1}
>
<Typography component="h1">{header}</Typography>
<Typography
sx={{
maxWidth: "300px",
wordWrap: "break-word",
}}
>
{info}
</Typography>
</Stack>
<Button
variant="contained"
color="accent"
onClick={onClick}
sx={{ alignSelf: "center" }}
disabled={true}
>
{t("add")}
</Button>
</Stack>
</Grid>
);
};
// PropTypes for IntegrationsComponent
IntegrationsComponent.propTypes = {
icon: PropTypes.object.isRequired,
header: PropTypes.string.isRequired,
info: PropTypes.string.isRequired,
onClick: PropTypes.func.isRequired,
};
/**
* Integrations Page Component
* @returns {JSX.Element} The JSX representation of the Integrations page.
*/
const Integrations = () => {
const theme = useTheme();
const { t } = useTranslation();
const integrations = [
{
icon: (
<Slack
alt="slack integration"
style={{ width: "45px", height: "45px", alignSelf: "center" }}
/>
),
header: t("integrationsSlack"),
info: t("integrationsSlackInfo"),
onClick: () => {},
},
{
icon: (
<Discord
alt="discord integration"
style={{ width: "42px", height: "42px", alignSelf: "center" }}
/>
),
header: t("integrationsDiscord"),
info: t("integrationsDiscordInfo"),
onClick: () => {},
},
{
icon: (
<Zapier
alt="zapier integration"
style={{ width: "42px", height: "42px", alignSelf: "center" }}
/>
),
header: t("integrationsZapier"),
info: t("integrationsZapierInfo"),
onClick: () => {},
},
// Add more integrations as needed
];
return (
<Stack
className="integrations"
pt={theme.spacing(20)}
gap={theme.spacing(2)}
sx={{
"& h1, & p": {
color: theme.palette.primary.contrastTextSecondary,
},
}}
>
<Typography component="h1">{t("integrations")}</Typography>
<Typography mb={theme.spacing(12)}>{t("integrationsPrism")}</Typography>
<Grid
container
spacing={theme.spacing(12)}
>
{integrations.map((integration, index) => (
<IntegrationsComponent
key={index}
icon={integration.icon}
header={integration.header}
info={integration.info}
onClick={integration.onClick}
/>
))}
</Grid>
</Stack>
);
};
export default Integrations;
+3 -3
View File
@@ -27,7 +27,7 @@ import {
import SettingsStats from "./SettingsStats.jsx";
import { useIsAdmin } from "@/Hooks/useIsAdmin.js";
import { useGet, usePost, useDelete, useLazyGet, usePut } from "@/Hooks/UseApi";
import { useGet, usePost, useDelete, useLazyGet, usePatch } from "@/Hooks/UseApi";
// Constants
const BREADCRUMBS = [{ name: `Settings`, path: "/settings" }];
@@ -62,10 +62,10 @@ const Settings = () => {
}
}, [fetchedSettings]);
const { put: saveSettingsFn, loading: isSaving } = usePut();
const { patch: saveSettingsFn, loading: isSaving } = usePatch();
const saveSettings = async (settings) => {
const response = await saveSettingsFn("/settings", { settings });
const response = await saveSettingsFn("/settings", settings);
if (response?.data) {
const data = response.data;
setIsApiKeySet(data.pagespeedKeySet);
+1 -7
View File
@@ -38,7 +38,6 @@ const baseTheme = (palette) => ({
fontWeight: 400,
},
label: {
fontSize: "var(--env-var-font-size-medium)",
color: palette.primary.contrastTextSecondary,
fontWeight: 500,
},
@@ -332,8 +331,6 @@ const baseTheme = (palette) => ({
"& .MuiInputBase-input": {
padding: ".75em",
minHeight: "var(--env-var-height-2)",
fontSize: "var(--env-var-font-size-medium)",
fontWeight: 400,
color: palette.primary.contrastTextSecondary,
"&.Mui-disabled": {
@@ -368,13 +365,12 @@ const baseTheme = (palette) => ({
"& .MuiFormHelperText-root": {
color: palette.error.main,
opacity: 0.8,
fontSize: "var(--env-var-font-size-medium)",
fontSize: "var()",
marginLeft: 0,
},
"& .MuiFormHelperText-root.Mui-error": {
opacity: 0.8,
fontSize: "var(--env-var-font-size-medium)",
color: palette.error.main,
whiteSpace: "nowrap",
},
@@ -762,7 +758,6 @@ const baseTheme = (palette) => ({
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
maxWidth: "calc((100vw - var(--env-var-width-2)) / 2)",
},
},
{
@@ -775,7 +770,6 @@ const baseTheme = (palette) => ({
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
maxWidth: "calc((100vw - var(--env-var-width-2)) / 2)",
},
},
],
+7
View File
@@ -49,6 +49,13 @@ export const theme = (mode: string, palette: any) =>
},
components: {
MuiTouchRipple: {
styleOverrides: {
root: {
display: "none",
},
},
},
MuiButtonBase: {
defaultProps: {
disableRipple: true,
-66
View File
@@ -18,70 +18,4 @@ html {
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
/* Generalized Stylings */
--env-var-radius-2: 8px;
--env-var-width-2: 360px;
--env-var-width-4: 100px;
--env-var-height-2: 34px;
--env-var-nav-bar-height: 70px;
--env-var-side-bar-width: 250px;
--env-var-side-bar-collapsed-width: 64px;
--env-var-side-bar-auth-footer-height: 50px;
--env-var-spacing-1: 12px;
--env-var-spacing-1-plus: 16px;
--env-var-spacing-1-minus: 10px;
--env-var-spacing-2: 24px;
--env-var-font-size-small: 11px;
--env-var-font-size-small-plus: 12px;
--env-var-font-size-medium: 13px;
--env-var-font-size-medium-plus: 14px;
--env-var-font-size-large: 16px;
--env-var-font-size-large-plus: 22px;
--env-var-font-size-xlarge: 30px;
}
.MuiInputBase-root.Mui-disabled input {
cursor: not-allowed;
}
/* .Toastify__toast-container {
min-width: 300px;
width: auto;
}
.Toastify__toast-body .alert {
min-width: 150px;
padding: 5px 10px;
align-items: center;
}
.Toastify [class^="Toastify__toast"] {
padding: 0;
margin: 0;
}
.Toastify__toast {
min-height: 0;
border-radius: 4px;
}
.Toastify [class*="Toastify__toast-theme"] {
background-color: transparent;
} */
.MuiTouchRipple-root {
display: none;
}
@keyframes ripple {
from {
opacity: 1;
transform: scale(0);
}
to {
opacity: 0;
transform: scale(2);
}
}
+1 -1
View File
@@ -3,7 +3,7 @@ import App from "./App.jsx";
import "./index.css";
import { BrowserRouter as Router } from "react-router-dom";
import { Provider } from "react-redux";
import { persistor, store } from "./store.js";
import { persistor, store } from "@/store.js";
import { PersistGate } from "redux-persist/integration/react";
import I18nLoader from "./Components/v1/I18nLoader/index.jsx";
import { initApiClient } from "./Utils/ApiClient.js";
+16 -9
View File
@@ -1,18 +1,21 @@
import { configureStore, combineReducers } from "@reduxjs/toolkit";
import authReducer from "./Features/Auth/authSlice";
import uiReducer from "./Features/UI/uiSlice";
import authReducer from "@/Features/Auth/authSlice";
import uiReducer from "@/Features/UI/uiSlice";
import storage from "redux-persist/lib/storage";
import { persistReducer, persistStore, createTransform } from "redux-persist";
import {
persistReducer,
persistStore,
createTransform,
PERSIST,
REHYDRATE,
} from "redux-persist";
const authTransform = createTransform(
(inboundState) => {
(inboundState: Record<string, unknown>) => {
const { profileImage, ...rest } = inboundState;
return rest;
},
// No transformation on rehydration
null,
// Only applies to auth
undefined,
{ whitelist: ["auth"] }
);
@@ -28,6 +31,7 @@ const rootReducer = combineReducers({
ui: uiReducer,
});
// @ts-expect-error - redux-persist types don't align perfectly with redux-toolkit
const persistedReducer = persistReducer(persistConfig, rootReducer);
export const store = configureStore({
@@ -35,10 +39,13 @@ export const store = configureStore({
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: ["persist/PERSIST", "persist/REHYDRATE", "persist/REGISTER"],
ignoredActions: [PERSIST, REHYDRATE, "persist/REGISTER"],
},
}),
});
export type RootState = ReturnType<typeof rootReducer>;
export type AppDispatch = typeof store.dispatch;
export const persistor = persistStore(store);
export default store;
@@ -89,7 +89,6 @@ class SuperSimpleQueueHelper {
// Step 4 Add check to buffer
this.buffer.addToBuffer({ check });
// Step 4. Update monitor status
const statusChangeResult = await this.statusService.updateMonitorStatus(status, check);
@@ -3,6 +3,7 @@ import MonitorStats from "../../db/models/MonitorStats.js";
import { CheckModel } from "@/db/models/index.js";
import type {
Monitor,
MonitorStatus,
MonitorStatusResponse,
StatusChangeResult,
Check,
@@ -200,14 +201,13 @@ export class StatusService implements IStatusService {
monitor.recentChecks.shift();
}
monitor.status = status === true ? "up" : "down";
const prevStatus = monitor.status;
let newStatus = monitor.status;
let newStatus: MonitorStatus = status === true ? "up" : "down";
let statusChanged = false;
// Return early if not enough data points
if (monitor.statusWindow.length < monitor.statusWindowSize) {
monitor.status = newStatus;
const updated = await this.monitorsRepository.updateById(monitor.id, monitor.teamId, monitor);
return {
monitor: updated,