| return jsonable_encoder({"plugins": plugins}) | return jsonable_encoder({"plugins": plugins}) | ||||
| class PluginListLatestVersionsApi(Resource): | |||||
| @setup_required | |||||
| @login_required | |||||
| @account_initialization_required | |||||
| def post(self): | |||||
| req = reqparse.RequestParser() | |||||
| req.add_argument("plugin_ids", type=list, required=True, location="json") | |||||
| args = req.parse_args() | |||||
| try: | |||||
| versions = PluginService.list_latest_versions(args["plugin_ids"]) | |||||
| except PluginDaemonClientSideError as e: | |||||
| raise ValueError(e) | |||||
| return jsonable_encoder({"versions": versions}) | |||||
| class PluginListInstallationsFromIdsApi(Resource): | class PluginListInstallationsFromIdsApi(Resource): | ||||
| @setup_required | @setup_required | ||||
| @login_required | @login_required | ||||
| api.add_resource(PluginDebuggingKeyApi, "/workspaces/current/plugin/debugging-key") | api.add_resource(PluginDebuggingKeyApi, "/workspaces/current/plugin/debugging-key") | ||||
| api.add_resource(PluginListApi, "/workspaces/current/plugin/list") | api.add_resource(PluginListApi, "/workspaces/current/plugin/list") | ||||
| api.add_resource(PluginListLatestVersionsApi, "/workspaces/current/plugin/list/latest-versions") | |||||
| api.add_resource(PluginListInstallationsFromIdsApi, "/workspaces/current/plugin/list/installations/ids") | api.add_resource(PluginListInstallationsFromIdsApi, "/workspaces/current/plugin/list/installations/ids") | ||||
| api.add_resource(PluginIconApi, "/workspaces/current/plugin/icon") | api.add_resource(PluginIconApi, "/workspaces/current/plugin/icon") | ||||
| api.add_resource(PluginUploadFromPkgApi, "/workspaces/current/plugin/upload/pkg") | api.add_resource(PluginUploadFromPkgApi, "/workspaces/current/plugin/upload/pkg") |
| name: str | name: str | ||||
| installation_id: str | installation_id: str | ||||
| version: str | version: str | ||||
| latest_version: Optional[str] = None | |||||
| latest_unique_identifier: Optional[str] = None | |||||
| @model_validator(mode="after") | @model_validator(mode="after") | ||||
| def set_plugin_id(self): | def set_plugin_id(self): |
| manager = PluginDebuggingManager() | manager = PluginDebuggingManager() | ||||
| return manager.get_debugging_key(tenant_id) | return manager.get_debugging_key(tenant_id) | ||||
| @staticmethod | |||||
| def list_latest_versions(plugin_ids: Sequence[str]) -> Mapping[str, Optional[LatestPluginCache]]: | |||||
| """ | |||||
| List the latest versions of the plugins | |||||
| """ | |||||
| return PluginService.fetch_latest_plugin_version(plugin_ids) | |||||
| @staticmethod | @staticmethod | ||||
| def list(tenant_id: str) -> list[PluginEntity]: | def list(tenant_id: str) -> list[PluginEntity]: | ||||
| """ | """ | ||||
| """ | """ | ||||
| manager = PluginInstallationManager() | manager = PluginInstallationManager() | ||||
| plugins = manager.list_plugins(tenant_id) | plugins = manager.list_plugins(tenant_id) | ||||
| plugin_ids = [plugin.plugin_id for plugin in plugins if plugin.source == PluginInstallationSource.Marketplace] | |||||
| try: | |||||
| manifests = PluginService.fetch_latest_plugin_version(plugin_ids) | |||||
| except Exception: | |||||
| manifests = {} | |||||
| logger.exception("failed to fetch plugin manifests") | |||||
| for plugin in plugins: | |||||
| if plugin.source == PluginInstallationSource.Marketplace: | |||||
| if plugin.plugin_id in manifests: | |||||
| latest_plugin_cache = manifests[plugin.plugin_id] | |||||
| if latest_plugin_cache: | |||||
| # set latest_version | |||||
| plugin.latest_version = latest_plugin_cache.version | |||||
| plugin.latest_unique_identifier = latest_plugin_cache.unique_identifier | |||||
| return plugins | return plugins | ||||
| @staticmethod | @staticmethod |
| import type { FilterState } from './filter-management' | import type { FilterState } from './filter-management' | ||||
| import FilterManagement from './filter-management' | import FilterManagement from './filter-management' | ||||
| import List from './list' | import List from './list' | ||||
| import { useInstalledPluginList, useInvalidateInstalledPluginList } from '@/service/use-plugins' | |||||
| import { useInstalledLatestVersion, useInstalledPluginList, useInvalidateInstalledPluginList } from '@/service/use-plugins' | |||||
| import PluginDetailPanel from '@/app/components/plugins/plugin-detail-panel' | import PluginDetailPanel from '@/app/components/plugins/plugin-detail-panel' | ||||
| import { usePluginPageContext } from './context' | import { usePluginPageContext } from './context' | ||||
| import { useDebounceFn } from 'ahooks' | import { useDebounceFn } from 'ahooks' | ||||
| import Empty from './empty' | import Empty from './empty' | ||||
| import Loading from '../../base/loading' | import Loading from '../../base/loading' | ||||
| import { PluginSource } from '../types' | |||||
| const PluginsPanel = () => { | const PluginsPanel = () => { | ||||
| const filters = usePluginPageContext(v => v.filters) as FilterState | const filters = usePluginPageContext(v => v.filters) as FilterState | ||||
| const setFilters = usePluginPageContext(v => v.setFilters) | const setFilters = usePluginPageContext(v => v.setFilters) | ||||
| const { data: pluginList, isLoading: isPluginListLoading } = useInstalledPluginList() | const { data: pluginList, isLoading: isPluginListLoading } = useInstalledPluginList() | ||||
| const { data: installedLatestVersion } = useInstalledLatestVersion( | |||||
| pluginList?.plugins | |||||
| .filter(plugin => plugin.source === PluginSource.marketplace) | |||||
| .map(plugin => plugin.plugin_id) ?? [], | |||||
| ) | |||||
| const invalidateInstalledPluginList = useInvalidateInstalledPluginList() | const invalidateInstalledPluginList = useInvalidateInstalledPluginList() | ||||
| const currentPluginID = usePluginPageContext(v => v.currentPluginID) | const currentPluginID = usePluginPageContext(v => v.currentPluginID) | ||||
| const setCurrentPluginID = usePluginPageContext(v => v.setCurrentPluginID) | const setCurrentPluginID = usePluginPageContext(v => v.setCurrentPluginID) | ||||
| setFilters(filters) | setFilters(filters) | ||||
| }, { wait: 500 }) | }, { wait: 500 }) | ||||
| const pluginListWithLatestVersion = useMemo(() => { | |||||
| return pluginList?.plugins.map(plugin => ({ | |||||
| ...plugin, | |||||
| latest_version: installedLatestVersion?.versions[plugin.plugin_id]?.version ?? '', | |||||
| latest_unique_identifier: installedLatestVersion?.versions[plugin.plugin_id]?.unique_identifier ?? '', | |||||
| })) || [] | |||||
| }, [pluginList, installedLatestVersion]) | |||||
| const filteredList = useMemo(() => { | const filteredList = useMemo(() => { | ||||
| const { categories, searchQuery, tags } = filters | const { categories, searchQuery, tags } = filters | ||||
| const filteredList = pluginList?.plugins.filter((plugin) => { | |||||
| const filteredList = pluginListWithLatestVersion.filter((plugin) => { | |||||
| return ( | return ( | ||||
| (categories.length === 0 || categories.includes(plugin.declaration.category)) | (categories.length === 0 || categories.includes(plugin.declaration.category)) | ||||
| && (tags.length === 0 || tags.some(tag => plugin.declaration.tags.includes(tag))) | && (tags.length === 0 || tags.some(tag => plugin.declaration.tags.includes(tag))) | ||||
| ) | ) | ||||
| }) | }) | ||||
| return filteredList | return filteredList | ||||
| }, [pluginList, filters]) | |||||
| }, [pluginListWithLatestVersion, filters]) | |||||
| const currentPluginDetail = useMemo(() => { | const currentPluginDetail = useMemo(() => { | ||||
| const detail = pluginList?.plugins.find(plugin => plugin.plugin_id === currentPluginID) | |||||
| const detail = pluginListWithLatestVersion.find(plugin => plugin.plugin_id === currentPluginID) | |||||
| return detail | return detail | ||||
| }, [currentPluginID, pluginList?.plugins]) | |||||
| }, [currentPluginID, pluginListWithLatestVersion]) | |||||
| const handleHide = () => setCurrentPluginID(undefined) | const handleHide = () => setCurrentPluginID(undefined) | ||||
| plugins: PluginDetail[] | plugins: PluginDetail[] | ||||
| } | } | ||||
| export type InstalledLatestVersionResponse = { | |||||
| versions: { | |||||
| [plugin_id: string]: { | |||||
| unique_identifier: string | |||||
| version: string | |||||
| } | null | |||||
| } | |||||
| } | |||||
| export type UninstallPluginResponse = { | export type UninstallPluginResponse = { | ||||
| success: boolean | success: boolean | ||||
| } | } |
| Dependency, | Dependency, | ||||
| GitHubItemAndMarketPlaceDependency, | GitHubItemAndMarketPlaceDependency, | ||||
| InstallPackageResponse, | InstallPackageResponse, | ||||
| InstalledLatestVersionResponse, | |||||
| InstalledPluginListResponse, | InstalledPluginListResponse, | ||||
| PackageDependency, | PackageDependency, | ||||
| Permissions, | Permissions, | ||||
| }) | }) | ||||
| } | } | ||||
| export const useInstalledLatestVersion = (pluginIds: string[]) => { | |||||
| return useQuery<InstalledLatestVersionResponse>({ | |||||
| queryKey: [NAME_SPACE, 'installedLatestVersion', pluginIds], | |||||
| queryFn: () => post<InstalledLatestVersionResponse>('/workspaces/current/plugin/list/latest-versions', { | |||||
| body: { | |||||
| plugin_ids: pluginIds, | |||||
| }, | |||||
| }), | |||||
| enabled: !!pluginIds.length, | |||||
| initialData: pluginIds.length ? undefined : { versions: {} }, | |||||
| }) | |||||
| } | |||||
| export const useInvalidateInstalledPluginList = () => { | export const useInvalidateInstalledPluginList = () => { | ||||
| const queryClient = useQueryClient() | const queryClient = useQueryClient() | ||||
| const invalidateAllBuiltInTools = useInvalidateAllBuiltInTools() | const invalidateAllBuiltInTools = useInvalidateAllBuiltInTools() |