Skip to content

Commit

Permalink
Merge pull request #27 from gnmyt/features/pve-integration
Browse files Browse the repository at this point in the history
Proxmox Integration
  • Loading branch information
gnmyt authored Sep 1, 2024
2 parents c89222b + bd2b28f commit cff9a75
Show file tree
Hide file tree
Showing 37 changed files with 1,095 additions and 83 deletions.
31 changes: 29 additions & 2 deletions client/src/common/contexts/ServerContext.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createContext, useContext, useEffect, useState } from "react";
import { UserContext } from "@/common/contexts/UserContext.jsx";
import { getRequest } from "@/common/utils/RequestUtil.js";
import { getRequest, postRequest } from "@/common/utils/RequestUtil.js";

export const ServerContext = createContext({});

Expand All @@ -11,6 +11,8 @@ export const ServerProvider = ({ children }) => {

const loadServers = async () => {
try {
postRequest("pve-servers/refresh").then(() => {});

getRequest("/servers/list").then((response) => {
setServers(response);
});
Expand All @@ -19,6 +21,31 @@ export const ServerProvider = ({ children }) => {
}
}

const getPVEServerById = (serverId, entries) => {
if (!entries) entries = servers;
for (const server of entries) {
if (server.id === parseInt(serverId) && server.type === "pve-server") {
return server;
} else if (server.type === "folder") {
const result = getPVEServerById(serverId, server.entries);
if (result) {
return result;
}
}
}
return null;
}

const getPVEContainerById = (serverId, containerId) => {
const pveServer = getPVEServerById(serverId);
if (!pveServer) return null;

for (const container of pveServer.entries) {
if (container.id === parseInt(containerId)) {
return container;
}
}
}

const getServerById = (serverId, entries) => {
if (!entries) entries = servers;
Expand Down Expand Up @@ -50,7 +77,7 @@ export const ServerProvider = ({ children }) => {
}, [user]);

return (
<ServerContext.Provider value={{servers, loadServers, getServerById}}>
<ServerContext.Provider value={{servers, loadServers, getServerById, getPVEServerById, getPVEContainerById}}>
{children}
</ServerContext.Provider>
)
Expand Down
3 changes: 3 additions & 0 deletions client/src/common/styles/_colors.sass
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ $error-opacity: rgba(164, 71, 71, 0.25)
$success: #29C16A
$success-opacity: rgba(41, 193, 106, 0.25)

$warning: #DC5600
$warning-opacity: rgba(220, 86, 0, 0.25)

$white: #FFFFFF

$subtext: #B7B7B7
48 changes: 36 additions & 12 deletions client/src/pages/Servers/Servers.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,36 @@ import WelcomeImage from "@/common/img/welcome.png";
import { GITHUB_URL } from "@/App.jsx";
import ServerDialog from "@/pages/Servers/components/ServerDialog";
import ViewContainer from "@/pages/Servers/components/ViewContainer";
import ProxmoxDialog from "@/pages/Servers/components/ProxmoxDialog/index.js";

export const Servers = () => {

const [serverDialogOpen, setServerDialogOpen] = useState(false);
const [proxmoxDialogOpen, setProxmoxDialogOpen] = useState(false);

const [currentFolderId, setCurrentFolderId] = useState(null);
const [editServerId, setEditServerId] = useState(null);
const {user} = useContext(UserContext);
const { user } = useContext(UserContext);

const [activeSessions, setActiveSessions] = useState([]);
const [activeSessionId, setActiveSessionId] = useState(null);

const connectToServer = (server, identity) => {
const sessionId = "session-" + (Math.random().toString(36).substring(2, 15))
setActiveSessions(activeSessions => [...activeSessions, {server, identity, id: sessionId}]);
const sessionId = "session-" + (Math.random().toString(36).substring(2, 15));
setActiveSessions(activeSessions => [...activeSessions, { server, identity, id: sessionId }]);

setActiveSessionId(sessionId);
}
};

const connectToPVEServer = (serverId, containerId) => {
const sessionId = "session-" + (Math.random().toString(36).substring(2, 15));
setActiveSessions(activeSessions => [...activeSessions, {
server: serverId.toString().replace("pve-", ""),
containerId: containerId.toString().split("-")[containerId.toString().split("-").length - 1], id: sessionId,
}]);

setActiveSessionId(sessionId);
};

const disconnectFromServer = (sessionId) => {
setActiveSessions(activeSessions => {
Expand All @@ -37,30 +50,41 @@ export const Servers = () => {

return newSessions;
});
}
};

const closeDialog = () => {
setServerDialogOpen(false);
setCurrentFolderId(null);
setEditServerId(null);
};

const closePVEDialog = () => {
setProxmoxDialogOpen(false);
setCurrentFolderId(null);
setEditServerId(null);
}

return (
<div className="server-page">
<ServerDialog open={serverDialogOpen} onClose={closeDialog} currentFolderId={currentFolderId}
editServerId={editServerId}/>
editServerId={editServerId} />
<ProxmoxDialog open={proxmoxDialogOpen} onClose={closePVEDialog}
currentFolderId={currentFolderId}
editServerId={editServerId} />
<ServerList setServerDialogOpen={() => setServerDialogOpen(true)} connectToServer={connectToServer}
setCurrentFolderId={setCurrentFolderId} setEditServerId={setEditServerId}/>
{activeSessions.length === 0 &&<div className="welcome-area">
connectToPVEServer={connectToPVEServer} setProxmoxDialogOpen={() => setProxmoxDialogOpen(true)}
setCurrentFolderId={setCurrentFolderId} setEditServerId={setEditServerId} />
{activeSessions.length === 0 && <div className="welcome-area">
<div className="area-left">
<h1>Hi, <span>{user?.firstName || "User"} {user?.lastName || "name"}</span>!</h1>
<p>Welcome to Nexterm. The open-source server manager for SSH, VNC and RDP.</p>
<Button text="Star on GitHub" onClick={() => window.open(GITHUB_URL, "_blank")} />
</div>
<img src={WelcomeImage} alt="Welcome" />
</div>}
{activeSessions.length > 0 && <ViewContainer activeSessions={activeSessions} disconnectFromServer={disconnectFromServer}
activeSessionId={activeSessionId} setActiveSessionId={setActiveSessionId} />}
{activeSessions.length > 0 &&
<ViewContainer activeSessions={activeSessions} disconnectFromServer={disconnectFromServer}
activeSessionId={activeSessionId} setActiveSessionId={setActiveSessionId} />}
</div>
)
}
);
};
101 changes: 101 additions & 0 deletions client/src/pages/Servers/components/ProxmoxDialog/ProxmoxDialog.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { DialogProvider } from "@/common/components/Dialog";
import "./styles.sass";
import { useContext, useEffect, useState } from "react";
import { ServerContext } from "@/common/contexts/ServerContext.jsx";
import IconInput from "@/common/components/IconInput";
import { mdiAccountCircleOutline, mdiFormTextbox, mdiIp, mdiLockOutline } from "@mdi/js";
import Button from "@/common/components/Button";
import Input from "@/common/components/IconInput/index.js";
import { getRequest, patchRequest, postRequest, putRequest } from "@/common/utils/RequestUtil.js";

