diff --git a/web/app/components/plugins/plugin-setting/modal.tsx b/web/app/components/plugins/permission-setting-modal/modal.tsx similarity index 66% rename from web/app/components/plugins/plugin-setting/modal.tsx rename to web/app/components/plugins/permission-setting-modal/modal.tsx index 83076cfd2b..a6eb0db852 100644 --- a/web/app/components/plugins/plugin-setting/modal.tsx +++ b/web/app/components/plugins/permission-setting-modal/modal.tsx @@ -1,33 +1,43 @@ 'use client' import type { FC } from 'react' -import React, { useState } from 'react' +import React, { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Modal from '@/app/components/base/modal' import OptionCard from '@/app/components/workflow/nodes/_base/components/option-card' import Button from '@/app/components/base/button' - -enum PluginManagementOption { - Everyone = 'Everyone', - Admins = 'Admins', - NoOne = 'No one', -} +import type { Permissions } from '@/app/components/plugins/types' +import { PermissionType } from '@/app/components/plugins/types' type Props = { - show: boolean + payload: Permissions onHide: () => void + onSave: (payload: Permissions) => void } const PluginSettingModal: FC = ({ - show, + payload, onHide, + onSave, }) => { const { t } = useTranslation() - const [manageOption, setManageOption] = useState(PluginManagementOption.Everyone) - const [debugOption, setDebugOption] = useState(PluginManagementOption.Everyone) + const [tempPrivilege, setTempPrivilege] = useState(payload) + const handlePrivilegeChange = useCallback((key: string) => { + return (value: PermissionType) => { + setTempPrivilege({ + ...tempPrivilege, + [key]: value, + }) + } + }, [tempPrivilege]) + + const handleSave = useCallback(() => { + onSave(tempPrivilege) + onHide() + }, [tempPrivilege]) return ( = ({
{[ - { title: 'Who can install and manage plugins?', key: 'manage', value: manageOption, setValue: setManageOption }, - { title: 'Who can debug plugins?', key: 'debug', value: debugOption, setValue: setDebugOption }, - ].map(({ title, key, value, setValue }) => ( + { title: 'Who can install and manage plugins?', key: 'canInstall', value: tempPrivilege.canInstall }, + { title: 'Who can debug plugins?', key: 'canDebugger', value: tempPrivilege.canDebugger }, + ].map(({ title, key, value }) => (
{title}
- {Object.values(PluginManagementOption).map(option => ( + {[PermissionType.everyone, PermissionType.admin, PermissionType.noOne].map(option => ( setValue(option)} + onSelect={() => handlePrivilegeChange(key)(option)} selected={value === option} className="flex-1" /> @@ -69,6 +79,7 @@ const PluginSettingModal: FC = ({ diff --git a/web/app/components/plugins/plugin-setting/style.module.css b/web/app/components/plugins/permission-setting-modal/style.module.css similarity index 100% rename from web/app/components/plugins/plugin-setting/style.module.css rename to web/app/components/plugins/permission-setting-modal/style.module.css diff --git a/web/app/components/plugins/plugin-page/context.tsx b/web/app/components/plugins/plugin-page/context.tsx index 2d026c7cd1..edf0591f81 100644 --- a/web/app/components/plugins/plugin-page/context.tsx +++ b/web/app/components/plugins/plugin-page/context.tsx @@ -3,18 +3,29 @@ import type { ReactNode } from 'react' import { useRef, + useState, } from 'react' import { createContext, useContextSelector, } from 'use-context-selector' +import type { Permissions } from '../types' +import { PermissionType } from '../types' export type PluginPageContextValue = { containerRef: React.RefObject + permissions: Permissions + setPermissions: (permissions: PluginPageContextValue['permissions']) => void + } export const PluginPageContext = createContext({ containerRef: { current: null }, + permissions: { + canInstall: PermissionType.noOne, + canDebugger: PermissionType.noOne, + }, + setPermissions: () => { }, }) type PluginPageContextProviderProps = { @@ -29,11 +40,17 @@ export const PluginPageContextProvider = ({ children, }: PluginPageContextProviderProps) => { const containerRef = useRef(null) + const [permissions, setPermissions] = useState({ + canInstall: PermissionType.noOne, + canDebugger: PermissionType.noOne, + }) return ( {children} diff --git a/web/app/components/plugins/plugin-page/index.tsx b/web/app/components/plugins/plugin-page/index.tsx index 14e065250a..2240db20dd 100644 --- a/web/app/components/plugins/plugin-page/index.tsx +++ b/web/app/components/plugins/plugin-page/index.tsx @@ -9,6 +9,7 @@ import { RiDragDropLine, RiEqualizer2Line, } from '@remixicon/react' +import { useBoolean } from 'ahooks' import InstallFromLocalPackage from '../install-plugin/install-from-local-package' import { PluginPageContextProvider, @@ -16,13 +17,14 @@ import { } from './context' import InstallPluginDropdown from './install-plugin-dropdown' import { useUploader } from './use-uploader' +import usePermission from './use-permission' import { useTabSearchParams } from '@/hooks/use-tab-searchparams' -import { useModalContext } from '@/context/modal-context' import Button from '@/app/components/base/button' import TabSlider from '@/app/components/base/tab-slider' import ActionButton from '@/app/components/base/action-button' import Tooltip from '@/app/components/base/tooltip' import cn from '@/utils/classnames' +import PermissionSetModal from '@/app/components/plugins/permission-setting-modal/modal' export type PluginPageProps = { plugins: React.ReactNode @@ -33,7 +35,17 @@ const PluginPage = ({ marketplace, }: PluginPageProps) => { const { t } = useTranslation() - const { setShowPluginSettingModal } = useModalContext() as any + const { + canInstall, + canDebugger, + canSetPermissions, + permissions, + setPermissions, + } = usePermission() + const [showPluginSettingModal, { + setTrue: setShowPluginSettingModal, + setFalse: setHidePluginSettingModal, + }] = useBoolean() const [currentFile, setCurrentFile] = useState(null) const containerRef = usePluginPageContext(v => v.containerRef) const options = useMemo(() => { @@ -76,52 +88,60 @@ const PluginPage = ({ />
- - -
- Debugging -
- View docs - -
-
-
- {['Port', 'Key'].map((label, index) => ( -
- {label} -
- - {index === 0 ? 'cloud.dify,ai:2048' : 'A1B2C3D4E5F6G7H8'} - - - - + {canInstall && ( + + )} + { + canDebugger && ( + +
+ Debugging +
+ View docs +
- ))} -
- - } - popupClassName='flex flex-col items-start w-[256px] px-4 py-3.5 gap-1 border border-components-panel-border - rounded-xl bg-components-tooltip-bg shadows-shadow-lg z-50' - asChild={false} - position='bottom' - > - - - +
+ {['Port', 'Key'].map((label, index) => ( +
+ {label} +
+ + {index === 0 ? 'cloud.dify,ai:2048' : 'A1B2C3D4E5F6G7H8'} + + + + +
+
+ ))} +
+ + } + popupClassName='flex flex-col items-start w-[256px] px-4 py-3.5 gap-1 border border-components-panel-border + rounded-xl bg-components-tooltip-bg shadows-shadow-lg z-50' + asChild={false} + position='bottom' + > + + + ) + } + { + canSetPermissions && ( + + ) + }
@@ -139,7 +159,7 @@ const PluginPage = ({ Drop plugin package here to install
{currentFile && ( - {})} /> + { })} /> )} {})} + onChange={fileChangeHandle ?? (() => { })} /> )} { activeTab === 'discover' && marketplace } + + {showPluginSettingModal && ( + + )}
) } diff --git a/web/app/components/plugins/plugin-page/use-permission.ts b/web/app/components/plugins/plugin-page/use-permission.ts new file mode 100644 index 0000000000..583839be94 --- /dev/null +++ b/web/app/components/plugins/plugin-page/use-permission.ts @@ -0,0 +1,39 @@ +import { useEffect } from 'react' +import { PermissionType } from '../types' +import { + usePluginPageContext, +} from './context' +import { useAppContext } from '@/context/app-context' + +const hasPermission = (permission: PermissionType, isAdmin: boolean) => { + if (permission === PermissionType.noOne) + return false + + if (permission === PermissionType.everyone) + return true + + return isAdmin +} + +const usePermission = () => { + const { isCurrentWorkspaceManager, isCurrentWorkspaceOwner } = useAppContext() + const [permissions, setPermissions] = usePluginPageContext(v => [v.permissions, v.setPermissions]) + const isAdmin = isCurrentWorkspaceManager || isCurrentWorkspaceOwner + + useEffect(() => { + // TODO: fetch permissions from server + setPermissions({ + canInstall: PermissionType.everyone, + canDebugger: PermissionType.everyone, + }) + }, []) + return { + canInstall: hasPermission(permissions.canInstall, isAdmin), + canDebugger: hasPermission(permissions.canDebugger, isAdmin), + canSetPermissions: isAdmin, + permissions, + setPermissions, + } +} + +export default usePermission diff --git a/web/app/components/plugins/types.ts b/web/app/components/plugins/types.ts index 3b8fdd12f2..d5b5098ab7 100644 --- a/web/app/components/plugins/types.ts +++ b/web/app/components/plugins/types.ts @@ -25,3 +25,14 @@ export type Plugin = { settings: CredentialFormSchemaBase[] } } + +export enum PermissionType { + everyone = 'everyone', + admin = 'admin', + noOne = 'noOne', +} + +export type Permissions = { + canInstall: PermissionType + canDebugger: PermissionType +} diff --git a/web/context/modal-context.tsx b/web/context/modal-context.tsx index 289c6dcdd2..727268a29a 100644 --- a/web/context/modal-context.tsx +++ b/web/context/modal-context.tsx @@ -5,7 +5,6 @@ import { useCallback, useState } from 'react' import { createContext, useContext, useContextSelector } from 'use-context-selector' import { useRouter, useSearchParams } from 'next/navigation' import AccountSetting from '@/app/components/header/account-setting' -import PluginSettingModal from '@/app/components/plugins/plugin-setting/modal' import ApiBasedExtensionModal from '@/app/components/header/account-setting/api-based-extension-page/modal' import ModerationSettingModal from '@/app/components/app/configuration/toolbox/moderation/moderation-setting-modal' import ExternalDataToolModal from '@/app/components/app/configuration/tools/external-data-tool-modal' @@ -52,7 +51,6 @@ export type LoadBalancingEntryModalType = ModelModalType & { } export type ModalContextState = { setShowAccountSettingModal: Dispatch | null>> - setShowPluginSettingModal: () => void setShowApiBasedExtensionModal: Dispatch | null>> setShowModerationSettingModal: Dispatch | null>> setShowExternalDataToolModal: Dispatch | null>> @@ -65,7 +63,6 @@ export type ModalContextState = { } const ModalContext = createContext({ setShowAccountSettingModal: () => { }, - setShowPluginSettingModal: () => { }, setShowApiBasedExtensionModal: () => { }, setShowModerationSettingModal: () => { }, setShowExternalDataToolModal: () => { }, @@ -103,7 +100,6 @@ export const ModalContextProvider = ({ const router = useRouter() const [showPricingModal, setShowPricingModal] = useState(searchParams.get('show-pricing') === '1') const [showAnnotationFullModal, setShowAnnotationFullModal] = useState(false) - const [showPluginSettingModal, setShowPluginSettingModal] = useState(false) const handleCancelAccountSettingModal = () => { setShowAccountSettingModal(null) if (showAccountSettingModal?.onCancelCallback) @@ -197,7 +193,6 @@ export const ModalContextProvider = ({ return ( setShowPluginSettingModal(true), setShowApiBasedExtensionModal, setShowModerationSettingModal, setShowExternalDataToolModal, @@ -219,15 +214,6 @@ export const ModalContextProvider = ({ ) } - { - !!showPluginSettingModal && ( - setShowPluginSettingModal(false)} - /> - ) - } - { !!showApiBasedExtensionModal && (