@blocklet/launcher-workflow 2.4.7 → 2.4.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/es/components/launch-serverless/allocate.js +21 -17
- package/es/components/launch-serverless/install.js +16 -27
- package/es/components/launch-serverless/shared/common-components.js +2 -2
- package/es/components/launch-serverless/shared/retry-error-message.js +7 -1
- package/es/components/launch-serverless/shared/use-workflow-progress.js +184 -0
- package/es/components/launch-serverless/start-app.js +54 -23
- package/es/locales/en.js +3 -0
- package/es/locales/zh.js +3 -0
- package/lib/components/launch-serverless/allocate.js +20 -16
- package/lib/components/launch-serverless/install.js +15 -26
- package/lib/components/launch-serverless/shared/common-components.js +2 -2
- package/lib/components/launch-serverless/shared/retry-error-message.js +7 -1
- package/lib/components/launch-serverless/shared/use-workflow-progress.js +191 -0
- package/lib/components/launch-serverless/start-app.js +53 -21
- package/lib/locales/en.js +3 -0
- package/lib/locales/zh.js +3 -0
- package/package.json +4 -4
|
@@ -6,7 +6,8 @@ import { LAUNCH_STATUS } from '@blocklet/launcher-util/lib/constant';
|
|
|
6
6
|
import { useLocaleContext } from '../../contexts/locale';
|
|
7
7
|
import useRequest from '../../contexts/request';
|
|
8
8
|
import BaseServerlessLayout from './shared/base-serverless-layout';
|
|
9
|
-
import {
|
|
9
|
+
import { useFormatTime } from './shared/common-components';
|
|
10
|
+
import useWorkflowProgress from './shared/use-workflow-progress';
|
|
10
11
|
import LoadingDisplayLayout from './shared/loading-display-layout';
|
|
11
12
|
import RetryErrorMessage from './shared/retry-error-message';
|
|
12
13
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
@@ -27,8 +28,6 @@ export default function LaunchServerless({
|
|
|
27
28
|
error: '',
|
|
28
29
|
launchSession: null
|
|
29
30
|
});
|
|
30
|
-
const formatTime = useFormatTime();
|
|
31
|
-
const time = useElapsedTime(0);
|
|
32
31
|
const actions = useMemo(() => {
|
|
33
32
|
// 基础步骤时间
|
|
34
33
|
const steps = [{
|
|
@@ -66,7 +65,21 @@ export default function LaunchServerless({
|
|
|
66
65
|
const estimatedTime = useMemo(() => {
|
|
67
66
|
return actions.reduce((acc, action) => acc + action.time, 0);
|
|
68
67
|
}, [actions]);
|
|
69
|
-
const
|
|
68
|
+
const formatTime = useFormatTime();
|
|
69
|
+
const {
|
|
70
|
+
time,
|
|
71
|
+
displayProgress
|
|
72
|
+
} = useWorkflowProgress({
|
|
73
|
+
sessionId,
|
|
74
|
+
pageName: 'allocate',
|
|
75
|
+
estimatedTime,
|
|
76
|
+
isCompleted: state.allocated
|
|
77
|
+
});
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (displayProgress >= 99 && state.allocated) {
|
|
80
|
+
navigate(`/install/${sessionId}${window.location.search || ''}`);
|
|
81
|
+
}
|
|
82
|
+
}, [displayProgress, state.allocated, sessionId, navigate]);
|
|
70
83
|
|
|
71
84
|
// 根据当前进度获取对应的 action
|
|
72
85
|
const getCurrentAction = progress => {
|
|
@@ -76,6 +89,7 @@ export default function LaunchServerless({
|
|
|
76
89
|
return action?.message || actions[actions.length - 1].message;
|
|
77
90
|
};
|
|
78
91
|
useEffect(() => {
|
|
92
|
+
const timersRef = timerRef;
|
|
79
93
|
const fetch = async () => {
|
|
80
94
|
try {
|
|
81
95
|
const {
|
|
@@ -94,7 +108,7 @@ export default function LaunchServerless({
|
|
|
94
108
|
await Promise.all([api.post('/serverless/allocate', {
|
|
95
109
|
launchId: sessionId
|
|
96
110
|
}), new Promise(resolve => {
|
|
97
|
-
|
|
111
|
+
timersRef.current.raceTimer = setTimeout(() => {
|
|
98
112
|
resolve();
|
|
99
113
|
}, estimatedTime * 1000);
|
|
100
114
|
})]);
|
|
@@ -112,7 +126,7 @@ export default function LaunchServerless({
|
|
|
112
126
|
}
|
|
113
127
|
};
|
|
114
128
|
if (window.blocklet.DEVELOPER_WORKFLOW_UI) {
|
|
115
|
-
|
|
129
|
+
timersRef.current.mockTimer = setTimeout(() => {
|
|
116
130
|
navigate(`/install/${sessionId}${window.location.search || ''}`);
|
|
117
131
|
}, 5000);
|
|
118
132
|
} else {
|
|
@@ -122,17 +136,7 @@ export default function LaunchServerless({
|
|
|
122
136
|
const timers = Object.values(timerRef.current); // eslint-disable-line react-hooks/exhaustive-deps
|
|
123
137
|
timers.forEach(timer => clearTimeout(timer));
|
|
124
138
|
};
|
|
125
|
-
|
|
126
|
-
}, []);
|
|
127
|
-
useEffect(() => {
|
|
128
|
-
if (state.allocated) {
|
|
129
|
-
const timer = setTimeout(() => {
|
|
130
|
-
navigate(`/install/${sessionId}${window.location.search || ''}`);
|
|
131
|
-
}, 1000);
|
|
132
|
-
return () => clearTimeout(timer);
|
|
133
|
-
}
|
|
134
|
-
return () => {};
|
|
135
|
-
}, [state.allocated, sessionId, navigate]);
|
|
139
|
+
}, [api, estimatedTime, navigate, sessionId, setState]);
|
|
136
140
|
const handleRetry = async () => {
|
|
137
141
|
try {
|
|
138
142
|
setState({
|
|
@@ -7,7 +7,8 @@ import { useLocaleContext } from '../../contexts/locale';
|
|
|
7
7
|
import useRequest from '../../contexts/request';
|
|
8
8
|
import useSerialPolling from '../../hooks/use-serial-polling';
|
|
9
9
|
import BaseServerlessLayout from './shared/base-serverless-layout';
|
|
10
|
-
import { calculateEstimatedTime,
|
|
10
|
+
import { calculateEstimatedTime, useFormatTime } from './shared/common-components';
|
|
11
|
+
import useWorkflowProgress from './shared/use-workflow-progress';
|
|
11
12
|
import LoadingDisplayLayout from './shared/loading-display-layout';
|
|
12
13
|
import RetryErrorMessage from './shared/retry-error-message';
|
|
13
14
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
@@ -80,6 +81,20 @@ export default function LaunchServerless({
|
|
|
80
81
|
time
|
|
81
82
|
}) => acc + time, 0) + Math.ceil(additionalTime * 0.6);
|
|
82
83
|
}, [actions]);
|
|
84
|
+
const {
|
|
85
|
+
time,
|
|
86
|
+
displayProgress
|
|
87
|
+
} = useWorkflowProgress({
|
|
88
|
+
sessionId,
|
|
89
|
+
pageName: 'install',
|
|
90
|
+
estimatedTime,
|
|
91
|
+
isCompleted: state.status === STATUS.installed
|
|
92
|
+
});
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
if (displayProgress >= 99 && state.status === STATUS.installed) {
|
|
95
|
+
navigate(`/start-app/${sessionId}${window.location.search || ''}`);
|
|
96
|
+
}
|
|
97
|
+
}, [displayProgress, state.status, sessionId, navigate]);
|
|
83
98
|
|
|
84
99
|
// 根据当前进度获取对应的 action
|
|
85
100
|
const getCurrentAction = progress => {
|
|
@@ -88,18 +103,6 @@ export default function LaunchServerless({
|
|
|
88
103
|
}) => progress >= start && progress < end);
|
|
89
104
|
return action?.message || actions[actions.length - 1].message;
|
|
90
105
|
};
|
|
91
|
-
|
|
92
|
-
// 时间管理 - 参考 launch-dedicated.js
|
|
93
|
-
let installStartTime = sessionStorage.getItem(`launcher-install-${sessionId}-time`);
|
|
94
|
-
if (!installStartTime) {
|
|
95
|
-
installStartTime = Date.now();
|
|
96
|
-
sessionStorage.setItem(`launcher-install-${sessionId}-time`, installStartTime);
|
|
97
|
-
}
|
|
98
|
-
const time = useElapsedTime(Math.round((Date.now() - installStartTime) / 1000));
|
|
99
|
-
const startProgress = useMemo(() => {
|
|
100
|
-
return Math.min(time / estimatedTime * 100, 90);
|
|
101
|
-
}, [time, estimatedTime]);
|
|
102
|
-
const displayProgress = useDisplayProgress(startProgress, state.status === STATUS.installed ? Math.min(estimatedTime - time, 2) : estimatedTime);
|
|
103
106
|
const checkLaunchSession = async () => {
|
|
104
107
|
if (window.blocklet.DEVELOPER_WORKFLOW_UI) {
|
|
105
108
|
// 开发模式下的模拟逻辑
|
|
@@ -107,7 +110,6 @@ export default function LaunchServerless({
|
|
|
107
110
|
setState({
|
|
108
111
|
status: STATUS.installed
|
|
109
112
|
});
|
|
110
|
-
sessionStorage.removeItem(`launcher-install-${sessionId}-time`);
|
|
111
113
|
navigate(`/start-app/${sessionId}${window.location.search || ''}`);
|
|
112
114
|
}, 47000);
|
|
113
115
|
return;
|
|
@@ -123,7 +125,6 @@ export default function LaunchServerless({
|
|
|
123
125
|
} = launchSession.metadata;
|
|
124
126
|
const hasInstalled = ['starting', 'installed', 'running', 'stopped'].includes(status) || launchSession.running || launchSession.status >= LAUNCH_STATUS.consuming;
|
|
125
127
|
if (hasInstalled) {
|
|
126
|
-
sessionStorage.removeItem(`launcher-install-${sessionId}-time`);
|
|
127
128
|
setState({
|
|
128
129
|
status: STATUS.installed
|
|
129
130
|
});
|
|
@@ -152,7 +153,6 @@ export default function LaunchServerless({
|
|
|
152
153
|
retryCount: state.retryCount + 1
|
|
153
154
|
});
|
|
154
155
|
} else {
|
|
155
|
-
sessionStorage.removeItem(`launcher-install-${sessionId}-time`);
|
|
156
156
|
setState({
|
|
157
157
|
error: error.message,
|
|
158
158
|
status: STATUS.error,
|
|
@@ -168,18 +168,7 @@ export default function LaunchServerless({
|
|
|
168
168
|
interval: CHECK_INTERVAL,
|
|
169
169
|
onPoll: checkLaunchSession
|
|
170
170
|
});
|
|
171
|
-
useEffect(() => {
|
|
172
|
-
if (state.status === STATUS.installed && displayProgress >= 99) {
|
|
173
|
-
const timer = setTimeout(() => {
|
|
174
|
-
sessionStorage.removeItem(`launcher-install-${sessionId}-time`);
|
|
175
|
-
navigate(`/start-app/${sessionId}${window.location.search || ''}`);
|
|
176
|
-
}, 1500);
|
|
177
|
-
return () => clearTimeout(timer);
|
|
178
|
-
}
|
|
179
|
-
return () => {};
|
|
180
|
-
}, [state.status, sessionId, navigate, displayProgress]);
|
|
181
171
|
const handleRetry = () => {
|
|
182
|
-
sessionStorage.removeItem(`launcher-install-${sessionId}-time`);
|
|
183
172
|
navigate(`/launch/${sessionId}${window.location.search || ''}`);
|
|
184
173
|
};
|
|
185
174
|
return /*#__PURE__*/_jsxs(BaseServerlessLayout, {
|
|
@@ -163,9 +163,9 @@ export function useDisplayProgress(start, duration = 5) {
|
|
|
163
163
|
setProgress(prev => Math.max(prev, start));
|
|
164
164
|
}, [start]);
|
|
165
165
|
useEffect(() => {
|
|
166
|
-
const timerDuration = Math.round((duration || 1) * 1000 /
|
|
166
|
+
const timerDuration = Math.round((duration || 1) * 1000 / 100);
|
|
167
167
|
const interval = setInterval(() => {
|
|
168
|
-
setProgress(prev => Math.min(prev + 1,
|
|
168
|
+
setProgress(prev => Math.min(prev + 1, 100));
|
|
169
169
|
}, timerDuration);
|
|
170
170
|
return () => clearInterval(interval);
|
|
171
171
|
}, [duration]);
|
|
@@ -10,6 +10,7 @@ import React, { useState } from 'react';
|
|
|
10
10
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
11
11
|
export default function RetryErrorMessage({
|
|
12
12
|
title,
|
|
13
|
+
desc = '',
|
|
13
14
|
onRetry,
|
|
14
15
|
retryText
|
|
15
16
|
}) {
|
|
@@ -25,9 +26,13 @@ export default function RetryErrorMessage({
|
|
|
25
26
|
setIsLoading(false);
|
|
26
27
|
};
|
|
27
28
|
return /*#__PURE__*/_jsx(LaunchResultMessage, {
|
|
29
|
+
style: {
|
|
30
|
+
marginTop: '20vh'
|
|
31
|
+
},
|
|
28
32
|
variant: "error",
|
|
29
33
|
title: title,
|
|
30
|
-
|
|
34
|
+
subTitle: desc,
|
|
35
|
+
footer: onRetry && /*#__PURE__*/_jsx(Button, {
|
|
31
36
|
size: "small",
|
|
32
37
|
style: {
|
|
33
38
|
marginTop: '-16px'
|
|
@@ -40,6 +45,7 @@ export default function RetryErrorMessage({
|
|
|
40
45
|
}
|
|
41
46
|
RetryErrorMessage.propTypes = {
|
|
42
47
|
title: PropTypes.string.isRequired,
|
|
48
|
+
desc: PropTypes.string,
|
|
43
49
|
onRetry: PropTypes.func.isRequired,
|
|
44
50
|
retryText: PropTypes.string.isRequired
|
|
45
51
|
};
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import { useDisplayProgress, useElapsedTime } from './common-components';
|
|
3
|
+
const CACHE_KEY = 'launcher-workflow';
|
|
4
|
+
const CACHE_EXPIRE_DAYS = 3;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 工作流进度管理 Hook
|
|
8
|
+
* @param {Object} options
|
|
9
|
+
* @param {string} options.sessionId - 会话ID
|
|
10
|
+
* @param {string} options.pageName - 页面名称,用于缓存标识
|
|
11
|
+
* @param {number} options.estimatedTime - 预计完成时间(秒)
|
|
12
|
+
* @param {boolean} options.isCompleted - 是否已完成
|
|
13
|
+
* @param {number} [options.initialStartTime] - 初始开始时间
|
|
14
|
+
* @returns {Object} 进度相关数据
|
|
15
|
+
*/
|
|
16
|
+
export default function useWorkflowProgress({
|
|
17
|
+
sessionId,
|
|
18
|
+
pageName,
|
|
19
|
+
estimatedTime,
|
|
20
|
+
isCompleted,
|
|
21
|
+
initialStartTime = Date.now()
|
|
22
|
+
}) {
|
|
23
|
+
// 缓存操作函数
|
|
24
|
+
const cache = useMemo(() => {
|
|
25
|
+
const getWorkflowCache = () => {
|
|
26
|
+
try {
|
|
27
|
+
return JSON.parse(localStorage.getItem(CACHE_KEY) || '{}');
|
|
28
|
+
} catch (error) {
|
|
29
|
+
return {};
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const setWorkflowCache = data => {
|
|
33
|
+
try {
|
|
34
|
+
localStorage.setItem(CACHE_KEY, JSON.stringify(data));
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error('Failed to save cache:', error);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
return {
|
|
40
|
+
get: () => {
|
|
41
|
+
try {
|
|
42
|
+
const allCache = getWorkflowCache();
|
|
43
|
+
const sessionCache = allCache[sessionId] || {};
|
|
44
|
+
const pageCache = sessionCache[pageName];
|
|
45
|
+
if (!pageCache) return null;
|
|
46
|
+
const {
|
|
47
|
+
startTime,
|
|
48
|
+
progress,
|
|
49
|
+
expireTime
|
|
50
|
+
} = pageCache;
|
|
51
|
+
|
|
52
|
+
// 检查是否过期
|
|
53
|
+
if (expireTime && expireTime < Date.now()) {
|
|
54
|
+
// 删除过期缓存
|
|
55
|
+
delete sessionCache[pageName];
|
|
56
|
+
allCache[sessionId] = sessionCache;
|
|
57
|
+
setWorkflowCache(allCache);
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
startTime,
|
|
62
|
+
progress
|
|
63
|
+
};
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error('Failed to get cache:', error);
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
save: (currentStartTime, progress) => {
|
|
70
|
+
try {
|
|
71
|
+
const allCache = getWorkflowCache();
|
|
72
|
+
const sessionCache = allCache[sessionId] || {};
|
|
73
|
+
const expireTime = Date.now() + CACHE_EXPIRE_DAYS * 24 * 60 * 60 * 1000;
|
|
74
|
+
sessionCache[pageName] = {
|
|
75
|
+
startTime: currentStartTime,
|
|
76
|
+
progress,
|
|
77
|
+
expireTime
|
|
78
|
+
};
|
|
79
|
+
allCache[sessionId] = sessionCache;
|
|
80
|
+
setWorkflowCache(allCache);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.error('Failed to save cache:', error);
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
clear: () => {
|
|
86
|
+
try {
|
|
87
|
+
const allCache = getWorkflowCache();
|
|
88
|
+
if (allCache[sessionId]) {
|
|
89
|
+
delete allCache[sessionId];
|
|
90
|
+
setWorkflowCache(allCache);
|
|
91
|
+
}
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.error('Failed to clear cache:', error);
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
// 清理所有过期缓存
|
|
97
|
+
cleanExpired: () => {
|
|
98
|
+
try {
|
|
99
|
+
const allCache = getWorkflowCache();
|
|
100
|
+
const now = Date.now();
|
|
101
|
+
let hasExpired = false;
|
|
102
|
+
|
|
103
|
+
// 遍历所有会话
|
|
104
|
+
Object.keys(allCache).forEach(sid => {
|
|
105
|
+
const sessionCache = allCache[sid];
|
|
106
|
+
// 遍历会话中的所有页面缓存
|
|
107
|
+
Object.keys(sessionCache).forEach(page => {
|
|
108
|
+
if (sessionCache[page].expireTime < now) {
|
|
109
|
+
delete sessionCache[page];
|
|
110
|
+
hasExpired = true;
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
// 如果会话没有页面缓存了,删除会话
|
|
114
|
+
if (Object.keys(sessionCache).length === 0) {
|
|
115
|
+
delete allCache[sid];
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
if (hasExpired) {
|
|
119
|
+
setWorkflowCache(allCache);
|
|
120
|
+
}
|
|
121
|
+
} catch (error) {
|
|
122
|
+
console.error('Failed to clean expired cache:', error);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
}, [sessionId, pageName]);
|
|
127
|
+
|
|
128
|
+
// 从缓存获取开始时间
|
|
129
|
+
const startTimeRef = useRef();
|
|
130
|
+
startTimeRef.current = useMemo(() => {
|
|
131
|
+
if (startTimeRef.current) return startTimeRef.current;
|
|
132
|
+
const cached = cache.get();
|
|
133
|
+
return cached?.startTime || initialStartTime;
|
|
134
|
+
}, [cache, initialStartTime]);
|
|
135
|
+
const startTime = startTimeRef.current;
|
|
136
|
+
// 计算已经过去的时间
|
|
137
|
+
const time = useElapsedTime(Math.round((Date.now() - startTime) / 1000));
|
|
138
|
+
|
|
139
|
+
// 计算开始进度
|
|
140
|
+
const progressRef = useRef();
|
|
141
|
+
progressRef.current = useMemo(() => {
|
|
142
|
+
if (progressRef.current) return progressRef.current;
|
|
143
|
+
if (isCompleted) return 100;
|
|
144
|
+
const cached = cache.get();
|
|
145
|
+
if (cached?.progress) {
|
|
146
|
+
return Math.max(cached.progress, Math.min(time / estimatedTime * 100, 100));
|
|
147
|
+
}
|
|
148
|
+
return Math.min(time / estimatedTime * 100, 100);
|
|
149
|
+
}, [cache, time, estimatedTime, isCompleted]);
|
|
150
|
+
const startProgress = progressRef.current;
|
|
151
|
+
|
|
152
|
+
// 自动保存进度
|
|
153
|
+
const [lastSaveTime, setLastSaveTime] = useState(Date.now());
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
if (isCompleted) {
|
|
156
|
+
cache.save(startTime, 100);
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
const saveTimer = setInterval(() => {
|
|
160
|
+
const now = Date.now();
|
|
161
|
+
// 每秒最多保存一次
|
|
162
|
+
if (now - lastSaveTime >= 1000) {
|
|
163
|
+
cache.save(startTime, startProgress);
|
|
164
|
+
setLastSaveTime(now);
|
|
165
|
+
}
|
|
166
|
+
}, 1000);
|
|
167
|
+
return () => {
|
|
168
|
+
clearInterval(saveTimer);
|
|
169
|
+
if (!isCompleted) {
|
|
170
|
+
cache.save(startTime, startProgress);
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
}, [cache, isCompleted, startProgress, lastSaveTime, startTime]);
|
|
174
|
+
|
|
175
|
+
// 使用进度显示 hook
|
|
176
|
+
const displayProgress = useDisplayProgress(startProgress, isCompleted ? Math.min(estimatedTime - time, 2) : estimatedTime);
|
|
177
|
+
return {
|
|
178
|
+
time: isCompleted ? Math.round((Date.now() - startTime) / 1000) : time,
|
|
179
|
+
startTime,
|
|
180
|
+
displayProgress,
|
|
181
|
+
startProgress,
|
|
182
|
+
cleanExpiredCache: cache.cleanExpired
|
|
183
|
+
};
|
|
184
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useSetState } from 'ahooks';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
|
-
import React, { useEffect, useMemo } from 'react';
|
|
3
|
+
import React, { useEffect, useMemo, useCallback } from 'react';
|
|
4
4
|
import { useNavigate } from 'react-router-dom';
|
|
5
5
|
import { LAUNCH_STATUS } from '@blocklet/launcher-util/es/constant';
|
|
6
6
|
import { useLocaleContext } from '../../contexts/locale';
|
|
@@ -8,7 +8,8 @@ import useRequest from '../../contexts/request';
|
|
|
8
8
|
import useSerialPolling from '../../hooks/use-serial-polling';
|
|
9
9
|
import { checkBlockletAccessible, getBlockletUrls, sortUrls, waitingForRaceAccessible } from '../../util';
|
|
10
10
|
import BaseServerlessLayout from './shared/base-serverless-layout';
|
|
11
|
-
import { AppSuccessDisplay, calculateEstimatedTime,
|
|
11
|
+
import { AppSuccessDisplay, calculateEstimatedTime, useFormatTime } from './shared/common-components';
|
|
12
|
+
import useWorkflowProgress from './shared/use-workflow-progress';
|
|
12
13
|
import LoadingDisplayLayout from './shared/loading-display-layout';
|
|
13
14
|
import RetryErrorMessage from './shared/retry-error-message';
|
|
14
15
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
@@ -33,12 +34,12 @@ export default function LaunchServerless({
|
|
|
33
34
|
accessibleUrl: null,
|
|
34
35
|
urls: [],
|
|
35
36
|
error: '',
|
|
37
|
+
hiddenRetry: false,
|
|
36
38
|
startTime: 0,
|
|
37
39
|
hasTryStart: false,
|
|
38
40
|
retryRequestCount: 0
|
|
39
41
|
});
|
|
40
42
|
const formatTime = useFormatTime();
|
|
41
|
-
const time = useElapsedTime(0);
|
|
42
43
|
const actions = useMemo(() => {
|
|
43
44
|
// 基础步骤时间
|
|
44
45
|
const steps = [{
|
|
@@ -84,7 +85,16 @@ export default function LaunchServerless({
|
|
|
84
85
|
const additionalTime = calculateEstimatedTime(components);
|
|
85
86
|
return actions.reduce((acc, action) => acc + action.time, 0) + additionalTime;
|
|
86
87
|
}, [actions]);
|
|
87
|
-
const
|
|
88
|
+
const {
|
|
89
|
+
time,
|
|
90
|
+
displayProgress,
|
|
91
|
+
cleanExpiredCache
|
|
92
|
+
} = useWorkflowProgress({
|
|
93
|
+
sessionId,
|
|
94
|
+
pageName: 'start-app',
|
|
95
|
+
estimatedTime,
|
|
96
|
+
isCompleted: state.started
|
|
97
|
+
});
|
|
88
98
|
|
|
89
99
|
// 根据当前进度获取对应的 action
|
|
90
100
|
const getCurrentAction = progress => {
|
|
@@ -102,24 +112,30 @@ export default function LaunchServerless({
|
|
|
102
112
|
return null;
|
|
103
113
|
}
|
|
104
114
|
};
|
|
105
|
-
const getUrls = async blocklet => {
|
|
115
|
+
const getUrls = useCallback(async blocklet => {
|
|
106
116
|
let {
|
|
107
117
|
urls
|
|
108
118
|
} = state;
|
|
119
|
+
const {
|
|
120
|
+
launchSession
|
|
121
|
+
} = state;
|
|
109
122
|
if (urls.length === 0) {
|
|
110
123
|
urls = getBlockletUrls({
|
|
111
|
-
blocklet
|
|
124
|
+
blocklet: blocklet || launchSession?.blocklet || {}
|
|
112
125
|
});
|
|
113
|
-
const
|
|
114
|
-
urls = sortedUrls;
|
|
126
|
+
const defaultUrl = launchSession?.appUrl || launchSession?.appInfo?.appUrl;
|
|
115
127
|
if (urls.length > 0) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
128
|
+
const sortedUrls = await sortUrls(urls);
|
|
129
|
+
urls = sortedUrls;
|
|
130
|
+
} else if (defaultUrl) {
|
|
131
|
+
urls = [defaultUrl];
|
|
119
132
|
}
|
|
133
|
+
setState({
|
|
134
|
+
urls
|
|
135
|
+
});
|
|
120
136
|
}
|
|
121
137
|
return urls;
|
|
122
|
-
};
|
|
138
|
+
}, [state, setState]);
|
|
123
139
|
const checkLaunchSession = async () => {
|
|
124
140
|
try {
|
|
125
141
|
const {
|
|
@@ -205,7 +221,17 @@ export default function LaunchServerless({
|
|
|
205
221
|
// 检查可用性 url
|
|
206
222
|
const checkAccessibleUrls = async () => {
|
|
207
223
|
try {
|
|
208
|
-
const urls = await getUrls(
|
|
224
|
+
const urls = await getUrls();
|
|
225
|
+
if (urls.length === 0) {
|
|
226
|
+
setState({
|
|
227
|
+
error: 'noAppUrl',
|
|
228
|
+
hiddenRetry: true,
|
|
229
|
+
starting: false,
|
|
230
|
+
checkingBlockletStatus: false,
|
|
231
|
+
checkingAccessible: false
|
|
232
|
+
});
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
209
235
|
const accessibleUrl = await checkOneAccessibleUrl(urls);
|
|
210
236
|
setState(prev => {
|
|
211
237
|
const obj = accessibleUrl ? {
|
|
@@ -271,7 +297,7 @@ export default function LaunchServerless({
|
|
|
271
297
|
started: false,
|
|
272
298
|
checkingBlockletStatus: false,
|
|
273
299
|
checkingAccessible: false,
|
|
274
|
-
error:
|
|
300
|
+
error: 'installFailed'
|
|
275
301
|
};
|
|
276
302
|
});
|
|
277
303
|
}
|
|
@@ -281,14 +307,14 @@ export default function LaunchServerless({
|
|
|
281
307
|
fetch(tryCount - 1);
|
|
282
308
|
}, 1000);
|
|
283
309
|
} else {
|
|
310
|
+
console.error('get launch session error', error);
|
|
284
311
|
setState({
|
|
285
|
-
error:
|
|
312
|
+
error: 'installFailed',
|
|
286
313
|
starting: false,
|
|
287
314
|
checkingBlockletStatus: false,
|
|
288
315
|
checkingAccessible: false
|
|
289
316
|
});
|
|
290
317
|
}
|
|
291
|
-
console.error('get launch session error', error);
|
|
292
318
|
}
|
|
293
319
|
};
|
|
294
320
|
if (window.blocklet.DEVELOPER_WORKFLOW_UI) {
|
|
@@ -322,12 +348,16 @@ export default function LaunchServerless({
|
|
|
322
348
|
window.location.reload();
|
|
323
349
|
}
|
|
324
350
|
};
|
|
325
|
-
const
|
|
326
|
-
const showSuccess = !
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
351
|
+
const showError = !!state.error;
|
|
352
|
+
const showSuccess = !showError && state.started && state.launchSession && displayProgress >= 99;
|
|
353
|
+
const showLoading = !state.error && !showSuccess;
|
|
354
|
+
|
|
355
|
+
// 在成功页面清理缓存
|
|
356
|
+
useEffect(() => {
|
|
357
|
+
if (showSuccess) {
|
|
358
|
+
cleanExpiredCache();
|
|
359
|
+
}
|
|
360
|
+
}, [showSuccess, cleanExpiredCache]);
|
|
331
361
|
return /*#__PURE__*/_jsxs(BaseServerlessLayout, {
|
|
332
362
|
title: t('startApp.pageTitle'),
|
|
333
363
|
children: [showLoading && /*#__PURE__*/_jsx(LoadingDisplayLayout, {
|
|
@@ -346,7 +376,8 @@ export default function LaunchServerless({
|
|
|
346
376
|
urls: state.urls
|
|
347
377
|
}), showError && /*#__PURE__*/_jsx(RetryErrorMessage, {
|
|
348
378
|
title: t('startApp.startFailed'),
|
|
349
|
-
|
|
379
|
+
desc: state.error && state.error !== 'installFailed' ? t(`startApp.error.${state.error}`) : '',
|
|
380
|
+
onRetry: state.hiddenRetry ? null : handleRetry,
|
|
350
381
|
retryText: t('common.retry')
|
|
351
382
|
})]
|
|
352
383
|
});
|
package/es/locales/en.js
CHANGED
|
@@ -124,6 +124,9 @@ export default {
|
|
|
124
124
|
creatingSecurityRules: 'Creating default security rules...',
|
|
125
125
|
assigningDomain: 'Assigning default domain name...',
|
|
126
126
|
waitingForDomain: 'Waiting for default domain name...'
|
|
127
|
+
},
|
|
128
|
+
error: {
|
|
129
|
+
noAppUrl: 'No accessible URL found, please contact the administrator'
|
|
127
130
|
}
|
|
128
131
|
},
|
|
129
132
|
loading: {
|
package/es/locales/zh.js
CHANGED
|
@@ -13,6 +13,7 @@ var _locale = require("../../contexts/locale");
|
|
|
13
13
|
var _request = _interopRequireDefault(require("../../contexts/request"));
|
|
14
14
|
var _baseServerlessLayout = _interopRequireDefault(require("./shared/base-serverless-layout"));
|
|
15
15
|
var _commonComponents = require("./shared/common-components");
|
|
16
|
+
var _useWorkflowProgress = _interopRequireDefault(require("./shared/use-workflow-progress"));
|
|
16
17
|
var _loadingDisplayLayout = _interopRequireDefault(require("./shared/loading-display-layout"));
|
|
17
18
|
var _retryErrorMessage = _interopRequireDefault(require("./shared/retry-error-message"));
|
|
18
19
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
@@ -41,8 +42,6 @@ function LaunchServerless(_ref) {
|
|
|
41
42
|
error: '',
|
|
42
43
|
launchSession: null
|
|
43
44
|
});
|
|
44
|
-
const formatTime = (0, _commonComponents.useFormatTime)();
|
|
45
|
-
const time = (0, _commonComponents.useElapsedTime)(0);
|
|
46
45
|
const actions = (0, _react.useMemo)(() => {
|
|
47
46
|
// 基础步骤时间
|
|
48
47
|
const steps = [{
|
|
@@ -79,7 +78,21 @@ function LaunchServerless(_ref) {
|
|
|
79
78
|
const estimatedTime = (0, _react.useMemo)(() => {
|
|
80
79
|
return actions.reduce((acc, action) => acc + action.time, 0);
|
|
81
80
|
}, [actions]);
|
|
82
|
-
const
|
|
81
|
+
const formatTime = (0, _commonComponents.useFormatTime)();
|
|
82
|
+
const {
|
|
83
|
+
time,
|
|
84
|
+
displayProgress
|
|
85
|
+
} = (0, _useWorkflowProgress.default)({
|
|
86
|
+
sessionId,
|
|
87
|
+
pageName: 'allocate',
|
|
88
|
+
estimatedTime,
|
|
89
|
+
isCompleted: state.allocated
|
|
90
|
+
});
|
|
91
|
+
(0, _react.useEffect)(() => {
|
|
92
|
+
if (displayProgress >= 99 && state.allocated) {
|
|
93
|
+
navigate("/install/".concat(sessionId).concat(window.location.search || ''));
|
|
94
|
+
}
|
|
95
|
+
}, [displayProgress, state.allocated, sessionId, navigate]);
|
|
83
96
|
|
|
84
97
|
// 根据当前进度获取对应的 action
|
|
85
98
|
const getCurrentAction = progress => {
|
|
@@ -92,6 +105,7 @@ function LaunchServerless(_ref) {
|
|
|
92
105
|
return (action === null || action === void 0 ? void 0 : action.message) || actions[actions.length - 1].message;
|
|
93
106
|
};
|
|
94
107
|
(0, _react.useEffect)(() => {
|
|
108
|
+
const timersRef = timerRef;
|
|
95
109
|
const fetch = async () => {
|
|
96
110
|
try {
|
|
97
111
|
const {
|
|
@@ -110,7 +124,7 @@ function LaunchServerless(_ref) {
|
|
|
110
124
|
await Promise.all([api.post('/serverless/allocate', {
|
|
111
125
|
launchId: sessionId
|
|
112
126
|
}), new Promise(resolve => {
|
|
113
|
-
|
|
127
|
+
timersRef.current.raceTimer = setTimeout(() => {
|
|
114
128
|
resolve();
|
|
115
129
|
}, estimatedTime * 1000);
|
|
116
130
|
})]);
|
|
@@ -128,7 +142,7 @@ function LaunchServerless(_ref) {
|
|
|
128
142
|
}
|
|
129
143
|
};
|
|
130
144
|
if (window.blocklet.DEVELOPER_WORKFLOW_UI) {
|
|
131
|
-
|
|
145
|
+
timersRef.current.mockTimer = setTimeout(() => {
|
|
132
146
|
navigate("/install/".concat(sessionId).concat(window.location.search || ''));
|
|
133
147
|
}, 5000);
|
|
134
148
|
} else {
|
|
@@ -138,17 +152,7 @@ function LaunchServerless(_ref) {
|
|
|
138
152
|
const timers = Object.values(timerRef.current); // eslint-disable-line react-hooks/exhaustive-deps
|
|
139
153
|
timers.forEach(timer => clearTimeout(timer));
|
|
140
154
|
};
|
|
141
|
-
|
|
142
|
-
}, []);
|
|
143
|
-
(0, _react.useEffect)(() => {
|
|
144
|
-
if (state.allocated) {
|
|
145
|
-
const timer = setTimeout(() => {
|
|
146
|
-
navigate("/install/".concat(sessionId).concat(window.location.search || ''));
|
|
147
|
-
}, 1000);
|
|
148
|
-
return () => clearTimeout(timer);
|
|
149
|
-
}
|
|
150
|
-
return () => {};
|
|
151
|
-
}, [state.allocated, sessionId, navigate]);
|
|
155
|
+
}, [api, estimatedTime, navigate, sessionId, setState]);
|
|
152
156
|
const handleRetry = async () => {
|
|
153
157
|
try {
|
|
154
158
|
var _state$launchSession;
|
|
@@ -14,6 +14,7 @@ var _request = _interopRequireDefault(require("../../contexts/request"));
|
|
|
14
14
|
var _useSerialPolling = _interopRequireDefault(require("../../hooks/use-serial-polling"));
|
|
15
15
|
var _baseServerlessLayout = _interopRequireDefault(require("./shared/base-serverless-layout"));
|
|
16
16
|
var _commonComponents = require("./shared/common-components");
|
|
17
|
+
var _useWorkflowProgress = _interopRequireDefault(require("./shared/use-workflow-progress"));
|
|
17
18
|
var _loadingDisplayLayout = _interopRequireDefault(require("./shared/loading-display-layout"));
|
|
18
19
|
var _retryErrorMessage = _interopRequireDefault(require("./shared/retry-error-message"));
|
|
19
20
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
@@ -97,6 +98,20 @@ function LaunchServerless(_ref) {
|
|
|
97
98
|
return acc + time;
|
|
98
99
|
}, 0) + Math.ceil(additionalTime * 0.6);
|
|
99
100
|
}, [actions]);
|
|
101
|
+
const {
|
|
102
|
+
time,
|
|
103
|
+
displayProgress
|
|
104
|
+
} = (0, _useWorkflowProgress.default)({
|
|
105
|
+
sessionId,
|
|
106
|
+
pageName: 'install',
|
|
107
|
+
estimatedTime,
|
|
108
|
+
isCompleted: state.status === STATUS.installed
|
|
109
|
+
});
|
|
110
|
+
(0, _react.useEffect)(() => {
|
|
111
|
+
if (displayProgress >= 99 && state.status === STATUS.installed) {
|
|
112
|
+
navigate("/start-app/".concat(sessionId).concat(window.location.search || ''));
|
|
113
|
+
}
|
|
114
|
+
}, [displayProgress, state.status, sessionId, navigate]);
|
|
100
115
|
|
|
101
116
|
// 根据当前进度获取对应的 action
|
|
102
117
|
const getCurrentAction = progress => {
|
|
@@ -108,18 +123,6 @@ function LaunchServerless(_ref) {
|
|
|
108
123
|
});
|
|
109
124
|
return (action === null || action === void 0 ? void 0 : action.message) || actions[actions.length - 1].message;
|
|
110
125
|
};
|
|
111
|
-
|
|
112
|
-
// 时间管理 - 参考 launch-dedicated.js
|
|
113
|
-
let installStartTime = sessionStorage.getItem("launcher-install-".concat(sessionId, "-time"));
|
|
114
|
-
if (!installStartTime) {
|
|
115
|
-
installStartTime = Date.now();
|
|
116
|
-
sessionStorage.setItem("launcher-install-".concat(sessionId, "-time"), installStartTime);
|
|
117
|
-
}
|
|
118
|
-
const time = (0, _commonComponents.useElapsedTime)(Math.round((Date.now() - installStartTime) / 1000));
|
|
119
|
-
const startProgress = (0, _react.useMemo)(() => {
|
|
120
|
-
return Math.min(time / estimatedTime * 100, 90);
|
|
121
|
-
}, [time, estimatedTime]);
|
|
122
|
-
const displayProgress = (0, _commonComponents.useDisplayProgress)(startProgress, state.status === STATUS.installed ? Math.min(estimatedTime - time, 2) : estimatedTime);
|
|
123
126
|
const checkLaunchSession = async () => {
|
|
124
127
|
if (window.blocklet.DEVELOPER_WORKFLOW_UI) {
|
|
125
128
|
// 开发模式下的模拟逻辑
|
|
@@ -127,7 +130,6 @@ function LaunchServerless(_ref) {
|
|
|
127
130
|
setState({
|
|
128
131
|
status: STATUS.installed
|
|
129
132
|
});
|
|
130
|
-
sessionStorage.removeItem("launcher-install-".concat(sessionId, "-time"));
|
|
131
133
|
navigate("/start-app/".concat(sessionId).concat(window.location.search || ''));
|
|
132
134
|
}, 47000);
|
|
133
135
|
return;
|
|
@@ -143,7 +145,6 @@ function LaunchServerless(_ref) {
|
|
|
143
145
|
} = launchSession.metadata;
|
|
144
146
|
const hasInstalled = ['starting', 'installed', 'running', 'stopped'].includes(status) || launchSession.running || launchSession.status >= _constant.LAUNCH_STATUS.consuming;
|
|
145
147
|
if (hasInstalled) {
|
|
146
|
-
sessionStorage.removeItem("launcher-install-".concat(sessionId, "-time"));
|
|
147
148
|
setState({
|
|
148
149
|
status: STATUS.installed
|
|
149
150
|
});
|
|
@@ -172,7 +173,6 @@ function LaunchServerless(_ref) {
|
|
|
172
173
|
retryCount: state.retryCount + 1
|
|
173
174
|
});
|
|
174
175
|
} else {
|
|
175
|
-
sessionStorage.removeItem("launcher-install-".concat(sessionId, "-time"));
|
|
176
176
|
setState({
|
|
177
177
|
error: error.message,
|
|
178
178
|
status: STATUS.error,
|
|
@@ -188,18 +188,7 @@ function LaunchServerless(_ref) {
|
|
|
188
188
|
interval: CHECK_INTERVAL,
|
|
189
189
|
onPoll: checkLaunchSession
|
|
190
190
|
});
|
|
191
|
-
(0, _react.useEffect)(() => {
|
|
192
|
-
if (state.status === STATUS.installed && displayProgress >= 99) {
|
|
193
|
-
const timer = setTimeout(() => {
|
|
194
|
-
sessionStorage.removeItem("launcher-install-".concat(sessionId, "-time"));
|
|
195
|
-
navigate("/start-app/".concat(sessionId).concat(window.location.search || ''));
|
|
196
|
-
}, 1500);
|
|
197
|
-
return () => clearTimeout(timer);
|
|
198
|
-
}
|
|
199
|
-
return () => {};
|
|
200
|
-
}, [state.status, sessionId, navigate, displayProgress]);
|
|
201
191
|
const handleRetry = () => {
|
|
202
|
-
sessionStorage.removeItem("launcher-install-".concat(sessionId, "-time"));
|
|
203
192
|
navigate("/launch/".concat(sessionId).concat(window.location.search || ''));
|
|
204
193
|
};
|
|
205
194
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_baseServerlessLayout.default, {
|
|
@@ -190,9 +190,9 @@ function useDisplayProgress(start) {
|
|
|
190
190
|
setProgress(prev => Math.max(prev, start));
|
|
191
191
|
}, [start]);
|
|
192
192
|
(0, _react.useEffect)(() => {
|
|
193
|
-
const timerDuration = Math.round((duration || 1) * 1000 /
|
|
193
|
+
const timerDuration = Math.round((duration || 1) * 1000 / 100);
|
|
194
194
|
const interval = setInterval(() => {
|
|
195
|
-
setProgress(prev => Math.min(prev + 1,
|
|
195
|
+
setProgress(prev => Math.min(prev + 1, 100));
|
|
196
196
|
}, timerDuration);
|
|
197
197
|
return () => clearInterval(interval);
|
|
198
198
|
}, [duration]);
|
|
@@ -17,6 +17,7 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
|
|
|
17
17
|
*/function RetryErrorMessage(_ref) {
|
|
18
18
|
let {
|
|
19
19
|
title,
|
|
20
|
+
desc = '',
|
|
20
21
|
onRetry,
|
|
21
22
|
retryText
|
|
22
23
|
} = _ref;
|
|
@@ -32,9 +33,13 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
|
|
|
32
33
|
setIsLoading(false);
|
|
33
34
|
};
|
|
34
35
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_launchResultMessage.default, {
|
|
36
|
+
style: {
|
|
37
|
+
marginTop: '20vh'
|
|
38
|
+
},
|
|
35
39
|
variant: "error",
|
|
36
40
|
title: title,
|
|
37
|
-
|
|
41
|
+
subTitle: desc,
|
|
42
|
+
footer: onRetry && /*#__PURE__*/(0, _jsxRuntime.jsx)(_button.default, {
|
|
38
43
|
size: "small",
|
|
39
44
|
style: {
|
|
40
45
|
marginTop: '-16px'
|
|
@@ -47,6 +52,7 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
|
|
|
47
52
|
}
|
|
48
53
|
RetryErrorMessage.propTypes = {
|
|
49
54
|
title: _propTypes.default.string.isRequired,
|
|
55
|
+
desc: _propTypes.default.string,
|
|
50
56
|
onRetry: _propTypes.default.func.isRequired,
|
|
51
57
|
retryText: _propTypes.default.string.isRequired
|
|
52
58
|
};
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = useWorkflowProgress;
|
|
7
|
+
var _react = require("react");
|
|
8
|
+
var _commonComponents = require("./common-components");
|
|
9
|
+
const CACHE_KEY = 'launcher-workflow';
|
|
10
|
+
const CACHE_EXPIRE_DAYS = 3;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 工作流进度管理 Hook
|
|
14
|
+
* @param {Object} options
|
|
15
|
+
* @param {string} options.sessionId - 会话ID
|
|
16
|
+
* @param {string} options.pageName - 页面名称,用于缓存标识
|
|
17
|
+
* @param {number} options.estimatedTime - 预计完成时间(秒)
|
|
18
|
+
* @param {boolean} options.isCompleted - 是否已完成
|
|
19
|
+
* @param {number} [options.initialStartTime] - 初始开始时间
|
|
20
|
+
* @returns {Object} 进度相关数据
|
|
21
|
+
*/
|
|
22
|
+
function useWorkflowProgress(_ref) {
|
|
23
|
+
let {
|
|
24
|
+
sessionId,
|
|
25
|
+
pageName,
|
|
26
|
+
estimatedTime,
|
|
27
|
+
isCompleted,
|
|
28
|
+
initialStartTime = Date.now()
|
|
29
|
+
} = _ref;
|
|
30
|
+
// 缓存操作函数
|
|
31
|
+
const cache = (0, _react.useMemo)(() => {
|
|
32
|
+
const getWorkflowCache = () => {
|
|
33
|
+
try {
|
|
34
|
+
return JSON.parse(localStorage.getItem(CACHE_KEY) || '{}');
|
|
35
|
+
} catch (error) {
|
|
36
|
+
return {};
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
const setWorkflowCache = data => {
|
|
40
|
+
try {
|
|
41
|
+
localStorage.setItem(CACHE_KEY, JSON.stringify(data));
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error('Failed to save cache:', error);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
return {
|
|
47
|
+
get: () => {
|
|
48
|
+
try {
|
|
49
|
+
const allCache = getWorkflowCache();
|
|
50
|
+
const sessionCache = allCache[sessionId] || {};
|
|
51
|
+
const pageCache = sessionCache[pageName];
|
|
52
|
+
if (!pageCache) return null;
|
|
53
|
+
const {
|
|
54
|
+
startTime,
|
|
55
|
+
progress,
|
|
56
|
+
expireTime
|
|
57
|
+
} = pageCache;
|
|
58
|
+
|
|
59
|
+
// 检查是否过期
|
|
60
|
+
if (expireTime && expireTime < Date.now()) {
|
|
61
|
+
// 删除过期缓存
|
|
62
|
+
delete sessionCache[pageName];
|
|
63
|
+
allCache[sessionId] = sessionCache;
|
|
64
|
+
setWorkflowCache(allCache);
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
startTime,
|
|
69
|
+
progress
|
|
70
|
+
};
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error('Failed to get cache:', error);
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
save: (currentStartTime, progress) => {
|
|
77
|
+
try {
|
|
78
|
+
const allCache = getWorkflowCache();
|
|
79
|
+
const sessionCache = allCache[sessionId] || {};
|
|
80
|
+
const expireTime = Date.now() + CACHE_EXPIRE_DAYS * 24 * 60 * 60 * 1000;
|
|
81
|
+
sessionCache[pageName] = {
|
|
82
|
+
startTime: currentStartTime,
|
|
83
|
+
progress,
|
|
84
|
+
expireTime
|
|
85
|
+
};
|
|
86
|
+
allCache[sessionId] = sessionCache;
|
|
87
|
+
setWorkflowCache(allCache);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error('Failed to save cache:', error);
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
clear: () => {
|
|
93
|
+
try {
|
|
94
|
+
const allCache = getWorkflowCache();
|
|
95
|
+
if (allCache[sessionId]) {
|
|
96
|
+
delete allCache[sessionId];
|
|
97
|
+
setWorkflowCache(allCache);
|
|
98
|
+
}
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.error('Failed to clear cache:', error);
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
// 清理所有过期缓存
|
|
104
|
+
cleanExpired: () => {
|
|
105
|
+
try {
|
|
106
|
+
const allCache = getWorkflowCache();
|
|
107
|
+
const now = Date.now();
|
|
108
|
+
let hasExpired = false;
|
|
109
|
+
|
|
110
|
+
// 遍历所有会话
|
|
111
|
+
Object.keys(allCache).forEach(sid => {
|
|
112
|
+
const sessionCache = allCache[sid];
|
|
113
|
+
// 遍历会话中的所有页面缓存
|
|
114
|
+
Object.keys(sessionCache).forEach(page => {
|
|
115
|
+
if (sessionCache[page].expireTime < now) {
|
|
116
|
+
delete sessionCache[page];
|
|
117
|
+
hasExpired = true;
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
// 如果会话没有页面缓存了,删除会话
|
|
121
|
+
if (Object.keys(sessionCache).length === 0) {
|
|
122
|
+
delete allCache[sid];
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
if (hasExpired) {
|
|
126
|
+
setWorkflowCache(allCache);
|
|
127
|
+
}
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.error('Failed to clean expired cache:', error);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
}, [sessionId, pageName]);
|
|
134
|
+
|
|
135
|
+
// 从缓存获取开始时间
|
|
136
|
+
const startTimeRef = (0, _react.useRef)();
|
|
137
|
+
startTimeRef.current = (0, _react.useMemo)(() => {
|
|
138
|
+
if (startTimeRef.current) return startTimeRef.current;
|
|
139
|
+
const cached = cache.get();
|
|
140
|
+
return (cached === null || cached === void 0 ? void 0 : cached.startTime) || initialStartTime;
|
|
141
|
+
}, [cache, initialStartTime]);
|
|
142
|
+
const startTime = startTimeRef.current;
|
|
143
|
+
// 计算已经过去的时间
|
|
144
|
+
const time = (0, _commonComponents.useElapsedTime)(Math.round((Date.now() - startTime) / 1000));
|
|
145
|
+
|
|
146
|
+
// 计算开始进度
|
|
147
|
+
const progressRef = (0, _react.useRef)();
|
|
148
|
+
progressRef.current = (0, _react.useMemo)(() => {
|
|
149
|
+
if (progressRef.current) return progressRef.current;
|
|
150
|
+
if (isCompleted) return 100;
|
|
151
|
+
const cached = cache.get();
|
|
152
|
+
if (cached !== null && cached !== void 0 && cached.progress) {
|
|
153
|
+
return Math.max(cached.progress, Math.min(time / estimatedTime * 100, 100));
|
|
154
|
+
}
|
|
155
|
+
return Math.min(time / estimatedTime * 100, 100);
|
|
156
|
+
}, [cache, time, estimatedTime, isCompleted]);
|
|
157
|
+
const startProgress = progressRef.current;
|
|
158
|
+
|
|
159
|
+
// 自动保存进度
|
|
160
|
+
const [lastSaveTime, setLastSaveTime] = (0, _react.useState)(Date.now());
|
|
161
|
+
(0, _react.useEffect)(() => {
|
|
162
|
+
if (isCompleted) {
|
|
163
|
+
cache.save(startTime, 100);
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
const saveTimer = setInterval(() => {
|
|
167
|
+
const now = Date.now();
|
|
168
|
+
// 每秒最多保存一次
|
|
169
|
+
if (now - lastSaveTime >= 1000) {
|
|
170
|
+
cache.save(startTime, startProgress);
|
|
171
|
+
setLastSaveTime(now);
|
|
172
|
+
}
|
|
173
|
+
}, 1000);
|
|
174
|
+
return () => {
|
|
175
|
+
clearInterval(saveTimer);
|
|
176
|
+
if (!isCompleted) {
|
|
177
|
+
cache.save(startTime, startProgress);
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
}, [cache, isCompleted, startProgress, lastSaveTime, startTime]);
|
|
181
|
+
|
|
182
|
+
// 使用进度显示 hook
|
|
183
|
+
const displayProgress = (0, _commonComponents.useDisplayProgress)(startProgress, isCompleted ? Math.min(estimatedTime - time, 2) : estimatedTime);
|
|
184
|
+
return {
|
|
185
|
+
time: isCompleted ? Math.round((Date.now() - startTime) / 1000) : time,
|
|
186
|
+
startTime,
|
|
187
|
+
displayProgress,
|
|
188
|
+
startProgress,
|
|
189
|
+
cleanExpiredCache: cache.cleanExpired
|
|
190
|
+
};
|
|
191
|
+
}
|
|
@@ -15,6 +15,7 @@ var _useSerialPolling = _interopRequireDefault(require("../../hooks/use-serial-p
|
|
|
15
15
|
var _util = require("../../util");
|
|
16
16
|
var _baseServerlessLayout = _interopRequireDefault(require("./shared/base-serverless-layout"));
|
|
17
17
|
var _commonComponents = require("./shared/common-components");
|
|
18
|
+
var _useWorkflowProgress = _interopRequireDefault(require("./shared/use-workflow-progress"));
|
|
18
19
|
var _loadingDisplayLayout = _interopRequireDefault(require("./shared/loading-display-layout"));
|
|
19
20
|
var _retryErrorMessage = _interopRequireDefault(require("./shared/retry-error-message"));
|
|
20
21
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
@@ -47,12 +48,12 @@ function LaunchServerless(_ref) {
|
|
|
47
48
|
accessibleUrl: null,
|
|
48
49
|
urls: [],
|
|
49
50
|
error: '',
|
|
51
|
+
hiddenRetry: false,
|
|
50
52
|
startTime: 0,
|
|
51
53
|
hasTryStart: false,
|
|
52
54
|
retryRequestCount: 0
|
|
53
55
|
});
|
|
54
56
|
const formatTime = (0, _commonComponents.useFormatTime)();
|
|
55
|
-
const time = (0, _commonComponents.useElapsedTime)(0);
|
|
56
57
|
const actions = (0, _react.useMemo)(() => {
|
|
57
58
|
// 基础步骤时间
|
|
58
59
|
const steps = [{
|
|
@@ -98,7 +99,16 @@ function LaunchServerless(_ref) {
|
|
|
98
99
|
const additionalTime = (0, _commonComponents.calculateEstimatedTime)(components);
|
|
99
100
|
return actions.reduce((acc, action) => acc + action.time, 0) + additionalTime;
|
|
100
101
|
}, [actions]);
|
|
101
|
-
const
|
|
102
|
+
const {
|
|
103
|
+
time,
|
|
104
|
+
displayProgress,
|
|
105
|
+
cleanExpiredCache
|
|
106
|
+
} = (0, _useWorkflowProgress.default)({
|
|
107
|
+
sessionId,
|
|
108
|
+
pageName: 'start-app',
|
|
109
|
+
estimatedTime,
|
|
110
|
+
isCompleted: state.started
|
|
111
|
+
});
|
|
102
112
|
|
|
103
113
|
// 根据当前进度获取对应的 action
|
|
104
114
|
const getCurrentAction = progress => {
|
|
@@ -119,24 +129,31 @@ function LaunchServerless(_ref) {
|
|
|
119
129
|
return null;
|
|
120
130
|
}
|
|
121
131
|
};
|
|
122
|
-
const getUrls = async blocklet => {
|
|
132
|
+
const getUrls = (0, _react.useCallback)(async blocklet => {
|
|
123
133
|
let {
|
|
124
134
|
urls
|
|
125
135
|
} = state;
|
|
136
|
+
const {
|
|
137
|
+
launchSession
|
|
138
|
+
} = state;
|
|
126
139
|
if (urls.length === 0) {
|
|
140
|
+
var _launchSession$appInf;
|
|
127
141
|
urls = (0, _util.getBlockletUrls)({
|
|
128
|
-
blocklet
|
|
142
|
+
blocklet: blocklet || (launchSession === null || launchSession === void 0 ? void 0 : launchSession.blocklet) || {}
|
|
129
143
|
});
|
|
130
|
-
const
|
|
131
|
-
urls = sortedUrls;
|
|
144
|
+
const defaultUrl = (launchSession === null || launchSession === void 0 ? void 0 : launchSession.appUrl) || (launchSession === null || launchSession === void 0 || (_launchSession$appInf = launchSession.appInfo) === null || _launchSession$appInf === void 0 ? void 0 : _launchSession$appInf.appUrl);
|
|
132
145
|
if (urls.length > 0) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
146
|
+
const sortedUrls = await (0, _util.sortUrls)(urls);
|
|
147
|
+
urls = sortedUrls;
|
|
148
|
+
} else if (defaultUrl) {
|
|
149
|
+
urls = [defaultUrl];
|
|
136
150
|
}
|
|
151
|
+
setState({
|
|
152
|
+
urls
|
|
153
|
+
});
|
|
137
154
|
}
|
|
138
155
|
return urls;
|
|
139
|
-
};
|
|
156
|
+
}, [state, setState]);
|
|
140
157
|
const checkLaunchSession = async () => {
|
|
141
158
|
try {
|
|
142
159
|
const {
|
|
@@ -219,7 +236,17 @@ function LaunchServerless(_ref) {
|
|
|
219
236
|
// 检查可用性 url
|
|
220
237
|
const checkAccessibleUrls = async () => {
|
|
221
238
|
try {
|
|
222
|
-
const urls = await getUrls(
|
|
239
|
+
const urls = await getUrls();
|
|
240
|
+
if (urls.length === 0) {
|
|
241
|
+
setState({
|
|
242
|
+
error: 'noAppUrl',
|
|
243
|
+
hiddenRetry: true,
|
|
244
|
+
starting: false,
|
|
245
|
+
checkingBlockletStatus: false,
|
|
246
|
+
checkingAccessible: false
|
|
247
|
+
});
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
223
250
|
const accessibleUrl = await checkOneAccessibleUrl(urls);
|
|
224
251
|
setState(prev => {
|
|
225
252
|
const obj = accessibleUrl ? _objectSpread(_objectSpread({}, prev), {}, {
|
|
@@ -284,7 +311,7 @@ function LaunchServerless(_ref) {
|
|
|
284
311
|
started: false,
|
|
285
312
|
checkingBlockletStatus: false,
|
|
286
313
|
checkingAccessible: false,
|
|
287
|
-
error:
|
|
314
|
+
error: 'installFailed'
|
|
288
315
|
});
|
|
289
316
|
});
|
|
290
317
|
}
|
|
@@ -294,14 +321,14 @@ function LaunchServerless(_ref) {
|
|
|
294
321
|
_fetch(tryCount - 1);
|
|
295
322
|
}, 1000);
|
|
296
323
|
} else {
|
|
324
|
+
console.error('get launch session error', error);
|
|
297
325
|
setState({
|
|
298
|
-
error:
|
|
326
|
+
error: 'installFailed',
|
|
299
327
|
starting: false,
|
|
300
328
|
checkingBlockletStatus: false,
|
|
301
329
|
checkingAccessible: false
|
|
302
330
|
});
|
|
303
331
|
}
|
|
304
|
-
console.error('get launch session error', error);
|
|
305
332
|
}
|
|
306
333
|
};
|
|
307
334
|
if (window.blocklet.DEVELOPER_WORKFLOW_UI) {
|
|
@@ -335,12 +362,16 @@ function LaunchServerless(_ref) {
|
|
|
335
362
|
window.location.reload();
|
|
336
363
|
}
|
|
337
364
|
};
|
|
338
|
-
const
|
|
339
|
-
const showSuccess = !
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
365
|
+
const showError = !!state.error;
|
|
366
|
+
const showSuccess = !showError && state.started && state.launchSession && displayProgress >= 99;
|
|
367
|
+
const showLoading = !state.error && !showSuccess;
|
|
368
|
+
|
|
369
|
+
// 在成功页面清理缓存
|
|
370
|
+
(0, _react.useEffect)(() => {
|
|
371
|
+
if (showSuccess) {
|
|
372
|
+
cleanExpiredCache();
|
|
373
|
+
}
|
|
374
|
+
}, [showSuccess, cleanExpiredCache]);
|
|
344
375
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_baseServerlessLayout.default, {
|
|
345
376
|
title: t('startApp.pageTitle'),
|
|
346
377
|
children: [showLoading && /*#__PURE__*/(0, _jsxRuntime.jsx)(_loadingDisplayLayout.default, {
|
|
@@ -359,7 +390,8 @@ function LaunchServerless(_ref) {
|
|
|
359
390
|
urls: state.urls
|
|
360
391
|
}), showError && /*#__PURE__*/(0, _jsxRuntime.jsx)(_retryErrorMessage.default, {
|
|
361
392
|
title: t('startApp.startFailed'),
|
|
362
|
-
|
|
393
|
+
desc: state.error && state.error !== 'installFailed' ? t("startApp.error.".concat(state.error)) : '',
|
|
394
|
+
onRetry: state.hiddenRetry ? null : handleRetry,
|
|
363
395
|
retryText: t('common.retry')
|
|
364
396
|
})]
|
|
365
397
|
});
|
package/lib/locales/en.js
CHANGED
|
@@ -130,6 +130,9 @@ var _default = exports.default = {
|
|
|
130
130
|
creatingSecurityRules: 'Creating default security rules...',
|
|
131
131
|
assigningDomain: 'Assigning default domain name...',
|
|
132
132
|
waitingForDomain: 'Waiting for default domain name...'
|
|
133
|
+
},
|
|
134
|
+
error: {
|
|
135
|
+
noAppUrl: 'No accessible URL found, please contact the administrator'
|
|
133
136
|
}
|
|
134
137
|
},
|
|
135
138
|
loading: {
|
package/lib/locales/zh.js
CHANGED
|
@@ -129,6 +129,9 @@ var _default = exports.default = {
|
|
|
129
129
|
creatingSecurityRules: '正在创建默认安全规则...',
|
|
130
130
|
assigningDomain: '正在为 Blocklet 分配默认域名...',
|
|
131
131
|
waitingForDomain: '正在等待默认域名生效...'
|
|
132
|
+
},
|
|
133
|
+
error: {
|
|
134
|
+
noAppUrl: '没有发现 Blocklet 的访问地址,请联系管理员'
|
|
132
135
|
}
|
|
133
136
|
},
|
|
134
137
|
loading: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blocklet/launcher-workflow",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.8",
|
|
4
4
|
"description": "Purchase components for Launcher UI",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -49,8 +49,8 @@
|
|
|
49
49
|
"@arcblock/react-hooks": "^3.0.27",
|
|
50
50
|
"@arcblock/ux": "^3.0.27",
|
|
51
51
|
"@blocklet/launcher-layout": "^3.0.27",
|
|
52
|
-
"@blocklet/launcher-util": "2.4.
|
|
53
|
-
"@blocklet/launcher-ux": "2.4.
|
|
52
|
+
"@blocklet/launcher-util": "2.4.8",
|
|
53
|
+
"@blocklet/launcher-ux": "2.4.8",
|
|
54
54
|
"@blocklet/payment": "^1.14.8",
|
|
55
55
|
"@blocklet/payment-react": "^1.19.5",
|
|
56
56
|
"@emotion/react": "^11.14.0",
|
|
@@ -109,5 +109,5 @@
|
|
|
109
109
|
"require": "./lib/locales/index.js"
|
|
110
110
|
}
|
|
111
111
|
},
|
|
112
|
-
"gitHead": "
|
|
112
|
+
"gitHead": "ad6b9051901b11aeacc305087d5d5ad89570a6e9"
|
|
113
113
|
}
|