export const ProxmoxDialog = ({ open, onClose, currentFolderId, editServerId }) => {

const [name, setName] = useState("");
const [ip, setIp] = useState("");
const [port, setPort] = useState("8006");
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");

const create = () => {
putRequest("pve-servers", {
name, folderId: currentFolderId, ip, port, username, password,
}).then(async () => {
onClose();
loadServers();
await postRequest("pve-servers/refresh");
}).catch(err => console.error(err));
};

const edit = () => {
patchRequest(`pve-servers/${editServerId.split("-")[1]}`, {
name, ip, port, username, password: password === "********" ? undefined : password,
}).then(async () => {
onClose();
await postRequest("pve-servers/refresh");
loadServers();
}).catch(err => console.error(err));
}

useEffect(() => {
if (editServerId) {
getRequest(`pve-servers/${editServerId.split("-")[1]}`).then(server => {
setName(server.name);
setIp(server.ip);
setPort(server.port);
setUsername(server.username);
setPassword("********");
}).catch(err => console.error(err));
} else {
setName("");
setIp("");
setPort("8006");
setUsername("");
setPassword("");
}
}, [editServerId, open]);

const { loadServers } = useContext(ServerContext);

return (
<DialogProvider open={open} onClose={onClose}>
<div className="proxmox-dialog">
<h2>{editServerId ? "Edit" : "Import"} Proxmox VE</h2>
<div className="form-group">
<label htmlFor="name">Name</label>
<IconInput icon={mdiFormTextbox} value={name} setValue={setName} placeholder="Name" id="name" />
</div>

<div className="ip-row">

<div className="form-group">
<label htmlFor="ip">Server-IP</label>
<Input icon={mdiIp} type="text" placeholder="Server-IP" id="ip"
autoComplete="off" value={ip} setValue={setIp} />
</div>

<div className="form-group">
<label htmlFor="port">Port</label>
<input type="text" placeholder="Port" value={port}
onChange={(event) => setPort(event.target.value)}
className="small-input" id="port" />
</div>
</div>

<div className="form-group">
<label htmlFor="username">Username</label>
<IconInput icon={mdiAccountCircleOutline} value={username} setValue={setUsername}
placeholder="Username (e.g. root@pam)" id="username" />
</div>

<div className="form-group">
<label htmlFor="password">Password</label>
<IconInput icon={mdiLockOutline} value={password} setValue={setPassword} placeholder="Password"
type="password" id="password" />
</div>

<Button onClick={editServerId ? edit : create} text={editServerId ? "Edit" : "Import"} />

</div>
</DialogProvider>
);
};
1 change: 1 addition & 0 deletions client/src/pages/Servers/components/ProxmoxDialog/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {ProxmoxDialog as default} from "./ProxmoxDialog.jsx";
44 changes: 44 additions & 0 deletions client/src/pages/Servers/components/ProxmoxDialog/styles.sass
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
@import "@/common/styles/colors"

