feat: integrate global policy API and implement data log page

This commit is contained in:
ningyv
2024-12-05 18:53:11 +08:00
parent 7d66f2628a
commit 84decf1310
10 changed files with 430 additions and 448 deletions
@@ -1,5 +1,5 @@
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { Radio, DatePicker, GetProps, RadioChangeEvent } from 'antd';
import dayjs, { Dayjs } from 'dayjs';
import customParseFormat from 'dayjs/plugin/customParseFormat';
@@ -12,64 +12,74 @@ export type RangeValue = [Dayjs | null, Dayjs | null] | null;
dayjs.extend(customParseFormat);
export type TimeRange = {
start:number|null
end:number|null
start: number | null
end: number | null
}
export type TimeRangeButton = ''| 'hour' | 'day' | 'threeDays' | 'sevenDays';
export type TimeRangeButton = '' | 'hour' | 'day' | 'threeDays' | 'sevenDays';
type TimeRangeSelectorProps = {
initialTimeButton?:TimeRangeButton,
initialDatePickerValue?:RangeValue
onTimeRangeChange?:(timeRange:TimeRange) =>void
hideTitle?:boolean
onTimeButtonChange:(time:TimeRangeButton) =>void
labelSize?:'small'|'default'
}
const TimeRangeSelector = (props:TimeRangeSelectorProps) => {
const {initialTimeButton,initialDatePickerValue,onTimeRangeChange,hideTitle,onTimeButtonChange,labelSize='default'} = props
initialTimeButton?: TimeRangeButton,
initialDatePickerValue?: RangeValue
onTimeRangeChange?: (timeRange: TimeRange) => void
hideTitle?: boolean
onTimeButtonChange: (time: TimeRangeButton) => void
labelSize?: 'small' | 'default'
bindRef?: any
}
const TimeRangeSelector = (props: TimeRangeSelectorProps) => {
const { initialTimeButton, initialDatePickerValue, onTimeRangeChange, hideTitle, onTimeButtonChange, labelSize = 'default', bindRef } = props
const [timeButton, setTimeButton] = useState(initialTimeButton || '');
const [datePickerValue, setDatePickerValue] = useState<RangeValue>(initialDatePickerValue || [null,null]);
const [datePickerValue, setDatePickerValue] = useState<RangeValue>(initialDatePickerValue || [null, null]);
useEffect(() => {
if (bindRef) {
bindRef({ reset });
}
}, [bindRef])
// 根据选择的时间范围计算开始和结束时间
const calculateTimeRange = (curBtn:'hour'|'day'|'threeDays'|'sevenDays') => {
const calculateTimeRange = (curBtn: 'hour' | 'day' | 'threeDays' | 'sevenDays') => {
const currentSecond = new Date().getTime() // 当前毫秒数时间戳
const currentMin = currentSecond - (currentSecond % (60 * 1000)) // 当前分钟数时间戳
let startMin = currentMin - 60 * 60 * 1000
switch (curBtn) {
case 'hour': {
startMin = currentMin - 60 * 60 * 1000
break
}
case 'day': {
startMin = currentMin - 24 * 60 * 60 * 1000
break
}
case 'threeDays': {
startMin =
new Date(new Date().setHours(0, 0, 0, 0)).getTime() -
2 * 24 * 60 * 60 * 1000
break
}
case 'sevenDays': {
startMin =
new Date(new Date().setHours(0, 0, 0, 0)).getTime() -
6 * 24 * 60 * 60 * 1000
break
}
case 'hour': {
startMin = currentMin - 60 * 60 * 1000
break
}
case 'day': {
startMin = currentMin - 24 * 60 * 60 * 1000
break
}
case 'threeDays': {
startMin =
new Date(new Date().setHours(0, 0, 0, 0)).getTime() -
2 * 24 * 60 * 60 * 1000
break
}
case 'sevenDays': {
startMin =
new Date(new Date().setHours(0, 0, 0, 0)).getTime() -
6 * 24 * 60 * 60 * 1000
break
}
}
if (onTimeRangeChange) {
onTimeRangeChange({ start: startMin / 1000, end: currentMin / 1000 });
onTimeRangeChange({ start: startMin / 1000, end: currentMin / 1000 });
}
};
// 处理单选按钮的变化
const handleRadioChange = (e:RadioChangeEvent) => {
const handleRadioChange = (e: RadioChangeEvent) => {
setTimeButton(e.target.value);
onTimeButtonChange?.(e.target.value)
setDatePickerValue(null)
calculateTimeRange(e.target.value);
};
const reset = () => {
setTimeButton('hour')
calculateTimeRange('hour')
setDatePickerValue(null)
}
// 处理日期选择器的变化
const handleDatePickerChange = (dates: RangeValue) => {
@@ -84,34 +94,37 @@ const TimeRangeSelector = (props:TimeRangeSelectorProps) => {
onTimeRangeChange({ start, end });
}
}
if (!dates) {
calculateTimeRange('hour')
}
};
const disabledDate: RangePickerProps['disabledDate'] = (current) => {
// Can not select days before today and today
const disabledDate: RangePickerProps['disabledDate'] = (current) => {
// Can not select days before today and today
return current && current.valueOf() > dayjs().startOf('day').valueOf();
};
};
return (
<div className="flex flex-nowrap items-center pt-btnybase mr-btnybase">
{!hideTitle && <label className={`whitespace-nowrap `}>{$t('时间')}</label>}
<Radio.Group className="whitespace-nowrap" value={timeButton} onChange={handleRadioChange} buttonStyle="solid">
<Radio.Button value="hour">{$t('近1小时')}</Radio.Button>
<Radio.Button value="day">{$t('近24小时')}</Radio.Button>
<Radio.Button value="threeDays">{$t('近3天')}</Radio.Button>
<Radio.Button className="rounded-e-none" value="sevenDays">{$t('近7天')}</Radio.Button>
</Radio.Group>
{!hideTitle && <label className={`whitespace-nowrap `}>{$t('时间')}</label>}
<Radio.Group className="whitespace-nowrap" value={timeButton} onChange={handleRadioChange} buttonStyle="solid">
<Radio.Button value="hour">{$t('近1小时')}</Radio.Button>
<Radio.Button value="day">{$t('近24小时')}</Radio.Button>
<Radio.Button value="threeDays">{$t('近3天')}</Radio.Button>
<Radio.Button className="rounded-e-none" value="sevenDays">{$t('近7天')}</Radio.Button>
</Radio.Group>
<DatePicker.RangePicker
value={datePickerValue}
className="rounded-s-none ml-[-1px]"
className="rounded-s-none ml-[-1px]"
disabledDate={disabledDate}
onChange={handleDatePickerChange}
onOpenChange={(open)=>{
if(!open && datePickerValue && datePickerValue.length > 2){
setTimeButton('')
onTimeButtonChange?.('')
}
onOpenChange={(open) => {
if (!open && datePickerValue && datePickerValue.length > 2) {
setTimeButton('')
onTimeButtonChange?.('')
}
}}
/>
</div>
@@ -13,6 +13,7 @@ export interface CodeboxApiRef {
formatCode: () => void
}
export type codeBoxLanguagesType = 'html' | 'json' | 'xml' | 'javascript' | 'css' | 'plaintext'|'yaml'
interface CodeboxProps {
options?: MonacoEditor.IStandaloneEditorConstructionOptions
value?: string
@@ -22,7 +23,7 @@ interface CodeboxProps {
height?: string | null
readOnly?: boolean
apiRef?: RefObject<CodeboxApiRef>
language?: 'html' | 'json' | 'xml' | 'javascript' | 'css' | 'plaintext'|'yaml'
language?: codeBoxLanguagesType
extraContent?:React.ReactNode
sx?:Record<string,unknown>
editorTheme?:'vs' | 'vs-dark' | 'hc-black'
@@ -1,3 +1,4 @@
import { codeBoxLanguagesType } from "@common/components/postcat/api/Codebox";
export const MatchRules = [
{ value: 'inner', label: '数据格式' },
@@ -54,4 +55,34 @@ export const StrategyStatusEnum = {
"offline":'text-status_fail',
"delete":'text-status_offline',
}
export const contentTypeToLanguageMap: Record<string, codeBoxLanguagesType> = {
// JSON
"application/json": "json",
// XML
"application/xml": "xml",
"text/xml": "xml",
// HTML
"text/html": "html",
// Plain text
"text/plain": "plaintext",
// JavaScript
"application/javascript": "javascript",
"text/javascript": "javascript",
// CSS
"text/css": "css",
// YAML
"application/x-yaml": "yaml",
"text/yaml": "yaml",
// Others (fallback)
"*/*": "plaintext", // 任意类型默认处理为普通文本
};
@@ -2,114 +2,133 @@
import { EntityItem } from "@common/const/type";
export type PartitionConfigFieldType = {
name?: string;
id?: string;
description?: string;
prefix?:string
url?:string
managerAddress?:string
canDelete?:boolean
name?: string;
id?: string;
description?: string;
prefix?: string
url?: string
managerAddress?: string
canDelete?: boolean
};
export type PartitionCertTableListItem = {
id:string;
name: string;
domains:string[];
notAfter:string;
notBefore:string;
updater:EntityItem;
updateTime:string;
id: string;
name: string;
domains: string[];
notAfter: string;
notBefore: string;
updater: EntityItem;
updateTime: string;
};
export type PartitionCertConfigFieldType = {
id?:string
key:string
pem:string
id?: string
key: string
pem: string
};
export type PartitionCertConfigProps = {
type:'add'|'edit'
entity?:PartitionCertConfigFieldType
type: 'add' | 'edit'
entity?: PartitionCertConfigFieldType
}
export type PartitionCertConfigHandle = {
save:()=>Promise<boolean|string>
save: () => Promise<boolean | string>
}
export type PartitionClusterFieldType = {
name?: string;
id?: string;
description?: string;
address?:string;
protocol?:'http'|'https'
name?: string;
id?: string;
description?: string;
address?: string;
protocol?: 'http' | 'https'
};
export type ClusterConfigProps = {
mode:'config' | 'retry' | 'result' | 'edit',
clusterId?:string
initFormValue?:{[k:string]:string|number}
mode: 'config' | 'retry' | 'result' | 'edit',
clusterId?: string
initFormValue?: { [k: string]: string | number }
}
export type ClusterConfigHandle = {
save:()=>Promise<boolean|string>
check:()=>Promise<boolean>
save: () => Promise<boolean | string>
check: () => Promise<boolean>
}
export type PartitionClusterTableListItem = {
id:string;
name: string;
status:0|1;
description:string;
id: string;
name: string;
status: 0 | 1;
description: string;
};
export type PartitionClusterNodeTableListItem = {
id:string;
name: string;
managerAddress:string[];
serviceAddress:string[];
peerAddress:string[];
status:0|1;
id: string;
name: string;
managerAddress: string[];
serviceAddress: string[];
peerAddress: string[];
status: 0 | 1;
};
export type PartitionClusterNodeModalTableListItem = {
id: string,
name: string,
managerAddress: [],
serviceAddress: [],
peerAddress: string,
status: string
id: string,
name: string,
managerAddress: [],
serviceAddress: [],
peerAddress: string,
status: string
}
export type NodeModalFieldType = {
address:string
address: string
}
export type NodeModalHandle = {
save:()=>void
save: () => void
}
export type NodeModalPropsType = {
changeStatus:(status:ClusterPageShowStatus)=>void
getClusterInfo:()=>void
status:ClusterPageShowStatus
changeStatus: (status: ClusterPageShowStatus) => void
getClusterInfo: () => void
status: ClusterPageShowStatus
}
export type ClusterPageShowStatus = 'view'|'preview'|'edit'
export type ClusterPageShowStatus = 'view' | 'preview' | 'edit'
export type PartitionTableListItem = {
id:string;
name: string;
clusterNum:number;
updater:EntityItem;
updateTime:string;
id: string;
name: string;
clusterNum: number;
updater: EntityItem;
updateTime: string;
};
export type SimplePartition = EntityItem & { clusters: (EntityItem & {description:string})[] }
export type SimplePartition = EntityItem & { clusters: (EntityItem & { description: string })[] }
export type PartitionDashboardConfigFieldType = {
driver:string
config:{
org:string
token:string
addr:string
driver: string
config: {
org: string
token: string
addr: string
}
}
export type PartitionDataLogConfigFieldType = {
create_time: string
id: string
update_time: string
updater: {
id: string
name: string
}
creator: {
id: string
name: string
}
config: {
headers: {
[k: string]: string
}
url: string
}
}
@@ -0,0 +1,18 @@
import { PartitionDataLogConfigFieldType } from "@core/const/partitions/types"
export type DashboardPageShowStatus = 'view'|'edit'
export type DashboardSettingEditProps = {
changeStatus:(status:DashboardPageShowStatus)=>void
refreshData:()=>void
data?:PartitionDataLogConfigFieldType
}
const DataLogSettingEdit = (props:DashboardSettingEditProps) => {
const {changeStatus,refreshData,data} = props
return (
<div>
222
</div>
);
}
export default DataLogSettingEdit;
@@ -1,93 +1,177 @@
import { FC, useEffect, useState} from "react";
import {useBreadcrumb} from "@common/contexts/BreadcrumbContext.tsx";
import {App, Button, Card, Col, Row, Spin, Tag} from "antd";
import {BasicResponse, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx";
import {useFetch} from "@common/hooks/http.ts";
import { FC, useEffect, useState } from "react";
import { useBreadcrumb } from "@common/contexts/BreadcrumbContext.tsx";
import { App, Button, Card, Col, Row, Spin, Tag } from "antd";
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from "@common/const/const.tsx";
import { useFetch } from "@common/hooks/http.ts";
import WithPermission from "@common/components/aoplatform/WithPermission.tsx";
import { LoadingOutlined } from "@ant-design/icons";
import InsidePage from "@common/components/aoplatform/InsidePage.tsx";
import { $t } from "@common/locales/index.ts";
import DashboardSettingEdit, { DashboardPageShowStatus } from "./DashboardSettingEdit.tsx";
import { PartitionDashboardConfigFieldType } from "@core/const/partitions/types.ts";
import { PartitionDashboardConfigFieldType, PartitionDataLogConfigFieldType } from "@core/const/partitions/types.ts";
import PageList from "@common/components/aoplatform/PageList.tsx";
import DataLogSettingEdit from "./DataLogSettingEdit.tsx";
const PartitionInsideDashboardSetting:FC = ()=> {
const {setBreadcrumb} = useBreadcrumb()
const {message} = App.useApp()
const {fetchData} = useFetch()
const [data, setData] = useState<PartitionDashboardConfigFieldType>()
const [loading, setLoading] = useState<boolean>(false)
const [showStatus, setShowStatus] = useState<DashboardPageShowStatus>('view')
const PartitionInsideDashboardSetting: FC = () => {
const { setBreadcrumb } = useBreadcrumb()
const { message } = App.useApp()
const { fetchData } = useFetch()
const [data, setData] = useState<PartitionDashboardConfigFieldType>()
const [dataLogData, setDataLogData] = useState<PartitionDataLogConfigFieldType>()
const [loading, setLoading] = useState<boolean>(false)
const [dataLogLoading, setDataLogLoading] = useState<boolean>(false)
const [showGraphStatus, setShowGraphStatus] = useState<DashboardPageShowStatus>('view')
const [showDataLogStatus, setShowDataLogStatus] = useState<DashboardPageShowStatus>('view')
const getDashboardSettingInfo = () => {
setLoading(true)
return fetchData<BasicResponse<{ nodes:PartitionDashboardConfigFieldType[] }>>('monitor/config', {method: 'GET',eoTransformKeys:[]}).then(response => {
const {code, data, msg} = response
if (code === STATUS_CODE.SUCCESS) {
data?.info?.driver && setData(data.info)
setShowStatus('view')
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
}
}).catch(() => {
return {data: [], success: false}
}).finally(()=>{
setLoading(false)
})
}
const getDashboardSettingInfo = () => {
setLoading(true)
return fetchData<BasicResponse<{ nodes: PartitionDashboardConfigFieldType[] }>>('monitor/config', { method: 'GET', eoTransformKeys: [] }).then(response => {
const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) {
data?.info?.driver && setData(data.info)
setShowGraphStatus('view')
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
}
}).catch(() => {
return { data: [], success: false }
}).finally(() => {
setLoading(false)
})
}
const getDataLogSettingInfo = () => {
setDataLogLoading(true)
return fetchData<BasicResponse<{ nodes: PartitionDataLogConfigFieldType[] }>>('log/loki', { method: 'GET', eoTransformKeys: [] }).then(response => {
const { code, data, msg } = response
console.log('data====', data);
if (code === STATUS_CODE.SUCCESS) {
data?.info && setDataLogData(data.info)
setShowDataLogStatus('view')
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
}
}).catch(() => {
return { data: [], success: false }
}).finally(() => {
setDataLogLoading(false)
})
}
useEffect(() => {
setBreadcrumb([
{title: $t('数据源')}
])
getDashboardSettingInfo()
}, []);
useEffect(() => {
setBreadcrumb([
{ title: $t('数据源') }
])
getDashboardSettingInfo()
getDataLogSettingInfo()
}, []);
const setDashboardSettingBtn = ()=>{
return (<>
{showStatus === 'view' && <WithPermission access="system.devops.data_source.edit" key="changeClusterConfig">
<Button type="primary" onClick={() => setShowStatus('edit')}>{$t('修改配置')}</Button>
</WithPermission> }</>
)
}
return (
<>
<InsidePage
pageTitle={$t('数据源')}
description={$t("设置监控报表的数据来源,设置完成之后即可获得详细的API调用统计图表。")}
showBorder={false}
scrollPage={true}
>
<div className="flex flex-col h-full overflow-auto pb-PAGE_INSIDE_B pr-PAGE_INSIDE_X">
<Spin wrapperClassName=" h-full flex-1" indicator={<LoadingOutlined style={{ fontSize: 24 }} spin/>} spinning={loading}>
<div className="h-full overflow-auto">
<Card
classNames={{
body: `overflow-auto ${(!data || !data?.driver) && showStatus === 'view' ? 'hidden': ''}`,
}}
className="overflow-hidden w-full max-h-full flex flex-col justify-between"
title={<div><span className="text-MAIN_TEXT my-btnybase mr-btnbase" > {$t('统计图表')}</span>
{!loading && !data?.driver && <Tag color='#f50'>{ $t('未配置')}
</Tag>}</div>}
extra={setDashboardSettingBtn()}>
{showStatus === 'view'&& data && data.driver && DashboardConfigPreview(data) }
{showStatus !== 'view' && <DashboardSettingEdit data={data} changeStatus={setShowStatus} refreshData={getDashboardSettingInfo} />}
</Card>
</div>
</Spin>
</div>
</InsidePage>
</>
const setDashboardSettingBtn = (type: string) => {
return (<>
{showGraphStatus === 'view' && <WithPermission access="system.devops.data_source.edit" key="changeClusterConfig">
<Button type="primary" onClick={() => type === 'graph' ? setShowGraphStatus('edit') : setShowDataLogStatus('edit')}>{$t('修改配置')}</Button>
</WithPermission>}</>
)
}
return (
<>
<InsidePage
pageTitle={$t('数据源')}
description={$t("设置监控报表的数据来源,设置完成之后即可获得详细的API调用统计图表。")}
showBorder={false}
scrollPage={true}
>
<div className="flex flex-col overflow-auto pb-PAGE_INSIDE_B pr-PAGE_INSIDE_X">
<Spin wrapperClassName="flex-1" indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} spinning={loading}>
<div className="h-full overflow-auto">
<Card
classNames={{
body: `overflow-auto ${(!data || !data?.driver) && showGraphStatus === 'view' ? 'hidden' : ''}`,
}}
className="overflow-hidden w-full max-h-full flex flex-col justify-between"
title={<div><span className="text-MAIN_TEXT my-btnybase mr-btnbase" > {$t('统计图表')}</span>
{!loading && !data?.driver && <Tag color='#f50'>{$t('未配置')}
</Tag>}</div>}
extra={setDashboardSettingBtn('graph')}>
{showGraphStatus === 'view' && data && data.driver && DashboardConfigPreview(data)}
{showGraphStatus !== 'view' && <DashboardSettingEdit data={data} changeStatus={setShowGraphStatus} refreshData={getDashboardSettingInfo} />}
</Card>
</div>
</Spin>
<Spin wrapperClassName="flex-1" indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} spinning={dataLogLoading}>
<div className="h-full overflow-auto">
<Card
classNames={{
body: `overflow-auto ${(!dataLogData) && showDataLogStatus === 'view' ? 'hidden' : ''}`,
}}
className="overflow-hidden mt-[30px] w-full max-h-full flex flex-col justify-between"
title={<div><span className="text-MAIN_TEXT my-btnybase mr-btnbase" > {$t('数据日志')}</span>
{!dataLogLoading && !dataLogData && <Tag color='#f50'>{$t('未配置')}
</Tag>}</div>}
extra={setDashboardSettingBtn('log')}>
{showDataLogStatus === 'view' && dataLogData && DataLogConfigPreview(dataLogData)}
{showDataLogStatus !== 'view' && <DataLogSettingEdit data={dataLogData} changeStatus={setShowDataLogStatus} refreshData={getDataLogSettingInfo} />}
</Card>
</div>
</Spin>
</div>
</InsidePage>
</>
)
}
export function DashboardConfigPreview (x:PartitionDashboardConfigFieldType){
return <div className="flex flex-col gap-[4px] ">
<Row className=""><Col className="font-bold text-right pr-[4px]">{$t('数据源')}</Col><Col>{x?.driver}</Col></Row>
<Row className=""><Col className="font-bold text-right pr-[4px]">{$t('地址(IP:端口)')}</Col><Col>{x?.config?.addr}</Col></Row>
<Row className=""><Col className="font-bold text-right pr-[4px]">{$t('组织(Organization')}</Col><Col>{x?.config?.org}</Col></Row>
</div>}
export function DashboardConfigPreview(x: PartitionDashboardConfigFieldType) {
return <div className="flex flex-col gap-[4px] ">
<Row className=""><Col className="font-bold text-right pr-[4px]">{$t('数据源')}</Col><Col>{x?.driver}</Col></Row>
<Row className=""><Col className="font-bold text-right pr-[4px]">{$t('地址(IP:端口)')}</Col><Col>{x?.config?.addr}</Col></Row>
<Row className=""><Col className="font-bold text-right pr-[4px]">{$t('组织(Organization')}</Col><Col>{x?.config?.org}</Col></Row>
</div>
}
export function DataLogConfigPreview(x: PartitionDataLogConfigFieldType) {
const columns = [
{
title: '标签',
dataIndex: 'tag',
},
{
title: '内容',
dataIndex: 'content'
}
]
const getTableList = () => {
return new Promise((resolve, reject) => {
resolve({
data: x?.config?.headers ? Object.keys(x?.config?.headers).map((key) => {
return {
tag: key,
content: x?.config?.headers[key]
}
}) : [],
success: true
})
})
}
console.log(getTableList());
return <div className="flex flex-col gap-[4px] ">
<Row className=""><Col className="font-bold text-right pr-[4px]">{$t('请求前缀')}</Col><Col>{x?.config?.url}</Col></Row>
<Row className=""><Col className="font-bold text-right pr-[4px]">{$t('HTTP 头部')}</Col><Col className="w-full">
<div className="w-full h-full p-[20px]">
<PageList
id="global_role"
tableClass="role_table mb-btnrbase"
primaryKey="'tag'"
columns={[...columns]}
request={() => getTableList()}
showPagination={false}
noScroll={true}
/>
</div>
</Col></Row>
</div>
}
export default PartitionInsideDashboardSetting
@@ -79,7 +79,7 @@ const DataMasking = (props: any) => {
{
title: '',
key: 'option',
btnNums: rowOperation.length -1,
btnNums: rowOperation.length,
fixed: 'right',
valueType: 'option',
render: (_: React.ReactNode, entity: any) => [
@@ -379,10 +379,11 @@ const DataMasking = (props: any) => {
<Modal
title={$t('处理日志')}
visible={modalVisible}
destroyOnClose={true}
onCancel={handleCloseModal}
footer={null}
wrapClassName="modal-without-footer"
width={1000}
width={1100}
maskClosable={true}
>
<div className="pb-btnybase flex flex-nowrap flex-col h-full w-full items-center justify-between">
@@ -86,7 +86,7 @@ export const DATA_MASKING_TABLE_LOG_COLUMNS: PageProColumns<any>[] = [
title: ('消费者IP'),
dataIndex: 'remote_ip',
ellipsis: true,
width: 150
width: 100
},
{
title: ('消费者'),
@@ -96,7 +96,7 @@ export const DATA_MASKING_TABLE_LOG_COLUMNS: PageProColumns<any>[] = [
},
{
title: ('鉴权名称'),
dataIndex: 'authorization',
dataIndex: ['authorization', 'name'],
ellipsis: true,
width: 100
},
@@ -1,13 +1,12 @@
import { Codebox } from "@common/components/postcat/api/Codebox";
import { Codebox, codeBoxLanguagesType } from "@common/components/postcat/api/Codebox";
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from "@common/const/const";
import { RouterParams } from "@common/const/type";
import { $t } from "@common/locales";
import { App, Button, message, Switch, Modal, Spin } from 'antd'
import { message, Spin } from 'antd'
import { useFetch } from "@common/hooks/http";
import { useEffect, useState } from "react";
import { LoadingOutlined } from "@ant-design/icons";
import { useParams } from "react-router-dom";
import { contentTypeToLanguageMap } from "@common/const/policy/consts";
type LogItems = {
id: string;
origin: string;
@@ -18,7 +17,15 @@ const DataMaskingCompare = () => {
const { fetchData } = useFetch()
const [loading, setLoading] = useState(false)
const [originValue, setOriginValue] = useState('')
const [targetValue, settTargetValue] = useState('')
const [targetValue, setTargetValue] = useState('')
const [language, setLanguage] = useState<codeBoxLanguagesType>('json')
const getMonacoEditorLanguage = (contentType: string): codeBoxLanguagesType => {
// 提取主类型,忽略参数(如 "; charset=utf-8"
const mainType = contentType.split(";")[0].trim().toLowerCase();
// 根据映射表获取语言,默认返回 "plaintext"
return contentTypeToLanguageMap[mainType] || "json";
};
const getLogData = () => {
setLoading(true)
return fetchData<BasicResponse<{ log: LogItems }>>(`strategy/${serviceId === undefined ? 'global' : 'service'}/data-masking/log`,
@@ -33,8 +40,10 @@ const DataMaskingCompare = () => {
const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) {
const { log } = data
setOriginValue(log.origin || '')
settTargetValue(log.target || '')
const docLanguage = getMonacoEditorLanguage(log.content_type)
setLanguage(docLanguage)
setOriginValue(docLanguage === 'json' ? JSON.stringify(JSON.parse(log.origin || ''), null, 2) : log.origin || '')
setTargetValue(docLanguage === 'json' ? JSON.stringify(JSON.parse(log.target || ''), null, 2) : log.target || '')
setLoading(false)
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
@@ -42,17 +51,6 @@ const DataMaskingCompare = () => {
}).catch(() => {
return { data: [], success: false }
}).finally(() => {
const aa = `{
"code": {
"gg": "gg",
"gg1": "gg",
"gg2": "gg",
"gg3": "gg",
"gg4": "gg"
}
}`
setOriginValue(JSON.stringify(JSON.parse(aa), null, 2))
settTargetValue(JSON.stringify(JSON.parse(aa), null, 2))
setLoading(false)
})
}
@@ -68,10 +66,12 @@ const DataMaskingCompare = () => {
</div>
<div style={{ height: 'calc(100vh - 50px)' }}>
<Codebox
language='json'
language={language}
height="100%"
width="100%"
value={originValue}
sx={{ whiteSpace: 'nowrap' }}
readOnly
/>
</div>
</div>
@@ -81,7 +81,7 @@ const DataMaskingCompare = () => {
</div>
<div style={{ height: 'calc(100vh - 50px)' }}>
<Codebox
language='json'
language={language}
width="100%"
height="100%"
value={targetValue}
@@ -1,40 +1,57 @@
import React, { useEffect, useMemo, useRef, useState } from 'react';
import React, { useMemo, useRef, useState } from 'react';
import { DataMaskLogItem } from "@common/const/policy/type";
import PageList, { PageProColumns } from "@common/components/aoplatform/PageList";
import { $t } from "@common/locales";
import { App, Button, message, DatePicker, Modal } from 'antd'
import { Button, message } from 'antd'
import { DATA_MASKING_TABLE_LOG_COLUMNS, DATA_MASKING_TABLE_COLUMNS } from './DataMaskingColumn';
import { DATA_MASKING_TABLE_LOG_COLUMNS } from './DataMaskingColumn';
import { useGlobalContext } from '@common/contexts/GlobalStateContext';
import { ActionType } from '@ant-design/pro-components';
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const';
import { useParams } from 'react-router-dom';
import { RouterParams } from '@common/const/type';
import { useFetch } from '@common/hooks/http';
import WithPermission from '@common/components/aoplatform/WithPermission';
import TimeRangeSelector, { TimeRange } from '@common/components/aoplatform/TimeRangeSelector';
import { SearchBody } from '@dashboard/const/type';
import TableBtnWithPermission from '@common/components/aoplatform/TableBtnWithPermission';
const { RangePicker } = DatePicker;
import { getTime } from '@dashboard/utils/dashboard';
const DataMaskingLogModal = (props: any) => {
const { strategy } = props;
const { state, accessData } = useGlobalContext()
const { serviceId, teamId } = useParams<RouterParams>()
const [datePickerValue, setDatePickerValue] = useState<any>();
const [queryData, setQueryData] = useState<SearchBody>({})
const defaultTime = getTime('hour', datePickerValue)
const [queryData, setQueryData] = useState<SearchBody>({
start: defaultTime.startTime,
end: defaultTime.endTime
})
/**
* 请求数据
*/
const { fetchData } = useFetch()
/**
* 列表ref
*/
* 列表ref
*/
const pageListRef = useRef<ActionType>(null);
/**
* 搜索关键字
*/
const [searchWord, setSearchWord] = useState<string>('')
/**
* 重置时间范围
*/
let resetTimeRange = () => {}
/**
* 时间按钮
*/
const [timeButton, setTimeButton] = useState<'' | 'hour' | 'day' | 'threeDays' | 'sevenDays'>('hour');
/**
* 绑定时间范围组件
* @param instance
*/
const bindRef = (instance: any) => {
resetTimeRange = instance.reset
};
/**
* 操作列
*/
@@ -54,7 +71,7 @@ const DataMaskingLogModal = (props: any) => {
url += `/${teamId}`
}
return [
<TableBtnWithPermission access={`${serviceId === undefined ? 'system.devops' : 'team.service'}.policy.view`} key="view" btnType="view" onClick={() => { window.open(url,'_blank') }} btnTitle="查看" />
<TableBtnWithPermission access={`${serviceId === undefined ? 'system.devops' : 'team.service'}.policy.view`} key="view" btnType="view" onClick={() => { window.open(url, '_blank') }} btnTitle="查看" />
]
}
}
@@ -88,7 +105,7 @@ const DataMaskingLogModal = (props: any) => {
current: number;
}) => {
return fetchData<BasicResponse<{ logs: DataMaskLogItem[], total: number }>>(
`strategy/${serviceId === undefined ? 'global' : 'service'}/data-masking/list`,
`strategy/${serviceId === undefined ? 'global' : 'service'}/data-masking/logs`,
{
method: 'GET',
eoParams: {
@@ -106,204 +123,9 @@ const DataMaskingLogModal = (props: any) => {
).then(response => {
const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) {
const mockData: any = [
{
id: '12334',
service: {
id: 'xxx',
name: 'xxx'
},
url: 'url',
remote_ip: '9234923',
consumer: {
id: 'yyy',
name: 'yyy'
},
method: 'GET',
authorization: 'authorization',
record_time: '2021-09-09 12:12:12',
},
{
id: 'fff1',
service: {
id: 'xxx',
name: 'xxx'
},
url: 'url',
remote_ip: '9234923',
consumer: {
id: 'yyy',
name: 'yyy'
},
method: 'GET',
authorization: 'authorization',
record_time: '2021-09-09 12:12:12',
},
{
id: 'fff2',
service: {
id: 'xxx',
name: 'xxx'
},
url: 'url',
remote_ip: '9234923',
consumer: {
id: 'yyy',
name: 'yyy'
},
method: 'GET',
authorization: 'authorization',
record_time: '2021-09-09 12:12:12',
},
{
id: 'fff3',
service: {
id: 'xxx',
name: 'xxx'
},
url: 'url',
remote_ip: '9234923',
consumer: {
id: 'yyy',
name: 'yyy'
},
method: 'GET',
authorization: 'authorization',
record_time: '2021-09-09 12:12:12',
},
{
id: 'fff4',
service: {
id: 'xxx',
name: 'xxx'
},
url: 'url',
remote_ip: '9234923',
consumer: {
id: 'yyy',
name: 'yyy'
},
method: 'GET',
authorization: 'authorization',
record_time: '2021-09-09 12:12:12',
},
{
id: 'fff5',
service: {
id: 'xxx',
name: 'xxx'
},
url: 'url',
remote_ip: '9234923',
consumer: {
id: 'yyy',
name: 'yyy'
},
method: 'GET',
authorization: 'authorization',
record_time: '2021-09-09 12:12:12',
},
{
id: 'fff6',
service: {
id: 'xxx',
name: 'xxx'
},
url: 'url',
remote_ip: '9234923',
consumer: {
id: 'yyy',
name: 'yyy'
},
method: 'GET',
authorization: 'authorization',
record_time: '2021-09-09 12:12:12',
},
{
id: 'fff7',
service: {
id: 'xxx',
name: 'xxx'
},
url: 'url',
remote_ip: '9234923',
consumer: {
id: 'yyy',
name: 'yyy'
},
method: 'GET',
authorization: 'authorization',
record_time: '2021-09-09 12:12:12',
},
{
id: 'fff8',
service: {
id: 'xxx',
name: 'xxx'
},
url: 'url',
remote_ip: '9234923',
consumer: {
id: 'yyy',
name: 'yyy'
},
method: 'GET',
authorization: 'authorization',
record_time: '2021-09-09 12:12:12',
},
{
id: 'fff9',
service: {
id: 'xxx',
name: 'xxx'
},
url: 'url',
remote_ip: '9234923',
consumer: {
id: 'yyy',
name: 'yyy'
},
method: 'GET',
authorization: 'authorization',
record_time: '2021-09-09 12:12:12',
},
{
id: 'fff11',
service: {
id: 'xxx',
name: 'xxx'
},
url: 'url',
remote_ip: '9234923',
consumer: {
id: 'yyy',
name: 'yyy'
},
method: 'GET',
authorization: 'authorization',
record_time: '2021-09-09 12:12:12',
},
{
id: 'fff22',
service: {
id: 'xxx',
name: 'xxx'
},
url: 'url',
remote_ip: '9234923',
consumer: {
id: 'yyy',
name: 'yyy'
},
method: 'GET',
authorization: 'authorization',
record_time: '2021-09-09 12:12:11',
}
]
// 保存数据
return {
data: mockData,
data: data.logs || [],
total: data.total,
success: true
}
@@ -320,24 +142,13 @@ const DataMaskingLogModal = (props: any) => {
const handleTimeRangeChange = (timeRange: TimeRange) => {
setQueryData(pre => ({ ...pre, ...timeRange } as SearchBody))
manualReloadTable()
};
const handleDatePickerChange = (dates: any) => {
if (dates && Array.isArray(dates) && dates.length === 2) {
const [startDate, endDate] = dates;
const start = startDate!.startOf('day').unix(); // 开始日期的00:00:00
const end = endDate!.endOf('day').unix(); // 结束日期的23:59:59
handleTimeRangeChange({ start, end });
} else {
handleTimeRangeChange({ start: null, end: null})
}
}
const resetQuery = () => {
setDatePickerValue(null)
handleTimeRangeChange({ start: null, end: null})
resetTimeRange()
};
return (
<>
<div className="w-full h-full p-[20px]">
@@ -348,11 +159,15 @@ const DataMaskingLogModal = (props: any) => {
columns={[...columns, ...operation]}
afterNewBtn={
[<div className="flex items-center flex-wrap p-[10px] px-btnbase content-before bg-MAIN_BG ">
<RangePicker
onChange={handleDatePickerChange}
value={datePickerValue} />
<div className="flex [&>.reset-btn]:!h-auto flex-nowrap items-center ml-[10px]">
<Button className="reset-btn" onClick={resetQuery}>{$t('重置')}</Button>
<TimeRangeSelector
labelSize="small"
bindRef={bindRef}
initialTimeButton={timeButton}
onTimeButtonChange={setTimeButton}
initialDatePickerValue={datePickerValue}
onTimeRangeChange={handleTimeRangeChange} />
<div className="flex flex-nowrap items-center pt-btnybase">
<Button onClick={resetQuery}>{$t('重置')}</Button>
</div>
</div>]
}