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
36 changes: 33 additions & 3 deletions packages/global/core/dataset/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,33 @@ import { i18nT } from '../../common/i18n/utils';

/* ------------ dataset -------------- */
/**
* 知识库训练审计事件;持久化复用 support/user/audit 的通用审计框架。
* 知识库审计事件(集合 + 训练);持久化复用 support/user/audit 的通用审计框架。
*
* value 采用与集合审计一致的点分小写路径:前半段是最多三级资源层级,最后一段是动词原形动作
* (retry / delete / rebuild);成员名保持仓库惯例的大写下划线。
* value 采用 OpenAI Audit Logs 风格的点分小写路径:前半段是资源层级
* (`dataset.collection` / `dataset.training` / `dataset.index`),最后一段是动词原形动作
* (create / update / sync / export / add / remove / retry / delete / rebuild);
* 成员名保持仓库惯例的大写下划线。
* 值直接进入 operationLogs.event,修改会影响历史日志展示。
*/
export enum DatasetAuditEventEnum {
/** 创建集合;导入文件按「一个文件一条日志」逐条写入。 */
DATASET_COLLECTION_CREATE = 'dataset.collection.create',
/** 更新集合对象属性;一次请求命中多项变更时按变更逐条写入。 */
DATASET_COLLECTION_UPDATE = 'dataset.collection.update',
DATASET_COLLECTION_EXPORT = 'dataset.collection.export',
/** 同步集合:只记录发起的同步操作,不区分同步结果。 */
DATASET_COLLECTION_SYNC = 'dataset.collection.sync',
DATASET_COLLECTION_TAG_ADD = 'dataset.collection.tag.add',
DATASET_COLLECTION_TAG_REMOVE = 'dataset.collection.tag.remove',
/**
* 集合权限恢复为继承上级。
*
* 集合的权限继承与知识库 / 应用不同:它既可以继承父目录,也可以直接继承知识库;
* 且只有知识库启用 collection 级权限后才存在「独立态」。因此单独建一个事件,
* 不和 DATASET_COLLECTION_UPDATE 的子动作混在一起——它不改集合的可编辑属性,
* 只重置 ACL 快照,审计展示上需要单列一栏。
*/
DATASET_COLLECTION_PERMISSION_INHERITANCE_RESUME = 'dataset.collection.permission.inheritance_resume',
/** 知识库级全部重试:不指定集合,重试该知识库下所有失败训练任务。 */
DATASET_TRAINING_RETRY = 'dataset.training.retry',
/** 集合级全部重试:重试指定集合下的所有失败训练任务。 */
Expand All @@ -21,6 +41,16 @@ export enum DatasetAuditEventEnum {
DATASET_INDEX_REBUILD = 'dataset.index.rebuild'
}

/** 集合对象的可审计变更动作,区分同一次更新中的改名 / 移动 / 启停。 */
export enum DatasetContentAuditActionEnum {
RENAME = 'rename',
MOVE = 'move',
ENABLE = 'enable',
DISABLE = 'disable'
}

export type DatasetContentAuditAction = `${DatasetContentAuditActionEnum}`;

export enum DatasetTypeEnum {
folder = 'folder',
dataset = 'dataset',
Expand Down
6 changes: 4 additions & 2 deletions packages/global/support/user/audit/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,9 @@ export enum AuditEventEnum {
EXPORT_DATASET = 'EXPORT_DATASET',
CREATE_DATASET_FOLDER = 'CREATE_DATASET_FOLDER',
//Collection
/** @deprecated 创建集合已统一由 DatasetAuditEventEnum.DATASET_COLLECTION_CREATE 记录,保留枚举仅为兼容历史日志展示。 */
CREATE_COLLECTION = 'CREATE_COLLECTION',
/** @deprecated 集合更新已统一由 DatasetAuditEventEnum.DATASET_COLLECTION_UPDATE 记录,保留枚举仅为兼容历史日志展示。 */
UPDATE_COLLECTION = 'UPDATE_COLLECTION',
DELETE_COLLECTION = 'DELETE_COLLECTION',
RETRAIN_COLLECTION = 'RETRAIN_COLLECTION',
Expand Down Expand Up @@ -126,15 +128,15 @@ export type AdminAuditEventParamsType = {
[K in AdminAuditEventEnum]: (typeof adminAuditLogMap)[K]['params'];
};

/** 允许以 system 作用域写入的知识库审计事件(无操作人成员 ID)。 */
/** 允许以 system 作用域写入的知识库审计事件(集合 + 训练,无操作人成员 ID)。 */
export type DatasetAuditEvent = `${DatasetAuditEventEnum}`;

export type DatasetAuditEventParamsType = {
[K in DatasetAuditEventEnum]: (typeof auditLogMap)[K]['params'];
};

/**
* 团队审计事件全集:成员事件 + 知识库事件。
* 团队审计事件全集:成员事件 + 知识库事件(集合 + 训练)。
*
* Mongo enum、Zod 校验、共享类型与前端筛选菜单都从这里派生,避免各个消费方
* 各自维护一份事件列表而错配(曾导致知识库事件能写入、却读不出)。
Expand Down
4 changes: 2 additions & 2 deletions packages/global/test/openapi/support/user/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,11 +210,11 @@ describe('support user OpenAPI contracts', () => {
});

it('accepts every dataset audit event in filter and response', () => {
// 知识库事件与成员事件共用同一个审计存储;双方都必须出现在 TeamAuditEventList 里,
// 知识库事件(集合 + 训练)与成员事件共用同一个审计存储;双方都必须出现在 TeamAuditEventList 里,
// 否则新事件会「能写入但读不出」——团队审计列表在响应 schema 校验阶段整体失败。
const datasetEvents = Object.values(DatasetAuditEventEnum);

expect(datasetEvents).toHaveLength(5);
expect(datasetEvents).toHaveLength(12);
expect(TeamAuditEventList).toEqual(expect.arrayContaining(datasetEvents));

expect(AuditListBodySchema.parse({ events: datasetEvents }).events).toEqual(datasetEvents);
Expand Down
25 changes: 25 additions & 0 deletions packages/service/core/dataset/collection/audit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { DatasetCollectionTypeMap } from '@fastgpt/global/core/dataset/constants';
import { getDatasetCollectionPaths } from './paths';

/** 未知集合类型没有展示名,回退原始枚举值,避免审计里出现空对象类型。 */
export const getCollectionAuditObjectType = (type: string): string =>
DatasetCollectionTypeMap[type as keyof typeof DatasetCollectionTypeMap]?.name ?? type;

/**
* 生成集合审计日志的位置字段(`知识库名/父目录/子目录`),不包含集合自身。
*
* 位置只服务于日志展示:调用方此时已完成鉴权与写入,取路径失败不能影响主流程,
* 因此这里吞掉查询异常并退回知识库名。
*/
export const getCollectionAuditLocation = async ({
datasetName,
parentId
}: {
datasetName: string;
parentId?: string | null;
}): Promise<string> => {
const paths = await getDatasetCollectionPaths({ parentId: parentId ?? undefined }).catch(
() => []
);
return [datasetName, ...paths.map((path) => path.parentName)].filter(Boolean).join('/');
};
45 changes: 45 additions & 0 deletions packages/service/core/dataset/collection/paths.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type {
ParentIdType,
ParentTreePathItemType
} from '@fastgpt/global/common/parentFolder/type';
import { MongoDatasetCollection } from './schema';

/**
* 递归查询集合的祖先目录链(从根到直接父级,不包含集合自身)。
*
* 递归而非聚合:调用方都已经校验过父目录深度,层级有限;且每一层都命中 `_id` 主键查询。
* 集合不存在或父级断链时提前返回已收集的部分,不会抛错。
*
* 带环检测而非信任数据:历史脏数据可能出现父子互指,此时每层都是 `await` 主键查询,
* 不会爆栈而是无限查询下去,必须用 visited 兜底。命中环时保留已收集的链路并停止向上,
* 与「父级断链」语义保持一致——审计位置只用于展示,不能因此让主流程报错。
*/
export async function getDatasetCollectionPaths({
Comment thread
shortlight5980 marked this conversation as resolved.
parentId = ''
}: {
parentId?: string;
}): Promise<ParentTreePathItemType[]> {
const visited = new Set<string>();

async function find(parentId: ParentIdType): Promise<ParentTreePathItemType[]> {
if (!parentId) {
return [];
}
// 已访问过说明链路成环,直接截断,避免无限查询
if (visited.has(String(parentId))) {
return [];
}
visited.add(String(parentId));

const parent = await MongoDatasetCollection.findOne({ _id: parentId }, 'name parentId');

if (!parent) return [];

const paths = await find(parent.parentId ?? undefined);
paths.push({ parentId, parentName: parent.name });

return paths;
}

return find(parentId);
}
2 changes: 1 addition & 1 deletion packages/service/support/user/audit/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export function addAuditLog<T extends AdminAuditEventEnum>(
input: AddAuditLogInput<T, AdminAuditEventParamsType[T]>
): Promise<void>;

/** 知识库事件:事件名取 DatasetAuditEventEnum 的点分 value,参数类型按 DatasetAuditEventParamsType 校验。 */
/** 知识库事件(集合 + 训练):事件名取 DatasetAuditEventEnum 的点分 value,参数类型按 DatasetAuditEventParamsType 校验。 */
export function addAuditLog<T extends DatasetAuditEventEnum>(
input: AddAuditLogInput<T, DatasetAuditEventParamsType[T]>
): Promise<void>;
Expand Down
19 changes: 19 additions & 0 deletions packages/web/i18n/en/account_team.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,18 @@
"dataset_training_task_retry": "Retry single training task",
"dataset_training_record_delete": "Delete collection training record",
"dataset_index_rebuild": "Rebuild dataset index",
"dataset_collection_created": "Create a collection",
"dataset_collection_updated": "Update collection property",
"dataset_collection_exported": "Export collection content",
"dataset_collection_synced": "Sync collection",
"collection_tag_added": "Add collection tag",
"collection_tag_removed": "Remove collection tag",
"collection_permission_inheritance_resumed": "Restore collection inherited permissions",
"dataset_content_change_item_name": "Name",
"dataset_content_change_item_location": "Location",
"dataset_content_change_item_status": "Enable status",
"dataset_content_status_enabled": "Enabled",
"dataset_content_status_disabled": "Disabled",
"create_data": "Insert data",
"create_dataset": "Create Dataset",
"create_dataset_folder": "Create Dataset folder",
Expand Down Expand Up @@ -195,6 +207,7 @@
"log_dataset_training_task_retry": "[{{name}}] retried a single training task in collection [{{collectionName}}] of dataset [{{datasetName}}]",
"log_dataset_training_record_delete": "[{{name}}] deleted a training record in collection [{{collectionName}}] of dataset [{{datasetName}}]",
"log_dataset_index_rebuild": "[{{name}}] changed the index model of dataset [{{datasetName}}] from [{{oldModelName}}] to [{{newModelName}}] and rebuilt the index",
"log_dataset_collection_created": "[{{name}}] created [{{objectType}}] [{{collectionName}}] in [{{location}}].",
"log_create_data": "【{{name}}】 inserted data into the collection 【{{collectionName}}】 in the {{datasetType}} 【{{datasetName}}】",
"log_create_dataset": "【{{name}}】Created 【{{datasetType}}】 named 【{{datasetName}}】",
"log_create_dataset_folder": "【{{name}}】Created a folder named {{folderName}}】",
Expand All @@ -218,6 +231,7 @@
"log_export_app_chat_log": "【{{name}}】Export a chat history called [{{appName}}] called [{{appType}}]",
"log_export_bill_records": "【{{name}}】Export the billing record",
"log_export_dataset": "[{{name}}] Export [{{datasetType}}] called [{{datasetName}}]",
"log_dataset_collection_exported": "[{{name}}] exported the content of [{{collectionName}}] in dataset [{{datasetName}}].",
"log_export_evaluation": "{{name}} exported evaluation data for {{appType}} named {{appName}}",
"log_join_team": "【{{name}}】Join the team through the invitation link 【{{link}}】",
"log_kick_out_team": "{{name}} removed member {{memberName}}",
Expand All @@ -228,6 +242,10 @@
"log_recover_team_member": "【{{name}}】Restored member【{{memberName}}】",
"log_relocate_department": "【{{name}}】Displayed department【{{departmentName}}】",
"log_retrain_collection": "【{{name}}】 reindexed the Collection 【{{collectionName}}】 in the {{datasetType}} 【{{datasetName}}】",
"log_dataset_collection_synced": "[{{name}}] synced [{{collectionName}}] in dataset [{{datasetName}}].",
"log_collection_tag_added": "[{{name}}] added tag [{{tagName}}] to [{{collectionName}}] in dataset [{{datasetName}}].",
"log_collection_tag_removed": "[{{name}}] removed tag [{{tagName}}] from [{{collectionName}}] in dataset [{{datasetName}}].",
"log_collection_permission_inheritance_resumed": "[{{name}}] restored permissions of [{{collectionName}}] in dataset [{{datasetName}}] to inherit from [{{parentResource}}]",
"log_search_test": "【{{name}}】 ran a search test in the {{datasetType}} 【{{datasetName}}】",
"log_set_invoice_header": "【{{name}}】The invoice header operation was set up",
"log_time": "Operation time",
Expand All @@ -240,6 +258,7 @@
"log_update_app_info": "[{{name}}] updated [{{appType}}] named [{{appName}}]: [{{newItemNames}}] to [{{newItemValues}}]",
"log_update_app_publish_channel": "[{{name}}] Updated a channel named [{{channelName}}] for [{{appType}}] called [{{appName}}].",
"log_update_collection": "[{{name}}] Updated a collection named [{{collectionName}}] in [{{datasetType}}] called [{{datasetName}}].",
"log_dataset_collection_updated": "[{{name}}] changed the [{{changeItem}}] of [{{collectionName}}] in dataset [{{datasetName}}] from [{{oldValue}}] to [{{newValue}}].",
"log_update_data": "【{{name}}】 updated data in the collection 【{{collectionName}}】 in the {{datasetType}} 【{{datasetName}}】",
"log_update_dataset": "【{{name}}】Updated [{{datasetType}}] named [{{datasetName}}]",
"log_update_dataset_collaborator": "【{{name}}】 updated collaborators for the {{datasetType}} 【{{datasetName}}】: organizations 【{{orgList}}】, groups 【{{groupList}}】, members 【{{tmbList}}】; permissions: 【{{readPermission}}】, 【{{writePermission}}】, 【{{managePermission}}】",
Expand Down
19 changes: 19 additions & 0 deletions packages/web/i18n/ko-KR/account_team.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,18 @@
"dataset_training_task_retry": "단일 학습 작업 재시도",
"dataset_training_record_delete": "컬렉션 학습 기록 삭제",
"dataset_index_rebuild": "지식베이스 인덱스 재구축",
"dataset_collection_created": "컬렉션 생성",
"dataset_collection_updated": "컬렉션 속성 업데이트",
"dataset_collection_exported": "컬렉션 콘텐츠 내보내기",
"dataset_collection_synced": "컬렉션 동기화",
"collection_tag_added": "컬렉션 태그 추가",
"collection_tag_removed": "컬렉션 태그 제거",
"collection_permission_inheritance_resumed": "컬렉션 상속 권한 복원",
"dataset_content_change_item_name": "이름",
"dataset_content_change_item_location": "위치",
"dataset_content_change_item_status": "활성화 상태",
"dataset_content_status_enabled": "활성화",
"dataset_content_status_disabled": "비활성화",
"create_data": "데이터 삽입",
"create_dataset": "데이터셋 생성",
"create_dataset_folder": "데이터셋 폴더 생성",
Expand Down Expand Up @@ -187,6 +199,7 @@
"log_dataset_training_task_retry": "[{{name}}]님이 지식베이스 [{{datasetName}}]의 컬렉션 [{{collectionName}}]에 있는 단일 학습 작업을 재시도함.",
"log_dataset_training_record_delete": "[{{name}}]님이 지식베이스 [{{datasetName}}]의 컬렉션 [{{collectionName}}]에 있는 학습 기록을 삭제함.",
"log_dataset_index_rebuild": "[{{name}}]님이 지식베이스 [{{datasetName}}]의 인덱스 모델을 [{{oldModelName}}]에서 [{{newModelName}}](으)로 변경하고 인덱스를 재구축함.",
"log_dataset_collection_created": "[{{name}}]님이 [{{location}}]에 [{{objectType}}] [{{collectionName}}]을(를) 생성함.",
"log_create_data": "[{{name}}]님이 [{{datasetType}}]의 [{{datasetName}}]이라는 이름의 컬렉션에 [{{collectionName}}]이라는 이름의 컬렉션으로 데이터를 삽입함",
"log_create_dataset": "【{{name}}】【{{datasetName}}】라는 이름의 【{{datasetType}}】을(를) 생성함",
"log_create_dataset_folder": "【{{name}}】{{folderName}}】이라는 이름의 폴더를 생성함",
Expand All @@ -210,6 +223,7 @@
"log_export_app_chat_log": "【{{name}}】[{{appType}}]인 [{{appName}}]의 대화 기록을 내보냄",
"log_export_bill_records": "【{{name}}】청구 기록을 내보냄",
"log_export_dataset": "[{{name}}]님이 [{{datasetName}}]이라는 이름의 [{{datasetType}}]을(를) 내보냄",
"log_dataset_collection_exported": "[{{name}}]님이 지식베이스 [{{datasetName}}]의 [{{collectionName}}] 콘텐츠를 내보냄.",
"log_export_evaluation": "{{name}}님이 {{appName}}이라는 이름의 {{appType}}에 대한 평가 데이터를 내보냄",
"log_join_team": "【{{name}}】초대 링크【{{link}}】를 통해 팀에 가입함",
"log_kick_out_team": "{{name}}님이 멤버 {{memberName}}을(를) 제거함",
Expand All @@ -220,6 +234,10 @@
"log_recover_team_member": "【{{name}}】멤버【{{memberName}}】을(를) 복구함",
"log_relocate_department": "【{{name}}】부서【{{departmentName}}】를 이동함",
"log_retrain_collection": "[{{name}}]님이 [{{datasetName}}]이라는 이름의 [{{datasetType}}]에서 [{{collectionName}}]이라는 이름의 컬렉션을 재학습함.",
"log_dataset_collection_synced": "[{{name}}]님이 지식베이스 [{{datasetName}}]의 [{{collectionName}}]을(를) 동기화함.",
"log_collection_tag_added": "[{{name}}]님이 지식베이스 [{{datasetName}}]의 [{{collectionName}}]에 태그 [{{tagName}}]을(를) 추가함.",
"log_collection_tag_removed": "[{{name}}]님이 지식베이스 [{{datasetName}}]의 [{{collectionName}}]에서 태그 [{{tagName}}]을(를) 제거함.",
"log_collection_permission_inheritance_resumed": "[{{name}}]님이 지식베이스 [{{datasetName}}]의 [{{collectionName}}] 권한을 [{{parentResource}}]에서 상속하도록 복원함.",
"log_search_test": "【{{name}}】[{{datasetName}}]이라는 이름의 [{{datasetType}}]에 대해 검색 테스트를 수행함",
"log_set_invoice_header": "【{{name}}】세금계산서 상호 설정 작업을 수행함",
"log_time": "작업 시간",
Expand All @@ -232,6 +250,7 @@
"log_update_app_info": "[{{name}}]님이 [{{appName}}]이라는 이름의 [{{appType}}]을(를) 업데이트함: [{{newItemNames}}]을(를) [{{newItemValues}}](으)로",
"log_update_app_publish_channel": "[{{name}}]님이 [{{appName}}]이라는 이름의 [{{appType}}]에 대해 [{{channelName}}]이라는 이름의 채널을 업데이트함.",
"log_update_collection": "[{{name}}]님이 [{{datasetName}}]이라는 이름의 [{{datasetType}}]에서 [{{collectionName}}]이라는 이름의 컬렉션을 업데이트함.",
"log_dataset_collection_updated": "[{{name}}]님이 [{{datasetName}}]의 [{{collectionName}}]에 있는 [{{changeItem}}]을(를) [{{oldValue}}]에서 [{{newValue}}](으)로 변경함.",
"log_update_data": "【{{name}}】[{{datasetType}}]의 [{{collectionName}}]으로 【{{datasetName}}】이라는 이름의 컬렉션 내 데이터를 업데이트함",
"log_update_dataset": "【{{name}}】[{{datasetName}}]이라는 이름의 [{{datasetType}}]을(를) 업데이트함",
"log_update_dataset_collaborator": "【{{name}}】님이 【{{datasetName}}】 {{datasetType}}의 협업자를 조직 【{{orgList}}】, 그룹 【{{groupList}}】, 멤버 【{{tmbList}}】(으)로 변경하고 권한을 【{{readPermission}}】, 【{{writePermission}}】, 【{{managePermission}}】(으)로 변경함",
Expand Down
Loading
Loading