UI Overhaul (Switch to tailwindcss and MUI)
This commit is contained in:
2325
package-lock.json
generated
2325
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
15
package.json
15
package.json
@@ -10,9 +10,20 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@emotion/react": "^11.14.0",
|
||||||
|
"@emotion/styled": "^11.14.0",
|
||||||
|
"@fontsource/inter": "^5.1.1",
|
||||||
|
"@mui/joy": "^5.0.0-beta.51",
|
||||||
|
"@tailwindcss/vite": "^4.0.8",
|
||||||
|
"@tiptap/extension-link": "^2.11.5",
|
||||||
|
"@tiptap/pm": "^2.11.5",
|
||||||
|
"@tiptap/react": "^2.11.5",
|
||||||
|
"@tiptap/starter-kit": "^2.11.5",
|
||||||
"@xterm/addon-fit": "^0.10.0",
|
"@xterm/addon-fit": "^0.10.0",
|
||||||
"@xterm/xterm": "^5.5.0",
|
"@xterm/xterm": "^5.5.0",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
|
"dayjs": "^1.11.13",
|
||||||
|
"embla-carousel-react": "^7.1.0",
|
||||||
"express": "^4.21.2",
|
"express": "^4.21.2",
|
||||||
"is-stream": "^4.0.1",
|
"is-stream": "^4.0.1",
|
||||||
"make-dir": "^5.0.0",
|
"make-dir": "^5.0.0",
|
||||||
@@ -20,12 +31,14 @@
|
|||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
|
"recharts": "^2.15.1",
|
||||||
"sb-promise-queue": "^2.1.1",
|
"sb-promise-queue": "^2.1.1",
|
||||||
"sb-scandir": "^3.1.0",
|
"sb-scandir": "^3.1.0",
|
||||||
"shell-escape": "^0.2.0",
|
"shell-escape": "^0.2.0",
|
||||||
"socket.io": "^4.8.1",
|
"socket.io": "^4.8.1",
|
||||||
"socket.io-client": "^4.8.1",
|
"socket.io-client": "^4.8.1",
|
||||||
"ssh2": "^1.16.0"
|
"ssh2": "^1.16.0",
|
||||||
|
"tailwindcss": "^4.0.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.17.0",
|
"@eslint/js": "^9.17.0",
|
||||||
|
|||||||
125
src/AddHostModal.jsx
Normal file
125
src/AddHostModal.jsx
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { CssVarsProvider } from '@mui/joy/styles';
|
||||||
|
import { Modal, Button, FormControl, FormLabel, Input, Stack, DialogTitle, DialogContent, ModalDialog } from '@mui/joy';
|
||||||
|
import theme from './theme';
|
||||||
|
|
||||||
|
const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidden }) => {
|
||||||
|
return (
|
||||||
|
<CssVarsProvider theme={theme}>
|
||||||
|
<Modal open={!isHidden} onClose={() => setIsAddHostHidden(true)}>
|
||||||
|
<ModalDialog
|
||||||
|
sx={{
|
||||||
|
backgroundColor: theme.palette.neutral[700],
|
||||||
|
borderColor: theme.palette.neutral[100],
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
padding: 3,
|
||||||
|
borderRadius: 10,
|
||||||
|
}}>
|
||||||
|
<DialogTitle>Add Host</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<form
|
||||||
|
onSubmit={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
handleAddHost();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Stack spacing={2}>
|
||||||
|
<FormControl>
|
||||||
|
<FormLabel>Host Name</FormLabel>
|
||||||
|
<Input
|
||||||
|
value={form.name}
|
||||||
|
onChange={(e) => setForm({ ...form, name: e.target.value })}
|
||||||
|
required={false}
|
||||||
|
sx={{
|
||||||
|
backgroundColor: theme.palette.neutral[500],
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl>
|
||||||
|
<FormLabel>Host IP</FormLabel>
|
||||||
|
<Input
|
||||||
|
value={form.ip}
|
||||||
|
onChange={(e) => setForm({ ...form, ip: e.target.value })}
|
||||||
|
required
|
||||||
|
sx={{
|
||||||
|
backgroundColor: theme.palette.neutral[500],
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl>
|
||||||
|
<FormLabel>Host User</FormLabel>
|
||||||
|
<Input
|
||||||
|
value={form.user}
|
||||||
|
onChange={(e) => setForm({ ...form, user: e.target.value })}
|
||||||
|
required
|
||||||
|
sx={{
|
||||||
|
backgroundColor: theme.palette.neutral[500],
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl>
|
||||||
|
<FormLabel>Host Password</FormLabel>
|
||||||
|
<Input
|
||||||
|
type="password"
|
||||||
|
value={form.password}
|
||||||
|
onChange={(e) => setForm({ ...form, password: e.target.value })}
|
||||||
|
required
|
||||||
|
sx={{
|
||||||
|
backgroundColor: theme.palette.neutral[500],
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl>
|
||||||
|
<FormLabel>Host Port</FormLabel>
|
||||||
|
<Input
|
||||||
|
value={form.port}
|
||||||
|
onChange={(e) => setForm({ ...form, port: e.target.value })}
|
||||||
|
min={1}
|
||||||
|
max={65535}
|
||||||
|
required
|
||||||
|
error={form.port < 1 || form.port > 65535 ? "Port must be between 1 and 65535" : ""}
|
||||||
|
sx={{
|
||||||
|
backgroundColor: theme.palette.neutral[500],
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
sx={{
|
||||||
|
backgroundColor: theme.palette.neutral[500],
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.palette.neutral[900],
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Add Host
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</form>
|
||||||
|
</DialogContent>
|
||||||
|
</ModalDialog>
|
||||||
|
</Modal>
|
||||||
|
</CssVarsProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
AddHostModal.propTypes = {
|
||||||
|
isHidden: PropTypes.bool.isRequired,
|
||||||
|
form: PropTypes.shape({
|
||||||
|
name: PropTypes.string,
|
||||||
|
ip: PropTypes.string.isRequired,
|
||||||
|
user: PropTypes.string.isRequired,
|
||||||
|
password: PropTypes.string.isRequired,
|
||||||
|
port: PropTypes.number.isRequired,
|
||||||
|
}).isRequired,
|
||||||
|
setForm: PropTypes.func.isRequired,
|
||||||
|
handleAddHost: PropTypes.func.isRequired,
|
||||||
|
setIsAddHostHidden: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddHostModal;
|
||||||
165
src/App.css
165
src/App.css
@@ -1,165 +0,0 @@
|
|||||||
#root {
|
|
||||||
max-width: 1280px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 2rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.5em;
|
|
||||||
position: fixed;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
height: 100vh;
|
|
||||||
padding: 2em;
|
|
||||||
width: 10em;
|
|
||||||
background-color: #323232;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.topbar {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
position: fixed;
|
|
||||||
align-items: center;
|
|
||||||
padding: 8px;
|
|
||||||
background-color: #323232;
|
|
||||||
top: 0;
|
|
||||||
left: 14em;
|
|
||||||
right: 0;
|
|
||||||
height: 48px;
|
|
||||||
overflow-x: auto;
|
|
||||||
gap: 8px;
|
|
||||||
border-bottom: 1px solid #404040;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
background: #404040;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 4px;
|
|
||||||
flex-shrink: 0; /* Prevent tabs from shrinking */
|
|
||||||
text-align: center;
|
|
||||||
vertical-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-item button {
|
|
||||||
padding: 6px 12px;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
color: #fff;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-item button:hover {
|
|
||||||
background: #1a1a1a;
|
|
||||||
border: white 1px solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-close {
|
|
||||||
padding: 2px 6px !important;
|
|
||||||
border-radius: 20%;
|
|
||||||
text-align: center;
|
|
||||||
vertical-align: center;
|
|
||||||
font-weight: bold;
|
|
||||||
line-height: 1;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-close:hover {
|
|
||||||
background: #1a1a1a !important;
|
|
||||||
border: white 1px solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.active-tab {
|
|
||||||
background: #1a1a1a !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminal-tab {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
display: none;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminal-tab.active {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-host {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
position: fixed;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
width: 200px;
|
|
||||||
height: 375px;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
gap: 0.5em;
|
|
||||||
padding: 1em;
|
|
||||||
z-index: 1;
|
|
||||||
background-color: #323232;
|
|
||||||
box-shadow: #1a1a1a 0 0 1em;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-host-close {
|
|
||||||
position: absolute;
|
|
||||||
top: -7px;
|
|
||||||
right: 5px;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
cursor: pointer;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 12px;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-host h2 {
|
|
||||||
margin: 0.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-host input {
|
|
||||||
margin-top: 0.1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-host button {
|
|
||||||
margin-top: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-host.hidden {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminal-wrapper {
|
|
||||||
position: fixed;
|
|
||||||
top: 64px;
|
|
||||||
left: 14em;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-color: #1c1c1c;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminal-container {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
border: none;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
140
src/App.jsx
140
src/App.jsx
@@ -1,13 +1,22 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import "./App.css";
|
|
||||||
import { NewTerminal } from "./Terminal.jsx";
|
import { NewTerminal } from "./Terminal.jsx";
|
||||||
|
import AddHostModal from './AddHostModal.jsx';
|
||||||
|
import {Button, ButtonGroup} from '@mui/joy';
|
||||||
|
import { CssVarsProvider } from '@mui/joy';
|
||||||
|
import theme from './theme';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [isAddHostHidden, setIsAddHostHidden] = useState(true);
|
const [isAddHostHidden, setIsAddHostHidden] = useState(true);
|
||||||
const [terminals, setTerminals] = useState([]);
|
const [terminals, setTerminals] = useState([]);
|
||||||
const [activeTab, setActiveTab] = useState(null);
|
const [activeTab, setActiveTab] = useState(null);
|
||||||
const [nextId, setNextId] = useState(1);
|
const [nextId, setNextId] = useState(1);
|
||||||
const [form, setForm] = useState({ name: "", ip: "", user: "", password: "", port: "22" });
|
const [form, setForm] = useState({
|
||||||
|
name: "",
|
||||||
|
ip: "",
|
||||||
|
user: "",
|
||||||
|
password: "",
|
||||||
|
port: 22
|
||||||
|
});
|
||||||
|
|
||||||
const handleAddHost = () => {
|
const handleAddHost = () => {
|
||||||
if (form.ip && form.user && form.password && form.port) {
|
if (form.ip && form.user && form.password && form.port) {
|
||||||
@@ -18,14 +27,14 @@ function App() {
|
|||||||
ip: form.ip,
|
ip: form.ip,
|
||||||
user: form.user,
|
user: form.user,
|
||||||
password: form.password,
|
password: form.password,
|
||||||
port: form.port,
|
port: Number(form.port),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
setTerminals([...terminals, newTerminal]);
|
setTerminals([...terminals, newTerminal]);
|
||||||
setActiveTab(nextId);
|
setActiveTab(nextId);
|
||||||
setNextId(nextId + 1);
|
setNextId(nextId + 1);
|
||||||
setIsAddHostHidden(true);
|
setIsAddHostHidden(true);
|
||||||
setForm({ name: "", ip: "", user: "", password: "", port: "22" });
|
setForm({ name: "", ip: "", user: "", password: "", port: 22 });
|
||||||
} else {
|
} else {
|
||||||
alert("Please fill out all fields.");
|
alert("Please fill out all fields.");
|
||||||
}
|
}
|
||||||
@@ -40,72 +49,93 @@ function App() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<CssVarsProvider theme={theme}>
|
||||||
<div className="sidebar">
|
<div className="flex h-screen bg-neutral-900 overflow-hidden">
|
||||||
<h2>Termix</h2>
|
{/* Sidebar */}
|
||||||
<button onClick={() => setIsAddHostHidden(!isAddHostHidden)}>Create Host</button>
|
<div className="w-64 bg-neutral-800 text-white p-6 flex flex-col justify-between fixed left-0 top-0 bottom-0">
|
||||||
|
<div className="flex flex-col items-center">
|
||||||
|
<h2 className="text-2xl font-bold mb-8">Termix</h2>
|
||||||
|
<Button
|
||||||
|
onClick={() => setIsAddHostHidden(false)}
|
||||||
|
sx={{
|
||||||
|
backgroundColor: theme.palette.neutral[500],
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.palette.neutral[900],
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Create Host
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="topbar">
|
</div>
|
||||||
{terminals.map((terminal) => (
|
|
||||||
<div key={terminal.id} className="tab-item">
|
{/* Main Content Area */}
|
||||||
<button
|
<div className="flex-1 flex flex-col ml-64 overflow-hidden">
|
||||||
|
{/* Topbar */}
|
||||||
|
<div className="bg-neutral-800 text-white p-4 flex justify-between items-center space-x-2 overflow-x-auto whitespace-nowrap min-h-[64px]">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{terminals.map((terminal, index) => (
|
||||||
|
<div key={terminal.id} className="flex items-center gap-2">
|
||||||
|
{/* Tab Button Group */}
|
||||||
|
<ButtonGroup>
|
||||||
|
<Button
|
||||||
onClick={() => setActiveTab(terminal.id)}
|
onClick={() => setActiveTab(terminal.id)}
|
||||||
className={activeTab === terminal.id ? "active-tab" : ""}
|
sx={{
|
||||||
|
backgroundColor: terminal.id === activeTab ? theme.palette.neutral[500] : theme.palette.neutral[900],
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.palette.neutral[300],
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{terminal.title}
|
{terminal.title}
|
||||||
</button>
|
</Button>
|
||||||
<button className="tab-close" onClick={() => closeTab(terminal.id)}>×</button>
|
<Button
|
||||||
|
onClick={() => closeTab(terminal.id)}
|
||||||
|
sx={{
|
||||||
|
backgroundColor: theme.palette.neutral[700],
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.palette.neutral[300],
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</Button>
|
||||||
|
</ButtonGroup>
|
||||||
|
|
||||||
|
{/* Separator (except after the last tab) */}
|
||||||
|
{index !== terminals.length - 1 && (
|
||||||
|
<div className="w-px h-6 bg-gray-600"></div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="terminal-wrapper">
|
</div>
|
||||||
|
|
||||||
|
{/* Terminal Views */}
|
||||||
|
<div className="flex-1 relative pt-12 overflow-hidden">
|
||||||
{terminals.map((terminal) => (
|
{terminals.map((terminal) => (
|
||||||
<div
|
<div
|
||||||
key={terminal.id}
|
key={terminal.id}
|
||||||
className={`terminal-tab ${terminal.id === activeTab ? "active" : ""}`}
|
className={`absolute top-0 left-0 right-0 bottom-0 ${terminal.id === activeTab ? "block" : "hidden"}`}
|
||||||
>
|
>
|
||||||
{terminal.hostConfig && <NewTerminal hostConfig={terminal.hostConfig}/>}
|
<NewTerminal hostConfig={terminal.hostConfig} />
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className={`add-host ${isAddHostHidden ? "hidden" : ""}`}>
|
|
||||||
<h2>Add Host</h2>
|
|
||||||
<button onClick={() => setIsAddHostHidden(true)} className="add-host-close">
|
|
||||||
×
|
|
||||||
</button>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="Host Name"
|
|
||||||
value={form.name}
|
|
||||||
onChange={(e) => setForm({ ...form, name: e.target.value })}
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="Host IP"
|
|
||||||
value={form.ip}
|
|
||||||
onChange={(e) => setForm({ ...form, ip: e.target.value })}
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="Host User"
|
|
||||||
value={form.user}
|
|
||||||
onChange={(e) => setForm({ ...form, user: e.target.value })}
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
placeholder="Host Password"
|
|
||||||
value={form.password}
|
|
||||||
onChange={(e) => setForm({ ...form, password: e.target.value })}
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
placeholder="Host Port"
|
|
||||||
value={form.port}
|
|
||||||
onChange={(e) => setForm({ ...form, port: e.target.value })}
|
|
||||||
/>
|
|
||||||
<button onClick={handleAddHost}>Add</button>
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
|
||||||
|
{/* Add Host Modal */}
|
||||||
|
<AddHostModal
|
||||||
|
isHidden={isAddHostHidden}
|
||||||
|
form={form}
|
||||||
|
setForm={setForm}
|
||||||
|
handleAddHost={handleAddHost}
|
||||||
|
setIsAddHostHidden={setIsAddHostHidden}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</CssVarsProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export function NewTerminal({ hostConfig }) {
|
|||||||
const terminal = new Terminal({
|
const terminal = new Terminal({
|
||||||
cursorBlink: true,
|
cursorBlink: true,
|
||||||
theme: {
|
theme: {
|
||||||
background: "#1a1a1a",
|
background: "#0f0f0f",
|
||||||
foreground: "#ffffff",
|
foreground: "#ffffff",
|
||||||
cursor: "#ffffff",
|
cursor: "#ffffff",
|
||||||
},
|
},
|
||||||
@@ -33,7 +33,7 @@ export function NewTerminal({ hostConfig }) {
|
|||||||
// Open terminal in the container
|
// Open terminal in the container
|
||||||
terminal.open(terminalRef.current);
|
terminal.open(terminalRef.current);
|
||||||
|
|
||||||
// Resize function (Restoring your original logic)
|
// Resize function
|
||||||
const resizeTerminal = () => {
|
const resizeTerminal = () => {
|
||||||
const terminalContainer = terminalRef.current;
|
const terminalContainer = terminalRef.current;
|
||||||
const sidebarWidth = 14 * 16; // Sidebar width in pixels
|
const sidebarWidth = 14 * 16; // Sidebar width in pixels
|
||||||
@@ -68,9 +68,6 @@ export function NewTerminal({ hostConfig }) {
|
|||||||
// Write initial connection message
|
// Write initial connection message
|
||||||
terminal.write("\r\n*** Connecting to backend ***\r\n");
|
terminal.write("\r\n*** Connecting to backend ***\r\n");
|
||||||
|
|
||||||
// Create socket connection
|
|
||||||
//const isSecure = window.location.protocol === "https:";
|
|
||||||
//let ioUrl = `${isSecure ? "https" : "http"}://${window.location.hostname}:${window.location.port}/socket.io/`;
|
|
||||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||||
let ioUrl = `${protocol}//${window.location.hostname}:${window.location.port}/socket.io/`;
|
let ioUrl = `${protocol}//${window.location.hostname}:${window.location.port}/socket.io/`;
|
||||||
|
|
||||||
@@ -106,6 +103,11 @@ export function NewTerminal({ hostConfig }) {
|
|||||||
socket.emit("data", key);
|
socket.emit("data", key);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Handle socket errors
|
||||||
|
socket.on("connect_error", (err) => {
|
||||||
|
terminal.write(`\r\n*** Error: ${err.message} ***\r\n`);
|
||||||
|
});
|
||||||
|
|
||||||
// Cleanup on component unmount
|
// Cleanup on component unmount
|
||||||
return () => {
|
return () => {
|
||||||
terminal.dispose();
|
terminal.dispose();
|
||||||
@@ -117,13 +119,7 @@ export function NewTerminal({ hostConfig }) {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={terminalRef}
|
ref={terminalRef}
|
||||||
style={{
|
className="w-full h-full min-h-[400px] overflow-hidden text-left"
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
minHeight: "400px",
|
|
||||||
overflow: "hidden",
|
|
||||||
textAlign: "left",
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,79 +1,10 @@
|
|||||||
:root {
|
@import '@fontsource/inter';
|
||||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
@import "tailwindcss";
|
||||||
line-height: 1.5;
|
|
||||||
font-weight: 400;
|
|
||||||
|
|
||||||
color-scheme: light dark;
|
.tab-group::after {
|
||||||
color: rgba(255, 255, 255, 0.87);
|
content: '';
|
||||||
background-color: #242424;
|
width: 1px;
|
||||||
|
height: 24px;
|
||||||
font-synthesis: none;
|
background-color: #4a5568; /* gray-600 */
|
||||||
text-rendering: optimizeLegibility;
|
margin: 0 8px; /* Adjust spacing as needed */
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
font-weight: 500;
|
|
||||||
color: #646cff;
|
|
||||||
text-decoration: inherit;
|
|
||||||
}
|
|
||||||
a:hover {
|
|
||||||
color: #535bf2;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
display: flex;
|
|
||||||
place-items: center;
|
|
||||||
min-width: 320px;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 3.2em;
|
|
||||||
line-height: 1.1;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
border-radius: 8px;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
padding: 0.6em 1.2em;
|
|
||||||
font-size: 1em;
|
|
||||||
font-weight: 500;
|
|
||||||
font-family: inherit;
|
|
||||||
background-color: #1a1a1a;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: border-color 0.25s;
|
|
||||||
}
|
|
||||||
button:hover {
|
|
||||||
border-color: #ffffff;
|
|
||||||
}
|
|
||||||
button:focus,
|
|
||||||
button:focus-visible {
|
|
||||||
outline: 4px auto -webkit-focus-ring-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
|
||||||
border-radius: 8px;
|
|
||||||
border: 1px solid #1a1a1a;
|
|
||||||
padding: 0.6em 1.2em;
|
|
||||||
font-size: 1em;
|
|
||||||
font-weight: 500;
|
|
||||||
font-family: inherit;
|
|
||||||
background-color: #1a1a1a;
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: light) {
|
|
||||||
:root {
|
|
||||||
color: #213547;
|
|
||||||
background-color: #ffffff;
|
|
||||||
}
|
|
||||||
a:hover {
|
|
||||||
color: #747bff;
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
background-color: #f9f9f9;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
50
src/theme.js
Normal file
50
src/theme.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { extendTheme } from '@mui/joy/styles';
|
||||||
|
|
||||||
|
const theme = extendTheme({
|
||||||
|
colorSchemes: {
|
||||||
|
light: {
|
||||||
|
palette: {
|
||||||
|
neutral: {
|
||||||
|
50: '#f7f7f7',
|
||||||
|
100: '#e1e1e1',
|
||||||
|
200: '#c4c4c4',
|
||||||
|
300: '#a7a7a7',
|
||||||
|
400: '#8a8a8a',
|
||||||
|
500: '#6e6e6e',
|
||||||
|
600: '#555555',
|
||||||
|
700: '#3d3d3d',
|
||||||
|
800: '#262626',
|
||||||
|
900: '#0f0f0f',
|
||||||
|
},
|
||||||
|
background: {
|
||||||
|
default: '#212121', // Dark background to contrast white text
|
||||||
|
paper: '#333333', // Slightly lighter paper background for depth
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
primary: '#ffffff', // White text for readability
|
||||||
|
secondary: '#b0b0b0', // Light gray for secondary text
|
||||||
|
},
|
||||||
|
primary: {
|
||||||
|
main: '#ff4081', // Bright pink for the primary accent color
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
main: '#00bcd4', // A fresh cyan-blue for secondary accents
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
main: '#e53935', // Strong red for error
|
||||||
|
},
|
||||||
|
warning: {
|
||||||
|
main: '#ff9800', // Vibrant yellow-orange for warning
|
||||||
|
},
|
||||||
|
success: {
|
||||||
|
main: '#4caf50', // Fresh green for success
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
typography: {
|
||||||
|
fontFamily: 'Arial, sans-serif',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default theme;
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import react from '@vitejs/plugin-react'
|
import react from '@vitejs/plugin-react'
|
||||||
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react(), tailwindcss()],
|
||||||
})
|
})
|
||||||
Reference in New Issue
Block a user