feat(master/manager/device): Enhance device management with new detail view, API updates, and improved request handling
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -14,3 +14,4 @@
|
|||||||
.turbopack
|
.turbopack
|
||||||
/dist
|
/dist
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
/wdoc
|
||||||
@@ -22,7 +22,7 @@ export default defineConfig({
|
|||||||
name: 'gms.monitor',
|
name: 'gms.monitor',
|
||||||
icon: 'icon-monitor',
|
icon: 'icon-monitor',
|
||||||
path: '/monitor',
|
path: '/monitor',
|
||||||
component: './Slave/GMS/Monitor',
|
component: './Slave/Spole/Monitor',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
...managerRouteBase,
|
...managerRouteBase,
|
||||||
|
|||||||
@@ -102,7 +102,9 @@ export const handleRequestConfig: RequestConfig = {
|
|||||||
...options,
|
...options,
|
||||||
headers: {
|
headers: {
|
||||||
...options.headers,
|
...options.headers,
|
||||||
...(token ? { Authorization: `${token}` } : {}),
|
...(token && !options.headers.Authorization
|
||||||
|
? { Authorization: `${token}` }
|
||||||
|
: {}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -86,7 +86,9 @@ export const handleRequestConfig: RequestConfig = {
|
|||||||
...options,
|
...options,
|
||||||
headers: {
|
headers: {
|
||||||
...options.headers,
|
...options.headers,
|
||||||
...(token ? { Authorization: `${token}` } : {}),
|
...(token && !options.headers.Authorization
|
||||||
|
? { Authorization: `${token}` }
|
||||||
|
: {}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -39,6 +39,10 @@ export const commonManagerRoutes = [
|
|||||||
path: '/manager/devices',
|
path: '/manager/devices',
|
||||||
component: './Manager/Device',
|
component: './Manager/Device',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/manager/devices/:thingId',
|
||||||
|
component: './Manager/Device/Detail',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ export const layout: RunTimeLayoutConfig = ({ initialState }) => {
|
|||||||
contentWidth: 'Fluid',
|
contentWidth: 'Fluid',
|
||||||
navTheme: isDark ? 'realDark' : 'light',
|
navTheme: isDark ? 'realDark' : 'light',
|
||||||
splitMenus: true,
|
splitMenus: true,
|
||||||
iconfontUrl: '//at.alicdn.com/t/c/font_5096559_pwy498d2aw.js',
|
iconfontUrl: '//at.alicdn.com/t/c/font_5096559_gjd5149497o.js',
|
||||||
contentStyle: {
|
contentStyle: {
|
||||||
padding: 0,
|
padding: 0,
|
||||||
margin: 0,
|
margin: 0,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { createFromIconfontCN } from '@ant-design/icons';
|
import { createFromIconfontCN } from '@ant-design/icons';
|
||||||
|
|
||||||
const IconFont = createFromIconfontCN({
|
const IconFont = createFromIconfontCN({
|
||||||
scriptUrl: '//at.alicdn.com/t/c/font_5096559_pwy498d2aw.js',
|
scriptUrl: '//at.alicdn.com/t/c/font_5096559_gjd5149497o.js',
|
||||||
});
|
});
|
||||||
|
|
||||||
export default IconFont;
|
export default IconFont;
|
||||||
|
|||||||
69
src/components/shared/TooltipIconFontButton.tsx
Normal file
69
src/components/shared/TooltipIconFontButton.tsx
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import type { ButtonProps } from 'antd';
|
||||||
|
import { Button, Tooltip } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
import IconFont from '../IconFont';
|
||||||
|
|
||||||
|
type TooltipIconFontButtonProps = {
|
||||||
|
tooltip?: string;
|
||||||
|
iconFontName: string;
|
||||||
|
color?: string;
|
||||||
|
onClick?: () => void;
|
||||||
|
} & Omit<ButtonProps, 'icon' | 'color'>;
|
||||||
|
|
||||||
|
const TooltipIconFontButton: React.FC<TooltipIconFontButtonProps> = ({
|
||||||
|
tooltip,
|
||||||
|
iconFontName,
|
||||||
|
color,
|
||||||
|
onClick,
|
||||||
|
...buttonProps
|
||||||
|
}) => {
|
||||||
|
const wrapperClassName = `tooltip-iconfont-wrapper-${color?.replace(
|
||||||
|
/[^a-zA-Z0-9]/g,
|
||||||
|
'-',
|
||||||
|
)}`;
|
||||||
|
const icon = (
|
||||||
|
<IconFont
|
||||||
|
type={iconFontName}
|
||||||
|
style={{ color: color || 'black' }}
|
||||||
|
className={wrapperClassName}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (tooltip) {
|
||||||
|
return (
|
||||||
|
<Tooltip title={tooltip}>
|
||||||
|
<Button
|
||||||
|
onClick={onClick}
|
||||||
|
icon={icon}
|
||||||
|
{...buttonProps}
|
||||||
|
style={
|
||||||
|
color
|
||||||
|
? {
|
||||||
|
...buttonProps.style,
|
||||||
|
['--icon-button-color' as string]: color,
|
||||||
|
}
|
||||||
|
: buttonProps.style
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
onClick={onClick}
|
||||||
|
icon={icon}
|
||||||
|
{...buttonProps}
|
||||||
|
style={
|
||||||
|
color
|
||||||
|
? {
|
||||||
|
...buttonProps.style,
|
||||||
|
['--icon-button-color' as string]: color,
|
||||||
|
}
|
||||||
|
: buttonProps.style
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TooltipIconFontButton;
|
||||||
@@ -76,3 +76,29 @@
|
|||||||
.table-row-select tbody tr:hover {
|
.table-row-select tbody tr:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Target icon inside the color wrapper - higher specificity
|
||||||
|
:local(.tooltip-iconfont-wrapper) .iconfont,
|
||||||
|
.tooltip-iconfont-wrapper .iconfont,
|
||||||
|
.tooltip-iconfont-wrapper svg {
|
||||||
|
color: currentcolor !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Even more specific - target within Button
|
||||||
|
.ant-btn .tooltip-iconfont-wrapper .iconfont {
|
||||||
|
color: currentcolor !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Most aggressive - global selector
|
||||||
|
:global {
|
||||||
|
.ant-btn .tooltip-iconfont-wrapper .iconfont,
|
||||||
|
.ant-btn .tooltip-iconfont-wrapper svg {
|
||||||
|
color: currentcolor !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use CSS variable approach
|
||||||
|
.ant-btn[style*='--icon-button-color'] .iconfont,
|
||||||
|
.ant-btn[style*='--icon-button-color'] svg {
|
||||||
|
color: var(--icon-button-color) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export const API_ALARMS_CONFIRM = '/api/alarms/confirm';
|
|||||||
// Thing API Constants
|
// Thing API Constants
|
||||||
export const API_THINGS_SEARCH = '/api/things/search';
|
export const API_THINGS_SEARCH = '/api/things/search';
|
||||||
export const API_THING_POLICY = '/api/things/policy2';
|
export const API_THING_POLICY = '/api/things/policy2';
|
||||||
export const API_SHARE_THING = '/api/things';
|
export const API_THING = '/api/things';
|
||||||
|
|
||||||
// Group API Constants
|
// Group API Constants
|
||||||
export const API_GROUPS = '/api/groups';
|
export const API_GROUPS = '/api/groups';
|
||||||
@@ -17,7 +17,7 @@ export const API_GROUP_MEMBERS = '/api/members';
|
|||||||
export const API_GROUP_CHILDREN = '/api/groups';
|
export const API_GROUP_CHILDREN = '/api/groups';
|
||||||
|
|
||||||
// Log API Constants
|
// Log API Constants
|
||||||
export const API_LOGS = '/api/reader/channels';
|
export const API_READER = '/api/reader/channels';
|
||||||
|
|
||||||
// User API Constants
|
// User API Constants
|
||||||
export const API_USERS = '/api/users';
|
export const API_USERS = '/api/users';
|
||||||
|
|||||||
@@ -2,4 +2,5 @@ export const ROUTE_LOGIN = '/login';
|
|||||||
export const ROUTER_HOME = '/';
|
export const ROUTER_HOME = '/';
|
||||||
export const ROUTE_PROFILE = '/profile';
|
export const ROUTE_PROFILE = '/profile';
|
||||||
export const ROUTE_MANAGER_USERS = '/manager/users';
|
export const ROUTE_MANAGER_USERS = '/manager/users';
|
||||||
|
export const ROUTE_MANAGER_DEVICES = '/manager/devices';
|
||||||
export const ROUTE_MANAGER_USERS_PERMISSIONS = 'permissions';
|
export const ROUTE_MANAGER_USERS_PERMISSIONS = 'permissions';
|
||||||
|
|||||||
34
src/pages/Manager/Device/Detail/components/ThingTitle.tsx
Normal file
34
src/pages/Manager/Device/Detail/components/ThingTitle.tsx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { getBadgeConnection } from '@/components/shared/ThingShared';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
|
import { Flex, Typography } from 'antd';
|
||||||
|
import moment from 'moment';
|
||||||
|
const { Text, Title } = Typography;
|
||||||
|
const ThingTitle = ({ thing }: { thing: MasterModel.Thing | null }) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
if (thing === null) {
|
||||||
|
return <Text>{intl.formatMessage({ id: 'common.undefined' })}</Text>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const connectionDuration = thing.metadata!.connected
|
||||||
|
? thing.metadata!.uptime! * 1000
|
||||||
|
: (Math.round(new Date().getTime() / 1000) -
|
||||||
|
thing.metadata!.updated_time!) *
|
||||||
|
1000;
|
||||||
|
return (
|
||||||
|
<Flex gap={10}>
|
||||||
|
<Title level={4} style={{ margin: 0 }}>
|
||||||
|
{thing.name || intl.formatMessage({ id: 'common.undefined' })}
|
||||||
|
</Title>
|
||||||
|
<Flex gap={5} align="center" justify="center">
|
||||||
|
{getBadgeConnection(thing.metadata!.connected || false)}
|
||||||
|
<Text type={thing.metadata?.connected ? undefined : 'secondary'}>
|
||||||
|
{connectionDuration > 0
|
||||||
|
? moment.duration(connectionDuration).humanize()
|
||||||
|
: ''}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ThingTitle;
|
||||||
90
src/pages/Manager/Device/Detail/index.tsx
Normal file
90
src/pages/Manager/Device/Detail/index.tsx
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import TooltipIconFontButton from '@/components/shared/TooltipIconFontButton';
|
||||||
|
import { ROUTER_HOME } from '@/constants/routes';
|
||||||
|
import { apiQueryMessage } from '@/services/master/MessageController';
|
||||||
|
import { apiGetThingDetail } from '@/services/master/ThingController';
|
||||||
|
import { PageContainer } from '@ant-design/pro-components';
|
||||||
|
import { history, useModel, useParams } from '@umijs/max';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import ThingTitle from './components/ThingTitle';
|
||||||
|
|
||||||
|
const DetailDevicePage = () => {
|
||||||
|
const { thingId } = useParams();
|
||||||
|
const [thing, setThing] = useState<MasterModel.Thing | null>(null);
|
||||||
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
|
const { initialState } = useModel('@@initialState');
|
||||||
|
const getThingDetail = async () => {
|
||||||
|
setIsLoading(true);
|
||||||
|
try {
|
||||||
|
const thing = await apiGetThingDetail(thingId || '');
|
||||||
|
setThing(thing);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error when get Thing: ', error);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const getDeviceConfig = async () => {
|
||||||
|
try {
|
||||||
|
const resp = await apiQueryMessage(
|
||||||
|
thing?.metadata?.data_channel_id || '',
|
||||||
|
initialState?.currentUserProfile?.metadata?.frontend_thing_key || '',
|
||||||
|
{
|
||||||
|
offset: 0,
|
||||||
|
limit: 1,
|
||||||
|
subtopic: `config.${thing?.metadata?.type}.node`,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
console.log('Device Config:', resp.messages![0].string_value_parsed);
|
||||||
|
} catch (error) {}
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
if (thingId) {
|
||||||
|
getThingDetail();
|
||||||
|
}
|
||||||
|
}, [thingId]);
|
||||||
|
useEffect(() => {
|
||||||
|
if (thing) {
|
||||||
|
getDeviceConfig();
|
||||||
|
}
|
||||||
|
}, [thing]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageContainer
|
||||||
|
title={isLoading ? 'Loading...' : <ThingTitle thing={thing} />}
|
||||||
|
header={{
|
||||||
|
onBack: () => history.push(ROUTER_HOME),
|
||||||
|
breadcrumb: undefined,
|
||||||
|
}}
|
||||||
|
extra={[
|
||||||
|
<TooltipIconFontButton
|
||||||
|
key="logs"
|
||||||
|
tooltip="Nhật ký"
|
||||||
|
iconFontName="icon-system-diary"
|
||||||
|
shape="circle"
|
||||||
|
size="middle"
|
||||||
|
onClick={() => {}}
|
||||||
|
/>,
|
||||||
|
<TooltipIconFontButton
|
||||||
|
key="notifications"
|
||||||
|
tooltip="Thông báo"
|
||||||
|
iconFontName="icon-bell"
|
||||||
|
shape="circle"
|
||||||
|
size="middle"
|
||||||
|
onClick={() => {}}
|
||||||
|
/>,
|
||||||
|
<TooltipIconFontButton
|
||||||
|
key="settings"
|
||||||
|
tooltip="Cài đặt"
|
||||||
|
iconFontName="icon-setting-device"
|
||||||
|
shape="circle"
|
||||||
|
size="middle"
|
||||||
|
onClick={() => {}}
|
||||||
|
/>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
Thing ID: {thingId}
|
||||||
|
</PageContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DetailDevicePage;
|
||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
} from '@/components/shared/ThingShared';
|
} from '@/components/shared/ThingShared';
|
||||||
import TreeGroup from '@/components/shared/TreeGroup';
|
import TreeGroup from '@/components/shared/TreeGroup';
|
||||||
import { DEFAULT_PAGE_SIZE } from '@/constants';
|
import { DEFAULT_PAGE_SIZE } from '@/constants';
|
||||||
|
import { ROUTE_MANAGER_DEVICES } from '@/constants/routes';
|
||||||
import { apiSearchThings } from '@/services/master/ThingController';
|
import { apiSearchThings } from '@/services/master/ThingController';
|
||||||
import {
|
import {
|
||||||
ActionType,
|
ActionType,
|
||||||
@@ -12,12 +13,12 @@ import {
|
|||||||
ProColumns,
|
ProColumns,
|
||||||
ProTable,
|
ProTable,
|
||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
import { FormattedMessage, useIntl } from '@umijs/max';
|
import { FormattedMessage, history, useIntl } from '@umijs/max';
|
||||||
import { Flex, Grid, theme, Typography } from 'antd';
|
import { Flex, Grid, theme, Typography } from 'antd';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import React, { useRef, useState } from 'react';
|
import React, { useRef, useState } from 'react';
|
||||||
import { TagStateCallbackPayload } from '../../SGW/Map/type';
|
import { TagStateCallbackPayload } from '../../SGW/Map/type';
|
||||||
const { Text } = Typography;
|
const { Text, Link } = Typography;
|
||||||
const SpoleHome: React.FC = () => {
|
const SpoleHome: React.FC = () => {
|
||||||
const { useBreakpoint } = Grid;
|
const { useBreakpoint } = Grid;
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
@@ -39,9 +40,6 @@ const SpoleHome: React.FC = () => {
|
|||||||
{
|
{
|
||||||
key: 'name',
|
key: 'name',
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
title: (
|
|
||||||
<FormattedMessage id="master.devices.name" defaultMessage="Name" />
|
|
||||||
),
|
|
||||||
tip: intl.formatMessage({
|
tip: intl.formatMessage({
|
||||||
id: 'master.devices.name.tip',
|
id: 'master.devices.name.tip',
|
||||||
defaultMessage: 'The device name',
|
defaultMessage: 'The device name',
|
||||||
@@ -49,6 +47,20 @@ const SpoleHome: React.FC = () => {
|
|||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
hideInSearch: true,
|
hideInSearch: true,
|
||||||
copyable: true,
|
copyable: true,
|
||||||
|
render: (_, row) => {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
copyable
|
||||||
|
onClick={() => history.push(`${ROUTE_MANAGER_DEVICES}/${row.id}`)}
|
||||||
|
>
|
||||||
|
{row.name ||
|
||||||
|
intl.formatMessage({
|
||||||
|
id: 'common.undefined',
|
||||||
|
defaultMessage: 'Undefined',
|
||||||
|
})}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'connected',
|
key: 'connected',
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { API_LOGS } from '@/constants/api';
|
import { API_READER } from '@/constants/api';
|
||||||
import { request } from '@umijs/max';
|
import { request } from '@umijs/max';
|
||||||
|
|
||||||
export async function apiQueryLogs(
|
export async function apiQueryLogs(
|
||||||
params: MasterModel.SearchLogPaginationBody,
|
params: MasterModel.SearchLogPaginationBody,
|
||||||
type: MasterModel.LogTypeRequest,
|
type: MasterModel.LogTypeRequest,
|
||||||
) {
|
) {
|
||||||
return request<MasterModel.LogResponse>(`${API_LOGS}/${type}/messages`, {
|
return request<MasterModel.LogResponse>(`${API_READER}/${type}/messages`, {
|
||||||
params: params,
|
params: params,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
148
src/services/master/MessageController.ts
Normal file
148
src/services/master/MessageController.ts
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
import { API_READER } from '@/constants/api';
|
||||||
|
import { request } from '@umijs/max';
|
||||||
|
|
||||||
|
// Transform functions
|
||||||
|
function transformEntityConfigChildDetail(
|
||||||
|
raw: MasterModel.PurpleC,
|
||||||
|
): MasterModel.EntityConfigChildDetail {
|
||||||
|
return {
|
||||||
|
entityId: raw.eid || '',
|
||||||
|
value: raw.v,
|
||||||
|
operation: raw.op,
|
||||||
|
duration: raw.for,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function transformEntityConfigChild(
|
||||||
|
raw: MasterModel.CElement,
|
||||||
|
): MasterModel.EntityConfigChild {
|
||||||
|
return {
|
||||||
|
type: raw.t || '',
|
||||||
|
children: raw.c ? [transformEntityConfigChildDetail(raw.c)] : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function transformEntityConfig(raw: MasterModel.EC): MasterModel.EntityConfig {
|
||||||
|
return {
|
||||||
|
level: raw.l as 0 | 1 | 2 | undefined,
|
||||||
|
normalCondition: raw.nc,
|
||||||
|
subType: raw.st,
|
||||||
|
children: raw.c?.map(transformEntityConfigChild),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function transformEntity(raw: MasterModel.E): MasterModel.Entity {
|
||||||
|
return {
|
||||||
|
entityId: raw.eid || '',
|
||||||
|
type: raw.t || '',
|
||||||
|
name: raw.n || '',
|
||||||
|
active: raw.a,
|
||||||
|
value: raw.v,
|
||||||
|
config: raw.c ? [transformEntityConfig(raw.c)] : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function transformNodeConfig(
|
||||||
|
raw: MasterModel.RawNodeConfig,
|
||||||
|
): MasterModel.NodeConfig {
|
||||||
|
return {
|
||||||
|
nodeId: raw.nid || '',
|
||||||
|
type: raw.t || '',
|
||||||
|
name: raw.n || '',
|
||||||
|
entities: raw.e?.map(transformEntity) || [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reverse transform functions
|
||||||
|
function reverseTransformEntityConfigChildDetail(
|
||||||
|
detail: MasterModel.EntityConfigChildDetail,
|
||||||
|
): MasterModel.PurpleC {
|
||||||
|
return {
|
||||||
|
eid: detail.entityId,
|
||||||
|
v: detail.value,
|
||||||
|
op: detail.operation,
|
||||||
|
for: detail.duration,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function reverseTransformEntityConfigChild(
|
||||||
|
child: MasterModel.EntityConfigChild,
|
||||||
|
): MasterModel.CElement {
|
||||||
|
return {
|
||||||
|
t: child.type,
|
||||||
|
c: child.children?.[0]
|
||||||
|
? reverseTransformEntityConfigChildDetail(child.children[0])
|
||||||
|
: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function reverseTransformEntityConfig(
|
||||||
|
raw: MasterModel.EntityConfig,
|
||||||
|
): MasterModel.EC {
|
||||||
|
return {
|
||||||
|
l: raw.level,
|
||||||
|
nc: raw.normalCondition,
|
||||||
|
st: raw.subType,
|
||||||
|
c: raw.children?.map(reverseTransformEntityConfigChild),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function reverseTransformEntity(entity: MasterModel.Entity): MasterModel.E {
|
||||||
|
return {
|
||||||
|
eid: entity.entityId,
|
||||||
|
t: entity.type,
|
||||||
|
n: entity.name,
|
||||||
|
a: entity.active,
|
||||||
|
v: entity.value,
|
||||||
|
c: entity.config?.[0]
|
||||||
|
? reverseTransformEntityConfig(entity.config[0])
|
||||||
|
: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function transformRawNodeConfig(
|
||||||
|
node: MasterModel.NodeConfig,
|
||||||
|
): MasterModel.RawNodeConfig {
|
||||||
|
return {
|
||||||
|
nid: node.nodeId,
|
||||||
|
t: node.type,
|
||||||
|
n: node.name,
|
||||||
|
e: node.entities.map(reverseTransformEntity),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function apiQueryMessage(
|
||||||
|
dataChanelId: string,
|
||||||
|
authorization: string,
|
||||||
|
params: MasterModel.SearchMessagePaginationBody,
|
||||||
|
) {
|
||||||
|
const resp = await request<MasterModel.MesageReaderResponse>(
|
||||||
|
`${API_READER}/${dataChanelId}/messages`,
|
||||||
|
{
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
Authorization: authorization,
|
||||||
|
},
|
||||||
|
params: params,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Process messages to add string_value_parsed
|
||||||
|
if (resp.messages) {
|
||||||
|
resp.messages = resp.messages.map((message) => {
|
||||||
|
if (message.string_value) {
|
||||||
|
try {
|
||||||
|
const rawNodeConfigs: MasterModel.RawNodeConfig[] = JSON.parse(
|
||||||
|
message.string_value,
|
||||||
|
);
|
||||||
|
message.string_value_parsed = rawNodeConfigs.map(transformNodeConfig);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to parse string_value:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
API_SHARE_THING,
|
API_THING,
|
||||||
API_THING_POLICY,
|
API_THING_POLICY,
|
||||||
API_THINGS_SEARCH,
|
API_THINGS_SEARCH,
|
||||||
} from '@/constants/api';
|
} from '@/constants/api';
|
||||||
@@ -55,7 +55,7 @@ export async function apiSearchThings(
|
|||||||
|
|
||||||
export async function apiUpdateThing(value: MasterModel.Thing) {
|
export async function apiUpdateThing(value: MasterModel.Thing) {
|
||||||
if (!value.id) throw new Error('Thing id is required');
|
if (!value.id) throw new Error('Thing id is required');
|
||||||
return request<MasterModel.Thing>(`${API_SHARE_THING}/${value.id}`, {
|
return request<MasterModel.Thing>(`${API_THING}/${value.id}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
data: value,
|
data: value,
|
||||||
});
|
});
|
||||||
@@ -77,7 +77,7 @@ export async function apiDeleteUserThingPolicy(
|
|||||||
thing_id: string,
|
thing_id: string,
|
||||||
user_id: string,
|
user_id: string,
|
||||||
) {
|
) {
|
||||||
return request(`${API_SHARE_THING}/${thing_id}/share`, {
|
return request(`${API_THING}/${thing_id}/share`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
data: {
|
data: {
|
||||||
policies: ['read', 'write', 'delete'],
|
policies: ['read', 'write', 'delete'],
|
||||||
@@ -91,7 +91,7 @@ export async function apiShareThingToUser(
|
|||||||
user_id: string,
|
user_id: string,
|
||||||
policies: string[],
|
policies: string[],
|
||||||
) {
|
) {
|
||||||
return request(`${API_SHARE_THING}/${thing_id}/share`, {
|
return request(`${API_THING}/${thing_id}/share`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
data: {
|
data: {
|
||||||
policies: policies,
|
policies: policies,
|
||||||
@@ -100,3 +100,7 @@ export async function apiShareThingToUser(
|
|||||||
getResponse: true,
|
getResponse: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function apiGetThingDetail(thing_id: string) {
|
||||||
|
return request<MasterModel.Thing>(`${API_THING}/${thing_id}`);
|
||||||
|
}
|
||||||
|
|||||||
3
src/services/master/typings/log.d.ts
vendored
3
src/services/master/typings/log.d.ts
vendored
@@ -8,7 +8,7 @@ declare namespace MasterModel {
|
|||||||
|
|
||||||
type LogTypeRequest = 'user_logs' | undefined;
|
type LogTypeRequest = 'user_logs' | undefined;
|
||||||
|
|
||||||
interface LogResponse {
|
interface MesageReaderResponse {
|
||||||
offset?: number;
|
offset?: number;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
publisher?: string;
|
publisher?: string;
|
||||||
@@ -27,5 +27,6 @@ declare namespace MasterModel {
|
|||||||
name?: string;
|
name?: string;
|
||||||
time?: number;
|
time?: number;
|
||||||
string_value?: string;
|
string_value?: string;
|
||||||
|
string_value_parsed?: NodeConfig[];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
101
src/services/master/typings/message.d.ts
vendored
Normal file
101
src/services/master/typings/message.d.ts
vendored
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
declare namespace MasterModel {
|
||||||
|
interface SearchMessagePaginationBody extends SearchPaginationBody {
|
||||||
|
subtopic?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RawNodeConfig {
|
||||||
|
nid?: string;
|
||||||
|
t?: string;
|
||||||
|
n?: string;
|
||||||
|
e?: E[];
|
||||||
|
}
|
||||||
|
interface E {
|
||||||
|
eid?: string;
|
||||||
|
t?: string;
|
||||||
|
n?: string;
|
||||||
|
a?: number;
|
||||||
|
v?: number;
|
||||||
|
c?: EC;
|
||||||
|
vs?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EC {
|
||||||
|
l?: number;
|
||||||
|
nc?: number;
|
||||||
|
st?: string;
|
||||||
|
c?: CElement[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CElement {
|
||||||
|
t?: string;
|
||||||
|
c?: PurpleC;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PurpleC {
|
||||||
|
eid?: string;
|
||||||
|
v?: number;
|
||||||
|
op?: number;
|
||||||
|
for?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interface Tranformed RawNodeConfig
|
||||||
|
|
||||||
|
interface NodeConfig {
|
||||||
|
/** Node ID - Định danh duy nhất */
|
||||||
|
nodeId: string;
|
||||||
|
/** Type - Loại thiết bị */
|
||||||
|
type: string;
|
||||||
|
/** Name - Tên hiển thị */
|
||||||
|
name: string;
|
||||||
|
/** Entities - Danh sách cảm biến */
|
||||||
|
entities: Entity[];
|
||||||
|
}
|
||||||
|
interface Entity {
|
||||||
|
/** Entity ID - Định danh duy nhất của cảm biến */
|
||||||
|
entityId: string;
|
||||||
|
/** Type - Loại cảm biến (vd: 'bin' - nhị phân 0/1, 'bin_t' - nhị phân có trigger) */
|
||||||
|
type: string;
|
||||||
|
/** Name - Tên hiển thị */
|
||||||
|
name: string;
|
||||||
|
/** Active - Đã kích hoạt cảm biến này hay chưa (1=đã kích hoạt, 0=chưa kích hoạt) */
|
||||||
|
active?: number;
|
||||||
|
/** Value - Giá trị hiện tại (1=có, 0=không) */
|
||||||
|
value?: number;
|
||||||
|
/** EntityConfig - Cấu hình bổ sung */
|
||||||
|
config?: EntityConfig[];
|
||||||
|
}
|
||||||
|
interface EntityConfig {
|
||||||
|
/** Level - Mức độ cảnh báo
|
||||||
|
0 = info,
|
||||||
|
1 = warning,
|
||||||
|
2 = critical */
|
||||||
|
level?: 0 | 1 | 2;
|
||||||
|
/** Normal Condition - Điều kiện bình thường */
|
||||||
|
normalCondition?: number;
|
||||||
|
/** SubType - Phân loại chi tiết */
|
||||||
|
subType?: string;
|
||||||
|
/** Children - Các cấu hình con */
|
||||||
|
children?: EntityConfigChild[];
|
||||||
|
}
|
||||||
|
interface EntityConfigChild {
|
||||||
|
/** Type - Loại điều kiện */
|
||||||
|
type: string;
|
||||||
|
children?: EntityConfigChildDetail[];
|
||||||
|
}
|
||||||
|
interface EntityConfigChildDetail {
|
||||||
|
/** entity ID - Cảm biến được theo dõi */
|
||||||
|
entityId: string;
|
||||||
|
/** Value - Ngưỡng giá trị */
|
||||||
|
value?: number;
|
||||||
|
/** Operation - Toán tử so sánh:
|
||||||
|
* 0 = == (bằng)
|
||||||
|
* 1 = != (khác)
|
||||||
|
* 2 = > (lớn hơn)
|
||||||
|
* 3 = >= (lớn hơn hoặc bằng)
|
||||||
|
* 4 = < (nhỏ hơn)
|
||||||
|
* 5 = <= (nhỏ hơn hoặc bằng) */
|
||||||
|
operation?: number;
|
||||||
|
/** Duration - Thời gian duy trì (giây) */
|
||||||
|
duration?: number;
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/services/master/typings/thing.d.ts
vendored
1
src/services/master/typings/thing.d.ts
vendored
@@ -30,6 +30,7 @@ declare namespace MasterModel {
|
|||||||
state_updated_time?: number;
|
state_updated_time?: number;
|
||||||
type?: string;
|
type?: string;
|
||||||
updated_time?: number;
|
updated_time?: number;
|
||||||
|
uptime?: number;
|
||||||
lat?: string;
|
lat?: string;
|
||||||
lng?: string;
|
lng?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user