diff --git a/dinky-web/src/components/Modal/WsErrorShow/WsErrorShow.tsx b/dinky-web/src/components/Modal/WsErrorShow/WsErrorShow.tsx
new file mode 100644
index 0000000000..1cb2fd44ea
--- /dev/null
+++ b/dinky-web/src/components/Modal/WsErrorShow/WsErrorShow.tsx
@@ -0,0 +1,33 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import { Button, Result } from 'antd';
+import { WsState } from '@/models/UseWebSocketModel';
+import * as React from 'react';
+import { l } from '@/utils/intl';
+
+const WsErrorShow = (props: { state: WsState; extra?: React.ReactNode }) => {
+ const { state, extra } = props;
+
+ return (
+
+ );
+};
+
+export default WsErrorShow;
diff --git a/dinky-web/src/locales/en-US/global.ts b/dinky-web/src/locales/en-US/global.ts
index 09c8b1a00b..ce78f16d68 100644
--- a/dinky-web/src/locales/en-US/global.ts
+++ b/dinky-web/src/locales/en-US/global.ts
@@ -236,5 +236,8 @@ export default {
'global.job.status.failed-tip': 'Failed to submit to the cluster, unable to get the task name',
'global.operation.unable': 'Unable to operate',
- 'global.operation.able': 'Can operate'
+ 'global.operation.able': 'Can operate',
+
+ //WS
+ 'global.ws.failed': 'Failed to connect to WebSocket'
};
diff --git a/dinky-web/src/locales/zh-CN/global.ts b/dinky-web/src/locales/zh-CN/global.ts
index d3c55ef835..88bde72050 100644
--- a/dinky-web/src/locales/zh-CN/global.ts
+++ b/dinky-web/src/locales/zh-CN/global.ts
@@ -232,5 +232,8 @@ export default {
'global.job.status.failed-tip': '未成功提交到集群,无法获取任务名称/作业ID',
'global.operation.unable': '无法操作',
- 'global.operation.able': '可以操作'
+ 'global.operation.able': '可以操作',
+
+ //WS
+ 'global.ws.failed': '连接WebSocket失败'
};
diff --git a/dinky-web/src/models/UseWebSocketModel.tsx b/dinky-web/src/models/UseWebSocketModel.tsx
index 3da3ebd774..6894ac5ad4 100644
--- a/dinky-web/src/models/UseWebSocketModel.tsx
+++ b/dinky-web/src/models/UseWebSocketModel.tsx
@@ -21,6 +21,7 @@ import { useEffect, useRef, useState } from 'react';
import { ErrorMessage } from '@/utils/messages';
import { v4 as uuidv4 } from 'uuid';
import { TOKEN_KEY } from '@/services/constants';
+
export type SseData = {
topic: string;
data: Record;
@@ -39,21 +40,33 @@ export type SubscriberData = {
params: string[];
call: (data: SseData) => void;
};
+
+export type WsState = {
+ wsOnReady: boolean;
+ wsUrl: string;
+};
+
export default () => {
const subscriberRef = useRef([]);
- const wsUrl = `ws://${window.location.hostname}:${window.location.port}/api/ws/global`;
- const ws = useRef(new WebSocket(wsUrl));
+ const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws';
+ const wsUrl = `${protocol}://${window.location.hostname}:${window.location.port}/api/ws/global`;
+ const [wsState, setWsState] = useState({ wsOnReady: true, wsUrl });
+
+ const ws = useRef();
const reconnect = () => {
- if (ws.current.readyState === WebSocket.OPEN) {
+ if (ws.current && ws.current.readyState === WebSocket.CLOSED) {
ws.current.close();
}
ws.current = new WebSocket(wsUrl);
ws.current.onopen = () => {
+ setWsState({ wsOnReady: true, wsUrl });
receiveMessage();
subscribe();
};
+ ws.current.onerror = () => setWsState({ wsOnReady: false, wsUrl });
+ ws.current.onclose = () => setWsState({ wsOnReady: false, wsUrl });
};
const subscribe = () => {
@@ -68,31 +81,36 @@ export default () => {
topics[sub.topic] = [...topics[sub.topic]];
}
});
- if (ws.current.readyState === WebSocket.CLOSED) {
+ if (!ws.current || ws.current.readyState === WebSocket.CLOSED) {
reconnect();
- } else {
+ } else if (ws.current.readyState === WebSocket.OPEN) {
const token = JSON.parse(localStorage.getItem(TOKEN_KEY) ?? '{}')?.tokenValue;
ws.current.send(JSON.stringify({ token, topics }));
+ } else {
+ //TODO 这里要做些什么
}
};
const receiveMessage = () => {
- ws.current.onmessage = (e) => {
- try {
- const data: SseData = JSON.parse(e.data);
- subscriberRef.current
- .filter((sub) => sub.topic === data.topic)
- .filter((sub) => !sub.params || sub.params.find((x) => data.data[x]))
- .forEach((sub) => sub.call(data));
- } catch (e: any) {
- ErrorMessage(e);
- }
- };
+ if (ws.current) {
+ ws.current.onmessage = (e) => {
+ try {
+ const data: SseData = JSON.parse(e.data);
+ subscriberRef.current
+ .filter((sub) => sub.topic === data.topic)
+ .filter((sub) => !sub.params || sub.params.find((x) => data.data[x]))
+ .forEach((sub) => sub.call(data));
+ } catch (e: any) {
+ ErrorMessage(e);
+ }
+ };
+ }
};
+
useEffect(() => {
receiveMessage();
setInterval(() => {
- if (ws.current.readyState === WebSocket.CLOSED) {
+ if (!ws.current || ws.current.readyState != WebSocket.OPEN) {
reconnect();
}
}, 2000);
@@ -111,6 +129,7 @@ export default () => {
return {
subscribeTopic,
- reconnect
+ reconnect,
+ wsState
};
};
diff --git a/dinky-web/src/pages/DataStudio/BottomContainer/Console/ConsoleContent.tsx b/dinky-web/src/pages/DataStudio/BottomContainer/Console/ConsoleContent.tsx
index 35a5e08e4e..9d9d793fea 100644
--- a/dinky-web/src/pages/DataStudio/BottomContainer/Console/ConsoleContent.tsx
+++ b/dinky-web/src/pages/DataStudio/BottomContainer/Console/ConsoleContent.tsx
@@ -29,11 +29,12 @@ import { SplitPane } from '@andrewray/react-multi-split-pane';
import { Pane } from '@andrewray/react-multi-split-pane/dist/lib/Pane';
import { CheckOutlined, CloseCircleFilled, LoadingOutlined, XFilled } from '@ant-design/icons';
import { connect, useModel, useRequest } from '@umijs/max';
-import { Empty, Space, Typography } from 'antd';
+import { Button, Empty, Space, Typography } from 'antd';
import { DataNode } from 'antd/es/tree';
import DirectoryTree from 'antd/es/tree/DirectoryTree';
import { Key, useEffect, useRef, useState } from 'react';
import { SseData, Topic } from '@/models/UseWebSocketModel';
+import WsErrorShow from '@/components/Modal/WsErrorShow/WsErrorShow';
const { Text } = Typography;
@@ -72,11 +73,17 @@ const ConsoleContent = (props: ConsoleProps) => {
const [processNode, setProcessNode] = useState();
const [expandedKeys, setExpandedKeys] = useState([]);
+ const [showCacheData, setShowCacheData] = useState(false);
+
const process = `FlinkSubmit/${tab.params.taskId}`;
const { subscribeTopic } = useModel('UseWebSocketModel', (model: any) => ({
subscribeTopic: model?.subscribeTopic
}));
+ const { wsState } = useModel('UseWebSocketModel', (model: any) => ({
+ wsState: model?.wsState
+ }));
+
const onUpdate = (data: ProcessStep) => {
setProcessNode((prevState: any) => {
//如果key不一致代表重新提交了任务,清空旧状态
@@ -147,6 +154,19 @@ const ConsoleContent = (props: ConsoleProps) => {
setExpandedKeys(expandedKeys);
};
+ if (!wsState?.wsOnReady && !showCacheData) {
+ return (
+ setShowCacheData(true)}>
+ {l('devops.jobinfo.recently.job.status')}
+
+ }
+ />
+ );
+ }
+
return (