Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions src/common/network/ai.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright 2025 OceanBase
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import request from '@/util/request';

/**
* AI功能状态响应接口
*/
export interface IAIStatusResponse {
/** AI功能是否启用 */
enabled: boolean;
/** AI功能是否可用 */
available: boolean;
/** 使用的AI模型 */
model: string;
/** API基础URL */
baseUrl: string;
/** API密钥是否已配置 */
apiKeyConfigured: boolean;
}

/**
* 查询AI功能状态
* @returns AI功能状态信息
*/
export async function getAIStatus(): Promise<IAIStatusResponse> {
const res = await request.get('/api/v2/ai/status');
return res?.data;
}

/**
* 检查AI功能是否可用
* @returns 是否可用
*/
export async function isAIAvailable(): Promise<boolean> {
try {
const status = await getAIStatus();
return status.enabled && status.available;
} catch (error) {
console.warn('Failed to check AI status:', error);
return false;
}
}
83 changes: 80 additions & 3 deletions src/common/network/sensitiveColumn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export async function startScanning(
databaseIds: number[];
allSensitiveRules: boolean;
sensitiveRuleIds: number[];
scanningMode?: string;
},
): Promise<string> {
const ret = await request.post(
Expand All @@ -41,6 +42,70 @@ export async function startScanning(
return ret?.data?.taskId;
}

// 新增单表扫描API
export async function scanSingleTableAsync(
projectId: number,
params: {
databaseId: number;
tableName: string;
scanningMode?: string;
},
): Promise<string> {
// 导入login store来检查organizationId
const login = require('@/store/login').default;
console.log('发起单表扫描请求:', { projectId, params });
console.log('当前organizationId:', login?.organizationId);
console.log('当前用户信息:', login?.user);

try {
const ret = await request.post(
`/api/v2/collaboration/projects/${projectId}/sensitiveColumns/scanSingleTableAsync`,
{
data: {
...params,
},
params: {
currentOrganizationId: login?.organizationId,
},
},
);
console.log('单表扫描请求响应:', ret);
// 后端直接返回taskId在data字段中,而不是data.taskId
const taskId = ret?.data?.taskId || ret?.data;
if (!taskId) {
console.error('单表扫描请求返回的taskId为空:', ret);
}
return taskId;
} catch (error) {
console.error('单表扫描请求失败:', error);
throw error;
}
}

// 获取单表扫描结果
export async function getSingleTableScanResult(projectId: number, taskId: string): Promise<any> {
// 导入login store来检查organizationId
const login = require('@/store/login').default;
console.log('获取单表扫描结果:', { projectId, taskId });
console.log('当前organizationId:', login?.organizationId);

try {
const ret = await request.get(
`/api/v2/collaboration/projects/${projectId}/sensitiveColumns/singleTableScan/${taskId}/result`,
{
params: {
currentOrganizationId: login?.organizationId,
},
},
);
console.log('单表扫描结果响应:', ret);
return ret?.data;
} catch (error) {
console.error('获取单表扫描结果失败:', error);
throw error;
}
}

export async function setEnabled(
projectId: number,
id: number,
Expand Down Expand Up @@ -95,6 +160,7 @@ export enum ScannResultType {
RUNNING = 'RUNNING',
SUCCESS = 'SUCCESS',
FAILED = 'FAILED',
CANCELLED = 'CANCELLED',
}
export interface IScannResult {
taskId: string;
Expand All @@ -110,9 +176,20 @@ export interface IScannResult {

export async function getScanningResults(projectId: number, taskId: string): Promise<IScannResult> {
const ret = await request.get(
`/api/v2/collaboration/projects/${projectId}/sensitiveColumns/getScanningResults?taskId=${encodeURIComponent(
taskId,
)}`,
`/api/v2/collaboration/projects/${projectId}/sensitiveColumns/getScanningResults`,
{
params: { taskId },
},
);
return ret?.data;
}

export async function stopScanning(projectId: number, taskId: string): Promise<boolean> {
const ret = await request.post(
`/api/v2/collaboration/projects/${projectId}/sensitiveColumns/stopScanning`,
{
params: { taskId },
},
);
return ret?.data;
}
Expand Down
96 changes: 96 additions & 0 deletions src/component/SensitiveColumnIndicator/index.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
.indicator {
display: inline-flex;
align-items: center;
cursor: pointer;
padding: 2px 6px;
border-radius: 3px;
transition: all 0.15s ease;
font-size: 12px;

&:hover {
background-color: rgba(0, 0, 0, 0.04);
transform: none;
}
}

.tooltipContent {
max-width: 300px;
background: #fff;
color: #000;
border-radius: 6px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
border: 1px solid #e8e8e8;
padding: 12px;

.tooltipTitle {
font-weight: 500;
margin-bottom: 8px;
color: #000;
}

.tooltipItem {
margin-bottom: 4px;
color: #333;

&:last-child {
margin-bottom: 0;
}
}

.tooltipDetails {
margin-top: 12px;
padding-top: 8px;
border-top: 1px solid #e8e8e8;

.tooltipSubtitle {
font-weight: 500;
margin-bottom: 6px;
color: #333;
font-size: 12px;
}

.tooltipDetailItem {
margin-bottom: 4px;
font-size: 12px;
color: #666;

&:last-child {
margin-bottom: 0;
}
}
}
}

.summary {
margin-top: 8px;
padding-top: 8px;
border-top: 1px solid #f0f0f0;
font-size: 12px;
color: #666;
text-align: center;
}

.scanInfo {
margin-top: 4px;
font-size: 11px;
color: #999;
font-style: italic;
}

// Tooltip 全局样式覆盖
:global(.ant-tooltip-inner) {
.tooltipContent {
background: #fff !important;
border-radius: 6px !important;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
border: 1px solid #e8e8e8 !important;
padding: 12px !important;
}
}

// 响应式调整
@media (max-width: 768px) {
.tooltipContent {
max-width: 250px;
}
}
Loading