UPDATE
This commit is contained in:
@@ -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;
|
||||||
@@ -112,15 +105,11 @@ const VerticalMenu = ({ scrollMenu }: Props) => {
|
|||||||
{t('cropZoning')}
|
{t('cropZoning')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</MenuSection>
|
</MenuSection>
|
||||||
{canShowSensor7Menu && (
|
|
||||||
|
|
||||||
<MenuSection label={t('sesnorSection')}>
|
<MenuSection label={t('sesnorSection')}>
|
||||||
|
|
||||||
<MenuItem href="/solid-sensor" icon={<i className="tabler-sensor" />}>
|
<MenuItem href="/solid-sensor" icon={<i className="tabler-sensor" />}>
|
||||||
Sensor 7
|
Sensor 7
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</MenuSection>
|
</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" />}>
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user