diff --git a/package-lock.json b/package-lock.json
index f5aabc0e6..2bb26e24a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,5 +1,5 @@
{
- "name": "educoder",
+ "name": "forge",
"version": "0.1.0",
"lockfileVersion": 1,
"requires": true,
@@ -3888,11 +3888,6 @@
"randomfill": "^1.0.3"
}
},
- "crypto-js": {
- "version": "4.0.0",
- "resolved": "https://registry.npm.taobao.org/crypto-js/download/crypto-js-4.0.0.tgz",
- "integrity": "sha1-KQSrJnep0EKFai6i74DekuSjbcw="
- },
"crypto-random-string": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz",
@@ -20480,6 +20475,16 @@
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
"dev": true
},
+ "xterm": {
+ "version": "4.8.1",
+ "resolved": "https://registry.npm.taobao.org/xterm/download/xterm-4.8.1.tgz",
+ "integrity": "sha1-FVoXKaQ+Gom0BlJOIsVjQznjnKE="
+ },
+ "xterm-addon-fit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npm.taobao.org/xterm-addon-fit/download/xterm-addon-fit-0.4.0.tgz",
+ "integrity": "sha1-BuDF0KaqrPsAnvVl76HIHpPZAZM="
+ },
"y18n": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz",
diff --git a/package.json b/package.json
index edc6e18e7..5ad750143 100644
--- a/package.json
+++ b/package.json
@@ -111,7 +111,9 @@
"webpack-dev-server": "^3.10.3",
"webpack-manifest-plugin": "^2.2.0",
"whatwg-fetch": "2.0.3",
- "wrap-md-editor": "^0.2.20"
+ "wrap-md-editor": "^0.2.20",
+ "xterm": "4.8.1",
+ "xterm-addon-fit": "0.4.0"
},
"scripts": {
"start": "node --max_old_space_size=15360 scripts/start.js",
diff --git a/src/forge/DevOps/OpsDetailRightpanel.jsx b/src/forge/DevOps/OpsDetailRightpanel.jsx
index 6998964e2..3546312d9 100644
--- a/src/forge/DevOps/OpsDetailRightpanel.jsx
+++ b/src/forge/DevOps/OpsDetailRightpanel.jsx
@@ -2,6 +2,7 @@ import React, { useState, useEffect } from "react";
import { Spin , Menu } from "antd";
import { FlexAJ, AlignCenter } from "../Component/layout";
import axios from "axios";
+import CodeSSH from './ssh/Index';
export default ({
data,
@@ -68,10 +69,10 @@ export default ({
}
return (
- {/*
{
nav === "0" &&
@@ -111,7 +112,9 @@ export default ({
)}
}
-
+ {
+ nav === "1" &&
+ }
);
};
diff --git a/src/forge/DevOps/ssh/Index.jsx b/src/forge/DevOps/ssh/Index.jsx
new file mode 100644
index 000000000..64fb5b4ef
--- /dev/null
+++ b/src/forge/DevOps/ssh/Index.jsx
@@ -0,0 +1,14 @@
+import React, { useState } from 'react';
+import XmlPanel from './XmlPanel';
+import mediator from './mediator';
+
+function Index(){
+ const [ sshConfigData ,setSshConfigData ] = useState(undefined);
+
+ return(
+
+
+
+ )
+}
+export default Index;
\ No newline at end of file
diff --git a/src/forge/DevOps/ssh/XmlPanel.jsx b/src/forge/DevOps/ssh/XmlPanel.jsx
new file mode 100644
index 000000000..5e2a5f50a
--- /dev/null
+++ b/src/forge/DevOps/ssh/XmlPanel.jsx
@@ -0,0 +1,218 @@
+import React, { useRef, useEffect, useState } from 'react';
+import { Base64 } from 'js-base64';
+
+import { Terminal } from 'xterm';
+import 'xterm/css/xterm.css';
+import mediator from './mediator';
+import ResizeObserver from 'resize-observer-polyfill';
+
+function getColsAndRows(width, height, term) {
+ let w = term._core._renderService.dimensions.actualCellWidth || 9.5;
+ let h = term._core._renderService.dimensions.actualCellHeight || 18;
+ const rows = Math.floor(height / h);
+ const cols = Math.floor(width / w);
+ return [cols, rows];
+}
+
+function onLayout(term, el) {
+ const ro = new ResizeObserver(entries => {
+ for (let entry of entries) {
+ if (entry.target.offsetHeight > 0 || entry.target.offsetWidth > 0) {
+ const [cols, rows] = getColsAndRows(
+ entry.target.offsetWidth,
+ entry.target.offsetHeight,
+ term,
+ );
+ console.log('cols, rows', cols, rows);
+ term.resize(cols, rows);
+ mediator.publish('ssh-xterm-resize', {
+ columns: cols,
+ rows: rows,
+ width: entry.target.offsetWidth,
+ height: entry.target.offsetHeight,
+ });
+ }
+ }
+ });
+ ro.observe(el);
+ return ro;
+}
+
+const TimeTicket = 30000;
+
+//建立 websockt 来交互
+//根据容器大小计算行数和列数并做到自适应
+//socket 与 term 需要分开初始化 因为socket 可能重置连接
+//mediator 监听消息,如果和id匹配,则建立连接,重置,或关闭连接
+
+export default ({ sshConfigData, sid }) => {
+ const [term, setTerm] = useState(null);
+
+ const { ws_url, password, port } = sshConfigData;
+ const el = useRef();
+ const socket = useRef();
+ const isFirstConnected = useRef(false);
+
+ //term init
+ useEffect(() => {
+ if (el.current && ws_url) {
+ const term = new Terminal({ fontSize: 16, rendererType: 'dom' });
+ term.open(el.current);
+
+ term.onData(data => {
+ if (socket.current) {
+ if (socket.current.readyState === 1) {
+ socket.current.send(JSON.stringify({ tp: 'client', data: data }));
+ mediator.publish('on-operating-ssh'); //有操作则自动延时
+ } else {
+ //断开连接后重连
+ // socket.current = null
+ // mediator.publish('create-socket', sid)
+ }
+ }
+ });
+ term.write('Connecting...');
+ setTerm(term);
+ const ro = onLayout(term, el.current);
+ return () => {
+ term.dispose();
+ ro.unobserve(el.current);
+ };
+ }
+ }, [ws_url, el.current]);
+
+ useEffect(() => {
+ if (term && ws_url) {
+ function createSocket() {
+ const socketInstance = new WebSocket(ws_url);
+ socket.current = socketInstance;
+
+ socketInstance.onopen = () => {
+ let container = term.element.parentElement;
+ if (container) {
+ let width = container.offsetWidth;
+ let height = container.offsetHeight;
+ console.log('init', {
+ tp: 'init',
+ data: {
+ ...sshConfigData,
+ secret: password,
+ width,
+ height,
+ rows: term.rows,
+ columns: term.cols,
+ },
+ });
+ socketInstance.send(
+ JSON.stringify({
+ tp: 'init',
+ data: {
+ ...sshConfigData,
+ secret: password,
+ width,
+ height,
+ rows: term.rows,
+ columns: term.cols,
+ },
+ }),
+ );
+ }
+ term.focus();
+ };
+ socketInstance.onerror = error => {
+ console.log(
+ '------in socket error----',
+ error,
+ socketInstance,
+ ws_url,
+ );
+ //连接报错后,重新请求资源
+ // mediator.publish('on-recreate-socket')
+ };
+ socketInstance.onmessage = event => {
+ if (!isFirstConnected.current) {
+ term.write('\r');
+ // term.focus()
+ setTimeout(() => {
+ // term.clear();
+ }, 1000);
+ }
+ isFirstConnected.current = true;
+ console.log('event:', event);
+
+ const data = Base64.decode(event.data.toString());
+ let w = term._core._renderService.dimensions.actualCellWidth || 9.5;
+
+ console.log('data:', data, w, term);
+ term.write(data);
+ };
+
+ socketInstance.onclose = evt => {
+ if (tid) {
+ clearInterval(tid);
+ }
+ term.write('\r\nconnection closed');
+ };
+ }
+
+ const tid = setInterval(() => {
+ if (socket.current) {
+ socket.current.send(JSON.stringify({ tp: 'h' }));
+ }
+ }, TimeTicket);
+
+ const unSubCreate = mediator.subscribe('create-socket', id => {
+ if (sid === id) {
+ if (socket.current && socket.current.readyState === 1) {
+ term.focus();
+ } else {
+ createSocket();
+ }
+ term.focus();
+ }
+ });
+
+ const unSubClose = mediator.subscribe('close-socket', id => {
+ if (sid === id) {
+ if (socket.current) {
+ socket.current.close();
+ isFirstConnected.current = false;
+ term.clear();
+ }
+ socket.current = null;
+ }
+ });
+
+ const unSubResize = mediator.subscribe('ssh-xterm-resize', option => {
+ if (socket.current && socket.current.readyState === 1) {
+ socket.current.send(
+ JSON.stringify({ tp: 'resize', data: { ...option } }),
+ );
+ }
+ });
+
+ const unSubAddTime = mediator.subscribe('ssh-add-connect-time', () => {
+ if (socket.current && socket.current.readyState === 1) {
+ socket.current.send(JSON.stringify({ tp: 'overtime' }));
+ }
+ });
+
+ return () => {
+ unSubClose();
+ unSubCreate();
+ unSubResize();
+ unSubAddTime();
+ if (socket.current) {
+ socket.current.close();
+ isFirstConnected.current = false;
+ }
+ };
+ }
+ }, [term, ws_url, port]);
+
+ return (
+
+ {!ws_url ?
正在连接命令行服务...
: null}
+
+ );
+};
diff --git a/src/forge/DevOps/ssh/mediator.jsx b/src/forge/DevOps/ssh/mediator.jsx
new file mode 100644
index 000000000..652705799
--- /dev/null
+++ b/src/forge/DevOps/ssh/mediator.jsx
@@ -0,0 +1,46 @@
+function Mediator(obj) {
+ const channels = {};
+
+ const mediator = {
+ subscribe: function(channel, cb) {
+ if (!channels[channel]) {
+ channels[channel] = [];
+ }
+ channels[channel].push(cb);
+ return this.unsubscribe.bind(null, channel, cb);
+ },
+
+ unsubscribe: function(channel, cb) {
+ let rs = channels[channel];
+ let index = -1;
+ if (rs) {
+ for (let i = 0; i < rs.length; i++) {
+ if (rs[i].name === cb.name) {
+ index = i;
+ break;
+ }
+ }
+ if (index >= 0) {
+ channels[channel].splice(index, 1);
+ return true;
+ }
+ }
+ return false;
+ },
+
+ publish: function(channel) {
+ if (!channels[channel]) {
+ return false;
+ }
+ const args = Array.prototype.slice.call(arguments, 1);
+ channels[channel].forEach(subscription => {
+ subscription.apply(null, args);
+ });
+ return this;
+ },
+ };
+
+ return obj ? Object.assign(obj, mediator) : mediator;
+}
+const mediator = new Mediator();
+export default mediator;