mirror of
https://github.com/langgenius/dify.git
synced 2026-05-09 12:59:18 +08:00
tweaks
This commit is contained in:
parent
f7cf0c050e
commit
96bc73e47d
@ -6,6 +6,7 @@ import type {
|
||||
} from '@/contract/console/deployments'
|
||||
import { useMemo } from 'react'
|
||||
import { useDeploymentAppData, useDeploymentsStore } from '../store'
|
||||
import { deploymentsSelectors } from '../store/selectors'
|
||||
import {
|
||||
deployedRows,
|
||||
} from '../utils'
|
||||
@ -28,7 +29,7 @@ type AccessTabProps = {
|
||||
|
||||
const AccessTab: FC<AccessTabProps> = ({ instanceId: appId }) => {
|
||||
const appData = useDeploymentAppData(appId)
|
||||
const createdApiToken = useDeploymentsStore(state => state.createdApiToken)
|
||||
const createdApiToken = useDeploymentsStore(deploymentsSelectors.createdApiToken)
|
||||
const clearCreatedApiToken = useDeploymentsStore(state => state.clearCreatedApiToken)
|
||||
const generateApiKey = useDeploymentsStore(state => state.generateApiKey)
|
||||
const revokeApiKey = useDeploymentsStore(state => state.revokeApiKey)
|
||||
|
||||
@ -5,6 +5,7 @@ import { useQuery } from '@tanstack/react-query'
|
||||
import { useMemo } from 'react'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import { useDeploymentsStore } from '../store'
|
||||
import { deploymentsSelectors } from '../store/selectors'
|
||||
|
||||
const MAX_SOURCE_APPS = 100
|
||||
|
||||
@ -17,8 +18,8 @@ type UseSourceAppsOptions = {
|
||||
|
||||
export function useSourceApps(options: UseSourceAppsOptions = {}) {
|
||||
const { enabled = true, environmentId, keyword, notDeployed } = options
|
||||
const instancesById = useDeploymentsStore(state => state.instancesById)
|
||||
const listRefreshToken = useDeploymentsStore(state => state.listRefreshToken)
|
||||
const instancesById = useDeploymentsStore(deploymentsSelectors.instancesById)
|
||||
const listRefreshToken = useDeploymentsStore(deploymentsSelectors.listRefreshToken)
|
||||
|
||||
const query = useMemo(() => ({
|
||||
pageNumber: 1,
|
||||
|
||||
@ -1,348 +1,19 @@
|
||||
import type { DeploymentAppData, ListAppDeploymentsQuery } from './data'
|
||||
import type { AppInfo } from './types'
|
||||
import type { AccessSubject, APIToken, ConsoleReleaseSummary, ListAppDeploymentsReply } from '@/contract/console/deployments'
|
||||
import type { DeploymentsStore } from './store/actions'
|
||||
|
||||
import { create } from 'zustand'
|
||||
import {
|
||||
cancelDeployment,
|
||||
createApiKey,
|
||||
createAppInstance,
|
||||
createDeployment,
|
||||
deleteApiKey,
|
||||
deleteAppInstance,
|
||||
fetchDeploymentAppData,
|
||||
listAppDeployments,
|
||||
patchAccessChannel,
|
||||
patchDeveloperAPI,
|
||||
refreshDeploymentAppData,
|
||||
refreshDeploymentAppDataWhenReady,
|
||||
toAppInfoFromOverview,
|
||||
toAppInfoFromSummary,
|
||||
undeployEnvironment,
|
||||
updateAppInstance,
|
||||
updateEnvironmentAccessPolicy,
|
||||
waitForAppInstanceInDeploymentList,
|
||||
} from './data'
|
||||
import { createDeploymentsActions } from './store/actions'
|
||||
import { initialDeploymentsState } from './store/initial-state'
|
||||
import { deploymentsSelectors } from './store/selectors'
|
||||
|
||||
export type StartDeployParams = {
|
||||
appId: string
|
||||
environmentId: string
|
||||
releaseId?: string
|
||||
releaseNote?: string
|
||||
}
|
||||
|
||||
type OpenDeployDrawerParams = {
|
||||
appId: string
|
||||
environmentId?: string
|
||||
releaseId?: string
|
||||
}
|
||||
|
||||
type OpenRollbackParams = {
|
||||
appId: string
|
||||
environmentId: string
|
||||
targetReleaseId: string
|
||||
deploymentId?: string
|
||||
}
|
||||
|
||||
type CreatedApiToken = Pick<APIToken, 'id' | 'environmentId' | 'maskedPrefix' | 'name'> & {
|
||||
appId: string
|
||||
token: string
|
||||
}
|
||||
|
||||
export type CreateInstanceParams = {
|
||||
sourceAppId: string
|
||||
name: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
export type CreateInstanceResult = {
|
||||
appInstanceId: string
|
||||
initialRelease?: ConsoleReleaseSummary
|
||||
}
|
||||
|
||||
type DeploymentsState = {
|
||||
instancesById: Record<string, AppInfo>
|
||||
appData: Record<string, DeploymentAppData>
|
||||
listRefreshToken: number
|
||||
createdApiToken?: CreatedApiToken
|
||||
|
||||
deployDrawer: {
|
||||
open: boolean
|
||||
appId?: string
|
||||
environmentId?: string
|
||||
releaseId?: string
|
||||
}
|
||||
rollbackModal: {
|
||||
open: boolean
|
||||
appId?: string
|
||||
environmentId?: string
|
||||
deploymentId?: string
|
||||
targetReleaseId?: string
|
||||
}
|
||||
createInstanceModal: { open: boolean }
|
||||
|
||||
openDeployDrawer: (params: OpenDeployDrawerParams) => void
|
||||
closeDeployDrawer: () => void
|
||||
|
||||
openRollbackModal: (params: OpenRollbackParams) => void
|
||||
closeRollbackModal: () => void
|
||||
|
||||
openCreateInstanceModal: () => void
|
||||
closeCreateInstanceModal: () => void
|
||||
|
||||
upsertInstances: (apps: AppInfo[]) => void
|
||||
applyAppData: (data: DeploymentAppData) => void
|
||||
bumpDeploymentListRefresh: () => void
|
||||
fetchSourceApps: (query: ListAppDeploymentsQuery) => Promise<ListAppDeploymentsReply>
|
||||
fetchAppData: (appId: string) => Promise<DeploymentAppData>
|
||||
refreshAppData: (appId: string) => Promise<void>
|
||||
|
||||
createInstance: (params: CreateInstanceParams) => Promise<CreateInstanceResult>
|
||||
updateInstance: (appId: string, patch: Pick<AppInfo, 'name' | 'description'>) => Promise<void>
|
||||
switchSourceApp: (appId: string, nextAppId: string) => void
|
||||
deleteInstance: (appId: string) => Promise<void>
|
||||
|
||||
startDeploy: (params: StartDeployParams) => Promise<void>
|
||||
retryDeploy: (appId: string, environmentId: string, targetReleaseId: string) => Promise<void>
|
||||
rollbackDeployment: (appId: string, environmentId: string, targetReleaseId: string) => Promise<void>
|
||||
undeployDeployment: (appId: string, environmentId: string, deploymentId?: string, isDeploying?: boolean) => Promise<void>
|
||||
|
||||
generateApiKey: (appId: string, environmentId: string) => Promise<void>
|
||||
revokeApiKey: (appId: string, environmentId: string, apiKeyId: string) => Promise<void>
|
||||
clearCreatedApiToken: () => void
|
||||
toggleAccessChannel: (appId: string, channel: string, enabled: boolean, expectedVersion: number) => Promise<void>
|
||||
setEnvironmentAccessPolicy: (
|
||||
appId: string,
|
||||
environmentId: string,
|
||||
channel: string,
|
||||
enabled: boolean,
|
||||
accessMode: string,
|
||||
subjects: AccessSubject[],
|
||||
expectedVersion: number,
|
||||
) => Promise<void>
|
||||
}
|
||||
|
||||
export const useDeploymentsStore = create<DeploymentsState>((set, get) => ({
|
||||
instancesById: {},
|
||||
appData: {},
|
||||
listRefreshToken: 0,
|
||||
createdApiToken: undefined,
|
||||
|
||||
deployDrawer: { open: false },
|
||||
rollbackModal: { open: false },
|
||||
createInstanceModal: { open: false },
|
||||
|
||||
openDeployDrawer: params => set({
|
||||
deployDrawer: {
|
||||
open: true,
|
||||
appId: params.appId,
|
||||
environmentId: params.environmentId,
|
||||
releaseId: params.releaseId,
|
||||
},
|
||||
}),
|
||||
closeDeployDrawer: () => set({ deployDrawer: { open: false } }),
|
||||
|
||||
openRollbackModal: ({ appId, environmentId, deploymentId, targetReleaseId }) => set({
|
||||
rollbackModal: { open: true, appId, environmentId, deploymentId, targetReleaseId },
|
||||
}),
|
||||
closeRollbackModal: () => set({ rollbackModal: { open: false } }),
|
||||
|
||||
openCreateInstanceModal: () => set({ createInstanceModal: { open: true } }),
|
||||
closeCreateInstanceModal: () => set({ createInstanceModal: { open: false } }),
|
||||
|
||||
upsertInstances: apps => set(state => ({
|
||||
instancesById: apps.reduce((next, app) => {
|
||||
next[app.id] = {
|
||||
...next[app.id],
|
||||
...app,
|
||||
}
|
||||
return next
|
||||
}, { ...state.instancesById }),
|
||||
})),
|
||||
|
||||
applyAppData: data => set(state => ({
|
||||
appData: {
|
||||
...state.appData,
|
||||
[data.appId]: data,
|
||||
},
|
||||
})),
|
||||
|
||||
bumpDeploymentListRefresh: () => set(state => ({
|
||||
listRefreshToken: state.listRefreshToken + 1,
|
||||
})),
|
||||
|
||||
fetchSourceApps: async (query) => {
|
||||
const response = await listAppDeployments(query)
|
||||
const apps = response.data
|
||||
?.map(toAppInfoFromSummary)
|
||||
.filter((app): app is AppInfo => Boolean(app)) ?? []
|
||||
get().upsertInstances(apps)
|
||||
return response
|
||||
},
|
||||
|
||||
fetchAppData: async (appId) => {
|
||||
const data = await fetchDeploymentAppData(appId)
|
||||
get().applyAppData(data)
|
||||
const app = toAppInfoFromOverview(data.overview.instance)
|
||||
if (app)
|
||||
get().upsertInstances([app])
|
||||
return data
|
||||
},
|
||||
|
||||
refreshAppData: async (appId) => {
|
||||
const data = await refreshDeploymentAppData(appId)
|
||||
get().applyAppData(data)
|
||||
const app = toAppInfoFromOverview(data.overview.instance)
|
||||
if (app)
|
||||
get().upsertInstances([app])
|
||||
},
|
||||
|
||||
createInstance: async ({ sourceAppId, name, description }) => {
|
||||
const response = await createAppInstance({ sourceAppId, name, description })
|
||||
if (!response.appInstanceId)
|
||||
throw new Error('Create app instance did not return an appInstanceId.')
|
||||
set({ createInstanceModal: { open: false } })
|
||||
await Promise.allSettled([
|
||||
refreshDeploymentAppDataWhenReady(response.appInstanceId)
|
||||
.then((data) => {
|
||||
get().applyAppData(data)
|
||||
const app = toAppInfoFromOverview(data.overview.instance)
|
||||
if (app)
|
||||
get().upsertInstances([app])
|
||||
}),
|
||||
waitForAppInstanceInDeploymentList(response.appInstanceId).then((list) => {
|
||||
const apps = list?.data
|
||||
?.map(toAppInfoFromSummary)
|
||||
.filter((app): app is AppInfo => Boolean(app)) ?? []
|
||||
get().upsertInstances(apps)
|
||||
}),
|
||||
])
|
||||
get().bumpDeploymentListRefresh()
|
||||
return {
|
||||
appInstanceId: response.appInstanceId,
|
||||
initialRelease: response.initialRelease,
|
||||
}
|
||||
},
|
||||
|
||||
updateInstance: async (appId, patch) => {
|
||||
await updateAppInstance(appId, {
|
||||
name: patch.name,
|
||||
description: patch.description,
|
||||
})
|
||||
await get().refreshAppData(appId)
|
||||
get().bumpDeploymentListRefresh()
|
||||
set(state => ({
|
||||
instancesById: {
|
||||
...state.instancesById,
|
||||
[appId]: {
|
||||
...state.instancesById[appId],
|
||||
id: appId,
|
||||
name: patch.name,
|
||||
mode: state.instancesById[appId]?.mode ?? 'workflow',
|
||||
description: patch.description,
|
||||
},
|
||||
},
|
||||
}))
|
||||
},
|
||||
|
||||
switchSourceApp: () => undefined,
|
||||
|
||||
deleteInstance: async (appId) => {
|
||||
await deleteAppInstance(appId)
|
||||
set((state) => {
|
||||
const { [appId]: _removed, ...appData } = state.appData
|
||||
const { [appId]: _removedInstance, ...instancesById } = state.instancesById
|
||||
return {
|
||||
instancesById,
|
||||
appData,
|
||||
}
|
||||
})
|
||||
get().bumpDeploymentListRefresh()
|
||||
},
|
||||
|
||||
startDeploy: async ({ appId, environmentId, releaseId, releaseNote }) => {
|
||||
set({ deployDrawer: { open: false } })
|
||||
await createDeployment({ appId, environmentId, releaseId, releaseNote })
|
||||
await get().refreshAppData(appId)
|
||||
get().bumpDeploymentListRefresh()
|
||||
},
|
||||
|
||||
retryDeploy: async (appId, environmentId, targetReleaseId) => {
|
||||
await createDeployment({ appId, environmentId, releaseId: targetReleaseId })
|
||||
await get().refreshAppData(appId)
|
||||
get().bumpDeploymentListRefresh()
|
||||
},
|
||||
|
||||
rollbackDeployment: async (appId, environmentId, targetReleaseId) => {
|
||||
set({ rollbackModal: { open: false } })
|
||||
await createDeployment({ appId, environmentId, releaseId: targetReleaseId })
|
||||
await get().refreshAppData(appId)
|
||||
get().bumpDeploymentListRefresh()
|
||||
},
|
||||
|
||||
undeployDeployment: async (appId, _environmentId, runtimeInstanceId, isDeploying) => {
|
||||
if (!runtimeInstanceId)
|
||||
return
|
||||
if (isDeploying)
|
||||
await cancelDeployment(appId, runtimeInstanceId)
|
||||
else
|
||||
await undeployEnvironment(appId, runtimeInstanceId)
|
||||
await get().refreshAppData(appId)
|
||||
get().bumpDeploymentListRefresh()
|
||||
},
|
||||
|
||||
generateApiKey: async (appId, environmentId) => {
|
||||
const appData = get().appData[appId]
|
||||
const existingCount = appData?.accessConfig.developerApi?.apiKeys?.filter(key =>
|
||||
(key.environmentId ?? key.environment?.id) === environmentId,
|
||||
).length ?? 0
|
||||
const environmentName = appData
|
||||
?.environmentDeployments
|
||||
.data
|
||||
?.find(row => row.environment?.id === environmentId)
|
||||
?.environment
|
||||
?.name ?? 'env'
|
||||
const label = `${environmentName}-key-${String(existingCount + 1).padStart(3, '0')}`
|
||||
const response = await createApiKey(appId, environmentId, label)
|
||||
await get().refreshAppData(appId)
|
||||
if (response.apiToken?.token) {
|
||||
set({
|
||||
createdApiToken: {
|
||||
id: response.apiToken.id,
|
||||
appId,
|
||||
environmentId: response.apiToken.environmentId ?? response.apiToken.environment?.id,
|
||||
maskedPrefix: response.apiToken.maskedPrefix ?? response.apiToken.maskedKey,
|
||||
name: response.apiToken.name || label,
|
||||
token: response.apiToken.token,
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
revokeApiKey: async (appId, _environmentId, apiKeyId) => {
|
||||
await deleteApiKey(appId, apiKeyId)
|
||||
await get().refreshAppData(appId)
|
||||
},
|
||||
|
||||
clearCreatedApiToken: () => set({ createdApiToken: undefined }),
|
||||
|
||||
toggleAccessChannel: async (appId, channel, enabled) => {
|
||||
if (channel === 'api')
|
||||
await patchDeveloperAPI(appId, enabled)
|
||||
else
|
||||
await patchAccessChannel(appId, enabled)
|
||||
await get().refreshAppData(appId)
|
||||
},
|
||||
|
||||
setEnvironmentAccessPolicy: async (appId, environmentId, _channel, _enabled, accessMode, subjects) => {
|
||||
await updateEnvironmentAccessPolicy(appId, environmentId, accessMode, subjects)
|
||||
await get().refreshAppData(appId)
|
||||
},
|
||||
export const useDeploymentsStore = create<DeploymentsStore>()((...parameters) => ({
|
||||
...initialDeploymentsState,
|
||||
...createDeploymentsActions(...parameters),
|
||||
}))
|
||||
|
||||
export const useDeploymentInstance = (appId?: string) => {
|
||||
return useDeploymentsStore(state => appId ? state.instancesById[appId] : undefined)
|
||||
return useDeploymentsStore(deploymentsSelectors.instance(appId))
|
||||
}
|
||||
|
||||
export const useDeploymentAppData = (appId?: string) => {
|
||||
return useDeploymentsStore(state => appId ? state.appData[appId] : undefined)
|
||||
return useDeploymentsStore(deploymentsSelectors.appData(appId))
|
||||
}
|
||||
|
||||
354
web/features/deployments/store/actions.ts
Normal file
354
web/features/deployments/store/actions.ts
Normal file
@ -0,0 +1,354 @@
|
||||
import type { StateCreator } from 'zustand'
|
||||
import type {
|
||||
CreateInstanceParams,
|
||||
DeploymentAppData,
|
||||
ListAppDeploymentsQuery,
|
||||
} from '../data'
|
||||
import type { AppInfo } from '../types'
|
||||
import type { DeploymentsState } from './initial-state'
|
||||
import type { AccessSubject, ConsoleReleaseSummary, ListAppDeploymentsReply } from '@/contract/console/deployments'
|
||||
import {
|
||||
cancelDeployment,
|
||||
createApiKey,
|
||||
createAppInstance,
|
||||
createDeployment,
|
||||
deleteApiKey,
|
||||
deleteAppInstance,
|
||||
fetchDeploymentAppData,
|
||||
listAppDeployments,
|
||||
patchAccessChannel,
|
||||
patchDeveloperAPI,
|
||||
refreshDeploymentAppData,
|
||||
refreshDeploymentAppDataWhenReady,
|
||||
toAppInfoFromOverview,
|
||||
toAppInfoFromSummary,
|
||||
undeployEnvironment,
|
||||
updateAppInstance,
|
||||
updateEnvironmentAccessPolicy,
|
||||
waitForAppInstanceInDeploymentList,
|
||||
} from '../data'
|
||||
|
||||
export type StartDeployParams = {
|
||||
appId: string
|
||||
environmentId: string
|
||||
releaseId?: string
|
||||
releaseNote?: string
|
||||
}
|
||||
|
||||
type OpenDeployDrawerParams = {
|
||||
appId: string
|
||||
environmentId?: string
|
||||
releaseId?: string
|
||||
}
|
||||
|
||||
type OpenRollbackParams = {
|
||||
appId: string
|
||||
environmentId: string
|
||||
targetReleaseId: string
|
||||
deploymentId?: string
|
||||
}
|
||||
|
||||
export type CreateInstanceResult = {
|
||||
appInstanceId: string
|
||||
initialRelease?: ConsoleReleaseSummary
|
||||
}
|
||||
|
||||
export type DeploymentsAction = {
|
||||
openDeployDrawer: (params: OpenDeployDrawerParams) => void
|
||||
closeDeployDrawer: () => void
|
||||
|
||||
openRollbackModal: (params: OpenRollbackParams) => void
|
||||
closeRollbackModal: () => void
|
||||
|
||||
openCreateInstanceModal: () => void
|
||||
closeCreateInstanceModal: () => void
|
||||
|
||||
upsertInstances: (apps: AppInfo[]) => void
|
||||
applyAppData: (data: DeploymentAppData) => void
|
||||
bumpDeploymentListRefresh: () => void
|
||||
fetchSourceApps: (query: ListAppDeploymentsQuery) => Promise<ListAppDeploymentsReply>
|
||||
fetchAppData: (appId: string) => Promise<DeploymentAppData>
|
||||
refreshAppData: (appId: string) => Promise<void>
|
||||
|
||||
createInstance: (params: CreateInstanceParams) => Promise<CreateInstanceResult>
|
||||
updateInstance: (appId: string, patch: Pick<AppInfo, 'name' | 'description'>) => Promise<void>
|
||||
switchSourceApp: (appId: string, nextAppId: string) => void
|
||||
deleteInstance: (appId: string) => Promise<void>
|
||||
|
||||
startDeploy: (params: StartDeployParams) => Promise<void>
|
||||
retryDeploy: (appId: string, environmentId: string, targetReleaseId: string) => Promise<void>
|
||||
rollbackDeployment: (appId: string, environmentId: string, targetReleaseId: string) => Promise<void>
|
||||
undeployDeployment: (appId: string, environmentId: string, deploymentId?: string, isDeploying?: boolean) => Promise<void>
|
||||
|
||||
generateApiKey: (appId: string, environmentId: string) => Promise<void>
|
||||
revokeApiKey: (appId: string, environmentId: string, apiKeyId: string) => Promise<void>
|
||||
clearCreatedApiToken: () => void
|
||||
toggleAccessChannel: (appId: string, channel: string, enabled: boolean, expectedVersion: number) => Promise<void>
|
||||
setEnvironmentAccessPolicy: (
|
||||
appId: string,
|
||||
environmentId: string,
|
||||
channel: string,
|
||||
enabled: boolean,
|
||||
accessMode: string,
|
||||
subjects: AccessSubject[],
|
||||
expectedVersion: number,
|
||||
) => Promise<void>
|
||||
}
|
||||
|
||||
export type DeploymentsStore = DeploymentsState & DeploymentsAction
|
||||
|
||||
type Setter = Parameters<StateCreator<DeploymentsStore>>[0]
|
||||
type Getter = Parameters<StateCreator<DeploymentsStore>>[1]
|
||||
|
||||
class DeploymentsActionImpl implements DeploymentsAction {
|
||||
readonly #get: Getter
|
||||
readonly #set: Setter
|
||||
|
||||
constructor(set: Setter, get: Getter, _api?: unknown) {
|
||||
void _api
|
||||
this.#set = set
|
||||
this.#get = get
|
||||
}
|
||||
|
||||
openDeployDrawer = (params: OpenDeployDrawerParams) => {
|
||||
this.#set({
|
||||
deployDrawer: {
|
||||
open: true,
|
||||
appId: params.appId,
|
||||
environmentId: params.environmentId,
|
||||
releaseId: params.releaseId,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
closeDeployDrawer = () => {
|
||||
this.#set({ deployDrawer: { open: false } })
|
||||
}
|
||||
|
||||
openRollbackModal = ({ appId, environmentId, deploymentId, targetReleaseId }: OpenRollbackParams) => {
|
||||
this.#set({
|
||||
rollbackModal: { open: true, appId, environmentId, deploymentId, targetReleaseId },
|
||||
})
|
||||
}
|
||||
|
||||
closeRollbackModal = () => {
|
||||
this.#set({ rollbackModal: { open: false } })
|
||||
}
|
||||
|
||||
openCreateInstanceModal = () => {
|
||||
this.#set({ createInstanceModal: { open: true } })
|
||||
}
|
||||
|
||||
closeCreateInstanceModal = () => {
|
||||
this.#set({ createInstanceModal: { open: false } })
|
||||
}
|
||||
|
||||
upsertInstances = (apps: AppInfo[]) => {
|
||||
this.#set(state => ({
|
||||
instancesById: apps.reduce((next, app) => {
|
||||
next[app.id] = {
|
||||
...next[app.id],
|
||||
...app,
|
||||
}
|
||||
return next
|
||||
}, { ...state.instancesById }),
|
||||
}))
|
||||
}
|
||||
|
||||
applyAppData = (data: DeploymentAppData) => {
|
||||
this.#set(state => ({
|
||||
appData: {
|
||||
...state.appData,
|
||||
[data.appId]: data,
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
bumpDeploymentListRefresh = () => {
|
||||
this.#set(state => ({
|
||||
listRefreshToken: state.listRefreshToken + 1,
|
||||
}))
|
||||
}
|
||||
|
||||
fetchSourceApps = async (query: ListAppDeploymentsQuery) => {
|
||||
const response = await listAppDeployments(query)
|
||||
const apps = response.data
|
||||
?.map(toAppInfoFromSummary)
|
||||
.filter((app): app is AppInfo => Boolean(app)) ?? []
|
||||
this.upsertInstances(apps)
|
||||
return response
|
||||
}
|
||||
|
||||
fetchAppData = async (appId: string) => {
|
||||
const data = await fetchDeploymentAppData(appId)
|
||||
this.applyAppData(data)
|
||||
const app = toAppInfoFromOverview(data.overview.instance)
|
||||
if (app)
|
||||
this.upsertInstances([app])
|
||||
return data
|
||||
}
|
||||
|
||||
refreshAppData = async (appId: string) => {
|
||||
const data = await refreshDeploymentAppData(appId)
|
||||
this.applyAppData(data)
|
||||
const app = toAppInfoFromOverview(data.overview.instance)
|
||||
if (app)
|
||||
this.upsertInstances([app])
|
||||
}
|
||||
|
||||
createInstance = async ({ sourceAppId, name, description }: CreateInstanceParams) => {
|
||||
const response = await createAppInstance({ sourceAppId, name, description })
|
||||
if (!response.appInstanceId)
|
||||
throw new Error('Create app instance did not return an appInstanceId.')
|
||||
|
||||
this.#set({ createInstanceModal: { open: false } })
|
||||
await Promise.allSettled([
|
||||
refreshDeploymentAppDataWhenReady(response.appInstanceId)
|
||||
.then((data) => {
|
||||
this.applyAppData(data)
|
||||
const app = toAppInfoFromOverview(data.overview.instance)
|
||||
if (app)
|
||||
this.upsertInstances([app])
|
||||
}),
|
||||
waitForAppInstanceInDeploymentList(response.appInstanceId).then((list) => {
|
||||
const apps = list?.data
|
||||
?.map(toAppInfoFromSummary)
|
||||
.filter((app): app is AppInfo => Boolean(app)) ?? []
|
||||
this.upsertInstances(apps)
|
||||
}),
|
||||
])
|
||||
this.bumpDeploymentListRefresh()
|
||||
return {
|
||||
appInstanceId: response.appInstanceId,
|
||||
initialRelease: response.initialRelease,
|
||||
}
|
||||
}
|
||||
|
||||
updateInstance = async (appId: string, patch: Pick<AppInfo, 'name' | 'description'>) => {
|
||||
await updateAppInstance(appId, {
|
||||
name: patch.name,
|
||||
description: patch.description,
|
||||
})
|
||||
await this.refreshAppData(appId)
|
||||
this.bumpDeploymentListRefresh()
|
||||
this.#set(state => ({
|
||||
instancesById: {
|
||||
...state.instancesById,
|
||||
[appId]: {
|
||||
...state.instancesById[appId],
|
||||
id: appId,
|
||||
name: patch.name,
|
||||
mode: state.instancesById[appId]?.mode ?? 'workflow',
|
||||
description: patch.description,
|
||||
},
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
switchSourceApp = () => undefined
|
||||
|
||||
deleteInstance = async (appId: string) => {
|
||||
await deleteAppInstance(appId)
|
||||
this.#set((state) => {
|
||||
const { [appId]: _removed, ...appData } = state.appData
|
||||
const { [appId]: _removedInstance, ...instancesById } = state.instancesById
|
||||
return {
|
||||
instancesById,
|
||||
appData,
|
||||
}
|
||||
})
|
||||
this.bumpDeploymentListRefresh()
|
||||
}
|
||||
|
||||
startDeploy = async ({ appId, environmentId, releaseId, releaseNote }: StartDeployParams) => {
|
||||
this.#set({ deployDrawer: { open: false } })
|
||||
await createDeployment({ appId, environmentId, releaseId, releaseNote })
|
||||
await this.refreshAppData(appId)
|
||||
this.bumpDeploymentListRefresh()
|
||||
}
|
||||
|
||||
retryDeploy = async (appId: string, environmentId: string, targetReleaseId: string) => {
|
||||
await createDeployment({ appId, environmentId, releaseId: targetReleaseId })
|
||||
await this.refreshAppData(appId)
|
||||
this.bumpDeploymentListRefresh()
|
||||
}
|
||||
|
||||
rollbackDeployment = async (appId: string, environmentId: string, targetReleaseId: string) => {
|
||||
this.#set({ rollbackModal: { open: false } })
|
||||
await createDeployment({ appId, environmentId, releaseId: targetReleaseId })
|
||||
await this.refreshAppData(appId)
|
||||
this.bumpDeploymentListRefresh()
|
||||
}
|
||||
|
||||
undeployDeployment = async (appId: string, _environmentId: string, runtimeInstanceId?: string, isDeploying?: boolean) => {
|
||||
if (!runtimeInstanceId)
|
||||
return
|
||||
if (isDeploying)
|
||||
await cancelDeployment(appId, runtimeInstanceId)
|
||||
else
|
||||
await undeployEnvironment(appId, runtimeInstanceId)
|
||||
await this.refreshAppData(appId)
|
||||
this.bumpDeploymentListRefresh()
|
||||
}
|
||||
|
||||
generateApiKey = async (appId: string, environmentId: string) => {
|
||||
const appData = this.#get().appData[appId]
|
||||
const existingCount = appData?.accessConfig.developerApi?.apiKeys?.filter(key =>
|
||||
(key.environmentId ?? key.environment?.id) === environmentId,
|
||||
).length ?? 0
|
||||
const environmentName = appData
|
||||
?.environmentDeployments
|
||||
.data
|
||||
?.find(row => row.environment?.id === environmentId)
|
||||
?.environment
|
||||
?.name ?? 'env'
|
||||
const label = `${environmentName}-key-${String(existingCount + 1).padStart(3, '0')}`
|
||||
const response = await createApiKey(appId, environmentId, label)
|
||||
await this.refreshAppData(appId)
|
||||
if (response.apiToken?.token) {
|
||||
this.#set({
|
||||
createdApiToken: {
|
||||
id: response.apiToken.id,
|
||||
appId,
|
||||
environmentId: response.apiToken.environmentId ?? response.apiToken.environment?.id,
|
||||
maskedPrefix: response.apiToken.maskedPrefix ?? response.apiToken.maskedKey,
|
||||
name: response.apiToken.name || label,
|
||||
token: response.apiToken.token,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
revokeApiKey = async (appId: string, _environmentId: string, apiKeyId: string) => {
|
||||
await deleteApiKey(appId, apiKeyId)
|
||||
await this.refreshAppData(appId)
|
||||
}
|
||||
|
||||
clearCreatedApiToken = () => {
|
||||
this.#set({ createdApiToken: undefined })
|
||||
}
|
||||
|
||||
toggleAccessChannel = async (appId: string, channel: string, enabled: boolean) => {
|
||||
if (channel === 'api')
|
||||
await patchDeveloperAPI(appId, enabled)
|
||||
else
|
||||
await patchAccessChannel(appId, enabled)
|
||||
await this.refreshAppData(appId)
|
||||
}
|
||||
|
||||
setEnvironmentAccessPolicy = async (
|
||||
appId: string,
|
||||
environmentId: string,
|
||||
_channel: string,
|
||||
_enabled: boolean,
|
||||
accessMode: string,
|
||||
subjects: AccessSubject[],
|
||||
) => {
|
||||
await updateEnvironmentAccessPolicy(appId, environmentId, accessMode, subjects)
|
||||
await this.refreshAppData(appId)
|
||||
}
|
||||
}
|
||||
|
||||
export const createDeploymentsActions = (
|
||||
...parameters: Parameters<StateCreator<DeploymentsStore>>
|
||||
) => new DeploymentsActionImpl(...parameters)
|
||||
41
web/features/deployments/store/initial-state.ts
Normal file
41
web/features/deployments/store/initial-state.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import type { DeploymentAppData } from '../data'
|
||||
import type { AppInfo } from '../types'
|
||||
import type { APIToken } from '@/contract/console/deployments'
|
||||
|
||||
export type CreatedApiToken = Pick<APIToken, 'id' | 'environmentId' | 'maskedPrefix' | 'name'> & {
|
||||
appId: string
|
||||
token: string
|
||||
}
|
||||
|
||||
export type DeploymentsState = {
|
||||
instancesById: Record<string, AppInfo>
|
||||
appData: Record<string, DeploymentAppData>
|
||||
listRefreshToken: number
|
||||
createdApiToken?: CreatedApiToken
|
||||
|
||||
deployDrawer: {
|
||||
open: boolean
|
||||
appId?: string
|
||||
environmentId?: string
|
||||
releaseId?: string
|
||||
}
|
||||
rollbackModal: {
|
||||
open: boolean
|
||||
appId?: string
|
||||
environmentId?: string
|
||||
deploymentId?: string
|
||||
targetReleaseId?: string
|
||||
}
|
||||
createInstanceModal: { open: boolean }
|
||||
}
|
||||
|
||||
export const initialDeploymentsState: DeploymentsState = {
|
||||
instancesById: {},
|
||||
appData: {},
|
||||
listRefreshToken: 0,
|
||||
createdApiToken: undefined,
|
||||
|
||||
deployDrawer: { open: false },
|
||||
rollbackModal: { open: false },
|
||||
createInstanceModal: { open: false },
|
||||
}
|
||||
11
web/features/deployments/store/selectors.ts
Normal file
11
web/features/deployments/store/selectors.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import type { DeploymentsStore } from './actions'
|
||||
|
||||
export const deploymentsSelectors = {
|
||||
appData: (appId?: string) => (state: DeploymentsStore) =>
|
||||
appId ? state.appData[appId] : undefined,
|
||||
createdApiToken: (state: DeploymentsStore) => state.createdApiToken,
|
||||
instance: (appId?: string) => (state: DeploymentsStore) =>
|
||||
appId ? state.instancesById[appId] : undefined,
|
||||
instancesById: (state: DeploymentsStore) => state.instancesById,
|
||||
listRefreshToken: (state: DeploymentsStore) => state.listRefreshToken,
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user