fix: 收口管理端页面权限与工作流运行授权

- 页面选项接口改用所属页面权限并返回最小数据视图

- 统一校验工作流引用、租户、状态与定时任务执行主体

- 补充聊天记录权限迁移和权限隔离回归测试
This commit is contained in:
2026-08-07 12:51:21 +08:00
parent 6ad004da9b
commit d244a0404d
55 changed files with 3350 additions and 387 deletions

View File

@@ -151,9 +151,7 @@ async function initializeWorkflow() {
}
await Promise.all([
loadCustomNode(),
getLlmList(),
getKnowledgeList(),
getCodeEngineList(),
getDesignerOptions(),
getWorkflowInfo(workflowId.value),
]);
showTinyFlow.value = true;
@@ -574,24 +572,14 @@ function reconcileWorkflowDraftAfterSave(savedContentSignature: string) {
pendingDraftContent = normalizedContent;
persistPendingWorkflowDraft();
}
async function getLlmList() {
return api.get('/api/v1/model/list').then((res) => {
llmList.value = res.data;
});
}
async function getKnowledgeList() {
return api.get('/api/v1/documentCollection/list').then((res) => {
knowledgeList.value = res.data;
});
}
async function getCodeEngineList() {
return api.get('/api/v1/workflow/supportedCodeEngines').then((res) => {
if (
res?.errorCode === 0 &&
Array.isArray(res.data) &&
res.data.length > 0
) {
codeEngineList.value = res.data;
async function getDesignerOptions() {
return api.get('/api/v1/workflow/designer/options').then((res) => {
llmList.value = Array.isArray(res.data?.models) ? res.data.models : [];
knowledgeList.value = Array.isArray(res.data?.knowledges)
? res.data.knowledges
: [];
if (Array.isArray(res.data?.codeEngines) && res.data.codeEngines.length > 0) {
codeEngineList.value = res.data.codeEngines;
}
});
}
@@ -785,7 +773,7 @@ function handleChoose(nodeName: string, value: any) {
function handleWorkflowNodeUpdate(chooseId: any) {
pageLoading.value = true;
api
.get('/api/v1/workflowNode/getChainParams', {
.get('/api/v1/workflow/designer/childWorkflow', {
params: {
currentId: workflowId.value,
workflowId: chooseId,
@@ -799,7 +787,7 @@ function handleWorkflowNodeUpdate(chooseId: any) {
function handlePluginNodeUpdate(chooseId: any) {
pageLoading.value = true;
api
.get('/api/v1/pluginItem/getTinyFlowData', {
.get('/api/v1/workflow/designer/pluginTinyFlow', {
params: {
id: chooseId,
},
@@ -848,7 +836,7 @@ function onAsyncExecute(info: any) {
:title="$t('menus.ai.plugin')"
width="730"
ref="pluginSelectRef"
page-url="/api/v1/plugin/page?availableOnly=true"
page-url="/api/v1/workflow/designer/plugins"
:has-parent="true"
single-select
@get-data="(v) => handleChoose(nodeNames.pluginNode, v)"

View File

@@ -201,7 +201,7 @@ const actions: ActionButton[] = [
icon: Share,
text: $t('button.share'),
permission: '/api/v1/workflow/save',
placement: 'inline',
placement: 'menu',
disabled: (row: any) =>
row.publishStatus !== 'PUBLISHED' || sharingWorkflowId.value === row.id,
loading: (row: any) => sharingWorkflowId.value === row.id,

View File

@@ -49,6 +49,8 @@ const createDefaultEntity = () => ({
});
const entity = ref<any>(createDefaultEntity());
const btnLoading = ref(false);
const categoryOptions = ref<any[]>([]);
let categoryOptionsLoaded = false;
const visibilityScopeOptions = computed(() => [
{
label: $t('aiWorkflow.visibilityScopePrivate'),
@@ -88,7 +90,7 @@ const rules = computed(() => ({
}),
}));
// functions
function openDialog(row: any, importMode = false) {
async function openDialog(row: any, importMode = false) {
isImport.value = importMode;
isAdd.value = !row?.id;
entity.value = {
@@ -96,6 +98,15 @@ function openDialog(row: any, importMode = false) {
...row,
};
dialogVisible.value = true;
if (!categoryOptionsLoaded) {
try {
const res = await api.get('/api/v1/workflowCategory/visibleList');
categoryOptions.value = Array.isArray(res.data) ? res.data : [];
categoryOptionsLoaded = true;
} catch {
categoryOptions.value = [];
}
}
}
const beforeUpload: UploadProps['beforeUpload'] = (file) => {
@@ -227,10 +238,14 @@ function closeDialog() {
<ElInput v-model.trim="entity.title" />
</ElFormItem>
<ElFormItem prop="categoryId" :label="$t('aiWorkflow.categoryId')">
<DictSelect
v-model="entity.categoryId"
dict-code="aiWorkFlowCategory"
/>
<ElSelect v-model="entity.categoryId" clearable filterable>
<ElOption
v-for="category in categoryOptions"
:key="category.id"
:label="category.categoryName"
:value="category.id"
/>
</ElSelect>
</ElFormItem>
<ElFormItem
prop="visibilityScope"

View File

@@ -76,17 +76,12 @@ function dedupeManagedDatasetOptions(options: ManagedDatasetOption[]) {
export async function loadManagedDatasetOptions(): Promise<
ManagedDatasetOption[]
> {
const sourceRes = await api.get('/api/v1/datacenterSource/page', {
params: {
pageNumber: 1,
pageSize: 200,
},
});
const sources = sourceRes.data?.records || [];
const sourceRes = await api.get('/api/v1/workflow/designer/dataSources');
const sources = sourceRes.data || [];
const options: ManagedDatasetOption[] = [];
for (const source of sources) {
try {
const catalogRes = await api.get('/api/v1/datacenterSource/catalogs', {
const catalogRes = await api.get('/api/v1/workflow/designer/catalogs', {
params: {
sourceId: source.id,
},
@@ -94,7 +89,7 @@ export async function loadManagedDatasetOptions(): Promise<
const catalogs = catalogRes.data || [];
for (const catalog of catalogs) {
const tableRes = await api.get(
'/api/v1/datacenterDataset/managedTables',
'/api/v1/workflow/designer/managedTables',
{
params: {
sourceId: source.id,
@@ -176,7 +171,7 @@ export async function loadManagedDatasetSchema(
fields: [],
};
}
const res = await api.get('/api/v1/datacenterDataset/schema', {
const res = await api.get('/api/v1/workflow/designer/schema', {
params: datasetRef,
});
const data = res.data || {};
@@ -188,8 +183,8 @@ export async function loadManagedDatasetSchema(
}))
: [];
return {
tableName: data.table?.tableName || datasetRef.tableName,
tableDesc: data.table?.tableDesc,
tableName: data.tableName || datasetRef.tableName,
tableDesc: data.tableDesc,
fields,
};
}