This commit is contained in:
2026-04-10 00:57:45 +03:30
parent 0d02031001
commit 76362309c8
4 changed files with 9 additions and 194 deletions
@@ -16,9 +16,6 @@ import CustomChip from "@core/components/mui/Chip";
// Hook Imports // Hook Imports
import useVerticalNav from "@menu/hooks/useVerticalNav"; import useVerticalNav from "@menu/hooks/useVerticalNav";
import { useFarmHub } from "@/hooks/useFarmHub";
import { useFarmAccessProfile } from "@/hooks/useFarmAccessProfile";
import { hasAccessByRule } from "@/libs/api/services/accessControlService";
// Styled Component Imports // Styled Component Imports
import StyledVerticalNavExpandIcon from "@menu/styles/vertical/StyledVerticalNavExpandIcon"; import StyledVerticalNavExpandIcon from "@menu/styles/vertical/StyledVerticalNavExpandIcon";
@@ -55,10 +52,6 @@ const VerticalMenu = ({ scrollMenu }: Props) => {
const t = useTranslations('navigation') const t = useTranslations('navigation')
const theme = useTheme(); const theme = useTheme();
const verticalNavOptions = useVerticalNav(); const verticalNavOptions = useVerticalNav();
const { farmHub } = useFarmHub();
const farmUuid = farmHub?.farm_uuid ?? null;
const { profile } = useFarmAccessProfile(farmUuid);
const canShowSensor7Menu = hasAccessByRule(profile, "sensor-7-page-access");
// Vars // Vars
const { isBreakpointReached, transitionDuration } = verticalNavOptions; const { isBreakpointReached, transitionDuration } = verticalNavOptions;
@@ -111,16 +104,12 @@ const VerticalMenu = ({ scrollMenu }: Props) => {
<MenuItem href="/crop-zoning" icon={<i className="tabler-map-2" />}> <MenuItem href="/crop-zoning" icon={<i className="tabler-map-2" />}>
{t('cropZoning')} {t('cropZoning')}
</MenuItem> </MenuItem>
</MenuSection>
{canShowSensor7Menu && (
<MenuSection label={t('sesnorSection')}>
<MenuItem href="/solid-sensor" icon={<i className="tabler-sensor" />}>
Sensor 7
</MenuItem>
</MenuSection> </MenuSection>
)} <MenuSection label={t('sesnorSection')}>
<MenuItem href="/solid-sensor" icon={<i className="tabler-sensor" />}>
Sensor 7
</MenuItem>
</MenuSection>
<MenuSection label={t('simulator')}> <MenuSection label={t('simulator')}>
<MenuItem href="/plant-simulator" icon={<i className="tabler-flower" />}> <MenuItem href="/plant-simulator" icon={<i className="tabler-flower" />}>
-60
View File
@@ -1,60 +0,0 @@
"use client";
import { useEffect, useState } from "react";
import type { ApiError } from "@/libs/api/client";
import {
accessControlService,
type FarmAccessProfile,
} from "@/libs/api/services/accessControlService";
interface UseFarmAccessProfileResult {
profile: FarmAccessProfile | null;
isLoading: boolean;
error: ApiError | null;
}
export const useFarmAccessProfile = (
farmUuid: string | null | undefined,
): UseFarmAccessProfileResult => {
const [profile, setProfile] = useState<FarmAccessProfile | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(Boolean(farmUuid));
const [error, setError] = useState<ApiError | null>(null);
useEffect(() => {
let active = true;
if (!farmUuid) {
setProfile(null);
setError(null);
setIsLoading(false);
return () => {
active = false;
};
}
setIsLoading(true);
setError(null);
accessControlService
.getFarmAccessProfile(farmUuid)
.then((nextProfile) => {
if (!active) return;
setProfile(nextProfile);
})
.catch((nextError: ApiError) => {
if (!active) return;
setProfile(null);
setError(nextError);
})
.finally(() => {
if (!active) return;
setIsLoading(false);
});
return () => {
active = false;
};
}, [farmUuid]);
return { profile, isLoading, error };
};
@@ -1,75 +0,0 @@
import { apiClient } from "../client";
export interface AccessMatchedRule {
code: string;
name: string;
effect: "allow" | "deny" | string;
priority: number;
}
export interface AccessSubscriptionPlan {
uuid: string;
code: string;
name: string;
}
export interface FarmAccessProfile {
farm_uuid: string;
subscription_plan?: AccessSubscriptionPlan | null;
matched_rules: AccessMatchedRule[];
resolved_from_profile: boolean;
}
interface AccessProfileEnvelope {
code?: number;
msg?: string;
data?: FarmAccessProfile;
}
const ACCESS_PREFIX = "/api/access-control/farms";
export const accessControlService = {
async getFarmAccessProfile(farmUuid: string): Promise<FarmAccessProfile> {
const response = await apiClient.get<AccessProfileEnvelope | FarmAccessProfile>(
`${ACCESS_PREFIX}/${encodeURIComponent(farmUuid)}/profile/`,
);
const payload =
response && typeof response === "object" && "data" in response
? response.data
: response;
return {
farm_uuid: payload?.farm_uuid ?? farmUuid,
subscription_plan: payload?.subscription_plan ?? null,
matched_rules: Array.isArray(payload?.matched_rules)
? payload.matched_rules
: [],
resolved_from_profile: Boolean(payload?.resolved_from_profile),
};
},
};
export const hasAccessByRule = (
profile: FarmAccessProfile | null,
requiredRuleCode: string,
): boolean => {
if (!profile) return false;
const relevantRules = profile.matched_rules.filter((rule) =>
rule.code === requiredRuleCode,
);
if (!relevantRules.length) return false;
const sortedRules = [...relevantRules].sort((left, right) => {
if (right.priority !== left.priority) {
return right.priority - left.priority;
}
if (left.effect === right.effect) return 0;
return left.effect === "deny" ? -1 : 1;
});
return sortedRules[0].effect === "allow";
};
@@ -31,9 +31,7 @@ import Typography from "@mui/material/Typography";
import { alpha, useTheme } from "@mui/material/styles"; import { alpha, useTheme } from "@mui/material/styles";
import { useFarmHub } from "@/hooks/useFarmHub"; import { useFarmHub } from "@/hooks/useFarmHub";
import { useFarmAccessProfile } from "@/hooks/useFarmAccessProfile";
import type { ApiError } from "@/libs/api/client"; import type { ApiError } from "@/libs/api/client";
import { hasAccessByRule } from "@/libs/api/services/accessControlService";
import { import {
sensorExternalApiService, sensorExternalApiService,
type SensorExternalCatalog, type SensorExternalCatalog,
@@ -41,7 +39,6 @@ import {
type SensorExternalRequestLog, type SensorExternalRequestLog,
} from "@/libs/api/services/sensorExternalApiService"; } from "@/libs/api/services/sensorExternalApiService";
const SENSOR_7_ACCESS_RULE = "sensor-7-page-access";
const PAGE_SIZE_OPTIONS = [10, 20, 50, 100]; const PAGE_SIZE_OPTIONS = [10, 20, 50, 100];
const DEFAULT_PAGE_SIZE = 20; const DEFAULT_PAGE_SIZE = 20;
@@ -337,8 +334,6 @@ const Sensor7Page = () => {
const theme = useTheme(); const theme = useTheme();
const { farmHub } = useFarmHub(); const { farmHub } = useFarmHub();
const farmUuid = farmHub?.farm_uuid ?? null; const farmUuid = farmHub?.farm_uuid ?? null;
const { profile, isLoading, error } = useFarmAccessProfile(farmUuid);
const canAccessSensor7 = hasAccessByRule(profile, SENSOR_7_ACCESS_RULE);
const [logs, setLogs] = useState<SensorExternalRequestLog[]>([]); const [logs, setLogs] = useState<SensorExternalRequestLog[]>([]);
const [count, setCount] = useState(0); const [count, setCount] = useState(0);
@@ -378,7 +373,7 @@ const Sensor7Page = () => {
const loadLogs = useCallback( const loadLogs = useCallback(
async (targetPage = page, targetPageSize = pageSize) => { async (targetPage = page, targetPageSize = pageSize) => {
if (!farmUuid || !canAccessSensor7) { if (!farmUuid) {
setLogs([]); setLogs([]);
setCount(0); setCount(0);
setSelectedLogId(null); setSelectedLogId(null);
@@ -431,7 +426,7 @@ const Sensor7Page = () => {
} }
} }
}, },
[canAccessSensor7, farmUuid, page, pageSize], [farmUuid, page, pageSize],
); );
useEffect(() => { useEffect(() => {
@@ -444,9 +439,9 @@ const Sensor7Page = () => {
}, [farmUuid]); }, [farmUuid]);
useEffect(() => { useEffect(() => {
if (!farmUuid || !canAccessSensor7) return; if (!farmUuid) return;
void loadLogs(page, pageSize); void loadLogs(page, pageSize);
}, [canAccessSensor7, farmUuid, loadLogs, page, pageSize]); }, [farmUuid, loadLogs, page, pageSize]);
const handlePageSizeChange = (event: SelectChangeEvent<number>) => { const handlePageSizeChange = (event: SelectChangeEvent<number>) => {
const nextPageSize = Number(event.target.value); const nextPageSize = Number(event.target.value);
@@ -457,14 +452,6 @@ const Sensor7Page = () => {
const heroBorder = alpha(theme.palette.primary.main, 0.18); const heroBorder = alpha(theme.palette.primary.main, 0.18);
const heroGlow = alpha(theme.palette.primary.main, 0.2); const heroGlow = alpha(theme.palette.primary.main, 0.2);
if (isLoading) {
return (
<Box className="flex items-center justify-center min-h-[320px]">
<CircularProgress size={30} />
</Box>
);
}
if (!farmUuid) { if (!farmUuid) {
return ( return (
<Card> <Card>
@@ -478,32 +465,6 @@ const Sensor7Page = () => {
); );
} }
if (error) {
return (
<Card>
<CardContent className="p-6">
<Typography variant="h5">مانیتورینگ سنسور خارجی</Typography>
<Typography color="error" className="mt-2">
{resolveErrorMessage(error, "خطا در دریافت سطح دسترسی مزرعه.")}
</Typography>
</CardContent>
</Card>
);
}
if (!canAccessSensor7) {
return (
<Card>
<CardContent className="p-6">
<Typography variant="h5">مانیتورینگ سنسور خارجی</Typography>
<Typography color="text.secondary" className="mt-2">
شما به این صفحه دسترسی ندارید.
</Typography>
</CardContent>
</Card>
);
}
const metrics = [ const metrics = [
{ {
icon: "tabler-database", icon: "tabler-database",