From ae30fb56abb78441e71577cc57cb8ca7fc077e08 Mon Sep 17 00:00:00 2001 From: charleswrayjr Date: Fri, 5 Sep 2025 18:18:40 -0500 Subject: [PATCH] Adding vpn and docker elements as well as adding more git operations. --- .idea/vcs.xml | 6 ++ src/app/routes.js | 9 +- src/app/services/Docker/DockerConfig.js | 11 ++ src/app/services/Docker/DockerService.js | 60 +++++++++++ src/app/services/Git/GitConfig.js | 9 ++ src/app/services/Git/GitService.js | 72 +++++++++++++ src/app/services/VPN/VPNConfig.js | 8 ++ src/app/services/VPN/VPNService.js | 60 +++++++++++ src/app/services/index.js | 5 + src/app/views/Docker/Docker.jsx | 108 +++++++++++++++++++ src/app/views/Git.jsx | 93 ---------------- src/app/views/Git/Git.jsx | 125 +++++++++++++++++++++ src/app/views/{ => Login}/Login.jsx | 2 +- src/app/views/VPN/VPN.jsx | 131 +++++++++++++++++++++++ src/app/views/index.js | 8 ++ 15 files changed, 610 insertions(+), 97 deletions(-) create mode 100644 .idea/vcs.xml create mode 100644 src/app/services/Docker/DockerConfig.js create mode 100644 src/app/services/Docker/DockerService.js create mode 100644 src/app/services/Git/GitConfig.js create mode 100644 src/app/services/Git/GitService.js create mode 100644 src/app/services/VPN/VPNConfig.js create mode 100644 src/app/services/VPN/VPNService.js create mode 100644 src/app/services/index.js create mode 100644 src/app/views/Docker/Docker.jsx delete mode 100755 src/app/views/Git.jsx create mode 100755 src/app/views/Git/Git.jsx rename src/app/views/{ => Login}/Login.jsx (99%) create mode 100644 src/app/views/VPN/VPN.jsx create mode 100644 src/app/views/index.js diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/app/routes.js b/src/app/routes.js index 67634de..72b3019 100755 --- a/src/app/routes.js +++ b/src/app/routes.js @@ -1,8 +1,9 @@ import { Route, Routes } from 'react-router-dom'; -import Login from './views/Login'; -import NotFound from './views/404/Error404Page'; +import { Login, NotFound, Dashboard, Git, VPN, Docker } from './views'; +/*import NotFound from './views/404/Error404Page'; import Dashboard from './views/Dashboard/Dashboard'; -import Git from './views/Git'; +import Git from './views/Git/Git';*/ + export default function AppRoutes() { return ( @@ -10,6 +11,8 @@ export default function AppRoutes() { } /> } /> } /> + } /> + } /> } /> ); diff --git a/src/app/services/Docker/DockerConfig.js b/src/app/services/Docker/DockerConfig.js new file mode 100644 index 0000000..5c12e52 --- /dev/null +++ b/src/app/services/Docker/DockerConfig.js @@ -0,0 +1,11 @@ +const base = 'docker/'; +const DockerConfig = { + index: base, + start: `${base}start-container`, + stop: `${base}stop-container`, + images: `${base}images`, + containers: `${base}containers`, + down: `${base}compose-down`, +}; + +export default DockerConfig; \ No newline at end of file diff --git a/src/app/services/Docker/DockerService.js b/src/app/services/Docker/DockerService.js new file mode 100644 index 0000000..d1c8d7d --- /dev/null +++ b/src/app/services/Docker/DockerService.js @@ -0,0 +1,60 @@ +import axios from 'axios'; +import { base_url } from '../../../globals'; +import DockerConfig from './DockerConfig'; + +class DockerService { + + fetchContainers = () => { + return new Promise( async ( resolve, reject ) => { + try { + const response = await axios.get( `${ base_url }${ DockerConfig.containers }` ); + resolve( response.data.containers ); // Resolve the promise with the data + } catch (error) { + reject( `Error fetching containers: ${ error.message }` ); // Reject the promise with an error message + } + } ); + }; + + fetchImages = async () => { + return new Promise( async ( resolve, reject ) => { + try { + const response = await axios.get( `${ base_url }${ DockerConfig.images }` ); + resolve( response.data.images ); // Resolve the promise with the data + } catch (error) { + console.error( error ); + reject( `Error fetching images: ${ error.message }` ); // Reject the promise with an error message + } + } ); + }; + + containerAction = async (action, id, fetchContainers) => { + return new Promise( async ( resolve, reject ) => { + try { + const response = await axios.post( `${ base_url }docker/${ action }-container`, { + containerId:id, + } ); + fetchContainers(); + resolve(response.data.message); + } catch (error) { + reject(`Error: ${ error.response?.data?.error || `Failed to ${ action } container` }`); + } + }) + }; + + composeDown = async (composeFile, fetchContainers ) => { + return new Promise( async ( resolve, reject ) => { + try { + const response = await axios.post(`${base_url}${DockerConfig.down}`, { + composeFile, + }); + resolve(response.data.message); + fetchContainers(); + } catch (error) { + reject(`Error: ${error.response?.data?.error || 'Failed to stop compose services'}`); + } + }); + }; +} + +const instance = new DockerService(); +export default instance; \ No newline at end of file diff --git a/src/app/services/Git/GitConfig.js b/src/app/services/Git/GitConfig.js new file mode 100644 index 0000000..fb47d54 --- /dev/null +++ b/src/app/services/Git/GitConfig.js @@ -0,0 +1,9 @@ +let base = 'git/'; +const GitConfig = { + index: base, + createRepo: `${base}create-repo/`, + deleteRepo: `${base}`, + cloneRepo: `${base}clone-repo/`, +}; + +export default GitConfig; \ No newline at end of file diff --git a/src/app/services/Git/GitService.js b/src/app/services/Git/GitService.js new file mode 100644 index 0000000..a381d71 --- /dev/null +++ b/src/app/services/Git/GitService.js @@ -0,0 +1,72 @@ +import axios from 'axios'; +import { base_url } from '../../../globals'; +import GitConfig from './GitConfig'; + +class GitService { + + fetchRepos = async ( token ) => { + return new Promise( async ( resolve, reject ) => { + try { + const response = await axios.get(`${base_url}${GitConfig.index}`, { headers: { Authorization: `Bearer ${token}` } }); + resolve(response.data.repos); + } catch (error) { + reject(error.response?.data?.message || error.message); + } + }) + }; + + /** + * + * @param {Object} data + * @param {Function} fetchRepos + * @returns {Promise} + */ + createRepo = (data, fetchRepos) => { + return new Promise( async ( resolve, reject ) => { + const { repoName, repoType, repoUser } = data; + try { + const response = await axios.post(`${base_url}${GitConfig.createRepo}`, { + name: repoName, + type: repoType, + user: repoType === 'private' ? repoUser : undefined, + }); + resolve(response.data.message); + fetchRepos(); + } catch (error) { + reject(`Error: ${error.response?.data?.error || 'Failed to create repository'}`); + } + }); + }; + + deleteRepo = (name, fetchRepos) => { + return new Promise( async ( resolve ) => { + try { + const response = await axios.delete(`${base_url}${GitConfig.deleteRepo}/${name}`); + resolve(response.data.message); + fetchRepos(); + } catch (error) { + resolve(`Error: ${error.response?.data?.error || 'Failed to delete repository'}`); + } + }); + }; + + cloneRepo = (data) => { + return new Promise( async ( resolve, reject ) => { + const {deployRepoName, deployPath, deployUser } = data; + try { + const response = await axios.post(`${base_url}${GitConfig.cloneRepo}`, { + repoName: deployRepoName, + deployPath, + user: deployUser, + }); + resolve(response.data.message); + } catch (error) { + reject(`Error: ${error.response?.data?.error || 'Failed to clone repository'}`); + } + }); + }; + +} + +const instance = new GitService(); +export default instance; \ No newline at end of file diff --git a/src/app/services/VPN/VPNConfig.js b/src/app/services/VPN/VPNConfig.js new file mode 100644 index 0000000..39e1127 --- /dev/null +++ b/src/app/services/VPN/VPNConfig.js @@ -0,0 +1,8 @@ +const base = 'vpn/'; +const VPNConfig = { + create: `${base}create-client/`, + delete: `${base}revoke-client/`, + index: `${base}clients/` +}; + +export default VPNConfig; \ No newline at end of file diff --git a/src/app/services/VPN/VPNService.js b/src/app/services/VPN/VPNService.js new file mode 100644 index 0000000..f60f577 --- /dev/null +++ b/src/app/services/VPN/VPNService.js @@ -0,0 +1,60 @@ +import axios from 'axios'; +import { base_url } from '../../../globals'; +import VPNConfig from './VPNConfig'; + +class VPNService { + fetchClients = () => { + return new Promise( async ( resolve ) => { + try { + const response = await axios.get(`${base_url}${VPNConfig.index}`); + resolve(response.data.clients); + } catch (error) { + resolve(`Error: ${error.response?.data?.error || 'Failed to fetch clients'}`); + } + }); + }; + + createClient = ( data, fetchClients ) => { + return new Promise( async ( resolve ) => { + const { clientName, useStaticIp, staticIp } = data; + try { + const response = await axios.post(`${base_url}${VPNConfig.create}`, { + clientName, + staticIp: useStaticIp ? staticIp : undefined, + }); + fetchClients(); + if (response.data.ovpn) { + const element = document.createElement('a'); + const file = new Blob([response.data.ovpn], { type: 'text/plain' }); + element.href = URL.createObjectURL(file); + element.download = `${clientName}.ovpn`; + document.body.appendChild(element); + element.click(); + document.body.removeChild(element); + } + resolve(response.data.message); + } catch (error) { + resolve(`Error: ${error.response?.data?.error || 'Failed to create client'}`); + } + }) + }; + + revokeClient = ( data, fetchClients ) => { + return new Promise( async ( resolve ) => { + const { revokeClientName } = data; + try { + const response = await axios.post(`${base_url}${VPNConfig.delete}`, { + clientName: revokeClientName, + }); + fetchClients(); + resolve(response.data.message); + } catch (error) { + resolve(`Error: ${error.response?.data?.error || 'Failed to revoke client'}`); + } + }); + }; + +} + +const instance = new VPNService(); +export default instance; \ No newline at end of file diff --git a/src/app/services/index.js b/src/app/services/index.js new file mode 100644 index 0000000..9c81015 --- /dev/null +++ b/src/app/services/index.js @@ -0,0 +1,5 @@ +import DockerService from './Docker/DockerService'; +import GitService from './Git/GitService'; +import VPNService from './VPN/VPNService'; + +export { DockerService, GitService, VPNService }; \ No newline at end of file diff --git a/src/app/views/Docker/Docker.jsx b/src/app/views/Docker/Docker.jsx new file mode 100644 index 0000000..8836601 --- /dev/null +++ b/src/app/views/Docker/Docker.jsx @@ -0,0 +1,108 @@ +import React, { useState, useEffect } from 'react'; +import { Container, Typography, TextField, Button } from '@mui/material'; +import { MaterialReactTable } from 'material-react-table'; +import { DockerService } from '../../services'; + + +const Docker = () => { + const [containers, setContainers] = useState([]); + const [images, setImages] = useState([]); + const [composeFile, setComposeFile] = useState(''); + const [message, setMessage] = useState(''); + + const fetchContainers = async () => { + try { + const containersData = await DockerService.fetchContainers(); // Assuming you have a service to fetch containers + setContainers(containersData); + } catch (error) { + setMessage(error); + } + }; + + const fetchImages = async () => { + try { + const imagesData = await DockerService.fetchImages(); // Assuming you have a service to fetch images + setImages(imagesData); + } catch (error) { + setMessage(error); + } + }; + + useEffect( () => { + fetchImages().catch( e => setMessage( e ) ); + fetchContainers().catch( e => setMessage( e ) ); + }, [] ); + + const handleContainerAction = async (action, containerId) => { + await DockerService.containerAction(action, containerId); + await fetchContainers(); + }; + + const handleComposeSubmit = async (event) => { + event.preventDefault(); + setMessage( await DockerService.composeDown( composeFile ) ); + }; + + return( + + Docker Containers + ( + <> + + + + ), + }, + ]} + data={containers} + /> + + Docker Images + cell.getValue().join(', ') }, + { accessorKey: 'created', header: 'Created' }, + ]} + data={images} + /> + + Stop Docker Compose Services +
+ setComposeFile(e.target.value)} + fullWidth + /> + + + + {message && {message}} +
+ ) +} + +export default Docker; \ No newline at end of file diff --git a/src/app/views/Git.jsx b/src/app/views/Git.jsx deleted file mode 100755 index 5d7f69c..0000000 --- a/src/app/views/Git.jsx +++ /dev/null @@ -1,93 +0,0 @@ -import { useState } from 'react'; -import axios from 'axios'; -import { base_url } from '../../globals'; - -const Git = () => { - const [repoName, setRepoName] = useState(''); - const [repoType, setRepoType] = useState('global'); - const [user, setUser] = useState(''); - const [message, setMessage] = useState(''); - - const handleSubmit = async (e) => { - e.preventDefault(); - if (!repoName.match(/^[a-zA-Z0-9_-]+$/)) { - setMessage('Error: Repository name must contain only letters, numbers, hyphens, or underscores.'); - return; - } - if (repoType === 'private' && !user) { - setMessage('Error: Username required for private repository.'); - return; - } - - try { - const response = await axios.post(`${base_url}git/create-repo`, { - name: repoName, - type: repoType, - user: repoType === 'private' ? user : undefined, - }); - setMessage(response.data.message); - } catch (error) { - setMessage(`Error: ${error.response?.data?.error || 'Failed to create repository'}`); - } - }; - - return ( -
-
-

Git Repository Manager

-
-
- - setRepoName(e.target.value)} - className="w-full p-2 border rounded" - placeholder="e.g., my-project" - required - /> -
-
- - -
- {repoType === 'private' && ( -
- - setUser(e.target.value)} - className="w-full p-2 border rounded" - placeholder="e.g., gituser1" - required - /> -
- )} - -
- {message && ( -

- {message} -

- )} -
-
- ); -} - -export default Git; \ No newline at end of file diff --git a/src/app/views/Git/Git.jsx b/src/app/views/Git/Git.jsx new file mode 100755 index 0000000..8f15f2f --- /dev/null +++ b/src/app/views/Git/Git.jsx @@ -0,0 +1,125 @@ +import React, { useState, useEffect } from 'react'; + +import { Container, Typography, TextField, Button, Select, MenuItem } from '@mui/material'; +import { MaterialReactTable } from 'material-react-table'; +import { GitService } from '../../services'; + +const Git = () => { + const [repoName, setRepoName] = useState(''); + const [repoType, setRepoType] = useState('global'); + const [repoUser, setRepoUser] = useState(''); + const [message, setMessage] = useState(''); + const [repos, setRepos] = useState( [] ); + const [deployRepoName, setDeployRepoName] = useState(''); + const [deployPath, setDeployPath] = useState(''); + const [deployUser, setDeployUser] = useState(''); + + const fetchRepos = async () => { + await GitService.fetchRepos().then( res => setRepos( res ) ).catch( err => setMessage( err ) ); + }; + + useEffect(() => { + fetchRepos().catch( e => setMessage( e ) ); + }, [] ); + + const handleRepoSubmit = async (e) => { + e.preventDefault(); + setMessage( await GitService.createRepo({repoName, repoType, repoUser}, fetchRepos ) ); + }; + + const handleDeleteRepo = async (name) => { + setMessage( await GitService.deleteRepo( name, fetchRepos ) ); + }; + + const handleCloneSubmit = async (e) => { + e.preventDefault(); + setMessage( await GitService.cloneRepo( { deployRepoName, deployPath, deployUser } ) ); + }; + + return( + + Create Git Repository +
+ setRepoName(e.target.value)} + fullWidth + /> + + {repoType === 'private' && ( + setRepoUser(e.target.value)} + fullWidth + /> + )} + + + + Clone Repository Locally +
+ setDeployRepoName(e.target.value)} + fullWidth + /> + setDeployPath(e.target.value)} + fullWidth + /> + setDeployUser(e.target.value)} + fullWidth + /> + + + + Repositories + ( + + ), + }, + ]} + data={repos} + enableRowActions + renderRowActions={({ row }) => ( + + )} + /> + + {message && {message}} +
+ ); +}; + +export default Git; \ No newline at end of file diff --git a/src/app/views/Login.jsx b/src/app/views/Login/Login.jsx similarity index 99% rename from src/app/views/Login.jsx rename to src/app/views/Login/Login.jsx index 8dd74f2..b3974fc 100755 --- a/src/app/views/Login.jsx +++ b/src/app/views/Login/Login.jsx @@ -14,7 +14,7 @@ import { InputAdornment, IconButton } from '@mui/material'; import * as yup from 'yup'; -import _ from '../../@lodash'; +import _ from '@lodash'; // import jwtService from '../../auth/services/jwtService'; import { Visibility, VisibilityOff } from '@mui/icons-material'; import { useNavigate } from 'react-router-dom'; diff --git a/src/app/views/VPN/VPN.jsx b/src/app/views/VPN/VPN.jsx new file mode 100644 index 0000000..e923618 --- /dev/null +++ b/src/app/views/VPN/VPN.jsx @@ -0,0 +1,131 @@ +import React, { useState, useEffect } from 'react'; +import { Container, Typography, TextField, Button, Select, MenuItem } from '@mui/material'; +import { MaterialReactTable } from 'material-react-table'; +/*import axios from 'axios';*/ +import 'tailwindcss/tailwind.css'; +import { VPNService } from '../../services'; + +const VPN = () => { + /*const [token, setToken] = useState(''); + const [username, setUsername] = useState(''); + const [password, setPassword] = useState('');*/ + const [clientName, setClientName] = useState(''); + const [staticIp, setStaticIp] = useState(''); + const [useStaticIp, setUseStaticIp] = useState(false); + const [revokeClientName, setRevokeClientName] = useState(''); + const [clients, setClients] = useState([]); + const [message, setMessage] = useState(''); + + const fetchClients = async () => { + await VPNService.fetchClients().then( res => setClients( res ) ).catch( err => setMessage( err ) ); + }; + + useEffect( () => { + fetchClients().catch( e => setMessage( e ) ); + }, [] ); + + /*const handleLogin = async (e) => { + e.preventDefault(); + try { + const response = await axios.post('http://localhost:3000/login', { username, password }); + setToken(response.data.token); + setMessage('Logged in successfully'); + } catch (error) { + setMessage(`Error: ${error.response?.data?.error || 'Login failed'}`); + } + };*/ + + const handleCreateClientSubmit = async (e) => { + e.preventDefault(); + setMessage( await VPNService.createClient({ clientName, useStaticIp, staticIp }, fetchClients ) ); + }; + + const handleRevokeClientSubmit = async (e) => { + e.preventDefault(); + setMessage( await VPNService.revokeClient(revokeClientName) ); + }; + + return ( + + Create OpenVPN Client +
+ setClientName(e.target.value)} + fullWidth + /> + + {useStaticIp && ( + setStaticIp(e.target.value)} + fullWidth + placeholder="e.g., 10.8.0.x" + /> + )} + + + + Revoke OpenVPN Client +
+ setRevokeClientName(e.target.value)} + fullWidth + /> + + + + Connected OpenVPN Clients + + + {message && {message}} +
+ ); + + /*return ( + + PHS Admin Dashboard + {!token ? ( +
+ setUsername(e.target.value)} + fullWidth + /> + setPassword(e.target.value)} + fullWidth + /> + + + ) : ( + <> + + )} +
+ );*/ +}; + +export default VPN; \ No newline at end of file diff --git a/src/app/views/index.js b/src/app/views/index.js new file mode 100644 index 0000000..8a3426a --- /dev/null +++ b/src/app/views/index.js @@ -0,0 +1,8 @@ +import NotFound from './404/Error404Page'; +import Dashboard from './Dashboard/Dashboard'; +import Login from './Login/Login'; +import Docker from './Docker/Docker'; +import VPN from './VPN/VPN'; +import Git from './Git/Git'; + +export {NotFound, Dashboard, Login, Docker, VPN, Git }; \ No newline at end of file -- 2.43.0