entitlements FE
This commit is contained in:
1 parent
c43241eb52
commit
e600f293b0
23 files changed
+124
-88
No files matched your search
@@ -0,0 +1,60 @@
|
||||
import Stack from "@mui/material/Stack";
|
||||
import { Button } from "@/components/inputs";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import { useSelector } from "react-redux";
|
||||
import type { RootState } from "@/store";
|
||||
import { useMemo } from "react";
|
||||
import type { EntitlemenstKey } from "@/types/entitlements";
|
||||
export const HeaderCreate = ({
|
||||
label,
|
||||
isLoading,
|
||||
path,
|
||||
entitlement,
|
||||
entitlementCount,
|
||||
}: {
|
||||
label?: string;
|
||||
isLoading: boolean;
|
||||
path: string;
|
||||
entitlement: EntitlemenstKey | null;
|
||||
entitlementCount: number;
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const ent = useSelector((s: RootState) => s.auth.user?.entitlements);
|
||||
const limitReached = useMemo(() => {
|
||||
if (!entitlement) return false;
|
||||
if (!ent || typeof ent[entitlement] !== "number") return false;
|
||||
return entitlementCount < ent[entitlement] ? false : true;
|
||||
}, [ent, entitlementCount]);
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="end"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(6)}
|
||||
>
|
||||
<Tooltip
|
||||
title={limitReached ? "Upgrade your plan to create more teams" : ""}
|
||||
disableHoverListener={!limitReached}
|
||||
>
|
||||
<span>
|
||||
<Button
|
||||
loading={isLoading}
|
||||
variant="contained"
|
||||
color="accent"
|
||||
disabled={limitReached}
|
||||
onClick={() => navigate(path)}
|
||||
>
|
||||
{limitReached
|
||||
? "Your plan's limit has been reached"
|
||||
: label || t("createNew")}
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { HeaderCreate } from "./HeaderCreate";
|
||||
@@ -1,35 +0,0 @@
|
||||
import Stack from "@mui/material/Stack";
|
||||
import { Button } from "@/components/inputs";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router";
|
||||
export const HeaderCreate = ({
|
||||
label,
|
||||
isLoading,
|
||||
path,
|
||||
}: {
|
||||
label?: string;
|
||||
isLoading: boolean;
|
||||
path: string;
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="end"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(6)}
|
||||
>
|
||||
<Button
|
||||
loading={isLoading}
|
||||
variant="contained"
|
||||
color="accent"
|
||||
onClick={() => navigate(path)}
|
||||
>
|
||||
{label || t("createNew")}
|
||||
</Button>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -1,4 +1,3 @@
|
||||
export { HeaderCreate } from "./HeaderCreate";
|
||||
export { HistogramStatus } from "./HistogramStatus";
|
||||
export { ChartAvgResponse } from "./ChartAvgResponse";
|
||||
export { HeaderRange } from "../common/HeaderRange";
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export { HeaderCreate } from "@/components/monitors/HeaderCreate";
|
||||
@@ -1 +0,0 @@
|
||||
export { HeaderCreate } from "@/components/monitors/HeaderCreate";
|
||||
@@ -25,6 +25,7 @@ export const useInitForm = ({
|
||||
notificationChannels: initialData?.notificationChannels || [],
|
||||
name: initialData?.name || "",
|
||||
interval: humanInterval,
|
||||
rejectUnauthorized: true,
|
||||
};
|
||||
return { defaults };
|
||||
}, [initialData]);
|
||||
|
||||
@@ -24,6 +24,7 @@ export const useInitForm = ({
|
||||
notificationChannels: initialData?.notificationChannels || [],
|
||||
name: initialData?.name || "",
|
||||
interval: humanInterval,
|
||||
rejectUnauthorized: true,
|
||||
};
|
||||
return { defaults };
|
||||
}, [initialData]);
|
||||
|
||||
@@ -58,7 +58,6 @@ const Login = () => {
|
||||
navigate("/login");
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(setAuthenticated(true));
|
||||
dispatch(setUser(user));
|
||||
dispatch(setSelectedTeamId(user.teams[0]?.id || null));
|
||||
|
||||
@@ -50,7 +50,7 @@ const ExportPage = () => {
|
||||
try {
|
||||
const text = await file.text();
|
||||
const json = JSON.parse(text);
|
||||
const res = await post("/monitors/import", { monitors: json });
|
||||
await post("/monitors/import", { monitors: json });
|
||||
} catch (error) {
|
||||
console.error("Failed to parse JSON", error);
|
||||
} finally {
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
PausedStatusBox,
|
||||
InfoBox,
|
||||
} from "@/components/design-elements";
|
||||
import { HeaderCreate } from "@/components/monitors";
|
||||
import { HeaderCreate } from "@/components/common";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import { Dialog } from "@/components/inputs";
|
||||
import { InfraMonitorsTable } from "./InfraMonitorsTable";
|
||||
@@ -86,7 +86,12 @@ const PageSpeedMonitorsPage = () => {
|
||||
title="Server Health Monitoring"
|
||||
description="Keep track of your servers' vital statistics including CPU usage, memory, disk space, and network performance. Receive alerts when resources run low."
|
||||
/>
|
||||
<HeaderCreate isLoading={loading} path="/infrastructure/create" />
|
||||
<HeaderCreate
|
||||
isLoading={loading}
|
||||
path="/infrastructure/create"
|
||||
entitlement="monitorsMax"
|
||||
entitlementCount={count}
|
||||
/>
|
||||
<Stack direction={isSmall ? "column" : "row"} gap={theme.spacing(8)}>
|
||||
<UpStatusBox n={upCount} />
|
||||
<DownStatusBox n={downCount} />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { HeaderCreate } from "@/components/notification-channels/HeaderCreate";
|
||||
import { HeaderCreate } from "@/components/common";
|
||||
import { BasePageWithStates, InfoBox } from "@/components/design-elements";
|
||||
import { ActionsMenu } from "@/components/actions-menu";
|
||||
import { Table } from "@/components/design-elements";
|
||||
@@ -134,6 +134,8 @@ const MaintenancePage = () => {
|
||||
label={"Create a new maintenance window"}
|
||||
isLoading={isValidating || isPausing || isDeleting}
|
||||
path="/maintenance/create"
|
||||
entitlement={null}
|
||||
entitlementCount={maintenance.length}
|
||||
/>
|
||||
<Table
|
||||
headers={headers}
|
||||
|
||||
@@ -39,7 +39,6 @@ export const MaintenanceForm = ({
|
||||
handleSubmit,
|
||||
control,
|
||||
reset,
|
||||
getValues,
|
||||
formState: { errors },
|
||||
} = useForm<FormValues>({
|
||||
resolver: zodResolver(maintenanceSchema),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { HeaderCreate } from "@/components/notification-channels/HeaderCreate";
|
||||
import { HeaderCreate } from "@/components/common";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { BasePageWithStates, InfoBox } from "@/components/design-elements";
|
||||
import { Table } from "@/components/design-elements";
|
||||
@@ -140,6 +140,8 @@ const NotificationChannelsPage = () => {
|
||||
label={"Create a new notification channel"}
|
||||
isLoading={isValidating || pausing || deleting}
|
||||
path="/notification-channels/create"
|
||||
entitlement={"notificationChannelsMax"}
|
||||
entitlementCount={notificationChannels.length}
|
||||
/>
|
||||
<Table
|
||||
headers={headers}
|
||||
|
||||
@@ -5,8 +5,8 @@ import {
|
||||
PausedStatusBox,
|
||||
InfoBox,
|
||||
} from "@/components/design-elements";
|
||||
import { HeaderCreate } from "@/components/monitors";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import { HeaderCreate } from "@/components/common";
|
||||
import { Dialog } from "@/components/inputs";
|
||||
import { PageSpeedMonitorTable } from "./PageSpeedMonitorTable";
|
||||
|
||||
@@ -86,7 +86,12 @@ const PageSpeedMonitorsPage = () => {
|
||||
title="Website Performance Tracking"
|
||||
description="Measure how fast your websites load and identify performance bottlenecks. Track Core Web Vitals and get actionable insights to improve user experience."
|
||||
/>
|
||||
<HeaderCreate isLoading={loading} path="/pagespeed/create" />
|
||||
<HeaderCreate
|
||||
isLoading={loading}
|
||||
path="/pagespeed/create"
|
||||
entitlement="monitorsMax"
|
||||
entitlementCount={count}
|
||||
/>
|
||||
<Stack direction={isSmall ? "column" : "row"} gap={theme.spacing(8)}>
|
||||
<UpStatusBox n={upCount} />
|
||||
<DownStatusBox n={downCount} />
|
||||
|
||||
@@ -1,26 +1,10 @@
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Box from "@mui/material/Box";
|
||||
import { BasePage, ConfigBox } from "@/components/design-elements";
|
||||
import {
|
||||
AutoComplete,
|
||||
Select,
|
||||
LanguageSelector,
|
||||
Button,
|
||||
RadioWithDescription,
|
||||
Dialog,
|
||||
} from "@/components/inputs";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { SettingsForm } from "@/pages/settings/SettingsForm";
|
||||
import DummyChart from "@/pages/settings/DummyChart";
|
||||
import { BasePage } from "@/components/design-elements";
|
||||
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { setTimezone, setMode, setChartType } from "@/features/uiSlice";
|
||||
import { useAppSelector, useAppDispatch } from "@/hooks/AppHooks";
|
||||
import timezones from "@/utils/timezones.json";
|
||||
import type { ITimeZone } from "@/types/timezone";
|
||||
import { SettingsForm } from "@/pages/settings/SettingsForm";
|
||||
|
||||
import { useMemo } from "react";
|
||||
import {} from "react-i18next";
|
||||
import { useAppSelector } from "@/hooks/AppHooks";
|
||||
import { useGet, usePatch, usePost } from "@/hooks/UseApi";
|
||||
import type { ApiResponse } from "@/hooks/UseApi";
|
||||
import { z } from "zod";
|
||||
@@ -34,20 +18,6 @@ const AdminSettingsPage = () => {
|
||||
const orgPermissions = user?.org?.permissions || [];
|
||||
const hasMaster = orgPermissions.includes("master");
|
||||
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const timezone = useAppSelector((state) => state.ui.timezone);
|
||||
const mode = useAppSelector((state) => state.ui.mode);
|
||||
const chartType = useAppSelector((state) => state.ui.chartType);
|
||||
const [selectedTimezone, setSelectedTimezone] = useState<ITimeZone | null>(
|
||||
null
|
||||
);
|
||||
useEffect(() => {
|
||||
const tz = timezones.find((tz) => tz._id === timezone);
|
||||
setSelectedTimezone(tz || null);
|
||||
}, [timezone]);
|
||||
|
||||
const { post, loading: posting } = usePost<Partial<FormValues>, any>();
|
||||
const { patch, loading: patching } = usePatch<Partial<FormValues>, any>();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { HeaderCreate } from "@/components/notification-channels/HeaderCreate";
|
||||
import { HeaderCreate } from "@/components/common";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { BasePageWithStates, InfoBox } from "@/components/design-elements";
|
||||
import { Table } from "@/components/design-elements";
|
||||
@@ -124,6 +124,8 @@ const StatusPages = () => {
|
||||
label={"Create a new Status Page"}
|
||||
isLoading={isValidating || deleting}
|
||||
path="/status-pages/create"
|
||||
entitlement="statusPagesMax"
|
||||
entitlementCount={statusPages.length}
|
||||
/>
|
||||
<Table
|
||||
headers={headers}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { BasePage } from "@/components/design-elements";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import { Table } from "@/components/design-elements";
|
||||
import { HeaderCreate } from "@/components/teams/HeaderCreate";
|
||||
import { HeaderCreate } from "@/components/common";
|
||||
import type { Header } from "@/components/design-elements/Table";
|
||||
|
||||
import { useNavigate } from "react-router";
|
||||
@@ -77,6 +77,8 @@ const TeamDetailsPage = () => {
|
||||
label="Add new team member"
|
||||
path={`/teams/${id}/member/create`}
|
||||
isLoading={false}
|
||||
entitlement={null}
|
||||
entitlementCount={teamMembers?.length || 0}
|
||||
/>
|
||||
<Typography variant="h2" marginTop={theme.spacing(4)}>
|
||||
Team Members
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { BasePage, InfoBox } from "@/components/design-elements";
|
||||
import { Table } from "@/components/design-elements";
|
||||
import { HeaderCreate } from "@/components/teams/HeaderCreate";
|
||||
import { HeaderCreate } from "@/components/common";
|
||||
import { ActionsMenu } from "@/components/actions-menu";
|
||||
import { Dialog } from "@/components/inputs";
|
||||
|
||||
@@ -93,6 +93,8 @@ const TeamsPage = () => {
|
||||
label="Create new team"
|
||||
isLoading={loading}
|
||||
path="/teams/create"
|
||||
entitlement="teamsMax"
|
||||
entitlementCount={teams.length}
|
||||
/>
|
||||
<Table
|
||||
headers={headers}
|
||||
|
||||
@@ -5,7 +5,8 @@ import {
|
||||
PausedStatusBox,
|
||||
InfoBox,
|
||||
} from "@/components/design-elements";
|
||||
import { HeaderCreate, HeaderFilter } from "@/components/monitors";
|
||||
import { HeaderFilter } from "@/components/monitors";
|
||||
import { HeaderCreate } from "@/components/common";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import { Dialog } from "@/components/inputs";
|
||||
import { MonitorTable } from "@/pages/uptime/MonitorTable";
|
||||
@@ -124,7 +125,12 @@ const UptimeMonitors = () => {
|
||||
title="Website & API Uptime Monitoring"
|
||||
description="Monitor your websites and APIs to ensure they're always accessible. Get instant alerts when your services go down and track uptime history over time."
|
||||
/>
|
||||
<HeaderCreate isLoading={loading} path="/uptime/create" />
|
||||
<HeaderCreate
|
||||
isLoading={loading}
|
||||
path="/uptime/create"
|
||||
entitlement="monitorsMax"
|
||||
entitlementCount={count}
|
||||
/>
|
||||
<Stack direction={isSmall ? "column" : "row"} gap={theme.spacing(8)}>
|
||||
<UpStatusBox n={upCount} />
|
||||
<DownStatusBox n={downCount} />
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
export type EntitlemenstKey = keyof Entitlements;
|
||||
|
||||
export interface Entitlements {
|
||||
plan: string;
|
||||
monitorsMax: number;
|
||||
notificationChannelsMax: number;
|
||||
statusPagesMax: number;
|
||||
checksIntervalMsMin: number;
|
||||
teamsMax: number;
|
||||
notificationsEnabled: boolean;
|
||||
retentionDays: number;
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { Entitlements } from "./entitlements";
|
||||
|
||||
export interface IUser {
|
||||
id: string;
|
||||
email: string;
|
||||
@@ -12,6 +14,7 @@ export interface IUser {
|
||||
name: string;
|
||||
permissions: string;
|
||||
}[];
|
||||
entitlements: Entitlements;
|
||||
}
|
||||
export interface IMongoUser {
|
||||
_id: string;
|
||||
|
||||
@@ -144,6 +144,7 @@ export const monitorSchemaPageSpeed = z.object({
|
||||
notificationChannels: z.array(z.string()).optional().default([]),
|
||||
name: z.string().min(1, "Display name is required"),
|
||||
interval: durationSchemaPageSpeed,
|
||||
rejectUnauthorized: z.boolean().default(true),
|
||||
});
|
||||
export const monitorSchemaInfra = z.object({
|
||||
type: z.string().min(1, "You must select an option"),
|
||||
@@ -156,6 +157,7 @@ export const monitorSchemaInfra = z.object({
|
||||
notificationChannels: z.array(z.string()).optional().default([]),
|
||||
name: z.string().min(1, "Display name is required"),
|
||||
interval: durationSchemaInfra,
|
||||
rejectUnauthorized: z.boolean().default(true),
|
||||
});
|
||||
|
||||
export const teamSchema = z.object({
|
||||
|
||||
Reference in new issue
Block a user