.proxmox-dialog
display: flex
flex-direction: column
gap: 1rem
width: 20rem

h2, p
margin: 0

p
font-weight: 600
font-size: 0.9rem

.error
border: 1px solid $error

.form-group
display: flex
flex-direction: column
gap: 0.3rem

label
color: $subtext
font-weight: 600

.ip-row
display: flex
gap: 1rem

.small-input
padding: 0.8rem
border: 1px solid $gray
width: 3rem
background-color: $dark-gray
color: $light-gray
border-radius: 0.5rem
font-size: 14pt
outline: none

&:focus
border: 1px solid $primary
background-color: $gray
17 changes: 11 additions & 6 deletions client/src/pages/Servers/components/ServerList/ServerList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,12 @@ const applyRenameState = (folderId) => (entry) => {
return { ...entry, entries: entry.entries.map(applyRenameState(folderId)) };
}
return entry;
}
};

export const ServerList = ({setServerDialogOpen, setCurrentFolderId, setEditServerId, connectToServer}) => {
export const ServerList = ({
setServerDialogOpen, setCurrentFolderId, setProxmoxDialogOpen,
setEditServerId, connectToServer, connectToPVEServer,
}) => {
const { servers } = useContext(ServerContext);
const [search, setSearch] = useState("");
const [contextMenuPosition, setContextMenuPosition] = useState(null);
Expand Down Expand Up @@ -83,7 +86,8 @@ export const ServerList = ({setServerDialogOpen, setCurrentFolderId, setEditServ
{servers && servers.length >= 1 && (
<div className="servers" onContextMenu={handleContextMenu}>
<ServerEntries entries={renameStateServers} setRenameStateId={setRenameStateId}
nestedLevel={0} connectToServer={connectToServer} />
nestedLevel={0} connectToServer={connectToServer}
connectToPVEServer={connectToPVEServer} />
</div>
)}
{servers && servers.length === 0 && (
Expand All @@ -94,9 +98,10 @@ export const ServerList = ({setServerDialogOpen, setCurrentFolderId, setEditServ
)}
{contextMenuPosition && (
<ContextMenu position={contextMenuPosition} type={contextClickedType} id={contextClickedId}
setRenameStateId={setRenameStateId} setServerDialogOpen={setServerDialogOpen}
setCurrentFolderId={setCurrentFolderId} setEditServerId={setEditServerId}
connectToServer={connectToServer} />
setRenameStateId={setRenameStateId} setServerDialogOpen={setServerDialogOpen}
setCurrentFolderId={setCurrentFolderId} setEditServerId={setEditServerId}
setProxmoxDialogOpen={setProxmoxDialogOpen}
connectToServer={connectToServer} connectToPVEServer={connectToPVEServer} />
)}
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import FolderObject from "@/pages/Servers/components/ServerList/components/Folde
import ServerEntries from "./ServerEntries.jsx";
import { useState } from "react";

const CollapsibleFolder = ({ id, name, entries, nestedLevel, renameState, setRenameStateId, connectToServer }) => {
const CollapsibleFolder = ({ id, name, entries, nestedLevel, renameState, setRenameStateId, connectToServer, connectToPVEServer }) => {
const [isOpen, setIsOpen] = useState(true);
const toggleFolder = () => setIsOpen(!isOpen);

Expand All @@ -12,7 +12,7 @@ const CollapsibleFolder = ({ id, name, entries, nestedLevel, renameState, setRen
isOpen={isOpen} renameState={renameState} setRenameStateId={setRenameStateId} />
{isOpen && (
<ServerEntries entries={entries} nestedLevel={nestedLevel + 1} setRenameStateId={setRenameStateId}
connectToServer={connectToServer} />
connectToServer={connectToServer} connectToPVEServer={connectToPVEServer} />
)}
</>
);
Expand Down
Loading

0 comments on commit cff9a75

Please sign in to comment.