| | |
| | | } |
| | | }, |
| | | enums: { |
| | | success: 'SUCCESS', |
| | | failure: 'FAILURE', |
| | | statusTrue: 'Enable', |
| | | statusFalse: 'Disable', |
| | | true: 'Yes', |
| | |
| | | } |
| | | } |
| | | }, |
| | | integrationRecord: { |
| | | enums: { |
| | | direction: { |
| | | none: 'Unknown', |
| | | inbound: 'Inbound', |
| | | outbound: 'Outbound', |
| | | }, |
| | | }, |
| | | }, |
| | | map: { |
| | | welcome: 'Welcome to the RCS System. Tip: Left-click to select objects, right-click to pan the view, and use the scroll wheel to zoom the view.', |
| | | devices: { |
| | |
| | | } |
| | | }, |
| | | enums: { |
| | | success: '成功', |
| | | failure: '失败', |
| | | statusTrue: '正常', |
| | | statusFalse: '禁用', |
| | | true: '是', |
| | |
| | | response: "响应内容", |
| | | err: "异常", |
| | | result: "结果", |
| | | costMs: "耗时(毫秒)", |
| | | costMs: "耗时(ms)", |
| | | }, |
| | | |
| | | } |
| | |
| | | } |
| | | } |
| | | }, |
| | | integrationRecord: { |
| | | enums: { |
| | | direction: { |
| | | none: '未知', |
| | | inbound: '接收', |
| | | outbound: '调用', |
| | | }, |
| | | }, |
| | | }, |
| | | map: { |
| | | welcome: '欢迎使用 RCS 系统。提示:鼠标左键选中对象,右键平移视图,滚轮缩放视图。', |
| | | devices: { |
| | |
| | | import DialogCloseButton from "../components/DialogCloseButton"; |
| | | import StatusSelectInput from "../components/StatusSelectInput"; |
| | | import MemoInput from "../components/MemoInput"; |
| | | import { buildDirectionChoices } from './direction'; |
| | | |
| | | const IntegrationRecordCreate = (props) => { |
| | | const { open, setOpen } = props; |
| | | |
| | | const translate = useTranslate(); |
| | | const notify = useNotify(); |
| | | const directionChoices = useMemo(() => buildDirectionChoices(translate), [translate]); |
| | | |
| | | const handleClose = (event, reason) => { |
| | | if (reason !== "backdropClick") { |
| | |
| | | <SelectInput |
| | | label="table.field.integrationRecord.direction" |
| | | source="direction" |
| | | choices={[ |
| | | { id: 1, name: '被调用' }, |
| | | { id: 2, name: '调用外部' }, |
| | | ]} |
| | | choices={directionChoices} |
| | | /> |
| | | </Grid> |
| | | <Grid item xs={6} display="flex" gap={1}> |
| | |
| | | import * as React from 'react'; |
| | | import { Labeled } from 'react-admin'; |
| | | import { Labeled, useTranslate } from 'react-admin'; |
| | | import { Box, Grid, Typography, Card, CardContent, TextField } from '@mui/material'; |
| | | import { format } from 'date-fns'; |
| | | import { getDirectionLabel } from './direction'; |
| | | |
| | | const IntegrationRecordDetail = (props) => { |
| | | const { integration } = props; |
| | | if (!integration) return null; |
| | | const translate = useTranslate(); |
| | | |
| | | const formatTimestamp = (timestamp) => { |
| | | if (!timestamp) return ''; |
| | |
| | | <Grid item xs={6}> |
| | | <Labeled label="table.field.integrationRecord.direction"> |
| | | <Typography variant="body2" flexWrap="nowrap"> |
| | | {integration.direction$ || ''} |
| | | {getDirectionLabel(translate, integration.direction)} |
| | | </Typography> |
| | | </Labeled> |
| | | </Grid> |
| | |
| | | import CustomerTopToolBar from "../components/EditTopToolBar"; |
| | | import MemoInput from "../components/MemoInput"; |
| | | import StatusSelectInput from "../components/StatusSelectInput"; |
| | | import { buildDirectionChoices } from './direction'; |
| | | |
| | | const FormToolbar = () => { |
| | | const { getValues } = useFormContext(); |
| | |
| | | |
| | | const IntegrationRecordEdit = () => { |
| | | const translate = useTranslate(); |
| | | const directionChoices = useMemo(() => buildDirectionChoices(translate), [translate]); |
| | | |
| | | return ( |
| | | <Edit |
| | |
| | | <SelectInput |
| | | label="table.field.integrationRecord.direction" |
| | | source="direction" |
| | | choices={[ |
| | | { id: 1, name: '被调用' }, |
| | | { id: 2, name: '调用外部' }, |
| | | ]} |
| | | choices={directionChoices} |
| | | validate={required()} |
| | | /> |
| | | </Stack> |
| | |
| | | import React, { useState } from "react"; |
| | | import React, { useState, useMemo } from "react"; |
| | | import { |
| | | List, |
| | | DatagridConfigurable, |
| | |
| | | TopToolbar, |
| | | SelectColumnsButton, |
| | | FilterButton, |
| | | useTranslate, |
| | | useRecordContext, |
| | | TextField, |
| | | NumberField, |
| | | DateField, |
| | | BooleanField, |
| | | ReferenceField, |
| | | TextInput, |
| | | DateInput, |
| | | SelectInput, |
| | |
| | | import { PAGE_DRAWER_WIDTH, DEFAULT_PAGE_SIZE } from '@/config/setting'; |
| | | import { format } from 'date-fns'; |
| | | import rowSx from './rowSx'; |
| | | import { buildDirectionChoices, getDirectionLabel } from './direction'; |
| | | |
| | | const StyledDatagrid = styled(DatagridConfigurable)(({ theme }) => ({ |
| | | '& .css-1vooibu-MuiSvgIcon-root': { |
| | |
| | | }, |
| | | })); |
| | | |
| | | const filters = [ |
| | | <SearchInput source="condition" alwaysOn />, |
| | | <DateInput label='common.time.after' source="timeStart" alwaysOn />, |
| | | <DateInput label='common.time.before' source="timeEnd" alwaysOn />, |
| | | |
| | | <TextInput source="uuid" label="table.field.integrationRecord.uuid" />, |
| | | <TextInput source="namespace" label="table.field.integrationRecord.namespace" />, |
| | | <TextInput source="url" label="table.field.integrationRecord.url" />, |
| | | <TextInput source="appkey" label="table.field.integrationRecord.appkey" />, |
| | | <TextInput source="caller" label="table.field.integrationRecord.caller" />, |
| | | <SelectInput source="direction" label="table.field.integrationRecord.direction" |
| | | choices={[ |
| | | { id: 1, name: '被调用' }, |
| | | { id: 2, name: '调用外部' }, |
| | | ]} |
| | | />, |
| | | <TextInput source="timestamp" label="table.field.integrationRecord.timestamp" />, |
| | | <TextInput source="clientIp" label="table.field.integrationRecord.clientIp" />, |
| | | <SelectInput source="result" label="table.field.integrationRecord.result" |
| | | choices={[ |
| | | { id: 1, name: '成功' }, |
| | | { id: 0, name: '失败' }, |
| | | ]} |
| | | />, |
| | | |
| | | <TextInput label="common.field.memo" source="memo" />, |
| | | ] |
| | | |
| | | const IntegrationRecordList = () => { |
| | | const translate = useTranslate(); |
| | | const [createDialog, setCreateDialog] = useState(false); |
| | | const [drawerVal, setDrawerVal] = useState(false); |
| | | const directionChoices = useMemo(() => buildDirectionChoices(translate), [translate]); |
| | | const filters = useMemo(() => [ |
| | | <SearchInput source="condition" alwaysOn />, |
| | | <DateInput label='common.time.after' source="timeStart" alwaysOn />, |
| | | <DateInput label='common.time.before' source="timeEnd" alwaysOn />, |
| | | <TextInput source="uuid" label="table.field.integrationRecord.uuid" />, |
| | | <TextInput source="namespace" label="table.field.integrationRecord.namespace" />, |
| | | <TextInput source="url" label="table.field.integrationRecord.url" />, |
| | | <TextInput source="appkey" label="table.field.integrationRecord.appkey" />, |
| | | <TextInput source="caller" label="table.field.integrationRecord.caller" />, |
| | | <SelectInput source="direction" |
| | | label="table.field.integrationRecord.direction" |
| | | choices={directionChoices} |
| | | alwaysOn |
| | | />, |
| | | // <TextInput source="timestamp" label="table.field.integrationRecord.timestamp" />, |
| | | <TextInput source="clientIp" label="table.field.integrationRecord.clientIp" />, |
| | | <SelectInput source="result" label="table.field.integrationRecord.result" |
| | | choices={[ |
| | | { id: 1, name: 'common.enums.success' }, |
| | | { id: 0, name: 'common.enums.failure' }, |
| | | ]} |
| | | alwaysOn |
| | | />, |
| | | <TextInput label="common.field.memo" source="memo" />, |
| | | ], [directionChoices]); |
| | | |
| | | return ( |
| | | <Box display="flex"> |
| | |
| | | <TextField source="url" label="table.field.integrationRecord.url" /> |
| | | <TextField source="appkey" label="table.field.integrationRecord.appkey" /> |
| | | <TextField source="caller" label="table.field.integrationRecord.caller" /> |
| | | <TextField source="direction" label="table.field.integrationRecord.direction" sortable={false} /> |
| | | <DirectionField source="direction" label="table.field.integrationRecord.direction" sortable={false} /> |
| | | <FormattedTimestampField source="timestamp" label="table.field.integrationRecord.timestamp" /> |
| | | <TextField source="clientIp" label="table.field.integrationRecord.clientIp" /> |
| | | <TextField source="request" label="table.field.integrationRecord.request" sortable={false} hidden={!!drawerVal} /> |
| | | <TextField source="response" label="table.field.integrationRecord.response" sortable={false} hidden={!!drawerVal} /> |
| | | <ResultField source="result" label="table.field.integrationRecord.result" /> |
| | | <ResultField source="result" label="table.field.integrationRecord.result" sortable={false} /> |
| | | <TextField source="err" label="table.field.integrationRecord.err" /> |
| | | <NumberField source="costMs" label="table.field.integrationRecord.costMs" sx={{ fontWeight: 'bold' }} /> |
| | | |
| | |
| | | }; |
| | | |
| | | const ResultField = ({ source }) => { |
| | | const translate = useTranslate(); |
| | | const record = useRecordContext(); |
| | | const val = record?.[source]; |
| | | return ( |
| | | <> |
| | | {val === 1 ? ( |
| | | <Chip label="success" color="success" variant="outlined" size="small" /> |
| | | <Chip label={translate("common.enums.success")} color="success" variant="outlined" size="small" /> |
| | | ) : ( |
| | | <Chip label="error" color="error" variant="outlined" size="small" /> |
| | | <Chip label={translate("common.enums.failure")} color="error" variant="outlined" size="small" /> |
| | | )} |
| | | </> |
| | | ); |
| | | }; |
| | | |
| | | const DirectionField = ({ source }) => { |
| | | const record = useRecordContext(); |
| | | const translate = useTranslate(); |
| | | const val = record?.[source]; |
| | | if (val === undefined || val === null) { |
| | | return null; |
| | | } |
| | | return ( |
| | | <Chip |
| | | label={getDirectionLabel(translate, val)} |
| | | variant="outlined" |
| | | size="small" |
| | | color="info" |
| | | /> |
| | | ); |
| | | }; |
| | | |
| | | export default IntegrationRecordList; |
| | |
| | | } from 'react-admin'; |
| | | import PanelTypography from "../components/PanelTypography"; |
| | | import * as Common from '@/utils/common' |
| | | import { getDirectionLabel } from './direction'; |
| | | |
| | | const IntegrationRecordPanel = () => { |
| | | const record = useRecordContext(); |
| | |
| | | <Grid item xs={6}> |
| | | <PanelTypography |
| | | title="table.field.integrationRecord.direction" |
| | | property={record.direction$} |
| | | property={getDirectionLabel(translate, record.direction)} |
| | | /> |
| | | </Grid> |
| | | <Grid item xs={6}> |
| New file |
| | |
| | | export const DIRECTION_OPTIONS = [ |
| | | { id: 0, labelKey: 'page.integrationRecord.enums.direction.none' }, |
| | | { id: 1, labelKey: 'page.integrationRecord.enums.direction.inbound' }, |
| | | { id: 2, labelKey: 'page.integrationRecord.enums.direction.outbound' }, |
| | | ]; |
| | | |
| | | export const buildDirectionChoices = (translate) => |
| | | DIRECTION_OPTIONS.map(option => ({ |
| | | id: option.id, |
| | | name: translate(option.labelKey), |
| | | })); |
| | | |
| | | export const getDirectionLabel = (translate, value) => { |
| | | if (value === null || value === undefined) { |
| | | return ''; |
| | | } |
| | | const numericValue = typeof value === 'number' ? value : Number(value); |
| | | if (Number.isNaN(numericValue)) { |
| | | return ''; |
| | | } |
| | | const option = DIRECTION_OPTIONS.find(opt => opt.id === numericValue); |
| | | if (!option) { |
| | | return ''; |
| | | } |
| | | return translate(option.labelKey); |
| | | }; |
| | |
| | | return record; |
| | | } |
| | | |
| | | private void applyResult(IntegrationRecord record, Object body, Exception failure) { |
| | | private void applyResult(IntegrationRecord record, Object responseBody, Exception failure) { |
| | | if (failure != null) { |
| | | record.setResult(0); |
| | | record.setErr(failure.getMessage()); |
| | | record.setErr("Request failed: " + failure.getMessage()); |
| | | return; |
| | | } |
| | | if (!(body instanceof R)) { |
| | | record.setResult(null); |
| | | record.setErr(null); |
| | | if (!(responseBody instanceof R)) { |
| | | record.setErr("Invalid response body structure. Expected: { code, msg, data }."); |
| | | return; |
| | | } |
| | | R response = (R) body; |
| | | Integer code = parseInteger(response.get("code")); |
| | | R r = (R) responseBody; |
| | | Integer code = parseInteger(r.get("code")); |
| | | if (code == null) { |
| | | record.setResult(null); |
| | | record.setErr(null); |
| | | record.setErr("Missing or invalid response field: code."); |
| | | return; |
| | | } |
| | | boolean success = code == 200; |
| | | record.setResult(success ? 1 : 0); |
| | | record.setErr(success ? null : String.valueOf(response.get("msg"))); |
| | | if (code == 200) { |
| | | record.setResult(1); |
| | | } |
| | | } |
| | | |
| | | private Integer parseInteger(Object codeObj) { |