Initial Commit

This commit is contained in:
LukeGus
2024-12-04 21:04:46 -06:00
commit b6a3f881a8
36 changed files with 16998 additions and 0 deletions

35
frontend/index.html Normal file
View File

@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using @vitejs/plugin-react"
/>
<link rel="apple-touch-icon" href="/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="/manifest.json" />
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
<script type="module" src="/src/index.jsx"></script>
</html>

7985
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

58
frontend/package.json Normal file
View File

@@ -0,0 +1,58 @@
{
"name": "codespaces-react",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^14.4.3",
"express": "^4.21.1",
"guacamole-common-js": "^1.5.0",
"http-proxy-middleware": "^3.0.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"ssh2": "^1.16.0",
"web-vitals": "^3.1.0",
"ws": "^8.18.0",
"xterm": "^5.3.0",
"xterm-addon-fit": "^0.8.0"
},
"overrides": {
"@svgr/webpack": "^8.0.1",
"@adobe/css-tools": "^4.3.1",
"postcss": "^8.4.31"
},
"scripts": {
"start-vite": "cross-env BROWSER=none WDS_SOCKET_PORT=0 vite --port 8080",
"start-server": "node 'D:/Programming Projects/SSH-Project/ssh-project/backend/server.js'",
"start": "npm run start-vite && npm run start-server",
"build": "vite build",
"preview": "vite preview",
"test": "vitest"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@vitejs/plugin-react": "^4.1.1",
"cross-env": "^7.0.3",
"jsdom": "^22.1.0",
"vite": "^4.5.5",
"vitest": "^0.34.6"
}
}

90
frontend/src/App.css Normal file
View File

@@ -0,0 +1,90 @@
.app-container {
display: flex;
flex-direction: column;
min-height: 100vh;
background-color: #000000;
font-family: Arial, sans-serif;
}
.main-content {
display: flex;
height: 100vh;
width: 100%;
margin: 0;
}
.sidebar {
background-color: #1a1a1a;
padding: 35px 35px 35px 20px;
width: 250px;
color: white;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: space-between;
border-radius: 5px;
position: relative;
}
.sidebar.hidden {
display: none;
}
.sidebar h2 {
margin-bottom: 15px;
}
.sidebar input {
margin-bottom: 10px;
padding: 10px;
width: 100%;
background-color: rgb(108, 108, 108);
border: 1px solid #bdc3c7;
border-radius: 5px;
margin-left: -4px;
color: white;
}
.sidebar button {
padding: 10px;
background-color: rgb(108, 108, 108);
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
margin-left: -4px;
}
.sidebar button:disabled {
background-color: #141414;
}
.hide-sidebar-button {
position: fixed;
bottom: 10px;
right: 10px;
background-color: rgb(108, 108, 108);
color: white;
border: none;
border-radius: 5px;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 20px;
z-index: 10;
}
.terminal-container {
background-color: #1e1e1e;
border-radius: 0px;
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.2);
overflow: auto;
height: 100%;
flex-grow: 1;
overflow: hidden;
position: relative;
}

153
frontend/src/App.jsx Normal file
View File

@@ -0,0 +1,153 @@
import React, { useEffect, useRef, useState } from 'react';
import { Terminal } from 'xterm';
import 'xterm/css/xterm.css';
import { FitAddon } from 'xterm-addon-fit';
import './App.css';
const App = () => {
const terminalRef = useRef(null);
const terminal = useRef(null);
const fitAddon = useRef(null);
const socket = useRef(null);
const [host, setHost] = useState('');
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [isConnected, setIsConnected] = useState(false);
const [isSideBarHidden, setIsSideBarHidden] = useState(false);
useEffect(() => {
// Initialize the terminal and the fit addon
terminal.current = new Terminal({
cursorBlink: true,
theme: {
background: '#1e1e1e',
foreground: '#ffffff',
},
macOptionIsMeta: true,
allowProposedApi: true,
scrollback: 1000, // Allow scrollback so the terminal doesn't lose state
});
// Initialize and attach the fit addon to the terminal
fitAddon.current = new FitAddon();
terminal.current.loadAddon(fitAddon.current);
terminal.current.open(terminalRef.current);
// Resize terminal to fit the container initially
fitAddon.current.fit();
// Adjust terminal size on window resize
const resizeListener = () => {
fitAddon.current.fit();
};
window.addEventListener('resize', resizeListener);
// Monitor terminal data (activity)
terminal.current.onData((data) => {
if (socket.current && socket.current.readyState === WebSocket.OPEN) {
socket.current.send(data);
}
});
// Add specific resize call for certain programs like nano or vim
const resizeTerminalOnStart = () => {
// Resize immediately after starting vim/nano or other programs
fitAddon.current.fit();
terminal.current.clear();
};
terminal.current.onData((data) => {
if (data.includes('nano') || data.includes('vim')) {
// Trigger resize immediately when these programs start
resizeTerminalOnStart();
}
});
// Cleanup on component unmount
return () => {
terminal.current.dispose();
if (socket.current) {
socket.current.close();
}
window.removeEventListener('resize', resizeListener);
};
}, []);
const handleConnect = () => {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${protocol}//${window.location.host}/ws/`; // Use current host and "/ws/" endpoint
socket.current = new WebSocket(wsUrl);
socket.current.onopen = () => {
terminal.current.writeln(`Connected to WebSocket server at ${wsUrl}`);
socket.current.send(JSON.stringify({ host, username, password }));
setIsConnected(true);
};
socket.current.onmessage = (event) => {
terminal.current.write(event.data);
};
socket.current.onerror = (error) => {
terminal.current.writeln(`WebSocket error: ${error.message}`);
};
socket.current.onclose = () => {
terminal.current.writeln('Disconnected from WebSocket server.');
setIsConnected(false);
};
};
const handleInputChange = (event, setState) => {
setState(event.target.value);
};
const handleSideBarHiding = () => {
setIsSideBarHidden((prevState) => !prevState);
};
return (
<div className="app-container">
<div className="main-content">
<div className={`sidebar ${isSideBarHidden ? 'hidden' : ''}`}>
<h2>Connection Details</h2>
<input
type="text"
placeholder="Host"
value={host}
onChange={(e) => handleInputChange(e, setHost)}
/>
<input
type="text"
placeholder="Username"
value={username}
onChange={(e) => handleInputChange(e, setUsername)}
/>
<input
type="password"
placeholder="Password"
value={password}
onChange={(e) => handleInputChange(e, setPassword)}
/>
<button onClick={handleConnect} disabled={isConnected}>
{isConnected ? 'Connected' : 'Start Session'}
</button>
</div>
<div ref={terminalRef} className="terminal-container"></div>
</div>
{/* Hide button always positioned in the bottom-right corner */}
<button
className="hide-sidebar-button"
onClick={handleSideBarHiding}
>
{isSideBarHidden ? '+' : '-'}
</button>
</div>
);
};
export default App;

14
frontend/src/index.css Normal file
View File

@@ -0,0 +1,14 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
overflow: hidden; /* Prevent scrolling */
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

17
frontend/src/index.jsx Normal file
View File

@@ -0,0 +1,17 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

1
frontend/src/logo.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1,13 @@
const reportWebVitals = onPerfEntry => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;

View File

@@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';