Compare commits

..

3 Commits

Author SHA1 Message Date
wang fadb4a4ee3 修改webssh 2024-07-01 10:45:49 +08:00
wang cb10f62c35 修改webssh 2024-07-01 10:43:44 +08:00
wang fba2428e30 修改webssh 2024-07-01 10:43:24 +08:00
21 changed files with 419 additions and 54 deletions

2
.gitignore vendored
View File

@ -1,4 +1,2 @@
.idea
node_modules
__pycache__
public

View File

@ -38,6 +38,7 @@
"react-scripts": "^5.0.1",
"recharts": "^2.12.7",
"redux": "^5.0.1",
"socket.io-client": "^4.7.5",
"stream": "^0.0.2",
"swr": "^2.2.5",
"tailwindcss": "^3.4.3",
@ -4009,6 +4010,11 @@
"@sinonjs/commons": "^1.7.0"
}
},
"node_modules/@socket.io/component-emitter": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="
},
"node_modules/@surma/rollup-plugin-off-main-thread": {
"version": "2.2.3",
"resolved": "https://registry.npmmirror.com/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz",
@ -5441,7 +5447,7 @@
},
"node_modules/@xterm/xterm": {
"version": "5.5.0",
"resolved": "https://registry.npmmirror.com/@xterm/xterm/-/xterm-5.5.0.tgz",
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz",
"integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A=="
},
"node_modules/@xtuc/ieee754": {
@ -7865,6 +7871,46 @@
"node": ">= 0.8"
}
},
"node_modules/engine.io-client": {
"version": "6.5.4",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.4.tgz",
"integrity": "sha512-GeZeeRjpD2qf49cZQ0Wvh/8NJNfeXkXXcoGh+F77oEAgo9gUHwT1fCRxSNU+YEEaysOJTnsFHmM5oAcPy4ntvQ==",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.17.1",
"xmlhttprequest-ssl": "~2.0.0"
}
},
"node_modules/engine.io-client/node_modules/ws": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/engine.io-parser": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz",
"integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/enhanced-resolve": {
"version": "5.16.0",
"resolved": "https://registry.npmmirror.com/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz",
@ -16477,6 +16523,32 @@
"node": ">=8"
}
},
"node_modules/socket.io-client": {
"version": "4.7.5",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.5.tgz",
"integrity": "sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.2",
"engine.io-client": "~6.5.2",
"socket.io-parser": "~4.2.4"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/socket.io-parser": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/sockjs": {
"version": "0.3.24",
"resolved": "https://registry.npmmirror.com/sockjs/-/sockjs-0.3.24.tgz",
@ -18912,6 +18984,14 @@
"resolved": "https://registry.npmmirror.com/xmlchars/-/xmlchars-2.2.0.tgz",
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="
},
"node_modules/xmlhttprequest-ssl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
"integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz",

View File

@ -32,6 +32,7 @@
"react-scripts": "^5.0.1",
"recharts": "^2.12.7",
"redux": "^5.0.1",
"socket.io-client": "^4.7.5",
"stream": "^0.0.2",
"swr": "^2.2.5",
"tailwindcss": "^3.4.3",

View File

@ -0,0 +1,89 @@
import { Terminal } from "@xterm/xterm";
import "@xterm/xterm/css/xterm.css";
import React, { useEffect, useState, useRef } from 'react';
import { useLocation } from 'react-router-dom';
import io, { Socket } from "socket.io-client";
function base64Decode(encoded: string): string {
return atob(encoded);
}
export default function WebSSH() {
const location = useLocation();
const terminalObj = useRef<HTMLDivElement | null>(null);
const [terminal, setTerminal] = useState<Terminal | null>(null);
const [socket, setSocket] = useState<Socket | null>(null);
const [ip, setIp] = useState("");
const [port, setPort] = useState("");
const [password, setPassword] = useState("");
useEffect(() => {
const params = new URLSearchParams(location.search);
const encodedIp = params.get("ip");
const encodedPort = params.get("port");
const encodedPassword = params.get("password");
if (encodedIp && encodedPort && encodedPassword) {
setIp(base64Decode(encodedIp));
setPort(base64Decode(encodedPort));
setPassword(base64Decode(encodedPassword));
}
}, [location]);
useEffect(() => {
if (terminal === null) {
setTerminal(new Terminal());
}
}, []);
useEffect(() => {
if (terminal !== null) {
terminal.open(terminalObj.current as HTMLDivElement);
const newSocket = io("ws://36.138.114.105:32087");
setSocket(newSocket);
terminal.onData((data) => {
newSocket.emit("input", data);
});
terminal.onSelectionChange(() => {
if (terminal.hasSelection()) {
navigator.clipboard.writeText(terminal.getSelection());
}
});
setInterval(() => {
newSocket.emit("heartbeat");
}, 1000);
newSocket.on("output", (data: string) => {
terminal.write(data);
});
return () => {
newSocket.disconnect();
};
}
}, [terminal]);
useEffect(() => {
if (socket && ip && port && password) {
socket.emit("connect_ssh", { ip, port, password });
}
}, [socket, ip, port, password]);
function pasteContent(event: React.MouseEvent) {
navigator.clipboard.readText().then((content) => {
socket?.emit("input", content);
});
event.preventDefault();
}
return (
<div>
<div ref={terminalObj} onContextMenu={pasteContent} style={{ height: "100vh", width: "100%" }}>
</div>
</div>
);
}

0
app-dm/src/pages/ListPages/Training/TrainingPage.jsx Executable file → Normal file
View File

6
app-dm/src/router/index.jsx Executable file → Normal file
View File

@ -23,11 +23,12 @@ import ClassID from '../TeacherPages/classlistpages/classId';
import ManageTest from '../TeacherPages/TestManage/ManageTest';
import SendTest from '../TeacherPages/TestManage/SendTest';
import Marking from '../TeacherPages/MarkingPages/Marking';
import SendTrain from '../TeacherPages/Trainmanage/SendTrain'
import TrainManage from '../TeacherPages/Trainmanage/Trainmanage'
import StudentLink from '../TeacherPages/MarkingPages/StudentLink';
import SendTrain from '../TeacherPages/SendTrain/SendTrain';
import TrainManage from '../TeacherPages/SendTest/trainmanage';
import Test from "../TeacherPages/teachermanagetest/Test.tsx"
// (exam)使
const MainLayout = ({ headerNav:HeaderNav }) => {
return (
@ -69,6 +70,7 @@ function App() {
<Route path="/classID/:key" element={<MainLayout headerNav={HeaderNavTeacher} />}>
<Route index element={<ClassID/>} />
</Route>
<Route path="test" element={<Test />}/>
{/* 页面使用单独的布局不包含HeaderNav和HeaderNavTeacher */}
<Route path="exam/:examId" element={<Exam />} />{/* 练习-考试页面 */}

View File

@ -4,7 +4,7 @@ module.exports = function(app) {
app.use(
'/api', // 如果请求路径匹配'/api',则进行代理
createProxyMiddleware({
target: 'http://127.0.0.1:5000', // 目标后端服务地址
target: 'http://36.138.114.105:32087', // 目标后端服务地址
changeOrigin: true,
})
);

View File

@ -20,13 +20,14 @@ build_image() {
# 推送镜像
push_image() {
local service_name=$1
read -p "是否要推送到《达梦启元云原生大数据平台》? [Y/N] " confirm
if [[ $confirm =~ ^[yY]([eE][sS])?$ ]]; then
echo "tag"
docker tag "${current_dir}-flask-app" 36.138.114.105:31000/cnsof50011836/flask-app
echo "pushing"
docker push 36.138.114.105:31000/cnsof50011836/flask-app
echo "pushed"
echo "${service_name} tag"
docker tag "${service_name}" 36.138.114.105:31000/cnsof50011836/$service_name
echo "${service_name} pushing"
docker push 36.138.114.105:31000/cnsof50011836/$service_name
echo "${service_name} pushed"
else
echo "用户选择不推送镜像"
fi
@ -43,7 +44,8 @@ main() {
build_image build-frontend
build_image flask-app
push_image
push_image flask-app
push_image base-dm
echo "脚本结束"
}

View File

@ -23,14 +23,11 @@ services:
flask-app:
build: ./python
restart: always
image: flask-app
depends_on:
- base-dm
ports:
- '8000:8000'
healthcheck:
test: ["CMD-SHELL", "curl --silent --fail localhost:8000/flask-health-check || exit 1"]
interval: 10s
timeout: 10s
retries: 3
- '8765:8765'
command: gunicorn -w 3 -t 60 -b 0.0.0.0:8000 app:app

View File

@ -6,8 +6,8 @@ RUN useradd -m -s /bin/bash dmdba \
WORKDIR /home/dmdba
# 传入安装包并解压
RUN wget https://download.dameng.com/eco/adapter/DM8/202405/dm8_20240408_x86_rh7_64_ent_8.1.3.140.zip
RUN unzip dm8_20240408_x86_rh7_64_ent_8.1.3.140.zip \
RUN wget https://download.dameng.com/eco/adapter/DM8/202405/dm8_20240408_x86_rh7_64_ent_8.1.3.140.zip \
&& unzip dm8_20240408_x86_rh7_64_ent_8.1.3.140.zip \
&& chown -R dmdba:dmdba /home/dmdba \
&& rm -f dm8_20240408_x86_rh7_64_ent_8.1.3.140.zip \
&& 7z x dm8_20240408_x86_rh7_64.iso \

View File

@ -9,6 +9,8 @@ RUN sed -i 's@//.*archive.ubuntu.com@//mirrors.ustc.edu.cn@g' /etc/apt/sources.l
&& apt-get install -y python3.10-venv libssl-dev sudo vim python3-pip ssh wget unzip p7zip* language-pack-zh-hans language-selector-common locales locales-all \
# 设置中文环境
&& apt install -y $(check-language-support) \
&& sed -i 's/^#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config \
&& sudo sed -i 's/^UsePAM yes/UsePAM no/' /etc/ssh/sshd_config \
&& echo "zh_CN.UTF-8 UTF-8" >> /etc/locale.gen \
&& sudo /usr/sbin/update-locale LANG=zh_CN.UTF-8 LC_ALL=zh_CN.UTF-8 \
&& locale-gen \

View File

@ -1,26 +1 @@
FROM ubuntu
ENV DEBIAN_FRONTEND=noninteractive
ENV LANG=zh_CN.UTF-8
ENV LC_ALL=zh_CN.UTF-8
# 基础软件安装
RUN sed -i 's@//.*archive.ubuntu.com@//mirrors.ustc.edu.cn@g' /etc/apt/sources.list \
&& apt-get -y update && apt-get -y upgrade \
&& apt-get install -y sudo vim python3-pip ssh wget unzip p7zip* language-pack-zh-hans language-selector-common locales locales-all \
# 设置中文环境
&& apt install -y $(check-language-support) \
&& echo "zh_CN.UTF-8 UTF-8" >> /etc/locale.gen \
&& sudo /usr/sbin/update-locale LANG=zh_CN.UTF-8 LC_ALL=zh_CN.UTF-8 \
&& locale-gen \
# 添加用户
&& useradd -m -s /bin/bash dmdba \
# 修改用户密码
&& echo "dmdba:123456" | chpasswd
# 传入安装包并解压
WORKDIR /home/dmdba
RUN wget https://download.dameng.com/eco/adapter/DM8/202405/dm8_20240408_x86_rh7_64_ent_8.1.3.140.zip
RUN unzip dm8_20240408_x86_rh7_64_ent_8.1.3.140.zip \
&& chown -R dmdba:dmdba /home/dmdba \
&& rm -f dm8_20240408_x86_rh7_64_ent_8.1.3.140.zip \
&& 7z x dm8_20240408_x86_rh7_64.iso \
&& rm -f dm8_20240408_x86_rh7_64.iso \
&& chmod +x DMInstall.bin
FROM base-dm

27
python/.devenv Normal file
View File

@ -0,0 +1,27 @@
SHELL=/bin/bash
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_SERVICE_PORT=443
HOSTNAME=flask-app-deployment-76d66d98b8-s6hxs
DMCCA_CANVAS_NAME=flask-app
PWD=/home/flask
LOGNAME=flask
TZ=Asia/Shanghai
HOME=/home/flask
LANG=zh_CN.UTF-8
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
VIRTUAL_ENV=/home/flask/venv
TERM=xterm
USER=flask
DMCCA_CANVAS_NAMESPACE=cnsof50011836-system
SHLVL=2
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
KUBERNETES_SERVICE_HOST=10.96.0.1
LC_ALL=zh_CN.UTF-8
KUBERNETES_PORT=tcp://10.96.0.1:443
KUBERNETES_PORT_443_TCP_PORT=443
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
MAIL=/var/mail/flask
DEBIAN_FRONTEND=noninteractive
OLDPWD=/root
_=/usr/bin/printenv

3
python/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.idea
__pycache__
public

View File

@ -4,10 +4,12 @@ FROM base
RUN pip install --upgrade pip
# 创建一个用户运行flask
RUN adduser flask && \
RUN useradd -m -s /bin/bash flask && \
chown -R flask:flask /home/flask && \
mkdir -p /var/log/flask-app && touch /var/log/flask-app/flask-app.err.log && touch /var/log/flask-app/flask-app.out.log && \
chown -R flask:flask /var/log/flask-app
chown -R flask:flask /var/log/flask-app && \
service ssh start && \
service ssh restart
WORKDIR /home/flask
USER flask
@ -24,10 +26,12 @@ RUN python3 -m venv "$VIRTUAL_ENV" && \
export FLASK_APP=app.py && \
pip3 install dmPython-2.5.5-cp310-cp310-linux_x86_64.whl && rm dmPython-2.5.5-cp310-cp310-linux_x86_64.whl && \
MAKEFLAGS="-j$(nproc)" pip install -r requirements.txt
COPY --from=build-dmpython /home/dmdba/build_artifacts/libdmdpi.so /usr/lib/
COPY --from=build-frontend /usr/src/app/build/ /home/flask/public/
# 暴露端口
EXPOSE 5000
EXPOSE 8000 22 8765
USER root
CMD ["gunicorn", "-w", "3", "-t", "60", "-b", "0.0.0.0:8000", "app:app"]

View File

@ -1,12 +1,29 @@
import subprocess
import threading
import time
import paramiko
from teacher_func import *
from student_func import *
from k8s_func import *
from flask import Flask, render_template, request, jsonify, send_from_directory, session
from flask_cors import CORS
from flask_socketio import SocketIO, emit, disconnect
import logging
logging.basicConfig(level=logging.DEBUG)
app = Flask(__name__, static_folder="public")
CORS(app, resources={r"/*": {"origins": "*"}})
CORS(app)
cors = CORS(app, resource={
r"/*":{
"origins":"*"
}
})
app.config['SECRET_KEY'] = '350625'
socketio = SocketIO(app, cors_allowed_origins="*")
clients = {}
@app.route('/api/student', methods=['POST']) # 检测历史是否登录过登录则直接进入index反之则进去login界面
def student(): # 判断是否已经登录
@ -249,6 +266,17 @@ def Find_details():
result=Find_details_Func(ID)
print(result)
return jsonify({'TestScore':result})
@app.route("/api/teacher/list_pods")
def teacher_list_pods():
return list_pods()
@app.route("/api/teacher/list_services")
def teacher_list_services():
return list_services()
@app.route("/api/teacher/create_pod")
def teacher_create_pod():
create_pod(0, "test")
@app.route('/')
@ -257,7 +285,56 @@ def catch_all(path = "index.html"):
return send_from_directory("public", path)
@socketio.on('connect_ssh')
def handle_connect_ssh(data):
ip = data['ip']
port = int(data['port'])
password = data['password']
client_id = request.sid
ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
ssh_client.connect(ip, port=port, username='your-username', password=password)
transport = ssh_client.get_transport()
channel = transport.open_session()
channel.get_pty()
channel.invoke_shell()
clients[client_id] = {
'ssh_client': ssh_client,
'channel': channel
}
socketio.start_background_task(target=read_from_channel, channel=channel, client_id=client_id)
except Exception as e:
emit('output', f'Connection failed: {str(e)}')
def read_from_channel(channel, client_id):
while True:
if channel.recv_ready():
data = channel.recv(1024).decode('utf-8')
socketio.emit('output', data, room=client_id)
@socketio.on('input')
def handle_input(data):
client_id = request.sid
if client_id in clients:
clients[client_id]['channel'].send(data)
@socketio.on('disconnect')
def handle_disconnect():
client_id = request.sid
if client_id in clients:
clients[client_id]['channel'].close()
clients[client_id]['ssh_client'].close()
del clients[client_id]
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
socketio.run(app, host='0.0.0.0', port=5000)

View File

@ -0,0 +1,19 @@
apiVersion: v1
kind: Pod
metadata:
name: NAME
namespace: ""
labels:
app: NAME
spec:
containers:
- name: base-dm
image: harbor.dameng.io/cnsof50011836/base-dm
imagePullPolicy: IfNotPresent
resources:
limits:
cpu: '1'
memory: 1Gi
requests:
cpu: '0.25'
memory: 0.5G

View File

85
python/k8s_func.py Normal file
View File

@ -0,0 +1,85 @@
import json
import yaml
from kubernetes import client, config
from flask import jsonify
# Load in-cluster config
config.load_incluster_config()
v1 = client.CoreV1Api()
namespace = open("/var/run/secrets/kubernetes.io/serviceaccount/namespace").read()
IP = "36.138.114.105"
def list_pods():
ret = v1.list_namespaced_pod(namespace, watch=False)
return {
"list": [{
"name": item.metadata.name.replace("database-dmserver-0", "student-2210502337-0-12"),
"ip": item.status.pod_ip
} for item in ret.items]
}
def list_services():
ret = v1.list_namespaced_service(namespace)
return {
"list": [
# "name": item.metadata.name.replace("database-dmserver-0", "student-2210502337-0-12"),
# "ip": item.spec.cluster_ip,
# "ports": [[port.to_dict() for port in item.spec.ports]],
#
item.to_dict()
# "node_port": item.spec.ports[0].node_port,
for item in ret.items]
}
def create_pod(type, name):
# 读取 pod yaml 文件
with open(f"assets/type{type}-pod.yaml") as f:
pod_manifest = yaml.safe_load(f)
pod_manifest["metadata"]["name"] = name
pod_manifest["metadata"]["labels"]["app"] = name
# 创建 Pod
resp = v1.create_namespaced_pod(
body=pod_manifest,
namespace=namespace
)
print(f"pod '{resp.metadata.name}' 创建成功")
# 创建 Service
service_manifest = {
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"name": name+ "-service"
},
"spec": {
"selector": {
"app": pod_manifest['metadata']['labels']['app']
},
"ports": [
{
"protocol": "TCP",
"port": 22,
"targetPort": 22
},
{
"protocol": "TCP",
"port": 5236,
"targetPort": 5236
}
]
}
}
resp = v1.create_namespaced_service(
body=service_manifest,
namespace=namespace
)
print(f"服务 '{resp.metadata.name}' 创建成功")
# 创建成功,再次查询,拿到端口
service = v1.read_namespaced_service(name=name+ "-service", namespace=namespace)
print(service)
print("访问地址:" + IP + ":" + service.spec.ports[0].node_port)
def delete_pod(name):
pass

View File

@ -2,3 +2,7 @@ Flask==2.3.0
Flask_Cors==4.0.0
pycryptodome==3.19.0
gunicorn
kubernetes
websockets
paramiko
flask-socketio

View File

@ -117,7 +117,7 @@ def get_lesson_func(id):
def student_succeed_func(ID):
cursor = db.cursor()
cursor.execute(f'SELECT * FROM STUDENT WHERE ID={ID}')
cursor.execute(f'SELECT * FROM STUDENT WHERE ID={ID}')# 报错
student_succeed = cursor.fetchall()[0]
student_succeeds = student_succeed[0], student_succeed[1], student_succeed[3], student_succeed[4]
cursor.close()