Merge branch 'feat/plugins' of github.com:langgenius/dify into feat/plugins
This commit is contained in:
commit
c40544a134
@ -67,7 +67,9 @@ const TabSlider: FC<TabSliderProps> = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{option.text}
|
{option.text}
|
||||||
|
{/* if no plugin installed, the badge won't show */}
|
||||||
{option.value === 'plugins'
|
{option.value === 'plugins'
|
||||||
|
&& pluginList.length > 0
|
||||||
&& <Badge
|
&& <Badge
|
||||||
size='s'
|
size='s'
|
||||||
uppercase={true}
|
uppercase={true}
|
||||||
|
@ -22,7 +22,7 @@ type Props = {
|
|||||||
isShowInfo: boolean
|
isShowInfo: boolean
|
||||||
isShowDelete: boolean
|
isShowDelete: boolean
|
||||||
onDelete: () => void
|
onDelete: () => void
|
||||||
meta: MetaData
|
meta?: MetaData
|
||||||
}
|
}
|
||||||
const Action: FC<Props> = ({
|
const Action: FC<Props> = ({
|
||||||
pluginId,
|
pluginId,
|
||||||
@ -99,9 +99,9 @@ const Action: FC<Props> = ({
|
|||||||
|
|
||||||
{isShowPluginInfo && (
|
{isShowPluginInfo && (
|
||||||
<PluginInfo
|
<PluginInfo
|
||||||
repository={meta.repo}
|
repository={meta!.repo}
|
||||||
release={meta.version}
|
release={meta!.version}
|
||||||
packageName={meta.package}
|
packageName={meta!.package}
|
||||||
onHide={hidePluginInfo}
|
onHide={hidePluginInfo}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -13,7 +13,7 @@ import { useTranslation } from 'react-i18next'
|
|||||||
import { usePluginPageContext } from '../plugin-page/context'
|
import { usePluginPageContext } from '../plugin-page/context'
|
||||||
import { Github } from '../../base/icons/src/public/common'
|
import { Github } from '../../base/icons/src/public/common'
|
||||||
import Badge from '../../base/badge'
|
import Badge from '../../base/badge'
|
||||||
import { type InstalledPlugin, PluginSource } from '../types'
|
import { type PluginDetail, PluginSource } from '../types'
|
||||||
import CornerMark from '../card/base/corner-mark'
|
import CornerMark from '../card/base/corner-mark'
|
||||||
import Description from '../card/base/description'
|
import Description from '../card/base/description'
|
||||||
import OrgInfo from '../card/base/org-info'
|
import OrgInfo from '../card/base/org-info'
|
||||||
@ -26,7 +26,7 @@ import { API_PREFIX, MARKETPLACE_URL_PREFIX } from '@/config'
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
className?: string
|
className?: string
|
||||||
plugin: InstalledPlugin
|
plugin: PluginDetail
|
||||||
}
|
}
|
||||||
|
|
||||||
const PluginItem: FC<Props> = ({
|
const PluginItem: FC<Props> = ({
|
||||||
@ -50,6 +50,7 @@ const PluginItem: FC<Props> = ({
|
|||||||
} = plugin
|
} = plugin
|
||||||
const { category, author, name, label, description, icon, verified } = plugin.declaration
|
const { category, author, name, label, description, icon, verified } = plugin.declaration
|
||||||
// Only plugin installed from GitHub need to check if it's the new version
|
// Only plugin installed from GitHub need to check if it's the new version
|
||||||
|
// todo check version manually
|
||||||
const hasNewVersion = useMemo(() => {
|
const hasNewVersion = useMemo(() => {
|
||||||
return source === PluginSource.github && latest_version !== version
|
return source === PluginSource.github && latest_version !== version
|
||||||
}, [source, latest_version, version])
|
}, [source, latest_version, version])
|
||||||
@ -124,7 +125,7 @@ const PluginItem: FC<Props> = ({
|
|||||||
<div className='flex items-center'>
|
<div className='flex items-center'>
|
||||||
{source === PluginSource.github
|
{source === PluginSource.github
|
||||||
&& <>
|
&& <>
|
||||||
<a href={meta.repo} target='_blank' className='flex items-center gap-1'>
|
<a href={meta!.repo} target='_blank' className='flex items-center gap-1'>
|
||||||
<div className='text-text-tertiary system-2xs-medium-uppercase'>{t('plugin.from')}</div>
|
<div className='text-text-tertiary system-2xs-medium-uppercase'>{t('plugin.from')}</div>
|
||||||
<div className='flex items-center space-x-0.5 text-text-secondary'>
|
<div className='flex items-center space-x-0.5 text-text-secondary'>
|
||||||
<Github className='w-3 h-3' />
|
<Github className='w-3 h-3' />
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import type { ReactNode } from 'react'
|
import type { ReactNode } from 'react'
|
||||||
import {
|
import {
|
||||||
|
useMemo,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
@ -9,22 +10,28 @@ import {
|
|||||||
createContext,
|
createContext,
|
||||||
useContextSelector,
|
useContextSelector,
|
||||||
} from 'use-context-selector'
|
} from 'use-context-selector'
|
||||||
import type { InstalledPlugin, Permissions } from '../types'
|
import { useSelector as useAppContextSelector } from '@/context/app-context'
|
||||||
|
import type { Permissions, PluginDetail } from '../types'
|
||||||
import type { FilterState } from './filter-management'
|
import type { FilterState } from './filter-management'
|
||||||
import { PermissionType } from '../types'
|
import { PermissionType } from '../types'
|
||||||
import { fetchInstalledPluginList } from '@/service/plugins'
|
import { fetchInstalledPluginList } from '@/service/plugins'
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useTabSearchParams } from '@/hooks/use-tab-searchparams'
|
||||||
|
|
||||||
export type PluginPageContextValue = {
|
export type PluginPageContextValue = {
|
||||||
containerRef: React.RefObject<HTMLDivElement>
|
containerRef: React.RefObject<HTMLDivElement>
|
||||||
permissions: Permissions
|
permissions: Permissions
|
||||||
setPermissions: (permissions: PluginPageContextValue['permissions']) => void
|
setPermissions: (permissions: PluginPageContextValue['permissions']) => void
|
||||||
currentPluginDetail: InstalledPlugin | undefined
|
currentPluginDetail: PluginDetail | undefined
|
||||||
setCurrentPluginDetail: (plugin: InstalledPlugin) => void
|
setCurrentPluginDetail: (plugin: PluginDetail) => void
|
||||||
installedPluginList: InstalledPlugin[]
|
installedPluginList: PluginDetail[]
|
||||||
mutateInstalledPluginList: () => void
|
mutateInstalledPluginList: () => void
|
||||||
filters: FilterState
|
filters: FilterState
|
||||||
setFilters: (filter: FilterState) => void
|
setFilters: (filter: FilterState) => void
|
||||||
|
activeTab: string
|
||||||
|
setActiveTab: (tab: string) => void
|
||||||
|
options: Array<{ value: string, text: string }>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PluginPageContext = createContext<PluginPageContextValue>({
|
export const PluginPageContext = createContext<PluginPageContextValue>({
|
||||||
@ -44,6 +51,9 @@ export const PluginPageContext = createContext<PluginPageContextValue>({
|
|||||||
searchQuery: '',
|
searchQuery: '',
|
||||||
},
|
},
|
||||||
setFilters: () => {},
|
setFilters: () => {},
|
||||||
|
activeTab: '',
|
||||||
|
setActiveTab: () => {},
|
||||||
|
options: [],
|
||||||
})
|
})
|
||||||
|
|
||||||
type PluginPageContextProviderProps = {
|
type PluginPageContextProviderProps = {
|
||||||
@ -57,6 +67,7 @@ export function usePluginPageContext(selector: (value: PluginPageContextValue) =
|
|||||||
export const PluginPageContextProvider = ({
|
export const PluginPageContextProvider = ({
|
||||||
children,
|
children,
|
||||||
}: PluginPageContextProviderProps) => {
|
}: PluginPageContextProviderProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const containerRef = useRef<HTMLDivElement>(null)
|
const containerRef = useRef<HTMLDivElement>(null)
|
||||||
const [permissions, setPermissions] = useState<PluginPageContextValue['permissions']>({
|
const [permissions, setPermissions] = useState<PluginPageContextValue['permissions']>({
|
||||||
install_permission: PermissionType.noOne,
|
install_permission: PermissionType.noOne,
|
||||||
@ -68,7 +79,22 @@ export const PluginPageContextProvider = ({
|
|||||||
searchQuery: '',
|
searchQuery: '',
|
||||||
})
|
})
|
||||||
const { data, mutate: mutateInstalledPluginList } = useSWR({ url: '/workspaces/current/plugin/list' }, fetchInstalledPluginList)
|
const { data, mutate: mutateInstalledPluginList } = useSWR({ url: '/workspaces/current/plugin/list' }, fetchInstalledPluginList)
|
||||||
const [currentPluginDetail, setCurrentPluginDetail] = useState<InstalledPlugin | undefined>()
|
const [currentPluginDetail, setCurrentPluginDetail] = useState<PluginDetail | undefined>()
|
||||||
|
|
||||||
|
const { enable_marketplace } = useAppContextSelector(s => s.systemFeatures)
|
||||||
|
const options = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{ value: 'plugins', text: t('common.menus.plugins') },
|
||||||
|
...(
|
||||||
|
enable_marketplace
|
||||||
|
? [{ value: 'discover', text: 'Explore Marketplace' }]
|
||||||
|
: []
|
||||||
|
),
|
||||||
|
]
|
||||||
|
}, [t, enable_marketplace])
|
||||||
|
const [activeTab, setActiveTab] = useTabSearchParams({
|
||||||
|
defaultTab: options[0].value,
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PluginPageContext.Provider
|
<PluginPageContext.Provider
|
||||||
@ -82,6 +108,9 @@ export const PluginPageContextProvider = ({
|
|||||||
mutateInstalledPluginList,
|
mutateInstalledPluginList,
|
||||||
filters,
|
filters,
|
||||||
setFilters,
|
setFilters,
|
||||||
|
activeTab,
|
||||||
|
setActiveTab,
|
||||||
|
options,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
114
web/app/components/plugins/plugin-page/empty/index.tsx
Normal file
114
web/app/components/plugins/plugin-page/empty/index.tsx
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import React, { useMemo, useRef, useState } from 'react'
|
||||||
|
import { MagicBox } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices'
|
||||||
|
import { FileZip } from '@/app/components/base/icons/src/vender/solid/files'
|
||||||
|
import { Github } from '@/app/components/base/icons/src/vender/solid/general'
|
||||||
|
import InstallFromGitHub from '@/app/components/plugins/install-plugin/install-from-github'
|
||||||
|
import InstallFromLocalPackage from '@/app/components/plugins/install-plugin/install-from-local-package'
|
||||||
|
import { usePluginPageContext } from '../context'
|
||||||
|
import type { PluginDetail } from '../../types'
|
||||||
|
import { Group } from '@/app/components/base/icons/src/vender/other'
|
||||||
|
import { useSelector as useAppContextSelector } from '@/context/app-context'
|
||||||
|
import Line from '../../marketplace/empty/line'
|
||||||
|
|
||||||
|
const Empty = () => {
|
||||||
|
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||||
|
const [selectedAction, setSelectedAction] = useState<string | null>(null)
|
||||||
|
const [selectedFile, setSelectedFile] = useState<File | null>(null)
|
||||||
|
const { enable_marketplace } = useAppContextSelector(s => s.systemFeatures)
|
||||||
|
const setActiveTab = usePluginPageContext(v => v.setActiveTab)
|
||||||
|
|
||||||
|
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const file = event.target.files?.[0]
|
||||||
|
if (file) {
|
||||||
|
setSelectedFile(file)
|
||||||
|
setSelectedAction('local')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const filters = usePluginPageContext(v => v.filters)
|
||||||
|
const pluginList = usePluginPageContext(v => v.installedPluginList) as PluginDetail[]
|
||||||
|
|
||||||
|
const text = useMemo(() => {
|
||||||
|
if (pluginList.length === 0)
|
||||||
|
return 'No plugins installed'
|
||||||
|
if (filters.categories.length > 0 || filters.tags.length > 0 || filters.searchQuery)
|
||||||
|
return 'No plugins found'
|
||||||
|
}, [pluginList.length, filters])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='grow w-full relative z-0'>
|
||||||
|
{/* skeleton */}
|
||||||
|
<div className='h-full w-full px-12 absolute top-0 grid grid-cols-2 gap-2 overflow-hidden z-10'>
|
||||||
|
{Array.from({ length: 20 }).fill(0).map((_, i) => (
|
||||||
|
<div key={i} className='h-[100px] bg-components-card-bg rounded-xl'/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{/* mask */}
|
||||||
|
<div className='h-full w-full absolute z-20 bg-gradient-to-b from-background-gradient-mask-transparent to-white'/>
|
||||||
|
<div className='flex items-center justify-center h-full relative z-30'>
|
||||||
|
<div className='flex flex-col items-center gap-y-3'>
|
||||||
|
<div className='relative -z-10 flex items-center justify-center w-[52px] h-[52px] rounded-xl
|
||||||
|
bg-components-card-bg border-[1px] border-dashed border-divider-deep shadow-xl shadow-shadow-shadow-5'>
|
||||||
|
<Group className='text-text-tertiary w-5 h-5' />
|
||||||
|
<Line className='absolute -right-[1px] top-1/2 -translate-y-1/2' />
|
||||||
|
<Line className='absolute -left-[1px] top-1/2 -translate-y-1/2' />
|
||||||
|
<Line className='absolute top-0 left-1/2 -translate-x-1/2 -translate-y-1/2 rotate-90' />
|
||||||
|
<Line className='absolute top-full left-1/2 -translate-x-1/2 -translate-y-1/2 rotate-90' />
|
||||||
|
</div>
|
||||||
|
<div className='text-text-tertiary text-sm font-normal'>
|
||||||
|
{text}
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-col w-[240px]'>
|
||||||
|
<input
|
||||||
|
type='file'
|
||||||
|
ref={fileInputRef}
|
||||||
|
style={{ display: 'none' }}
|
||||||
|
onChange={handleFileChange}
|
||||||
|
accept='.difypkg'
|
||||||
|
/>
|
||||||
|
<div className='w-full flex flex-col gap-y-1'>
|
||||||
|
{[
|
||||||
|
...(
|
||||||
|
(enable_marketplace || true)
|
||||||
|
? [{ icon: MagicBox, text: 'Marketplace', action: 'marketplace' }]
|
||||||
|
: []
|
||||||
|
),
|
||||||
|
{ icon: Github, text: 'GitHub', action: 'github' },
|
||||||
|
{ icon: FileZip, text: 'Local Package File', action: 'local' },
|
||||||
|
].map(({ icon: Icon, text, action }) => (
|
||||||
|
<div
|
||||||
|
key={action}
|
||||||
|
className='flex items-center px-3 py-2 gap-x-1 rounded-lg bg-components-button-secondary-bg
|
||||||
|
hover:bg-state-base-hover cursor-pointer border-[0.5px] shadow-shadow-shadow-3 shadow-xs'
|
||||||
|
onClick={() => {
|
||||||
|
if (action === 'local')
|
||||||
|
fileInputRef.current?.click()
|
||||||
|
else if (action === 'marketplace')
|
||||||
|
setActiveTab('discover')
|
||||||
|
else
|
||||||
|
setSelectedAction(action)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon className="w-4 h-4 text-text-tertiary" />
|
||||||
|
<span className='text-text-secondary system-md-regular'>{`Install from ${text}`}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{selectedAction === 'github' && <InstallFromGitHub onClose={() => setSelectedAction(null)} />}
|
||||||
|
{selectedAction === 'local' && selectedFile
|
||||||
|
&& (<InstallFromLocalPackage
|
||||||
|
file={selectedFile}
|
||||||
|
onClose={() => setSelectedAction(null)}
|
||||||
|
onSuccess={() => { }}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Empty.displayName = 'Empty'
|
||||||
|
|
||||||
|
export default React.memo(Empty)
|
@ -18,7 +18,6 @@ import usePermission from './use-permission'
|
|||||||
import DebugInfo from './debug-info'
|
import DebugInfo from './debug-info'
|
||||||
import { usePluginTasksStore } from './store'
|
import { usePluginTasksStore } from './store'
|
||||||
import InstallInfo from './install-info'
|
import InstallInfo from './install-info'
|
||||||
import { useTabSearchParams } from '@/hooks/use-tab-searchparams'
|
|
||||||
import Button from '@/app/components/base/button'
|
import Button from '@/app/components/base/button'
|
||||||
import TabSlider from '@/app/components/base/tab-slider'
|
import TabSlider from '@/app/components/base/tab-slider'
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
@ -100,22 +99,11 @@ const PluginPage = ({
|
|||||||
}] = useBoolean()
|
}] = useBoolean()
|
||||||
const [currentFile, setCurrentFile] = useState<File | null>(null)
|
const [currentFile, setCurrentFile] = useState<File | null>(null)
|
||||||
const containerRef = usePluginPageContext(v => v.containerRef)
|
const containerRef = usePluginPageContext(v => v.containerRef)
|
||||||
|
const options = usePluginPageContext(v => v.options)
|
||||||
|
const [activeTab, setActiveTab] = usePluginPageContext(v => [v.activeTab, v.setActiveTab])
|
||||||
const { enable_marketplace } = useAppContextSelector(s => s.systemFeatures)
|
const { enable_marketplace } = useAppContextSelector(s => s.systemFeatures)
|
||||||
const [installed, total] = [2, 3] // Replace this with the actual progress
|
const [installed, total] = [2, 3] // Replace this with the actual progress
|
||||||
const progressPercentage = (installed / total) * 100
|
const progressPercentage = (installed / total) * 100
|
||||||
const options = useMemo(() => {
|
|
||||||
return [
|
|
||||||
{ value: 'plugins', text: t('common.menus.plugins') },
|
|
||||||
...(
|
|
||||||
enable_marketplace
|
|
||||||
? [{ value: 'discover', text: 'Explore Marketplace' }]
|
|
||||||
: []
|
|
||||||
),
|
|
||||||
]
|
|
||||||
}, [t, enable_marketplace])
|
|
||||||
const [activeTab, setActiveTab] = useTabSearchParams({
|
|
||||||
defaultTab: options[0].value,
|
|
||||||
})
|
|
||||||
|
|
||||||
const uploaderProps = useUploader({
|
const uploaderProps = useUploader({
|
||||||
onFileChange: setCurrentFile,
|
onFileChange: setCurrentFile,
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import PluginItem from '../../plugin-item'
|
import PluginItem from '../../plugin-item'
|
||||||
import type { InstalledPlugin } from '../../types'
|
import type { PluginDetail } from '../../types'
|
||||||
|
|
||||||
type IPluginListProps = {
|
type IPluginListProps = {
|
||||||
pluginList: InstalledPlugin[]
|
pluginList: PluginDetail[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const PluginList: FC<IPluginListProps> = ({ pluginList }) => {
|
const PluginList: FC<IPluginListProps> = ({ pluginList }) => {
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import type { InstalledPlugin } from '../types'
|
import type { PluginDetail } from '../types'
|
||||||
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 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'
|
||||||
|
|
||||||
const PluginsPanel = () => {
|
const PluginsPanel = () => {
|
||||||
const [filters, setFilters] = usePluginPageContext(v => [v.filters, v.setFilters])
|
const [filters, setFilters] = usePluginPageContext(v => [v.filters, v.setFilters])
|
||||||
const pluginList = usePluginPageContext(v => v.installedPluginList) as InstalledPlugin[]
|
const pluginList = usePluginPageContext(v => v.installedPluginList) as PluginDetail[]
|
||||||
const mutateInstalledPluginList = usePluginPageContext(v => v.mutateInstalledPluginList)
|
const mutateInstalledPluginList = usePluginPageContext(v => v.mutateInstalledPluginList)
|
||||||
|
|
||||||
const { run: handleFilterChange } = useDebounceFn((filters: FilterState) => {
|
const { run: handleFilterChange } = useDebounceFn((filters: FilterState) => {
|
||||||
@ -37,11 +38,15 @@ const PluginsPanel = () => {
|
|||||||
onFilterChange={handleFilterChange}
|
onFilterChange={handleFilterChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex px-12 items-start content-start gap-2 flex-grow self-stretch flex-wrap'>
|
{filteredList.length > 0 ? (
|
||||||
<div className='w-full'>
|
<div className='flex px-12 items-start content-start gap-2 flex-grow self-stretch flex-wrap'>
|
||||||
<List pluginList={filteredList} />
|
<div className='w-full'>
|
||||||
|
<List pluginList={filteredList} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
) : (
|
||||||
|
<Empty />
|
||||||
|
)}
|
||||||
<PluginDetailPanel onDelete={() => mutateInstalledPluginList()}/>
|
<PluginDetailPanel onDelete={() => mutateInstalledPluginList()}/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
@ -89,6 +89,24 @@ export type PluginManifestInMarket = {
|
|||||||
install_count: number
|
install_count: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type PluginDetail = {
|
||||||
|
id: string
|
||||||
|
created_at: string
|
||||||
|
updated_at: string
|
||||||
|
name: string
|
||||||
|
plugin_id: string
|
||||||
|
plugin_unique_identifier: string
|
||||||
|
declaration: PluginDeclaration
|
||||||
|
installation_id: string
|
||||||
|
tenant_id: string
|
||||||
|
endpoints_setups: number
|
||||||
|
endpoints_active: number
|
||||||
|
version: string
|
||||||
|
latest_version: string
|
||||||
|
source: PluginSource
|
||||||
|
meta?: MetaData
|
||||||
|
}
|
||||||
|
|
||||||
export type Plugin = {
|
export type Plugin = {
|
||||||
type: PluginType
|
type: PluginType
|
||||||
org: string
|
org: string
|
||||||
@ -244,21 +262,8 @@ export type MetaData = {
|
|||||||
package: string
|
package: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type InstalledPlugin = {
|
|
||||||
plugin_id: string
|
|
||||||
plugin_unique_identifier: string
|
|
||||||
installation_id: string
|
|
||||||
declaration: PluginDeclaration
|
|
||||||
source: PluginSource
|
|
||||||
tenant_id: string
|
|
||||||
version: string
|
|
||||||
latest_version: string
|
|
||||||
endpoints_active: number
|
|
||||||
meta: MetaData
|
|
||||||
}
|
|
||||||
|
|
||||||
export type InstalledPluginListResponse = {
|
export type InstalledPluginListResponse = {
|
||||||
plugins: InstalledPlugin[]
|
plugins: PluginDetail[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UninstallPluginResponse = {
|
export type UninstallPluginResponse = {
|
||||||
|
Loading…
Reference in New Issue
Block a user