Compare commits

...

37 Commits

Author SHA1 Message Date
ningyv d36c66371f Merge pull request #147 from APIParkLab/fix/fixbug-cx
fix: consumer permission
2024-12-13 20:02:45 +08:00
ningyv b7bb409e96 fix: consumer permission 2024-12-13 20:00:38 +08:00
Dot.L 517007c941 Merge pull request #146 from APIParkLab/feature/aibug-fix
fix aksk bug
2024-12-13 19:41:10 +08:00
Liujian 4c685a9ec6 fix aksk bug 2024-12-13 18:52:21 +08:00
Dot.L 1aca2099de Merge pull request #145 from APIParkLab/feature/aibug-fix
fix ai provider upstream bug
2024-12-10 17:26:47 +08:00
Liujian a93e5b4ff8 fix ai provider upstream bug 2024-12-10 17:24:51 +08:00
ningyv 85d25bebe2 Merge branch 'main' of github.com:APIParkLab/APIPark 2024-12-10 15:34:44 +08:00
lichunxian 9fa43ccc00 Merge branch 'fix/certificatePermission' into 'main'
fix: table-permission

See merge request apipark/APIPark!124
2024-12-10 15:17:11 +08:00
ningyv c2a11050dd fix: table-permission 2024-12-10 15:16:19 +08:00
Dot.L 080bfc3a44 Merge pull request #143 from eltociear/patch-1
chore: update index.tsx
2024-12-09 21:48:14 +08:00
Liujian f6956ddeca Merge remote-tracking branch 'origin/main' into main-github-pro 2024-12-09 21:42:09 +08:00
Liujian 9f56fa5e14 update init log 2024-12-09 21:32:57 +08:00
lichunxian ccc39b95de Merge branch 'fix/certificatePermission' into 'main'
fix: implement certificate popup permission handling

See merge request apipark/APIPark!123
2024-12-09 15:22:29 +08:00
ningyv 9a2782e54b fix: implement certificate popup permission handling 2024-12-09 15:18:59 +08:00
Liujian 22455e2301 Automatically publish policies and logs during cluster initialization 2024-12-09 00:43:05 +08:00
Liujian 8ed2c84b68 update loki publish 2024-12-09 00:17:50 +08:00
Liujian ccd2a209e2 update .gitignore 2024-12-06 18:48:00 +08:00
lichunxian baf8ed4830 Merge branch 'feature/dataLogPage' into 'main'
fix: improve column width adjustment and optimize date picker performance

See merge request apipark/APIPark!122
2024-12-06 18:03:16 +08:00
ningyv dedb586daf fix: improve column width adjustment and optimize date picker performance 2024-12-06 18:02:44 +08:00
刘健 21cd823791 Merge branch 'feature/data-mask' into 'main'
update publish problem

See merge request apipark/APIPark!121
2024-12-06 16:13:23 +08:00
lichunxian f1c16fd992 Merge branch 'feature/dataLogPage' into 'main'
Feature/data log page

See merge request apipark/APIPark!120
2024-12-06 15:25:51 +08:00
ningyv 52035341f6 fix: refine time range calculation with second-level precision 2024-12-06 15:24:06 +08:00
ningyv aa62d44717 fix: resolve subscriber permissions 2024-12-06 14:22:59 +08:00
lichunxian a072d1fc8d Merge branch 'feature/dataLogPage' into 'main'
feat: integrate global policy API and implement data log page

See merge request apipark/APIPark!119
2024-12-06 11:51:13 +08:00
ningyv 38a00570d0 feat: integrate global policy API and implement data log page 2024-12-06 11:50:32 +08:00
刘健 43283b9da3 Merge branch 'feature/data-mask' into 'main'
update service publish

See merge request apipark/APIPark!118
2024-12-06 11:32:15 +08:00
刘健 3eb4f98fd8 Merge branch 'feature/data-mask' into 'main'
add service strategy log

See merge request apipark/APIPark!117
2024-12-06 10:49:21 +08:00
刘健 b8308a446b Merge branch 'feature/data-mask' into 'main'
update service publish

See merge request apipark/APIPark!116
2024-12-06 10:40:20 +08:00
lichunxian 952c519e45 Merge branch 'feature/dataLogPage' into 'main'
feat: integrate global policy API and implement data log page

See merge request apipark/APIPark!115
2024-12-05 18:53:51 +08:00
ningyv 07d97fa0bf feat: integrate global policy API and implement data log page 2024-12-05 18:53:11 +08:00
刘健 b0defedf04 Merge branch 'feature/data-mask' into 'main'
fix log bug:Keyword query failed

See merge request apipark/APIPark!114
2024-12-05 18:17:49 +08:00
刘健 a75b8a3f13 Merge branch 'feature/data-mask' into 'main'
fix logs bug

See merge request apipark/APIPark!113
2024-12-05 17:21:46 +08:00
刘健 9ab7989c8b Merge branch 'feature/data-mask' into 'main'
fix log bug

See merge request apipark/APIPark!112
2024-12-05 17:15:04 +08:00
刘健 4bae2edc49 Merge branch 'feature/data-mask' into 'main'
update strategy publish bug

See merge request apipark/APIPark!111
2024-12-05 15:44:22 +08:00
刘健 e01f596525 Merge branch 'feature/data-mask' into 'main'
update log label

See merge request apipark/APIPark!109
2024-12-05 15:06:28 +08:00
刘健 570c80af91 Merge branch 'feature/data-mask' into 'main'
Feature/data mask

