new restore

This commit is contained in:
hjlarry 2026-01-23 14:22:58 +08:00
parent 51c8c50b82
commit e105dc6289
31 changed files with 361 additions and 18 deletions

View File

@ -12,6 +12,9 @@ import type {
NodePanelPresenceMap,
NodePanelPresenceUser,
OnlineUser,
RestoreCompleteData,
RestoreIntentData,
RestoreRequestData,
} from '../types/collaboration'
import { cloneDeep } from 'es-toolkit/object'
import { isEqual } from 'es-toolkit/predicate'
@ -658,6 +661,51 @@ export class CollaborationManager {
return this.eventEmitter.on('undoRedoStateChange', callback)
}
emitRestoreRequest(data: RestoreRequestData): void {
if (!this.currentAppId || !webSocketClient.isConnected(this.currentAppId))
return
this.sendCollaborationEvent({
type: 'workflow_restore_request',
data: data as unknown as Record<string, unknown>,
timestamp: Date.now(),
})
}
emitRestoreIntent(data: RestoreIntentData): void {
if (!this.currentAppId || !webSocketClient.isConnected(this.currentAppId))
return
this.sendCollaborationEvent({
type: 'workflow_restore_intent',
data: data as unknown as Record<string, unknown>,
timestamp: Date.now(),
})
}
emitRestoreComplete(data: RestoreCompleteData): void {
if (!this.currentAppId || !webSocketClient.isConnected(this.currentAppId))
return
this.sendCollaborationEvent({
type: 'workflow_restore_complete',
data: data as unknown as Record<string, unknown>,
timestamp: Date.now(),
})
}
onRestoreRequest(callback: (data: RestoreRequestData) => void): () => void {
return this.eventEmitter.on('restoreRequest', callback)
}
onRestoreIntent(callback: (data: RestoreIntentData) => void): () => void {
return this.eventEmitter.on('restoreIntent', callback)
}
onRestoreComplete(callback: (data: RestoreCompleteData) => void): () => void {
return this.eventEmitter.on('restoreComplete', callback)
}
getLeaderId(): string | null {
return this.leaderId
}
@ -898,6 +946,14 @@ export class CollaborationManager {
})
}
refreshGraphSynchronously(): void {
const mergedNodes = this.mergeLocalNodeState(this.getNodes())
this.eventEmitter.emit('graphImport', {
nodes: mergedNodes,
edges: this.getEdges(),
})
}
private mergeLocalNodeState(nodes: Node[]): Node[] {
const reactFlowStore = this.reactFlowStore
const state = reactFlowStore?.getState()
@ -979,6 +1035,16 @@ export class CollaborationManager {
if (this.isLeader)
this.broadcastCurrentGraph()
}
else if (update.type === 'workflow_restore_request') {
if (this.isLeader)
this.eventEmitter.emit('restoreRequest', update.data as RestoreRequestData)
}
else if (update.type === 'workflow_restore_intent') {
this.eventEmitter.emit('restoreIntent', update.data as RestoreIntentData)
}
else if (update.type === 'workflow_restore_complete') {
this.eventEmitter.emit('restoreComplete', update.data as RestoreCompleteData)
}
})
socket.on('online_users', (data: { users: OnlineUser[], leader?: string }) => {

View File

@ -1,4 +1,6 @@
import type { Edge, Node } from '../../types'
import type { Viewport } from 'reactflow'
import type { ConversationVariable, Edge, EnvironmentVariable, Node } from '../../types'
import type { Features } from '@/app/components/base/features/types'
export type OnlineUser = {
user_id: string
@ -61,6 +63,9 @@ export type CollaborationEventType
| 'node_panel_presence'
| 'app_publish_update'
| 'graph_resync_request'
| 'workflow_restore_request'
| 'workflow_restore_intent'
| 'workflow_restore_complete'
export type CollaborationUpdate = {
type: CollaborationEventType
@ -68,3 +73,31 @@ export type CollaborationUpdate = {
data: Record<string, unknown>
timestamp: number
}
export type RestoreRequestData = {
versionId: string
versionName?: string
initiatorUserId: string
initiatorName: string
graphData: {
nodes: Node[]
edges: Edge[]
viewport?: Viewport
}
features?: Features
environmentVariables?: EnvironmentVariable[]
conversationVariables?: ConversationVariable[]
}
export type RestoreIntentData = {
versionId: string
versionName?: string
initiatorUserId: string
initiatorName: string
}
export type RestoreCompleteData = {
versionId: string
success: boolean
error?: string
}

View File

@ -3,15 +3,15 @@ import {
useCallback,
} from 'react'
import { useTranslation } from 'react-i18next'
import { useStore as useAppStore } from '@/app/components/app/store'
import Button from '@/app/components/base/button'
import { useFeaturesStore } from '@/app/components/base/features/hooks'
import { useSelector as useAppContextSelector } from '@/context/app-context'
import useTheme from '@/hooks/use-theme'
import { useInvalidAllLastRun } from '@/service/use-workflow'
import { cn } from '@/utils/classnames'
import Toast from '../../base/toast'
import { collaborationManager } from '../collaboration/core/collaboration-manager'
import {
useNodesSyncDraft,
useLeaderRestore,
useWorkflowRun,
} from '../hooks'
import { useHooksStore } from '../hooks-store'
@ -33,7 +33,8 @@ const HeaderInRestoring = ({
const { t } = useTranslation()
const { theme } = useTheme()
const workflowStore = useWorkflowStore()
const appDetail = useAppStore.getState().appDetail
const userProfile = useAppContextSelector(s => s.userProfile)
const featuresStore = useFeaturesStore()
const configsMap = useHooksStore(s => s.configsMap)
const invalidAllLastRun = useInvalidAllLastRun(configsMap?.flowType, configsMap?.flowId)
const {
@ -45,7 +46,7 @@ const HeaderInRestoring = ({
const {
handleLoadBackupDraft,
} = useWorkflowRun()
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
const { requestRestore } = useLeaderRestore()
const handleCancelRestore = useCallback(() => {
handleLoadBackupDraft()
@ -54,18 +55,37 @@ const HeaderInRestoring = ({
}, [workflowStore, handleLoadBackupDraft, setShowWorkflowVersionHistoryPanel])
const handleRestore = useCallback(() => {
if (!currentVersion)
return
setShowWorkflowVersionHistoryPanel(false)
workflowStore.setState({ isRestoring: false })
workflowStore.setState({ backupDraft: undefined })
handleSyncWorkflowDraft(true, false, {
const { graph } = currentVersion
const features = featuresStore?.getState().features
const environmentVariables = currentVersion.environment_variables || []
const conversationVariables = currentVersion.conversation_variables || []
requestRestore({
versionId: currentVersion.id,
versionName: currentVersion.marked_name,
initiatorUserId: userProfile.id,
initiatorName: userProfile.name,
graphData: {
nodes: graph.nodes,
edges: graph.edges,
viewport: graph.viewport,
},
features,
environmentVariables,
conversationVariables,
}, {
onSuccess: () => {
Toast.notify({
type: 'success',
message: t('versionHistory.action.restoreSuccess', { ns: 'workflow' }),
})
// Notify other collaboration clients about the workflow restore
if (appDetail)
collaborationManager.emitWorkflowUpdate(appDetail.id)
},
onError: () => {
Toast.notify({
@ -76,10 +96,10 @@ const HeaderInRestoring = ({
onSettled: () => {
onRestoreSettled?.()
},
}, true) // Enable forceUpload for restore operation
})
deleteAllInspectVars()
invalidAllLastRun()
}, [setShowWorkflowVersionHistoryPanel, workflowStore, handleSyncWorkflowDraft, deleteAllInspectVars, invalidAllLastRun, t, onRestoreSettled, appDetail])
}, [currentVersion, featuresStore, setShowWorkflowVersionHistoryPanel, workflowStore, requestRestore, userProfile, deleteAllInspectVars, invalidAllLastRun, t, onRestoreSettled])
return (
<>

View File

@ -5,6 +5,7 @@ export * from './use-checklist'
export * from './use-DSL'
export * from './use-edges-interactions'
export * from './use-inspect-vars-crud'
export * from './use-leader-restore'
export * from './use-node-data-update'
export * from './use-nodes-interactions'
export * from './use-nodes-layout'

View File

@ -0,0 +1,168 @@
import type { RestoreCompleteData, RestoreIntentData, RestoreRequestData } from '../collaboration/types/collaboration'
import type { SyncCallback } from './use-nodes-sync-draft'
import { useCallback, useEffect, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import { useReactFlow } from 'reactflow'
import { useStore as useAppStore } from '@/app/components/app/store'
import { useFeaturesStore } from '@/app/components/base/features/hooks'
import Toast from '@/app/components/base/toast'
import { useGlobalPublicStore } from '@/context/global-public-context'
import { collaborationManager } from '../collaboration/core/collaboration-manager'
import { useWorkflowStore } from '../store'
import { useNodesSyncDraft } from './use-nodes-sync-draft'
type RestoreCallbacks = SyncCallback
export const usePerformRestore = () => {
const { doSyncWorkflowDraft } = useNodesSyncDraft()
const appDetail = useAppStore.getState().appDetail
const featuresStore = useFeaturesStore()
const workflowStore = useWorkflowStore()
const reactflow = useReactFlow()
return useCallback((data: RestoreRequestData, callbacks?: RestoreCallbacks) => {
collaborationManager.emitRestoreIntent({
versionId: data.versionId,
versionName: data.versionName,
initiatorUserId: data.initiatorUserId,
initiatorName: data.initiatorName,
})
if (data.features && featuresStore) {
const { setFeatures } = featuresStore.getState()
setFeatures(data.features)
}
if (data.environmentVariables) {
workflowStore.getState().setEnvironmentVariables(data.environmentVariables)
}
if (data.conversationVariables) {
workflowStore.getState().setConversationVariables(data.conversationVariables)
}
const { nodes, edges, viewport } = data.graphData
const currentNodes = collaborationManager.getNodes()
const currentEdges = collaborationManager.getEdges()
collaborationManager.setNodes(currentNodes, nodes)
collaborationManager.setEdges(currentEdges, edges)
collaborationManager.refreshGraphSynchronously()
if (viewport)
reactflow.setViewport(viewport)
doSyncWorkflowDraft(false, {
onSuccess: () => {
collaborationManager.emitRestoreComplete({
versionId: data.versionId,
success: true,
})
if (appDetail)
collaborationManager.emitWorkflowUpdate(appDetail.id)
callbacks?.onSuccess?.()
},
onError: () => {
collaborationManager.emitRestoreComplete({
versionId: data.versionId,
success: false,
error: 'Failed to sync restore to server',
})
callbacks?.onError?.()
},
onSettled: () => {
callbacks?.onSettled?.()
},
})
}, [appDetail, doSyncWorkflowDraft, featuresStore, reactflow, workflowStore])
}
export const useLeaderRestoreListener = () => {
const { t } = useTranslation()
const performRestore = usePerformRestore()
useEffect(() => {
const unsubscribe = collaborationManager.onRestoreRequest((data: RestoreRequestData) => {
Toast.notify({
type: 'info',
message: t('versionHistory.action.restoreInProgress', {
ns: 'workflow',
userName: data.initiatorName,
versionName: data.versionName || data.versionId,
}),
duration: 3000,
})
performRestore(data)
})
return unsubscribe
}, [performRestore, t])
useEffect(() => {
const unsubscribe = collaborationManager.onRestoreIntent((data: RestoreIntentData) => {
Toast.notify({
type: 'info',
message: t('versionHistory.action.restoreInProgress', {
ns: 'workflow',
userName: data.initiatorName,
versionName: data.versionName || data.versionId,
}),
duration: 3000,
})
})
return unsubscribe
}, [t])
}
export const useLeaderRestore = () => {
const performRestore = usePerformRestore()
const pendingCallbacksRef = useRef<{
versionId: string
callbacks: RestoreCallbacks | null
} | null>(null)
const isCollaborationEnabled = useGlobalPublicStore(s => s.systemFeatures.enable_collaboration_mode)
const requestRestore = useCallback((data: RestoreRequestData, callbacks?: RestoreCallbacks) => {
if (!isCollaborationEnabled || !collaborationManager.isConnected() || collaborationManager.getIsLeader()) {
performRestore(data, callbacks)
return
}
pendingCallbacksRef.current = {
versionId: data.versionId,
callbacks: callbacks || null,
}
collaborationManager.emitRestoreRequest(data)
}, [isCollaborationEnabled, performRestore])
useEffect(() => {
const unsubscribe = collaborationManager.onRestoreComplete((data: RestoreCompleteData) => {
const pending = pendingCallbacksRef.current
if (!pending || pending.versionId !== data.versionId)
return
const callbacks = pending.callbacks
if (!callbacks) {
pendingCallbacksRef.current = null
return
}
if (data.success)
callbacks.onSuccess?.()
else
callbacks.onError?.()
callbacks.onSettled?.()
pendingCallbacksRef.current = null
})
return unsubscribe
}, [])
return {
requestRestore,
}
}

View File

@ -1,5 +1,6 @@
import { useCallback } from 'react'
import { useHooksStore } from '@/app/components/workflow/hooks-store'
import { collaborationManager } from '../collaboration/core/collaboration-manager'
import { useStore } from '../store'
import { useNodesReadOnly } from './use-workflow'
@ -28,6 +29,12 @@ export const useNodesSyncDraft = () => {
if (getNodesReadOnly())
return
if (collaborationManager.isConnected() && !collaborationManager.getIsLeader()) {
if (sync)
collaborationManager.emitSyncRequest()
return
}
if (sync)
doSyncWorkflowDraft(notRefreshWhenSyncError, callback, forceUpload)
else

View File

@ -68,6 +68,7 @@ import DatasetsDetailProvider from './datasets-detail-store/provider'
import HelpLine from './help-line'
import {
useEdgesInteractions,
useLeaderRestoreListener,
useNodesInteractions,
useNodesReadOnly,
useNodesSyncDraft,
@ -333,7 +334,7 @@ export const Workflow: FC<WorkflowProps> = memo(({
else if (document.visibilityState === 'visible')
setTimeout(() => handleRefreshWorkflowDraft(), 500)
}, [syncWorkflowDraftWhenPageClose, handleRefreshWorkflowDraft, workflowStore])
}, [syncWorkflowDraftWhenPageClose, handleRefreshWorkflowDraft])
// Also add beforeunload handler as additional safety net for tab close
const handleBeforeUnload = useCallback(() => {
@ -478,6 +479,8 @@ export const Workflow: FC<WorkflowProps> = memo(({
// Initialize workflow node search functionality
useWorkflowSearch()
useLeaderRestoreListener()
// Set up scroll to node event listener using the utility function
useEffect(() => {
return setupScrollToNodeListener(nodes, reactflow)

View File

@ -71,11 +71,13 @@ vi.mock('@/service/use-workflow', () => ({
vi.mock('../../hooks', () => ({
useDSL: () => ({ handleExportDSL: vi.fn() }),
useNodesSyncDraft: () => ({ handleSyncWorkflowDraft: vi.fn() }),
useWorkflowRun: () => ({
handleRestoreFromPublishedWorkflow: mockHandleRestoreFromPublishedWorkflow,
handleLoadBackupDraft: mockHandleLoadBackupDraft,
}),
useLeaderRestore: () => ({
requestRestore: vi.fn(),
}),
}))
vi.mock('../../hooks-store', () => ({

View File

@ -7,10 +7,11 @@ import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import VersionInfoModal from '@/app/components/app/app-publisher/version-info-modal'
import Divider from '@/app/components/base/divider'
import { useFeaturesStore } from '@/app/components/base/features/hooks'
import Toast from '@/app/components/base/toast'
import { useSelector as useAppContextSelector } from '@/context/app-context'
import { useDeleteWorkflow, useInvalidAllLastRun, useResetWorkflowVersionHistory, useUpdateWorkflow, useWorkflowVersionHistory } from '@/service/use-workflow'
import { useDSL, useNodesSyncDraft, useWorkflowRun } from '../../hooks'
import { useDSL, useLeaderRestore, useWorkflowRun } from '../../hooks'
import { useHooksStore } from '../../hooks-store'
import { useStore, useWorkflowStore } from '../../store'
import { VersionHistoryContextMenuOptions, WorkflowVersion, WorkflowVersionFilterOptions } from '../../types'
@ -43,8 +44,9 @@ export const VersionHistoryPanel = ({
const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false)
const [editModalOpen, setEditModalOpen] = useState(false)
const workflowStore = useWorkflowStore()
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
const { handleRestoreFromPublishedWorkflow, handleLoadBackupDraft } = useWorkflowRun()
const { requestRestore } = useLeaderRestore()
const featuresStore = useFeaturesStore()
const { handleExportDSL } = useDSL()
const setShowWorkflowVersionHistoryPanel = useStore(s => s.setShowWorkflowVersionHistoryPanel)
const currentVersion = useStore(s => s.currentVersion)
@ -150,7 +152,26 @@ export const VersionHistoryPanel = ({
handleRestoreFromPublishedWorkflow(item)
workflowStore.setState({ isRestoring: false })
workflowStore.setState({ backupDraft: undefined })
handleSyncWorkflowDraft(true, false, {
const { graph } = item
const features = featuresStore?.getState().features
const environmentVariables = item.environment_variables || []
const conversationVariables = item.conversation_variables || []
requestRestore({
versionId: item.id,
versionName: item.marked_name,
initiatorUserId: userProfile.id,
initiatorName: userProfile.name,
graphData: {
nodes: graph.nodes,
edges: graph.edges,
viewport: graph.viewport,
},
features,
environmentVariables,
conversationVariables,
}, {
onSuccess: () => {
Toast.notify({
type: 'success',
@ -169,7 +190,7 @@ export const VersionHistoryPanel = ({
resetWorkflowVersionHistory()
},
})
}, [setShowWorkflowVersionHistoryPanel, handleRestoreFromPublishedWorkflow, workflowStore, handleSyncWorkflowDraft, deleteAllInspectVars, invalidAllLastRun, t, resetWorkflowVersionHistory])
}, [setShowWorkflowVersionHistoryPanel, handleRestoreFromPublishedWorkflow, workflowStore, featuresStore, requestRestore, userProfile, deleteAllInspectVars, invalidAllLastRun, t, resetWorkflowVersionHistory])
const { mutateAsync: deleteWorkflow } = useDeleteWorkflow()

View File

@ -1050,6 +1050,7 @@
"versionHistory.action.deleteFailure": "فشل حذف الإصدار",
"versionHistory.action.deleteSuccess": "تم حذف الإصدار",
"versionHistory.action.restoreFailure": "فشل استعادة الإصدار",
"versionHistory.action.restoreInProgress": "{{userName}} is restoring to version {{versionName}}...",
"versionHistory.action.restoreSuccess": "تم استعادة الإصدار",
"versionHistory.action.updateFailure": "فشل تحديث الإصدار",
"versionHistory.action.updateSuccess": "تم تحديث الإصدار",

View File

@ -1050,6 +1050,7 @@
"versionHistory.action.deleteFailure": "Version löschen fehlgeschlagen",
"versionHistory.action.deleteSuccess": "Version gelöscht",
"versionHistory.action.restoreFailure": "Wiederherstellung der Version fehlgeschlagen",
"versionHistory.action.restoreInProgress": "{{userName}} is restoring to version {{versionName}}...",
"versionHistory.action.restoreSuccess": "Version wiederhergestellt",
"versionHistory.action.updateFailure": "Aktualisierung der Version fehlgeschlagen",
"versionHistory.action.updateSuccess": "Version aktualisiert",

View File

@ -1050,6 +1050,7 @@
"versionHistory.action.deleteFailure": "Failed to delete version",
"versionHistory.action.deleteSuccess": "Version deleted",
"versionHistory.action.restoreFailure": "Failed to restore version",
"versionHistory.action.restoreInProgress": "{{userName}} is restoring to version {{versionName}}...",
"versionHistory.action.restoreSuccess": "Version restored",
"versionHistory.action.updateFailure": "Failed to update version",
"versionHistory.action.updateSuccess": "Version updated",

View File

@ -1050,6 +1050,7 @@
"versionHistory.action.deleteFailure": "Error al eliminar la versión",
"versionHistory.action.deleteSuccess": "Versión eliminada",
"versionHistory.action.restoreFailure": "Error al restaurar la versión",
"versionHistory.action.restoreInProgress": "{{userName}} is restoring to version {{versionName}}...",
"versionHistory.action.restoreSuccess": "Versión restaurada",
"versionHistory.action.updateFailure": "Error al actualizar la versión",
"versionHistory.action.updateSuccess": "Versión actualizada",

View File

@ -1050,6 +1050,7 @@
"versionHistory.action.deleteFailure": "حذف نسخه موفق نبود",
"versionHistory.action.deleteSuccess": "نسخه حذف شد",
"versionHistory.action.restoreFailure": "بازگرداندن نسخه ناموفق بود",
"versionHistory.action.restoreInProgress": "{{userName}} is restoring to version {{versionName}}...",
"versionHistory.action.restoreSuccess": "نسخه بازگردانی شده",
"versionHistory.action.updateFailure": "به‌روزرسانی نسخه ناموفق بود",
"versionHistory.action.updateSuccess": "نسخه به‌روزرسانی شد",

View File

@ -1050,6 +1050,7 @@
"versionHistory.action.deleteFailure": "Échec de la suppression de la version",
"versionHistory.action.deleteSuccess": "Version supprimée",
"versionHistory.action.restoreFailure": "Échec de la restauration de la version",
"versionHistory.action.restoreInProgress": "{{userName}} is restoring to version {{versionName}}...",
"versionHistory.action.restoreSuccess": "Version restaurée",
"versionHistory.action.updateFailure": "Échec de la mise à jour de la version",
"versionHistory.action.updateSuccess": "Version mise à jour",

View File

@ -1050,6 +1050,7 @@
"versionHistory.action.deleteFailure": "संस्करण को हटाने में विफल",
"versionHistory.action.deleteSuccess": "संस्करण हटाया गया",
"versionHistory.action.restoreFailure": "संस्करण को पुनर्स्थापित करने में विफल",
"versionHistory.action.restoreInProgress": "{{userName}} is restoring to version {{versionName}}...",
"versionHistory.action.restoreSuccess": "संस्करण पुनर्स्थापित किया गया",
"versionHistory.action.updateFailure": "संस्करण अपडेट करने में विफल",
"versionHistory.action.updateSuccess": "संस्करण अपडेट किया गया",

View File

@ -1050,6 +1050,7 @@
"versionHistory.action.deleteFailure": "Gagal menghapus versi",
"versionHistory.action.deleteSuccess": "Versi dihapus",
"versionHistory.action.restoreFailure": "Gagal memulihkan versi",
"versionHistory.action.restoreInProgress": "{{userName}} is restoring to version {{versionName}}...",
"versionHistory.action.restoreSuccess": "Versi dipulihkan",
"versionHistory.action.updateFailure": "Gagal memperbarui versi",
"versionHistory.action.updateSuccess": "Versi diperbarui",

View File

@ -1050,6 +1050,7 @@
"versionHistory.action.deleteFailure": "Impossibile eliminare la versione",
"versionHistory.action.deleteSuccess": "Versione eliminata",
"versionHistory.action.restoreFailure": "Impossibile ripristinare la versione",
"versionHistory.action.restoreInProgress": "{{userName}} is restoring to version {{versionName}}...",
"versionHistory.action.restoreSuccess": "Versione ripristinata",
"versionHistory.action.updateFailure": "Impossibile aggiornare la versione",
"versionHistory.action.updateSuccess": "Versione aggiornata",

View File

@ -1050,6 +1050,7 @@
"versionHistory.action.deleteFailure": "削除に失敗しました",
"versionHistory.action.deleteSuccess": "削除が完了しました",
"versionHistory.action.restoreFailure": "復元に失敗しました",
"versionHistory.action.restoreInProgress": "{{userName}} is restoring to version {{versionName}}...",
"versionHistory.action.restoreSuccess": "復元が完了しました",
"versionHistory.action.updateFailure": "更新に失敗しました",
"versionHistory.action.updateSuccess": "更新が完了しました",

View File

@ -1050,6 +1050,7 @@
"versionHistory.action.deleteFailure": "버전을 삭제하지 못했습니다.",
"versionHistory.action.deleteSuccess": "버전 삭제됨",
"versionHistory.action.restoreFailure": "버전을 복원하지 못했습니다.",
"versionHistory.action.restoreInProgress": "{{userName}} is restoring to version {{versionName}}...",
"versionHistory.action.restoreSuccess": "복원된 버전",
"versionHistory.action.updateFailure": "버전 업데이트에 실패했습니다.",
"versionHistory.action.updateSuccess": "버전이 업데이트되었습니다.",

View File

@ -1050,6 +1050,7 @@
"versionHistory.action.deleteFailure": "Nie udało się usunąć wersji",
"versionHistory.action.deleteSuccess": "Wersja usunięta",
"versionHistory.action.restoreFailure": "Nie udało się przywrócić wersji",
"versionHistory.action.restoreInProgress": "{{userName}} is restoring to version {{versionName}}...",
"versionHistory.action.restoreSuccess": "Wersja przywrócona",
"versionHistory.action.updateFailure": "Nie udało się zaktualizować wersji",
"versionHistory.action.updateSuccess": "Wersja zaktualizowana",

View File

@ -1050,6 +1050,7 @@
"versionHistory.action.deleteFailure": "Falha ao deletar versão",
"versionHistory.action.deleteSuccess": "Versão excluída",
"versionHistory.action.restoreFailure": "Falha ao restaurar versão",
"versionHistory.action.restoreInProgress": "{{userName}} is restoring to version {{versionName}}...",
"versionHistory.action.restoreSuccess": "Versão restaurada",
"versionHistory.action.updateFailure": "Falha ao atualizar a versão",
"versionHistory.action.updateSuccess": "Versão atualizada",

View File

@ -1050,6 +1050,7 @@
"versionHistory.action.deleteFailure": "Ștergerea versiunii a eșuat",
"versionHistory.action.deleteSuccess": "Versiune ștearsă",
"versionHistory.action.restoreFailure": "Restaurarea versiunii a eșuat",
"versionHistory.action.restoreInProgress": "{{userName}} is restoring to version {{versionName}}...",
"versionHistory.action.restoreSuccess": "Versiune restaurată",
"versionHistory.action.updateFailure": "Actualizarea versiunii a eșuat",
"versionHistory.action.updateSuccess": "Versiune actualizată",

View File

@ -1050,6 +1050,7 @@
"versionHistory.action.deleteFailure": "Не удалось удалить версию",
"versionHistory.action.deleteSuccess": "Версия удалена",
"versionHistory.action.restoreFailure": "Не удалось восстановить версию",
"versionHistory.action.restoreInProgress": "{{userName}} is restoring to version {{versionName}}...",
"versionHistory.action.restoreSuccess": "Версия восстановлена",
"versionHistory.action.updateFailure": "Не удалось обновить версию",
"versionHistory.action.updateSuccess": "Версия обновлена",

View File

@ -1050,6 +1050,7 @@
"versionHistory.action.deleteFailure": "Brisanje različice ni uspelo",
"versionHistory.action.deleteSuccess": "Različica izbrisana",
"versionHistory.action.restoreFailure": "Obnavljanje različice ni uspelo",
"versionHistory.action.restoreInProgress": "{{userName}} is restoring to version {{versionName}}...",
"versionHistory.action.restoreSuccess": "Obnovljena različica",
"versionHistory.action.updateFailure": "Posodobitev različice ni uspela",
"versionHistory.action.updateSuccess": "Različica posodobljena",

View File

@ -1050,6 +1050,7 @@
"versionHistory.action.deleteFailure": "ลบเวอร์ชันไม่สำเร็จ",
"versionHistory.action.deleteSuccess": "เวอร์ชันถูกลบ",
"versionHistory.action.restoreFailure": "ไม่สามารถกู้คืนเวอร์ชันได้",
"versionHistory.action.restoreInProgress": "{{userName}} is restoring to version {{versionName}}...",
"versionHistory.action.restoreSuccess": "เวอร์ชันที่กู้คืน",
"versionHistory.action.updateFailure": "ไม่สามารถอัปเดตเวอร์ชันได้",
"versionHistory.action.updateSuccess": "อัปเดตเวอร์ชัน",

View File

@ -1050,6 +1050,7 @@
"versionHistory.action.deleteFailure": "Versiyonu silme işlemi başarısız oldu",
"versionHistory.action.deleteSuccess": "Sürüm silindi",
"versionHistory.action.restoreFailure": "Sürümü geri yüklemekte başarısız olundu",
"versionHistory.action.restoreInProgress": "{{userName}} is restoring to version {{versionName}}...",
"versionHistory.action.restoreSuccess": "Sürüm geri yüklendi",
"versionHistory.action.updateFailure": "Sürüm güncellenemedi",
"versionHistory.action.updateSuccess": "Sürüm güncellendi",

View File

@ -1050,6 +1050,7 @@
"versionHistory.action.deleteFailure": "Не вдалося видалити версію",
"versionHistory.action.deleteSuccess": "Версія видалена",
"versionHistory.action.restoreFailure": "Не вдалося відновити версію",
"versionHistory.action.restoreInProgress": "{{userName}} is restoring to version {{versionName}}...",
"versionHistory.action.restoreSuccess": "Версія відновлена",
"versionHistory.action.updateFailure": "Не вдалося оновити версію",
"versionHistory.action.updateSuccess": "Версія оновлена",

View File

@ -1050,6 +1050,7 @@
"versionHistory.action.deleteFailure": "Xóa phiên bản thất bại",
"versionHistory.action.deleteSuccess": "Phiên bản đã bị xóa",
"versionHistory.action.restoreFailure": "Không thể khôi phục phiên bản",
"versionHistory.action.restoreInProgress": "{{userName}} is restoring to version {{versionName}}...",
"versionHistory.action.restoreSuccess": "Phiên bản đã được khôi phục",
"versionHistory.action.updateFailure": "Cập nhật phiên bản không thành công",
"versionHistory.action.updateSuccess": "Phiên bản đã được cập nhật",

View File

@ -1050,6 +1050,7 @@
"versionHistory.action.deleteFailure": "删除失败",
"versionHistory.action.deleteSuccess": "版本已删除",
"versionHistory.action.restoreFailure": "回滚失败",
"versionHistory.action.restoreInProgress": "{{userName}} 正在回滚到版本 {{versionName}}...",
"versionHistory.action.restoreSuccess": "回滚成功",
"versionHistory.action.updateFailure": "更新失败",
"versionHistory.action.updateSuccess": "版本信息已更新",

View File

@ -1050,6 +1050,7 @@
"versionHistory.action.deleteFailure": "無法刪除版本",
"versionHistory.action.deleteSuccess": "版本已刪除",
"versionHistory.action.restoreFailure": "無法恢復版本",
"versionHistory.action.restoreInProgress": "{{userName}} is restoring to version {{versionName}}...",
"versionHistory.action.restoreSuccess": "恢復版本",
"versionHistory.action.updateFailure": "更新版本失敗",
"versionHistory.action.updateSuccess": "版本已更新",