├── .gitignore ├── LICENSE ├── README.md ├── config └── config.json ├── lumenone.js ├── modules └── README.md ├── package.json ├── public └── js │ ├── restart.js │ └── sidebar.js ├── src ├── api │ └── v.1 │ │ └── api.js ├── config │ └── config.js ├── db.js ├── middleware │ ├── auth-admin.js │ ├── auth.js │ ├── protect-sensitive-files.js │ └── rate-limiter.js ├── pages │ ├── admin │ │ ├── customers.js │ │ ├── customers │ │ │ └── create.js │ │ ├── customers_edit.js │ │ ├── information.js │ │ ├── settings.js │ │ ├── subscriptions.js │ │ └── subscriptions │ │ │ └── create.js │ ├── auth │ │ ├── login.js │ │ └── logout.js │ ├── load.js │ └── user │ │ ├── account.js │ │ ├── edit │ │ └── files.js │ │ ├── home.js │ │ ├── manage.js │ │ └── statistics │ │ ├── domains.js │ │ └── statistics.js ├── start-webserver.js ├── utils │ └── version-checker.js └── web │ ├── domain.js │ └── size-limit.js ├── storage ├── statistics.json └── volumes │ └── test.txt └── views ├── auth └── login.ejs ├── components ├── footer.ejs └── sidebar.ejs ├── error ├── 400.ejs ├── 403.ejs ├── 404.ejs └── 500.ejs ├── load.ejs └── web ├── account.ejs ├── admin ├── customers.ejs ├── customers │ └── create.ejs ├── customers_edit.ejs ├── information.ejs ├── settings.ejs ├── subscriptions.ejs └── subscriptions │ └── create.ejs ├── backup.ejs ├── edit └── files.ejs ├── list.ejs ├── manage.ejs └── statistics ├── domains.ejs └── statistics.ejs /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lumenone.db 3 | package-lock.json 4 | .vscode 5 | storage/volumes/ 6 | storage/statistics.json -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 LumenLabs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![image](https://github.com/user-attachments/assets/5222bd49-29c6-4346-8a37-296a2ecb6e8c) 2 | 3 | # LumenOne Pre-Alpha 4 | 5 | | :exclamation: **LumenOne is under development**: some features may be unstable or incomplete. Its use in a production environment is strongly discouraged at this time. | 6 | | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 7 | 8 | **LumenOne** is a free and open-source alternative to Plesk, designed to simplify web hosting management (websites, domains, databases, FTP, emails, etc.) through a modern, intuitive, and lightweight interface. Developed in **Node.js**, LumenOne aims to provide a performant and extensible solution for developers and system administrators. 9 | 10 | --- 11 | 12 | ## :sparkles: Key Features (some feature not available now) 13 | 14 | - :control_knobs: **Simple, responsive, and modern web interface** 15 | - :globe_with_meridians: **Domain & subdomain management** 16 | - :file_folder: **File management** (via SFTP/WebFTP) 17 | - :dolphin: **Database management** (SQLite) 18 | - :outbox_tray: **FTP account management** 19 | - 📧 **Email management** (optional) 20 | - :whale: **Docker integration** (optional for service isolation) 21 | - :closed_lock_with_key: **Let's Encrypt SSL certificates** 22 | - :jigsaw: **Module/extension system** for customization 23 | - :arrows_counterclockwise: **REST API** for automation and integration 24 | 25 | | ⚠️ LumenOne currently doesn't encrypt user passwords. This will be fixed in Alpha 1.0.0 or Bêta 1.0.0, but for now, just don't leak your "lumenone.db". | 26 | | ------------------------------------------------------------------------------------------------------------------------------------------------------- | 27 | 28 | --- 29 | 30 | ## :rocket: Installation 31 | 32 | ### Prerequisites 33 | 34 | - **Node.js** (version 18 or higher) 35 | - **npm** or **yarn** 36 | - **SQLite database** (default) 37 | - Linux or Windows (recommended: Linux) 38 | - **Nginx** 39 | 40 | ### Installation Steps 41 | 42 | 0. Go to : 43 | 44 | ```bash 45 | cd var/www/ 46 | ``` 47 | 48 | 1. Clone the repository: 49 | 50 | ```bash 51 | git clone https://github.com/lumenlabss/LumenOne.git 52 | cd LumenOne 53 | ``` 54 | 55 | 2. Install dependencies: 56 | 57 | ```bash 58 | npm install 59 | ``` 60 | 61 | 3. Configure the `config/config.json` file: 62 | 63 | ```json 64 | { 65 | "hostname": "localhost", 66 | "port": 3000, 67 | "name": "LumenOne", 68 | "version": "Pre-Alpha" 69 | } 70 | ``` 71 | 72 | 4. Start the server: 73 | 74 | ```bash 75 | node lumenone.js 76 | ``` 77 | 78 | 5. Access the web interface: 79 | 80 | ``` 81 | http://localhost:3000 82 | ``` 83 | 84 | 6. Nginx Config: 85 | 86 | ``` 87 | server { 88 | listen 80; 89 | server_name example.com; # Replace with your domain 90 | 91 | # Redirect all HTTP requests to your Node.js application 92 | location / { 93 | proxy_pass http://localhost:3000; # Replace with the port on which your Node.js app is listening 94 | proxy_http_version 1.1; 95 | proxy_set_header Upgrade $http_upgrade; 96 | proxy_set_header Connection 'upgrade'; 97 | proxy_set_header Host $host; 98 | proxy_cache_bypass $http_upgrade; 99 | } 100 | } 101 | ``` 102 | 103 | --- 104 | 105 | ## :page_facing_up: License 106 | 107 | LumenOne is distributed under the **MIT** license. You are free to use, modify, and distribute it. 108 | 109 | --- 110 | 111 | ## :handshake: Contributing 112 | 113 | Contributions are welcome! Here's how you can contribute: 114 | 115 | 1. Fork the project 116 | 2. Create a branch (`git checkout -b feature/feature-name`) 117 | 3. Commit your changes (`git commit -am 'Add a new feature'`) 118 | 4. Push your changes (`git push origin feature/feature-name`) 119 | 5. Open a Pull Request 120 | 121 | --- 122 | 123 | ## :white_check_mark: ToDo List 124 | 125 | Project completion : ⁓ 25% 126 | 127 | ### Core 128 | 129 | - [x] User authentication (login, logout, sessions) 130 | - [x] Role system for user (admin, user) 131 | - [ ] Role system for php (admin, user, and custom) 132 | - [ ] Dashboard for service management (web, FTP, databases) 133 | - [ ] User/action logs system (audit) 134 | - [ ] devloppement and production mode (production = no logs) 135 | 136 | ### Web 137 | 138 | - [x] Welcome interface with service summary 139 | - [x] Domain and subdomain management 140 | - [x] Integrated file manager (upload, delete, edit) 141 | - [ ] Interface for database management (create, delete, access) 142 | - [x] Add backend integration 143 | - [ ] Add stastisic (To be realized potentially by the community) 144 | 145 | ### Backend 146 | 147 | - [ ] REST API (users, domains, files, databases) 148 | - [ ] Security (rate limiting, JWT token management, CSRF protection) 149 | - [ ] Multi-server support for scalability 150 | - [x] Size limit system (normally works) 151 | - [x] Code reorganization into modules (`src`) 152 | - [ ] Create a Middlewares files (`src/middlewares/exemple.js`) 153 | - [ ] Optimize code (make it faster, more readable, more orderly, etc.) 154 | - [ ] Convert stastistic storage from .json to .db 155 | 156 | ### Users pages (frontend) 157 | 158 | - [x] Websites & Domains 159 | - [ ] Mail (To be realized potentially by the community) 160 | - [x] Backup 161 | - [x] Statistics (To be realized potentially by the community) 162 | - [ ] Users 163 | - [x] Account 164 | 165 | ### Admin pages (frontend) 166 | 167 | - [x] Customers 168 | - [ ] Service Plans 169 | - [x] Subscriptions 170 | - [x] Information 171 | - [ ] Settings 172 | 173 | ### Users pages (Backend) 174 | 175 | - [x] Websites & Domains 176 | - [ ] Mail (To be realized potentially by the community) 177 | - [ ] Backup 178 | - [ ] Statistics (To be realized potentially by the community) 179 | - [ ] Users 180 | - [x] Account 181 | 182 | ### Admin pages (Backend) 183 | 184 | - [x] Customers 185 | - [ ] Service Plans 186 | - [x] Subscriptions 187 | - [x] Information 188 | - [ ] Settings 189 | 190 | ### Modules 191 | 192 | - [ ] Let's Encrypt SSL certificates (automatic generation and renewal) 193 | - [ ] Integrated webmail (Rainloop, Roundcube) 194 | - [ ] Application installation (WordPress, Joomla, etc.) 195 | - [ ] Resource monitoring (CPU, RAM, storage) 196 | 197 | ### Bonus 198 | 199 | - [ ] Complete documentation (installation, API, development) 200 | - [ ] Mobile-friendly interface 201 | - [ ] Multiple theme (Light theme, Plesk theme, and other) 202 | - [ ] Notification system (emails, alerts) 203 | - [ ] Language system 204 | - [ ] Docker integration (optional for service isolation) 205 | - [x] Add `console.log("exemple.js loaded");` for all backend files 206 | - [ ] 2FA system 207 | - [ ] Recent logins 208 | 209 | --- 210 | 211 | ## :speech_balloon: Community 212 | 213 | Join the LumenLabs community to ask questions, report bugs, or propose ideas: 214 | 215 | - [GitHub Issues](https://github.com/lumenlabss/LumenOne/issues) 216 | - [Discord](https://discord.gg/ty92ffCYUC) 217 | 218 | --- 219 | 220 | ## :tada: Acknowledgments 221 | 222 | Thanks to all contributors and users who support the LumenOne project! 223 | 224 | 225 | ## Star History 226 | 227 | [![Star History Chart](https://api.star-history.com/svg?repos=lumenlabss/lumenone&type=Date)](https://www.star-history.com/#lumenlabss/lumenone&Date) -------------------------------------------------------------------------------- /config/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "hostname": "localhost", 3 | "port": 3000, 4 | "name": "LumenOne", 5 | "version": "Pre-Alpha", 6 | "session": { 7 | "secret": "secret-key", 8 | "resave": false, 9 | "saveUninitialized": false, 10 | "cookie": { 11 | "secure": false 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lumenone.js: -------------------------------------------------------------------------------- 1 | /* 2 | * ╳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╳ 3 | * LumenOne - Open Source Project by LumenLabs 4 | * 5 | * © 2025 LumenLabs. Licensed under the MIT License 6 | * ╳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╳ 7 | */ 8 | 9 | console.log("lumenone.js loaded"); // To confirm that the page has been loaded correctly 10 | const express = require("express"); 11 | const bodyParser = require("body-parser"); 12 | const session = require("express-session"); 13 | const path = require("path"); 14 | const fs = require("fs"); 15 | 16 | const config = require("./src/config/config.js"); 17 | 18 | // Import Routes 19 | const loginRoutes = require("./src/pages/auth/login.js"); 20 | const logoutRoutes = require("./src/pages/auth/logout.js"); 21 | const homeRoutes = require("./src/pages/user/home.js"); 22 | const customersRoutes = require("./src/pages/admin/customers.js"); 23 | const customersEditRoutes = require("./src/pages/admin/customers_edit.js"); 24 | const subscriptions_createRoute = require("./src/pages/admin/subscriptions/create.js"); 25 | const subscriptionsRoute = require("./src/pages/admin/subscriptions.js"); 26 | const manageRoute = require("./src/pages/user/manage.js"); 27 | const filesRoutes = require("./src/pages/user/edit/files.js"); 28 | const createuserRoutes = require("./src/pages/admin/customers/create.js"); 29 | const informationRoutes = require("./src/pages/admin/information.js"); 30 | const accountRoutes = require("./src/pages/user/account.js"); 31 | const loadRoutes = require("./src/pages/load.js"); 32 | const statisticsdomainsRoutes = require("./src/pages/user/statistics/domains.js"); 33 | const statisticsRoutes = require("./src/pages/user/statistics/statistics.js"); 34 | const settingsadminRoutes = require("./src/pages/admin/settings.js"); 35 | 36 | //const { authLimiter } = require("./src/middleware/rate-limiter.js"); 37 | 38 | // Application initialization 39 | const app = express(); 40 | 41 | app.use(express.json({ limit: "80mb" })); 42 | app.use(express.urlencoded({ limit: "80mb", extended: true })); 43 | 44 | // Global variables for EJS 45 | app.use((req, res, next) => { 46 | res.locals.appName = config.name; 47 | res.locals.appVersion = config.version; 48 | next(); 49 | }); 50 | 51 | // Middleware configuration 52 | app.use(bodyParser.urlencoded({ extended: true, limit: "10mb" })); 53 | app.use(bodyParser.json({ limit: "10mb" })); 54 | app.use( 55 | session({ 56 | secret: config.session.secret, // Use of the config.json secret key 57 | resave: config.session.resave, 58 | saveUninitialized: config.session.saveUninitialized, 59 | cookie: config.session.cookie, // Setting cookies from config.json 60 | }) 61 | ); 62 | 63 | // Rendering engine and static files configuration 64 | app.set("view engine", "ejs"); 65 | app.set("views", path.join(__dirname, "views")); 66 | app.use(express.static(path.join(__dirname, "public"))); 67 | 68 | // Registering routes 69 | app.use("/", loginRoutes); 70 | app.use("/", logoutRoutes); 71 | app.use("/", homeRoutes); 72 | app.use("/", customersRoutes); 73 | app.use("/", customersEditRoutes); 74 | app.use("/", subscriptions_createRoute); 75 | app.use("/", subscriptionsRoute); 76 | app.use("/", manageRoute); 77 | app.use("/", filesRoutes); 78 | app.use("/", createuserRoutes); 79 | app.use("/", informationRoutes); 80 | app.use("/", accountRoutes); 81 | app.use("/", loadRoutes); 82 | app.use("/", statisticsdomainsRoutes); 83 | app.use("/", statisticsRoutes); 84 | app.use("/", settingsadminRoutes); 85 | 86 | // Middleware to handle errors 87 | app.use((req, res, next) => { 88 | res.status(404).render("error/404.ejs"); 89 | }); 90 | app.use((req, res, next) => { 91 | res.status(500).render("error/500.ejs", { 92 | message: "Internal server error", 93 | }); 94 | }); 95 | app.use((req, res, next) => { 96 | res.status(403).render("error/403.ejs", { 97 | message: "Access denied.", 98 | }); 99 | }); 100 | app.use((req, res, next) => { 101 | res.status(400).render("error/400.ejs", { 102 | message: "Bad request.", 103 | }); 104 | }); 105 | 106 | // Starting the server 107 | const port = config.port || 3000; 108 | const hostname = config.hostname || "localhost"; 109 | 110 | app.listen(port, hostname, () => { 111 | console.log(`LumenOne successfully started: http://${hostname}:${port}`); 112 | }); 113 | -------------------------------------------------------------------------------- /modules/README.md: -------------------------------------------------------------------------------- 1 | This will be the location of community-created modules, currently non-functional. 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "axios": "^1.9.0", 4 | "bcrypt": "^6.0.0", 5 | "body-parser": "^2.2.0", 6 | "cookie-parser": "^1.4.7", 7 | "dockerode": "^4.0.5", 8 | "ejs": "^3.1.10", 9 | "express": "^5.1.0", 10 | "express-rate-limit": "^7.5.0", 11 | "express-session": "^1.18.1", 12 | "fs": "^0.0.1-security", 13 | "jsonfile": "^6.1.0", 14 | "path": "^0.12.7", 15 | "sqlite3": "^5.1.7", 16 | "systeminformation": "^5.25.11", 17 | "tailwindcss": "^4.1.3" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /public/js/restart.js: -------------------------------------------------------------------------------- 1 | function toggleMenu(uuid) { 2 | const menu = document.getElementById(`dropdownMenu-${uuid}`); 3 | if (menu.classList.contains("hidden")) { 4 | document.querySelectorAll('[id^="dropdownMenu-"]').forEach((el) => { 5 | el.classList.add("hidden"); 6 | }); 7 | menu.classList.remove("hidden"); 8 | } else { 9 | menu.classList.add("hidden"); 10 | } 11 | } 12 | 13 | function restartServer(uuid) { 14 | fetch(`/web/restart/${uuid}`, { 15 | method: "POST", 16 | }) 17 | .then((res) => res.json()) 18 | .then((data) => { 19 | if (data.success) { 20 | alert("Serveur redémarré !"); 21 | } else { 22 | alert("Erreur : " + (data.error || "Inconnue")); 23 | } 24 | }) 25 | .catch((err) => { 26 | alert("Server communication error."); 27 | console.error(err); 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /public/js/sidebar.js: -------------------------------------------------------------------------------- 1 | document.addEventListener("DOMContentLoaded", function () { 2 | const toggleButton = document.querySelector( 3 | "[data-drawer-toggle='separator-sidebar']" 4 | ); 5 | const sidebar = document.getElementById("separator-sidebar"); 6 | const body = document.body; 7 | 8 | toggleButton.addEventListener("click", function () { 9 | sidebar.classList.toggle("-translate-x-full"); 10 | }); 11 | 12 | body.addEventListener("click", function (event) { 13 | if ( 14 | !sidebar.contains(event.target) && 15 | !toggleButton.contains(event.target) 16 | ) { 17 | sidebar.classList.add("-translate-x-full"); 18 | } 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/api/v.1/api.js: -------------------------------------------------------------------------------- 1 | console.log("api/v.1/api.js loaded"); // To confirm that the file has been loaded correctly 2 | 3 | const express = require("express"); 4 | const router = express.Router(); 5 | const db = require("../../db.js"); 6 | const crypto = require("crypto"); 7 | 8 | // Utils 9 | function generateApiKey() { 10 | return crypto.randomBytes(32).toString("hex"); 11 | } 12 | 13 | function checkApiKey(req, res, next) { 14 | const apiKey = req.headers["x-api-key"]; 15 | if (!apiKey) return res.status(401).json({ error: "API key required." }); 16 | 17 | db.get("SELECT * FROM api_keys WHERE key = ?", [apiKey], (err, row) => { 18 | if (err) return res.status(500).json({ error: "Database error." }); 19 | if (!row) return res.status(403).json({ error: "Invalid API key." }); 20 | next(); 21 | }); 22 | } 23 | 24 | // Generate a new API key 25 | router.post("/api/application/generate-key", (req, res) => { 26 | const key = generateApiKey(); 27 | db.run( 28 | "INSERT INTO api_keys (key, created_at) VALUES (?, datetime('now'))", 29 | [key], 30 | function (err) { 31 | if (err) return res.status(500).json({ error: "Database error." }); 32 | res.json({ success: true, key }); 33 | } 34 | ); 35 | }); 36 | 37 | // Check if API key is valid 38 | router.get("/api/application/validate-key", checkApiKey, (req, res) => { 39 | res.json({ success: true, message: "API key is valid." }); 40 | }); 41 | 42 | // 👤 Get all users (secured route) 43 | router.get("/api/application/users", checkApiKey, (req, res) => { 44 | db.all("SELECT id, username, email FROM users", (err, rows) => { 45 | if (err) return res.status(500).json({ error: "Database error." }); 46 | res.json({ success: true, users: rows }); 47 | }); 48 | }); 49 | 50 | module.exports = router; 51 | -------------------------------------------------------------------------------- /src/config/config.js: -------------------------------------------------------------------------------- 1 | console.log("config/config.js loaded"); // To confirm that the page has been loaded correctly 2 | const fs = require("fs"); 3 | const path = require("path"); 4 | 5 | let config; 6 | 7 | try { 8 | const configData = fs.readFileSync( 9 | path.join(__dirname, "../../config/config.json"), 10 | "utf8" 11 | ); 12 | config = JSON.parse(configData); 13 | console.log("Configuration loaded:", config); 14 | } catch (err) { 15 | console.error("Error reading or parsing config.json:", err); 16 | process.exit(1); 17 | } 18 | 19 | module.exports = config; 20 | -------------------------------------------------------------------------------- /src/db.js: -------------------------------------------------------------------------------- 1 | console.log("db.js loaded"); // To confirm that the page has been loaded correctly 2 | const sqlite = require("sqlite3").verbose(); 3 | 4 | // Database initialization 5 | const db = new sqlite.Database("lumenone.db", (err) => { 6 | if (err) { 7 | console.error("Error while opening the database: " + err.message); 8 | } else { 9 | console.log("Connected to the database."); 10 | } 11 | }); 12 | 13 | // Creating the `users` table with a `rank` column and `created_at` 14 | db.run( 15 | `CREATE TABLE IF NOT EXISTS users ( 16 | id INTEGER PRIMARY KEY AUTOINCREMENT, 17 | username TEXT NOT NULL UNIQUE, 18 | password TEXT NOT NULL, 19 | rank TEXT DEFAULT 'default', -- By default, the rank is 'default' 20 | created_at TEXT DEFAULT (datetime('now')) -- Auto timestamp at creation 21 | )`, 22 | (err) => { 23 | if (err) { 24 | console.error("Error while creating the table: " + err.message); 25 | } else { 26 | console.log("Users table created or already exists."); 27 | 28 | // Inserting the admin user after the table is created 29 | db.run( 30 | `INSERT OR IGNORE INTO users (username, password, rank) VALUES (?, ?, ?)`, 31 | ["admin", "admin", "admin"], 32 | (err) => { 33 | if (err) { 34 | console.error( 35 | "Error while inserting the admin user: " + err.message 36 | ); 37 | } else { 38 | console.log("Admin user created or already exists."); 39 | } 40 | } 41 | ); 42 | 43 | db.run( 44 | `INSERT OR IGNORE INTO users (username, password) VALUES (?, ?)`, 45 | ["user", "123"], 46 | (err) => { 47 | if (err) { 48 | console.error( 49 | "Error while inserting a default user: " + err.message 50 | ); 51 | } else { 52 | console.log("Default user created or already exists."); 53 | } 54 | } 55 | ); 56 | } 57 | } 58 | ); 59 | 60 | db.run( 61 | `CREATE TABLE IF NOT EXISTS websites ( 62 | id INTEGER PRIMARY KEY AUTOINCREMENT, 63 | user_id INTEGER NOT NULL, 64 | uuid TEXT NOT NULL, 65 | name TEXT NOT NULL, 66 | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, 67 | port INTEGER NOT NULL, 68 | disk_limit INTEGER NOT NULL, 69 | FOREIGN KEY (user_id) REFERENCES users(id) 70 | )`, 71 | (err) => { 72 | if (err) { 73 | console.error("Error creating websites table:", err.message); 74 | } else { 75 | console.log("Websites table created or already exists."); 76 | } 77 | } 78 | ); 79 | 80 | module.exports = db; 81 | -------------------------------------------------------------------------------- /src/middleware/auth-admin.js: -------------------------------------------------------------------------------- 1 | console.log("middleware/auth-admin.js"); // To confirm that the page has been loaded correctly 2 | const db = require("../db.js"); 3 | 4 | // Middleware to check if the user is authenticated and has admin rank 5 | function isAuthenticated(req, res, next) { 6 | if (req.session && req.session.user) { 7 | const userId = req.session.user.id; 8 | 9 | db.get("SELECT rank FROM users WHERE id = ?", [userId], (err, row) => { 10 | if (err) { 11 | console.error("Error retrieving rank:", err.message); 12 | return res.status(500).render("error/500.ejs", { 13 | message: "Internal server error", 14 | }); 15 | } 16 | 17 | if (row && row.rank === "admin") { 18 | req.session.user.rank = row.rank; 19 | return next(); 20 | } 21 | 22 | return res.status(403).render("error/403.ejs", { 23 | message: "Access denied. Admins only.", 24 | }); 25 | }); 26 | } else { 27 | res.redirect("/"); 28 | } 29 | } 30 | 31 | module.exports = { 32 | isAuthenticated, 33 | }; 34 | -------------------------------------------------------------------------------- /src/middleware/auth.js: -------------------------------------------------------------------------------- 1 | console.log("middleware/auth.js"); // To confirm that the page has been loaded correctly 2 | const db = require("../db.js"); 3 | 4 | function isAuthenticated(req, res, next) { 5 | if (req.session && req.session.user) { 6 | return next(); 7 | } 8 | res.redirect("/"); 9 | } 10 | 11 | module.exports = { 12 | isAuthenticated, 13 | }; 14 | -------------------------------------------------------------------------------- /src/middleware/protect-sensitive-files.js: -------------------------------------------------------------------------------- 1 | console.log("protect-sensitive-files.js loaded"); 2 | const path = require("path"); 3 | const fs = require("fs"); 4 | 5 | const protectedFiles = ["php.ini"]; 6 | function protectSensitiveFiles(baseDir) { 7 | return (req, res, next) => { 8 | const fileName = path.basename(req.path); // "php.ini" 9 | const match = req.path.match(/\/web\/manage\/([^/]+)/); 10 | if (!match) { 11 | console.log("[MIDDLEWARE] No detected UUID, Skipping"); 12 | return next(); 13 | } 14 | 15 | const websiteUuid = match[1]; 16 | const requestedPath = path.join(baseDir, websiteUuid, fileName); 17 | 18 | console.log("[MIDDLEWARE] Checking file:", requestedPath); 19 | 20 | if (protectedFiles.includes(fileName)) { 21 | fs.stat(requestedPath, (err, stats) => { 22 | if (!err && stats.isFile()) { 23 | console.log("[MIDDLEWARE] BLOCKED:", requestedPath); 24 | return res 25 | .status(403) 26 | .send( 27 | "Error 403 - Access Denied. It is an error on your side, don't make an issue report on github." 28 | ); 29 | } 30 | next(); 31 | }); 32 | } else { 33 | next(); 34 | } 35 | }; 36 | } 37 | 38 | module.exports = { protectSensitiveFiles }; 39 | -------------------------------------------------------------------------------- /src/middleware/rate-limiter.js: -------------------------------------------------------------------------------- 1 | const rateLimit = require("express-rate-limit"); 2 | 3 | // Basic rate limiter for all routes 4 | const globalLimiter = rateLimit({ 5 | windowMs: 15 * 60 * 1000, 6 | max: 100, 7 | standardHeaders: true, 8 | legacyHeaders: false, 9 | message: { 10 | success: false, 11 | message: "Too many requests, please try again later", 12 | }, 13 | }); 14 | 15 | // Strict limiter for auth route(Login, Reditsr) 16 | const authLimiter = rateLimit({ 17 | windowMs: 15 * 60 * 1000, 18 | max: 5, 19 | handler: (req, res) => { 20 | // Render the login page with an error message 21 | res.status(429).render("auth/login.ejs", { 22 | error: "Too many requests, please try again later.", 23 | }); 24 | }, 25 | }); 26 | 27 | module.exports = { 28 | globalLimiter, 29 | authLimiter, 30 | }; 31 | -------------------------------------------------------------------------------- /src/pages/admin/customers.js: -------------------------------------------------------------------------------- 1 | console.log("pages/admin/customers.js loaded"); // To confirm that the page has been loaded correctly 2 | const express = require("express"); 3 | const db = require("../../db.js"); 4 | const router = express.Router(); 5 | const { isAuthenticated } = require("../../middleware/auth-admin.js"); 6 | 7 | // Route for the admin customers page 8 | router.get("/web/admin/customers", isAuthenticated, (req, res) => { 9 | db.all("SELECT id, username, rank FROM users", (err, rows) => { 10 | if (err) { 11 | console.error("Error fetching users: " + err.message); 12 | return res.status(500).send("Internal server error"); 13 | } 14 | 15 | // Render the customers page with the list of users 16 | res.render("web/admin/customers.ejs", { 17 | user: req.session.user, 18 | rank: req.session.user.rank, 19 | users: rows, 20 | }); 21 | }); 22 | }); 23 | 24 | // Route to delete a user 25 | router.get("/web/admin/customers/delete/:id", isAuthenticated, (req, res) => { 26 | const userId = req.params.id; 27 | 28 | // Prevent deletion of the admin account 29 | if (userId === "1") { 30 | return res.status(403).render("error/403.ejs", { 31 | message: "You cannot delete the admin account.", 32 | }); 33 | } 34 | 35 | db.run("DELETE FROM users WHERE id = ?", [userId], (err) => { 36 | if (err) { 37 | console.error("Error deleting user: " + err.message); 38 | return res.status(500).render("error/500.ejs", { 39 | message: "Internal server error", 40 | }); 41 | } 42 | 43 | console.log(`User with ID ${userId} deleted.`); // debugging 44 | res.redirect("/web/admin/customers"); 45 | }); 46 | }); 47 | 48 | router.get("/web/admin/customers/edit/:id", isAuthenticated, (req, res) => { 49 | const userId = req.params.id; 50 | db.get( 51 | "SELECT id, username, rank FROM users WHERE id = ?", 52 | [userId], 53 | (err, row) => { 54 | if (err) { 55 | console.error("Error fetching user: " + err.message); 56 | return res.status(500).render("error/500.ejs", { 57 | message: "Internal server error", 58 | }); 59 | } 60 | 61 | if (!row) { 62 | return res.status(404).render("error/404.ejs", { 63 | message: "User not found.", 64 | }); 65 | } 66 | 67 | // Render the edit user page with the user's details 68 | res.render("web/admin/customers_edit.ejs", { 69 | user: req.session.user, 70 | rank: req.session.user.rank, 71 | userToEdit: row, 72 | }); 73 | } 74 | ); 75 | }); 76 | 77 | module.exports = router; 78 | -------------------------------------------------------------------------------- /src/pages/admin/customers/create.js: -------------------------------------------------------------------------------- 1 | console.log("pages/admin/customers/create.js loaded"); // To confirm that the page has been loaded correctly 2 | const express = require("express"); 3 | const db = require("../../../db.js"); 4 | const router = express.Router(); 5 | const { isAuthenticated } = require("../../../middleware/auth-admin.js"); 6 | 7 | // Route GET : display registration form 8 | router.get("/web/admin/customers/create", isAuthenticated, (req, res) => { 9 | res.render("web/admin/customers/create.ejs", { 10 | user: req.session.user, 11 | rank: req.session.user.rank, 12 | }); 13 | }); 14 | 15 | // Route POST : register a new user in the database 16 | router.post("/web/admin/customers/create", isAuthenticated, (req, res) => { 17 | const { username, password, rank } = req.body; 18 | 19 | if (!username || !password || !rank) { 20 | return res.status(400).send("All fields are required."); 21 | } 22 | 23 | const sql = `INSERT INTO users (username, password, rank) VALUES (?, ?, ?)`; 24 | 25 | db.run(sql, [username, password, rank], function (err) { 26 | if (err) { 27 | console.error("User registration error :", err.message); 28 | return res.status(500).send("Server error during user creation."); 29 | } 30 | 31 | console.log("New user registered with ID :", this.lastID); 32 | res.redirect("/web/admin/customers/create"); 33 | }); 34 | }); 35 | 36 | module.exports = router; 37 | -------------------------------------------------------------------------------- /src/pages/admin/customers_edit.js: -------------------------------------------------------------------------------- 1 | console.log("pages/admin/customers_edit.js loaded"); // To confirm that the page has been loaded correctly 2 | const express = require("express"); 3 | const db = require("../../db.js"); 4 | const router = express.Router(); 5 | const { isAuthenticated } = require("../../middleware/auth-admin.js"); 6 | 7 | // Route to edit a user 8 | router.get("/web/admin/customers/edit/:id", isAuthenticated, (req, res) => { 9 | const userId = req.params.id; 10 | db.get( 11 | "SELECT id, username, rank FROM users WHERE id = ?", 12 | [userId], 13 | (err, row) => { 14 | if (err) { 15 | console.error("Error fetching user: " + err.message); 16 | return res.status(500).render("error/500.ejs", { 17 | message: "Internal server error", 18 | }); 19 | } 20 | 21 | if (!row) { 22 | return res.status(404).render("error/404.ejs", { 23 | message: "User not found.", 24 | }); 25 | } 26 | 27 | res.render("web/admin/customers_edit.ejs", { 28 | user: req.session.user, 29 | rank: req.session.user.rank, 30 | userToEdit: row, 31 | }); 32 | } 33 | ); 34 | }); 35 | 36 | // Route to update a user 37 | router.post("/web/admin/customers/edit/:id", isAuthenticated, (req, res) => { 38 | const userId = req.params.id; 39 | const { username, rank, password } = req.body; 40 | 41 | if (!username || !rank) { 42 | return res.status(400).render("error/400.ejs", { 43 | message: "Bad request. Username and rank are required.", 44 | }); 45 | } 46 | 47 | db.get("SELECT id FROM users WHERE id = ?", [userId], (err, row) => { 48 | if (err) { 49 | console.error("Error fetching user: " + err.message); 50 | return res.status(500).render("error/500.ejs", { 51 | message: "Internal server error", 52 | }); 53 | } 54 | 55 | if (!row) { 56 | return res.status(404).render("error/404.ejs", { 57 | message: "User not found.", 58 | }); 59 | } 60 | 61 | if (password && password.trim() !== "") { 62 | db.run( 63 | "UPDATE users SET username = ?, rank = ?, password = ? WHERE id = ?", 64 | [username, rank, password, userId], 65 | function (err) { 66 | if (err) { 67 | console.error("Error updating user with password: " + err.message); 68 | return res.status(500).render("error/500.ejs", { 69 | message: "Internal server error", 70 | }); 71 | } 72 | 73 | res.redirect("/web/admin/customers"); 74 | } 75 | ); 76 | } else { 77 | db.run( 78 | "UPDATE users SET username = ?, rank = ? WHERE id = ?", 79 | [username, rank, userId], 80 | function (err) { 81 | if (err) { 82 | console.error("Error updating user: " + err.message); 83 | return res.status(500).render("error/500.ejs", { 84 | message: "Internal server error", 85 | }); 86 | } 87 | 88 | res.redirect("/web/admin/customers"); 89 | } 90 | ); 91 | } 92 | }); 93 | }); 94 | 95 | module.exports = router; 96 | -------------------------------------------------------------------------------- /src/pages/admin/information.js: -------------------------------------------------------------------------------- 1 | console.log("pages/admin/information.js loaded"); // To confirm that the page has been loaded correctly 2 | const express = require("express"); 3 | const router = express.Router(); 4 | const db = require("../../db.js"); 5 | const os = require("os"); 6 | const si = require("systeminformation"); 7 | const getLumenOneOverviewData = require("../../utils/version-checker"); 8 | const { isAuthenticated } = require("../../middleware/auth-admin.js"); 9 | 10 | // Route to system information page 11 | router.get("/web/admin/information", isAuthenticated, async (req, res) => { 12 | try { 13 | const cpuData = await si.cpu(); 14 | const memData = await si.mem(); 15 | const memLayout = await si.memLayout(); 16 | const diskData = await si.diskLayout(); 17 | const osInfo = await si.osInfo(); 18 | const netData = await si.networkInterfaces(); 19 | const uptime = os.uptime(); 20 | const { localVersion, latestVersion, updateAvailable } = 21 | await getLumenOneOverviewData(); 22 | 23 | const uptimeDays = Math.floor(uptime / 86400); 24 | const uptimeHours = Math.floor((uptime % 86400) / 3600); 25 | const bootTime = new Date(Date.now() - uptime * 1000); 26 | 27 | const CPUfrequence = cpuData.speed + " GHz"; 28 | const CPUname = `${cpuData.manufacturer} ${cpuData.brand}`; 29 | const RAMtype = memLayout.length > 0 ? memLayout[0].type : "Unknown"; 30 | const RAMspeed = 31 | memLayout.length > 0 ? memLayout[0].speed + " MHz" : "Unknown"; 32 | 33 | const cpuCores = cpuData.cores; 34 | const cpuThreads = cpuData.logical; 35 | 36 | // Storage detection 37 | let storageSize = "Unknown"; 38 | let storageMainType = "Unknown"; // HDD / SSD 39 | let storageInterface = "Unknown"; // SATA / NVMe / etc. 40 | 41 | if (diskData.length > 0) { 42 | const disk = diskData[0]; 43 | storageSize = (disk.size / 1024 / 1024 / 1024).toFixed(0) + " GB"; 44 | 45 | if (disk.type === "SSD") { 46 | storageMainType = "SSD"; 47 | } else if (disk.type === "HD") { 48 | storageMainType = "HDD"; 49 | } 50 | 51 | storageInterface = disk.interfaceType || "Unknown"; 52 | } 53 | 54 | const sql = ` 55 | SELECT websites.*, users.username 56 | FROM websites 57 | JOIN users ON websites.user_id = users.id 58 | `; 59 | 60 | const allWebsites = await new Promise((resolve, reject) => { 61 | db.all(sql, (err, rows) => { 62 | if (err) return reject(err); 63 | resolve(rows); 64 | }); 65 | }); 66 | 67 | const totalListWebsite = allWebsites.length; 68 | 69 | const totalUsers = await new Promise((resolve, reject) => { 70 | db.get("SELECT COUNT(*) AS count FROM users", (err, row) => { 71 | if (err) return reject(err); 72 | resolve(row.count); 73 | }); 74 | }); 75 | 76 | res.render("web/admin/information.ejs", { 77 | user: req.session.user, 78 | rank: req.session.user.rank, 79 | websites: allWebsites, 80 | totalListWebsite: totalListWebsite, 81 | totalUsers: totalUsers, 82 | appVersion: localVersion, 83 | newVersion: latestVersion, 84 | updateAvailable: updateAvailable 85 | ? "Your version is out of date" 86 | : "Your version is up-to-date", 87 | 88 | CPUname: CPUname, 89 | CPUarchitecture: os.arch(), 90 | CPUfrequence: CPUfrequence, 91 | 92 | RAMsize: Math.round(memData.total / 1024 / 1024 / 1024) + " GB", 93 | RAMtype: RAMtype, 94 | RAMspeed: RAMspeed, 95 | 96 | StorageSize: 97 | storageSize + 98 | (storageMainType !== "Unknown" ? ` ${storageMainType}` : ""), 99 | StorageType: storageInterface, 100 | 101 | OSname: osInfo.distro, 102 | Osversion: osInfo.release, 103 | KernelVersion: osInfo.kernel, 104 | 105 | NetworkIP: netData.length > 0 ? netData[0].ip4 : "Unknown", 106 | NetworkSpeed: 107 | netData.length > 0 ? netData[0].speed || "1 Gbps" : "Unknown", 108 | 109 | SystemUptime: `${uptimeDays} days, ${uptimeHours} hours`, 110 | LastBoot: bootTime.toLocaleString(), 111 | 112 | CPUCores: cpuCores, 113 | CPUThreads: cpuThreads, 114 | }); 115 | } catch (err) { 116 | console.error("System info retrieval error : " + err.message); 117 | res.status(500).render("error/500.ejs", { 118 | message: "Error retrieving system information.", 119 | }); 120 | } 121 | }); 122 | 123 | module.exports = router; 124 | -------------------------------------------------------------------------------- /src/pages/admin/settings.js: -------------------------------------------------------------------------------- 1 | console.log("pages/admin/settings.js loaded"); 2 | 3 | const express = require("express"); 4 | const db = require("../../db.js"); 5 | const router = express.Router(); 6 | const fs = require("fs"); 7 | const path = require("path"); 8 | const { isAuthenticated } = require("../../middleware/auth-admin.js"); 9 | 10 | const configPath = path.join(__dirname, "../../../config/config.json"); 11 | 12 | // GET - Display settings page 13 | router.get("/web/admin/settings", isAuthenticated, (req, res) => { 14 | fs.readFile(configPath, "utf8", (err, data) => { 15 | if (err) { 16 | console.error("Error reading config file: " + err.message); 17 | return res.status(500).render("error/500.ejs"); 18 | } 19 | 20 | let config; 21 | try { 22 | config = JSON.parse(data); 23 | } catch (parseErr) { 24 | console.error("Error parsing config file: " + parseErr.message); 25 | return res.status(500).render("error/500.ejs"); 26 | } 27 | 28 | db.all("SELECT id, username, rank FROM users", (err, rows) => { 29 | if (err) { 30 | console.error("Error fetching users: " + err.message); 31 | return res.status(500).render("error/500.ejs"); 32 | } 33 | 34 | res.render("web/admin/settings.ejs", { 35 | user: req.session.user, 36 | rank: req.session.user.rank, 37 | users: rows, 38 | config: config, 39 | }); 40 | }); 41 | }); 42 | }); 43 | 44 | // POST - Update config 45 | router.post("/web/admin/settings", isAuthenticated, (req, res) => { 46 | fs.readFile(configPath, "utf8", (err, data) => { 47 | if (err) { 48 | console.error("Error reading config file: " + err.message); 49 | return res.status(500).render("error/500.ejs"); 50 | } 51 | 52 | let config; 53 | try { 54 | config = JSON.parse(data); 55 | } catch (parseErr) { 56 | console.error("Error parsing config file: " + parseErr.message); 57 | return res.status(500).render("error/500.ejs"); 58 | } 59 | 60 | // Update fields, retaining those that have not been modified 61 | config.hostname = req.body.hostname || config.hostname; 62 | 63 | const port = parseInt(req.body.port); 64 | config.port = isNaN(port) ? config.port : port; 65 | 66 | config.name = req.body.name || config.name; 67 | config.version = req.body.version || config.version; 68 | 69 | // Session 70 | config.session.secret = req.body.session_secret || config.session.secret; 71 | config.session.resave = req.body.session_resave === "true"; 72 | config.session.saveUninitialized = 73 | req.body.session_saveUninitialized === "true"; 74 | config.session.cookie.secure = req.body.session_cookie_secure === "true"; 75 | 76 | fs.writeFile(configPath, JSON.stringify(config, null, 2), "utf8", (err) => { 77 | if (err) { 78 | console.error("Error writing config file: " + err.message); 79 | return res.status(500).send("Error saving configuration."); 80 | } 81 | 82 | res.redirect("/web/admin/settings"); 83 | }); 84 | }); 85 | }); 86 | 87 | module.exports = router; 88 | -------------------------------------------------------------------------------- /src/pages/admin/subscriptions.js: -------------------------------------------------------------------------------- 1 | console.log("pages/admin/subscriptions.js loaded"); // To confirm that the page has been loaded correctly 2 | const express = require("express"); 3 | const db = require("../../db.js"); 4 | const router = express.Router(); 5 | const fs = require("fs"); 6 | const path = require("path"); 7 | const { isAuthenticated } = require("../../middleware/auth-admin.js"); 8 | 9 | // GET route to show all websites (admin view) 10 | router.get("/web/admin/subscriptions", isAuthenticated, (req, res) => { 11 | const sql = ` 12 | SELECT websites.*, users.username 13 | FROM websites 14 | JOIN users ON websites.user_id = users.id 15 | `; 16 | 17 | db.all(sql, (err, allWebsites) => { 18 | if (err) { 19 | console.error("Error retrieving websites:", err.message); 20 | return res.status(500).send("Server error"); 21 | } 22 | 23 | res.render("web/admin/subscriptions.ejs", { 24 | totalListWebsite: allWebsites.length, 25 | websites: allWebsites, 26 | user: req.session?.user, 27 | rank: req.session?.user?.rank, 28 | }); 29 | }); 30 | }); 31 | 32 | // DELETE route to remove a website by UUID (admin only) 33 | router.get( 34 | "/web/admin/subscriptions/delete/:uuid", 35 | isAuthenticated, 36 | (req, res) => { 37 | const websiteUuid = req.params.uuid; 38 | 39 | db.get( 40 | "SELECT * FROM websites WHERE uuid = ?", 41 | [websiteUuid], 42 | (err, website) => { 43 | if (err) { 44 | console.error("Error fetching website:", err.message); 45 | return res.status(500).render("error/500.ejs"); 46 | } 47 | 48 | if (!website) { 49 | return res.status(404).render("error/404.ejs"); 50 | } 51 | 52 | const filePath = path.join( 53 | __dirname, 54 | "../../../storage/volumes", 55 | websiteUuid 56 | ); 57 | 58 | // Corrected path for Nginx config file 59 | const nginxConfigPath = path.join( 60 | "/etc", // Go directly to /etc 61 | "nginx", // Go to /nginx 62 | "sites-enabled", // Directory of activated configurations 63 | website.name + ".conf" // The Nginx configuration file 64 | ); 65 | 66 | // Delete from DB 67 | db.run("DELETE FROM websites WHERE uuid = ?", [websiteUuid], (err) => { 68 | if (err) { 69 | console.error("Error deleting website:", err.message); 70 | return res.status(500).render("error/500.ejs"); 71 | } 72 | 73 | // Delete folder (optional) 74 | fs.rm(filePath, { recursive: true, force: true }, (err) => { 75 | if (err) { 76 | console.error("Error deleting files:", err.message); 77 | // still redirect even if file deletion failed 78 | } 79 | 80 | // Delete Nginx config file 81 | fs.rm(nginxConfigPath, (err) => { 82 | if (err) { 83 | console.error("Error deleting Nginx config:", err.message); 84 | // still redirect even if Nginx config deletion failed 85 | } else { 86 | console.log(`Deleted Nginx config for ${website.name}`); 87 | } 88 | 89 | // After deletion of config, restart Nginx 90 | const { exec } = require("child_process"); 91 | exec("sudo systemctl restart nginx", (err, stdout, stderr) => { 92 | if (err) { 93 | console.error(`Error restarting Nginx: ${stderr}`); 94 | } else { 95 | console.log("Nginx restarted successfully"); 96 | } 97 | }); 98 | 99 | res.redirect("/web/admin/subscriptions"); 100 | }); 101 | }); 102 | }); 103 | } 104 | ); 105 | } 106 | ); 107 | 108 | module.exports = router; 109 | -------------------------------------------------------------------------------- /src/pages/admin/subscriptions/create.js: -------------------------------------------------------------------------------- 1 | console.log("pages/admin/subscriptions/create.js loaded"); // To confirm that the page has been loaded correctly 2 | const path = require("path"); 3 | const fs = require("fs"); 4 | const crypto = require("crypto"); 5 | const express = require("express"); 6 | const http = require("http"); 7 | const router = express.Router(); 8 | const db = require("../../../db"); 9 | const { addDomain } = require("../../../web/domain"); 10 | const { isAuthenticated } = require("../../../middleware/auth-admin.js"); 11 | 12 | let activeServers = {}; 13 | 14 | // Route GET to display the creation page 15 | router.get("/web/admin/subscriptions/create", isAuthenticated, (req, res) => { 16 | db.all("SELECT id, username FROM users", (err, rows) => { 17 | if (err) { 18 | console.error("Error loading users:", err.message); 19 | return res.status(500).send("Server error"); 20 | } 21 | 22 | res.render("web/admin/subscriptions/create", { 23 | users: rows, 24 | user: req.session?.user, 25 | rank: req.session?.user?.rank, 26 | }); 27 | }); 28 | }); 29 | 30 | // POST route to create a site 31 | router.post("/web/admin/subscriptions/create", isAuthenticated, (req, res) => { 32 | const { userId, diskLimit, port, name } = req.body; 33 | 34 | if (!userId || !diskLimit || !port || !name) { 35 | return res.status(400).send("Missing fields!"); 36 | } 37 | 38 | const uuid = crypto.randomUUID(); 39 | 40 | const sql = ` 41 | INSERT INTO websites (user_id, uuid, name, port, disk_limit) 42 | VALUES (?, ?, ?, ?, ?) 43 | `; 44 | 45 | db.run(sql, [userId, uuid, name, port, diskLimit], function (err) { 46 | if (err) { 47 | console.error("Database error:", err.message); 48 | return res.status(500).send("Database error."); 49 | } 50 | 51 | const folderPath = path.join( 52 | __dirname, 53 | "../../../../storage/volumes", 54 | uuid 55 | ); 56 | 57 | fs.mkdir(folderPath, { recursive: true }, (err) => { 58 | if (err) { 59 | console.error("Folder creation error:", err.message); 60 | return res.status(500).send("Folder creation error."); 61 | } 62 | 63 | const filePath = path.join(folderPath, "index.html"); 64 | 65 | fs.exists(filePath, (exists) => { 66 | if (!exists) { 67 | const defaultHtml = ` 68 | 69 | 70 | 71 | 72 | 73 | Welcome to ${name} 74 | 75 | 76 |

Welcome to ${name}!

77 |

This is the default page for your website.

78 | 79 | 80 | `; 81 | 82 | fs.writeFile(filePath, defaultHtml, (err) => { 83 | if (err) { 84 | console.error("Error creating index.html:", err.message); 85 | return res.status(500).send("Error creating index.html."); 86 | } 87 | console.log(`Created default index.html for ${name}`); 88 | }); 89 | } 90 | 91 | // Creating the php.ini file 92 | const phpIniPath = path.join(folderPath, "php.ini"); 93 | const phpIniContent = ` 94 | disable_functions = exec,passthru,shell_exec,system,proc_open,popen 95 | display_errors = Off 96 | expose_php = Off 97 | memory_limit = 256M 98 | upload_max_filesize = 100M 99 | post_max_size = 100M 100 | `.trim(); 101 | 102 | fs.writeFile(phpIniPath, phpIniContent, (err) => { 103 | if (err) { 104 | console.error("Error writing php.ini:", err.message); 105 | return res.status(500).send("Failed to create php.ini."); 106 | } 107 | 108 | // Ajout du domaine nginx 109 | addDomain(name, folderPath, (err) => { 110 | if (err) { 111 | console.error(`Error adding domain for ${name}:`, err); 112 | return res.status(500).send("Failed to add domain."); 113 | } 114 | 115 | console.log( 116 | `New website : UUID=${uuid}, Domaine=${name}, Port=${port}, Disk=${diskLimit}MB` 117 | ); 118 | return res.redirect("/web/admin/subscriptions"); 119 | }); 120 | }); 121 | }); 122 | }); 123 | }); 124 | }); 125 | 126 | module.exports = router; 127 | module.exports.activeServers = activeServers; 128 | -------------------------------------------------------------------------------- /src/pages/auth/login.js: -------------------------------------------------------------------------------- 1 | console.log("pages/auth/login.js loaded"); // To confirm that the page has been loaded correctly 2 | const express = require("express"); 3 | const db = require("../../db.js"); 4 | const { authLimiter } = require("../../middleware/rate-limiter.js"); 5 | const router = express.Router(); 6 | 7 | // Route GET to display the login page 8 | router.get("/", (req, res) => { 9 | res.render("auth/login.ejs", { error: null }); 10 | }); 11 | 12 | // Route POST with limiter to handle login 13 | router.post("/login", (req, res) => { 14 | if (!req.body || !req.body.username || !req.body.password) { 15 | return res 16 | .status(400) 17 | .render("auth/login.ejs", { error: "All fields are required." }); 18 | } 19 | 20 | const { username, password } = req.body; 21 | 22 | db.get( 23 | "SELECT * FROM users WHERE username = ? AND password = ?", 24 | [username, password], 25 | (err, row) => { 26 | if (err) { 27 | console.error("ERROR SQL : " + err.message); 28 | res.status(500).send("Internal server error"); 29 | } else if (row) { 30 | req.session.user = { id: row.id, username: row.username }; 31 | console.log("User logged in :", req.session.user); 32 | res.redirect("/web/list"); 33 | } else { 34 | res.render("auth/login.ejs", { error: "Identifiants invalides." }); 35 | } 36 | } 37 | ); 38 | }); 39 | 40 | module.exports = router; 41 | -------------------------------------------------------------------------------- /src/pages/auth/logout.js: -------------------------------------------------------------------------------- 1 | console.log("pages/auth/logout.js loaded"); // To confirm that the page has been loaded correctly 2 | const express = require("express"); 3 | 4 | const router = express.Router(); 5 | 6 | // Route to handle logout 7 | router.get("/logout", (req, res) => { 8 | req.session.destroy((err) => { 9 | if (err) { 10 | console.error("Error while destroying the session: " + err.message); 11 | } 12 | res.redirect("/"); 13 | }); 14 | }); 15 | 16 | module.exports = router; 17 | -------------------------------------------------------------------------------- /src/pages/load.js: -------------------------------------------------------------------------------- 1 | console.log("pages/load.js loaded"); // To confirm that the page has been loaded correctly 2 | const express = require("express"); 3 | const router = express.Router(); 4 | 5 | // Route GET to /load/:x that redirects to /x 6 | router.get("/load/:x", (req, res) => { 7 | const x = req.params.x; 8 | 9 | if (x) { 10 | return res.redirect(`/${x}`); 11 | } 12 | 13 | res.render("load.ejs", { error: null }); 14 | }); 15 | 16 | // Optional fallback 17 | router.get("/load", (req, res) => { 18 | res.render("load.ejs", { error: null }); 19 | }); 20 | 21 | module.exports = router; 22 | -------------------------------------------------------------------------------- /src/pages/user/account.js: -------------------------------------------------------------------------------- 1 | console.log("pages/user/account.js loaded"); // To confirm that the page has been loaded correctly 2 | const express = require("express"); 3 | const db = require("../../db.js"); 4 | const router = express.Router(); 5 | const { isAuthenticated } = require("../../middleware/auth.js"); 6 | 7 | // Route for the user's account info 8 | router.get("/web/account", isAuthenticated, (req, res) => { 9 | const userId = req.session.user.id; 10 | 11 | db.get( 12 | "SELECT username, rank, created_at FROM users WHERE id = ?", 13 | [userId], 14 | (err, userInfo) => { 15 | if (err) { 16 | console.error("Error retrieving user info:", err.message); 17 | return res.status(500).send("Internal server error"); 18 | } 19 | 20 | if (!userInfo) { 21 | return res.status(404).send("User not found"); 22 | } 23 | 24 | const fullUser = { 25 | ...userInfo, 26 | id: userId, 27 | }; 28 | 29 | res.render("web/account.ejs", { 30 | user: fullUser, 31 | rank: userInfo.rank, 32 | rank: userInfo.rank, // Ensure rank is passed here 33 | error: null, 34 | succes: null, 35 | }); 36 | } 37 | ); 38 | }); 39 | 40 | // Route for change username 41 | router.post("/web/account/username/save", isAuthenticated, (req, res) => { 42 | const userId = req.session.user.id; 43 | const newUsername = req.body.username; 44 | 45 | if (!newUsername) { 46 | return res.render("web/account", { 47 | error: "Username cannot be empty", 48 | rank: req.session.user.rank, // Pass rank to the view 49 | user: req.session.user, 50 | }); 51 | } 52 | 53 | db.run( 54 | "UPDATE users SET username = ? WHERE id = ?", 55 | [newUsername, userId], 56 | function (err) { 57 | if (err) { 58 | console.error("Error updating username:", err.message); 59 | return res.render("web/account", { 60 | error: "Server Error", 61 | rank: req.session.user.rank, // Pass rank to the view 62 | user: req.session.user, 63 | }); 64 | } 65 | 66 | req.session.user.username = newUsername; 67 | res.render("web/account", { 68 | succes: "Username updated successfully", 69 | error: null, 70 | rank: req.session.user.rank, // Pass rank to the view 71 | user: req.session.user, 72 | }); 73 | } 74 | ); 75 | }); 76 | 77 | // Route for change password 78 | router.post("/web/account/password/save", isAuthenticated, (req, res) => { 79 | const userId = req.session.user.id; 80 | const { current_password, new_password, confirm_new_password } = req.body; 81 | 82 | if (!current_password || !new_password || !confirm_new_password) { 83 | return res.render("web/account", { 84 | error: "All fields are required", 85 | succes: null, 86 | user: req.session.user, 87 | rank: req.session.user.rank, // Pass rank to the view 88 | }); 89 | } 90 | 91 | if (new_password !== confirm_new_password) { 92 | return res.render("web/account", { 93 | error: "New passwords do not match", 94 | succes: null, 95 | user: req.session.user, 96 | rank: req.session.user.rank, // Pass rank to the view 97 | }); 98 | } 99 | 100 | db.get("SELECT password FROM users WHERE id = ?", [userId], (err, row) => { 101 | if (err) { 102 | console.error("Error fetching user password:", err.message); 103 | return res.render("web/account", { 104 | error: "Server Error", 105 | succes: null, 106 | user: req.session.user, 107 | rank: req.session.user.rank, // Pass rank to the view 108 | }); 109 | } 110 | 111 | if (!row) { 112 | return res.render("web/account", { 113 | error: "User not found", 114 | succes: null, 115 | user: req.session.user, 116 | rank: req.session.user.rank, // Pass rank to the view 117 | }); 118 | } 119 | 120 | if (row.password !== current_password) { 121 | return res.render("web/account", { 122 | error: "Current password is incorrect", 123 | user: req.session.user, 124 | succes: null, 125 | rank: req.session.user.rank, // Pass rank to the view 126 | }); 127 | } 128 | 129 | db.run( 130 | "UPDATE users SET password = ? WHERE id = ?", 131 | [new_password, userId], 132 | function (err) { 133 | if (err) { 134 | console.error("Error updating password:", err.message); 135 | return res.render("web/account", { 136 | error: "Server Error", 137 | succes: null, 138 | user: req.session.user, 139 | rank: req.session.user.rank, // Pass rank to the view 140 | }); 141 | } 142 | 143 | res.render("web/account", { 144 | succes: "Password updated successfully", 145 | error: null, 146 | user: req.session.user, 147 | rank: req.session.user.rank, // Pass rank to the view 148 | }); 149 | } 150 | ); 151 | }); 152 | }); 153 | 154 | module.exports = router; 155 | -------------------------------------------------------------------------------- /src/pages/user/edit/files.js: -------------------------------------------------------------------------------- 1 | console.log("pages/user/edit/files.js loaded"); // To confirm that the page has been loaded correctly 2 | const express = require("express"); 3 | const fs = require("fs"); 4 | const path = require("path"); 5 | const db = require("../../../db.js"); 6 | const router = express.Router(); 7 | const { checkSizeBeforeCreate } = require("../../../web/size-limit.js"); 8 | const app = express(); 9 | const { isAuthenticated } = require("../../../middleware/auth.js"); 10 | const { 11 | protectSensitiveFiles, 12 | } = require("../../../middleware/protect-sensitive-files.js"); 13 | app.use(express.json({ limit: "80mb" })); 14 | app.use(express.urlencoded({ limit: "80mb", extended: true })); 15 | 16 | // Function to calculate the total size of a folder 17 | function getFolderSize(folderPath, callback) { 18 | let totalSize = 0; 19 | 20 | const walk = (dir) => { 21 | return new Promise((resolve, reject) => { 22 | fs.readdir(dir, { withFileTypes: true }, async (err, entries) => { 23 | if (err) return reject(err); 24 | 25 | for (const entry of entries) { 26 | const fullPath = path.join(dir, entry.name); 27 | if (entry.isDirectory()) { 28 | await walk(fullPath); 29 | } else { 30 | const stats = fs.statSync(fullPath); 31 | totalSize += stats.size; 32 | } 33 | } 34 | resolve(); 35 | }); 36 | }); 37 | }; 38 | 39 | walk(folderPath) 40 | .then(() => callback(null, totalSize)) 41 | .catch((err) => callback(err)); 42 | } 43 | 44 | // Helper function to get the volume directory path (avoids repetition) 45 | function getVolumesDir() { 46 | return path.join(__dirname, "../../../../storage/volumes"); 47 | } 48 | 49 | // Route to display the file editor 50 | router.get( 51 | "/web/manage/:id/edit/:file", 52 | isAuthenticated, 53 | protectSensitiveFiles(getVolumesDir()), // Use the helper to avoid repetition 54 | (req, res) => { 55 | const userId = req.session.user.id; 56 | const websiteUuid = req.params.id; 57 | const fileName = req.params.file; 58 | 59 | console.log( 60 | `DEBUG: Edit route accessed - website: ${websiteUuid}, file: ${fileName}` 61 | ); 62 | 63 | db.get( 64 | "SELECT * FROM websites WHERE uuid = ? AND user_id = ?", 65 | [websiteUuid, userId], 66 | (err, website) => { 67 | if (err) { 68 | console.error("DATABASE ERROR:", err.message); 69 | return res.status(500).render("error/500.ejs"); 70 | } 71 | 72 | if (!website) { 73 | console.error( 74 | `Website not found: ${websiteUuid} for user: ${userId}` 75 | ); 76 | return res.status(404).render("error/404.ejs"); 77 | } 78 | 79 | const websiteDir = path.join(getVolumesDir(), websiteUuid); // Use the helper 80 | 81 | db.get("SELECT rank FROM users WHERE id = ?", [userId], (err, row) => { 82 | if (err) { 83 | console.error("Error recovering rank:", err.message); 84 | return res.status(500).render("error/500.ejs"); 85 | } 86 | 87 | fs.mkdir(websiteDir, { recursive: true }, (err) => { 88 | if (err && err.code !== "EEXIST") { 89 | console.error(`Error creating directory: ${websiteDir}`, err); 90 | return res.status(500).render("error/500.ejs"); 91 | } 92 | 93 | const filePath = path.join(websiteDir, fileName); 94 | console.log(`Looking for file at: ${filePath}`); 95 | 96 | fs.access(filePath, fs.constants.F_OK, (err) => { 97 | if (err) { 98 | console.error(`File not found: ${filePath}`); 99 | 100 | if (req.query.new === "true") { 101 | console.log(`Creating new file: ${filePath}`); 102 | 103 | // Calculate file size and check disk space 104 | const fileSize = 0; // size of the file, here 0 for an empty file 105 | checkSizeBeforeCreate(websiteUuid, fileSize, (err) => { 106 | if (err) { 107 | console.error("Disk limit exceeded: ", err.message); 108 | return res.render("web/edit/files.ejs", { 109 | user: req.session.user, 110 | error: err.message, // Sends error to view 111 | websiteUuid, 112 | fileName, 113 | fileContent: "", 114 | website, 115 | }); 116 | } 117 | 118 | fs.writeFile(filePath, "", "utf8", (err) => { 119 | if (err) { 120 | console.error( 121 | `Error creating new file: ${filePath}`, 122 | err 123 | ); 124 | return res.status(500).render("error/500.ejs"); 125 | } 126 | 127 | return res.render("web/edit/files.ejs", { 128 | user: req.session.user, 129 | rank: row ? row.rank : null, 130 | error: null, 131 | websiteUuid, 132 | fileName, 133 | fileContent: "", 134 | website, 135 | }); 136 | }); 137 | }); 138 | } else { 139 | return res.status(404).render("error/404.ejs", { 140 | message: `File '${fileName}' not found`, 141 | }); 142 | } 143 | return; 144 | } 145 | 146 | fs.readFile(filePath, "utf8", (err, fileContent) => { 147 | if (err) { 148 | console.error(`Error reading file: ${filePath}`, err); 149 | return res.status(500).render("error/500.ejs"); 150 | } 151 | 152 | res.render("web/edit/files.ejs", { 153 | user: req.session.user, 154 | rank: row ? row.rank : null, 155 | error: null, 156 | websiteUuid, 157 | fileName, 158 | fileContent, 159 | website, 160 | }); 161 | }); 162 | }); 163 | }); 164 | }); 165 | } 166 | ); 167 | } 168 | ); 169 | 170 | // Route to save file changes 171 | router.post( 172 | "/web/manage/:id/edit/:file", 173 | isAuthenticated, 174 | protectSensitiveFiles(getVolumesDir()), // Use the helper to avoid repetition 175 | (req, res) => { 176 | const userId = req.session.user.id; 177 | const websiteUuid = req.params.id; 178 | const fileName = req.params.file; 179 | const fileContent = req.body.content; 180 | 181 | console.log( 182 | `DEBUG: Save route accessed - website: ${websiteUuid}, file: ${fileName}` 183 | ); 184 | 185 | if (fileContent === undefined) { 186 | console.error("No content provided in request body"); 187 | return res.status(400).send("No content provided"); 188 | } 189 | 190 | db.get( 191 | "SELECT * FROM websites WHERE uuid = ? AND user_id = ?", 192 | [websiteUuid, userId], 193 | (err, website) => { 194 | if (err) { 195 | console.error("DATABASE ERROR:", err.message); 196 | return res.status(500).render("error/500.ejs"); 197 | } 198 | 199 | if (!website) { 200 | console.error( 201 | `Website not found: ${websiteUuid} for user: ${userId}` 202 | ); 203 | return res.status(404).render("error/500.ejs"); 204 | } 205 | 206 | const websiteDir = path.join(getVolumesDir(), websiteUuid); // Use the helper 207 | 208 | fs.mkdir(websiteDir, { recursive: true }, (err) => { 209 | if (err && err.code !== "EEXIST") { 210 | console.error(`Error creating directory: ${websiteDir}`, err); 211 | return res.status(500).render("error/500.ejs"); 212 | } 213 | 214 | const filePath = path.join(websiteDir, fileName); 215 | 216 | // Calculate file size to be added and check the size 217 | const fileSize = Buffer.byteLength(fileContent, "utf8"); // Calculate content size in bytes 218 | checkSizeBeforeCreate(websiteUuid, fileSize, (err) => { 219 | if (err) { 220 | console.error("Disk limit exceeded: ", err.message); 221 | 222 | // Pass error to the view 223 | return res.render("web/edit/files.ejs", { 224 | user: req.session.user, 225 | error: err.message, // Send error message to view 226 | websiteUuid, 227 | fileName, 228 | fileContent, 229 | website, 230 | }); 231 | } 232 | 233 | fs.writeFile(filePath, fileContent, "utf8", (err) => { 234 | if (err) { 235 | console.error(`Error writing file: ${filePath}`, err); 236 | return res.status(500).render("error/500.ejs"); 237 | } 238 | 239 | console.log(`File saved successfully: ${filePath}`); 240 | res.redirect(`/load?x=/web/manage/${websiteUuid}`); 241 | }); 242 | }); 243 | }); 244 | } 245 | ); 246 | } 247 | ); 248 | 249 | // Route to get the total size of the website's folder 250 | router.get( 251 | "/web/manage/:id/size", 252 | isAuthenticated, 253 | protectSensitiveFiles(getVolumesDir()), 254 | (req, res) => { 255 | const userId = req.session.user.id; 256 | const websiteUuid = req.params.id; 257 | 258 | console.log(`[DEBUG] GET /web/manage/${websiteUuid}/size`); 259 | 260 | db.get( 261 | "SELECT * FROM websites WHERE uuid = ? AND user_id = ?", 262 | [websiteUuid, userId], 263 | (err, website) => { 264 | if (err) { 265 | console.error("DATABASE ERROR:", err.message); 266 | return res.status(500).json({ error: "Database error" }); 267 | } 268 | 269 | if (!website) { 270 | console.error( 271 | `Website not found: ${websiteUuid} for user: ${userId}` 272 | ); 273 | return res.status(404).json({ error: "Website not found" }); 274 | } 275 | 276 | const websiteDir = path.join(getVolumesDir(), websiteUuid); 277 | 278 | getFolderSize(websiteDir, (err, size) => { 279 | if (err) { 280 | console.error(`Error getting folder size:`, err); 281 | return res.status(500).json({ error: "Failed to get folder size" }); 282 | } 283 | 284 | res.json({ size }); // returns { size: 123456 } (in bytes) 285 | }); 286 | } 287 | ); 288 | } 289 | ); 290 | 291 | module.exports = router; 292 | -------------------------------------------------------------------------------- /src/pages/user/home.js: -------------------------------------------------------------------------------- 1 | console.log("pages/user/home.js loaded"); // To confirm that the page has been loaded correctly 2 | const express = require("express"); 3 | const db = require("../../db.js"); 4 | const { isAuthenticated } = require("../../middleware/auth.js"); 5 | const router = express.Router(); 6 | 7 | // Route for the user's website list 8 | router.get("/web/list", isAuthenticated, (req, res) => { 9 | const userId = req.session.user.id; 10 | 11 | db.all( 12 | "SELECT name, port, disk_limit, uuid FROM websites WHERE user_id = ?", 13 | [userId], 14 | (err, websites) => { 15 | if (err) { 16 | console.error("Error retrieving websites:", err.message); 17 | return res.status(500).send("Internal server error"); 18 | } 19 | 20 | db.get("SELECT rank FROM users WHERE id = ?", [userId], (err, row) => { 21 | if (err) { 22 | console.error("Error while retrieving the rank: " + err.message); 23 | return res.status(500).send("Internal server error"); 24 | } 25 | 26 | res.render("web/list.ejs", { 27 | user: req.session.user, 28 | websites, 29 | rank: row ? row.rank : null, 30 | }); 31 | }); 32 | } 33 | ); 34 | }); 35 | 36 | module.exports = router; 37 | -------------------------------------------------------------------------------- /src/pages/user/manage.js: -------------------------------------------------------------------------------- 1 | console.log("pages/user/manage.js loaded"); // To confirm that the page has been loaded correctly 2 | const express = require("express"); 3 | const db = require("../../db.js"); 4 | const router = express.Router(); 5 | const fs = require("fs"); 6 | const path = require("path"); 7 | const { isAuthenticated } = require("../../middleware/auth.js"); 8 | const { 9 | protectSensitiveFiles, 10 | } = require("../../middleware/protect-sensitive-files.js"); 11 | const { checkSizeBeforeCreate } = require("../../web/size-limit.js"); // <- ajout du size check 12 | 13 | const filesPath = path.join(__dirname, "../../../storage/volumes"); 14 | const filesProtect = protectSensitiveFiles(filesPath); 15 | 16 | // Website management page route 17 | router.get("/web/manage/:id", isAuthenticated, (req, res) => { 18 | const userId = req.session.user.id; 19 | const websiteUuid = req.params.id; 20 | 21 | db.get( 22 | "SELECT * FROM websites WHERE uuid = ? AND user_id = ?", 23 | [websiteUuid, userId], 24 | (err, website) => { 25 | if (err) { 26 | console.error("DB error:", err.message); 27 | return res.status(500).render("error/500.ejs"); 28 | } 29 | if (!website) { 30 | return res.status(404).render("error/404.ejs"); 31 | } 32 | 33 | db.get("SELECT rank FROM users WHERE id = ?", [userId], (err, row) => { 34 | if (err) { 35 | console.error("Error retrieving rank:", err.message); 36 | return res.status(500).render("error/500.ejs"); 37 | } 38 | 39 | const filesPath = path.join( 40 | __dirname, 41 | "../../../storage/volumes", 42 | websiteUuid 43 | ); 44 | 45 | fs.readdir(filesPath, (err, fileList) => { 46 | if (err) { 47 | console.error("Error reading directory:", err.message); 48 | return res.status(500).render("error/500.ejs"); 49 | } 50 | 51 | const files = fileList.map((fileName) => { 52 | const fileFullPath = path.join(filesPath, fileName); 53 | const stats = fs.statSync(fileFullPath); 54 | return { 55 | name: fileName, 56 | size: (stats.size / 1024 / 1024).toFixed(2), // Taille en Mo 57 | }; 58 | }); 59 | 60 | res.render("web/manage.ejs", { 61 | user: req.session.user, 62 | website, 63 | rank: row ? row.rank : null, 64 | websiteUuid, 65 | files, 66 | }); 67 | }); 68 | }); 69 | } 70 | ); 71 | }); 72 | 73 | // Create file route 74 | router.post("/web/manage/:id/create-file", isAuthenticated, (req, res) => { 75 | const userId = req.session.user.id; 76 | const websiteUuid = req.params.id; 77 | const filename = req.body.filename; 78 | 79 | // Basic security check 80 | if ( 81 | !filename || 82 | filename.includes("..") || 83 | filename.includes("/") || 84 | filename.length > 100 85 | ) { 86 | return res.status(400).send("Invalid file name."); 87 | } 88 | 89 | db.get( 90 | "SELECT * FROM websites WHERE uuid = ? AND user_id = ?", 91 | [websiteUuid, userId], 92 | (err, website) => { 93 | if (err) { 94 | console.error("DB error:", err.message); 95 | return res.status(500).render("error/500.ejs"); 96 | } 97 | if (!website) { 98 | return res.status(404).render("error/404.ejs"); 99 | } 100 | 101 | const filePath = path.join( 102 | __dirname, 103 | "../../../storage/volumes", 104 | websiteUuid, 105 | filename 106 | ); 107 | 108 | // Vérifie la taille avant de créer 109 | checkSizeBeforeCreate(websiteUuid, 0, (err) => { 110 | if (err) { 111 | console.error("Disk quota error:", err.message); 112 | return res 113 | .status(403) 114 | .send("Disk limit exceeded, cannot create file."); 115 | } 116 | 117 | fs.writeFile(filePath, "", (err) => { 118 | if (err) { 119 | console.error("File creation error:", err.message); 120 | return res.status(500).render("error/500.ejs"); 121 | } 122 | 123 | res.redirect(`/web/manage/${websiteUuid}`); 124 | }); 125 | }); 126 | } 127 | ); 128 | }); 129 | 130 | // File deletion route with protection middleware 131 | router.get( 132 | "/web/manage/:id/delete/:file", 133 | isAuthenticated, 134 | filesProtect, 135 | (req, res) => { 136 | const userId = req.session.user.id; 137 | const websiteUuid = req.params.id; 138 | const fileToDelete = req.params.file; 139 | 140 | // Basic security check 141 | if ( 142 | !fileToDelete || 143 | fileToDelete.includes("..") || 144 | fileToDelete.includes("/") 145 | ) { 146 | return res.status(400).send("Invalid file name."); 147 | } 148 | 149 | db.get( 150 | "SELECT * FROM websites WHERE uuid = ? AND user_id = ?", 151 | [websiteUuid, userId], 152 | (err, website) => { 153 | if (err) { 154 | console.error("Database error:", err.message); 155 | return res.status(500).render("error/500.ejs"); 156 | } 157 | if (!website) { 158 | return res.status(404).render("error/404.ejs"); 159 | } 160 | 161 | const filePath = path.join( 162 | __dirname, 163 | "../../../storage/volumes", 164 | websiteUuid, 165 | fileToDelete 166 | ); 167 | 168 | fs.unlink(filePath, (err) => { 169 | if (err) { 170 | console.error("File deletion error:", err.message); 171 | return res.status(500).render("error/500.ejs"); 172 | } 173 | 174 | res.redirect(`/web/manage/${websiteUuid}`); 175 | }); 176 | } 177 | ); 178 | } 179 | ); 180 | 181 | module.exports = router; 182 | -------------------------------------------------------------------------------- /src/pages/user/statistics/domains.js: -------------------------------------------------------------------------------- 1 | console.log("pages/user/statistics/domains.js loaded"); // To confirm that the page has been loaded correctly 2 | const express = require("express"); 3 | const db = require("../../../db.js"); 4 | const router = express.Router(); 5 | const fs = require("fs"); 6 | const path = require("path"); 7 | const { isAuthenticated } = require("../../../middleware/auth.js"); 8 | 9 | // Route for the user's website list 10 | router.get("/web/statistics/list", isAuthenticated, (req, res) => { 11 | const userId = req.session.user.id; 12 | const statisticsFile = path.join( 13 | __dirname, 14 | "../../../../storage/statistics.json" 15 | ); 16 | 17 | db.all( 18 | "SELECT name, port, disk_limit, uuid FROM websites WHERE user_id = ?", 19 | [userId], 20 | (err, websites) => { 21 | if (err) { 22 | console.error("Error retrieving websites:", err.message); 23 | return res.status(500).send("Internal server error"); 24 | } 25 | 26 | db.get("SELECT rank FROM users WHERE id = ?", [userId], (err, row) => { 27 | if (err) { 28 | console.error("Error retrieving rank: " + err.message); 29 | return res.status(500).send("Internal server error"); 30 | } 31 | 32 | // Read the statistics file 33 | let stats = {}; 34 | if (fs.existsSync(statisticsFile)) { 35 | try { 36 | const data = fs.readFileSync(statisticsFile, "utf8"); 37 | stats = JSON.parse(data); 38 | } catch (e) { 39 | console.error("Error reading / parsing statistics.json:", e); 40 | } 41 | } 42 | 43 | websites.forEach((website) => { 44 | website.visitCount = stats[website.uuid] || 0; 45 | }); 46 | 47 | res.render("web/statistics/domains.ejs", { 48 | user: req.session.user, 49 | websites, 50 | rank: row ? row.rank : null, 51 | }); 52 | }); 53 | } 54 | ); 55 | }); 56 | 57 | module.exports = router; 58 | -------------------------------------------------------------------------------- /src/pages/user/statistics/statistics.js: -------------------------------------------------------------------------------- 1 | console.log("pages/user/statistics/statistics.js loaded"); // To confirm that the page has been loaded correctly 2 | const express = require("express"); 3 | const db = require("../../../db.js"); 4 | const router = express.Router(); 5 | const fs = require("fs"); 6 | const path = require("path"); 7 | const { isAuthenticated } = require("../../../middleware/auth.js"); 8 | 9 | // Website statistics page route 10 | router.get("/web/statistics/:id", isAuthenticated, (req, res) => { 11 | const userId = req.session.user.id; 12 | const websiteUuid = req.params.id; 13 | const statisticsFile = path.join( 14 | __dirname, 15 | "../../../../storage/statistics.json" 16 | ); 17 | 18 | db.get( 19 | "SELECT * FROM websites WHERE uuid = ? AND user_id = ?", 20 | [websiteUuid, userId], 21 | (err, website) => { 22 | if (err) { 23 | console.error("DB error:", err.message); 24 | return res.status(500).render("error/500.ejs"); 25 | } 26 | if (!website) { 27 | return res.status(404).render("error/404.ejs"); 28 | } 29 | 30 | db.get("SELECT rank FROM users WHERE id = ?", [userId], (err, row) => { 31 | if (err) { 32 | console.error("Error retrieving rank:", err.message); 33 | return res.status(500).render("error/500.ejs"); 34 | } 35 | 36 | let visitCount = 0; 37 | 38 | // Read the statistics file 39 | if (fs.existsSync(statisticsFile)) { 40 | try { 41 | const data = fs.readFileSync(statisticsFile, "utf8"); 42 | const stats = JSON.parse(data); 43 | visitCount = stats[websiteUuid] || 0; 44 | } catch (e) { 45 | console.error("Error reading/parsing statistics.json:", e); 46 | } 47 | } 48 | 49 | res.render("web/statistics/statistics.ejs", { 50 | user: req.session.user, 51 | website, 52 | rank: row ? row.rank : null, 53 | websiteUuid, 54 | visitCount, 55 | }); 56 | }); 57 | } 58 | ); 59 | }); 60 | 61 | module.exports = router; 62 | -------------------------------------------------------------------------------- /src/start-webserver.js: -------------------------------------------------------------------------------- 1 | console.log("start-webserver.js loaded"); 2 | 3 | const express = require("express"); 4 | const fs = require("fs"); 5 | const http = require("http"); 6 | const path = require("path"); 7 | const db = require("./db.js"); 8 | const router = express.Router(); 9 | const createWS = require("./pages/admin/subscriptions/create.js"); 10 | 11 | let activeServers = createWS.activeServers || {}; 12 | 13 | const statisticsFile = path.join(__dirname, "../storage/statistics.json"); 14 | 15 | function getContentType(filename) { 16 | const ext = path.extname(filename).toLowerCase(); 17 | switch (ext) { 18 | case ".html": 19 | return "text/html"; 20 | case ".css": 21 | return "text/css"; 22 | case ".js": 23 | return "application/javascript"; 24 | case ".json": 25 | return "application/json"; 26 | case ".png": 27 | return "image/png"; 28 | case ".jpg": 29 | case ".jpeg": 30 | return "image/jpeg"; 31 | case ".svg": 32 | return "image/svg+xml"; 33 | case ".ico": 34 | return "image/x-icon"; 35 | default: 36 | return "application/octet-stream"; 37 | } 38 | } 39 | 40 | function trackVisit(uuid, req, res) { 41 | const cookieHeader = req.headers.cookie || ""; 42 | const visitedKey = `visited_${uuid}`; 43 | const hasVisited = cookieHeader.includes(`${visitedKey}=true`); 44 | 45 | if (!hasVisited) { 46 | fs.readFile(statisticsFile, "utf8", (err, data) => { 47 | let stats = {}; 48 | if (!err && data) { 49 | try { 50 | stats = JSON.parse(data); 51 | } catch (e) { 52 | console.error("Failed to parse statistics.json:", e); 53 | } 54 | } 55 | 56 | if (!stats[uuid]) stats[uuid] = 0; 57 | stats[uuid] += 1; 58 | 59 | fs.writeFile(statisticsFile, JSON.stringify(stats, null, 2), (err) => { 60 | if (err) console.error("Error writing statistics file:", err); 61 | }); 62 | }); 63 | 64 | const expires = new Date(Date.now() + 86400000).toUTCString(); 65 | res.setHeader( 66 | "Set-Cookie", 67 | `${visitedKey}=true; Expires=${expires}; Path=/` 68 | ); 69 | } 70 | } 71 | 72 | router.post("/web/restart/:uuid", (req, res) => { 73 | const uuid = req.params.uuid; 74 | activeServers = createWS.activeServers; 75 | 76 | db.get("SELECT * FROM websites WHERE uuid = ?", [uuid], (err, row) => { 77 | if (err || !row) return res.status(404).json({ error: "Site not found." }); 78 | 79 | const folderPath = path.join(__dirname, "../storage/volumes", uuid); 80 | const indexPath = path.join(folderPath, "index.html"); 81 | 82 | fs.access(indexPath, fs.constants.F_OK, (err) => { 83 | if (err) { 84 | return res.status(404).json({ 85 | error: "index.html not found. Path searched: " + indexPath, 86 | }); 87 | } 88 | 89 | if (activeServers[uuid]) { 90 | activeServers[uuid].close(() => { 91 | console.log(`Old server for ${uuid} stopped.`); 92 | }); 93 | } 94 | 95 | const server = http.createServer((req2, res2) => { 96 | trackVisit(uuid, req2, res2); 97 | 98 | const requestedFile = 99 | req2.url === "/" 100 | ? "index.html" 101 | : decodeURIComponent(req2.url.slice(1)); 102 | const requestedFilePath = path.join(folderPath, requestedFile); 103 | 104 | fs.access(requestedFilePath, fs.constants.F_OK, (err) => { 105 | if (err) { 106 | res2.statusCode = 404; 107 | res2.setHeader("Content-Type", "text/plain"); 108 | res2.end(`File ${requestedFile} not found.`); 109 | return; 110 | } 111 | 112 | fs.readFile(requestedFilePath, (err, data) => { 113 | if (err) { 114 | res2.statusCode = 500; 115 | res2.setHeader("Content-Type", "text/plain"); 116 | res2.end("Internal server error."); 117 | return; 118 | } 119 | 120 | res2.statusCode = 200; 121 | res2.setHeader("Content-Type", getContentType(requestedFile)); 122 | res2.end(data); 123 | }); 124 | }); 125 | }); 126 | 127 | server.listen(row.port, () => { 128 | console.log(`Server for UUID=${uuid} restarted on port ${row.port}`); 129 | activeServers[uuid] = server; 130 | res.json({ success: true }); 131 | }); 132 | }); 133 | }); 134 | }); 135 | 136 | function startAllActiveServers() { 137 | db.all("SELECT * FROM websites", (err, rows) => { 138 | if (err) return console.error("Error retrieving sites:", err.message); 139 | 140 | rows.forEach((row) => { 141 | const folderPath = path.join(__dirname, "../storage/volumes", row.uuid); 142 | const filePath = path.join(folderPath, "index.html"); 143 | 144 | fs.exists(filePath, (exists) => { 145 | if (exists) { 146 | const server = http.createServer((req, res) => { 147 | trackVisit(row.uuid, req, res); 148 | 149 | const requestedFile = 150 | req.url === "/" 151 | ? "index.html" 152 | : decodeURIComponent(req.url.slice(1)); 153 | const requestedFilePath = path.join(folderPath, requestedFile); 154 | 155 | fs.access(requestedFilePath, fs.constants.F_OK, (err) => { 156 | if (err) { 157 | res.statusCode = 404; 158 | res.setHeader("Content-Type", "text/plain"); 159 | res.end(`File ${requestedFile} not found.`); 160 | return; 161 | } 162 | 163 | fs.readFile(requestedFilePath, (err, data) => { 164 | if (err) { 165 | res.statusCode = 500; 166 | res.setHeader("Content-Type", "text/plain"); 167 | res.end("Error reading file."); 168 | } else { 169 | res.statusCode = 200; 170 | res.setHeader("Content-Type", getContentType(requestedFile)); 171 | res.end(data); 172 | } 173 | }); 174 | }); 175 | }); 176 | 177 | activeServers[row.uuid] = server; 178 | server.listen(row.port, () => { 179 | console.log( 180 | `Server for UUID=${row.uuid} started on http://localhost:${row.port}` 181 | ); 182 | }); 183 | } else { 184 | console.log( 185 | `index.html missing for UUID=${row.uuid}, not starting server.` 186 | ); 187 | } 188 | }); 189 | }); 190 | }); 191 | } 192 | 193 | startAllActiveServers(); 194 | 195 | module.exports = router; 196 | module.exports.activeServers = activeServers; 197 | -------------------------------------------------------------------------------- /src/utils/version-checker.js: -------------------------------------------------------------------------------- 1 | console.log("utils/version-checker.js loaded"); // To confirm that the page has been loaded correctly 2 | const fs = require("fs"); 3 | const path = require("path"); 4 | const axios = require("axios"); 5 | 6 | const localConfigPath = path.join(__dirname, "../../config/config.json"); 7 | 8 | async function getLumenOneOverviewData() { 9 | const config = JSON.parse(fs.readFileSync(localConfigPath, "utf8")); 10 | const localVersion = config.version || "Unknown"; 11 | let latestVersion = "Unavailable"; 12 | let updateAvailable = false; 13 | 14 | try { 15 | const response = await axios.get( 16 | "https://lumenlabs.pro/version/lumenone.html" 17 | ); 18 | latestVersion = response.data.trim(); 19 | 20 | updateAvailable = localVersion !== latestVersion; 21 | } catch (err) { 22 | console.error("Error verifying version :", err.message); 23 | } 24 | 25 | return { 26 | localVersion, 27 | latestVersion, 28 | updateAvailable, 29 | }; 30 | } 31 | 32 | module.exports = getLumenOneOverviewData; 33 | -------------------------------------------------------------------------------- /src/web/domain.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const { exec } = require("child_process"); 3 | 4 | function addDomain(name, filepath, callback) { 5 | // Checks if PHP is installed 6 | exec("php -v", (phpErr, stdout, stderr) => { 7 | const phpInstalled = !phpErr; 8 | let phpBlock = ""; 9 | 10 | if (phpInstalled) { 11 | // Extract installed PHP version 12 | const match = stdout.match(/PHP (\d+\.\d+)/); 13 | const version = match ? match[1] : "8.1"; // fallback to 8.1 if not detected 14 | 15 | phpBlock = ` 16 | location ~ \\.php$ { 17 | fastcgi_split_path_info ^(.+\\.php)(/.+)$; 18 | fastcgi_pass unix:/run/php/php${version}-fpm.sock; 19 | fastcgi_index index.php; 20 | include fastcgi_params; 21 | fastcgi_param PHP_VALUE "upload_max_filesize = 100M \\n post_max_size=100M"; 22 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 23 | fastcgi_param HTTP_PROXY ""; 24 | fastcgi_intercept_errors off; 25 | fastcgi_buffer_size 16k; 26 | fastcgi_buffers 4 16k; 27 | fastcgi_connect_timeout 300; 28 | fastcgi_send_timeout 300; 29 | fastcgi_read_timeout 300; 30 | }`; 31 | } 32 | 33 | const nginxConfig = ` 34 | server { 35 | listen 80; 36 | server_name ${name}; 37 | 38 | root ${filepath}; 39 | index index.php index.html index.htm; 40 | 41 | location / { 42 | try_files $uri $uri/ =404; 43 | }${phpBlock} 44 | }`; 45 | 46 | const configPath = `/etc/nginx/sites-enabled/${name}.conf`; 47 | 48 | fs.writeFile(configPath, nginxConfig, (err) => { 49 | if (err) { 50 | console.error("Failed to create nginx config:", err); 51 | if (callback) callback(err); 52 | return; 53 | } 54 | 55 | console.log(`Nginx config written to ${configPath}. Reloading Nginx...`); 56 | 57 | exec("nginx -s reload", (reloadErr, stdout, stderr) => { 58 | if (reloadErr) { 59 | console.error(`Reload error: ${reloadErr.message}`); 60 | if (callback) callback(reloadErr); 61 | return; 62 | } 63 | if (stderr) console.error(`Reload stderr: ${stderr}`); 64 | console.log("Nginx reloaded successfully."); 65 | if (callback) callback(null); 66 | }); 67 | }); 68 | }); 69 | } 70 | 71 | module.exports = { addDomain }; 72 | -------------------------------------------------------------------------------- /src/web/size-limit.js: -------------------------------------------------------------------------------- 1 | console.log("web/size-limit.js loaded"); // To confirm that the page has been loaded correctly 2 | const db = require("../db.js"); 3 | const fs = require("fs"); 4 | const path = require("path"); 5 | 6 | // Function to calculate the size of a folder 7 | function getFolderSize(folderPath) { 8 | let totalSize = 0; 9 | const files = fs.readdirSync(folderPath); 10 | 11 | for (const file of files) { 12 | const filePath = path.join(folderPath, file); 13 | const stats = fs.statSync(filePath); 14 | 15 | if (stats.isFile()) { 16 | totalSize += stats.size; 17 | } else if (stats.isDirectory()) { 18 | totalSize += getFolderSize(filePath); 19 | } 20 | } 21 | 22 | return totalSize; 23 | } 24 | 25 | // Checks if creating a file will exceed the disk limit 26 | function checkSizeBeforeCreate(websiteUuid, fileSize, callback) { 27 | db.get( 28 | "SELECT disk_limit FROM websites WHERE uuid = ?", 29 | [websiteUuid], 30 | (err, row) => { 31 | if (err) { 32 | console.error("Database error:", err.message); 33 | return callback(err); 34 | } 35 | 36 | if (!row) { 37 | return callback(new Error("Website not found")); 38 | } 39 | 40 | const diskLimitBytes = row.disk_limit * 1024 * 1024; // MB -> Bytes 41 | const folderPath = path.join( 42 | __dirname, 43 | "../../storage/volumes", 44 | websiteUuid 45 | ); 46 | 47 | // Check if the folder exists 48 | if (!fs.existsSync(folderPath)) { 49 | return callback(new Error("Directory not found")); 50 | } 51 | 52 | // Calculate the current used size of the folder 53 | const usedSize = getFolderSize(folderPath); 54 | 55 | // If adding the file exceeds the limit 56 | if (usedSize + fileSize > diskLimitBytes) { 57 | return callback(new Error("Disk limit exceeded, cannot create file")); 58 | } 59 | 60 | // If passed the check, the file can be created 61 | callback(null); 62 | } 63 | ); 64 | } 65 | 66 | module.exports = { 67 | checkSizeBeforeCreate, 68 | }; 69 | -------------------------------------------------------------------------------- /storage/statistics.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /storage/volumes/test.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lumenlabss/LumenOne/e4d735f353aaa0b94014bd6b94a313b0bcc94a63/storage/volumes/test.txt -------------------------------------------------------------------------------- /views/auth/login.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Connexion - <%= appName %> 7 | 8 | 12 | 21 | 22 | 25 |
26 |
27 |
28 | 35 | 41 | 42 |
43 |

<%= appName %>

44 |

Log in to your account

45 |
46 | 47 |
52 | <% if (error) { %> 53 |
57 |
58 | 64 | 70 | 71 | <%= error %> 72 |
73 |
74 | <% } %> 75 | 76 |
77 |
78 | 83 | 91 |
92 | 93 |
94 | 99 | 107 |
108 | 109 | 128 |
129 |
130 |
131 | 132 | 133 | -------------------------------------------------------------------------------- /views/components/footer.ejs: -------------------------------------------------------------------------------- 1 | 9 | 10 | 25 | 26 |
27 | <%= appVersion %> - Load in 28 |
29 | -------------------------------------------------------------------------------- /views/components/sidebar.ejs: -------------------------------------------------------------------------------- 1 | 2 | 24 | 25 | 26 | 324 | -------------------------------------------------------------------------------- /views/error/400.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 400 - <%= appName %> 7 | 8 | 12 | 17 | 18 | 21 |
24 |

403

25 |

Bad request

26 |
27 | 28 | 29 | -------------------------------------------------------------------------------- /views/error/403.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 403 - <%= appName %> 7 | 8 | 12 | 17 | 18 | 21 |
24 |

403

25 |

Forbidden

26 |
27 | 28 | 29 | -------------------------------------------------------------------------------- /views/error/404.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 404 - <%= appName %> 7 | 8 | 12 | 17 | 18 | 21 |
24 |

404

25 |

Page not found

26 |
27 | 28 | 29 | -------------------------------------------------------------------------------- /views/error/500.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 500 - <%= appName %> 7 | 8 | 12 | 17 | 18 | 21 |
24 |

500

25 |

Internal server error

26 |
27 | 28 | 29 | -------------------------------------------------------------------------------- /views/load.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | loading... 7 | 8 | 12 | 17 | 29 | 30 | 33 |
36 |
39 |

40 | Loading... 41 |

42 |
43 | 44 | 45 | -------------------------------------------------------------------------------- /views/web/admin/customers.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Customers - <%= appName %> 7 | 8 | 12 | 47 | 48 | 49 | 50 | <%- include('../../components/sidebar') %> 51 | 52 | 53 | 54 |
55 |
56 | 57 |
58 |
59 |

60 | User Management 61 |

62 |

Manage and monitor user accounts

63 |
64 | 70 |
71 | 72 | 73 |
74 |
75 | 76 | 77 | 78 | 83 | 88 | 93 | 98 | 99 | 100 | 101 | <% users.forEach(user => { %> 102 | 103 | 106 | 120 | 127 | 170 | 171 | <% }) %> 172 | 173 |
81 | ID 82 | 86 | Username 87 | 91 | Rank 92 | 96 | Actions 97 |
104 | <%= user.id %> 105 | 107 |
108 |
111 | 112 | <%= user.username.charAt(0).toUpperCase() %> 113 | 114 |
115 | <%= user.username %> 118 |
119 |
121 | 124 | <%= user.rank %> 125 | 126 | 128 |
129 | 150 | 168 |
169 |
174 |
175 |
176 |
177 |
178 | 179 | 180 | <%- include('../../components/footer') %> 181 | 182 | 183 | 184 | -------------------------------------------------------------------------------- /views/web/admin/customers/create.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Create user - <%= appName %> 7 | 8 | 12 | 67 | 68 | 69 | 70 | <%- include('../../../components/sidebar') %> 71 | 72 | 73 | 74 |
75 |
76 |

Create new user

77 | 78 |
83 |
84 | 87 | 95 |
96 | 97 |
98 | 101 | 109 |
110 | 111 |
112 | 115 | 123 |
124 | 125 |
126 | 132 | 133 | 140 |
141 |
142 |
143 |
144 | 145 | 146 | <%- include('../../../components/footer') %> 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /views/web/admin/customers_edit.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Edit user - <%= appName %> 7 | 8 | 12 | 71 | 72 | 73 | 74 | <%- include('../../components/sidebar') %> 75 | 76 | 77 | 78 |
79 |
80 |

Edit user

81 | 82 |
87 |
88 | 91 | 99 |
100 | 101 |
102 | 105 | 112 |
113 | 114 |
115 | 118 | 126 |
127 | 128 |
129 | 135 | 136 | 143 |
144 |
145 |
146 |
147 | 148 | 149 | <%- include('../../components/footer') %> 150 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /views/web/admin/information.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | System Information - <%= appName %> 7 | 8 | 9 | 13 | 46 | 47 | 48 | 49 | <%- include('../../components/sidebar') %> 50 | 51 | 52 |
53 |
54 |

55 | System Information 56 |

57 |
58 | 59 |
60 | 61 |
62 |
63 |
64 | 65 |
66 |
67 |

Processor

68 |

69 | <%= CPUname %> <%= CPUfrequence %> 70 |

71 |

72 | <%= CPUCores %> Cores 73 | 74 |

75 |
76 |
77 |
78 | 79 |
80 |
81 |
82 | 83 |
84 |
85 |

Memory (RAM)

86 |

<%= RAMsize %> <%= RAMtype %>

87 |

<%= RAMspeed %>

88 |
89 |
90 |
91 | 92 |
93 |
94 |
95 | 96 |
97 |
98 |

Storage

99 |

<%= StorageSize %>

100 | <% if (StorageType !== "Unknown") { %> 101 |

<%= StorageType %>

102 | <% } %> 103 |
104 |
105 |
106 | 107 |
108 |
109 |
110 | 111 |
112 |
113 |

Operating System

114 |

<%= OSname %>

115 |

<%= Osversion %>

116 |
117 |
118 |
119 | 120 |
121 |
122 |
123 | 124 |
125 |
126 |

Network

127 |

IPv4: <%= NetworkIP %>

128 |

<%= NetworkSpeed %>

129 |
130 |
131 |
132 | 133 |
134 |
135 |
136 | 137 |
138 |
139 |

System Uptime

140 |

<%= SystemUptime %>

141 |

<%= LastBoot %>

142 |
143 |
144 |
145 |
146 | 147 |
148 |

LumenOne Overview

149 |
150 |
153 | LumenOne Version 154 | <%= appVersion %> 155 |
156 |
159 | Update Available 160 | <%= updateAvailable %> 161 |
162 |
165 | Total Users 166 | <%= totalUsers %> 167 |
168 |
171 | Total Websites 172 | <%= totalListWebsite %> 173 |
174 |
175 |
176 |
177 | 178 | 179 | <%- include('../../components/footer') %> 180 | 181 | 184 | 185 | 186 | -------------------------------------------------------------------------------- /views/web/admin/settings.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Account - <%= appName %> 6 | 7 | 8 | 12 | 90 | 91 | 92 | 93 | 94 | <%- include('../../components/sidebar') %> 95 | 96 |
97 |
98 |

Settings

99 | 100 |
101 |
102 |

General

103 |
104 |
105 |
106 | 109 | 115 |
116 |
117 | 120 | 126 |
127 |
128 | 131 | 137 |
138 |
139 | 142 | 149 |
150 |
151 | 154 | 160 |
161 |
162 | 165 | 174 |
175 | 176 |
177 | 178 |
179 |
180 | 186 |
187 | 188 |
189 |
190 |
191 | 192 | 193 | <%- include('../../components/footer') %> 194 | 195 | 196 | 197 | -------------------------------------------------------------------------------- /views/web/admin/subscriptions.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Subscriptions - <%= appName %> 7 | 8 | 12 | 91 | 92 | 93 | 94 | <%- include('../../components/sidebar') %> 95 | 96 | 97 | 98 |
99 |
100 |

101 | 102 | 103 |
104 |
105 |

Total Sites

106 |

107 | <%= totalListWebsite %> 108 |

109 |
110 |
111 | 112 | 113 |
114 | 115 | 116 | 117 | 120 | 121 | 124 | 125 | 128 | 131 | 132 | 133 | 134 | <% websites.forEach(site => { %> 135 | 138 | 139 | 140 | 141 | 142 | 143 | 152 | 153 | <% }) %> 154 | 155 |
118 | Username 119 | UUID 122 | Site Name 123 | Port 126 | Disk Limit (MB) 127 | 129 | Actions 130 |
<%= site.username %><%= site.uuid %><%= site.name %><%= site.port %><%= site.disk_limit %> 144 | 149 | Delete 150 | 151 |
156 |
157 | 158 | 159 |
160 | 166 |
167 |
168 |
169 | 170 | 171 | <%- include('../../components/footer') %> 172 | 173 | 174 | 175 | -------------------------------------------------------------------------------- /views/web/admin/subscriptions/create.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Create Website - <%= appName %> 7 | 8 | 12 | 71 | 72 | 73 | 74 | <%- include('../../../components/sidebar') %> 75 | 76 | 77 |
78 |
79 |

Create Website

80 | 81 |
86 |
87 | 90 | 100 |
101 | 102 |
103 | 106 | 114 |
115 | 116 |
117 | 120 | 128 |
129 | 130 |
131 | 134 | 142 |
143 | 144 |
145 | 151 | 152 | 159 |
160 |
161 |
162 |
163 | 164 | 165 | <%- include('../../../components/footer') %> 166 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /views/web/backup.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Backups - LumenOne 6 | 7 | 8 | 12 | 46 | 47 | 48 |
49 |
50 |
51 | 52 | 53 | 54 |

Backup Manager

55 |
56 | 57 |
58 |
59 |
60 |
61 | 62 |
63 |

Total Backups

64 |
65 |

12

66 |

5.4GB total storage

67 |
68 | 69 |
70 |
71 |
72 | 73 |
74 |

Last Backup

75 |
76 |

2h ago

77 |

456MB

78 |
79 | 80 |
81 |
82 |
83 | 84 |
85 |

Next Backup

86 |
87 |

22:00

88 |

Automatic daily

89 |
90 |
91 | 92 |
93 |
94 |

Create New Backup

95 |
96 |
97 |
98 |
99 | 102 | 107 |
108 |
109 | 110 | 117 |
118 |
119 |
120 | 125 | 131 |
132 |
133 |
134 | 135 |
136 |
137 |

Recent Backups

138 |
139 | 142 | 149 |
150 |
151 | 152 |
153 |
156 |
157 |
158 | 159 |
160 |
161 |

Full Backup - 2023-11-15

162 |

Created 2 hours ago • 456MB

163 |
164 |
165 |
166 | 172 | 178 | 184 |
185 |
186 | 187 |
190 |
191 |
192 | 193 |
194 |
195 |

Database Backup - 2023-11-14

196 |

Created 1 day ago • 128MB

197 |
198 |
199 |
200 | 206 | 212 | 218 |
219 |
220 | 221 |
224 |
225 |
226 | 227 |
228 |
229 |

Files Backup - 2023-11-13

230 |

Created 2 days ago • 342MB

231 |
232 |
233 |
234 | 240 | 246 | 252 |
253 |
254 | 255 |
258 |
259 |
260 | 261 |
262 |
263 |

Full Backup - 2023-11-12

264 |

Created 3 days ago • 498MB

265 |
266 |
267 |
268 | 274 | 280 | 286 |
287 |
288 |
289 | 290 |
293 |
Showing 4 of 12 backups
294 |
295 | 301 | Page 1 of 3 302 | 305 |
306 |
307 |
308 |
309 |
310 | 311 | 318 | 319 | 320 | -------------------------------------------------------------------------------- /views/web/edit/files.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Edit File - <%= appName %> 7 | 8 | 65 | 66 | 67 | 68 | <%- include('../../components/sidebar') %> 69 | 70 | 71 | 72 |
73 |
74 |

Edit File

75 | 76 | <% if (error) { %> 77 |
81 |
82 | 88 | 94 | 95 | <%= error %> 96 |
97 |
98 | <% } %> 99 | 100 | 101 |
102 |
106 |
107 | 112 | 120 |
121 | 122 |
123 | 128 | 137 |
138 | 139 | 145 |
146 |
147 | 148 | 152 | Back to File Manager 153 | 154 |
155 |
156 | 157 | 158 | <%- include('../../components/footer') %> 159 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /views/web/list.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Websites & Domains - <%= appName %> 7 | 8 | 12 | 45 | 46 | 47 | 48 | <%- include('../components/sidebar') %> 49 | 50 | 51 | 52 |
53 |
54 |
55 |

My Websites

56 |
57 | 58 | <% if (websites.length === 0) { %> 59 |
60 |
61 | 68 | 74 | 75 |
76 |

No Websites Yet

77 |

78 | Start by creating your first website to begin building your online 79 | presence. 80 |

81 |
82 | <% } else { %> 83 |
84 | <% websites.forEach(site => { %> 85 |
86 |
87 |

<%= site.name %>

88 |
89 |
90 |
93 | Port 94 | <%= site.port %> 95 |
96 |
99 | Disk Limit 100 | <%= site.disk_limit %> MB 101 |
102 |
103 |
104 | 110 | 126 |
127 |
128 | <% }) %> 129 |
130 | <% } %> 131 |
132 |
133 | 134 | 135 | <%- include('../components/footer') %> 136 | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /views/web/manage.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Manages - <%= appName %> 7 | 8 | 12 | 79 | 80 | 81 | 82 | <%- include('../components/sidebar') %> 83 | 84 | 85 | 86 |
87 |
88 |

Website Manager

89 |
90 | 91 |
92 |
Port
93 |
94 | 102 | 105 | 108 | 109 | 110 |  <%= website.port %> 111 |
112 |
113 | 114 | 115 |
116 |
Disk Limit
117 |
118 | 126 | 129 | 130 |  <%= website.disk_limit %> MB 131 |
132 |
133 |
134 | 135 |
136 |

Files Manager

137 | 138 | 139 |
144 | 151 | 157 |
158 |
159 | 160 | 161 | 162 | 165 | 168 | 171 | 172 | 173 | 174 | <% files.forEach(file => { %> 175 | 176 | 177 | 192 | 193 | 194 | <% }) %> 195 | 196 |
163 | File Name 164 | 166 | Actions 167 | 169 | Size (MB) 170 |
<%= file.name %> 178 |
179 | 184 | 190 |
191 |
<%= file.size %> MB
197 |
198 |
199 |
200 |
201 | 202 | 203 | <%- include('../components/footer') %> 204 | 205 | 206 | 207 | -------------------------------------------------------------------------------- /views/web/statistics/domains.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | List - <%= appName %> 6 | 7 | 8 | 12 | 100 | 101 | 102 | <%- include('../../components/sidebar') %> 103 | 104 | 105 |
106 |
107 |
108 |

Statistics

109 |
110 | 111 | 135 |
136 |
137 | 138 | <%- include('../../components/footer') %> 139 | 140 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /views/web/statistics/statistics.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Statistics - <%= appName %> 6 | 7 | 8 | 9 | 13 | 44 | 45 | 46 | <%- include('../../components/sidebar') %> 47 | 48 | 49 |
50 |
51 |
52 | 53 | 54 | 55 |
56 | 57 |
58 |
59 |
60 |
61 | 62 |
63 |

Total Visits

64 |
65 |

<%= visitCount %>

66 |
67 | 68 |
69 |
70 |
71 | 72 |
73 |

Page Views

74 |
75 |

SOON

76 |
77 | 78 |
79 |
80 |
81 | 82 |
83 |

Total Files

84 |
85 |

SOON

86 |
87 | 88 |
89 |
90 |
91 | 92 |
93 |

Visits This Month

94 |
95 |

SOON

96 |
97 |
98 | 99 |
100 |
101 |

Monthly Visits - SOON

102 | 107 |
108 |
109 | 110 |
111 |
112 | 113 |
114 |
115 |

Top Pages - SOON

116 |
117 |
120 |
121 |
122 | 126 |
127 | /index.html 128 |
129 | 1,024 visits 130 |
131 |
134 |
135 |
136 | 140 |
141 | /products 142 |
143 | 872 visits 144 |
145 |
148 |
149 |
150 | 154 |
155 | /about 156 |
157 | 645 visits 158 |
159 |
160 |
161 | 162 |
163 |

Traffic Sources - SOON

164 |
165 | 166 |
167 |
168 |
169 |
170 |
171 | 172 | <%- include('../../components/footer') %> 173 | 174 | 270 | 271 | 272 | --------------------------------------------------------------------------------