See merge request apipark/APIPark!108
2024-12-05 14:50:19 +08:00
Ikko Eltociear Ashimine 836c7699b8 chore: update index.tsx
protocal -> protocol
2024-12-04 15:54:09 +09:00
42 changed files with 923 additions and 676 deletions
+1
View File
@@ -3,3 +3,4 @@
/config.yml /config.yml
/build/ /build/
/apipark /apipark
.gitlab-ci.yml
-98
View File
@@ -1,98 +0,0 @@
variables:
PATH: /opt/go-1.21/go/bin/:/opt/node/node/bin/:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
GOROOT: /opt/go-1.21/go
GOPROXY: https://goproxy.cn
VERSION: $CI_COMMIT_SHORT_SHA
APP: apipark
APP_PRE: ${APP}_${VERSION}
BUILD_DIR: ${APP}-build
DEPLOY_DESC: "DEV 环境"
VIEW_ADDR: http://172.18.166.219:8288
SAVE_DIR: /opt/${APP}
NODE_OPTIONS: --max_old_space_size=8192
stages:
- notice
- prefix
- build
- deploy
- webhook
feishu-informer: # 飞书回调
stage: notice
variables:
DIFF_URL: "$CI_MERGE_REQUEST_PROJECT_URL/-/merge_requests/$CI_MERGE_REQUEST_IID/diffs"
rules:
- if: $CI_PIPELINE_SOURCE=="merge_request_event" && $CI_COMMIT_BRANCH =~ "main"
script:
- echo "merge request"
- |
curl -X POST -H "Content-Type: application/json" \
-d "{\"msg_type\":\"text\",\"content\":{\"text\":\"项目:${CI_PROJECT_NAME}\\n提交人:${GITLAB_USER_NAME}\\n提交信息:${CI_MERGE_REQUEST_TITLE}\\n合并分支信息:${CI_MERGE_REQUEST_SOURCE_BRANCH_NAME} -> ${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}\\n差异性地址:${DIFF_URL}\\n请及时review代码\"}}" \
https://open.feishu.cn/open-apis/bot/v2/hook/1c334752-2874-41a1-8f1b-3060f2d46b6c
prebuild:
stage: prefix
rules:
- if: $CI_COMMIT_BRANCH == "main"
script:
- echo "prebuild"
- chmod +x ./scripts/prefix.sh
- ./scripts/prefix.sh
builder:
stage: build
rules:
- if: $CI_COMMIT_BRANCH == "main"
script:
- set -e
- |
if [ ! -d "../artifacts" ]; then
mkdir -p ../artifacts
fi
if [ -d "../artifacts/dist" ]; then
cp -r ../artifacts/dist frontend/dist
fi
- |
if [ -n "$(git diff --name-status HEAD~1 HEAD -- frontend)" ]; then
./scripts/build.sh $BUILD_DIR ${VERSION} all ""
else
./scripts/build.sh $BUILD_DIR ${VERSION}
fi
if [ -d "frontend/dist" ]; then
echo "copy frontend/dist to artifacts/dist"
rm -fr ../artifacts/dist
cp -r frontend/dist ../artifacts/dist
fi
cp $BUILD_DIR/${APP_PRE}_linux_amd64.tar.gz ${SAVE_DIR}
deployer:
stage: deploy
rules:
- if: $CI_COMMIT_BRANCH == "main"
variables:
APIPARK_GUEST_MODE: allow
APIPARK_GUEST_ID: dklejrfbhjqwdh
script:
- cd ${SAVE_DIR};mkdir -p ${APP_PRE};tar -zxvf ${APP_PRE}_linux_amd64.tar.gz -C ${APP_PRE};cd ${APP_PRE};./install.sh ${SAVE_DIR};./run.sh restart;cd ${SAVE_DIR} && ./clean.sh ${APP_PRE}
when: on_success
success:
stage: webhook
rules:
- if: $CI_COMMIT_BRANCH == "main"
script:
- |
curl -X POST -H "Content-Type: application/json" \
-d "{\"msg_type\":\"text\",\"content\":{\"text\":\"最近一次提交:${CI_COMMIT_TITLE}\\n提交人:${GITLAB_USER_NAME}\\n项目:${CI_PROJECT_NAME}\\n环境:${DEPLOY_DESC}\\n更新部署完成.\\n访问地址:${VIEW_ADDR}\\n工作流地址:${CI_PIPELINE_URL}\"}}" \
https://open.feishu.cn/open-apis/bot/v2/hook/c3672932-4dfa-4989-8023-0128bae59338
when: on_success
failure:
stage: webhook
rules:
- if: $CI_COMMIT_BRANCH == "main"
script:
- |
curl -X POST -H "Content-Type: application/json" \
-d "{\"msg_type\":\"text\",\"content\":{\"text\":\"最近一次提交:${CI_COMMIT_TITLE}\\n提交人:${GITLAB_USER_NAME}\\n项目:${CI_PROJECT_NAME}\\n环境:${DEPLOY_DESC}\\n更新部署失败,请及时到gitlab上查看\\n工作流地址:${CI_PIPELINE_URL}\"}}" \
https://open.feishu.cn/open-apis/bot/v2/hook/c3672932-4dfa-4989-8023-0128bae59338
when: on_failure
+6 -6
View File
@@ -27,7 +27,7 @@ type imlAPIController struct {
} }
func (i *imlAPIController) Create(ctx *gin.Context, serviceId string, input *ai_api_dto.CreateAPI) (*ai_api_dto.API, error) { func (i *imlAPIController) Create(ctx *gin.Context, serviceId string, input *ai_api_dto.CreateAPI) (*ai_api_dto.API, error) {
info, err := i.serviceModule.Get(ctx, serviceId) _, err := i.serviceModule.Get(ctx, serviceId)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -52,7 +52,7 @@ func (i *imlAPIController) Create(ctx *gin.Context, serviceId string, input *ai_
plugins["ai_formatter"] = api.PluginSetting{ plugins["ai_formatter"] = api.PluginSetting{
Config: plugin_model.ConfigType{ Config: plugin_model.ConfigType{
"model": input.AiModel.Id, "model": input.AiModel.Id,
"provider": fmt.Sprintf("%s@ai-provider", info.Provider.Id), "provider": fmt.Sprintf("%s@ai-provider", input.AiModel.Provider),
"config": input.AiModel.Config, "config": input.AiModel.Config,
}, },
} }
@@ -73,7 +73,7 @@ func (i *imlAPIController) Create(ctx *gin.Context, serviceId string, input *ai_
Retry: input.Retry, Retry: input.Retry,
Plugins: plugins, Plugins: plugins,
}, },
Upstream: info.Provider.Id, Upstream: input.AiModel.Provider,
Disable: false, Disable: false,
}) })
@@ -86,7 +86,7 @@ func (i *imlAPIController) Create(ctx *gin.Context, serviceId string, input *ai_
} }
func (i *imlAPIController) Edit(ctx *gin.Context, serviceId string, apiId string, input *ai_api_dto.EditAPI) (*ai_api_dto.API, error) { func (i *imlAPIController) Edit(ctx *gin.Context, serviceId string, apiId string, input *ai_api_dto.EditAPI) (*ai_api_dto.API, error) {
info, err := i.serviceModule.Get(ctx, serviceId) _, err := i.serviceModule.Get(ctx, serviceId)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -106,11 +106,11 @@ func (i *imlAPIController) Edit(ctx *gin.Context, serviceId string, apiId string
proxy.Plugins["ai_formatter"] = api.PluginSetting{ proxy.Plugins["ai_formatter"] = api.PluginSetting{
Config: plugin_model.ConfigType{ Config: plugin_model.ConfigType{
"model": input.AiModel.Id, "model": input.AiModel.Id,
"provider": fmt.Sprintf("%s@ai-provider", info.Provider.Id), "provider": fmt.Sprintf("%s@ai-provider", input.AiModel.Provider),
"config": input.AiModel.Config, "config": input.AiModel.Config,
}, },
} }
upstream = &info.Provider.Id upstream = &input.AiModel.Provider
} }
if input.AiPrompt != nil { if input.AiPrompt != nil {
@@ -146,8 +146,11 @@ export const PublishApprovalModalContent = forwardRef<PublishApprovalModalHandle
...x, ...x,
title: typeof x.title === 'string' ? $t(x.title) : x.title, title: typeof x.title === 'string' ? $t(x.title) : x.title,
...(x.dataIndex === 'status' ? { ...(x.dataIndex === 'status' ? {
render:(_,entity)=>( render:(_,entity)=> (
entity.status === 0 ? $t('正常') : $t('无效')) <span className={`${ApprovalStatusColorClass[entity.change as keyof typeof ApprovalStatusColorClass]} truncate block`}>
{$t(ChangeTypeEnum[entity.change as (keyof typeof ChangeTypeEnum)] || '-')}
</span>
)
}:{}) }:{})
} }
}),[state.language]) }),[state.language])
@@ -37,6 +37,8 @@ const TableIconName = {
const TableBtnWithPermission = ({ btnTitle, access, tooltip, disabled, navigateTo, onClick, className, btnType }: TableBtnWithPermissionProps) => { const TableBtnWithPermission = ({ btnTitle, access, tooltip, disabled, navigateTo, onClick, className, btnType }: TableBtnWithPermissionProps) => {
const [btnAccess, setBtnAccess] = useState<boolean>(false) const [btnAccess, setBtnAccess] = useState<boolean>(false)
const [btnStatus, setBtnStatus] = useState<boolean>(false)
const [closeToolTip, setCloseToolTip] = useState<boolean>(false)
const { accessData, checkPermission, accessInit } = useGlobalContext() const { accessData, checkPermission, accessInit } = useGlobalContext()
const navigate = useNavigate() const navigate = useNavigate()
const lastAccess = useMemo(() => { const lastAccess = useMemo(() => {
@@ -52,16 +54,27 @@ const TableBtnWithPermission = ({ btnTitle, access, tooltip, disabled, navigateT
const handleClick = useCallback((e: React.MouseEvent<HTMLButtonElement>) => { const handleClick = useCallback((e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation() e.stopPropagation()
setTimeout(() => {
setBtnStatus(false)
setCloseToolTip(true)
})
navigateTo ? navigate(navigateTo) : onClick?.() navigateTo ? navigate(navigateTo) : onClick?.()
}, [navigateTo, navigate, onClick]) }, [navigateTo, navigate, onClick])
const changeTooltipStatus = (open: boolean) => {
setBtnStatus(open)
if (closeToolTip) {
setBtnStatus(false)
setCloseToolTip(false)
}
}
return (<>{ return (<>{
!btnAccess || (disabled && tooltip) ? !btnAccess || (disabled && tooltip) ?
<Tooltip placement="top" title={tooltip ?? $t('暂无(0)权限,请联系管理员分配。', [$t(btnTitle).toLowerCase()])}> <Tooltip placement="top" title={tooltip ?? $t('暂无(0)权限,请联系管理员分配。', [$t(btnTitle).toLowerCase()])}>
<Button type="text" disabled={true} className={`h-[22px] border-none p-0 flex items-center bg-transparent ${className}`} key={btnType} icon={<Icon icon={TableIconName[btnType as keyof typeof TableIconName]} width="18" height="18" />} >{ }</Button> <Button type="text" disabled={true} className={`h-[22px] border-none p-0 flex items-center bg-transparent ${className}`} key={btnType} icon={<Icon icon={TableIconName[btnType as keyof typeof TableIconName]} width="18" height="18" />} >{ }</Button>
</Tooltip> </Tooltip>
: :
<Tooltip placement="top" title={$t(btnTitle)}> <Tooltip placement="top" title={$t(btnTitle)} trigger='hover' open={btnStatus} onOpenChange={changeTooltipStatus}>
<Button type="text" disabled={disabled} className={`h-[22px] border-none p-0 flex items-center bg-transparent ${className} `} key={btnType} icon={<Icon icon={TableIconName[btnType as keyof typeof TableIconName]} width="18" height="18" />} onClick={handleClick}>{ }</Button> <Button type="text" disabled={disabled} className={`h-[22px] border-none p-0 flex items-center bg-transparent ${className} `} key={btnType} icon={<Icon icon={TableIconName[btnType as keyof typeof TableIconName]} width="18" height="18" />} onClick={handleClick}>{ }</Button>
</Tooltip> </Tooltip>
@@ -1,5 +1,5 @@
import { useState } from 'react'; import { useEffect, useState } from 'react';
import { Radio, DatePicker, GetProps, RadioChangeEvent } from 'antd'; import { Radio, DatePicker, GetProps, RadioChangeEvent } from 'antd';
import dayjs, { Dayjs } from 'dayjs'; import dayjs, { Dayjs } from 'dayjs';
import customParseFormat from 'dayjs/plugin/customParseFormat'; import customParseFormat from 'dayjs/plugin/customParseFormat';
@@ -12,69 +12,80 @@ export type RangeValue = [Dayjs | null, Dayjs | null] | null;
dayjs.extend(customParseFormat); dayjs.extend(customParseFormat);
export type TimeRange = { export type TimeRange = {
start:number|null start: number | null
end:number|null end: number | null
} }
export type TimeRangeButton = ''| 'hour' | 'day' | 'threeDays' | 'sevenDays'; export type TimeRangeButton = '' | 'hour' | 'day' | 'threeDays' | 'sevenDays';
type TimeRangeSelectorProps = { type TimeRangeSelectorProps = {
initialTimeButton?:TimeRangeButton, initialTimeButton?: TimeRangeButton,
initialDatePickerValue?:RangeValue initialDatePickerValue?: RangeValue
onTimeRangeChange?:(timeRange:TimeRange) =>void onTimeRangeChange?: (timeRange: TimeRange) => void
hideTitle?:boolean hideTitle?: boolean
onTimeButtonChange:(time:TimeRangeButton) =>void onTimeButtonChange: (time: TimeRangeButton) => void
labelSize?:'small'|'default' labelSize?: 'small' | 'default'
} bindRef?: any
const TimeRangeSelector = (props:TimeRangeSelectorProps) => { hideBtns?: TimeRangeButton[]
const {initialTimeButton,initialDatePickerValue,onTimeRangeChange,hideTitle,onTimeButtonChange,labelSize='default'} = props defaultTimeButton?: TimeRangeButton
}
const TimeRangeSelector = (props: TimeRangeSelectorProps) => {
const { initialTimeButton, initialDatePickerValue, onTimeRangeChange, hideTitle, onTimeButtonChange, labelSize = 'default', bindRef, hideBtns = [], defaultTimeButton = 'hour' } = props
const [timeButton, setTimeButton] = useState(initialTimeButton || ''); 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: TimeRangeButton) => {
const currentSecond = new Date().getTime() // 当前毫秒数时间戳 const currentSecond = Math.floor(Date.now() / 1000); // 当前秒级时间戳
const currentMin = currentSecond - (currentSecond % (60 * 1000)) // 当前分钟数时间戳 let startMin = currentSecond - 60 * 60
let startMin = currentMin - 60 * 60 * 1000
switch (curBtn) { switch (curBtn) {
case 'hour': { case 'hour': {
startMin = currentMin - 60 * 60 * 1000 startMin = currentSecond - 60 * 60
break 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 'day': {
startMin = currentSecond - 24 * 60 * 60
break
}
case 'threeDays': {
startMin =
Math.floor(new Date().setHours(0, 0, 0, 0) / 1000) -
2 * 24 * 60 * 60
break
}
case 'sevenDays': {
startMin =
Math.floor(new Date().setHours(0, 0, 0, 0) / 1000) -
6 * 24 * 60 * 60
break
}
}
if (onTimeRangeChange) { if (onTimeRangeChange) {
onTimeRangeChange({ start: startMin / 1000, end: currentMin / 1000 }); onTimeRangeChange({ start: startMin, end: currentSecond });
} }
}; };
// 处理单选按钮的变化 // 处理单选按钮的变化
const handleRadioChange = (e:RadioChangeEvent) => { const handleRadioChange = (e: RadioChangeEvent) => {
setTimeButton(e.target.value); setTimeButton(e.target.value);
onTimeButtonChange?.(e.target.value) onTimeButtonChange?.(e.target.value)
setDatePickerValue(null) setDatePickerValue(null)
calculateTimeRange(e.target.value); calculateTimeRange(e.target.value);
}; };
const reset = () => {
setTimeButton(defaultTimeButton)
calculateTimeRange(defaultTimeButton)
setDatePickerValue(null)
}
// 处理日期选择器的变化 // 处理日期选择器的变化
const handleDatePickerChange = (dates: RangeValue) => { const handleDatePickerChange = (dates: RangeValue) => {
setTimeButton(dates ? '' : 'hour') setTimeButton(dates ? '' : defaultTimeButton)
onTimeButtonChange?.(dates ? '' : 'hour') onTimeButtonChange?.(dates ? '' : defaultTimeButton)
setDatePickerValue(dates); setDatePickerValue(dates);
if (dates && Array.isArray(dates) && dates.length === 2) { if (dates && Array.isArray(dates) && dates.length === 2) {
const [startDate, endDate] = dates; const [startDate, endDate] = dates;
@@ -84,34 +95,37 @@ const TimeRangeSelector = (props:TimeRangeSelectorProps) => {
onTimeRangeChange({ start, end }); onTimeRangeChange({ start, end });
} }
} }
if (!dates) {
calculateTimeRange(defaultTimeButton)
}
}; };
const disabledDate: RangePickerProps['disabledDate'] = (current) => { const disabledDate: RangePickerProps['disabledDate'] = (current) => {
// Can not select days before today and today // Can not select days before today and today
return current && current.valueOf() > dayjs().startOf('day').valueOf(); return current && current.valueOf() > dayjs().startOf('day').valueOf();
}; };
return ( return (
<div className="flex flex-nowrap items-center pt-btnybase mr-btnybase"> <div className="flex flex-nowrap items-center pt-btnybase mr-btnybase">
{!hideTitle && <label className={`whitespace-nowrap `}>{$t('时间')}</label>} {!hideTitle && <label className={`whitespace-nowrap `}>{$t('时间')}</label>}
<Radio.Group className="whitespace-nowrap" value={timeButton} onChange={handleRadioChange} buttonStyle="solid"> <Radio.Group className="whitespace-nowrap" value={timeButton} onChange={handleRadioChange} buttonStyle="solid">
<Radio.Button value="hour">{$t('近1小时')}</Radio.Button> {hideBtns?.length && hideBtns.includes('hour') ? null : <Radio.Button value="hour">{$t('近1小时')}</Radio.Button>}
<Radio.Button value="day">{$t('近24小时')}</Radio.Button> {hideBtns?.length && hideBtns.includes('day') ? null : <Radio.Button value="day">{$t('近24小时')}</Radio.Button>}
<Radio.Button value="threeDays">{$t('近3天')}</Radio.Button> {hideBtns?.length && hideBtns.includes('threeDays') ? null : <Radio.Button value="threeDays">{$t('近3天')}</Radio.Button>}
<Radio.Button className="rounded-e-none" value="sevenDays">{$t('近7天')}</Radio.Button> {hideBtns?.length && hideBtns.includes('sevenDays') ? null : <Radio.Button className="rounded-e-none" value="sevenDays">{$t('近7天')}</Radio.Button>}
</Radio.Group> </Radio.Group>
<DatePicker.RangePicker <DatePicker.RangePicker
value={datePickerValue} value={datePickerValue}
className="rounded-s-none ml-[-1px]" className="rounded-s-none ml-[-1px]"
disabledDate={disabledDate} disabledDate={disabledDate}
onChange={handleDatePickerChange} onChange={handleDatePickerChange}
onOpenChange={(open)=>{ onOpenChange={(open) => {
if(!open && datePickerValue && datePickerValue.length > 2){ if (!open && datePickerValue && datePickerValue.length > 2) {
setTimeButton('') setTimeButton('')
onTimeButtonChange?.('') onTimeButtonChange?.('')
} }
}} }}
/> />
</div> </div>
@@ -181,6 +181,10 @@ export const TranslateWord = ()=>{
{$t('请输入IP地址或CIDR范围,每条以换行分割')} {$t('请输入IP地址或CIDR范围,每条以换行分割')}
{$t('待更新')} {$t('待更新')}
{$t('待删除')} {$t('待删除')}
{$t('内容')}
{$t('调用地址')}
{$t('消费者 IP')}
{$t('鉴权名称')}
</> </>
) )
} }
@@ -13,6 +13,7 @@ export interface CodeboxApiRef {
formatCode: () => void formatCode: () => void
} }
export type codeBoxLanguagesType = 'html' | 'json' | 'xml' | 'javascript' | 'css' | 'plaintext'|'yaml'
interface CodeboxProps { interface CodeboxProps {
options?: MonacoEditor.IStandaloneEditorConstructionOptions options?: MonacoEditor.IStandaloneEditorConstructionOptions
value?: string value?: string
@@ -22,7 +23,7 @@ interface CodeboxProps {
height?: string | null height?: string | null
readOnly?: boolean readOnly?: boolean
apiRef?: RefObject<CodeboxApiRef> apiRef?: RefObject<CodeboxApiRef>
language?: 'html' | 'json' | 'xml' | 'javascript' | 'css' | 'plaintext'|'yaml' language?: codeBoxLanguagesType
extraContent?:React.ReactNode extraContent?:React.ReactNode
sx?:Record<string,unknown> sx?:Record<string,unknown>
editorTheme?:'vs' | 'vs-dark' | 'hc-black' editorTheme?:'vs' | 'vs-dark' | 'hc-black'
@@ -295,7 +295,7 @@ export interface IconParkIconElement extends HTMLElement {
| 'apispace' | 'apispace'
| 'auto-generate-api' | 'auto-generate-api'
| 'compare-api' | 'compare-api'
| 'multi-protocal' | 'multi-protocol'
| 'read-good' | 'read-good'
| 'richdoc' | 'richdoc'
| 'mockapi' | 'mockapi'
@@ -486,17 +486,17 @@ export const PERMISSION_DEFINITION = [
}, },
"team.application.subscription.add": { "team.application.subscription.add": {
"granted": { "granted": {
"anyOf": [{ "backend": ["system.workspace.application.manager_al","team.consumer.subscription.subscribe"] }] "anyOf": [{ "backend": ["system.workspace.application.manager_all","team.consumer.subscription.subscribe"] }]
} }
}, },
"team.application.subscription.edit": { "team.application.subscription.edit": {
"granted": { "granted": {
"anyOf": [{ "backend": ["system.workspace.application.manager_al","team.consumer.subscription.manager_subscribed_services"] }] "anyOf": [{ "backend": ["system.workspace.application.manager_all","team.consumer.subscription.manager_subscribed_services"] }]
} }
}, },
"team.application.subscription.delete": { "team.application.subscription.delete": {
"granted": { "granted": {
"anyOf": [{ "backend": ["system.workspace.application.manager_al","team.team.consumer.subscription.manager_subscribed_services"] }] "anyOf": [{ "backend": ["system.workspace.application.manager_all","team.team.consumer.subscription.manager_subscribed_services"] }]
} }
}, },
"team.application.application.view": { "team.application.application.view": {
@@ -506,47 +506,47 @@ export const PERMISSION_DEFINITION = [
}, },
"team.application.application.add": { "team.application.application.add": {
"granted": { "granted": {
"anyOf": [{ "backend": ["system.workspace.application.manager_al",'team.team.consumer.manager',"team.consumer.application.manager"] }] "anyOf": [{ "backend": ["system.workspace.application.manager_all",'team.team.consumer.manager',"team.consumer.application.manager"] }]
} }
}, },
"team.application.application.edit": { "team.application.application.edit": {
"granted": { "granted": {
"anyOf": [{ "backend": ["system.workspace.application.manager_al",'team.team.consumer.manager',"team.consumer.application.manager"] }] "anyOf": [{ "backend": ["system.workspace.application.manager_all",'team.team.consumer.manager',"team.consumer.application.manager"] }]
} }
}, },
"team.application.application.delete": { "team.application.application.delete": {
"granted": { "granted": {
"anyOf": [{ "backend": ["system.workspace.application.manager_al",'team.team.consumer.manager',"team.consumer.application.manager"] }] "anyOf": [{ "backend": ["system.workspace.application.manager_all",'team.team.consumer.manager',"team.consumer.application.manager"] }]
} }
}, },
"team.consumer.authorization.view": { "team.consumer.authorization.view": {
"granted": { "granted": {
"anyOf": [{ "backend": ["system.workspace.application.manager_al","system.workspace.application.view_all","team.consumer.authorization.view"] }] "anyOf": [{ "backend": ["system.workspace.application.manager_all","system.workspace.application.view_all","team.consumer.authorization.view"] }]
} }
}, },
"team.application.authorization.add": { "team.application.authorization.add": {
"granted": { "granted": {
"anyOf": [{ "backend": ["system.workspace.application.manager_al","team.consumer.authorization.manager"] }] "anyOf": [{ "backend": ["system.workspace.application.manager_all","team.consumer.authorization.manager"] }]
} }
}, },
"team.application.authorization.edit": { "team.application.authorization.edit": {
"granted": { "granted": {
"anyOf": [{ "backend": ["system.workspace.application.manager_al","team.consumer.authorization.manager"] }] "anyOf": [{ "backend": ["system.workspace.application.manager_all","team.consumer.authorization.manager"] }]
} }
}, },
"team.application.authorization.delete": { "team.application.authorization.delete": {
"granted": { "granted": {
"anyOf": [{ "backend": ["system.workspace.application.manager_al","team.consumer.authorization.manager"] }] "anyOf": [{ "backend": ["system.workspace.application.manager_all","team.consumer.authorization.manager"] }]
} }
}, },
"team.application.authorization.cancelSubApply": { "team.application.authorization.cancelSubApply": {
"granted": { "granted": {
"anyOf": [{ "backend": ["system.workspace.application.manager_al","team.consumer.authorization.manager"] }] "anyOf": [{ "backend": ["system.workspace.application.manager_all","team.consumer.authorization.manager"] }]
} }
}, },
"team.application.authorization.cancelSub": { "team.application.authorization.cancelSub": {
"granted": { "granted": {
"anyOf": [{ "backend": ["system.workspace.application.manager_al","team.consumer.authorization.manager"] }] "anyOf": [{ "backend": ["system.workspace.application.manager_all","team.consumer.authorization.manager"] }]
} }
}, },
"team.team.team.view": { "team.team.team.view": {
@@ -1,3 +1,4 @@
import { codeBoxLanguagesType } from "@common/components/postcat/api/Codebox";
export const MatchRules = [ export const MatchRules = [
{ value: 'inner', label: '数据格式' }, { value: 'inner', label: '数据格式' },
@@ -55,3 +56,33 @@ export const StrategyStatusEnum = {
"delete":'text-status_offline', "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", // 任意类型默认处理为普通文本
};
@@ -749,5 +749,19 @@
"K67f4e9bb": "The domain name for obtaining API market documentation information when integrating with external platforms", "K67f4e9bb": "The domain name for obtaining API market documentation information when integrating with external platforms",
"K1da86266": "Invalid", "K1da86266": "Invalid",
"K3a34d49b": "Pending Update", "K3a34d49b": "Pending Update",
"Kd2850420": "Pending Deletion" "Kd2850420": "Pending Deletion",
"K9ada4366": "Operation successful, the page will refresh shortly",
"K9b332ab1": "Request prefix",
"K3d78d483": "HTTP headers",
"K17dc3a62": "Data logs",
"Ke429194e": "Processing logs",
"K84ffb1dd": "Enter the invocation address, consumer IP, and consumer condition to search",
"Kb147fabc": "Create",
"K40ca4f2": "Update",
"K3e7aa0ad": "Content",
"K2f5fdf5e": "Call Address",
"K1bc5e0a3": "Consumer IP",
"K6f39ea21": "Authentication Name",
"K8c34c02f": "Before Masking",
"K8e3d388d": "After Masking"
} }
@@ -771,6 +771,19 @@
"K67f4e9bb": "外部プラットフォームと統合する際に、API市場のドキュメント情報を取得するためのドメイン名", "K67f4e9bb": "外部プラットフォームと統合する際に、API市場のドキュメント情報を取得するためのドメイン名",
"K1da86266": "無効", "K1da86266": "無効",
"K3a34d49b": "更新待ち", "K3a34d49b": "更新待ち",
"Kd2850420": "削除待ち" "Kd2850420": "削除待ち",
"K9ada4366": "操作成功、ページを更新します",
"K9b332ab1": "リクエストプレフィックス",
"K3d78d483": "HTTPヘッダー",
"K17dc3a62": "データログ",
"Ke429194e": "ログの処理",
"K84ffb1dd": "呼び出しアドレス、コンシューマーIP、条件を入力して検索",
"Kb147fabc": "新規作成",
"K40ca4f2": "更新",
"K3e7aa0ad": "内容",
"K2f5fdf5e": "呼び出しアドレス",
"K1bc5e0a3": "コンシューマー IP",
"K6f39ea21": "認証名",
"K8c34c02f": "マスキング前",
"K8e3d388d": "マスキング後"
} }
@@ -702,5 +702,19 @@
"K31faa2a1": "优先级", "K31faa2a1": "优先级",
"Kbdec9fa": "筛选条件", "Kbdec9fa": "筛选条件",
"Kbcbb7391": "处理数", "Kbcbb7391": "处理数",
"Kad207008": "编辑" "Kad207008": "编辑",
"K9ada4366": "操作成功,即将刷新页面",
"K9b332ab1": "请求前缀",
"K3d78d483": "HTTP 头部",
"K17dc3a62": "数据日志",
"Ke429194e": "处理日志",
"K84ffb1dd": "输入调用地址、消费者IP和消费者条件查找",
"Kb147fabc": "新建",
"K40ca4f2": "更新",
"K3e7aa0ad": "内容",
"K2f5fdf5e": "调用地址",
"K1bc5e0a3": "消费者 IP",
"K6f39ea21": "鉴权名称",
"K8c34c02f": "脱敏前",
"K8e3d388d": "脱敏后"
} }
@@ -771,5 +771,19 @@
"K67f4e9bb": "與外部平台集成時,用於獲取 API 市場文檔信息的域名", "K67f4e9bb": "與外部平台集成時,用於獲取 API 市場文檔信息的域名",
"K1da86266": "無效", "K1da86266": "無效",
"K3a34d49b": "待更新", "K3a34d49b": "待更新",
"Kd2850420": "待刪除" "Kd2850420": "待刪除",
"K9ada4366": "操作成功,即將刷新頁面",
"K9b332ab1": "請求前綴",
"K3d78d483": "HTTP 標頭",
"K17dc3a62": "數據日誌",
"Ke429194e": "處理日誌",
"K84ffb1dd": "輸入調用地址、消費者 IP 和消費者條件進行查找",
"Kb147fabc": "新建",
"K40ca4f2": "更新",
"K3e7aa0ad": "內容",
"K2f5fdf5e": "調用地址",
"K1bc5e0a3": "消費者 IP",
"K6f39ea21": "鑑權名稱",
"K8c34c02f": "脫敏前",
"K8e3d388d": "脫敏後"
} }
@@ -1,115 +1,157 @@
import { PageProColumns } from "@common/components/aoplatform/PageList";
import { COLUMNS_TITLE } from "@common/const/const";
import { EntityItem } from "@common/const/type"; import { EntityItem } from "@common/const/type";
export type PartitionConfigFieldType = { export type PartitionConfigFieldType = {
name?: string; name?: string;
id?: string; id?: string;
description?: string; description?: string;
prefix?:string prefix?: string
url?:string url?: string
managerAddress?:string managerAddress?: string
canDelete?:boolean canDelete?: boolean
}; };
export type PartitionCertTableListItem = { export type PartitionCertTableListItem = {
id:string; id: string;
name: string; name: string;
domains:string[]; domains: string[];
notAfter:string; notAfter: string;
notBefore:string; notBefore: string;
updater:EntityItem; updater: EntityItem;
updateTime:string; updateTime: string;
}; };
export type PartitionCertConfigFieldType = { export type PartitionCertConfigFieldType = {
id?:string id?: string
key:string key: string
pem:string pem: string
}; };
export type PartitionCertConfigProps = { export type PartitionCertConfigProps = {
type:'add'|'edit' type: 'add' | 'edit'
entity?:PartitionCertConfigFieldType entity?: PartitionCertConfigFieldType
} }
export type PartitionCertConfigHandle = { export type PartitionCertConfigHandle = {
save:()=>Promise<boolean|string> save: () => Promise<boolean | string>
} }
export type PartitionClusterFieldType = { export type PartitionClusterFieldType = {
name?: string; name?: string;
id?: string; id?: string;
description?: string; description?: string;
address?:string; address?: string;
protocol?:'http'|'https' protocol?: 'http' | 'https'
}; };
export type ClusterConfigProps = { export type ClusterConfigProps = {
mode:'config' | 'retry' | 'result' | 'edit', mode: 'config' | 'retry' | 'result' | 'edit',
clusterId?:string clusterId?: string
initFormValue?:{[k:string]:string|number} initFormValue?: { [k: string]: string | number }
} }
export type ClusterConfigHandle = { export type ClusterConfigHandle = {
save:()=>Promise<boolean|string> save: () => Promise<boolean | string>
check:()=>Promise<boolean> check: () => Promise<boolean>
} }
export type PartitionClusterTableListItem = { export type PartitionClusterTableListItem = {
id:string; id: string;
name: string; name: string;
status:0|1; status: 0 | 1;
description:string; description: string;
}; };
export type PartitionClusterNodeTableListItem = { export type PartitionClusterNodeTableListItem = {
id:string; id: string;
name: string; name: string;
managerAddress:string[]; managerAddress: string[];
serviceAddress:string[]; serviceAddress: string[];
peerAddress:string[]; peerAddress: string[];
status:0|1; status: 0 | 1;
}; };
export type PartitionClusterNodeModalTableListItem = { export type PartitionClusterNodeModalTableListItem = {
id: string, id: string,
name: string, name: string,
managerAddress: [], managerAddress: [],
serviceAddress: [], serviceAddress: [],
peerAddress: string, peerAddress: string,
status: string status: string
} }
export type NodeModalFieldType = { export type NodeModalFieldType = {
address:string address: string
} }
export type NodeModalHandle = { export type NodeModalHandle = {
save:()=>void save: () => void
} }
export type NodeModalPropsType = { export type NodeModalPropsType = {
changeStatus:(status:ClusterPageShowStatus)=>void changeStatus: (status: ClusterPageShowStatus) => void
getClusterInfo:()=>void getClusterInfo: () => void
status:ClusterPageShowStatus status: ClusterPageShowStatus
} }
export type ClusterPageShowStatus = 'view'|'preview'|'edit' export type ClusterPageShowStatus = 'view' | 'preview' | 'edit'
export type PartitionTableListItem = { export type PartitionTableListItem = {
id:string; id: string;
name: string; name: string;
clusterNum:number; clusterNum: number;
updater:EntityItem; updater: EntityItem;
updateTime:string; updateTime: string;
}; };
export type SimplePartition = EntityItem & { clusters: (EntityItem & {description:string})[] } export type SimplePartition = EntityItem & { clusters: (EntityItem & { description: string })[] }
export type PartitionDashboardConfigFieldType = { export type PartitionDashboardConfigFieldType = {
driver:string driver: string
config:{ config: {
org:string org: string
token:string token: string
addr:string addr: string
} }
} }
export type PartitionDataLogHeaderListFieldType = {
key: string
value: string
}
export type PartitionDataLogConfigFieldType = {
headers: PartitionDataLogHeaderListFieldType[]
url: string
}
export const PARTITION_DATA_LOG_CONFIG_TABLE_COLUMNS: PageProColumns<PartitionDataLogConfigFieldType & { _id: string }>[] = [
{
title: ('标签'),
dataIndex: 'key',
formItemProps: {
className: 'p-0 bg-transparent border-none',
rootClassName: 'test',
rules: [
{
required: true,
whitespace: true
},
],
},
ellipsis: true
},
{
title: ('内容'),
dataIndex: 'value',
formItemProps: {
className: 'p-0 bg-transparent border-none'
}
},
{
title: COLUMNS_TITLE.operate,
valueType: 'option',
btnNums: 2,
render: () => null
},
];
@@ -239,7 +239,7 @@ export const AiServiceSubscriberConfig = forwardRef<AiServiceSubscriberConfigHan
getAiServiceList() getAiServiceList()
}, [serviceId]); }, [serviceId]);
return (<WithPermission access="team.service.subscription.add"> return (<WithPermission access="">
<Form <Form
layout='vertical' layout='vertical'
labelAlign='left' labelAlign='left'
@@ -106,9 +106,9 @@ const AiSettingList = ()=>{
</a> </a>
<div> <div>
<CancelBtn/> <CancelBtn/>
<WithPermission access="system.devops.ai_provider.edit" showDisabled={false}> {
<OkBtn/> checkAccess('system.devops.ai_provider.edit', accessData) ? <OkBtn/> : null
</WithPermission> }
</div> </div>
</div> </div>
); );
@@ -3,7 +3,7 @@ import { useEffect } from "react";
import { PartitionDashboardConfigFieldType } from "../../const/partitions/types"; import { PartitionDashboardConfigFieldType } from "../../const/partitions/types";
import { DASHBOARD_SETTING_DRIVER_OPTION_LIST } from "../../const/partitions/const"; import { DASHBOARD_SETTING_DRIVER_OPTION_LIST } from "../../const/partitions/const";
import WithPermission from "@common/components/aoplatform/WithPermission"; import WithPermission from "@common/components/aoplatform/WithPermission";
import { BasicResponse, PLACEHOLDER, STATUS_CODE, VALIDATE_MESSAGE } from "@common/const/const"; import { BasicResponse, PLACEHOLDER, STATUS_CODE } from "@common/const/const";
import { useFetch } from "@common/hooks/http"; import { useFetch } from "@common/hooks/http";
import { $t } from "@common/locales"; import { $t } from "@common/locales";
@@ -29,10 +29,10 @@ export type DashboardSettingEditProps = {
fetchData<BasicResponse<{info: PartitionDashboardConfigFieldType}>>('monitor/config',{method: 'POST',body:JSON.stringify(value),eoParams:{}}).then(response=>{ fetchData<BasicResponse<{info: PartitionDashboardConfigFieldType}>>('monitor/config',{method: 'POST',body:JSON.stringify(value),eoParams:{}}).then(response=>{
const {code,msg} = response const {code,msg} = response
if(code === STATUS_CODE.SUCCESS){ if(code === STATUS_CODE.SUCCESS){
message.success(msg || '操作成功,即将刷新页面') message.success(msg || $t('操作成功,即将刷新页面'))
refreshData?.() refreshData?.()
}else{ }else{
message.error(msg || '操作失败') message.error(msg || $t('操作失败'))
} }
}) })
}) })
@@ -0,0 +1,90 @@
import EditableTable from "@common/components/aoplatform/EditableTable"
import WithPermission from "@common/components/aoplatform/WithPermission"
import { BasicResponse, PLACEHOLDER, STATUS_CODE } from "@common/const/const"
import { useFetch } from "@common/hooks/http"
import { $t } from "@common/locales"
import { PARTITION_DATA_LOG_CONFIG_TABLE_COLUMNS, PartitionDataLogConfigFieldType, PartitionDataLogHeaderListFieldType } from "@core/const/partitions/types"
import { Button, Form, Input, message } from "antd"
import { useEffect } from "react"
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
const [form] = Form.useForm();
const { fetchData } = useFetch()
const onFinish = () => {
form.validateFields().then((value) => {
const formData = {
config: {
url: value.url,
headers: value.headers.filter((item: PartitionDataLogHeaderListFieldType) => item.key).map((item: PartitionDataLogHeaderListFieldType) => ({key:item.key, value:item.value || ''}))
}
}
fetchData<BasicResponse<{ info: PartitionDataLogConfigFieldType }>>('log/loki', { method: 'POST', body: JSON.stringify(formData), eoParams: {} }).then(response => {
const { code, msg } = response
if (code === STATUS_CODE.SUCCESS) {
message.success(msg || $t('操作成功,即将刷新页面'))
refreshData?.()
} else {
message.error(msg || $t('操作失败'))
}
})
})
}
useEffect(() => { form.setFieldsValue(data) }, [data])
useEffect(() => {
return (form.setFieldsValue({}))
}, []);
return (
<>
<div className="overflow-auto h-full">
<WithPermission access={''} >
<Form
form={form}
className="mx-auto flex flex-col justify-between h-full"
layout="vertical"
onFinish={onFinish}
autoComplete="off"
>
<Form.Item<PartitionDataLogConfigFieldType>
label={$t("请求前缀")}
name="url"
rules={[{ required: true }]}
>
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} />
</Form.Item>
<Form.Item<PartitionDataLogConfigFieldType>
label={$t("HTTP 头部")}
name="headers"
>
<EditableTable<PartitionDataLogConfigFieldType & { _id: string }>
configFields={PARTITION_DATA_LOG_CONFIG_TABLE_COLUMNS}
/>
</Form.Item>
<div className="flex gap-btnbase">
<WithPermission access='system.devops.data_source.edit'>
<Button type="primary" htmlType="submit">
{$t('保存')}
</Button>
</WithPermission>
<Button type="default" onClick={() => changeStatus('view')}>
{$t('取消')}
</Button>
</div>
</Form>
</WithPermission>
</div>
</>
);
}
export default DataLogSettingEdit;
@@ -57,7 +57,7 @@ const CertConfigModal = forwardRef<PartitionCertConfigHandle,PartitionCertConfig
} }
}, []); }, []);
return (<WithPermission access={type === 'edit' ? 'system.devops.ssl_certificate.edit':'system.devops.ssl_certificate.add'}> return (<WithPermission access=''>
<Form <Form
layout='vertical' layout='vertical'
labelAlign='left' labelAlign='left'
@@ -187,7 +187,7 @@ const PartitionInsideCert:FC = ()=>{
switch (type){ switch (type){
case 'add': case 'add':
title=$t('添加证书') title=$t('添加证书')
content= <CertConfigModal ref={addRef} type="add"/> content= <WithPermission access='system.devops.ssl_certificate.add'><CertConfigModal ref={addRef} type="add"/></WithPermission>
break; break;
case 'edit':{ case 'edit':{
title=$t('修改证书') title=$t('修改证书')
@@ -195,7 +195,7 @@ const PartitionInsideCert:FC = ()=>{
const {code,data,msg} = await fetchData<BasicResponse<{cert:{key:string, pem:string}}>>('certificate',{method:'GET',eoParams:{id:entity!.id}}) const {code,data,msg} = await fetchData<BasicResponse<{cert:{key:string, pem:string}}>>('certificate',{method:'GET',eoParams:{id:entity!.id}})
message.destroy() message.destroy()
if(code === STATUS_CODE.SUCCESS){ if(code === STATUS_CODE.SUCCESS){
content= <CertConfigModal ref={editRef} type="edit" entity={{...data.cert,id:entity!.id}}/> content= <WithPermission access={'system.devops.ssl_certificate.edit'}><CertConfigModal ref={editRef} type="edit" entity={{...data.cert,id:entity!.id}}/></WithPermission>
}else{ }else{
message.error(msg || $t(RESPONSE_TIPS.error)) message.error(msg || $t(RESPONSE_TIPS.error))
return return
@@ -1,93 +1,175 @@
import { FC, useEffect, useState} from "react"; import { FC, useEffect, useState } from "react";
import {useBreadcrumb} from "@common/contexts/BreadcrumbContext.tsx"; import { useBreadcrumb } from "@common/contexts/BreadcrumbContext.tsx";
import {App, Button, Card, Col, Row, Spin, Tag} from "antd"; import { App, Button, Card, Col, Row, Spin, Tag } from "antd";
import {BasicResponse, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx"; import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from "@common/const/const.tsx";
import {useFetch} from "@common/hooks/http.ts"; import { useFetch } from "@common/hooks/http.ts";
import WithPermission from "@common/components/aoplatform/WithPermission.tsx"; import WithPermission from "@common/components/aoplatform/WithPermission.tsx";
import { LoadingOutlined } from "@ant-design/icons"; import { LoadingOutlined } from "@ant-design/icons";
import InsidePage from "@common/components/aoplatform/InsidePage.tsx"; import InsidePage from "@common/components/aoplatform/InsidePage.tsx";
import { $t } from "@common/locales/index.ts"; import { $t } from "@common/locales/index.ts";
import DashboardSettingEdit, { DashboardPageShowStatus } from "./DashboardSettingEdit.tsx"; import DashboardSettingEdit, { DashboardPageShowStatus } from "./DashboardSettingEdit.tsx";
import { PartitionDashboardConfigFieldType } from "@core/const/partitions/types.ts"; import { PARTITION_DATA_LOG_CONFIG_TABLE_COLUMNS, 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 PartitionInsideDashboardSetting: FC = () => {
const {setBreadcrumb} = useBreadcrumb() const { setBreadcrumb } = useBreadcrumb()
const {message} = App.useApp() const { message } = App.useApp()
const {fetchData} = useFetch() const { fetchData } = useFetch()
const [data, setData] = useState<PartitionDashboardConfigFieldType>() const [data, setData] = useState<PartitionDashboardConfigFieldType>()
const [loading, setLoading] = useState<boolean>(false) const [dataLogData, setDataLogData] = useState<PartitionDataLogConfigFieldType>()
const [showStatus, setShowStatus] = useState<DashboardPageShowStatus>('view') 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 = () => { const getDashboardSettingInfo = () => {
setLoading(true) setLoading(true)
return fetchData<BasicResponse<{ nodes:PartitionDashboardConfigFieldType[] }>>('monitor/config', {method: 'GET',eoTransformKeys:[]}).then(response => { return fetchData<BasicResponse<{ nodes: PartitionDashboardConfigFieldType[] }>>('monitor/config', { method: 'GET', eoTransformKeys: [] }).then(response => {
const {code, data, msg} = response const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) { if (code === STATUS_CODE.SUCCESS) {
data?.info?.driver && setData(data.info) data?.info?.driver && setData(data.info)
setShowStatus('view') setShowGraphStatus('view')
} else { } else {
message.error(msg || $t(RESPONSE_TIPS.error)) message.error(msg || $t(RESPONSE_TIPS.error))
} }
}).catch(() => { }).catch(() => {
return {data: [], success: false} return { data: [], success: false }
}).finally(()=>{ }).finally(() => {
setLoading(false) setLoading(false)
})
}
const getDataLogSettingInfo = () => {
setDataLogLoading(true)
return fetchData<BasicResponse<{ nodes: PartitionDataLogConfigFieldType[] }>>('log/loki', { method: 'GET', eoTransformKeys: [] }).then(response => {
const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) {
data?.info && setDataLogData({
url: data.info?.config?.url || '',
headers: data.info?.config?.headers || []
}) })
} setShowDataLogStatus('view')
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
}
}).catch(() => {
return { data: [], success: false }
}).finally(() => {
setDataLogLoading(false)
})
}
useEffect(() => {
setBreadcrumb([
{ title: $t('数据源') }
])
getDashboardSettingInfo()
getDataLogSettingInfo()
}, []);
useEffect(() => { const setDashboardSettingBtn = () => {
setBreadcrumb([ return (<>
{title: $t('数据源')} {showGraphStatus === 'view' && <WithPermission access="system.devops.data_source.edit" key="changeClusterConfig">
]) <Button type="primary" onClick={() => setShowGraphStatus('edit')}>{$t('修改配置')}</Button>
getDashboardSettingInfo() </WithPermission>}</>
}, []);
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 setDataLogSettingBtn = () => {
return (<>
{showDataLogStatus === 'view' && <WithPermission access="system.devops.data_source.edit" key="changeClusterConfig">
<Button type="primary" onClick={() => setShowDataLogStatus('edit')}>{$t('修改配置')}</Button>
</WithPermission>}</>
)
}
return (
<>
<InsidePage
pageTitle={$t('数据源')}
description={$t("设置监控报表的数据来源,设置完成之后即可获得详细的API调用统计图表。")}
showBorder={false}
scrollPage={false}
>
<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()}>
{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={setDataLogSettingBtn()}>
{showDataLogStatus === 'view' && dataLogData && DataLogConfigPreview(dataLogData)}
{showDataLogStatus !== 'view' && <DataLogSettingEdit data={dataLogData} changeStatus={setShowDataLogStatus} refreshData={getDataLogSettingInfo} />}
</Card>
</div>
</Spin>
</div>
</InsidePage>
</>
)
} }
export function DashboardConfigPreview (x:PartitionDashboardConfigFieldType){ export function DashboardConfigPreview(x: PartitionDashboardConfigFieldType) {
return <div className="flex flex-col gap-[4px] "> 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('数据源')}</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('地址(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> <Row className=""><Col className="font-bold text-right pr-[4px]">{$t('组织(Organization')}</Col><Col>{x?.config?.org}</Col></Row>
</div>} </div>
}
export function DataLogConfigPreview(x: PartitionDataLogConfigFieldType) {
const columns = PARTITION_DATA_LOG_CONFIG_TABLE_COLUMNS.map(x => {
return {
...x,
title: (<span title={$t(x.title as string)}>{$t(x.title as string)}</span>)
}
})
const getTableList = () => {
return new Promise((resolve, reject) => {
resolve({
data: x?.headers || [],
success: true
})
})
}
return <div className="flex flex-col gap-[4px] ">
<Row className=""><Col className="font-bold text-right pr-[4px]">{$t('请求前缀')}</Col><Col>{x?.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="'key'"
columns={[...columns]}
request={() => getTableList()}
showPagination={false}
noScroll={true}
/>
</div>
</Col></Row>
</div>
}
export default PartitionInsideDashboardSetting export default PartitionInsideDashboardSetting
@@ -79,7 +79,7 @@ const DataMasking = (props: any) => {
{ {
title: '', title: '',
key: 'option', key: 'option',
btnNums: rowOperation.length -1, btnNums: rowOperation.length,
fixed: 'right', fixed: 'right',
valueType: 'option', valueType: 'option',
render: (_: React.ReactNode, entity: any) => [ render: (_: React.ReactNode, entity: any) => [
@@ -233,7 +233,6 @@ const DataMasking = (props: any) => {
* @param entity * @param entity
*/ */
const openLogsModal = (entity: any) => { const openLogsModal = (entity: any) => {
console.log('日志', entity);
setStrategy(entity.id) setStrategy(entity.id)
setModalVisible(true) setModalVisible(true)
} }
@@ -379,10 +378,11 @@ const DataMasking = (props: any) => {
<Modal <Modal
title={$t('处理日志')} title={$t('处理日志')}
visible={modalVisible} visible={modalVisible}
destroyOnClose={true}
onCancel={handleCloseModal} onCancel={handleCloseModal}
footer={null} footer={null}
wrapClassName="modal-without-footer" wrapClassName="modal-without-footer"
width={1000} width={1100}
maskClosable={true} maskClosable={true}
> >
<div className="pb-btnybase flex flex-nowrap flex-col h-full w-full items-center justify-between"> <div className="pb-btnybase flex flex-nowrap flex-col h-full w-full items-center justify-between">
@@ -27,7 +27,7 @@ export const DATA_MASKING_TABLE_COLUMNS: PageProColumns<any>[] = [
filters: true, filters: true,
onFilter: false , onFilter: false ,
ellipsis: true, ellipsis: true,
width: 140, width: 110,
valueEnum: new Map( valueEnum: new Map(
Object.keys(StrategyStatusEnum).map(key=> Object.keys(StrategyStatusEnum).map(key=>
[key, [key,
@@ -40,6 +40,7 @@ export const DATA_MASKING_TABLE_COLUMNS: PageProColumns<any>[] = [
filters: true, filters: true,
onFilter: false , onFilter: false ,
ellipsis: true, ellipsis: true,
width: 90,
valueEnum: { valueEnum: {
false: { text: <span className="text-status_success">{$t('启用')}</span> }, false: { text: <span className="text-status_success">{$t('启用')}</span> },
true: { text: <span className="text-status_fail">{$t('禁用')}</span> } true: { text: <span className="text-status_fail">{$t('禁用')}</span> }
@@ -53,7 +54,8 @@ export const DATA_MASKING_TABLE_COLUMNS: PageProColumns<any>[] = [
{ {
title: ('处理数'), title: ('处理数'),
dataIndex: 'processedTotal', dataIndex: 'processedTotal',
ellipsis: true ellipsis: true,
width: 80,
}, },
{ {
title: ('更新者'), title: ('更新者'),
@@ -83,10 +85,10 @@ export const DATA_MASKING_TABLE_LOG_COLUMNS: PageProColumns<any>[] = [
width: 200 width: 200
}, },
{ {
title: ('消费者IP'), title: ('消费者 IP'),
dataIndex: 'remote_ip', dataIndex: 'remote_ip',
ellipsis: true, ellipsis: true,
width: 150 width: 100
}, },
{ {
title: ('消费者'), title: ('消费者'),
@@ -96,14 +98,14 @@ export const DATA_MASKING_TABLE_LOG_COLUMNS: PageProColumns<any>[] = [
}, },
{ {
title: ('鉴权名称'), title: ('鉴权名称'),
dataIndex: 'authorization', dataIndex: ['authorization', 'name'],
ellipsis: true, ellipsis: true,
width: 100 width: 100
}, },
{ {
title: ('时间'), title: ('时间'),
dataIndex: 'record_time', dataIndex: 'record_time',
width: 150, width: 110,
ellipsis: true ellipsis: true
}, },
] ]
@@ -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 { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from "@common/const/const";
import { RouterParams } from "@common/const/type";
import { $t } from "@common/locales"; 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 { useFetch } from "@common/hooks/http";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { LoadingOutlined } from "@ant-design/icons"; import { LoadingOutlined } from "@ant-design/icons";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { contentTypeToLanguageMap } from "@common/const/policy/consts";
type LogItems = { type LogItems = {
id: string; id: string;
origin: string; origin: string;
@@ -18,7 +17,15 @@ const DataMaskingCompare = () => {
const { fetchData } = useFetch() const { fetchData } = useFetch()
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [originValue, setOriginValue] = useState('') 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 = () => { const getLogData = () => {
setLoading(true) setLoading(true)
return fetchData<BasicResponse<{ log: LogItems }>>(`strategy/${serviceId === undefined ? 'global' : 'service'}/data-masking/log`, return fetchData<BasicResponse<{ log: LogItems }>>(`strategy/${serviceId === undefined ? 'global' : 'service'}/data-masking/log`,
@@ -33,8 +40,10 @@ const DataMaskingCompare = () => {
const { code, data, msg } = response const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) { if (code === STATUS_CODE.SUCCESS) {
const { log } = data const { log } = data
setOriginValue(log.origin || '') const docLanguage = getMonacoEditorLanguage(log.content_type)
settTargetValue(log.target || '') 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) setLoading(false)
} else { } else {
message.error(msg || $t(RESPONSE_TIPS.error)) message.error(msg || $t(RESPONSE_TIPS.error))
@@ -42,17 +51,6 @@ const DataMaskingCompare = () => {
}).catch(() => { }).catch(() => {
return { data: [], success: false } return { data: [], success: false }
}).finally(() => { }).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) setLoading(false)
}) })
} }
@@ -64,24 +62,26 @@ const DataMaskingCompare = () => {
<div className="flex h-full overflow-hidden"> <div className="flex h-full overflow-hidden">
<div className="w-1/2 p-2 h-full"> <div className="w-1/2 p-2 h-full">
<div className="h-[30px] bg-gray-200 mb-2 flex items-center justify-center"> <div className="h-[30px] bg-gray-200 mb-2 flex items-center justify-center">
{$t('脱敏前')}
</div> </div>
<div style={{ height: 'calc(100vh - 50px)' }}> <div style={{ height: 'calc(100vh - 50px)' }}>
<Codebox <Codebox
language='json' language={language}
height="100%" height="100%"
width="100%" width="100%"
value={originValue} value={originValue}
sx={{ whiteSpace: 'nowrap' }}
readOnly
/> />
</div> </div>
</div> </div>
<div className="w-1/2 p-2 h-full"> <div className="w-1/2 p-2 h-full">
<div className="h-[30px] bg-green-100 mb-2 flex items-center justify-center"> <div className="h-[30px] bg-green-100 mb-2 flex items-center justify-center">
{$t('脱敏后')}
</div> </div>
<div style={{ height: 'calc(100vh - 50px)' }}> <div style={{ height: 'calc(100vh - 50px)' }}>
<Codebox <Codebox
language='json' language={language}
width="100%" width="100%"
height="100%" height="100%"
value={targetValue} value={targetValue}
@@ -1,46 +1,62 @@
import React, { useEffect, useMemo, useRef, useState } from 'react'; import React, { useMemo, useRef, useState } from 'react';
import { DataMaskLogItem } from "@common/const/policy/type"; import { DataMaskLogItem } from "@common/const/policy/type";
import PageList, { PageProColumns } from "@common/components/aoplatform/PageList"; import PageList, { PageProColumns } from "@common/components/aoplatform/PageList";
import { $t } from "@common/locales"; 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 { useGlobalContext } from '@common/contexts/GlobalStateContext';
import { ActionType } from '@ant-design/pro-components'; import { ActionType } from '@ant-design/pro-components';
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'; import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { RouterParams } from '@common/const/type'; import { RouterParams } from '@common/const/type';
import { useFetch } from '@common/hooks/http'; import { useFetch } from '@common/hooks/http';
import WithPermission from '@common/components/aoplatform/WithPermission';
import TimeRangeSelector, { TimeRange } from '@common/components/aoplatform/TimeRangeSelector'; import TimeRangeSelector, { TimeRange } from '@common/components/aoplatform/TimeRangeSelector';
import { SearchBody } from '@dashboard/const/type'; import { SearchBody } from '@dashboard/const/type';
import TableBtnWithPermission from '@common/components/aoplatform/TableBtnWithPermission'; import TableBtnWithPermission from '@common/components/aoplatform/TableBtnWithPermission';
const { RangePicker } = DatePicker;
const DataMaskingLogModal = (props: any) => { const DataMaskingLogModal = (props: any) => {
const { strategy } = props; const { strategy } = props;
const { state, accessData } = useGlobalContext() const { state, accessData } = useGlobalContext()
const { serviceId, teamId } = useParams<RouterParams>() const { serviceId, teamId } = useParams<RouterParams>()
const [datePickerValue, setDatePickerValue] = useState<any>(); const [datePickerValue, setDatePickerValue] = useState<any>();
const [queryData, setQueryData] = useState<SearchBody>({}) const currentSecond = Math.floor(Date.now() / 1000); // 当前秒级时间戳
const [queryData, setQueryData] = useState<SearchBody>({
start: currentSecond - 24 * 60 * 60,
end: currentSecond
})
/** /**
* *
*/ */
const { fetchData } = useFetch() const { fetchData } = useFetch()
/** /**
* ref * ref
*/ */
const pageListRef = useRef<ActionType>(null); const pageListRef = useRef<ActionType>(null);
/** /**
* *
*/ */
const [searchWord, setSearchWord] = useState<string>('') const [searchWord, setSearchWord] = useState<string>('')
/**
*
*/
let resetTimeRange = () => {}
/**
*
*/
const [timeButton, setTimeButton] = useState<'' | 'hour' | 'day' | 'threeDays' | 'sevenDays'>('day');
/**
*
* @param instance
*/
const bindRef = (instance: any) => {
resetTimeRange = instance.reset
};
/** /**
* *
*/ */
const operation: PageProColumns<any>[] = [ const operation: PageProColumns<any>[] = [
{ {
title: '操作', title: '',
key: 'option', key: 'option',
btnNums: 1, btnNums: 1,
fixed: 'right', fixed: 'right',
@@ -54,7 +70,7 @@ const DataMaskingLogModal = (props: any) => {
url += `/${teamId}` url += `/${teamId}`
} }
return [ 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="查看" />
] ]
} }
} }
@@ -68,11 +84,11 @@ const DataMaskingLogModal = (props: any) => {
const columns = useMemo(() => { const columns = useMemo(() => {
const res = DATA_MASKING_TABLE_LOG_COLUMNS.map(x => { const res = DATA_MASKING_TABLE_LOG_COLUMNS.map(x => {
if (x.dataIndex === 'url') { if (x.dataIndex === 'url') {
x.render = (text: any, record: any) => <><span className='text-green-500'>{record.method}</span>&nbsp;<span>{text}</span></> x.render = (text: any, record: any) => <><div className='w-full'><span className='text-green-500'>{record.method}</span>&nbsp;<span className='w-[calc(100%-25px)] text-ellipsis overflow-hidden whitespace-nowrap inline-block align-top'>{text}</span></div></>
} }
return { return {
...x, ...x,
title: typeof x.title === 'string' ? $t(x.title as string) : x.title title: (<span title={$t(x.title as string)}>{$t(x.title as string)}</span>)
} }
}) })
return res return res
@@ -88,7 +104,7 @@ const DataMaskingLogModal = (props: any) => {
current: number; current: number;
}) => { }) => {
return fetchData<BasicResponse<{ logs: DataMaskLogItem[], total: 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', method: 'GET',
eoParams: { eoParams: {
@@ -106,204 +122,9 @@ const DataMaskingLogModal = (props: any) => {
).then(response => { ).then(response => {
const { code, data, msg } = response const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) { 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 { return {
data: mockData, data: data.logs || [],
total: data.total, total: data.total,
success: true success: true
} }
@@ -322,22 +143,11 @@ const DataMaskingLogModal = (props: any) => {
manualReloadTable() 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 = () => { const resetQuery = () => {
setDatePickerValue(null) resetTimeRange()
handleTimeRangeChange({ start: null, end: null})
}; };
return ( return (
<> <>
<div className="w-full h-full p-[20px]"> <div className="w-full h-full p-[20px]">
@@ -348,11 +158,17 @@ const DataMaskingLogModal = (props: any) => {
columns={[...columns, ...operation]} columns={[...columns, ...operation]}
afterNewBtn={ afterNewBtn={
[<div className="flex items-center flex-wrap p-[10px] px-btnbase content-before bg-MAIN_BG "> [<div className="flex items-center flex-wrap p-[10px] px-btnbase content-before bg-MAIN_BG ">
<RangePicker <TimeRangeSelector
onChange={handleDatePickerChange} labelSize="small"
value={datePickerValue} /> bindRef={bindRef}
<div className="flex [&>.reset-btn]:!h-auto flex-nowrap items-center ml-[10px]"> hideBtns={['hour']}
<Button className="reset-btn" onClick={resetQuery}>{$t('重置')}</Button> defaultTimeButton="day"
initialTimeButton={timeButton}
onTimeButtonChange={setTimeButton}
initialDatePickerValue={datePickerValue}
onTimeRangeChange={handleTimeRangeChange} />
<div className="flex flex-nowrap items-center pt-btnybase">
<Button onClick={resetQuery}>{$t('重置')}</Button>
</div> </div>
</div>] </div>]
} }
@@ -239,7 +239,7 @@ export const SystemSubscriberConfig = forwardRef<SystemSubscriberConfigHandle,Sy
getSystemList() getSystemList()
}, [serviceId]); }, [serviceId]);
return (<WithPermission access="team.service.subscription.add"> return (<WithPermission access="">
<Form <Form
layout='vertical' layout='vertical'
labelAlign='left' labelAlign='left'
+2 -1
View File
@@ -77,7 +77,8 @@ export default defineConfig({
target: 'http://172.18.166.219:8288/', target: 'http://172.18.166.219:8288/',
changeOrigin: true, changeOrigin: true,
} }
} },
open: true
}, },
logLevel:'info' logLevel:'info'
}) })
@@ -11,7 +11,7 @@ import { RouterParams } from "@core/components/aoplatform/RenderRoutes";
import { SimpleTeamItem } from "@common/const/type"; import { SimpleTeamItem } from "@common/const/type";
import { useTenantManagementContext } from "../../../contexts/TenantManagementContext"; import { useTenantManagementContext } from "../../../contexts/TenantManagementContext";
import { Icon } from "@iconify/react/dist/iconify.js"; import { Icon } from "@iconify/react/dist/iconify.js";
import { useGlobalContext } from "@common/contexts/GlobalStateContext"; import { GlobalProvider, useGlobalContext } from "@common/contexts/GlobalStateContext";
import { $t } from "@common/locales"; import { $t } from "@common/locales";
import WithPermission from "@common/components/aoplatform/WithPermission"; import WithPermission from "@common/components/aoplatform/WithPermission";
import InsidePage from "@common/components/aoplatform/InsidePage"; import InsidePage from "@common/components/aoplatform/InsidePage";
@@ -149,7 +149,7 @@ export default function ServiceHubManagement() {
switch (type){ switch (type){
case 'add': case 'add':
title=$t('添加消费者') title=$t('添加消费者')
content=<ManagementConfig ref={addManagementRef} dataShowType={dataShowType} type={type} teamId={teamId!} /> content=<GlobalProvider><ManagementConfig ref={addManagementRef} dataShowType={dataShowType} type={type} teamId={teamId!} /></GlobalProvider>
break; break;
// case 'edit':{ // case 'edit':{
// title='配置 Open Api' // title='配置 Open Api'
-1
View File
@@ -20,7 +20,6 @@ func NewDynamicClient(client admin_client.Client, resource string) (*DynamicClie
cfg, has := gateway.GetDynamicResourceDriver(resource) cfg, has := gateway.GetDynamicResourceDriver(resource)
if !has { if !has {
return nil, errors.New("resource not found") return nil, errors.New("resource not found")
} }
return &DynamicClient{client: client, profession: cfg.Profession, driver: cfg.Driver}, nil return &DynamicClient{client: client, profession: cfg.Profession, driver: cfg.Driver}, nil
+4 -36
View File
@@ -57,42 +57,10 @@ var dynamicResourceMap = map[string]Worker{
Profession: ProfessionCertificate, Profession: ProfessionCertificate,
Driver: "server", Driver: "server",
}, },
//"openai": { "loki": {
// Profession: ProfessionAIProvider, Profession: ProfessionOutput,
// Driver: "openai", Driver: "loki",
//}, },
//"google": {
// Profession: ProfessionAIProvider,
// Driver: "google",
//},
//"anthropic": {
// Profession: ProfessionAIProvider,
// Driver: "anthropic",
//},
//"moonshot": {
// Profession: ProfessionAIProvider,
// Driver: "moonshot",
//},
//"tongyi": {
// Profession: ProfessionAIProvider,
// Driver: "tongyi",
//},
//"zhipuai": {
// Profession: ProfessionAIProvider,
// Driver: "zhipuai",
//},
//"fireworks": {
// Profession: ProfessionAIProvider,
// Driver: "fireworks",
//},
//"novita": {
// Profession: ProfessionAIProvider,
// Driver: "novita",
//},
//"mistralai": {
// Profession: ProfessionAIProvider,
// Driver: "mistralai",
//},
} }
type Worker struct { type Worker struct {
+1 -1
View File
@@ -7,7 +7,7 @@ var (
) )
type IFactory interface { type IFactory interface {
Create(config string) (ILogDriver, error) Create(config string) (ILogDriver, map[string]interface{}, error)
} }
type factoryManager struct { type factoryManager struct {
+21 -7
View File
@@ -11,6 +11,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/eolinker/eosc/log"
log_driver "github.com/APIParkLab/APIPark/log-driver" log_driver "github.com/APIParkLab/APIPark/log-driver"
) )
@@ -21,7 +23,7 @@ func init() {
type factory struct { type factory struct {
} }
func (f *factory) Create(config string) (log_driver.ILogDriver, error) { func (f *factory) Create(config string) (log_driver.ILogDriver, map[string]interface{}, error) {
return NewDriver(config) return NewDriver(config)
} }
@@ -35,24 +37,27 @@ type Driver struct {
headers map[string]string headers map[string]string
} }
func NewDriver(config string) (*Driver, error) { func NewDriver(config string) (*Driver, map[string]interface{}, error) {
cfg := new(DriverConfig) cfg := new(DriverConfig)
err := json.Unmarshal([]byte(config), cfg) err := json.Unmarshal([]byte(config), cfg)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
err = cfg.Check() err = cfg.Check()
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
headers := map[string]string{} headers := map[string]string{}
for _, h := range cfg.Header { for _, h := range cfg.Header {
headers[h.Key] = h.Value headers[h.Key] = h.Value
} }
return &Driver{ return &Driver{
url: cfg.URL, url: cfg.URL,
headers: headers, headers: headers,
}, nil }, map[string]interface{}{
"url": cfg.URL,
"headers": headers,
}, nil
} }
func (d *Driver) LogInfo(clusterId string, id string) (*log_driver.LogInfo, error) { func (d *Driver) LogInfo(clusterId string, id string) (*log_driver.LogInfo, error) {
@@ -66,6 +71,8 @@ func (d *Driver) LogInfo(clusterId string, id string) (*log_driver.LogInfo, erro
queries.Set("start", strconv.FormatInt(start.UnixNano(), 10)) queries.Set("start", strconv.FormatInt(start.UnixNano(), 10))
queries.Set("end", strconv.FormatInt(now.UnixNano(), 10)) queries.Set("end", strconv.FormatInt(now.UnixNano(), 10))
queries.Set("limit", "1") queries.Set("limit", "1")
log.Debug("query is ", queries.Get("query"))
list, err := send[LogInfo](http.MethodGet, fmt.Sprintf("%s/loki/api/v1/query_range", d.url), d.headers, queries, "") list, err := send[LogInfo](http.MethodGet, fmt.Sprintf("%s/loki/api/v1/query_range", d.url), d.headers, queries, "")
if err != nil { if err != nil {
return nil, err return nil, err
@@ -100,10 +107,13 @@ func (d *Driver) LogCount(clusterId string, conditions map[string]string, spendH
} }
queries := url.Values{} queries := url.Values{}
queries.Set("query", fmt.Sprintf("sum(count_over_time({cluster=\"%s\"} | json %s [%dh])) by (%s)", clusterId, tmpCondition, spendHour, group)) queries.Set("query", fmt.Sprintf("sum(count_over_time({cluster=\"%s\"} | json %s [%dh])) by (%s)", clusterId, tmpCondition, spendHour, group))
sendRequestTime := time.Now()
list, err := send[LogCount](http.MethodGet, fmt.Sprintf("%s/loki/api/v1/query", d.url), d.headers, queries, "") list, err := send[LogCount](http.MethodGet, fmt.Sprintf("%s/loki/api/v1/query", d.url), d.headers, queries, "")
if err != nil { if err != nil {
return nil, err return nil, err
} }
log.DebugF("send request spend time: %v", time.Now().Sub(sendRequestTime))
log.Debug("query is ", queries.Get("query"))
result := make(map[string]int64) result := make(map[string]int64)
for _, l := range list { for _, l := range list {
if len(l.Value) != 2 { if len(l.Value) != 2 {
@@ -158,6 +168,7 @@ func (d *Driver) Logs(clusterId string, conditions map[string]string, start time
queries.Set("limit", strconv.FormatInt(limit, 10)) queries.Set("limit", strconv.FormatInt(limit, 10))
queries.Set("direction", "backward") queries.Set("direction", "backward")
queries.Set("start", strconv.FormatInt(start.UnixNano(), 10)) queries.Set("start", strconv.FormatInt(start.UnixNano(), 10))
log.Debug("query is ", queries.Get("query"))
logs, err := d.recuseLogs(queries, end, offset) logs, err := d.recuseLogs(queries, end, offset)
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
@@ -259,10 +270,13 @@ func send[T any](method string, uri string, headers map[string]string, queries u
for key, value := range headers { for key, value := range headers {
req.Header.Set(key, value) req.Header.Set(key, value)
} }
log.DebugF("do request: %s", uri)
doRequestTime := time.Now()
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to send request: %w", err) return nil, fmt.Errorf("failed to send request: %w", err)
} }
log.DebugF("do request spend time: %v", time.Now().Sub(doRequestTime))
defer resp.Body.Close() defer resp.Body.Close()
respData, err := io.ReadAll(resp.Body) respData, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
+1 -1
View File
@@ -34,7 +34,7 @@ func newAIUpstream(provider string, uri model_runtime.IProviderURI) *gateway.Dyn
"driver": "http", "driver": "http",
"balance": "round-robin", "balance": "round-robin",
"nodes": []string{fmt.Sprintf("%s weight=100", uri.Host())}, "nodes": []string{fmt.Sprintf("%s weight=100", uri.Host())},
"pass_node": "node", "pass_host": "node",
"scheme": uri.Scheme(), "scheme": uri.Scheme(),
"timeout": 300000, "timeout": 300000,
}, },
@@ -26,8 +26,7 @@ type Config struct {
} }
func (a *Config) ID() string { func (a *Config) ID() string {
//TODO implement me return a.Ak
panic("implement me")
} }
func (a *Config) Valid() ([]byte, error) { func (a *Config) Valid() ([]byte, error) {
+3
View File
@@ -99,6 +99,9 @@ func (i *imlAuthorizationModule) getApplications(ctx context.Context, appIds []s
Expire: a.ExpireTime, Expire: a.ExpireTime,
Config: authCfg, Config: authCfg,
HideCredential: a.HideCredential, HideCredential: a.HideCredential,
Label: map[string]string{
"authorization": a.UUID,
},
} }
}), }),
} }
+167 -5
View File
@@ -4,6 +4,13 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
"time"
log_driver "github.com/APIParkLab/APIPark/log-driver"
"github.com/APIParkLab/APIPark/gateway"
"github.com/eolinker/go-common/store"
"gorm.io/gorm" "gorm.io/gorm"
@@ -13,15 +20,64 @@ import (
log_dto "github.com/APIParkLab/APIPark/module/log/dto" log_dto "github.com/APIParkLab/APIPark/module/log/dto"
"github.com/APIParkLab/APIPark/service/log" "github.com/APIParkLab/APIPark/service/log"
log_print "github.com/eolinker/eosc/log"
) )
var _ ILogModule = (*imlLogModule)(nil) var _ ILogModule = (*imlLogModule)(nil)
type imlLogModule struct { type imlLogModule struct {
service log.ILogService `autowired:""` service log.ILogService `autowired:""`
clusterService cluster.IClusterService `autowired:""`
transaction store.ITransaction `autowired:""`
}
var labels = map[string]string{
"cluster": "$cluster",
"node": "$node",
}
var logFormatter = map[string]interface{}{
"fields": []string{
"$msec",
"$service",
"$provider",
"$scheme as request_scheme",
"$url as request_uri",
"$host as request_host",
"$header as request_header",
"$remote_addr",
"$request_body",
"$proxy_body",
"$proxy_method",
"$proxy_scheme",
"$proxy_uri",
"$api",
"$proxy_host",
"$proxy_header",
"$proxy_addr",
"$response_headers",
"$status",
"$content_type",
"$proxy_status",
"$request_time",
"$response_time",
"$node",
"$cluster",
"$application",
"$src_ip",
"$block_name as strategy",
"$request_id",
"$request_method",
"$authorization",
"$response_body",
"$proxy_response_body",
},
} }
func (i *imlLogModule) Save(ctx context.Context, driver string, input *log_dto.Save) error { func (i *imlLogModule) Save(ctx context.Context, driver string, input *log_dto.Save) error {
factory, has := log_driver.GetFactory(driver)
if !has {
return errors.New("driver not found")
}
input.Cluster = cluster.DefaultClusterID input.Cluster = cluster.DefaultClusterID
var cfg *string var cfg *string
if input.Config != nil { if input.Config != nil {
@@ -29,10 +85,57 @@ func (i *imlLogModule) Save(ctx context.Context, driver string, input *log_dto.S
tmp := string(data) tmp := string(data)
cfg = &tmp cfg = &tmp
} }
return i.service.UpdateLogSource(ctx, driver, &log.Save{ return i.transaction.Transaction(ctx, func(txCtx context.Context) error {
ID: input.ID, err := i.service.UpdateLogSource(txCtx, driver, &log.Save{
Cluster: &input.Cluster, ID: input.ID,
Config: cfg, Cluster: &input.Cluster,
Config: cfg,
})
if err != nil {
return err
}
info, err := i.service.GetLogSource(txCtx, driver)
if err != nil {
return err
}
d, c, err := factory.Create(info.Config)
if err != nil {
return err
}
client, err := i.clusterService.GatewayClient(txCtx, input.Cluster)
if err != nil {
return err
}
defer client.Close(txCtx)
dynamicClient, err := client.Dynamic(driver)
if err != nil {
return err
}
attr := make(map[string]interface{})
attr["driver"] = driver
attr["formatter"] = logFormatter
attr["labels"] = labels
attr["method"] = "POST"
attr["scopes"] = []string{"access_log"}
attr["type"] = "json"
for k, v := range c {
attr[k] = v
}
err = dynamicClient.Online(txCtx, &gateway.DynamicRelease{
BasicItem: &gateway.BasicItem{
ID: driver,
Description: "collect access log",
Version: time.Now().Format("20060102150405"),
Resource: gateway.ProfessionOutput,
},
Attr: attr,
})
if err != nil {
return err
}
log_driver.SetDriver(driver, d)
return nil
}) })
} }
@@ -60,3 +163,62 @@ func (i *imlLogModule) Get(ctx context.Context, driver string) (*log_dto.LogSour
UpdateAt: auto.TimeLabel(info.UpdateAt), UpdateAt: auto.TimeLabel(info.UpdateAt),
}, nil }, nil
} }
func (i *imlLogModule) OnComplete() {
}
func (i *imlLogModule) initGateway(ctx context.Context, clusterId string, clientDriver gateway.IClientDriver) error {
drivers := log_driver.Drivers()
if len(drivers) < 1 {
return nil
}
for _, driver := range drivers {
factory, has := log_driver.GetFactory(driver)
if !has {
log_print.Errorf("driver %s not found", driver)
continue
}
info, err := i.service.GetLogSource(ctx, driver)
if err != nil {
log_print.Errorf("get log source %s error: %s", driver, err)
continue
}
d, c, err := factory.Create(info.Config)
if err != nil {
log_print.Errorf("create driver %s error: %s,config: %s", driver, err, info.Config)
continue
}
log_driver.SetDriver(driver, d)
dynamicClient, err := clientDriver.Dynamic(driver)
if err != nil {
log_print.Errorf("get dynamic client %s error: %s", driver, err)
continue
}
attr := make(map[string]interface{})
attr["driver"] = driver
attr["formatter"] = logFormatter
attr["labels"] = labels
attr["method"] = "POST"
for k, v := range c {
attr[k] = v
}
err = dynamicClient.Online(ctx, &gateway.DynamicRelease{
BasicItem: &gateway.BasicItem{
ID: driver,
Description: "collect access log",
Version: time.Now().Format("20060102150405"),
Resource: gateway.ProfessionOutput,
},
Attr: attr,
})
if err != nil {
log_print.Errorf("online driver %s error: %s", driver, err)
continue
}
}
return nil
}
+6 -1
View File
@@ -6,6 +6,7 @@ import (
"github.com/eolinker/go-common/autowire" "github.com/eolinker/go-common/autowire"
"github.com/APIParkLab/APIPark/gateway"
log_dto "github.com/APIParkLab/APIPark/module/log/dto" log_dto "github.com/APIParkLab/APIPark/module/log/dto"
) )
@@ -15,7 +16,11 @@ type ILogModule interface {
} }
func init() { func init() {
logModule := new(imlLogModule)
autowire.Auto[ILogModule](func() reflect.Value { autowire.Auto[ILogModule](func() reflect.Value {
return reflect.ValueOf(new(imlLogModule))
gateway.RegisterInitHandleFunc(logModule.initGateway)
return reflect.ValueOf(logModule)
}) })
} }
+33
View File
@@ -359,3 +359,36 @@ func (i *imlStrategyModule) Delete(ctx context.Context, id string) error {
} }
return i.strategyService.SortDelete(ctx, id) return i.strategyService.SortDelete(ctx, id)
} }
func (i *imlStrategyModule) initGateway(ctx context.Context, clusterId string, clientDriver gateway.IClientDriver) error {
commits, err := i.strategyService.ListLatestStrategyCommit(ctx, strategy_dto.ScopeGlobal, "")
if err != nil {
return err
}
publishStrategies := make([]*eosc.Base[gateway.StrategyRelease], 0, len(commits))
for _, c := range commits {
l := c.Data
if l.IsDelete {
err = i.strategyService.Delete(ctx, l.Id)
if err != nil {
return err
}
}
d, has := strategy_driver.GetDriver(l.Driver)
if !has {
continue
}
publishStrategies = append(publishStrategies, d.ToRelease(strategy_dto.ToStrategy(&strategy.Strategy{
Id: l.Id,
Name: l.Name,
Priority: l.Priority,
Filters: l.Filters,
Config: l.Config,
Driver: l.Driver,
IsStop: l.IsStop,
IsDelete: l.IsDelete,
}), nil, 5000))
}
return clientDriver.Strategy().Online(ctx, publishStrategies...)
}
+3
View File
@@ -5,6 +5,8 @@ import (
"reflect" "reflect"
"time" "time"
"github.com/APIParkLab/APIPark/gateway"
"github.com/eolinker/go-common/autowire" "github.com/eolinker/go-common/autowire"
_ "github.com/APIParkLab/APIPark/module/strategy/driver/data-masking" _ "github.com/APIParkLab/APIPark/module/strategy/driver/data-masking"
@@ -32,6 +34,7 @@ type IStrategyModule interface {
func init() { func init() {
strategyModule := new(imlStrategyModule) strategyModule := new(imlStrategyModule)
autowire.Auto[IStrategyModule](func() reflect.Value { autowire.Auto[IStrategyModule](func() reflect.Value {
gateway.RegisterInitHandleFunc(strategyModule.initGateway)
return reflect.ValueOf(strategyModule) return reflect.ValueOf(strategyModule)
}) })
} }
+3
View File
@@ -162,6 +162,9 @@ func (i *imlAPIService) Save(ctx context.Context, id string, model *Edit) error
if model.Disable != nil { if model.Disable != nil {
ev.Disable = *model.Disable ev.Disable = *model.Disable
} }
if model.Upstream != nil {
ev.Upstream = *model.Upstream
}
e := i.apiInfoStore.Save(ctx, ev) e := i.apiInfoStore.Save(ctx, ev)
if e != nil { if e != nil {
+16 -15
View File
@@ -5,6 +5,8 @@ import (
"errors" "errors"
"time" "time"
log_print "github.com/eolinker/eosc/log"
"github.com/google/uuid" "github.com/google/uuid"
log_driver "github.com/APIParkLab/APIPark/log-driver" log_driver "github.com/APIParkLab/APIPark/log-driver"
@@ -26,29 +28,31 @@ type imlLogService struct {
func (i *imlLogService) OnComplete() { func (i *imlLogService) OnComplete() {
drivers := log_driver.Drivers() drivers := log_driver.Drivers()
for _, d := range drivers { if len(drivers) < 1 {
factory, has := log_driver.GetFactory(d) return
}
ctx := context.Background()
for _, driver := range drivers {
factory, has := log_driver.GetFactory(driver)
if !has { if !has {
log_print.Errorf("driver %s not found", driver)
continue continue
} }
s, err := i.GetLogSource(context.Background(), d) info, err := i.GetLogSource(ctx, driver)
if err != nil { if err != nil {
log_print.Errorf("get log source %s error: %s", driver, err)
continue continue
} }
driver, err := factory.Create(s.Config) d, _, err := factory.Create(info.Config)
if err != nil { if err != nil {
log_print.Errorf("create driver %s error: %s,config: %s", driver, err, info.Config)
continue continue
} }
log_driver.SetDriver(d, driver) log_driver.SetDriver(driver, d)
} }
} }
func (i *imlLogService) UpdateLogSource(ctx context.Context, driver string, input *Save) error { func (i *imlLogService) UpdateLogSource(ctx context.Context, driver string, input *Save) error {
factory, has := log_driver.GetFactory(driver)
if !has {
return errors.New("driver not found")
}
s, err := i.store.First(ctx, map[string]interface{}{"driver": driver}) s, err := i.store.First(ctx, map[string]interface{}{"driver": driver})
if err != nil { if err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) { if !errors.Is(err, gorm.ErrRecordNotFound) {
@@ -83,15 +87,12 @@ func (i *imlLogService) UpdateLogSource(ctx context.Context, driver string, inpu
s.Updater = utils.UserId(ctx) s.Updater = utils.UserId(ctx)
s.UpdateAt = time.Now() s.UpdateAt = time.Now()
} }
newDriver, err := factory.Create(s.Config)
if err != nil {
return err
}
err = i.store.Save(ctx, s) err = i.store.Save(ctx, s)
if err != nil { if err != nil {
return err return err
} }
log_driver.SetDriver(driver, newDriver)
return nil return nil
} }