├── backend ├── data │ ├── .gitkeep │ └── ssh-keys │ │ └── .gitkeep ├── nodemon.json ├── .gitignore ├── .env.example ├── package.json └── src │ ├── middleware │ └── auth.middleware.js │ ├── utils │ ├── encryption.js │ ├── sshKeyManager.js │ └── commands.js │ ├── config │ └── env.js │ ├── routes │ ├── auth.routes.js │ ├── monitoring.routes.js │ └── servers.routes.js │ ├── db │ └── index.js │ ├── services │ ├── ssh.service.v2.js │ ├── ssh.service.js │ └── storage.service.js │ └── server.js ├── frontend ├── .gitignore ├── public │ ├── icon-192.png.txt │ ├── icon-512.png.txt │ ├── icon.svg │ ├── manifest.json │ ├── create-icons.sh │ └── sw.js ├── postcss.config.js ├── src │ ├── lib │ │ └── utils.js │ ├── components │ │ ├── ui │ │ │ ├── skeleton.jsx │ │ │ ├── label.jsx │ │ │ ├── textarea.jsx │ │ │ ├── separator.jsx │ │ │ ├── input.jsx │ │ │ ├── switch.jsx │ │ │ ├── badge.jsx │ │ │ ├── avatar.jsx │ │ │ ├── tooltip.jsx │ │ │ ├── alert.jsx │ │ │ ├── card.jsx │ │ │ ├── button.jsx │ │ │ ├── dialog.jsx │ │ │ ├── sheet.jsx │ │ │ └── dropdown-menu.jsx │ │ ├── DiskMonitor.jsx │ │ ├── ProcessMonitor.jsx │ │ ├── NetworkMonitor.jsx │ │ ├── MemoryMonitor.jsx │ │ ├── ServerCard.jsx │ │ ├── SSHTerminal.jsx │ │ ├── CPUMonitor.jsx │ │ ├── ServerCardSimple.jsx │ │ ├── AddServerModal.jsx │ │ └── EditServerModal.jsx │ ├── main.jsx │ ├── hooks │ │ └── use-mobile.jsx │ ├── App.jsx │ ├── services │ │ ├── api.js │ │ └── socket.js │ ├── store │ │ └── useStore.js │ ├── pages │ │ ├── Login.jsx │ │ ├── Dashboard.jsx │ │ └── Settings.jsx │ └── index.css ├── .env.example ├── components.json ├── vite.config.js ├── package.json ├── index.html └── tailwind.config.js ├── .cursor └── mcp.json ├── package.json ├── .gitignore ├── INSTALLATION.md ├── PWA_SETUP.md └── README.md /backend/data/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/data/ssh-keys/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | dist-ssr 4 | *.local 5 | .DS_Store 6 | 7 | 8 | -------------------------------------------------------------------------------- /frontend/public/icon-192.png.txt: -------------------------------------------------------------------------------- 1 | This is a placeholder for icon-192.png 2 | Create a 192x192 PNG icon for your app 3 | -------------------------------------------------------------------------------- /frontend/public/icon-512.png.txt: -------------------------------------------------------------------------------- 1 | This is a placeholder for icon-512.png 2 | Create a 512x512 PNG icon for your app 3 | -------------------------------------------------------------------------------- /backend/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["src"], 3 | "ignore": ["data/*", "*.log"], 4 | "ext": "js,json" 5 | } 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | 8 | 9 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .env 3 | data/*.json 4 | data/*.tmp 5 | data/ssh-keys/*.pem 6 | data/ssh-keys/*.key 7 | *.log 8 | .DS_Store 9 | 10 | 11 | -------------------------------------------------------------------------------- /frontend/src/lib/utils.js: -------------------------------------------------------------------------------- 1 | import { clsx } from "clsx"; 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | -------------------------------------------------------------------------------- /.cursor/mcp.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "shadcn": { 4 | "command": "npx", 5 | "args": [ 6 | "shadcn@latest", 7 | "mcp" 8 | ] 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /frontend/.env.example: -------------------------------------------------------------------------------- 1 | # API endpoint served by the backend (include /api suffix) 2 | VITE_API_URL=https://your-backend-domain.com/api 3 | 4 | # Socket.io endpoint (omit /socket.io path, defaults to window.location.origin) 5 | VITE_SOCKET_URL=https://your-backend-domain.com 6 | -------------------------------------------------------------------------------- /frontend/src/components/ui/skeleton.jsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils" 2 | 3 | function Skeleton({ 4 | className, 5 | ...props 6 | }) { 7 | return ( 8 |
11 | ); 12 | } 13 | 14 | export { Skeleton } 15 | -------------------------------------------------------------------------------- /frontend/public/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | SM 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /frontend/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": false, 5 | "tsx": false, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "src/index.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "iconLibrary": "lucide", 14 | "aliases": { 15 | "components": "@/components", 16 | "utils": "@/lib/utils", 17 | "ui": "@/components/ui", 18 | "lib": "@/lib", 19 | "hooks": "@/hooks" 20 | }, 21 | "registries": {} 22 | } 23 | -------------------------------------------------------------------------------- /frontend/src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import App from './App'; 4 | import './index.css'; 5 | 6 | // Initialize theme before React renders 7 | const savedTheme = localStorage.getItem('theme') || 'dark'; 8 | if (savedTheme === 'dark') { 9 | document.documentElement.classList.add('dark'); 10 | } else { 11 | document.documentElement.classList.remove('dark'); 12 | } 13 | 14 | ReactDOM.createRoot(document.getElementById('root')).render( 15 | 16 | 17 | 18 | ); 19 | 20 | 21 | -------------------------------------------------------------------------------- /frontend/src/components/ui/label.jsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as LabelPrimitive from "@radix-ui/react-label" 3 | import { cva } from "class-variance-authority"; 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const labelVariants = cva( 8 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" 9 | ) 10 | 11 | const Label = React.forwardRef(({ className, ...props }, ref) => ( 12 | 13 | )) 14 | Label.displayName = LabelPrimitive.Root.displayName 15 | 16 | export { Label } 17 | -------------------------------------------------------------------------------- /frontend/src/hooks/use-mobile.jsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | const MOBILE_BREAKPOINT = 768 4 | 5 | export function useIsMobile() { 6 | const [isMobile, setIsMobile] = React.useState(undefined) 7 | 8 | React.useEffect(() => { 9 | const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`) 10 | const onChange = () => { 11 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) 12 | } 13 | mql.addEventListener("change", onChange) 14 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) 15 | return () => mql.removeEventListener("change", onChange); 16 | }, []) 17 | 18 | return !!isMobile 19 | } 20 | -------------------------------------------------------------------------------- /frontend/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import react from '@vitejs/plugin-react'; 3 | 4 | export default defineConfig({ 5 | plugins: [react()], 6 | server: { 7 | host: '0.0.0.0', // Listen on all network interfaces 8 | port: 5173, 9 | strictPort: true, 10 | hmr: { 11 | clientPort: 5173, 12 | host: 'server.hiddencyber.online' // HMR host 13 | }, 14 | // Disable host check for Cloudflare Tunnel 15 | allowedHosts: 'all', 16 | proxy: { 17 | '/api': { 18 | target: process.env.VITE_API_URL || 'http://localhost:3000', 19 | changeOrigin: true 20 | } 21 | } 22 | } 23 | }); 24 | 25 | -------------------------------------------------------------------------------- /frontend/src/components/ui/textarea.jsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Textarea = React.forwardRef(({ className, ...props }, ref) => { 6 | return ( 7 |