diff --git a/api/controllers/console/app/app.py b/api/controllers/console/app/app.py
index 4b852c1a49..082838334a 100644
--- a/api/controllers/console/app/app.py
+++ b/api/controllers/console/app/app.py
@@ -129,6 +129,10 @@ class AppApi(Resource):
@marshal_with(app_detail_fields_with_site)
def put(self, app_model):
"""Update app"""
+ # The role of the current user in the ta table must be admin, owner, or editor
+ if not current_user.is_editor:
+ raise Forbidden()
+
parser = reqparse.RequestParser()
parser.add_argument('name', type=str, required=True, nullable=False, location='json')
parser.add_argument('description', type=str, location='json')
@@ -147,6 +151,7 @@ class AppApi(Resource):
@get_app_model
def delete(self, app_model):
"""Delete app"""
+ # The role of the current user in the ta table must be admin, owner, or editor
if not current_user.is_editor:
raise Forbidden()
@@ -203,6 +208,10 @@ class AppNameApi(Resource):
@get_app_model
@marshal_with(app_detail_fields)
def post(self, app_model):
+ # The role of the current user in the ta table must be admin, owner, or editor
+ if not current_user.is_editor:
+ raise Forbidden()
+
parser = reqparse.RequestParser()
parser.add_argument('name', type=str, required=True, location='json')
args = parser.parse_args()
@@ -220,6 +229,10 @@ class AppIconApi(Resource):
@get_app_model
@marshal_with(app_detail_fields)
def post(self, app_model):
+ # The role of the current user in the ta table must be admin, owner, or editor
+ if not current_user.is_editor:
+ raise Forbidden()
+
parser = reqparse.RequestParser()
parser.add_argument('icon', type=str, location='json')
parser.add_argument('icon_background', type=str, location='json')
@@ -241,6 +254,7 @@ class AppSiteStatus(Resource):
# The role of the current user in the ta table must be admin, owner, or editor
if not current_user.is_editor:
raise Forbidden()
+
parser = reqparse.RequestParser()
parser.add_argument('enable_site', type=bool, required=True, location='json')
args = parser.parse_args()
@@ -261,6 +275,7 @@ class AppApiStatus(Resource):
# The role of the current user in the ta table must be admin or owner
if not current_user.is_admin_or_owner:
raise Forbidden()
+
parser = reqparse.RequestParser()
parser.add_argument('enable_api', type=bool, required=True, location='json')
args = parser.parse_args()
diff --git a/api/controllers/console/app/workflow.py b/api/controllers/console/app/workflow.py
index 641997f3f3..668a722bf7 100644
--- a/api/controllers/console/app/workflow.py
+++ b/api/controllers/console/app/workflow.py
@@ -3,7 +3,7 @@ import logging
from flask import abort, request
from flask_restful import Resource, marshal_with, reqparse
-from werkzeug.exceptions import InternalServerError, NotFound
+from werkzeug.exceptions import Forbidden, InternalServerError, NotFound
import services
from controllers.console import api
@@ -36,6 +36,10 @@ class DraftWorkflowApi(Resource):
"""
Get draft workflow
"""
+ # The role of the current user in the ta table must be admin, owner, or editor
+ if not current_user.is_editor:
+ raise Forbidden()
+
# fetch draft workflow by app_model
workflow_service = WorkflowService()
workflow = workflow_service.get_draft_workflow(app_model=app_model)
@@ -54,6 +58,10 @@ class DraftWorkflowApi(Resource):
"""
Sync draft workflow
"""
+ # The role of the current user in the ta table must be admin, owner, or editor
+ if not current_user.is_editor:
+ raise Forbidden()
+
content_type = request.headers.get('Content-Type')
if 'application/json' in content_type:
@@ -110,6 +118,10 @@ class AdvancedChatDraftWorkflowRunApi(Resource):
"""
Run draft workflow
"""
+ # The role of the current user in the ta table must be admin, owner, or editor
+ if not current_user.is_editor:
+ raise Forbidden()
+
parser = reqparse.RequestParser()
parser.add_argument('inputs', type=dict, location='json')
parser.add_argument('query', type=str, required=True, location='json', default='')
@@ -146,6 +158,10 @@ class AdvancedChatDraftRunIterationNodeApi(Resource):
"""
Run draft workflow iteration node
"""
+ # The role of the current user in the ta table must be admin, owner, or editor
+ if not current_user.is_editor:
+ raise Forbidden()
+
parser = reqparse.RequestParser()
parser.add_argument('inputs', type=dict, location='json')
args = parser.parse_args()
@@ -179,6 +195,10 @@ class WorkflowDraftRunIterationNodeApi(Resource):
"""
Run draft workflow iteration node
"""
+ # The role of the current user in the ta table must be admin, owner, or editor
+ if not current_user.is_editor:
+ raise Forbidden()
+
parser = reqparse.RequestParser()
parser.add_argument('inputs', type=dict, location='json')
args = parser.parse_args()
@@ -212,6 +232,10 @@ class DraftWorkflowRunApi(Resource):
"""
Run draft workflow
"""
+ # The role of the current user in the ta table must be admin, owner, or editor
+ if not current_user.is_editor:
+ raise Forbidden()
+
parser = reqparse.RequestParser()
parser.add_argument('inputs', type=dict, required=True, nullable=False, location='json')
parser.add_argument('files', type=list, required=False, location='json')
@@ -243,6 +267,10 @@ class WorkflowTaskStopApi(Resource):
"""
Stop workflow task
"""
+ # The role of the current user in the ta table must be admin, owner, or editor
+ if not current_user.is_editor:
+ raise Forbidden()
+
AppQueueManager.set_stop_flag(task_id, InvokeFrom.DEBUGGER, current_user.id)
return {
@@ -260,6 +288,10 @@ class DraftWorkflowNodeRunApi(Resource):
"""
Run draft workflow node
"""
+ # The role of the current user in the ta table must be admin, owner, or editor
+ if not current_user.is_editor:
+ raise Forbidden()
+
parser = reqparse.RequestParser()
parser.add_argument('inputs', type=dict, required=True, nullable=False, location='json')
args = parser.parse_args()
@@ -286,6 +318,10 @@ class PublishedWorkflowApi(Resource):
"""
Get published workflow
"""
+ # The role of the current user in the ta table must be admin, owner, or editor
+ if not current_user.is_editor:
+ raise Forbidden()
+
# fetch published workflow by app_model
workflow_service = WorkflowService()
workflow = workflow_service.get_published_workflow(app_model=app_model)
@@ -301,6 +337,10 @@ class PublishedWorkflowApi(Resource):
"""
Publish workflow
"""
+ # The role of the current user in the ta table must be admin, owner, or editor
+ if not current_user.is_editor:
+ raise Forbidden()
+
workflow_service = WorkflowService()
workflow = workflow_service.publish_workflow(app_model=app_model, account=current_user)
@@ -319,6 +359,10 @@ class DefaultBlockConfigsApi(Resource):
"""
Get default block config
"""
+ # The role of the current user in the ta table must be admin, owner, or editor
+ if not current_user.is_editor:
+ raise Forbidden()
+
# Get default block configs
workflow_service = WorkflowService()
return workflow_service.get_default_block_configs()
@@ -333,6 +377,10 @@ class DefaultBlockConfigApi(Resource):
"""
Get default block config
"""
+ # The role of the current user in the ta table must be admin, owner, or editor
+ if not current_user.is_editor:
+ raise Forbidden()
+
parser = reqparse.RequestParser()
parser.add_argument('q', type=str, location='args')
args = parser.parse_args()
@@ -363,6 +411,10 @@ class ConvertToWorkflowApi(Resource):
Convert expert mode of chatbot app to workflow mode
Convert Completion App to Workflow App
"""
+ # The role of the current user in the ta table must be admin, owner, or editor
+ if not current_user.is_editor:
+ raise Forbidden()
+
if request.data:
parser = reqparse.RequestParser()
parser.add_argument('name', type=str, required=False, nullable=True, location='json')
diff --git a/api/controllers/console/auth/data_source_bearer_auth.py b/api/controllers/console/auth/data_source_bearer_auth.py
index 81678f61fc..f79b93b74f 100644
--- a/api/controllers/console/auth/data_source_bearer_auth.py
+++ b/api/controllers/console/auth/data_source_bearer_auth.py
@@ -16,15 +16,21 @@ class ApiKeyAuthDataSource(Resource):
@login_required
@account_initialization_required
def get(self):
- # The role of the current user in the table must be admin or owner
- if not current_user.is_admin_or_owner:
- raise Forbidden()
data_source_api_key_bindings = ApiKeyAuthService.get_provider_auth_list(current_user.current_tenant_id)
if data_source_api_key_bindings:
return {
- 'settings': [data_source_api_key_binding.to_dict() for data_source_api_key_binding in
- data_source_api_key_bindings]}
- return {'settings': []}
+ 'sources': [{
+ 'id': data_source_api_key_binding.id,
+ 'category': data_source_api_key_binding.category,
+ 'provider': data_source_api_key_binding.provider,
+ 'disabled': data_source_api_key_binding.disabled,
+ 'created_at': int(data_source_api_key_binding.created_at.timestamp()),
+ 'updated_at': int(data_source_api_key_binding.updated_at.timestamp()),
+ }
+ for data_source_api_key_binding in
+ data_source_api_key_bindings]
+ }
+ return {'sources': []}
class ApiKeyAuthDataSourceBinding(Resource):
diff --git a/web/app/(commonLayout)/apps/AppCard.tsx b/web/app/(commonLayout)/apps/AppCard.tsx
index 59040f207d..c36090d476 100644
--- a/web/app/(commonLayout)/apps/AppCard.tsx
+++ b/web/app/(commonLayout)/apps/AppCard.tsx
@@ -279,27 +279,27 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
'items-center shrink-0 mt-1 pt-1 pl-[14px] pr-[6px] pb-[6px] h-[42px]',
tags.length ? 'flex' : '!hidden group-hover:!flex',
)}>
-
{
- e.stopPropagation()
- e.preventDefault()
- }}>
-
- tag.id)}
- selectedTags={tags}
- onCacheUpdate={setTags}
- onChange={onRefresh}
- />
-
-
{isCurrentWorkspaceEditor && (
<>
+ {
+ e.stopPropagation()
+ e.preventDefault()
+ }}>
+
+ tag.id)}
+ selectedTags={tags}
+ onCacheUpdate={setTags}
+ onChange={onRefresh}
+ />
+
+
{
setShowConfirmDelete(false)
}, [appDetail, mutateApps, notify, onPlanInfoChanged, replace, t])
+ const { isCurrentWorkspaceEditor } = useAppContext()
+
if (!appDetail)
return null
@@ -154,10 +156,13 @@ const AppInfo = ({ expand }: IAppInfoProps) => {
>
setOpen(v => !v)}
+ onClick={() => {
+ if (isCurrentWorkspaceEditor)
+ setOpen(v => !v)
+ }}
className='block'
>
-
+
{
{appDetail.name}
-
+ {isCurrentWorkspaceEditor &&
}
{appDetail.mode === 'advanced-chat' && (
diff --git a/web/app/components/datasets/create/website/index.tsx b/web/app/components/datasets/create/website/index.tsx
index 14ac40163a..e06fbb4a12 100644
--- a/web/app/components/datasets/create/website/index.tsx
+++ b/web/app/components/datasets/create/website/index.tsx
@@ -5,8 +5,8 @@ import NoData from './no-data'
import Firecrawl from './firecrawl'
import { useModalContext } from '@/context/modal-context'
import type { CrawlOptions, CrawlResultItem } from '@/models/datasets'
-import { fetchFirecrawlApiKey } from '@/service/datasets'
-import { type DataSourceWebsiteItem, WebsiteProvider } from '@/models/common'
+import { fetchDataSources } from '@/service/datasets'
+import { type DataSourceItem, DataSourceProvider } from '@/models/common'
type Props = {
onPreview: (payload: CrawlResultItem) => void
@@ -29,9 +29,9 @@ const Website: FC
= ({
const [isLoaded, setIsLoaded] = useState(false)
const [isSetFirecrawlApiKey, setIsSetFirecrawlApiKey] = useState(false)
const checkSetApiKey = useCallback(async () => {
- const res = await fetchFirecrawlApiKey() as any
- const list = res.settings.filter((item: DataSourceWebsiteItem) => item.provider === WebsiteProvider.fireCrawl && !item.disabled)
- setIsSetFirecrawlApiKey(list.length > 0)
+ const res = await fetchDataSources() as any
+ const isFirecrawlSet = res.sources.some((item: DataSourceItem) => item.provider === DataSourceProvider.fireCrawl)
+ setIsSetFirecrawlApiKey(isFirecrawlSet)
}, [])
useEffect(() => {
diff --git a/web/app/components/header/account-setting/data-source-page/data-source-notion/index.tsx b/web/app/components/header/account-setting/data-source-page/data-source-notion/index.tsx
index f5541999a4..b0db511550 100644
--- a/web/app/components/header/account-setting/data-source-page/data-source-notion/index.tsx
+++ b/web/app/components/header/account-setting/data-source-page/data-source-notion/index.tsx
@@ -58,7 +58,7 @@ const DataSourceNotion: FC = ({
type={DataSourceType.notion}
isConfigured={connected}
onConfigure={handleConnectNotion}
- readonly={!isCurrentWorkspaceManager}
+ readOnly={!isCurrentWorkspaceManager}
isSupportList
configuredList={workspaces.map(workspace => ({
id: workspace.id,
diff --git a/web/app/components/header/account-setting/data-source-page/data-source-website/config-firecrawl-modal.tsx b/web/app/components/header/account-setting/data-source-page/data-source-website/config-firecrawl-modal.tsx
index 21277c8ec1..983fb29b03 100644
--- a/web/app/components/header/account-setting/data-source-page/data-source-website/config-firecrawl-modal.tsx
+++ b/web/app/components/header/account-setting/data-source-page/data-source-website/config-firecrawl-modal.tsx
@@ -11,7 +11,7 @@ import Button from '@/app/components/base/button'
import type { FirecrawlConfig } from '@/models/common'
import Field from '@/app/components/datasets/create/website/firecrawl/base/field'
import Toast from '@/app/components/base/toast'
-import { createFirecrawlApiKey } from '@/service/datasets'
+import { createDataSourceApiKeyBinding } from '@/service/datasets'
import { LinkExternal02 } from '@/app/components/base/icons/src/vender/line/general'
type Props = {
onCancel: () => void
@@ -76,7 +76,7 @@ const ConfigFirecrawlModal: FC = ({
}
try {
setIsSaving(true)
- await createFirecrawlApiKey(postData)
+ await createDataSourceApiKeyBinding(postData)
Toast.notify({
type: 'success',
message: t('common.api.success'),
diff --git a/web/app/components/header/account-setting/data-source-page/data-source-website/index.tsx b/web/app/components/header/account-setting/data-source-page/data-source-website/index.tsx
index b6ac22436c..63ad8df0d8 100644
--- a/web/app/components/header/account-setting/data-source-page/data-source-website/index.tsx
+++ b/web/app/components/header/account-setting/data-source-page/data-source-website/index.tsx
@@ -7,15 +7,15 @@ import cn from 'classnames'
import Panel from '../panel'
import { DataSourceType } from '../panel/types'
import ConfigFirecrawlModal from './config-firecrawl-modal'
-import { fetchFirecrawlApiKey, removeFirecrawlApiKey } from '@/service/datasets'
+import { fetchDataSources, removeDataSourceApiKeyBinding } from '@/service/datasets'
import type {
- DataSourceWebsiteItem,
+ DataSourceItem,
} from '@/models/common'
import { useAppContext } from '@/context/app-context'
import {
- WebsiteProvider,
+ DataSourceProvider,
} from '@/models/common'
import Toast from '@/app/components/base/toast'
@@ -24,11 +24,11 @@ type Props = {}
const DataSourceWebsite: FC = () => {
const { t } = useTranslation()
const { isCurrentWorkspaceManager } = useAppContext()
- const [list, setList] = useState([])
+ const [sources, setSources] = useState([])
const checkSetApiKey = useCallback(async () => {
- const res = await fetchFirecrawlApiKey() as any
- const list = res.settings.filter((item: DataSourceWebsiteItem) => item.provider === WebsiteProvider.fireCrawl && !item.disabled)
- setList(list)
+ const res = await fetchDataSources() as any
+ const list = res.sources
+ setSources(list)
}, [])
useEffect(() => {
@@ -46,23 +46,33 @@ const DataSourceWebsite: FC = () => {
hideConfig()
}, [checkSetApiKey, hideConfig])
- const handleRemove = useCallback(async () => {
- await removeFirecrawlApiKey(list[0].id)
- setList([])
- Toast.notify({
- type: 'success',
- message: t('common.api.remove'),
- })
- }, [list, t])
+ const getIdByProvider = (provider: string): string | undefined => {
+ const source = sources.find(item => item.provider === provider)
+ return source?.id
+ }
+
+ const handleRemove = useCallback((provider: string) => {
+ return async () => {
+ const dataSourceId = getIdByProvider(provider)
+ if (dataSourceId) {
+ await removeDataSourceApiKeyBinding(dataSourceId)
+ setSources(sources.filter(item => item.provider !== provider))
+ Toast.notify({
+ type: 'success',
+ message: t('common.api.remove'),
+ })
+ }
+ }
+ }, [sources, t])
return (
<>
0}
+ isConfigured={sources.length > 0}
onConfigure={showConfig}
- readonly={!isCurrentWorkspaceManager}
- configuredList={list.map(item => ({
+ readOnly={!isCurrentWorkspaceManager}
+ configuredList={sources.map(item => ({
id: item.id,
logo: ({ className }: { className: string }) => (
🔥
@@ -70,7 +80,7 @@ const DataSourceWebsite: FC = () => {
name: 'FireCrawl',
isActive: true,
}))}
- onRemove={handleRemove}
+ onRemove={handleRemove(DataSourceProvider.fireCrawl)}
/>
{isShowConfig && (
diff --git a/web/app/components/header/account-setting/data-source-page/panel/config-item.tsx b/web/app/components/header/account-setting/data-source-page/panel/config-item.tsx
index 376c4aea7b..0b27dd7392 100644
--- a/web/app/components/header/account-setting/data-source-page/panel/config-item.tsx
+++ b/web/app/components/header/account-setting/data-source-page/panel/config-item.tsx
@@ -26,6 +26,7 @@ type Props = {
notionActions?: {
onChangeAuthorizedPage: () => void
}
+ readOnly: boolean
}
const ConfigItem: FC = ({
@@ -33,6 +34,7 @@ const ConfigItem: FC = ({
payload,
onRemove,
notionActions,
+ readOnly,
}) => {
const { t } = useTranslation()
const isNotion = type === DataSourceType.notion
@@ -65,7 +67,7 @@ const ConfigItem: FC = ({
)}
{
- isWebsite && (
+ isWebsite && !readOnly && (
diff --git a/web/app/components/header/account-setting/data-source-page/panel/index.tsx b/web/app/components/header/account-setting/data-source-page/panel/index.tsx
index b0f6f4ad13..2c27005d1d 100644
--- a/web/app/components/header/account-setting/data-source-page/panel/index.tsx
+++ b/web/app/components/header/account-setting/data-source-page/panel/index.tsx
@@ -14,7 +14,7 @@ type Props = {
type: DataSourceType
isConfigured: boolean
onConfigure: () => void
- readonly: boolean
+ readOnly: boolean
isSupportList?: boolean
configuredList: ConfigItemType[]
onRemove: () => void
@@ -27,7 +27,7 @@ const Panel: FC = ({
type,
isConfigured,
onConfigure,
- readonly,
+ readOnly,
configuredList,
isSupportList,
onRemove,
@@ -67,7 +67,7 @@ const Panel: FC = ({
className={
`flex items-center ml-3 px-3 h-7 bg-white border border-gray-200
rounded-md text-xs font-medium text-gray-700
- ${!readonly ? 'cursor-pointer' : 'grayscale opacity-50 cursor-default'}`
+ ${!readOnly ? 'cursor-pointer' : 'grayscale opacity-50 cursor-default'}`
}
onClick={onConfigure}
>
@@ -79,7 +79,7 @@ const Panel: FC = ({
{isSupportList &&
@@ -96,10 +96,10 @@ const Panel: FC
= ({
{t('common.dataSource.configure')}
@@ -108,28 +108,28 @@ const Panel: FC = ({
{
isConfigured && (
-
-
- {isNotion ? t('common.dataSource.notion.connectedWorkspace') : t('common.dataSource.website.configuredCrawlers')}
+ <>
+
+
+ {isNotion ? t('common.dataSource.notion.connectedWorkspace') : t('common.dataSource.website.configuredCrawlers')}
+
+
-
-
- )
- }
- {
- isConfigured && (
-
- {
- configuredList.map(item => (
-
- ))
- }
-
+
+ {
+ configuredList.map(item => (
+
+ ))
+ }
+
+ >
)
}
diff --git a/web/models/common.ts b/web/models/common.ts
index 730cfee05d..6a941655c5 100644
--- a/web/models/common.ts
+++ b/web/models/common.ts
@@ -175,34 +175,26 @@ export type DataSourceNotion = {
export enum DataSourceCategory {
website = 'website',
}
-export enum WebsiteProvider {
+export enum DataSourceProvider {
fireCrawl = 'firecrawl',
}
-export type WebsiteCredentials = {
- auth_type: 'bearer'
- config: {
- base_url: string
- api_key: string
- }
-}
-
export type FirecrawlConfig = {
api_key: string
base_url: string
}
-export type DataSourceWebsiteItem = {
+export type DataSourceItem = {
id: string
- category: DataSourceCategory.website
- provider: WebsiteProvider
- credentials: WebsiteCredentials
+ category: DataSourceCategory
+ provider: DataSourceProvider
disabled: boolean
created_at: number
updated_at: number
}
-export type DataSourceWebsite = {
- settings: DataSourceWebsiteItem[]
+
+export type DataSources = {
+ sources: DataSourceItem[]
}
export type GithubRepo = {
diff --git a/web/service/datasets.ts b/web/service/datasets.ts
index a382ee8ec8..35330a0dec 100644
--- a/web/service/datasets.ts
+++ b/web/service/datasets.ts
@@ -231,15 +231,15 @@ export const fetchDatasetApiBaseUrl: Fetcher<{ api_base_url: string }, string> =
return get<{ api_base_url: string }>(url)
}
-export const fetchFirecrawlApiKey = () => {
+export const fetchDataSources = () => {
return get('api-key-auth/data-source')
}
-export const createFirecrawlApiKey: Fetcher> = (body) => {
+export const createDataSourceApiKeyBinding: Fetcher> = (body) => {
return post('api-key-auth/data-source/binding', { body })
}
-export const removeFirecrawlApiKey: Fetcher = (id: string) => {
+export const removeDataSourceApiKeyBinding: Fetcher = (id: string) => {
return del(`api-key-auth/data-source/${id}`)
}