diff --git a/api/.ruff.toml b/api/.ruff.toml
index 41a24abad9..facb0d5419 100644
--- a/api/.ruff.toml
+++ b/api/.ruff.toml
@@ -43,6 +43,7 @@ select = [
"S307", # suspicious-eval-usage, disallow use of `eval` and `ast.literal_eval`
"S301", # suspicious-pickle-usage, disallow use of `pickle` and its wrappers.
"S302", # suspicious-marshal-usage, disallow use of `marshal` module
+ "S311", # suspicious-non-cryptographic-random-usage
]
ignore = [
diff --git a/api/core/helper/moderation.py b/api/core/helper/moderation.py
index 6a5982eca4..a324ac2767 100644
--- a/api/core/helper/moderation.py
+++ b/api/core/helper/moderation.py
@@ -1,5 +1,5 @@
import logging
-import random
+import secrets
from typing import cast
from core.app.entities.app_invoke_entities import ModelConfigWithCredentialsEntity
@@ -38,7 +38,7 @@ def check_moderation(tenant_id: str, model_config: ModelConfigWithCredentialsEnt
if len(text_chunks) == 0:
return True
- text_chunk = random.choice(text_chunks)
+ text_chunk = secrets.choice(text_chunks)
try:
model_provider_factory = ModelProviderFactory(tenant_id)
diff --git a/api/core/workflow/nodes/http_request/executor.py b/api/core/workflow/nodes/http_request/executor.py
index e28ac6343b..2c83b00d4a 100644
--- a/api/core/workflow/nodes/http_request/executor.py
+++ b/api/core/workflow/nodes/http_request/executor.py
@@ -1,8 +1,9 @@
import base64
import json
+import secrets
+import string
from collections.abc import Mapping
from copy import deepcopy
-from random import randint
from typing import Any, Literal
from urllib.parse import urlencode, urlparse
@@ -434,4 +435,4 @@ def _generate_random_string(n: int) -> str:
>>> _generate_random_string(5)
'abcde'
"""
- return "".join([chr(randint(97, 122)) for _ in range(n)])
+ return "".join(secrets.choice(string.ascii_lowercase) for _ in range(n))
diff --git a/api/libs/helper.py b/api/libs/helper.py
index 463ba3308b..e78a782fbe 100644
--- a/api/libs/helper.py
+++ b/api/libs/helper.py
@@ -1,7 +1,7 @@
import json
import logging
-import random
import re
+import secrets
import string
import subprocess
import time
@@ -176,7 +176,7 @@ def generate_string(n):
letters_digits = string.ascii_letters + string.digits
result = ""
for i in range(n):
- result += random.choice(letters_digits)
+ result += secrets.choice(letters_digits)
return result
diff --git a/api/services/account_service.py b/api/services/account_service.py
index ac84a46299..14d238467d 100644
--- a/api/services/account_service.py
+++ b/api/services/account_service.py
@@ -1,7 +1,6 @@
import base64
import json
import logging
-import random
import secrets
import uuid
from datetime import UTC, datetime, timedelta
@@ -261,7 +260,7 @@ class AccountService:
@staticmethod
def generate_account_deletion_verification_code(account: Account) -> tuple[str, str]:
- code = "".join([str(random.randint(0, 9)) for _ in range(6)])
+ code = "".join([str(secrets.randbelow(exclusive_upper_bound=10)) for _ in range(6)])
token = TokenManager.generate_token(
account=account, token_type="account_deletion", additional_data={"code": code}
)
@@ -429,7 +428,7 @@ class AccountService:
additional_data: dict[str, Any] = {},
):
if not code:
- code = "".join([str(random.randint(0, 9)) for _ in range(6)])
+ code = "".join([str(secrets.randbelow(exclusive_upper_bound=10)) for _ in range(6)])
additional_data["code"] = code
token = TokenManager.generate_token(
account=account, email=email, token_type="reset_password", additional_data=additional_data
@@ -456,7 +455,7 @@ class AccountService:
raise EmailCodeLoginRateLimitExceededError()
- code = "".join([str(random.randint(0, 9)) for _ in range(6)])
+ code = "".join([str(secrets.randbelow(exclusive_upper_bound=10)) for _ in range(6)])
token = TokenManager.generate_token(
account=account, email=email, token_type="email_code_login", additional_data={"code": code}
)
diff --git a/api/services/dataset_service.py b/api/services/dataset_service.py
index 133e3765f7..17232a9759 100644
--- a/api/services/dataset_service.py
+++ b/api/services/dataset_service.py
@@ -2,7 +2,7 @@ import copy
import datetime
import json
import logging
-import random
+import secrets
import time
import uuid
from collections import Counter
@@ -1137,7 +1137,7 @@ class DocumentService:
documents.append(document)
batch = document.batch
else:
- batch = time.strftime("%Y%m%d%H%M%S") + str(random.randint(100000, 999999))
+ batch = time.strftime("%Y%m%d%H%M%S") + str(100000 + secrets.randbelow(exclusive_upper_bound=900000))
# save process rule
if not dataset_process_rule:
process_rule = knowledge_config.process_rule
diff --git a/api/services/webapp_auth_service.py b/api/services/webapp_auth_service.py
index 79d5217de7..d83303056b 100644
--- a/api/services/webapp_auth_service.py
+++ b/api/services/webapp_auth_service.py
@@ -1,4 +1,4 @@
-import random
+import secrets
from datetime import UTC, datetime, timedelta
from typing import Any, Optional, cast
@@ -66,7 +66,7 @@ class WebAppAuthService:
if email is None:
raise ValueError("Email must be provided.")
- code = "".join([str(random.randint(0, 9)) for _ in range(6)])
+ code = "".join([str(secrets.randbelow(exclusive_upper_bound=10)) for _ in range(6)])
token = TokenManager.generate_token(
account=account, email=email, token_type="webapp_email_code_login", additional_data={"code": code}
)
diff --git a/api/tests/unit_tests/core/helper/test_ssrf_proxy.py b/api/tests/unit_tests/core/helper/test_ssrf_proxy.py
index c688d3952b..37749f0c66 100644
--- a/api/tests/unit_tests/core/helper/test_ssrf_proxy.py
+++ b/api/tests/unit_tests/core/helper/test_ssrf_proxy.py
@@ -1,4 +1,4 @@
-import random
+import secrets
from unittest.mock import MagicMock, patch
import pytest
@@ -34,7 +34,7 @@ def test_retry_logic_success(mock_request):
side_effects = []
for _ in range(SSRF_DEFAULT_MAX_RETRIES):
- status_code = random.choice(STATUS_FORCELIST)
+ status_code = secrets.choice(STATUS_FORCELIST)
mock_response = MagicMock()
mock_response.status_code = status_code
side_effects.append(mock_response)
diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/chartView.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/chartView.tsx
index 4afba06eae..32822e3315 100644
--- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/chartView.tsx
+++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/chartView.tsx
@@ -18,9 +18,10 @@ const queryDateFormat = 'YYYY-MM-DD HH:mm'
export type IChartViewProps = {
appId: string
+ headerRight: React.ReactNode
}
-export default function ChartView({ appId }: IChartViewProps) {
+export default function ChartView({ appId, headerRight }: IChartViewProps) {
const { t } = useTranslation()
const appDetail = useAppStore(state => state.appDetail)
const isChatApp = appDetail?.mode !== 'completion' && appDetail?.mode !== 'workflow'
@@ -46,19 +47,22 @@ export default function ChartView({ appId }: IChartViewProps) {
return (
-
-
{t('appOverview.analysis.title')}
-
({ value: k, name: t(`appLog.filter.period.${v.name}`) }))}
- className='mt-0 !w-40'
- onSelect={(item) => {
- const id = item.value
- const value = TIME_PERIOD_MAPPING[id]?.value ?? '-1'
- const name = item.name || t('appLog.filter.period.allTime')
- onSelect({ value, name })
- }}
- defaultValue={'2'}
- />
+
+
+ {t('appOverview.analysis.title')}
+ ({ value: k, name: t(`appLog.filter.period.${v.name}`) }))}
+ className='mt-0 !w-40'
+ onSelect={(item) => {
+ const id = item.value
+ const value = TIME_PERIOD_MAPPING[id]?.value ?? '-1'
+ const name = item.name || t('appLog.filter.period.allTime')
+ onSelect({ value, name })
+ }}
+ defaultValue={'2'}
+ />
+
+ {headerRight}
{!isWorkflow && (
diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/page.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/page.tsx
index 0f1bb7e18d..fc97f5e669 100644
--- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/page.tsx
+++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/page.tsx
@@ -1,6 +1,5 @@
import React from 'react'
import ChartView from './chartView'
-import CardView from './cardView'
import TracingPanel from './tracing/panel'
import ApikeyInfoPanel from '@/app/components/app/overview/apikey-info-panel'
@@ -18,9 +17,10 @@ const Overview = async (props: IDevelopProps) => {
return (
)
}
diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx
index 8575117c41..bc85f3a734 100644
--- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx
+++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx
@@ -154,7 +154,6 @@ const Panel: FC = () => {
if (!isLoaded) {
return (
-
@@ -163,8 +162,7 @@ const Panel: FC = () => {
}
return (
-
-
+
{
+ const { eventEmitter } = useEventEmitterContextContext()
+ const [secretEnvList, setSecretEnvList] = useState
([])
const showInputFieldDialog = useStore(state => state.showInputFieldDialog)
+ const showImportDSLModal = useStore(s => s.showImportDSLModal)
+ const setShowImportDSLModal = useStore(s => s.setShowImportDSLModal)
+ const {
+ handlePaneContextmenuCancel,
+ } = usePanelInteractions()
+ const {
+ exportCheck,
+ handleExportDSL,
+ } = useDSL()
+
+ eventEmitter?.useSubscription((v: any) => {
+ if (v.type === DSL_EXPORT_CHECK)
+ setSecretEnvList(v.payload.data as EnvironmentVariable[])
+ })
return (
<>
+ {
+ showImportDSLModal && (
+ setShowImportDSLModal(false)}
+ onBackup={exportCheck!}
+ onImport={handlePaneContextmenuCancel}
+ />
+ )
+ }
+ {
+ secretEnvList.length > 0 && (
+ setSecretEnvList([])}
+ />
+ )
+ }
{
diff --git a/web/app/components/rag-pipeline/components/rag-pipeline-main.tsx b/web/app/components/rag-pipeline/components/rag-pipeline-main.tsx
index 53de99609e..cf9b9744e9 100644
--- a/web/app/components/rag-pipeline/components/rag-pipeline-main.tsx
+++ b/web/app/components/rag-pipeline/components/rag-pipeline-main.tsx
@@ -6,6 +6,7 @@ import type { WorkflowProps } from '@/app/components/workflow'
import RagPipelineChildren from './rag-pipeline-children'
import {
useAvailableNodesMetaData,
+ useDSL,
useGetRunAndTraceUrl,
useNodesSyncDraft,
usePipelineRefreshDraft,
@@ -37,6 +38,10 @@ const RagPipelineMain = ({
} = usePipelineStartRun()
const availableNodesMetaData = useAvailableNodesMetaData()
const { getWorkflowRunAndTraceUrl } = useGetRunAndTraceUrl()
+ const {
+ exportCheck,
+ handleExportDSL,
+ } = useDSL()
const hooksStore = useMemo(() => {
return {
@@ -52,6 +57,8 @@ const RagPipelineMain = ({
handleStartWorkflowRun,
handleWorkflowStartRunInWorkflow,
getWorkflowRunAndTraceUrl,
+ exportCheck,
+ handleExportDSL,
}
}, [
availableNodesMetaData,
@@ -66,6 +73,8 @@ const RagPipelineMain = ({
handleStartWorkflowRun,
handleWorkflowStartRunInWorkflow,
getWorkflowRunAndTraceUrl,
+ exportCheck,
+ handleExportDSL,
])
return (
diff --git a/web/app/components/rag-pipeline/hooks/index.ts b/web/app/components/rag-pipeline/hooks/index.ts
index 216a6d51cb..3754ddb02d 100644
--- a/web/app/components/rag-pipeline/hooks/index.ts
+++ b/web/app/components/rag-pipeline/hooks/index.ts
@@ -5,3 +5,4 @@ export * from './use-pipeline-run'
export * from './use-pipeline-start-run'
export * from './use-pipeline-init'
export * from './use-get-run-and-trace-url'
+export * from './use-DSL'
diff --git a/web/app/components/rag-pipeline/hooks/use-DSL.ts b/web/app/components/rag-pipeline/hooks/use-DSL.ts
new file mode 100644
index 0000000000..36ac6b9e0b
--- /dev/null
+++ b/web/app/components/rag-pipeline/hooks/use-DSL.ts
@@ -0,0 +1,81 @@
+import {
+ useCallback,
+ useState,
+} from 'react'
+import { useTranslation } from 'react-i18next'
+import {
+ DSL_EXPORT_CHECK,
+} from '@/app/components/workflow/constants'
+import { useNodesSyncDraft } from './use-nodes-sync-draft'
+import { useEventEmitterContextContext } from '@/context/event-emitter'
+import { fetchWorkflowDraft } from '@/service/workflow'
+import { useToastContext } from '@/app/components/base/toast'
+import { useWorkflowStore } from '@/app/components/workflow/store'
+import { useExportPipelineDSL } from '@/service/use-pipeline'
+
+export const useDSL = () => {
+ const { t } = useTranslation()
+ const { notify } = useToastContext()
+ const { eventEmitter } = useEventEmitterContextContext()
+ const [exporting, setExporting] = useState(false)
+ const { doSyncWorkflowDraft } = useNodesSyncDraft()
+ const workflowStore = useWorkflowStore()
+ const { mutateAsync: exportPipelineConfig } = useExportPipelineDSL()
+
+ const handleExportDSL = useCallback(async (include = false) => {
+ const { pipelineId, knowledgeName } = workflowStore.getState()
+ if (!pipelineId)
+ return
+
+ if (exporting)
+ return
+
+ try {
+ setExporting(true)
+ await doSyncWorkflowDraft()
+ const { data } = await exportPipelineConfig({
+ pipelineId,
+ include,
+ })
+ const a = document.createElement('a')
+ const file = new Blob([data], { type: 'application/yaml' })
+ a.href = URL.createObjectURL(file)
+ a.download = `${knowledgeName}.yml`
+ a.click()
+ }
+ catch {
+ notify({ type: 'error', message: t('app.exportFailed') })
+ }
+ finally {
+ setExporting(false)
+ }
+ }, [notify, t, doSyncWorkflowDraft, exporting, exportPipelineConfig, workflowStore])
+
+ const exportCheck = useCallback(async () => {
+ const { pipelineId } = workflowStore.getState()
+ if (!pipelineId)
+ return
+ try {
+ const workflowDraft = await fetchWorkflowDraft(`/rag/pipelines/${pipelineId}/workflows/draft`)
+ const list = (workflowDraft.environment_variables || []).filter(env => env.value_type === 'secret')
+ if (list.length === 0) {
+ handleExportDSL()
+ return
+ }
+ eventEmitter?.emit({
+ type: DSL_EXPORT_CHECK,
+ payload: {
+ data: list,
+ },
+ } as any)
+ }
+ catch {
+ notify({ type: 'error', message: t('app.exportFailed') })
+ }
+ }, [eventEmitter, handleExportDSL, notify, t, workflowStore])
+
+ return {
+ exportCheck,
+ handleExportDSL,
+ }
+}
diff --git a/web/app/components/rag-pipeline/hooks/use-get-run-and-trace-url.tsx b/web/app/components/rag-pipeline/hooks/use-get-run-and-trace-url.ts
similarity index 100%
rename from web/app/components/rag-pipeline/hooks/use-get-run-and-trace-url.tsx
rename to web/app/components/rag-pipeline/hooks/use-get-run-and-trace-url.ts
diff --git a/web/app/components/rag-pipeline/hooks/use-pipeline-init.ts b/web/app/components/rag-pipeline/hooks/use-pipeline-init.ts
index 0c8f1d51c9..d3e65c8ca6 100644
--- a/web/app/components/rag-pipeline/hooks/use-pipeline-init.ts
+++ b/web/app/components/rag-pipeline/hooks/use-pipeline-init.ts
@@ -24,10 +24,11 @@ export const usePipelineInit = () => {
const [data, setData] = useState()
const [isLoading, setIsLoading] = useState(true)
const datasetId = useDatasetDetailContextWithSelector(s => s.dataset)?.pipeline_id
+ const knowledgeName = useDatasetDetailContextWithSelector(s => s.dataset)?.name
useEffect(() => {
- workflowStore.setState({ pipelineId: datasetId })
- }, [datasetId, workflowStore])
+ workflowStore.setState({ pipelineId: datasetId, knowledgeName })
+ }, [datasetId, workflowStore, knowledgeName])
usePipelineConfig()
diff --git a/web/app/components/rag-pipeline/store/index.ts b/web/app/components/rag-pipeline/store/index.ts
index cdda5b52f9..7799c29daa 100644
--- a/web/app/components/rag-pipeline/store/index.ts
+++ b/web/app/components/rag-pipeline/store/index.ts
@@ -8,6 +8,7 @@ import { transformDataSourceToTool } from '@/app/components/workflow/block-selec
export type RagPipelineSliceShape = {
pipelineId: string
+ knowledgeName: string
showInputFieldDialog: boolean
setShowInputFieldDialog: (showInputFieldPanel: boolean) => void
nodesDefaultConfigs: Record
@@ -21,6 +22,7 @@ export type RagPipelineSliceShape = {
export type CreateRagPipelineSliceSlice = StateCreator
export const createRagPipelineSliceSlice: StateCreator = set => ({
pipelineId: '',
+ knowledgeName: '',
showInputFieldDialog: false,
setShowInputFieldDialog: showInputFieldDialog => set(() => ({ showInputFieldDialog })),
nodesDefaultConfigs: {},
diff --git a/web/app/components/workflow-app/components/workflow-children.tsx b/web/app/components/workflow-app/components/workflow-children.tsx
index 6a6bbcd61a..baccb0b60a 100644
--- a/web/app/components/workflow-app/components/workflow-children.tsx
+++ b/web/app/components/workflow-app/components/workflow-children.tsx
@@ -46,7 +46,7 @@ const WorkflowChildren = () => {
showImportDSLModal && (
setShowImportDSLModal(false)}
- onBackup={exportCheck}
+ onBackup={exportCheck!}
onImport={handlePaneContextmenuCancel}
/>
)
@@ -55,7 +55,7 @@ const WorkflowChildren = () => {
secretEnvList.length > 0 && (
setSecretEnvList([])}
/>
)
diff --git a/web/app/components/workflow-app/components/workflow-main.tsx b/web/app/components/workflow-app/components/workflow-main.tsx
index 50ae6ed35c..ead930ba5e 100644
--- a/web/app/components/workflow-app/components/workflow-main.tsx
+++ b/web/app/components/workflow-app/components/workflow-main.tsx
@@ -8,6 +8,7 @@ import type { WorkflowProps } from '@/app/components/workflow'
import WorkflowChildren from './workflow-children'
import {
useAvailableNodesMetaData,
+ useDSL,
useGetRunAndTraceUrl,
useNodesSyncDraft,
useWorkflowRefreshDraft,
@@ -50,6 +51,10 @@ const WorkflowMain = ({
} = useWorkflowStartRun()
const availableNodesMetaData = useAvailableNodesMetaData()
const { getWorkflowRunAndTraceUrl } = useGetRunAndTraceUrl()
+ const {
+ exportCheck,
+ handleExportDSL,
+ } = useDSL()
const hooksStore = useMemo(() => {
return {
@@ -66,6 +71,8 @@ const WorkflowMain = ({
handleWorkflowStartRunInWorkflow,
availableNodesMetaData,
getWorkflowRunAndTraceUrl,
+ exportCheck,
+ handleExportDSL,
}
}, [
syncWorkflowDraftWhenPageClose,
@@ -81,6 +88,8 @@ const WorkflowMain = ({
handleWorkflowStartRunInWorkflow,
availableNodesMetaData,
getWorkflowRunAndTraceUrl,
+ exportCheck,
+ handleExportDSL,
])
return (
diff --git a/web/app/components/workflow-app/hooks/index.ts b/web/app/components/workflow-app/hooks/index.ts
index 3b4a587d8f..b17943afc5 100644
--- a/web/app/components/workflow-app/hooks/index.ts
+++ b/web/app/components/workflow-app/hooks/index.ts
@@ -7,3 +7,4 @@ export * from './use-is-chat-mode'
export * from './use-available-nodes-meta-data'
export * from './use-workflow-refresh-draft'
export * from './use-get-run-and-trace-url'
+export * from './use-DSL'
diff --git a/web/app/components/workflow-app/hooks/use-DSL.ts b/web/app/components/workflow-app/hooks/use-DSL.ts
new file mode 100644
index 0000000000..372eb2c850
--- /dev/null
+++ b/web/app/components/workflow-app/hooks/use-DSL.ts
@@ -0,0 +1,79 @@
+import {
+ useCallback,
+ useState,
+} from 'react'
+import { useTranslation } from 'react-i18next'
+import {
+ DSL_EXPORT_CHECK,
+} from '@/app/components/workflow/constants'
+import { useNodesSyncDraft } from './use-nodes-sync-draft'
+import { useEventEmitterContextContext } from '@/context/event-emitter'
+import { fetchWorkflowDraft } from '@/service/workflow'
+import { exportAppConfig } from '@/service/apps'
+import { useToastContext } from '@/app/components/base/toast'
+import { useStore as useAppStore } from '@/app/components/app/store'
+
+export const useDSL = () => {
+ const { t } = useTranslation()
+ const { notify } = useToastContext()
+ const { eventEmitter } = useEventEmitterContextContext()
+ const [exporting, setExporting] = useState(false)
+ const { doSyncWorkflowDraft } = useNodesSyncDraft()
+
+ const appDetail = useAppStore(s => s.appDetail)
+
+ const handleExportDSL = useCallback(async (include = false) => {
+ if (!appDetail)
+ return
+
+ if (exporting)
+ return
+
+ try {
+ setExporting(true)
+ await doSyncWorkflowDraft()
+ const { data } = await exportAppConfig({
+ appID: appDetail.id,
+ include,
+ })
+ const a = document.createElement('a')
+ const file = new Blob([data], { type: 'application/yaml' })
+ a.href = URL.createObjectURL(file)
+ a.download = `${appDetail.name}.yml`
+ a.click()
+ }
+ catch {
+ notify({ type: 'error', message: t('app.exportFailed') })
+ }
+ finally {
+ setExporting(false)
+ }
+ }, [appDetail, notify, t, doSyncWorkflowDraft, exporting])
+
+ const exportCheck = useCallback(async () => {
+ if (!appDetail)
+ return
+ try {
+ const workflowDraft = await fetchWorkflowDraft(`/apps/${appDetail?.id}/workflows/draft`)
+ const list = (workflowDraft.environment_variables || []).filter(env => env.value_type === 'secret')
+ if (list.length === 0) {
+ handleExportDSL()
+ return
+ }
+ eventEmitter?.emit({
+ type: DSL_EXPORT_CHECK,
+ payload: {
+ data: list,
+ },
+ } as any)
+ }
+ catch {
+ notify({ type: 'error', message: t('app.exportFailed') })
+ }
+ }, [appDetail, eventEmitter, handleExportDSL, notify, t])
+
+ return {
+ exportCheck,
+ handleExportDSL,
+ }
+}
diff --git a/web/app/components/workflow-app/hooks/use-get-run-and-trace-url.tsx b/web/app/components/workflow-app/hooks/use-get-run-and-trace-url.ts
similarity index 100%
rename from web/app/components/workflow-app/hooks/use-get-run-and-trace-url.tsx
rename to web/app/components/workflow-app/hooks/use-get-run-and-trace-url.ts
diff --git a/web/app/components/workflow/block-selector/utils.ts b/web/app/components/workflow/block-selector/utils.ts
index b1ad7b1256..0a4bed25ff 100644
--- a/web/app/components/workflow/block-selector/utils.ts
+++ b/web/app/components/workflow/block-selector/utils.ts
@@ -13,6 +13,7 @@ export const transformDataSourceToTool = (dataSourceItem: DataSourceItem) => {
type: dataSourceItem.declaration.provider_type,
team_credentials: {},
allow_delete: true,
+ is_team_authorization: dataSourceItem.is_authorized,
is_authorized: dataSourceItem.is_authorized,
labels: dataSourceItem.declaration.identity.tags || [],
plugin_id: dataSourceItem.plugin_id,
diff --git a/web/app/components/workflow/hooks-store/store.ts b/web/app/components/workflow/hooks-store/store.ts
index cea8f3a248..967682c7dd 100644
--- a/web/app/components/workflow/hooks-store/store.ts
+++ b/web/app/components/workflow/hooks-store/store.ts
@@ -37,6 +37,8 @@ export type CommonHooksFnMap = {
handleWorkflowStartRunInChatflow: () => void
availableNodesMetaData?: AvailableNodesMetaData
getWorkflowRunAndTraceUrl: (runId?: string) => { runUrl: string; traceUrl: string }
+ exportCheck?: () => Promise
+ handleExportDSL?: (include?: boolean) => Promise
}
export type Shape = {
@@ -62,6 +64,8 @@ export const createHooksStore = ({
runUrl: '',
traceUrl: '',
}),
+ exportCheck = async () => noop(),
+ handleExportDSL = async () => noop(),
}: Partial) => {
return createStore(set => ({
refreshAll: props => set(state => ({ ...state, ...props })),
@@ -78,6 +82,8 @@ export const createHooksStore = ({
handleWorkflowStartRunInChatflow,
availableNodesMetaData,
getWorkflowRunAndTraceUrl,
+ exportCheck,
+ handleExportDSL,
}))
}
diff --git a/web/app/components/workflow/hooks/index.ts b/web/app/components/workflow/hooks/index.ts
index 317ca23d4a..8a33643d69 100644
--- a/web/app/components/workflow/hooks/index.ts
+++ b/web/app/components/workflow/hooks/index.ts
@@ -19,3 +19,4 @@ export * from './use-nodes-meta-data'
export * from './use-available-blocks'
export * from './use-workflow-refresh-draft'
export * from './use-tool-icon'
+export * from './use-DSL'
diff --git a/web/app/components/workflow/hooks/use-DSL.ts b/web/app/components/workflow/hooks/use-DSL.ts
new file mode 100644
index 0000000000..2470ca7b46
--- /dev/null
+++ b/web/app/components/workflow/hooks/use-DSL.ts
@@ -0,0 +1,11 @@
+import { useHooksStore } from '@/app/components/workflow/hooks-store'
+
+export const useDSL = () => {
+ const exportCheck = useHooksStore(s => s.exportCheck)
+ const handleExportDSL = useHooksStore(s => s.handleExportDSL)
+
+ return {
+ exportCheck,
+ handleExportDSL,
+ }
+}
diff --git a/web/app/components/workflow/hooks/use-workflow-interactions.ts b/web/app/components/workflow/hooks/use-workflow-interactions.ts
index 636d3b94f9..c9e04fe801 100644
--- a/web/app/components/workflow/hooks/use-workflow-interactions.ts
+++ b/web/app/components/workflow/hooks/use-workflow-interactions.ts
@@ -1,13 +1,11 @@
import {
useCallback,
- useState,
} from 'react'
-import { useTranslation } from 'react-i18next'
import { useReactFlow, useStoreApi } from 'reactflow'
import produce from 'immer'
import { useStore, useWorkflowStore } from '../store'
import {
- CUSTOM_NODE, DSL_EXPORT_CHECK,
+ CUSTOM_NODE,
NODE_LAYOUT_HORIZONTAL_PADDING,
NODE_LAYOUT_VERTICAL_PADDING,
WORKFLOW_DATA_UPDATE,
@@ -30,10 +28,6 @@ import { useNodesInteractionsWithoutSync } from './use-nodes-interactions-withou
import { useNodesSyncDraft } from './use-nodes-sync-draft'
import { WorkflowHistoryEvent, useWorkflowHistory } from './use-workflow-history'
import { useEventEmitterContextContext } from '@/context/event-emitter'
-import { fetchWorkflowDraft } from '@/service/workflow'
-import { exportAppConfig } from '@/service/apps'
-import { useToastContext } from '@/app/components/base/toast'
-import { useStore as useAppStore } from '@/app/components/app/store'
export const useWorkflowInteractions = () => {
const workflowStore = useWorkflowStore()
@@ -336,68 +330,3 @@ export const useWorkflowUpdate = () => {
handleUpdateWorkflowCanvas,
}
}
-
-export const useDSL = () => {
- const { t } = useTranslation()
- const { notify } = useToastContext()
- const { eventEmitter } = useEventEmitterContextContext()
- const [exporting, setExporting] = useState(false)
- const { doSyncWorkflowDraft } = useNodesSyncDraft()
-
- const appDetail = useAppStore(s => s.appDetail)
-
- const handleExportDSL = useCallback(async (include = false) => {
- if (!appDetail)
- return
-
- if (exporting)
- return
-
- try {
- setExporting(true)
- await doSyncWorkflowDraft()
- const { data } = await exportAppConfig({
- appID: appDetail.id,
- include,
- })
- const a = document.createElement('a')
- const file = new Blob([data], { type: 'application/yaml' })
- a.href = URL.createObjectURL(file)
- a.download = `${appDetail.name}.yml`
- a.click()
- }
- catch {
- notify({ type: 'error', message: t('app.exportFailed') })
- }
- finally {
- setExporting(false)
- }
- }, [appDetail, notify, t, doSyncWorkflowDraft, exporting])
-
- const exportCheck = useCallback(async () => {
- if (!appDetail)
- return
- try {
- const workflowDraft = await fetchWorkflowDraft(`/apps/${appDetail?.id}/workflows/draft`)
- const list = (workflowDraft.environment_variables || []).filter(env => env.value_type === 'secret')
- if (list.length === 0) {
- handleExportDSL()
- return
- }
- eventEmitter?.emit({
- type: DSL_EXPORT_CHECK,
- payload: {
- data: list,
- },
- } as any)
- }
- catch {
- notify({ type: 'error', message: t('app.exportFailed') })
- }
- }, [appDetail, eventEmitter, handleExportDSL, notify, t])
-
- return {
- exportCheck,
- handleExportDSL,
- }
-}
diff --git a/web/app/components/workflow/panel-contextmenu.tsx b/web/app/components/workflow/panel-contextmenu.tsx
index 0a09452c67..2ee69df671 100644
--- a/web/app/components/workflow/panel-contextmenu.tsx
+++ b/web/app/components/workflow/panel-contextmenu.tsx
@@ -112,7 +112,7 @@ const PanelContextmenu = () => {
exportCheck()}
+ onClick={() => exportCheck?.()}
>
{t('app.export')}
diff --git a/web/package.json b/web/package.json
index affbef9382..ff4214f966 100644
--- a/web/package.json
+++ b/web/package.json
@@ -5,6 +5,18 @@
"engines": {
"node": ">=v22.11.0"
},
+ "browserslist": [
+ "last 1 Chrome version",
+ "last 1 Firefox version",
+ "last 1 Edge version",
+ "last 1 Safari version",
+ "iOS >=15",
+ "Android >= 10",
+ "and_chr >= 126",
+ "and_ff >= 137",
+ "and_uc >= 15.5",
+ "and_qq >= 14.9"
+ ],
"scripts": {
"dev": "cross-env NODE_OPTIONS='--inspect' next dev",
"build": "next build",
diff --git a/web/service/use-pipeline.ts b/web/service/use-pipeline.ts
index 9b03f9ea33..b338615ef6 100644
--- a/web/service/use-pipeline.ts
+++ b/web/service/use-pipeline.ts
@@ -298,3 +298,15 @@ export const usePublishedPipelinePreProcessingParams = (params: PipelinePreProce
enabled,
})
}
+
+export const useExportPipelineDSL = () => {
+ return useMutation({
+ mutationKey: [NAME_SPACE, 'export-pipeline-dsl'],
+ mutationFn: ({
+ pipelineId,
+ include = false,
+ }: { pipelineId: string; include?: boolean }) => {
+ return get
(`/rag/pipelines/${pipelineId}/export?include_secret=${include}`)
+ },
+ })
+}