├── public └── dbdl │ └── test.txt ├── frontend ├── src │ ├── pages │ │ ├── admin │ │ │ ├── PanelsPage.css │ │ │ ├── AgentsPage.css │ │ │ ├── AdminHomePage.module.css │ │ │ ├── AdminSettingsPage.module.css │ │ │ └── AdminLogsPage.css │ │ ├── agent │ │ │ ├── AgentLogsPage.css │ │ │ ├── AgentHomePage.jsx │ │ │ ├── UsersPage.css │ │ │ └── AgentSettingsPage.module.css │ │ ├── NotFoundPage.jsx │ │ └── Login.css │ ├── assets │ │ ├── amn.png │ │ ├── bm.png │ │ ├── logo.png │ │ ├── power.png │ │ ├── accept.png │ │ ├── crypto.png │ │ ├── loading.gif │ │ ├── zarinpal.png │ │ ├── fonts │ │ │ ├── vazir.ttf │ │ │ ├── Inter-Bold.ttf │ │ │ ├── Inter-Medium.ttf │ │ │ └── Inter-Regular.ttf │ │ └── svg │ │ │ ├── chevron-down.svg │ │ │ ├── checked.svg │ │ │ ├── expired.svg │ │ │ ├── x-mark.svg │ │ │ ├── arrow-left.svg │ │ │ ├── arrow-right.svg │ │ │ ├── limited.svg │ │ │ ├── search.svg │ │ │ ├── plus.svg │ │ │ ├── login.svg │ │ │ ├── disabled.svg │ │ │ ├── person.svg │ │ │ ├── three-dots.svg │ │ │ ├── add-user.svg │ │ │ ├── refresh.svg │ │ │ ├── edit.svg │ │ │ ├── link.svg │ │ │ ├── pie-chart.svg │ │ │ ├── refreshWhite.svg │ │ │ ├── active.svg │ │ │ ├── anonym.svg │ │ │ ├── accept.svg │ │ │ ├── download.svg │ │ │ ├── paste.svg │ │ │ ├── users.svg │ │ │ ├── lock.svg │ │ │ ├── lockWhite.svg │ │ │ ├── delete.svg │ │ │ ├── delete2.svg │ │ │ ├── graph-bar.svg │ │ │ ├── db.svg │ │ │ ├── LoginAsAgent.svg │ │ │ ├── document.svg │ │ │ ├── spinner.svg │ │ │ ├── exclamation-mark.svg │ │ │ ├── qrcode.svg │ │ │ ├── qr-code.svg │ │ │ ├── filter.svg │ │ │ ├── switch.svg │ │ │ ├── panel.svg │ │ │ ├── restore.svg │ │ │ ├── people.svg │ │ │ ├── logo.svg │ │ │ ├── power.svg │ │ │ ├── powerWhite.svg │ │ │ ├── death.svg │ │ │ ├── data-center.svg │ │ │ ├── cart.svg │ │ │ ├── cart2.svg │ │ │ └── dldb.svg │ ├── components │ │ ├── UniversalLogout.css │ │ ├── admin │ │ │ ├── CreateAgent.module.css │ │ │ ├── EditPanel.module.css │ │ │ ├── EditAgent.module.css │ │ │ ├── CreatePanel.css │ │ │ ├── AgentStats.jsx │ │ │ ├── PanelStats.jsx │ │ │ ├── UsageStats.jsx │ │ │ ├── VerifyDelete.jsx │ │ │ ├── VerifyLogout.jsx │ │ │ ├── VerifyReset.jsx │ │ │ ├── VerifyUnlock.jsx │ │ │ ├── LogsList.jsx │ │ │ ├── EditPanel.jsx │ │ │ ├── PanelsTable.jsx │ │ │ └── AgentsTable.jsx │ │ ├── Pagination.css │ │ ├── LeadingIcon.jsx │ │ ├── LoadingGif.css │ │ ├── CircularProgress.jsx │ │ ├── LeadingIcon.css │ │ ├── TimerBar.jsx │ │ ├── agent │ │ │ ├── SubscriptionActions.css │ │ │ ├── UsageStats.css │ │ │ ├── CreateUser.module.css │ │ │ ├── EditUser.module.css │ │ │ └── UsageStats.jsx │ │ ├── ProgressBar.jsx │ │ ├── Accordion.jsx │ │ ├── Button.jsx │ │ ├── EmptyTable.css │ │ ├── AmneziaFilter.jsx │ │ ├── TimerBar.module.css │ │ ├── Tooltip.jsx │ │ ├── EmptyTable.jsx │ │ ├── AmneziaFilter.module.css │ │ ├── form │ │ │ └── inputs │ │ │ │ ├── FileInput.css │ │ │ │ ├── ValueAdjuster.module.css │ │ │ │ ├── MultiSelect2.jsx │ │ │ │ ├── ValueAdjuster.jsx │ │ │ │ ├── MultiSelect3.jsx │ │ │ │ ├── MultiSelect5.jsx │ │ │ │ ├── MultiSelect4.jsx │ │ │ │ ├── IOSSwitch.jsx │ │ │ │ ├── MultiSelect.jsx │ │ │ │ └── MultiSelect.css │ │ ├── Tooltip.css │ │ ├── Search.jsx │ │ ├── Search.css │ │ ├── MessageCard.jsx │ │ ├── QRCode.css │ │ ├── UniversalLogout.jsx │ │ ├── ProgressBar.css │ │ ├── Navbar.css │ │ ├── Modal.jsx │ │ ├── ErrorCard.jsx │ │ ├── OkCard.jsx │ │ ├── Modal.css │ │ ├── ShoppingCard.jsx │ │ ├── UsersTableAccordion.jsx │ │ ├── Button.css │ │ ├── Dropdown.css │ │ ├── ErrorCard.module.css │ │ ├── Navbar.jsx │ │ └── Dropdown.jsx │ ├── debug.log │ ├── utils │ │ ├── gbOrTb.js │ │ ├── file-size-util.js │ │ ├── status-util.js │ │ └── expire-time-util.js │ ├── index.js │ ├── hooks │ │ └── use-hover.js │ ├── axiosConfig.js │ └── App.js ├── .env ├── .dockerignore ├── Dockerfile ├── windows.json ├── public │ └── index.html └── package.json ├── knp_manager_bot └── panels.txt ├── secondary_backend ├── .dockerignore ├── Dockerfile ├── package.json └── run.sh ├── teritary_backend ├── .dockerignore ├── Dockerfile ├── package.json └── run.sh ├── wireguard_wrapper ├── .dockerignore ├── .env ├── Dockerfile ├── run.sh └── notes.txt ├── readme ├── dev_notes.txt ├── 1.jpg ├── 2.jpg ├── 3.jpg ├── 4.jpg ├── 5.jpg ├── 6.jpg ├── 7.jpg ├── 8.jpg ├── 9.jpg └── 10.jpg ├── install.sh ├── get_cert_urls.js ├── .dockerignore ├── Dockerfile ├── .env ├── amnezia_wrapper ├── sync.js ├── .env ├── run.sh ├── test_input.json ├── restart_awg_container.js ├── notes.txt └── decoder.py ├── nginx.conf ├── .gitignore ├── backup_config.json ├── README.md ├── ecosystem.config.js ├── custom_sub └── 404.html ├── package.json ├── testing ├── fix_undefined.js └── fix_time.js ├── cli.sh ├── docker-compose.yml ├── main.conf ├── agent_scanner.js ├── db_interface.js └── backup_service.js /public/dbdl/test.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/pages/admin/PanelsPage.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/pages/agent/AgentLogsPage.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/.env: -------------------------------------------------------------------------------- 1 | DANGEROUSLY_DISABLE_HOST_CHECK=true -------------------------------------------------------------------------------- /knp_manager_bot/panels.txt: -------------------------------------------------------------------------------- 1 | knp_local4,http://localhost:5000 2 | -------------------------------------------------------------------------------- /secondary_backend/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log -------------------------------------------------------------------------------- /teritary_backend/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log -------------------------------------------------------------------------------- /wireguard_wrapper/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log -------------------------------------------------------------------------------- /readme/dev_notes.txt: -------------------------------------------------------------------------------- 1 | any change to endpoints ==> logs and access items -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hadish100/kn-panel/HEAD/install.sh -------------------------------------------------------------------------------- /readme/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hadish100/kn-panel/HEAD/readme/1.jpg -------------------------------------------------------------------------------- /readme/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hadish100/kn-panel/HEAD/readme/2.jpg -------------------------------------------------------------------------------- /readme/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hadish100/kn-panel/HEAD/readme/3.jpg -------------------------------------------------------------------------------- /readme/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hadish100/kn-panel/HEAD/readme/4.jpg -------------------------------------------------------------------------------- /readme/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hadish100/kn-panel/HEAD/readme/5.jpg -------------------------------------------------------------------------------- /readme/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hadish100/kn-panel/HEAD/readme/6.jpg -------------------------------------------------------------------------------- /readme/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hadish100/kn-panel/HEAD/readme/7.jpg -------------------------------------------------------------------------------- /readme/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hadish100/kn-panel/HEAD/readme/8.jpg -------------------------------------------------------------------------------- /readme/9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hadish100/kn-panel/HEAD/readme/9.jpg -------------------------------------------------------------------------------- /readme/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hadish100/kn-panel/HEAD/readme/10.jpg -------------------------------------------------------------------------------- /frontend/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .git 3 | .gitignore 4 | README.md 5 | build 6 | dist -------------------------------------------------------------------------------- /frontend/src/assets/amn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hadish100/kn-panel/HEAD/frontend/src/assets/amn.png -------------------------------------------------------------------------------- /frontend/src/assets/bm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hadish100/kn-panel/HEAD/frontend/src/assets/bm.png -------------------------------------------------------------------------------- /frontend/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hadish100/kn-panel/HEAD/frontend/src/assets/logo.png -------------------------------------------------------------------------------- /frontend/src/assets/power.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hadish100/kn-panel/HEAD/frontend/src/assets/power.png -------------------------------------------------------------------------------- /frontend/src/assets/accept.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hadish100/kn-panel/HEAD/frontend/src/assets/accept.png -------------------------------------------------------------------------------- /frontend/src/assets/crypto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hadish100/kn-panel/HEAD/frontend/src/assets/crypto.png -------------------------------------------------------------------------------- /frontend/src/assets/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hadish100/kn-panel/HEAD/frontend/src/assets/loading.gif -------------------------------------------------------------------------------- /frontend/src/assets/zarinpal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hadish100/kn-panel/HEAD/frontend/src/assets/zarinpal.png -------------------------------------------------------------------------------- /frontend/src/assets/fonts/vazir.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hadish100/kn-panel/HEAD/frontend/src/assets/fonts/vazir.ttf -------------------------------------------------------------------------------- /frontend/src/assets/fonts/Inter-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hadish100/kn-panel/HEAD/frontend/src/assets/fonts/Inter-Bold.ttf -------------------------------------------------------------------------------- /frontend/src/assets/fonts/Inter-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hadish100/kn-panel/HEAD/frontend/src/assets/fonts/Inter-Medium.ttf -------------------------------------------------------------------------------- /frontend/src/assets/fonts/Inter-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hadish100/kn-panel/HEAD/frontend/src/assets/fonts/Inter-Regular.ttf -------------------------------------------------------------------------------- /wireguard_wrapper/.env: -------------------------------------------------------------------------------- 1 | SERVER_ADDRESS=http://92.60.70.175:51821 2 | SERVER_PASSWORD=123456 3 | SUDO_USERNAME=admin 4 | SUDO_PASSWORD=123456 5 | -------------------------------------------------------------------------------- /frontend/src/components/UniversalLogout.css: -------------------------------------------------------------------------------- 1 | .lo_button 2 | { 3 | position:absolute; 4 | top:20px; 5 | right:25px; 6 | border:none; 7 | } -------------------------------------------------------------------------------- /frontend/src/components/admin/CreateAgent.module.css: -------------------------------------------------------------------------------- 1 | @media (max-width: 740px) { 2 | .modal__form__row { 3 | flex-direction: column; 4 | } 5 | } -------------------------------------------------------------------------------- /frontend/src/pages/admin/AgentsPage.css: -------------------------------------------------------------------------------- 1 | .admin_users_body { 2 | padding: 1.5rem; 3 | } 4 | 5 | .refresh svg { 6 | width: 15px; 7 | height: 15px; 8 | } -------------------------------------------------------------------------------- /get_cert_urls.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config() 2 | 3 | console.log(`PANEL_DOMAIN: ${process.env.PANEL_URL}`); 4 | console.log(`SUBLINK_DOMAIN: ${process.env.SUB_URL}`); -------------------------------------------------------------------------------- /frontend/src/pages/admin/AdminHomePage.module.css: -------------------------------------------------------------------------------- 1 | @media (max-width: 768px) { 2 | .charts-subinfo { 3 | flex-direction: column; 4 | margin-left: 2rem; 5 | } 6 | } -------------------------------------------------------------------------------- /frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18 2 | 3 | WORKDIR /knp_frontend 4 | 5 | COPY package*.json ./ 6 | 7 | RUN npm install 8 | 9 | COPY . . 10 | 11 | CMD [ "npm", "start" ] -------------------------------------------------------------------------------- /wireguard_wrapper/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18 2 | 3 | WORKDIR /knww 4 | 5 | COPY package*.json . 6 | 7 | RUN npm install 8 | 9 | COPY . . 10 | 11 | EXPOSE 7001 7002 12 | 13 | CMD [ "node", "server.js" ] -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | frontend 2 | knp_manager_bot 3 | node_modules 4 | readme 5 | secondary_backend 6 | teritary_backend 7 | .git 8 | .gitignore 9 | commands.sh 10 | README.md 11 | amnezia_wrapper 12 | wireguard_wrapper -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18 2 | 3 | WORKDIR /knp_backend 4 | 5 | RUN npm install pm2 -g 6 | 7 | COPY package*.json ./ 8 | 9 | RUN npm install 10 | 11 | COPY . . 12 | 13 | CMD ["pm2-runtime", "start", "ecosystem.config.js"] -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | RELEASE=TEST 2 | # ARMAN | ALI | V 3 | SERVER_PORT=5000 4 | # + proxy port + react port 5 | SUB_URL=test2.ir 6 | PANEL_URL=test.ir 7 | ACCESS_API_KEY=resllmwriewfeujeh3i3ifdkmwheweljedifefhyr 8 | ZARINPAL_TOKEN= 9 | NOWPAYMENTS_TOKEN= 10 | -------------------------------------------------------------------------------- /frontend/src/debug.log: -------------------------------------------------------------------------------- 1 | [0807/233246.665:ERROR:registration_protocol_win.cc(107)] CreateFile: The system cannot find the file specified. (0x2) 2 | [0808/030651.286:ERROR:registration_protocol_win.cc(107)] CreateFile: The system cannot find the file specified. (0x2) 3 | -------------------------------------------------------------------------------- /frontend/windows.json: -------------------------------------------------------------------------------- 1 | { 2 | "proxy": "http://92.60.70.175:5000", 3 | "start": "set NODE_ENV=production && set GENERATE_SOURCEMAP=false && set DISABLE_ESLINT_PLUGIN=true && set BROWSER=none && set PORT=3000 && react-scripts start", 4 | "test": "hi" 5 | } 6 | -------------------------------------------------------------------------------- /frontend/src/utils/gbOrTb.js: -------------------------------------------------------------------------------- 1 | const gbOrTb = (num) => { 2 | 3 | if(num<1000) return num + " GB"; 4 | else 5 | { 6 | let gb = num/1000; 7 | return gb.toFixed(2) + " TB"; 8 | } 9 | 10 | }; 11 | 12 | export default gbOrTb -------------------------------------------------------------------------------- /frontend/src/assets/svg/chevron-down.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /secondary_backend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18 2 | 3 | WORKDIR /knsb 4 | 5 | COPY package*.json . 6 | 7 | RUN npm install 8 | 9 | VOLUME /var/lib/marzban 10 | VOLUME /opt/marzban 11 | 12 | COPY . . 13 | 14 | EXPOSE 7002 15 | 16 | CMD [ "node", "server.js" ] -------------------------------------------------------------------------------- /teritary_backend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18 2 | 3 | WORKDIR /kntb 4 | 5 | COPY package*.json . 6 | 7 | RUN npm install 8 | 9 | VOLUME /var/lib/marzban 10 | VOLUME /opt/marzban 11 | 12 | COPY . . 13 | 14 | EXPOSE 7002 15 | 16 | CMD [ "node", "server.js" ] -------------------------------------------------------------------------------- /amnezia_wrapper/sync.js: -------------------------------------------------------------------------------- 1 | const { 2 | sleep, 3 | $sync_accounting, 4 | } = require('./utils.js'); 5 | 6 | async function init() 7 | { 8 | while(true) 9 | { 10 | await $sync_accounting(); 11 | await sleep(90); 12 | } 13 | } 14 | 15 | init(); -------------------------------------------------------------------------------- /frontend/src/components/Pagination.css: -------------------------------------------------------------------------------- 1 | .pagination { 2 | display: flex; 3 | align-items: center; 4 | justify-content: flex-end; 5 | font-family: "Inter"; 6 | font-weight: 600; 7 | } 8 | 9 | .pagination-left-btn svg, 10 | .pagination-right-btn svg { 11 | width: 15px; 12 | } -------------------------------------------------------------------------------- /amnezia_wrapper/.env: -------------------------------------------------------------------------------- 1 | SERVER_ADDRESS=tr1.diggerv.cloud 2 | ENDPOINT_ADDRESS=lodgamer.com 3 | SERVER_PORT=7001 4 | SUDO_USERNAME=admin 5 | SUDO_PASSWORD=L3M593XVGP 6 | JWT_SECRET_KEY=resllmwriewfeujeh3i3ifdkmwheweljedifefhyr 7 | SUB_JWT_SECRET=wzcggjkhjnqpe4cz9j5l25jrade85i 8 | COUNTRY_EMOJI=🇹🇷 9 | # 🇳🇱 -------------------------------------------------------------------------------- /frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { createRoot } from "react-dom/client"; 3 | import App from "./App"; 4 | import "./index.css"; 5 | 6 | const rootElement = document.getElementById("root"); 7 | const root = createRoot(rootElement); 8 | 9 | root.render( 10 | 11 | ) -------------------------------------------------------------------------------- /amnezia_wrapper/run.sh: -------------------------------------------------------------------------------- 1 | # install 2 | docker pull hadish10/knaw && docker run -p 7002:7002 -p 7001:7001 -d --restart always hadish10/knaw 3 | # remove 4 | docker stop $(docker ps -a -q --filter "ancestor=hadish10/knaw") && docker rm $(docker ps -a -q --filter "ancestor=hadish10/knaw") && docker rmi hadish10/knaw -f -------------------------------------------------------------------------------- /wireguard_wrapper/run.sh: -------------------------------------------------------------------------------- 1 | # install 2 | docker pull hadish10/knww && docker run -p 7002:7002 -p 7001:7001 -d --restart always hadish10/knww 3 | # remove 4 | docker stop $(docker ps -a -q --filter "ancestor=hadish10/knww") && docker rm $(docker ps -a -q --filter "ancestor=hadish10/knww") && docker rmi hadish10/knww -f -------------------------------------------------------------------------------- /frontend/src/assets/svg/checked.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/svg/expired.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/svg/x-mark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/components/LeadingIcon.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import "./LeadingIcon.css"; 4 | 5 | const LeadingIcon = ({ children }) => { 6 | return ( 7 |
8 | {children} 9 |
10 | ); 11 | } 12 | 13 | export default LeadingIcon; -------------------------------------------------------------------------------- /amnezia_wrapper/test_input.json: -------------------------------------------------------------------------------- 1 | { 2 | "config_version": 1, 3 | "api_endpoint": "https://lodgamer.com/sub", 4 | "protocol": "awg", 5 | "name": "EB_Pooya", 6 | "api_key": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IkVCX1Bvb3lhIiwiaWF0IjoxNzMzODI1NDE5fQ.P9bIyGGFGaOqiJWNNotXe06MLckI94cA2Abui5s2glE" 7 | } -------------------------------------------------------------------------------- /frontend/src/assets/svg/arrow-left.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/svg/arrow-right.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | user nginx; 2 | worker_processes auto; 3 | 4 | events { 5 | worker_connections 1024; 6 | } 7 | 8 | http { 9 | include /etc/nginx/mime.types; 10 | default_type application/octet-stream; 11 | 12 | sendfile on; 13 | keepalive_timeout 65; 14 | 15 | include /etc/nginx/conf.d/*.conf; 16 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | /.pnp 4 | .pnp.js 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | 23 | install_org.sh -------------------------------------------------------------------------------- /amnezia_wrapper/restart_awg_container.js: -------------------------------------------------------------------------------- 1 | const {restart_awg_container} = require('./utils.js'); 2 | 3 | async function init() 4 | { 5 | await restart_awg_container(); 6 | process.exit(0); 7 | } 8 | 9 | init(); 10 | 11 | // echo "alias awg-restart='node /root/wrapper/restart_awg_container.js'" >> ~/.bashrc && source ~/.bashrc -------------------------------------------------------------------------------- /frontend/src/assets/svg/limited.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/svg/search.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/components/admin/EditPanel.module.css: -------------------------------------------------------------------------------- 1 | .footer { 2 | padding-top: 1rem; 3 | gap: 1rem; 4 | } 5 | 6 | @media (max-width: 410px) { 7 | .footer { 8 | align-items: flex-start; 9 | flex-direction: column; 10 | } 11 | 12 | .primaryButtons, 13 | .primaryButtons>* { 14 | width: 100%; 15 | } 16 | } -------------------------------------------------------------------------------- /frontend/src/components/LoadingGif.css: -------------------------------------------------------------------------------- 1 | .loading_gif 2 | { 3 | width:100px; 4 | height:100px; 5 | background:transparent; 6 | opacity:1; 7 | } 8 | 9 | .loading_gif_container 10 | { 11 | display:flex; 12 | justify-content:center; 13 | align-items:center; 14 | align-content:center; 15 | flex-direction:row; 16 | } -------------------------------------------------------------------------------- /frontend/src/assets/svg/plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/src/components/CircularProgress.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import CircularProgress from '@mui/material/CircularProgress'; 3 | import Box from '@mui/material/Box'; 4 | 5 | export default function CircularIndeterminate() { 6 | return ( 7 | 8 | 9 | 10 | ); 11 | } -------------------------------------------------------------------------------- /frontend/src/assets/svg/login.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/svg/disabled.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/utils/file-size-util.js: -------------------------------------------------------------------------------- 1 | const convertData = (data) => { 2 | const units = ['B', 'KB', 'MB', 'GB', 'TB']; 3 | let size = data; 4 | let unitIndex = 0; 5 | 6 | while (size >= 1024 && unitIndex < units.length - 1) { 7 | size /= 1024; 8 | unitIndex++; 9 | } 10 | return size.toFixed(2) + ' ' + units[unitIndex]; 11 | }; 12 | 13 | export default convertData -------------------------------------------------------------------------------- /frontend/src/assets/svg/person.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /secondary_backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "secondary_backend", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "hadish", 10 | "license": "ISC", 11 | "dependencies": { 12 | "adm-zip": "^0.5.10", 13 | "express": "^4.18.2", 14 | "sqlite3": "^5.1.6" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /teritary_backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "secondary_backend", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "hadish", 10 | "license": "ISC", 11 | "dependencies": { 12 | "adm-zip": "^0.5.10", 13 | "express": "^4.18.2", 14 | "mysql2": "^3.6.1" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /frontend/src/components/LeadingIcon.css: -------------------------------------------------------------------------------- 1 | .leading-icon { 2 | margin-right: 0.9rem; 3 | background-color: #4d7ee766; 4 | border-radius: 0.5rem; 5 | padding: 4px 4px 0 4px; 6 | } 7 | 8 | .leading-icon svg, 9 | img { 10 | background-color: #4D7DE7; 11 | color: white; 12 | opacity: 0.6; 13 | border-radius: 0.5rem; 14 | width: 38px; 15 | height: 38px; 16 | padding: 7px; 17 | stroke-width: 1.5; 18 | } -------------------------------------------------------------------------------- /frontend/src/assets/svg/three-dots.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/hooks/use-hover.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | const useHover = () => { 4 | const [isHovered, setIsHovered] = useState(false); 5 | 6 | const handleMouseEnter = () => { 7 | setIsHovered(true); 8 | }; 9 | 10 | const handleMouseLeave = () => { 11 | setIsHovered(false); 12 | }; 13 | 14 | return { isHovered, handleMouseEnter, handleMouseLeave }; 15 | } 16 | 17 | export default useHover -------------------------------------------------------------------------------- /frontend/src/assets/svg/add-user.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backup_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "email": { 3 | "disabled": true, 4 | "sender": { 5 | "service": "Gmail", 6 | "auth": { 7 | "user": "backup@gmail.com", 8 | "pass": "12345" 9 | } 10 | }, 11 | "receiver": [] 12 | }, 13 | "telegram": { 14 | "disabled": true, 15 | "bot_token": "", 16 | "chat_id": "" 17 | }, 18 | "interval": 1800 19 | } -------------------------------------------------------------------------------- /frontend/src/assets/svg/refresh.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/components/TimerBar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import styles from './TimerBar.module.css' 4 | 5 | const TimerBar = ({ duration }) => { 6 | return ( 7 |
8 |
9 |
10 |
11 |
12 | ) 13 | } 14 | 15 | export default TimerBar -------------------------------------------------------------------------------- /frontend/src/assets/svg/edit.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/svg/link.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/svg/pie-chart.svg: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /frontend/src/assets/svg/refreshWhite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/components/agent/SubscriptionActions.css: -------------------------------------------------------------------------------- 1 | .subscription-section { 2 | width: 100%; 3 | } 4 | 5 | .subscription-section__buttons { 6 | display: flex; 7 | justify-content: space-between; 8 | } 9 | 10 | .subscription-section__button { 11 | background-color: transparent; 12 | border: none; 13 | border-radius: 4px; 14 | color: #1a202c; 15 | position: relative; 16 | } 17 | 18 | .subscription-section__button svg { 19 | cursor: pointer; 20 | } -------------------------------------------------------------------------------- /frontend/src/assets/svg/active.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/svg/anonym.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | -------------------------------------------------------------------------------- /frontend/src/assets/svg/accept.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /frontend/src/utils/status-util.js: -------------------------------------------------------------------------------- 1 | // const handleUserStatus = ({ dataUsage, totalData, isActive }) => { 2 | // if (dataUsage >= totalData) { 3 | // return "limited"; 4 | // } else if (isActive) { 5 | // return "active"; 6 | // } else { 7 | // return "expired"; 8 | // } 9 | // }; 10 | 11 | // export default handleUserStatus; 12 | 13 | 14 | // const handleUserStatus = ({ status }) => { 15 | // return status 16 | // }; 17 | 18 | // export default handleUserStatus; -------------------------------------------------------------------------------- /secondary_backend/run.sh: -------------------------------------------------------------------------------- 1 | # install 2 | docker pull hadish10/knsb && docker run -v /var/lib/marzban:/var/lib/marzban -v /opt/marzban:/opt/marzban -p 7002:7002 -d --restart always hadish10/knsb 3 | # update 4 | docker stop $(docker ps -a -q --filter "ancestor=hadish10/knsb") && docker rm $(docker ps -a -q --filter "ancestor=hadish10/knsb") && docker rmi hadish10/knsb -f && docker pull hadish10/knsb && docker run -v /var/lib/marzban:/var/lib/marzban -v /opt/marzban:/opt/marzban -p 7002:7002 -d --restart always hadish10/knsb -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | KN PANEL 10 | 11 | 12 | 13 |
14 | 15 | 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /frontend/src/components/ProgressBar.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import "./ProgressBar.css"; 4 | 5 | const ProgressBar = ({ dataUsage, totalData, status }) => { 6 | return ( 7 |
8 |
9 |
10 |
11 |
12 | ); 13 | } 14 | 15 | export default ProgressBar -------------------------------------------------------------------------------- /frontend/src/components/Accordion.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { motion } from "framer-motion" 3 | 4 | const Accordion = ({ children }) => { 5 | return ( 6 | 12 | {children} 13 | 14 | ) 15 | } 16 | 17 | export default Accordion -------------------------------------------------------------------------------- /frontend/src/pages/NotFoundPage.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ReactComponent as PageNotFoundSVG } from '../assets/svg/page-not-found.svg' 4 | 5 | const NotFoundPage = () => { 6 | return ( 7 |
16 | 17 |
18 | ) 19 | } 20 | 21 | export default NotFoundPage -------------------------------------------------------------------------------- /wireguard_wrapper/notes.txt: -------------------------------------------------------------------------------- 1 | docker run --detach \ 2 | --name wg-easy \ 3 | --env LANG=en \ 4 | --env WG_HOST=92.60.70.175 \ 5 | --env PASSWORD_HASH='$2a$12$9zywptzEL/nmr5cmGabyuO/IKSPgUjTTW98pNlLfd4XapgiZYmFSu' \ 6 | --env PORT=51821 \ 7 | --env WG_PORT=51820 \ 8 | --volume ~/.wg-easy:/etc/wireguard \ 9 | --publish 51820:51820/udp \ 10 | --publish 51821:51821/tcp \ 11 | --cap-add NET_ADMIN \ 12 | --cap-add SYS_MODULE \ 13 | --sysctl 'net.ipv4.conf.all.src_valid_mark=1' \ 14 | --sysctl 'net.ipv4.ip_forward=1' \ 15 | --restart unless-stopped \ 16 | ghcr.io/wg-easy/wg-easy -------------------------------------------------------------------------------- /frontend/src/components/admin/EditAgent.module.css: -------------------------------------------------------------------------------- 1 | .footer { 2 | padding-top: 1rem; 3 | gap: 1rem; 4 | } 5 | 6 | @media (max-width: 470px) { 7 | .footer { 8 | align-items: flex-start; 9 | flex-direction: column; 10 | } 11 | 12 | .primaryButtons, 13 | .primaryButtons>* { 14 | width: 100%; 15 | } 16 | 17 | .multiSelect { 18 | font-size: 10px; 19 | } 20 | 21 | .buttons-row { 22 | flex-direction: column; 23 | } 24 | } 25 | 26 | @media (max-width: 740px) { 27 | .modal__form__row { 28 | flex-direction: column; 29 | } 30 | } -------------------------------------------------------------------------------- /frontend/src/assets/svg/download.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /frontend/src/components/Button.jsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | import "./Button.css" 4 | import { motion } from "framer-motion" 5 | 6 | const Button = ({ children, onClick, className, disabled, onMouseEnter, onMouseLeave }) => { 7 | return ( 8 | 16 | {children} 17 | 18 | ) 19 | } 20 | 21 | export default Button -------------------------------------------------------------------------------- /frontend/src/assets/svg/paste.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/components/EmptyTable.css: -------------------------------------------------------------------------------- 1 | .empty-row:hover { 2 | background-color: transparent !important; 3 | cursor: default; 4 | } 5 | 6 | .empty-table { 7 | display: flex; 8 | flex-direction: column; 9 | justify-content: center; 10 | align-items: center; 11 | gap: 1rem; 12 | padding: 2rem 1.25rem; 13 | } 14 | 15 | .empty-table>svg { 16 | max-width: 200px; 17 | max-height: 200px; 18 | } 19 | 20 | .empty-table>h6 { 21 | font-family: InterMedium; 22 | font-size: 1rem; 23 | font-weight: 400; 24 | color: var(--dark-clr-200); 25 | } 26 | 27 | .empty-table>button { 28 | text-transform: capitalize; 29 | } -------------------------------------------------------------------------------- /frontend/src/components/AmneziaFilter.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styles from './AmneziaFilter.module.css' 3 | import Amnezia from '../assets/amn.png' 4 | 5 | const AmneziaFilter = ({ enabled,setEnabled }) => { 6 | 7 | 8 | // if enabled amnezia-filter and amnezia-filter-active else amnezia-filter 9 | 10 | return ( 11 |
12 | Amnezia Filter setEnabled(!enabled)} /> 13 |
14 | ) 15 | } 16 | 17 | export default AmneziaFilter -------------------------------------------------------------------------------- /frontend/src/assets/svg/users.svg: -------------------------------------------------------------------------------- 1 | 12 | 13 | -------------------------------------------------------------------------------- /frontend/src/components/TimerBar.module.css: -------------------------------------------------------------------------------- 1 | .timer-bar { 2 | width: 100%; 3 | height: 6px; 4 | background-color: #e2e8f0; 5 | border-radius: 50px; 6 | margin: 10px 0; 7 | position: relative; 8 | display: flex; 9 | align-items: center; 10 | } 11 | 12 | .timer-bar__filler { 13 | animation-duration: --duration; 14 | height: 6px; 15 | border-radius: 50px; 16 | background-color: #396fe4; 17 | animation: timer calc(var(--duration) * 1s) linear 0.2s forwards; 18 | transform-origin: left center; 19 | } 20 | 21 | @keyframes timer { 22 | 0% { 23 | width: 0%; 24 | } 25 | 26 | 100% { 27 | width: 100%; 28 | } 29 | } -------------------------------------------------------------------------------- /frontend/src/assets/svg/lock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /frontend/src/assets/svg/lockWhite.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /frontend/src/pages/agent/AgentHomePage.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useNavigate } from 'react-router-dom'; 3 | 4 | import Button from "../../components/Button" 5 | import { ReactComponent as PowerIcon } from '../../assets/svg/power.svg'; 6 | 7 | const AgentHomePage = ({ setLocation }) => { 8 | 9 | const navigate = useNavigate(); 10 | 11 | const handleLogout = () => { 12 | sessionStorage.clear(); 13 | setLocation("/login") 14 | navigate("/login"); 15 | } 16 | 17 | return ( 18 | 22 | ) 23 | } 24 | 25 | export default AgentHomePage -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

6 | 7 |

8 | 9 |

10 | 11 |

12 | 13 |

14 | 15 |

16 | 17 |

18 | 19 |

20 | 21 |

22 | 23 |

24 | 25 |

26 | 27 |

28 | 29 |

30 | 31 |

32 | 33 |

34 | 35 |

36 | 37 |

38 | 39 |

40 | 41 | -------------------------------------------------------------------------------- /frontend/src/assets/svg/delete.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/svg/delete2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/components/Tooltip.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { motion, AnimatePresence } from "framer-motion" 3 | 4 | 5 | import "./Tooltip.css" 6 | 7 | const Tooltip = ({ children, isHovered }) => { 8 | return ( 9 | 10 | {isHovered && ( 11 | 17 | {children} 18 | 19 | )} 20 | 21 | ) 22 | } 23 | 24 | export default Tooltip -------------------------------------------------------------------------------- /frontend/src/assets/svg/graph-bar.svg: -------------------------------------------------------------------------------- 1 | 12 | 13 | -------------------------------------------------------------------------------- /ecosystem.config.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config() 2 | var backup_config = require('./backup_config.json'); 3 | 4 | const export_arr = 5 | [ 6 | { 7 | name: "server", 8 | script: "server.js", 9 | }, 10 | { 11 | name: "sync", 12 | script: "sync.js", 13 | }, 14 | ] 15 | 16 | if(!backup_config.telegram.disabled || !backup_config.email.disabled) 17 | { 18 | export_arr.push 19 | ({ 20 | name: "backup", 21 | script: "backup_service.js", 22 | }); 23 | } 24 | 25 | if(process.env.RELEASE=="ALI" || process.env.RELEASE=="REZA") 26 | { 27 | export_arr.push 28 | ({ 29 | name: "agent_scanner", 30 | script: "agent_scanner.js", 31 | }); 32 | } 33 | 34 | module.exports = { apps: export_arr }; 35 | -------------------------------------------------------------------------------- /custom_sub/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | NOT FOUND 7 | 24 | 25 | 26 |

27 | 404 28 |

29 | 30 | -------------------------------------------------------------------------------- /frontend/src/components/EmptyTable.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import Button from './Button' 4 | import { ReactComponent as EmptyTableIcon } from '../assets/svg/empty-table.svg' 5 | import './EmptyTable.css' 6 | 7 | const EmptyTable = ({ tableType, colSpan, onCreateButton }) => { 8 | return ( 9 | 10 | 11 |
12 | 13 |
There is no {tableType} added to the system
14 | 15 |
16 | 17 | 18 | ) 19 | } 20 | 21 | export default EmptyTable -------------------------------------------------------------------------------- /frontend/src/assets/svg/db.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /frontend/src/components/AmneziaFilter.module.css: -------------------------------------------------------------------------------- 1 | .amnezia-filter 2 | { 3 | opacity:1; 4 | background:white; 5 | cursor: pointer; 6 | border:0.5px solid var(--border-clr); 7 | width:45px; 8 | height:auto; 9 | padding:3px; 10 | transition:all 0.2s; 11 | } 12 | 13 | .amnezia-filter-active 14 | { 15 | border:0.5px solid var(--primary-clr-200); 16 | box-shadow: var(--primary-clr-100) 0px 0px 6px -1px; 17 | opacity:1; 18 | background:white; 19 | cursor: pointer; 20 | width:45px; 21 | height:auto; 22 | padding:3px; 23 | transition:all 0.2s; 24 | } 25 | 26 | .amnezia-filter-container 27 | { 28 | display:flex; 29 | justify-content:center; 30 | align-items:center; 31 | align-content:center; 32 | flex-direction:row; 33 | height:100%; 34 | } -------------------------------------------------------------------------------- /teritary_backend/run.sh: -------------------------------------------------------------------------------- 1 | # install 2 | docker pull hadish10/kntb && docker run -v /var/lib/marzban:/var/lib/marzban -v /opt/marzban:/opt/marzban -e PW="123456" --network="host" -p 7002:7002 -d --restart always hadish10/kntb 3 | # update 4 | docker stop $(docker ps -a -q --filter "ancestor=hadish10/kntb") && docker rm $(docker ps -a -q --filter "ancestor=hadish10/kntb") && docker rmi hadish10/kntb -f 5 | 6 | # MYSQL: 7 | 8 | # UPDATE users 9 | # SET username = CONCAT('Mo_', username) 10 | # WHERE admin_id = 2 AND id NOT IN (129); 11 | 12 | # SQLITE : 13 | 14 | # UPDATE users 15 | # SET username = 'Ava_' || username 16 | # WHERE admin_id = 2 AND id NOT IN (129); 17 | 18 | # docker stop $(docker ps -a -q --filter "ancestor=hadish10/kntb") && docker rm $(docker ps -a -q --filter "ancestor=hadish10/kntb") && docker rmi hadish10/kntb -f -------------------------------------------------------------------------------- /frontend/src/components/form/inputs/FileInput.css: -------------------------------------------------------------------------------- 1 | input[type=file]::file-selector-button { 2 | background-color: var(--background-color); 3 | color: var(--color, #fff); 4 | padding: 0.44rem 1.25rem; 5 | text-align: center; 6 | text-decoration: none; 7 | display: inline-flex; 8 | font-size: 0.875rem; 9 | border-radius: 0.375rem; 10 | font-family: InterMedium; 11 | transition: background-color 0.1s ease-in-out; 12 | border: 1px solid var(--border-color); 13 | align-items: center; 14 | justify-content: center; 15 | width: max-content; 16 | cursor: pointer; 17 | } 18 | 19 | input[type=file]::file-selector-button:hover { 20 | background-color: var(--background-color-hover); 21 | } 22 | 23 | input[type=file]::file-selector-button:active { 24 | background-color: var(--background-color-active); 25 | } -------------------------------------------------------------------------------- /frontend/src/assets/svg/LoginAsAgent.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /frontend/src/assets/svg/document.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/src/components/Tooltip.css: -------------------------------------------------------------------------------- 1 | .tooltip { 2 | position: absolute; 3 | z-index: 1; 4 | bottom: 100%; 5 | left: 50%; 6 | transform: translateX(-50%); 7 | width: max-content; 8 | background-color: #1a202ce6; 9 | font-size: 0.875rem; 10 | font-family: InterMedium; 11 | color: var(--gray-clr-100); 12 | padding: 7px 10px; 13 | border-radius: 6px; 14 | text-align: center; 15 | margin-bottom: 15px; 16 | } 17 | 18 | @keyframes fade-in { 19 | from { 20 | opacity: 0; 21 | } 22 | 23 | to { 24 | opacity: 1; 25 | } 26 | } 27 | 28 | @keyframes fade-out { 29 | from { 30 | opacity: 1; 31 | } 32 | 33 | to { 34 | opacity: 0; 35 | } 36 | } 37 | 38 | .tooltip.tooltip-fade-in { 39 | animation: fade-in .3s forwards; 40 | } 41 | 42 | .tooltip.tooltip-fade-out { 43 | animation: fade-out .3s backwards; 44 | } -------------------------------------------------------------------------------- /frontend/src/assets/svg/spinner.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /frontend/src/assets/svg/exclamation-mark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /frontend/src/utils/expire-time-util.js: -------------------------------------------------------------------------------- 1 | const handleExpireTime = (expireTime) => { 2 | const current_time = parseInt(Date.now()/1000); 3 | const isActive = current_time < expireTime; 4 | const remaining_days = Math.abs(parseInt((current_time+1-expireTime)/(60*60*24))); 5 | const remaining_hours = Math.abs(parseInt((current_time-expireTime)/(60*60))); 6 | const remaining_minutes = Math.abs(parseInt((current_time-expireTime)/(60)))%60; 7 | if (isActive) { 8 | if (remaining_days !== 0) { 9 | return `Expires in ${remaining_days} days`; 10 | } else { 11 | return `Expires in ${remaining_hours} hours, ${remaining_minutes} minutes`; 12 | } 13 | } else { 14 | if (remaining_days !== 0) { 15 | return `Expired ${remaining_days} days ago`; 16 | } else { 17 | return `Expired ${remaining_hours} hours, ${remaining_minutes} minutes ago`; 18 | } 19 | } 20 | }; 21 | 22 | export default handleExpireTime -------------------------------------------------------------------------------- /frontend/src/axiosConfig.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | axios.interceptors.response.use 4 | ( 5 | response => { 6 | return response 7 | }, 8 | 9 | error => { 10 | if (error.request.responseURL.endsWith('/login')) { 11 | return Promise.reject(error); 12 | } 13 | 14 | else if (error.response && error.response.status.toString().startsWith('5')) { 15 | const customResponse = 16 | { 17 | data: { status: "ERR", msg: "server is not responding" }, 18 | status: error.response ? error.response.status : 500, 19 | headers: error.response ? error.response.headers : {}, 20 | error: true, 21 | }; 22 | return Promise.resolve(customResponse); 23 | } 24 | 25 | return Promise.reject(error); 26 | } 27 | ); 28 | 29 | export default axios; -------------------------------------------------------------------------------- /frontend/src/components/form/inputs/ValueAdjuster.module.css: -------------------------------------------------------------------------------- 1 | .label { 2 | margin-bottom: 0.25rem; 3 | font-size: 0.875rem; 4 | font-family: InterMedium; 5 | } 6 | 7 | .input { 8 | width: 100%; 9 | padding: 0.25rem 0.75rem; 10 | border: var(--border-radius-lg) solid var(--border-clr); 11 | border-radius: 4px; 12 | font-size: 1rem; 13 | line-height: 1.5; 14 | color: #4a5568; 15 | background-color: #fff; 16 | } 17 | 18 | .input:hover, 19 | .input:focus { 20 | border: 1px solid var(--border-clr-hover); 21 | } 22 | 23 | .input:first-of-type { 24 | width: 70%; 25 | border-top-right-radius: 0; 26 | border-bottom-right-radius: 0; 27 | border-right: none; 28 | } 29 | 30 | .input:last-of-type { 31 | border-top-left-radius: 0; 32 | border-bottom-left-radius: 0; 33 | width: 30%; 34 | } 35 | 36 | .button { 37 | padding: .375rem .75rem; 38 | border: var(--border-radius-lg) solid var(--border-clr); 39 | border-radius: 0; 40 | border-right: none; 41 | } -------------------------------------------------------------------------------- /frontend/src/assets/svg/qrcode.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "knp", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node server", 9 | "dev": "nodemon server" 10 | }, 11 | "author": "HEX GROUP", 12 | "license": "ISC", 13 | "dependencies": { 14 | "adm-zip": "^0.5.10", 15 | "axios": "^1.10.0", 16 | "chalk": "^4", 17 | "dotenv": "^16.3.1", 18 | "express": "^4.18.2", 19 | "express-fileupload": "^1.4.2", 20 | "jalali-moment": "^3.3.11", 21 | "jsonwebtoken": "^9.0.2", 22 | "moment": "^2.29.4", 23 | "moment-timezone": "^0.5.43", 24 | "mongodb": "^5.7.0", 25 | "mongoose": "^8.7.3", 26 | "node-cron": "^3.0.3", 27 | "nodemailer": "^6.9.4", 28 | "prompt-sync": "^4.2.0", 29 | "redis": "^4.7.0", 30 | "sqlite3": "^5.1.6", 31 | "telegraf": "^4.12.2", 32 | "why-is-node-running": "^2.2.2" 33 | }, 34 | "devDependencies": { 35 | "nodemon": "^2.0.22" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /frontend/src/assets/svg/qr-code.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/svg/filter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/src/assets/svg/switch.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /frontend/src/components/admin/CreatePanel.css: -------------------------------------------------------------------------------- 1 | .modal__form>*+* { 2 | margin-top: 0.625rem; 3 | } 4 | 5 | .modal__form__group { 6 | display: flex; 7 | flex-direction: column; 8 | width: 100%; 9 | } 10 | 11 | .modal__form__label { 12 | margin-bottom: 0.25rem; 13 | font-size: 0.875rem; 14 | font-family: InterMedium; 15 | } 16 | 17 | .modal__form__input { 18 | padding: 0.25rem 0.75rem; 19 | border: var(--border-radius-lg) solid var(--border-clr); 20 | border-radius: 21 | /*0.375rem*/ 22 | 4px; 23 | font-size: 1rem; 24 | line-height: 1.5; 25 | color: #4a5568; 26 | background-color: #fff; 27 | background-clip: padding-box; 28 | transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; 29 | } 30 | 31 | .modal__form__input:hover, 32 | .modal__form__input:focus { 33 | border: 0.8px solid var(--border-clr-hover); 34 | } 35 | 36 | .modal__form__input:focus-visible { 37 | outline-color: #749aec; 38 | } 39 | 40 | .gap-16 { 41 | gap: 1rem; 42 | } 43 | 44 | .style_exception1 { 45 | color: black; 46 | } -------------------------------------------------------------------------------- /frontend/src/components/agent/UsageStats.css: -------------------------------------------------------------------------------- 1 | .usage-stats { 2 | display: flex; 3 | gap: 1.25rem; 4 | flex-direction: column; 5 | } 6 | 7 | .flex { 8 | display: flex; 9 | } 10 | 11 | .usage-stats .flex { 12 | gap: 1.25rem; 13 | } 14 | 15 | .usage-stats__item { 16 | flex: 1 1 0px; 17 | display: flex; 18 | padding: 1rem; 19 | flex-direction: row; 20 | text-align: center; 21 | color: white; 22 | background-color: #F9FAFB; 23 | padding: 1.5rem; 24 | border-radius: 0.75rem; 25 | align-items: center; 26 | border: 1px solid #d2d2d4; 27 | } 28 | 29 | .usage-stats__item__label { 30 | color: #4A5568; 31 | font-size: 0.875rem; 32 | font-family: InterMedium; 33 | } 34 | 35 | .usage-stats__item__value { 36 | color: #1a202c; 37 | margin-left: auto; 38 | } 39 | 40 | .usage-stats__item__value span { 41 | font-size: 1.875rem; 42 | font-weight: 600; 43 | } 44 | 45 | 46 | @media (max-width: 750px) { 47 | 48 | .usage-stats .flex { 49 | flex-direction: column; 50 | } 51 | } 52 | 53 | .agent_name_box 54 | { 55 | font-size: 1.875rem; 56 | font-weight: 600; 57 | } -------------------------------------------------------------------------------- /testing/fix_undefined.js: -------------------------------------------------------------------------------- 1 | const {get_marzban_user,get_all_users,get_panels} = require("../utils"); 2 | 3 | var { users_clct } = require('../db_interface'); 4 | 5 | async function init() 6 | { 7 | var db_users = await get_all_users(); 8 | db_users = db_users.filter(u=>u.real_subscription_url.includes("undefined")); 9 | console.log(`found ${db_users.length} users`); 10 | 11 | var panels = await get_panels(); 12 | 13 | for(let panel of panels) 14 | { 15 | var panel_users = db_users.filter(u=>u.corresponding_panel_id == panel.id); 16 | 17 | for(let user of panel_users) 18 | { 19 | var marzban_user = await get_marzban_user(panel.panel_url, panel.panel_username, panel.panel_password, user.username); 20 | console.log(marzban_user.subscription_url); 21 | 22 | if(marzban_user) 23 | { 24 | await (await users_clct()).updateOne({id:user.id},{$set:{real_subscription_url:marzban_user.subscription_url}}); 25 | console.log(`user ${user.id} updated`); 26 | } 27 | } 28 | } 29 | 30 | console.log("done"); 31 | } 32 | 33 | init(); -------------------------------------------------------------------------------- /frontend/src/components/Search.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import "./Search.css"; 4 | import { ReactComponent as XMarkIcon } from "../assets/svg/x-mark.svg"; 5 | import { ReactComponent as SearchIcon } from "../assets/svg/search.svg"; 6 | 7 | const Search = ({ value, onChange }) => { 8 | const handleChnage = (e) => { 9 | onChange(e.target.value); 10 | } 11 | 12 | return ( 13 |
14 |
15 |
16 | 17 |
18 | 24 | { 25 | value.length > 0 && 26 |
{ onChange("") }} className="search__icon icon-button"> 27 | 28 |
29 | } 30 |
31 |
32 | ); 33 | } 34 | 35 | export default Search; -------------------------------------------------------------------------------- /cli.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | case "$1" in 4 | start) 5 | cd /root/knp && docker compose up -d 6 | ;; 7 | stop) 8 | cd /root/knp && docker compose down 9 | ;; 10 | restart) 11 | cd /root/knp && docker compose down && docker compose up -d 12 | ;; 13 | logs) 14 | case "$2" in 15 | frontend) 16 | cd /root/knp && docker logs -f knp-frontend --tail 100 17 | ;; 18 | backend) 19 | cd /root/knp && docker logs -f knp-backend --tail 100 20 | ;; 21 | sync) 22 | cd /root/knp && docker exec -it knp-backend pm2 logs sync 23 | ;; 24 | *) 25 | cd /root/knp && docker logs -f knp-backend --tail 100 26 | ;; 27 | esac 28 | ;; 29 | update) 30 | cd /root/knp && git checkout -- cli.sh && git pull && chmod +x cli.sh && docker image prune -f && docker compose build && docker compose down && docker compose up -d 31 | ;; 32 | *) 33 | echo "Usage: knp {start|stop|restart|logs|update}" 34 | echo "For logs: knp logs {frontend|backend|sync}" 35 | exit 1 36 | ;; 37 | esac 38 | -------------------------------------------------------------------------------- /frontend/src/components/Search.css: -------------------------------------------------------------------------------- 1 | .search-wrapper { 2 | border: 1px solid var(--border-clr); 3 | border-radius: 6px; 4 | } 5 | 6 | .search { 7 | display: inline-flex; 8 | align-items: center; 9 | border: 2px solid transparent; 10 | border-radius: 6px; 11 | outline: 2px solid transparent; 12 | outline-offset: 4px; 13 | transition: all 0.1s ease-in-out; 14 | width: 100%; 15 | } 16 | 17 | .search__icon { 18 | width: 18px; 19 | height: 18px; 20 | } 21 | 22 | 23 | .search__icon { 24 | margin: 0 10px; 25 | } 26 | 27 | .search input { 28 | border: none; 29 | height: 40px; 30 | font-family: Inter; 31 | font-size: 1rem; 32 | width: 100%; 33 | background-color: transparent; 34 | } 35 | 36 | .search input::placeholder { 37 | font-size: 1rem; 38 | } 39 | 40 | .search input:focus { 41 | outline: none; 42 | } 43 | 44 | .search:focus-within { 45 | outline: 2px solid #95afec; 46 | outline-offset: 2px; 47 | border: 2px solid #2684ea; 48 | } 49 | 50 | .icon-button { 51 | height: 25px; 52 | width: 25px; 53 | transition: all 0.3s ease; 54 | } 55 | 56 | .icon-button:hover { 57 | background-color: #edf2f7; 58 | border-radius: 6px; 59 | cursor: pointer; 60 | } -------------------------------------------------------------------------------- /frontend/src/components/MessageCard.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { AnimatePresence } from 'framer-motion' 4 | import { ReactComponent as Dldb } from "../assets/svg/dldb2.svg" 5 | import { ReactComponent as XMarkIcon } from '../assets/svg/x-mark.svg' 6 | import LeadingIcon from './LeadingIcon' 7 | import Modal from './Modal' 8 | import TimerBar from './TimerBar' 9 | 10 | const MessageCard = ({ title, duration, showCard, onClose }) => { 11 | return ( 12 | 13 | {showCard && ( 14 | 15 |
16 | 17 | 18 | 19 |

{title}

20 |
21 | 22 |
23 |
24 |
25 | 26 |
27 |
28 | )} 29 |
30 | ) 31 | } 32 | 33 | export default MessageCard -------------------------------------------------------------------------------- /frontend/src/components/QRCode.css: -------------------------------------------------------------------------------- 1 | .links__slider { 2 | display: flex; 3 | align-items: center; 4 | gap: 1rem; 5 | } 6 | 7 | .qr-code__links { 8 | display: flex; 9 | max-width: 300px; 10 | gap: 1rem; 11 | overflow-x: hidden; 12 | } 13 | 14 | .arrow-left svg, 15 | .arrow-right svg { 16 | width: 20px; 17 | height: 20px; 18 | } 19 | 20 | .inner { 21 | display: flex; 22 | gap: 1rem; 23 | } 24 | 25 | .arrow-left svg, 26 | .arrow-right svg { 27 | flex: 0 0 40px; 28 | } 29 | 30 | .qr-code__modal .modal__content { 31 | position: fixed; 32 | width: fit-content; 33 | } 34 | 35 | .qr-code__main 36 | { 37 | gap:1rem; 38 | } 39 | 40 | .qr-code-svg-div-container 41 | { 42 | gap:1rem; 43 | } 44 | 45 | @media (max-width: 900px) { 46 | .qr-code__main { 47 | flex-direction: column; 48 | } 49 | 50 | .qr-code-svg-div 51 | { 52 | height:200px; 53 | } 54 | 55 | .qr-code__modal .modal__content { 56 | top: 10px; 57 | width: 1; 58 | } 59 | 60 | .qr-code-svg-div-container 61 | { 62 | gap:0.7rem; 63 | } 64 | } 65 | 66 | .qrcode_navigation_handler 67 | { 68 | display:flex; 69 | justify-content:center; 70 | align-items:center; 71 | align-content:center; 72 | flex-direction:row; 73 | gap:10px; 74 | } -------------------------------------------------------------------------------- /frontend/src/components/UniversalLogout.jsx: -------------------------------------------------------------------------------- 1 | import React,{useState} from 'react' 2 | import { useNavigate } from 'react-router-dom'; 3 | import "./UniversalLogout.css" 4 | import Button from "./Button" 5 | import VerifyLogout from './admin/VerifyLogout' 6 | import { ReactComponent as PowerIcon } from '../assets/svg/power.svg'; 7 | 8 | const AdminHomePage = ({ setLocation }) => { 9 | 10 | const [showVerifyLogout, setShowVerifyLogout] = useState(false) 11 | 12 | const navigate = useNavigate(); 13 | 14 | const handleLogout = () => { 15 | setShowVerifyLogout(true) 16 | } 17 | 18 | const handleVerifyLogout = () => { 19 | sessionStorage.clear(); 20 | setLocation("/login") 21 | navigate("/login"); 22 | } 23 | 24 | const handleCloseVerifyLogout = () => { 25 | setShowVerifyLogout(false) 26 | } 27 | 28 | 29 | 30 | return ( 31 | <> 32 | 35 | 36 | 37 | 42 | 43 | ) 44 | } 45 | 46 | export default AdminHomePage -------------------------------------------------------------------------------- /frontend/src/components/ProgressBar.css: -------------------------------------------------------------------------------- 1 | .progress-bar { 2 | width: 100%; 3 | height: 6px; 4 | background-color: #e2e8f0; 5 | border-radius: 50px; 6 | margin: 10px 0; 7 | position: relative; 8 | display: flex; 9 | align-items: center; 10 | } 11 | 12 | .progress-bar__filler--expired { 13 | height: 6px; 14 | border-radius: 50px; 15 | background-color: #dd6b20; 16 | } 17 | 18 | .progress-bar__filler--limited { 19 | height: 6px; 20 | border-radius: 50px; 21 | background-color: #e53e3e; 22 | } 23 | 24 | .progress-bar__filler--active { 25 | height: 6px; 26 | border-radius: 50px; 27 | background-color: #396fe4; 28 | } 29 | 30 | .progress-bar__filler--anonym { 31 | height: 6px; 32 | border-radius: 50px; 33 | background-color: #571e7b; 34 | } 35 | 36 | .progress-bar__filler--disable { 37 | height: 6px; 38 | border-radius: 50px; 39 | background-color: #1a202c; 40 | } 41 | 42 | .progress-bar__text { 43 | margin-top: -5px; 44 | font-size: 0.75rem; 45 | color: #4a5568; 46 | font-weight: 500; 47 | display: flex; 48 | justify-content: space-between; 49 | gap: 0.5rem; 50 | font-family: InterMedium; 51 | } 52 | 53 | /* @media (max-width: 690px) { 54 | 55 | .progress-bar, 56 | .progress-bar__text__total-data { 57 | display: none; 58 | } 59 | } */ -------------------------------------------------------------------------------- /frontend/src/assets/svg/panel.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /frontend/src/components/admin/AgentStats.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import "../agent/UsageStats.css"; 4 | import LeadingIcon from "../LeadingIcon"; 5 | import { ReactComponent as GraphBarIcon } from "../../assets/svg/graph-bar.svg" 6 | import { ReactComponent as UsersIcon } from "../../assets/svg/users.svg" 7 | 8 | 9 | const UsageStats = ({ activeUsers, totalUsers, dataUsage, remainingData, allocableData }) => { 10 | return ( 11 |
12 |
13 |
14 | 15 | 16 | 17 |
Active Agents
18 |
{activeUsers} / {totalUsers}
19 |
20 |
21 | 22 | 23 | 24 |
Total Data Usage
25 |
{dataUsage}
26 |
27 |
28 | 29 |
30 | ) 31 | } 32 | 33 | export default UsageStats; -------------------------------------------------------------------------------- /frontend/src/components/admin/PanelStats.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import "../agent/UsageStats.css"; 4 | import LeadingIcon from "../LeadingIcon"; 5 | import { ReactComponent as GraphBarIcon } from "../../assets/svg/graph-bar.svg" 6 | import { ReactComponent as UsersIcon } from "../../assets/svg/panel.svg" 7 | 8 | 9 | const UsageStats = ({ activeUsers, totalUsers, dataUsage, remainingData, allocableData }) => { 10 | return ( 11 |
12 |
13 |
14 | 15 | 16 | 17 |
Active Panels
18 |
{activeUsers} / {totalUsers}
19 |
20 |
21 | 22 | 23 | 24 |
Total Data Usage
25 |
{dataUsage}
26 |
27 |
28 | 29 |
30 | ) 31 | } 32 | 33 | export default UsageStats; -------------------------------------------------------------------------------- /frontend/src/components/admin/UsageStats.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import "../agent/UsageStats.css"; 4 | import LeadingIcon from "../LeadingIcon"; 5 | import { ReactComponent as GraphBarIcon } from "../../assets/svg/graph-bar.svg" 6 | import { ReactComponent as UsersIcon } from "../../assets/svg/users.svg" 7 | 8 | const UsageStats = ({ activeUsers, totalUsers, dataUsage, remainingData, allocableData }) => { 9 | return ( 10 |
11 |
12 |
13 | 14 | 15 | 16 |
Total Active Users
17 |
{activeUsers} / {totalUsers}
18 |
19 |
20 | 21 | 22 | 23 |
Total Data Usage
24 |
{dataUsage}
25 |
26 |
27 | 28 |
29 | ) 30 | } 31 | 32 | export default UsageStats; -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | knp-backend: 5 | build: . 6 | container_name: knp-backend 7 | ports: 8 | - '5000:5000' 9 | restart: always 10 | volumes: 11 | - ./.env:/knp_backend/.env 12 | - ./backup_config.json:/knp_backend/backup_config.json 13 | - /etc/letsencrypt:/etc/letsencrypt 14 | depends_on: 15 | - mongo-knp 16 | - redis-knp 17 | #----------------------------------------------------# 18 | knp-frontend: 19 | build: ./frontend 20 | container_name: knp-frontend 21 | ports: 22 | - '3000:3000' 23 | restart: always 24 | #----------------------------------------------------# 25 | mongo-knp: 26 | image: mongo:latest 27 | container_name: mongo-knp 28 | volumes: 29 | - ./docker_volumes/mongodb_data:/data/db 30 | #----------------------------------------------------# 31 | redis-knp: 32 | image: redis:latest 33 | container_name: redis-knp 34 | volumes: 35 | - ./docker_volumes/redis_data:/data 36 | #----------------------------------------------------# 37 | nginx: 38 | image: nginx:latest 39 | container_name: nginx 40 | volumes: 41 | - ./nginx.conf:/etc/nginx/nginx.conf 42 | - ./main.conf:/etc/nginx/conf.d/main.conf 43 | - /etc/letsencrypt:/etc/letsencrypt 44 | ports: 45 | - "80:80" 46 | - "443:443" 47 | depends_on: 48 | - knp-backend 49 | -------------------------------------------------------------------------------- /frontend/src/pages/agent/UsersPage.css: -------------------------------------------------------------------------------- 1 | .flex { 2 | display: flex; 3 | } 4 | 5 | .items-center { 6 | align-items: center; 7 | } 8 | 9 | .justify-between { 10 | justify-content: space-between; 11 | } 12 | 13 | .container { 14 | padding: 1rem 0; 15 | } 16 | 17 | .search-wrapper { 18 | width: 25rem; 19 | } 20 | 21 | .gap-16 { 22 | gap: 1rem; 23 | } 24 | 25 | .refresh-icon svg { 26 | transition: transform 0.3s linear; 27 | width: 15px; 28 | height: 15px; 29 | } 30 | 31 | .refresh-icon svg:hover { 32 | animation: rotate 1s infinite linear; 33 | transform: rotate(180deg); 34 | } 35 | 36 | .users-page__footer { 37 | display: flex; 38 | justify-content: space-between; 39 | align-items: flex-start; 40 | margin-top: 1rem; 41 | } 42 | 43 | @keyframes rotate { 44 | 0% { 45 | transform: rotate(0deg); 46 | } 47 | 48 | 100% { 49 | transform: rotate(360deg); 50 | } 51 | } 52 | 53 | @media (max-width: 950px) { 54 | .column-reverse { 55 | flex-direction: column-reverse; 56 | } 57 | } 58 | 59 | @media (max-width: 750px) { 60 | .items-end { 61 | align-items: flex-end; 62 | } 63 | 64 | .search-wrapper { 65 | width: 100%; 66 | } 67 | } 68 | 69 | @media (max-width: 585px) { 70 | .users-page__footer { 71 | flex-direction: column-reverse; 72 | gap: 1rem; 73 | align-items: center; 74 | } 75 | } -------------------------------------------------------------------------------- /main.conf: -------------------------------------------------------------------------------- 1 | 2 | server { 3 | listen 443 ssl; 4 | server_name test2.ir; 5 | 6 | ssl_certificate /etc/letsencrypt/live/test.ir/fullchain.pem; 7 | ssl_certificate_key /etc/letsencrypt/live/test.ir/privkey.pem; 8 | 9 | location / { 10 | proxy_pass http://knp-backend:5000; 11 | proxy_set_header Host $host; 12 | proxy_set_header X-Real-IP $remote_addr; 13 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 14 | proxy_set_header X-Forwarded-Proto $scheme; 15 | } 16 | } 17 | 18 | server { 19 | listen 80; 20 | server_name test2.ir; 21 | return 301 https://$host$request_uri; 22 | } 23 | 24 | 25 | 26 | server { 27 | listen 443 ssl; 28 | server_name test.ir; 29 | 30 | ssl_certificate /etc/letsencrypt/live/test.ir/fullchain.pem; 31 | ssl_certificate_key /etc/letsencrypt/live/test.ir/privkey.pem; 32 | 33 | location / { 34 | proxy_pass http://knp-frontend:3000; 35 | proxy_set_header Host $host; 36 | proxy_set_header X-Real-IP $remote_addr; 37 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 38 | proxy_set_header X-Forwarded-Proto $scheme; 39 | } 40 | } 41 | 42 | server { 43 | listen 80; 44 | server_name test.ir; 45 | return 301 https://$host$request_uri; 46 | } 47 | 48 | -------------------------------------------------------------------------------- /frontend/src/components/form/inputs/MultiSelect2.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | import Box from '@mui/material/Box' 3 | import InputLabel from '@mui/material/InputLabel' 4 | import MenuItem from '@mui/material/MenuItem' 5 | import FormControl from '@mui/material/FormControl' 6 | import Select from '@mui/material/Select' 7 | 8 | export default function BasicSelect({ onChange, defaultValue, id, disabled }) { 9 | const [age, setAge] = useState(defaultValue) 10 | 11 | const handleChange = (event) => { 12 | setAge(event.target.value) 13 | if (onChange) { 14 | onChange(event.target.value) 15 | } 16 | } 17 | 18 | useEffect(() => { 19 | setAge(defaultValue) 20 | }, [defaultValue]) 21 | 22 | const agent = JSON.parse(sessionStorage.getItem("agent")) 23 | 24 | return ( 25 | 26 | 27 | 28 | 43 | 44 | 45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /frontend/src/components/Navbar.css: -------------------------------------------------------------------------------- 1 | .navbar { 2 | margin-bottom: 1.5rem; 3 | } 4 | 5 | .navbar__links { 6 | display: flex; 7 | font-family: InterMedium; 8 | font-size: 1rem; 9 | margin-bottom: .25rem; 10 | } 11 | 12 | .navbar__links li { 13 | padding: 0.5rem 0.5rem; 14 | position: relative; 15 | } 16 | 17 | .navbar__link { 18 | color: var(--dark-clr-200); 19 | opacity: 0.7; 20 | transition: all 0.3s ease; 21 | } 22 | 23 | .navbar__line { 24 | height: 2px; 25 | width: 0; 26 | left: 0; 27 | background-color: var(--primary-clr-100); 28 | position: absolute; 29 | transition: width 0.3s ease, transform 0.3s ease; 30 | transform-origin: left; 31 | } 32 | 33 | .navbar__links li:hover~.navbar__line, 34 | .navbar__links li:focus~.navbar__line { 35 | width: 100%; 36 | transform: scaleX(1); 37 | } 38 | 39 | .navbar__background-line { 40 | height: 2px; 41 | background-color: var(--gray-clr-200); 42 | border-radius: 10px; 43 | } 44 | 45 | .navbar__links li:hover .navbar__link, 46 | .navbar__links li:focus .navbar__link, 47 | .navbar__links li:active .navbar__link { 48 | opacity: 1; 49 | } 50 | 51 | .navbar__link.active { 52 | width: 100%; 53 | opacity: 1; 54 | transform: scaleX(1); 55 | color: var(--primary-clr-200); 56 | } 57 | 58 | @media (max-width: 420px) { 59 | .navbar__links { 60 | font-size: 12px; 61 | } 62 | 63 | .navbar__links li { 64 | padding: 0.5rem 0.25rem; 65 | position: relative; 66 | } 67 | } -------------------------------------------------------------------------------- /frontend/src/components/admin/VerifyDelete.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ReactComponent as DeleteIcon } from "../../assets/svg/delete2.svg" 4 | import { ReactComponent as XMarkIcon } from '../../assets/svg/x-mark.svg'; 5 | import { AnimatePresence } from 'framer-motion'; 6 | import LeadingIcon from '../LeadingIcon'; 7 | import Modal from '../Modal'; 8 | import Button from '../Button'; 9 | 10 | const VerifyDelete = ({ onClose, showForm, onDeleteItem, deleteMode }) => { 11 | return ( 12 | 13 | {showForm && ( 14 | 15 |
16 | 17 | 18 | 19 |

Confirm Delete

20 |
21 | 22 |
23 |
24 |
25 | 26 | 27 |
28 |
29 | )} 30 |
31 | ) 32 | } 33 | 34 | export default VerifyDelete -------------------------------------------------------------------------------- /frontend/src/components/admin/VerifyLogout.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ReactComponent as DeleteIcon } from "../../assets/svg/powerWhite.svg" 4 | import { ReactComponent as XMarkIcon } from '../../assets/svg/x-mark.svg'; 5 | import { motion,AnimatePresence } from 'framer-motion'; 6 | import LeadingIcon from '../LeadingIcon'; 7 | import Modal from '../Modal'; 8 | import Button from '../Button'; 9 | 10 | const VerifyDelete = ({ onClose, showForm, onDeleteItem }) => { 11 | 12 | 13 | const formHeader = ( 14 |
15 | 16 | 17 | 18 |

Confirm Logout

19 |
20 | 21 |
22 |
23 | ) 24 | 25 | 26 | const formFooter = ( 27 | 28 | 29 | 30 | 31 | ) 32 | 33 | return ( 34 | 35 | {showForm && ( 36 | 37 | {formHeader} 38 | {formFooter} 39 | 40 | )} 41 | 42 | ) 43 | } 44 | 45 | export default VerifyDelete -------------------------------------------------------------------------------- /agent_scanner.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config() 2 | const axios = require('axios'); 3 | 4 | const { 5 | sleep, 6 | get_accounts, 7 | } = require("./utils"); 8 | 9 | 10 | 11 | async function main() 12 | { 13 | 14 | await sleep(10000); 15 | 16 | while (true) 17 | { 18 | var agents = await get_accounts(); 19 | 20 | var {username, password} = agents.find(agent => agent.is_admin == 1); 21 | agents = agents.filter(agent => agent.is_admin == 0); 22 | 23 | for(let agent of agents) 24 | { 25 | 26 | if(agent.volume < 0 && agent.disable == 0 && agent.business_mode == 0 ) 27 | { 28 | var access_token = (await axios.post("http://knp-backend:" + process.env.SERVER_PORT + "/login", { username, password })).data.access_token; 29 | var disable_agent_request = await axios.post("http://knp-backend:" + process.env.SERVER_PORT + "/disable_agent", { access_token, agent_id: agent.id }); 30 | var disable_user_request = await axios.post("http://knp-backend:" + process.env.SERVER_PORT + "/disable_all_agent_users", { access_token, agent_id: agent.id }); 31 | 32 | console.log(`Agent: ${agent.name} has been disabled because of underpayment (volume: ${agent.volume})`); 33 | } 34 | 35 | console.log(`Agent: ${agent.name} has no underpayments (volume: ${agent.volume})`); 36 | 37 | 38 | } 39 | 40 | await sleep(30 * 60 * 1000); 41 | } 42 | } 43 | 44 | main(); -------------------------------------------------------------------------------- /frontend/src/components/admin/VerifyReset.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ReactComponent as DeleteIcon } from "../../assets/svg/refreshWhite.svg" 4 | import { ReactComponent as XMarkIcon } from '../../assets/svg/x-mark.svg' 5 | import { motion, AnimatePresence } from 'framer-motion' 6 | import LeadingIcon from '../LeadingIcon' 7 | import Modal from '../Modal' 8 | import Button from '../Button' 9 | 10 | const VerifyDelete = ({ onClose, showForm, onDeleteItem, resetMode }) => { 11 | const formHeader = ( 12 |
13 | 14 | 15 | 16 |

Confirm Reset usage

17 |
18 | 19 |
20 |
21 | ) 22 | 23 | const formFooter = ( 24 | 25 | 26 | 27 | 28 | ) 29 | 30 | return ( 31 | 32 | {showForm && ( 33 | 34 | {formHeader} 35 | {formFooter} 36 | 37 | )} 38 | 39 | ) 40 | } 41 | 42 | export default VerifyDelete -------------------------------------------------------------------------------- /frontend/src/components/admin/VerifyUnlock.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ReactComponent as DeleteIcon } from "../../assets/svg/lockWhite.svg" 4 | import { ReactComponent as XMarkIcon } from '../../assets/svg/x-mark.svg' 5 | import { motion, AnimatePresence } from 'framer-motion' 6 | import LeadingIcon from '../LeadingIcon' 7 | import Modal from '../Modal' 8 | import Button from '../Button' 9 | 10 | const VerifyDelete = ({ onClose, showForm, onDeleteItem, unlockMode }) => { 11 | const formHeader = ( 12 |
13 | 14 | 15 | 16 |

Confirm Unlock user

17 |
18 | 19 |
20 |
21 | ) 22 | 23 | const formFooter = ( 24 | 25 | 26 | 27 | 28 | ) 29 | 30 | return ( 31 | 32 | {showForm && ( 33 | 34 | {formHeader} 35 | {formFooter} 36 | 37 | )} 38 | 39 | ) 40 | } 41 | 42 | export default VerifyDelete -------------------------------------------------------------------------------- /frontend/src/components/form/inputs/ValueAdjuster.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | 3 | import Button from '../../Button' 4 | import styles from './ValueAdjuster.module.css' 5 | 6 | const ValueAdjuster = ({ defaultValue, label, id, name }) => { 7 | const [valueToAdjust, setValueToAdjust] = useState('0') 8 | const [value, setValue] = useState(defaultValue) 9 | 10 | 11 | const addToValue = (e) => { 12 | e.preventDefault() 13 | setValue(parseFloat(value) + parseFloat(valueToAdjust)) 14 | setValueToAdjust('0') 15 | } 16 | 17 | const subtractFromValue = (e) => { 18 | e.preventDefault() 19 | setValue(parseFloat(value) - parseFloat(valueToAdjust)) 20 | setValueToAdjust('0') 21 | } 22 | 23 | return ( 24 | <> 25 | 26 |
27 | setValue(e.target.value)} /> 28 |
29 | 30 | 31 |
32 | setValueToAdjust(e.target.value)} /> 33 |
34 | 35 | ) 36 | } 37 | 38 | export default ValueAdjuster -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kn_panel", 3 | "version": "0.1.0", 4 | "private": true, 5 | "proxy": "http://knp-backend:5000", 6 | "dependencies": { 7 | "@emotion/react": "^11.11.1", 8 | "@emotion/styled": "^11.11.0", 9 | "@mui/material": "^5.14.1", 10 | "@mui/x-charts": "^6.18.0", 11 | "@mui/x-date-pickers": "^6.10.1", 12 | "@testing-library/jest-dom": "^5.16.5", 13 | "@testing-library/react": "^13.4.0", 14 | "@testing-library/user-event": "^13.5.0", 15 | "axios": "^1.4.0", 16 | "dayjs": "^1.11.9", 17 | "framer-motion": "^4.1.17", 18 | "qrcode.react": "^3.1.0", 19 | "react": "^18.2.0", 20 | "react-dom": "^18.2.0", 21 | "react-scripts": "5.0.1", 22 | "react-slick": "^0.29.0", 23 | "web-vitals": "^2.1.4" 24 | }, 25 | "scripts": { 26 | "start": "NODE_ENV=production GENERATE_SOURCEMAP=false DISABLE_ESLINT_PLUGIN=true BROWSER=none PORT=3000 react-scripts start", 27 | "wstart": "set NODE_ENV=production && set GENERATE_SOURCEMAP=false && set DISABLE_ESLINT_PLUGIN=true && set BROWSER=none && set PORT=3000 && react-scripts start", 28 | "build": "react-scripts build", 29 | "test": "react-scripts test", 30 | "eject": "react-scripts eject" 31 | }, 32 | "eslintConfig": { 33 | "extends": [ 34 | "react-app", 35 | "react-app/jest" 36 | ] 37 | }, 38 | "browserslist": { 39 | "production": [ 40 | ">0.2%", 41 | "not dead", 42 | "not op_mini all" 43 | ], 44 | "development": [ 45 | "last 1 chrome version", 46 | "last 1 firefox version", 47 | "last 1 safari version" 48 | ] 49 | }, 50 | "devDependencies": { 51 | "react-router-dom": "^6.14.1" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /frontend/src/components/Modal.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import ReactDOM from "react-dom"; 3 | import { motion } from "framer-motion" 4 | 5 | import "./Modal.css"; 6 | 7 | const Modal = ({ children, onClose, className, width }) => { 8 | useEffect(() => { 9 | document.body.classList.add("overflow-hidden") 10 | 11 | return () => { document.body.classList.remove("overflow-hidden") } 12 | }, []) 13 | 14 | return ReactDOM.createPortal( 15 | e.stopPropagation()} 19 | onClick={(e) => e.stopPropagation()} 20 | initial={{ opacity: 0 }} 21 | animate={{ opacity: 1 }} 22 | exit={{ opacity: 0 }} 23 | transition={{ duration: 0.1 }} 24 | > 25 |
26 |
27 | e.stopPropagation()} 30 | onMouseDown={(e) => e.stopPropagation()} 31 | initial={{ scale: 0.9, opacity: 1 }} 32 | animate={{ scale: 1, opacity: 1 }} 33 | exit={{ scale: 0.9, opacity: 1 }} 34 | transition={{ duration: 0.1 }} 35 | style={{ width: width }} 36 | > 37 | {children} 38 | 39 |
40 |
, 41 | document.querySelector(".modal-container") 42 | ) 43 | } 44 | 45 | export default Modal; -------------------------------------------------------------------------------- /frontend/src/assets/svg/restore.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | -------------------------------------------------------------------------------- /frontend/src/components/ErrorCard.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import ReactDOM from 'react-dom' 3 | 4 | import { motion, AnimatePresence } from 'framer-motion' 5 | import { ReactComponent as XMarkIcon } from '../assets/svg/x-mark.svg' 6 | import { ReactComponent as ExclamationIcon } from '../assets/svg/exclamation-mark.svg' 7 | import styels from "./ErrorCard.module.css" 8 | 9 | const ErrorCard = ({ setHasError, hasError, errorTitle, errorMessage }) => { 10 | useEffect(() => { 11 | const errorTimer = setTimeout(() => { 12 | handleClose() 13 | }, 5000) 14 | 15 | return () => clearTimeout(errorTimer) 16 | },) 17 | 18 | const handleClose = () => { 19 | setHasError(false) 20 | } 21 | 22 | return ReactDOM.createPortal( 23 | 24 | {hasError && 31 | 32 |
33 |
34 |

{errorTitle}

35 | 36 |
37 |

{errorMessage}

38 |
39 |
} 40 |
, 41 | document.querySelector(".error-container") 42 | ) 43 | } 44 | 45 | export default ErrorCard -------------------------------------------------------------------------------- /frontend/src/components/OkCard.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import ReactDOM from 'react-dom' 3 | 4 | import { motion, AnimatePresence } from 'framer-motion' 5 | import { ReactComponent as XMarkIcon } from '../assets/svg/x-mark.svg' 6 | import okIcon from '../assets/accept.png' 7 | import styels from "./ErrorCard.module.css" 8 | 9 | const ErrorCard = ({ setHasError, hasError, errorTitle, errorMessage }) => { 10 | useEffect(() => { 11 | const errorTimer = setTimeout(() => { 12 | handleClose() 13 | }, 5000) 14 | 15 | return () => clearTimeout(errorTimer) 16 | },) 17 | 18 | const handleClose = () => { 19 | setHasError(false) 20 | } 21 | 22 | return ReactDOM.createPortal( 23 | 24 | {hasError && 31 | 32 |
33 |
34 |

{errorTitle}

35 | 36 |
37 |

{errorMessage}

38 |
39 |
} 40 |
, 41 | document.querySelector(".error-container") 42 | ) 43 | } 44 | 45 | export default ErrorCard -------------------------------------------------------------------------------- /testing/fix_time.js: -------------------------------------------------------------------------------- 1 | var { users_clct,logs_clct } = require('../db_interface'); 2 | 3 | 4 | async function init() 5 | { 6 | const users = await (await users_clct()).find({corresponding_panel:"https://connection.irtunir.com"}).toArray() 7 | // const wrong_expire_users = users.filter(u=> u.expire - Math.floor(Date.now() / 1000) > 90 * 24 * 60 * 60) 8 | const wrong_expire_users = users.filter(u => true) 9 | 10 | 11 | const valid_expires = {} 12 | 13 | 14 | for(let u of wrong_expire_users) 15 | { 16 | const usernamePart = u.username.includes("_") ? u.username.split("_").slice(1).join("_") : u.username 17 | 18 | const logs = await (await logs_clct()).find({ 19 | $or: [ 20 | { 21 | $and: [ 22 | { msg: { $regex: `!${u.username} with !`, $options: "i" } }, 23 | { msg: { $regex: `edited user`, $options: "i" } } 24 | ] 25 | }, 26 | { 27 | $and: [ 28 | { msg: { $regex: `!${usernamePart} with !`, $options: "i" } }, 29 | { msg: { $regex: `created user`, $options: "i" } } 30 | ] 31 | } 32 | ] 33 | }) 34 | .sort({ time: -1 }) 35 | .limit(1) 36 | .toArray(); 37 | 38 | 39 | if(!logs[0]) 40 | { 41 | console.log(`No logs found for ${u.username}`) 42 | continue 43 | } 44 | 45 | const expire = logs[0].msg.split('!')[3].split(' ')[0] 46 | 47 | valid_expires[u.username] = logs[0].time + (expire * 24 * 60 * 60) 48 | 49 | } 50 | 51 | console.log(valid_expires) 52 | 53 | } 54 | 55 | init() -------------------------------------------------------------------------------- /frontend/src/components/Modal.css: -------------------------------------------------------------------------------- 1 | .modal { 2 | position: fixed; 3 | top: 0; 4 | left: 0; 5 | width: 100vw; 6 | height: 100vh; 7 | background: rgba(0, 0, 0, 0.16); 8 | z-index: 1000; 9 | backdrop-filter: blur(10px); 10 | } 11 | 12 | .modal__background { 13 | height: 100vh; 14 | width: 100vw; 15 | overscroll-behavior-y: none; 16 | } 17 | 18 | .modal__content-container { 19 | display: flex; 20 | width: 100vw; 21 | height: 100dvh; 22 | top: 0; 23 | left: 0; 24 | position: fixed; 25 | justify-content: center; 26 | padding: 4rem 0; 27 | z-index: 1000; 28 | overflow-y: auto; 29 | overflow-x: hidden; 30 | } 31 | 32 | .modal__content { 33 | display: flex; 34 | flex-direction: column; 35 | position: relative; 36 | margin: auto 1rem; 37 | background-color: #ffffff; 38 | border-radius: 6px; 39 | padding: 1.5rem; 40 | z-index: 1000; 41 | } 42 | 43 | .modal__header { 44 | display: flex; 45 | align-items: center; 46 | padding-bottom: 1rem; 47 | } 48 | 49 | .modal__title { 50 | font-size: 1.125rem; 51 | font-weight: 600; 52 | color: #1a202c; 53 | } 54 | 55 | .modal__footer { 56 | display: flex; 57 | justify-content: space-between; 58 | align-items: center; 59 | padding-top: 1.5rem; 60 | gap: 0.5rem; 61 | } 62 | 63 | .modal__footer svg { 64 | width: 22px; 65 | height: 22px; 66 | } 67 | 68 | .close-icon { 69 | height: 30px; 70 | width: 25px; 71 | align-self: self-start; 72 | margin-left: auto; 73 | cursor: pointer; 74 | border-radius: 6px; 75 | transition: all 0.2s ease; 76 | } 77 | 78 | .close-icon:hover { 79 | background-color: var(--gray-clr-200); 80 | } 81 | 82 | .overflow-hidden { 83 | overflow: hidden; 84 | } 85 | 86 | #desc 87 | { 88 | font-family:Vazir; 89 | } -------------------------------------------------------------------------------- /frontend/src/components/form/inputs/MultiSelect3.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import OutlinedInput from '@mui/material/OutlinedInput'; 3 | import InputLabel from '@mui/material/InputLabel'; 4 | import MenuItem from '@mui/material/MenuItem'; 5 | import FormControl from '@mui/material/FormControl'; 6 | import ListItemText from '@mui/material/ListItemText'; 7 | import Select from '@mui/material/Select'; 8 | import Checkbox from '@mui/material/Checkbox'; 9 | 10 | const ITEM_HEIGHT = 80; 11 | const ITEM_PADDING_TOP = 4; 12 | const MenuProps = { 13 | PaperProps: { 14 | style: { 15 | maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP, 16 | width: 250, 17 | }, 18 | }, 19 | }; 20 | 21 | export default function MultipleSelectCheckmarks({ actions, onChange, value, lable }) { 22 | 23 | const [names, setNames] = React.useState(actions); 24 | 25 | const handleChange = (event) => { 26 | const { 27 | target: { value }, 28 | } = event; 29 | onChange( 30 | typeof value === 'string' ? value.split(',') : value, 31 | 32 | ); 33 | }; 34 | 35 | return ( 36 | 37 | {lable} 38 | 55 | 56 | ); 57 | } -------------------------------------------------------------------------------- /frontend/src/pages/admin/AdminSettingsPage.module.css: -------------------------------------------------------------------------------- 1 | /* .settings-page { 2 | display: flex; 3 | flex-direction: column; 4 | } 5 | 6 | .settings-page>*+* { 7 | margin-top: 1rem; 8 | } 9 | 10 | .settings-page__footer { 11 | display: flex; 12 | align-items: center; 13 | justify-content: end; 14 | gap: 1rem; 15 | } */ 16 | 17 | .change-credentials-section, 18 | .create-admin-section { 19 | border: .8px solid var(--border-clr); 20 | border-radius: 6px; 21 | padding: 1rem; 22 | background-color: #f9fafb; 23 | box-shadow: black 1px 1px 5px -3px; 24 | } 25 | 26 | .admins { 27 | max-height: 150px; 28 | overflow-y: auto; 29 | } 30 | 31 | .admin { 32 | display: flex; 33 | padding: .15em .75em; 34 | align-items: center; 35 | font-family: InterMedium; 36 | font-weight: 400; 37 | border: 1px solid var(--border-clr); 38 | border-radius: var(--border-radius-md); 39 | width: 100%; 40 | background-color: #fff; 41 | } 42 | 43 | @media (max-width: 630px) { 44 | .flex-col { 45 | flex-direction: column; 46 | gap: 1rem; 47 | } 48 | 49 | .admin-section { 50 | padding-left: 1rem; 51 | } 52 | } 53 | 54 | .perms_checkboxes 55 | { 56 | display:flex; 57 | justify-content:flex-start; 58 | flex-direction:column; 59 | } 60 | 61 | .perm_box 62 | { 63 | padding:5px 10px; 64 | font-weight:1000; 65 | border-radius:100%; 66 | font-size:13px; 67 | margin-left:5px; 68 | background:#E2E2E2 69 | } 70 | 71 | .perm_title 72 | { 73 | margin-right:10px; 74 | } 75 | 76 | .perm_box_color_blue 77 | { 78 | background:#AEE3FF; 79 | } 80 | 81 | .perm_box_color_yellow 82 | { 83 | background:#FFEEAE; 84 | } 85 | 86 | .perm_box_color_red 87 | { 88 | background:#FFB3AE; 89 | } 90 | 91 | .perm_box_color_green 92 | { 93 | background:#AEFFC5; 94 | } -------------------------------------------------------------------------------- /frontend/src/components/agent/CreateUser.module.css: -------------------------------------------------------------------------------- 1 | .modal__body { 2 | display: flex; 3 | gap: 0.75rem; 4 | } 5 | 6 | .modal__form>*+* { 7 | margin-top: 0.625rem; 8 | } 9 | 10 | .modal__form, 11 | .protocols-section { 12 | width: 100%; 13 | } 14 | 15 | .protocols { 16 | display: flex; 17 | flex-direction: column; 18 | gap: .5rem; 19 | } 20 | 21 | .protocols-section>h4 { 22 | margin-bottom: 0.25rem; 23 | font-size: 0.875rem; 24 | font-family: InterMedium; 25 | font-weight: 400; 26 | width: 100%; 27 | } 28 | 29 | .protocols { 30 | display: flex; 31 | flex-direction: column; 32 | width: 100%; 33 | } 34 | 35 | .protocol { 36 | display: flex; 37 | justify-content: space-between; 38 | padding: .5em .75em; 39 | font-family: InterMedium; 40 | font-weight: 400; 41 | border: 1px solid var(--border-clr); 42 | border-radius: var(--border-radius-md); 43 | width: 100%; 44 | cursor: pointer; 45 | overflow: hidden; 46 | } 47 | 48 | .protocol.selected { 49 | background-color: #f7fafc; 50 | border-color: transparent; 51 | outline-style: solid; 52 | outline-width: 2px; 53 | outline-color: var(--primary-clr-100); 54 | } 55 | 56 | .protocol.disabled { 57 | cursor: not-allowed; 58 | background-color: var(--gray-clr-100); 59 | opacity: .4; 60 | } 61 | 62 | .protocol button { 63 | color: var(--primary-clr-100); 64 | } 65 | 66 | .protocol__name { 67 | text-transform: capitalize; 68 | font-size: 0.875rem; 69 | color: #2d3748; 70 | font-weight: 400; 71 | } 72 | 73 | .protocol__description { 74 | font-size: 0.75rem; 75 | color: var(--dark-clr-100); 76 | } 77 | 78 | .style_exception1 { 79 | color: black; 80 | } 81 | 82 | @media (max-width: 740px) { 83 | .modal__body { 84 | flex-direction: column; 85 | gap: 1.5rem; 86 | } 87 | } -------------------------------------------------------------------------------- /frontend/src/components/form/inputs/MultiSelect5.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | import Box from '@mui/material/Box' 3 | import InputLabel from '@mui/material/InputLabel' 4 | import MenuItem from '@mui/material/MenuItem' 5 | import FormControl from '@mui/material/FormControl' 6 | import Select from '@mui/material/Select' 7 | 8 | export default function BasicSelect({ onChange, defaultValue, id }) { 9 | const [age, setAge] = useState(defaultValue) 10 | 11 | const handleChange = (event) => { 12 | setAge(event.target.value) 13 | if (onChange) { 14 | onChange(event.target.value) 15 | } 16 | } 17 | 18 | 19 | useEffect(() => { 20 | setAge(defaultValue) 21 | }, [defaultValue]) 22 | 23 | const panels = JSON.parse(sessionStorage.getItem("panels")) 24 | 25 | return ( 26 | 27 | 28 | 29 | 48 | 49 | 50 | ) 51 | } 52 | -------------------------------------------------------------------------------- /frontend/src/pages/agent/AgentSettingsPage.module.css: -------------------------------------------------------------------------------- 1 | /* .settings-page { 2 | display: flex; 3 | flex-direction: column; 4 | } 5 | 6 | .settings-page>*+* { 7 | margin-top: 1rem; 8 | } 9 | 10 | .settings-page__footer { 11 | display: flex; 12 | align-items: center; 13 | justify-content: end; 14 | gap: 1rem; 15 | } */ 16 | 17 | .change-credentials-section, 18 | .create-admin-section { 19 | border: .8px solid var(--border-clr); 20 | border-radius: 6px; 21 | padding: 1rem; 22 | background-color: #f9fafb; 23 | box-shadow: black 1px 1px 5px -3px; 24 | } 25 | 26 | .admins { 27 | max-height: 150px; 28 | overflow-y: scroll; 29 | } 30 | 31 | .admin { 32 | display: flex; 33 | padding: .15em .75em; 34 | align-items: center; 35 | font-family: InterMedium; 36 | font-weight: 400; 37 | border: 1px solid var(--border-clr); 38 | border-radius: var(--border-radius-md); 39 | width: 100%; 40 | background-color: #fff; 41 | } 42 | 43 | @media (max-width: 630px) { 44 | .flex-col { 45 | flex-direction: column; 46 | gap: 1rem; 47 | } 48 | 49 | .admin-section { 50 | padding-left: 1rem; 51 | } 52 | } 53 | 54 | .perms_checkboxes 55 | { 56 | display:flex; 57 | justify-content:flex-start; 58 | flex-direction:column; 59 | } 60 | 61 | .perm_box 62 | { 63 | padding:5px 10px; 64 | font-weight:1000; 65 | border-radius:100%; 66 | font-size:13px; 67 | margin-left:5px; 68 | background:#E2E2E2 69 | } 70 | 71 | .perm_title 72 | { 73 | margin-right:10px; 74 | } 75 | 76 | .perm_box_color_blue 77 | { 78 | background:#AEE3FF; 79 | } 80 | 81 | .perm_box_color_yellow 82 | { 83 | background:#FFEEAE; 84 | } 85 | 86 | .perm_box_color_red 87 | { 88 | background:#FFB3AE; 89 | } 90 | 91 | .perm_box_color_green 92 | { 93 | background:#AEFFC5; 94 | } -------------------------------------------------------------------------------- /db_interface.js: -------------------------------------------------------------------------------- 1 | const { MongoClient } = require('mongodb'); 2 | const dbClient = new MongoClient('mongodb://mongo-knp:27017'); 3 | 4 | const { createClient } = require('redis'); 5 | const redis_client_conn = createClient({ url: 'redis://redis-knp:6379' }); 6 | 7 | const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); 8 | 9 | var accounts_clct_conn; 10 | var panels_clct_conn; 11 | var users_clct_conn; 12 | var logs_clct_conn; 13 | 14 | var initialized = false; 15 | 16 | async function init() 17 | { 18 | await dbClient.connect(); 19 | const db = dbClient.db('KN_PANEL'); 20 | 21 | accounts_clct_conn = db.collection('accounts'); 22 | panels_clct_conn = db.collection('panels'); 23 | users_clct_conn = db.collection('users'); 24 | logs_clct_conn = db.collection('logs'); 25 | 26 | await redis_client_conn.connect(); 27 | 28 | initialized = true; 29 | 30 | console.log('DB Interface initialized'); 31 | 32 | } 33 | 34 | init(); 35 | 36 | async function wait_for_init() 37 | { 38 | while (!initialized) 39 | { 40 | await sleep(1000); 41 | } 42 | } 43 | 44 | async function accounts_clct() 45 | { 46 | await wait_for_init(); 47 | return accounts_clct_conn; 48 | } 49 | 50 | async function panels_clct() 51 | { 52 | await wait_for_init(); 53 | return panels_clct_conn; 54 | } 55 | 56 | async function users_clct() 57 | { 58 | await wait_for_init(); 59 | return users_clct_conn; 60 | } 61 | 62 | async function logs_clct() 63 | { 64 | await wait_for_init(); 65 | return logs_clct_conn; 66 | } 67 | 68 | async function redis_client() 69 | { 70 | await wait_for_init(); 71 | return redis_client_conn; 72 | } 73 | 74 | module.exports = { 75 | accounts_clct, 76 | panels_clct, 77 | users_clct, 78 | logs_clct, 79 | redis_client 80 | }; 81 | -------------------------------------------------------------------------------- /frontend/src/pages/Login.css: -------------------------------------------------------------------------------- 1 | .login { 2 | /* 1.5rem is the padding for the body padding*/ 3 | min-height: calc(100vh - 2 * 1.5rem); 4 | min-height: calc(100svh - 2 * 1.5rem); 5 | display: flex; 6 | justify-content: center; 7 | align-items: center; 8 | flex-direction: column; 9 | } 10 | 11 | .login>svg { 12 | width: 50px; 13 | margin-bottom: 10px; 14 | } 15 | 16 | .login__title { 17 | font-size: 1.5rem; 18 | font-weight: 900; 19 | color: var(--dark-clr-200); 20 | } 21 | 22 | .login__subtitle { 23 | margin: 0.75rem 0 2rem; 24 | font-size: 1rem; 25 | color: var(--dark-clr-100); 26 | font-weight: 300; 27 | } 28 | 29 | .login__form { 30 | display: flex; 31 | flex-direction: column; 32 | align-items: center; 33 | } 34 | 35 | .login__form>* { 36 | width: 300px; 37 | height: 40px; 38 | font-size: 1rem; 39 | padding: 0 1rem; 40 | } 41 | 42 | .login__form>*+* { 43 | margin-top: 1rem; 44 | } 45 | 46 | .login__form input { 47 | border: 1px solid var(--border-clr); 48 | border-radius: 0.375rem; 49 | } 50 | 51 | .login__form input:focus { 52 | border: 1px solid var(--primary-clr-200); 53 | } 54 | 55 | .login__btn { 56 | font-family: Inter; 57 | font-weight: 600; 58 | transition: all 0.2s; 59 | } 60 | 61 | .login__btn svg { 62 | width: 20px; 63 | height: 20px; 64 | stroke-width: 2px; 65 | margin-right: 5px; 66 | stroke: white; 67 | } 68 | 69 | .login__btn.spin svg { 70 | animation: spin 1.25s linear infinite; 71 | } 72 | 73 | @keyframes spin { 74 | from { 75 | transform: rotate(0deg); 76 | } 77 | 78 | to { 79 | transform: rotate(360deg); 80 | } 81 | } 82 | 83 | .login__logo { 84 | width: 130px; 85 | height: 130px; 86 | margin-bottom: 20px; 87 | background: transparent; 88 | opacity: 1; 89 | } -------------------------------------------------------------------------------- /frontend/src/components/form/inputs/MultiSelect4.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import OutlinedInput from '@mui/material/OutlinedInput'; 3 | import InputLabel from '@mui/material/InputLabel'; 4 | import MenuItem from '@mui/material/MenuItem'; 5 | import FormControl from '@mui/material/FormControl'; 6 | import ListItemText from '@mui/material/ListItemText'; 7 | import Select from '@mui/material/Select'; 8 | import Checkbox from '@mui/material/Checkbox'; 9 | 10 | const ITEM_HEIGHT = 80; 11 | const ITEM_PADDING_TOP = 4; 12 | const MenuProps = { 13 | PaperProps: { 14 | style: { 15 | maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP, 16 | width: 250, 17 | }, 18 | }, 19 | }; 20 | 21 | var filter_accounts = ["test"] 22 | 23 | 24 | export default function MultipleSelectCheckmarks() { 25 | 26 | const [names, setNames] = React.useState(filter_accounts); 27 | const [personName, setPersonName] = React.useState([]); 28 | 29 | 30 | 31 | const handleChange = (event) => { 32 | const { 33 | target: { value }, 34 | } = event; 35 | setPersonName( 36 | typeof value === 'string' ? value.split(',') : value, 37 | 38 | ); 39 | }; 40 | 41 | return ( 42 | 43 | ACCOUNTS 44 | 61 | 62 | ); 63 | } -------------------------------------------------------------------------------- /frontend/src/components/ShoppingCard.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import ReactDOM from 'react-dom' 3 | 4 | import { motion, AnimatePresence } from 'framer-motion' 5 | import { ReactComponent as XMarkIcon } from '../assets/svg/x-mark.svg' 6 | import cartIcon from '../assets/svg/cart.svg' 7 | import styels from "./ErrorCard.module.css" 8 | 9 | const ShoppingCard = ({ setHasPaymentNotif, hasPaymentNotif, paymentNotifTitle, paymentNotifMessage, isPaymentNotifPositive }) => { 10 | useEffect(() => { 11 | const errorTimer = setTimeout(() => { 12 | handleClose() 13 | }, 5000) 14 | 15 | return () => clearTimeout(errorTimer) 16 | },) 17 | 18 | const handleClose = () => { 19 | setHasPaymentNotif(false) 20 | } 21 | 22 | return ReactDOM.createPortal( 23 | 24 | {hasPaymentNotif && 31 | 32 |
33 |
34 |

{paymentNotifTitle}

35 | 36 |
37 |

{paymentNotifMessage}

38 |
39 |
} 40 |
, 41 | document.querySelector(".error-container") 42 | ) 43 | } 44 | 45 | export default ShoppingCard -------------------------------------------------------------------------------- /frontend/src/components/form/inputs/IOSSwitch.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Switch from '@mui/material/Switch' 3 | import { styled } from '@mui/material/styles' 4 | 5 | const IOSSwitch = styled((props) => ( 6 | 7 | ))(({ theme }) => ({ 8 | width: 38, 9 | height: 22, 10 | padding: 0, 11 | margin: 0, 12 | '& .MuiSwitch-switchBase': { 13 | padding: 0, 14 | margin: 2, 15 | transitionDuration: '300ms', 16 | '&.Mui-checked': { 17 | transform: 'translateX(16px)', 18 | color: '#fff', 19 | '& + .MuiSwitch-track': { 20 | backgroundColor: theme.palette.mode === 'dark' ? '#396fe4' : '#396fe4', 21 | opacity: 1, 22 | border: 0, 23 | }, 24 | '&.Mui-disabled + .MuiSwitch-track': { 25 | opacity: 0.5, 26 | }, 27 | }, 28 | '&.Mui-focusVisible .MuiSwitch-thumb': { 29 | color: '#33cf4d', 30 | border: '6px solid #fff', 31 | }, 32 | '&.Mui-disabled .MuiSwitch-thumb': { 33 | color: 34 | theme.palette.mode === 'light' 35 | ? theme.palette.grey[100] 36 | : theme.palette.grey[600], 37 | }, 38 | '&.Mui-disabled + .MuiSwitch-track': { 39 | opacity: theme.palette.mode === 'light' ? 0.7 : 0.3, 40 | }, 41 | }, 42 | '& .MuiSwitch-thumb': { 43 | boxSizing: 'border-box', 44 | width: 18, 45 | height: 18, 46 | }, 47 | '& .MuiSwitch-track': { 48 | borderRadius: 26 / 2, 49 | backgroundColor: theme.palette.mode === 'light' ? '#E9E9EA' : '#396fe4', 50 | opacity: 1, 51 | transition: theme.transitions.create(['background-color'], { 52 | duration: 500, 53 | }), 54 | }, 55 | '& .MuiSwitch-root': { 56 | marginLeft: 50, 57 | }, 58 | })) 59 | 60 | export default IOSSwitch 61 | -------------------------------------------------------------------------------- /frontend/src/components/agent/EditUser.module.css: -------------------------------------------------------------------------------- 1 | .footer { 2 | padding-top: 1rem; 3 | gap: 1rem; 4 | } 5 | 6 | @media (max-width: 470px) { 7 | .footer { 8 | align-items: flex-start; 9 | flex-direction: column; 10 | } 11 | 12 | .primaryButtons, 13 | .primaryButtons>* { 14 | width: 100%; 15 | } 16 | } 17 | 18 | /* */ 19 | 20 | .modal__body { 21 | display: flex; 22 | gap: 0.75rem; 23 | } 24 | 25 | .modal__form>*+* { 26 | margin-top: 0.625rem; 27 | } 28 | 29 | .modal__form, 30 | .protocols-section { 31 | width: 100%; 32 | } 33 | 34 | .protocols { 35 | display: flex; 36 | flex-direction: column; 37 | gap: .5rem; 38 | } 39 | 40 | .protocols-section>h4 { 41 | margin-bottom: 0.25rem; 42 | font-size: 0.875rem; 43 | font-family: InterMedium; 44 | font-weight: 400; 45 | width: 100%; 46 | } 47 | 48 | .protocols { 49 | display: flex; 50 | flex-direction: column; 51 | width: 100%; 52 | } 53 | 54 | .protocol { 55 | display: flex; 56 | justify-content: space-between; 57 | padding: .5em .75em; 58 | font-family: InterMedium; 59 | font-weight: 400; 60 | border: 1px solid var(--border-clr); 61 | border-radius: var(--border-radius-md); 62 | width: 100%; 63 | cursor: pointer; 64 | overflow: hidden; 65 | } 66 | 67 | .protocol.selected { 68 | background-color: #f7fafc; 69 | border-color: transparent; 70 | outline-style: solid; 71 | outline-width: 2px; 72 | outline-color: var(--primary-clr-100); 73 | } 74 | 75 | .protocol.disabled { 76 | cursor: not-allowed; 77 | background-color: var(--gray-clr-100); 78 | opacity: .4; 79 | } 80 | 81 | .protocol button { 82 | color: var(--primary-clr-100); 83 | } 84 | 85 | .protocol__name { 86 | text-transform: capitalize; 87 | font-size: 0.875rem; 88 | color: #2d3748; 89 | font-weight: 400; 90 | } 91 | 92 | .protocol__description { 93 | font-size: 0.75rem; 94 | color: var(--dark-clr-100); 95 | } 96 | 97 | .style_exception1 { 98 | color: black; 99 | } 100 | 101 | @media (max-width: 740px) { 102 | .modal__body { 103 | flex-direction: column; 104 | gap: 1.5rem; 105 | } 106 | } 107 | 108 | -------------------------------------------------------------------------------- /frontend/src/components/UsersTableAccordion.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import ProgressBar from "./ProgressBar"; 4 | import SubscriptionActions from "./agent/SubscriptionActions"; 5 | import Accordion from "./Accordion" 6 | import convertData from "../utils/file-size-util" 7 | 8 | const UsersTableAccordion = ({ 9 | item, 10 | itemKey, 11 | userStatus, 12 | expireTime, 13 | totalData, 14 | dataUsage, 15 | subscriptionLink, 16 | lifetimeUsedTraffic, 17 | config, 18 | QRCodeLinks, 19 | onEditItem 20 | }) => { 21 | return ( 22 | 23 | 24 | 25 | Data Usage 26 | 27 |
28 | {convertData(dataUsage)} / {convertData(totalData)} 29 | Total: {convertData(lifetimeUsedTraffic)} 30 |
31 |
32 |
33 | {userStatus} 34 | {expireTime} 35 |
36 |
37 | {} 38 |
39 |
40 |
41 | 42 | 43 | ) 44 | } 45 | 46 | export default UsersTableAccordion -------------------------------------------------------------------------------- /frontend/src/assets/svg/people.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /frontend/src/assets/svg/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/components/form/inputs/MultiSelect.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import OutlinedInput from '@mui/material/OutlinedInput' 3 | import InputLabel from '@mui/material/InputLabel' 4 | import MenuItem from '@mui/material/MenuItem' 5 | import FormControl from '@mui/material/FormControl' 6 | import ListItemText from '@mui/material/ListItemText' 7 | import Select from '@mui/material/Select' 8 | import Checkbox from '@mui/material/Checkbox' 9 | 10 | const ITEM_HEIGHT = 50 11 | const ITEM_PADDING_TOP = 4 12 | const MenuProps = { 13 | PaperProps: { 14 | style: { 15 | maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP, 16 | width: 250, 17 | }, 18 | }, 19 | } 20 | 21 | 22 | 23 | export default function MultipleSelectCheckmarks({ editValue, styles }) { 24 | 25 | const [names, setNames] = useState([]) 26 | const [personName, setPersonName] = useState([]) 27 | 28 | // useEffect(() => { 29 | // if (editValue) setPersonName(editValue) 30 | // console.log("here") 31 | // }, [editValue]) 32 | 33 | useEffect(() => { 34 | setNames(JSON.parse(sessionStorage.getItem("country_list"))) 35 | if (editValue) { 36 | setPersonName(editValue) 37 | } 38 | }, []) 39 | 40 | const handleChange = (event) => { 41 | const { 42 | target: { value }, 43 | } = event 44 | setPersonName( 45 | typeof value === 'string' ? value.split(',') : value, 46 | 47 | ) 48 | } 49 | 50 | return ( 51 |
52 | 53 | 54 | 79 | 80 |
81 | ) 82 | } -------------------------------------------------------------------------------- /frontend/src/components/Button.css: -------------------------------------------------------------------------------- 1 | .button { 2 | background-color: var(--background-color); 3 | color: var(--color, #fff); 4 | padding: 0.44rem 1.25rem; 5 | text-align: center; 6 | text-decoration: none; 7 | display: inline-flex; 8 | font-size: 0.875rem; 9 | border-radius: 0.375rem; 10 | font-family: InterMedium; 11 | transition: background-color 0.1s ease-in-out; 12 | border: 1px solid var(--border-color); 13 | align-items: center; 14 | justify-content: center; 15 | width: max-content; 16 | gap: .125rem; 17 | } 18 | 19 | .button:hover { 20 | background-color: var(--background-color-hover); 21 | } 22 | 23 | .button:active { 24 | background-color: var(--background-color-active); 25 | } 26 | 27 | .primary { 28 | --background-color: var(--primary-clr-200); 29 | --background-color-hover: var(--primary-clr-300); 30 | --background-color-active: var(--primary-clr-400) 31 | } 32 | 33 | .outlined { 34 | --background-color: transparent; 35 | --border-color: var(--gray-clr-200); 36 | --color: var(--dark-clr-200); 37 | --background-color-hover: var(--gray-clr-100); 38 | --background-color-active: var(--gray-clr-200) 39 | } 40 | 41 | .ghosted { 42 | --background-color: transparent; 43 | --border-color: transparent; 44 | --color: var(--dark-clr-200); 45 | --background-color-hover: var(--gray-clr-200); 46 | --background-color-active: var(--gray-clr-300); 47 | } 48 | 49 | .gray-100 { 50 | --background-color: var(--gray-clr-100); 51 | --background-color-hover: var(--gray-clr-200); 52 | --background-color-active: var(--gray-clr-300); 53 | } 54 | 55 | .button.disabled { 56 | opacity: 0.4; 57 | cursor: not-allowed; 58 | } 59 | 60 | .active { 61 | --background-color: var(--gray-clr-100); 62 | --background-color-hover: var(--gray-clr-200); 63 | } 64 | 65 | .button:has(svg) { 66 | padding: 0.375rem; 67 | } 68 | 69 | .pagination-left-btn { 70 | border-top-right-radius: 0; 71 | border-bottom-right-radius: 0; 72 | border-right: 0; 73 | display: inline-flex; 74 | height: 32px; 75 | } 76 | 77 | .pagination-right-btn { 78 | border-top-left-radius: 0; 79 | border-bottom-left-radius: 0; 80 | display: inline-flex; 81 | height: 32px; 82 | } 83 | 84 | .pagination-btn { 85 | display: flex; 86 | text-align: center; 87 | align-items: center; 88 | border-radius: 0; 89 | border-right: 0; 90 | padding: 0 0.75rem; 91 | height: 32px; 92 | } 93 | 94 | .wide_btn { 95 | width: 50%; 96 | } -------------------------------------------------------------------------------- /frontend/src/components/form/inputs/MultiSelect.css: -------------------------------------------------------------------------------- 1 | .multi-select__options { 2 | width: 100%; 3 | border: 0.5px solid var(--border-clr); 4 | border-radius: var(--border-radius-md); 5 | overflow: hidden; 6 | } 7 | 8 | .multi-select__option { 9 | cursor: pointer; 10 | /* border: 0.5px solid var(--border-clr); */ 11 | /* padding: 0.43rem 0.7rem; */ 12 | max-height: 2.1rem; 13 | border-top: 0.5px solid var(--border-clr); 14 | width: 100%; 15 | transition: background-color 0.1s ease; 16 | overflow: hidden; 17 | } 18 | 19 | .multi-select__option:first-of-type { 20 | border-top: none; 21 | } 22 | 23 | .multi-select__value { 24 | margin-bottom: 0.5rem; 25 | display: flex; 26 | justify-content: space-between; 27 | align-items: center; 28 | align-items: center; 29 | padding: .45rem 0.7rem; 30 | flex-wrap: wrap; 31 | /* max-height: 2.1rem; */ 32 | cursor: pointer; 33 | border: 0.5px solid var(--border-clr); 34 | border-radius: 6px; 35 | gap: 0.5rem; 36 | } 37 | 38 | .chevrondown-icon { 39 | display: flex; 40 | } 41 | 42 | .chevrondown-icon svg { 43 | width: 15px; 44 | height: 15px; 45 | transition: all 0.2s ease-in-out; 46 | } 47 | 48 | .chevrondown-icon.up svg { 49 | transform: rotate(180deg); 50 | } 51 | 52 | .chevrondown-icon.down svg { 53 | transform: rotate(0deg); 54 | } 55 | 56 | .multi-select__value .chevrondown-icon { 57 | margin-left: auto; 58 | } 59 | 60 | .option:last-of-type { 61 | border-bottom-left-radius: 6px; 62 | border-bottom-right-radius: 6px; 63 | } 64 | 65 | .option:first-of-type { 66 | border-top-left-radius: 6px; 67 | border-top-right-radius: 6px; 68 | } 69 | 70 | .option:hover { 71 | background-color: var(--gray-clr-200); 72 | } 73 | 74 | .multi-select__selection { 75 | display: flex; 76 | align-items: center; 77 | font-size: 0.9rem; 78 | padding: 0.15rem 0.4rem; 79 | justify-content: space-between; 80 | background-color: var(--gray-clr-100); 81 | border-radius: var(--border-radius-md); 82 | gap: 0.25rem; 83 | } 84 | 85 | .multi-select__selection .x-icon { 86 | display: flex; 87 | align-items: center; 88 | } 89 | 90 | .multi-select__selection svg { 91 | width: 18px; 92 | height: 15px; 93 | cursor: pointer; 94 | border-radius: var(--border-radius-md); 95 | transition: all 0.1s ease; 96 | } 97 | 98 | .multi-select__selection svg:hover { 99 | background-color: var(--gray-clr-200); 100 | } -------------------------------------------------------------------------------- /frontend/src/components/Dropdown.css: -------------------------------------------------------------------------------- 1 | .dropdown-container { 2 | height: auto; 3 | } 4 | 5 | .dropdown { 6 | border-radius: 6px; 7 | font-size: 0.875rem; 8 | display: flex; 9 | flex-direction: column; 10 | } 11 | 12 | .options { 13 | width: 100%; 14 | } 15 | 16 | .option { 17 | cursor: pointer; 18 | padding: 0.6rem .7rem; 19 | border: 0.5px solid var(--border-clr); 20 | border-top: none; 21 | width: 100%; 22 | } 23 | 24 | .dropdown__value { 25 | display: flex; 26 | align-items: center; 27 | justify-content: flex-start; 28 | cursor: pointer; 29 | padding: 0.4rem .7rem; 30 | border: 0.5px solid var(--border-clr); 31 | border-radius: 6px; 32 | gap: 0.5rem; 33 | } 34 | 35 | .dropdown__value>svg { 36 | width: 40px; 37 | height: 20px; 38 | border-radius: 999px; 39 | padding: 2px; 40 | } 41 | 42 | .dropdown__value .dropdown__value--chevron { 43 | width: 15px; 44 | height: 15px; 45 | margin-left: auto; 46 | transition: all 0.3s ease-in-out; 47 | stroke: currentColor; 48 | } 49 | 50 | .dropdown.open .dropdown__value--chevron { 51 | transform: rotate(180deg); 52 | transition: all 0.3s ease-in-out; 53 | } 54 | 55 | .dropdown.close .dropdown__value--chevron { 56 | transform: rotate(0deg); 57 | transition: all 0.3s ease-in-out; 58 | } 59 | 60 | .option:last-of-type { 61 | border-bottom-left-radius: 6px; 62 | border-bottom-right-radius: 6px; 63 | } 64 | 65 | .option:first-of-type { 66 | border-top-left-radius: 6px; 67 | border-top-right-radius: 6px; 68 | } 69 | 70 | .option:hover { 71 | background-color: var(--gray-clr-200); 72 | } 73 | 74 | .option svg { 75 | stroke: black; 76 | border-radius: 999px; 77 | transform: rotate(0deg) !important; 78 | width: 35px; 79 | height: 20px; 80 | padding: 2px; 81 | } 82 | 83 | .option.active svg, 84 | .dropdown__value.active>svg { 85 | background-color: #c6f6d5; 86 | stroke: #22543d; 87 | } 88 | 89 | .option.disabled svg, 90 | .dropdown__value.disabled>svg { 91 | background-color: #edf2f7; 92 | stroke: #1a202c; 93 | } 94 | 95 | .option.limited svg, 96 | .dropdown__value.limited>svg { 97 | background-color: #fed7d7; 98 | stroke: #822727; 99 | } 100 | 101 | .option.expired svg, 102 | .dropdown__value.expired>svg { 103 | background-color: #feebc8; 104 | stroke: #7b341e; 105 | } 106 | 107 | .option.anonym svg, 108 | .dropdown__value.anonym>svg { 109 | background-color: #f6c8fe; 110 | stroke: #571e7b; 111 | } -------------------------------------------------------------------------------- /frontend/src/assets/svg/power.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 11 | 12 | 13 | 16 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /frontend/src/assets/svg/powerWhite.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 11 | 12 | 13 | 16 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /frontend/src/components/admin/LogsList.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const parse_log = (log) => 4 | { 5 | var words = log.split(" "); 6 | words = words.map((word) => { 7 | if (word.startsWith("!")) { 8 | return `${word.replace("!","")}`; 9 | } 10 | return word; 11 | }); 12 | 13 | return words.join(" "); 14 | } 15 | 16 | const LogsList = ({ logs }) => { 17 | const timestamp_to_date = (timestamp) => { 18 | const date = new Date(timestamp * 1000); 19 | return date.toLocaleString("en-US",{ hourCycle: 'h23' }).replace(", "," - "); 20 | } 21 | 22 | const get_log_style = (log) => 23 | { 24 | if(log.is_syslog) 25 | { 26 | if(log.is_positive) return {backgroundColor:"#edf7fd",border:"1px solid #86c7ed"} 27 | else return {backgroundColor:"#fdedef",border:"1px solid #ed8693"} 28 | } 29 | 30 | if(log.action === "BUY_VOLUME") 31 | { 32 | return {backgroundColor:"#edfdf8",border:"1px solid #67caa1"} 33 | } 34 | 35 | return {} 36 | } 37 | 38 | return ( 39 |
    40 | {logs.map((log) => 41 | { 42 | 43 | if(!log.is_syslog) 44 | return ( 45 |
  • 46 |
    47 |

    48 | 49 | {log.msg.split(" ")[0]} 50 | 51 | 52 |

    53 |
    54 |
    {timestamp_to_date(log.time)}
    55 |
  • 56 | ) 57 | 58 | else 59 | return ( 60 |
  • 61 |
    62 |

    63 | 64 |

    65 |
    66 |
    {timestamp_to_date(log.time)}
    67 |
  • 68 | ) 69 | 70 | })} 71 |
72 | ); 73 | }; 74 | 75 | export default LogsList; -------------------------------------------------------------------------------- /frontend/src/components/admin/EditPanel.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { ReactComponent as EditIcon } from '../../assets/svg/edit.svg' 4 | import { ReactComponent as DeleteIcon } from "../../assets/svg/delete.svg" 5 | import Form from '../form/Form' 6 | import styles from "./EditPanel.module.css" 7 | 8 | const EditPanel = ({ onClose, showForm, onDeleteItem, item, onPowerItem, onEditItem, editMode }) => { 9 | 10 | const formFields = [ 11 | { label: "Name", type: "text", id: "panel_name", name: "name" }, 12 | { label: "Username", type: "text", id: "panel_username", name: "username" }, 13 | { label: "Password", type: "text", id: "panel_password", name: "password" }, 14 | { label: "Panel Url", type: "text", id: "panel_url", name: "panel_url" }, 15 | { label: "Capacity", type: "number", id: "panel_user_max_count", name: "capacity" }, 16 | { label: "Traffic", type: "number", id: "panel_traffic", name: "traffic" }, 17 | { label: "Country", type: "text", id: "panel_country", name: "country", disabled: true } 18 | ] 19 | 20 | const primaryButtons = [ 21 | { label: "Cancel", className: "outlined", onClick: onClose }, 22 | { 23 | label: "Edit Panel", className: "primary", onClick: () => onEditItem( 24 | item.id, 25 | document.getElementById("panel_name").value, 26 | document.getElementById("panel_username").value, 27 | document.getElementById("panel_password").value, 28 | document.getElementById("panel_url").value, 29 | document.getElementById("panel_user_max_count").value, 30 | document.getElementById("panel_traffic").value, 31 | document.getElementById("panel_country").value 32 | ), 33 | disabled: editMode, 34 | pendingText: "Editing..." 35 | }, 36 | ] 37 | 38 | const secondaryButtons = [ 39 | { icon: , type: "button", label: "Delete", className: "ghosted", onClick: (e) => onDeleteItem(e, item.id) }, 40 | { type: "switch", label: "Power", className: "ghosted", onClick: () => onPowerItem(item.id, item.disable) }, 41 | ] 42 | 43 | return ( 44 |
} 49 | primaryButtons={primaryButtons} 50 | secondaryButtons={secondaryButtons} 51 | formFields={formFields} 52 | item={item} 53 | width={"40rem"} 54 | styles={styles} 55 | /> 56 | 57 | ) 58 | } 59 | 60 | export default EditPanel -------------------------------------------------------------------------------- /frontend/src/pages/admin/AdminLogsPage.css: -------------------------------------------------------------------------------- 1 | .admin-log-page__filter { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: space-between; 5 | align-items: end; 6 | gap: 1rem; 7 | margin-bottom: 1.5rem; 8 | } 9 | 10 | 11 | .MuiInputBase-root.MuiOutlinedInput-root.MuiInputBase-sizeSmall { 12 | height: 56px; 13 | border-radius: 6px; 14 | outline: none; 15 | } 16 | 17 | .logs { 18 | display: flex; 19 | flex-direction: column; 20 | gap: 1rem; 21 | } 22 | 23 | .log { 24 | display: flex; 25 | flex-direction: column; 26 | border: 1px solid var(--border-clr); 27 | border-radius: var(--border-radius-md); 28 | background-color: #F9FAFB; 29 | padding: 1rem; 30 | gap: .5rem; 31 | } 32 | 33 | .log__text { 34 | font-family: InterMedium; 35 | color: var(--dark-clr-300); 36 | } 37 | 38 | .log__date { 39 | font-size: 0.875rem; 40 | font-family: Inter; 41 | color: var(--dark-clr-100); 42 | } 43 | 44 | .admin-log-page__filter .MuiInputBase-root { 45 | width: 100%; 46 | } 47 | 48 | .admin-log-page__filter .MuiFormControl-root { 49 | width: 100%; 50 | } 51 | 52 | /* @media (max-width: 768px) { 53 | .MuiInputBase-root.MuiOutlinedInput-root.MuiInputBase-sizeSmall { 54 | width: 100%; 55 | } 56 | 57 | .admin-log-page__filter { 58 | flex-wrap:wrap; 59 | } 60 | } */ 61 | 62 | .exit-account { 63 | width: 150px; 64 | display: flex; 65 | justify-content: center; 66 | align-items: center; 67 | align-content: center; 68 | flex-direction: row; 69 | font-size: 20px; 70 | cursor: pointer; 71 | background-color: #F9FAFB; 72 | border: 1px solid #d2d2d4; 73 | padding: 5px; 74 | border-radius: 0.875rem; 75 | transition: all 0.2s; 76 | } 77 | 78 | .exit-account:hover { 79 | background-color: #ededee; 80 | } 81 | 82 | .exit-account img { 83 | opacity: 1; 84 | background: transparent; 85 | width: 40px; 86 | height: 40px; 87 | } 88 | 89 | .loading_gif { 90 | width: 100px; 91 | height: 100px; 92 | background: transparent; 93 | opacity: 1; 94 | } 95 | 96 | .loading_gif_container { 97 | display: flex; 98 | justify-content: center; 99 | align-items: center; 100 | align-content: center; 101 | flex-direction: row; 102 | } 103 | 104 | .log_buttons_div 105 | { 106 | display:flex; 107 | justify-content:space-between; 108 | align-items:center; 109 | align-content:center; 110 | flex-direction:row; 111 | width:100%; 112 | } 113 | 114 | .syslog_gray_bg 115 | { 116 | background-color:#e2e8f0 !important; 117 | } -------------------------------------------------------------------------------- /frontend/src/components/ErrorCard.module.css: -------------------------------------------------------------------------------- 1 | .errorCard { 2 | position: fixed; 3 | top: 0; 4 | right: 0; 5 | color: #191919; 6 | background-color: #fdedef; 7 | margin-right: 1rem; 8 | margin-top: 1rem; 9 | padding: 1rem 1.5rem; 10 | border-radius: 10px; 11 | box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.2); 12 | max-width: 425px; 13 | min-height: 95px; 14 | display: flex; 15 | align-items: center; 16 | gap: 1rem; 17 | z-index:2000; 18 | } 19 | 20 | .okCard 21 | { 22 | position: fixed; 23 | top: 0; 24 | right: 0; 25 | color: #191919; 26 | background-color: rgb(237, 253, 246); 27 | margin-right: 1rem; 28 | margin-top: 1rem; 29 | padding: 1rem 1.5rem; 30 | border-radius: 10px; 31 | box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.2); 32 | max-width: 425px; 33 | min-height: 95px; 34 | display: flex; 35 | align-items: center; 36 | gap: 1rem; 37 | z-index:2000; 38 | } 39 | 40 | .shoppingCard 41 | { 42 | position: fixed; 43 | top: 0; 44 | right: 0; 45 | color: #191919; 46 | background-color: rgb(230, 230, 230); 47 | margin-right: 1rem; 48 | margin-top: 1rem; 49 | padding: 1rem 1.5rem; 50 | border-radius: 10px; 51 | box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.2); 52 | max-width: 425px; 53 | min-height: 95px; 54 | display: flex; 55 | align-items: center; 56 | gap: 1rem; 57 | z-index:2000; 58 | } 59 | 60 | .errorCard__divider>*+* { 61 | margin-top: 0.25rem; 62 | } 63 | 64 | .errorCard__header { 65 | display: flex; 66 | align-items: center; 67 | } 68 | 69 | .errorCard__header h2 { 70 | font-family: InterMedium; 71 | font-size: 1.25rem; 72 | font-weight: 600; 73 | } 74 | 75 | .errorCard__header svg { 76 | width: 20px; 77 | height: 20px; 78 | align-self: self-start; 79 | cursor: pointer; 80 | stroke-width: 2; 81 | margin-left: auto; 82 | } 83 | 84 | .errorCard__message { 85 | font-size: 1rem; 86 | font-weight: 400; 87 | line-height: 1.25; 88 | font-family: InterMedium; 89 | } 90 | 91 | .okCardImg 92 | { 93 | width:64px; 94 | height:64px; 95 | padding:0; 96 | background:transparent; 97 | opacity:1; 98 | } 99 | 100 | .shoppingCardImgNegative 101 | { 102 | width:64px; 103 | height:64px; 104 | padding:10px; 105 | background:transparent; 106 | opacity:1; 107 | background:#ef4d61; 108 | border-radius:100%; 109 | } 110 | 111 | .shoppingCardImgPositive 112 | { 113 | width:64px; 114 | height:64px; 115 | padding:10px; 116 | background:transparent; 117 | opacity:1; 118 | background:#4bae4f; 119 | border-radius:100%; 120 | } -------------------------------------------------------------------------------- /backup_service.js: -------------------------------------------------------------------------------- 1 | const { Telegraf } = require('telegraf'); 2 | const nodemailer = require('nodemailer'); 3 | const fs = require('fs'); 4 | const { get_backup_from_everything } = require('./utils'); 5 | const sleep = (s) => new Promise(resolve => setTimeout(resolve,s*1000)); 6 | require('dotenv').config() 7 | 8 | async function init() 9 | { 10 | await sleep(20); 11 | 12 | var config_obj = JSON.parse(await fs.promises.readFile("./backup_config.json", "utf8")); 13 | const bot = new Telegraf(config_obj.telegram.bot_token); 14 | const chat_id = config_obj.telegram.chat_id; 15 | const transporter = nodemailer.createTransport(config_obj.email.sender); 16 | 17 | while(true) 18 | { 19 | try 20 | { 21 | console.log("*STARTING BACKUP SERVICE"); 22 | 23 | var dbdl_files = await fs.promises.readdir("./public/dbdl"); 24 | 25 | for(var i=0;i 2*60*60*24*1000) await fs.promises.unlink(file_path); 32 | } 33 | 34 | 35 | var db_url = await get_backup_from_everything() 36 | var filePath = "./public" + db_url; 37 | var today = new Date(); 38 | var time = today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds(); 39 | 40 | const mailOptions = 41 | { 42 | from: config_obj.email.sender.auth.user, 43 | to: config_obj.email.receiver, 44 | subject: db_url.split("/dbdl/")[1].replace(".zip", ""), 45 | text: '', 46 | attachments: 47 | [ 48 | { 49 | filename: 'db.zip', 50 | path: filePath 51 | } 52 | ] 53 | }; 54 | 55 | // SEND IN TELEGRAM 56 | if(!config_obj.telegram.disabled) 57 | { 58 | var res1 = await bot.telegram.sendDocument(chat_id, { source: filePath }); 59 | console.log(time + " ---> Backup Sent to telegram"); 60 | } 61 | 62 | // SEND IN EMAIL 63 | if(!config_obj.email.disabled) 64 | { 65 | var res2 = await transporter.sendMail(mailOptions); 66 | console.log(time + " ---> Backup Sent to email"); 67 | } 68 | 69 | await fs.promises.unlink(filePath); 70 | } 71 | catch (err) 72 | { 73 | console.log(err); 74 | } 75 | 76 | await sleep(config_obj.interval); 77 | } 78 | } 79 | 80 | init(); 81 | 82 | -------------------------------------------------------------------------------- /frontend/src/components/admin/PanelsTable.jsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | import EmptyTable from "../EmptyTable" 4 | import "./PanelsTable.css" 5 | import gbOrTb from "../../utils/gbOrTb" 6 | const show_url = (str) => 7 | { 8 | str = str.replace(/^https?:\/\//, ''); 9 | str = str.replace(/:\d+$/, ''); 10 | return ({str}) 11 | } 12 | 13 | const render_panel_type = (panel_type) => 14 | { 15 | if(!panel_type || panel_type === "MZ") 16 | { 17 | return (MZ) 18 | } 19 | 20 | if(panel_type === "AMN") 21 | { 22 | return (AMN) 23 | } 24 | } 25 | 26 | const AdminPanelsTable = ({ items, itemsPerPage, currentItems, onEditItem, onCreateItem }) => { 27 | return ( 28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | {items.length === 0 43 | ? 44 | : currentItems.map((item) => ( 45 | onEditItem(item)} key={item.id}> 46 | 47 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | ))} 59 | 60 |
NameStatusData UsageTrafficActive UserscapacityCountry
{item.panel_name}

{render_panel_type(item.panel_type) } {show_url(item.panel_url)}
48 | 49 | {item.disable ? "Disabled" : "Active"} 50 | 51 | item.panel_traffic?"panel_table_alarm_text":""}>{gbOrTb(item.panel_data_usage)}{gbOrTb(item.panel_traffic)}item.panel_user_max_count?"panel_table_alarm_text":""} >{item.active_users + " / " + item.total_users}{item.panel_user_max_count}{item.panel_country}
61 |
62 | ) 63 | } 64 | 65 | 66 | 67 | export default AdminPanelsTable -------------------------------------------------------------------------------- /frontend/src/components/Navbar.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { NavLink, useLocation } from 'react-router-dom'; 3 | 4 | import './Navbar.css'; 5 | 6 | const Navbar = () => { 7 | const [style, setStyle] = useState({}); 8 | const location = useLocation(); 9 | const isAgentPath = location.pathname.startsWith('/agent'); 10 | 11 | const calculateLinkStyles = (element) => { 12 | const liRect = element.getBoundingClientRect(); 13 | const liWidth = liRect.width; 14 | const liLeftPostion = liRect.left; 15 | 16 | setStyle({ width: liWidth, transform: `translateX(${liLeftPostion}px)` }); 17 | }; 18 | 19 | const handleLinkClick = (event) => { 20 | const liElement = event.currentTarget.parentElement; 21 | calculateLinkStyles(liElement); 22 | }; 23 | 24 | useEffect(() => { 25 | setTimeout(() => { 26 | const activeLiElement = document.querySelector('.navbar__links .active').parentElement; 27 | if (activeLiElement) { 28 | calculateLinkStyles(activeLiElement); 29 | } 30 | }, 400) 31 | }, [location.pathname]); 32 | 33 | const agentLinks = [ 34 | // { path: '/agent/home', linkText: 'Home' }, 35 | { path: '/agent/users', linkText: 'Users' }, 36 | { path: '/agent/settings', linkText: 'Settings' }, 37 | { path: '/agent/log', linkText: 'Log' }, 38 | ]; 39 | 40 | const adminLinks = [ 41 | { path: '/admin/home', linkText: 'Home' }, 42 | { path: '/admin/panels', linkText: 'Panels' }, 43 | { path: '/admin/agents', linkText: 'Agents' }, 44 | { path: '/admin/settings', linkText: 'Settings' }, 45 | { path: '/admin/log', linkText: 'Log' }, 46 | ]; 47 | 48 | return ( 49 | 77 | ); 78 | }; 79 | 80 | export default Navbar; 81 | -------------------------------------------------------------------------------- /frontend/src/components/admin/AgentsTable.jsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | import "./AgentsTable.css" 4 | import EmptyTable from "../EmptyTable" 5 | import gbOrTb from "../../utils/gbOrTb" 6 | import businessModeIcon from "../../assets/bm.png" 7 | 8 | const b2gb = (bytes) => { 9 | return (bytes / (2 ** 10) ** 3).toFixed(2); 10 | } 11 | 12 | const showCountries = (str) => { 13 | if(!str) return NO ACCESS; 14 | var country_arr = str.split(","); 15 | return country_arr.map((country) => ( 16 | {country} 17 | )); 18 | } 19 | 20 | const AdminPanelsTable = ({ items, itemsPerPage, currentItems, onEditItem, onCreateItem }) => { 21 | return ( 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | {items.length === 0 38 | ? 39 | : currentItems.map((item) => ( 40 | onEditItem(item)} key={item.id} agent_id={item.id} > 41 | 42 | 47 | 48 | 49 | 50 | 51 | 52 | 55 | 56 | ))} 57 | 58 |
NameStatusActive UsersData UsageRemaining DataAllocatable DataPrefixCountry
{item.name}
43 | 44 | {item.disable ? "Disabled" : "Active"} 45 | 46 | {item.active_users + " / " + item.total_users}{gbOrTb(item.used_traffic) + " / " + gbOrTb(b2gb(item.lifetime_volume))}{gbOrTb(b2gb(item.volume))}{gbOrTb(item.allocatable_data)}{item.prefix} 53 |
{showCountries(item.country)}
54 |
59 |
60 | ) 61 | } 62 | 63 | export default AdminPanelsTable -------------------------------------------------------------------------------- /frontend/src/assets/svg/death.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 10 | 13 | 16 | -------------------------------------------------------------------------------- /frontend/src/components/Dropdown.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef } from 'react' 2 | import { motion, AnimatePresence } from 'framer-motion' 3 | 4 | import "./Dropdown.css" 5 | import { ReactComponent as ChevronDownIcon } from '../assets/svg/chevron-down.svg' 6 | 7 | const Dropdown = ({ options, value, onChange, overlap, showChevron = true }) => { 8 | const [isOpen, setIsOpen] = useState(false) 9 | const divEl = useRef(null) 10 | 11 | useEffect(() => { 12 | const handler = (event) => { 13 | if (!divEl.current) return 14 | 15 | if (!divEl.current.contains(event.target)) { 16 | setIsOpen(false) 17 | } 18 | } 19 | document.addEventListener('click', handler, true) 20 | 21 | return () => { 22 | document.removeEventListener('click', handler, true) 23 | } 24 | }, []) 25 | 26 | const handleClick = (e) => { 27 | e.stopPropagation() 28 | setIsOpen(!isOpen) 29 | } 30 | 31 | const handleOptionClick = (option, e) => { 32 | e.stopPropagation() 33 | setIsOpen(false) 34 | onChange(option) 35 | } 36 | 37 | const renderedOptions = options.map((option, index) => { 38 | if (option.value === value?.value) return null 39 | 40 | return handleOptionClick(option, e)} 43 | key={option.value} 44 | initial={{ x: -10, opacity: 0 }} 45 | animate={{ x: 0, opacity: 1 }} 46 | exit={{ x: 0, opacity: 0 }} 47 | transition={{ delay: index * 0.07 }} 48 | > 49 | {option.label} 50 | 51 | }) 52 | 53 | return ( 54 |
55 |
56 |
handleClick(e)}> 57 | {value?.label || 10} 58 | {showChevron && 59 | 60 | 61 | 62 | } 63 |
64 | 65 | {isOpen && 66 | 73 | {renderedOptions} 74 | 75 | } 76 | 77 |
78 |
79 | ) 80 | } 81 | 82 | export default Dropdown -------------------------------------------------------------------------------- /amnezia_wrapper/notes.txt: -------------------------------------------------------------------------------- 1 | cat /opt/amnezia/awg/clientsTable 2 | cat /opt/amnezia/awg/wg0.conf 3 | 4 | wg show wg0 dump 5 | wg genkey 6 | echo ${privateKey} | wg pubkey 7 | wg genpsk 8 | cd /opt/amnezia/awg/ && wg syncconf wg0 <(wg-quick strip ./wg0.conf) 9 | wg-quick down wg0 10 | wg-quick up wg0 11 | wg show wg0 public-key 12 | wg show wg0 preshared-keys 13 | 14 | 15 | ----> get clients & add new client 16 | 17 | 18 | 19 | sDhwgcNuHoATzfNpBsDvKkcvM7Sbh/fHmC+mUgucNFQ= 20 | echo sDhwgcNuHoATzfNpBsDvKkcvM7Sbh/fHmC+mUgucNFQ= | wg pubkey 21 | WxslksY7PyIygXDSUfrUnL9DHdTFrjsGRa8iL93JlEU= 22 | 23 | [Interface] 24 | Address = 10.8.1.9/32 25 | DNS = 1.1.1.1, 1.0.0.1 26 | PrivateKey = pUjGJr8438anm5Ou6WoO5v+aPl4+eNWsxfizjEgqwXs= 27 | Jc = 4 28 | Jmin = 10 29 | Jmax = 50 30 | S1 = 108 31 | S2 = 18 32 | H1 = 548102439 33 | H2 = 96202383 34 | H3 = 1018342978 35 | H4 = 415451259 36 | 37 | [Peer] 38 | PublicKey = q/EyVtN6iNBLR2gaCrNJHSYMMETtaaoQc96FMC539m0= 39 | PresharedKey = AVj26itcm+3ggdSbIW2yVQiAJO3SPz9ymMh639KXi9Y= 40 | AllowedIPs = 0.0.0.0/0, ::/0 41 | Endpoint = 92.60.70.175:49365 42 | PersistentKeepalive = 25 43 | 44 | 45 | 46 | [Symbol(shapeMode)]: false, 47 | [Symbol(kCapture)]: false, 48 | [Symbol(kBytesWritten)]: 0, 49 | [Symbol(kNeedDrain)]: false, 50 | [Symbol(corked)]: 0, 51 | [Symbol(kOutHeaders)]: [Object: null prototype] { 'x-powered-by': [Array] }, 52 | [Symbol(errored)]: null, 53 | [Symbol(kHighWaterMark)]: 16384, 54 | [Symbol(kRejectNonStandardBodyWrites)]: false, 55 | [Symbol(kUniqueHeaders)]: null 56 | }, 57 | body: { 58 | app_version: '4.8.1.9', 59 | installation_uuid: '0dba1e29-7903-4938-a043-8b11e02b0054', 60 | os_version: 'android', 61 | public_key: 'CRU0XC9rotpvcYrPvimnVzAutJD/82l98qqxoDzqjUA=' 62 | }, 63 | _body: true, 64 | length: undefined, 65 | _eventsCount: 0, 66 | route: Route { path: '/ping', stack: [ [Layer] ], methods: { post: true } }, 67 | [Symbol(shapeMode)]: true, 68 | [Symbol(kCapture)]: false, 69 | [Symbol(kHeaders)]: { 70 | host: '92.60.70.175:3000', 71 | 'content-type': 'application/json', 72 | authorization: 'Api-Key RNg3cqqe.1rfYYZvVv4Dt8CtNsveQ7hr90ddwGS9N', 73 | 'content-length': '193', 74 | connection: 'Keep-Alive', 75 | 'accept-encoding': 'gzip, deflate', 76 | 'accept-language': 'en-GB,*', 77 | 'user-agent': 'Mozilla/5.0' 78 | }, 79 | [Symbol(kHeadersCount)]: 16, 80 | 81 | 82 | 83 | { 84 | "clientId": "ueEoTIXSR0sXvYjysmwDtjbG7+g/pRce2rqX4h2DoEg=", 85 | "userData": { 86 | "clientName": "New client666", 87 | "creationDate": "Sun Oct 13 05:14:28 2024" 88 | } 89 | } 90 | 91 | // (item?.corresponding_panel_id == 948263502) ? "AMN" : "MZ" == "AMN"? { icon: , type: "button", label: "Unlock Account", className: "ghosted", onClick: () => onUnlockItem(item.id) } : 92 | npm i express dotenv mongoose jsonwebtoken jalali-moment node-cron adm-zip 93 | sudo apt install wireguard 94 | mongoimport --uri "mongodb://localhost:27017/knaw" --collection users --file /root/users.json --jsonArray 95 | docker cp amnezia_interface.conf 213:/opt/amnezia/awg/wg0.conf 96 | docker cp amnezia_clients_table.json 213:/opt/amnezia/awg/clientsTable 97 | -------------------------------------------------------------------------------- /frontend/src/assets/svg/data-center.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /frontend/src/assets/svg/cart.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /frontend/src/assets/svg/cart2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /frontend/src/components/agent/UsageStats.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import "./UsageStats.css"; 4 | import LeadingIcon from "../LeadingIcon"; 5 | import { ReactComponent as PieChartIcon } from "../../assets/svg/pie-chart.svg"; 6 | import { ReactComponent as DataCenterIcon } from "../../assets/svg/data-center.svg" 7 | import { ReactComponent as GraphBarIcon } from "../../assets/svg/graph-bar.svg" 8 | import { ReactComponent as UsersIcon } from "../../assets/svg/users.svg" 9 | import { ReactComponent as PersonIcon } from "../../assets/svg/person.svg" 10 | import PlusIcon from "../../assets/svg/plus.svg" 11 | 12 | 13 | const UsageStats = ({ activeUsers, totalUsers, dataUsage, remainingData, allocableData, remainingUsers, lifetime_volume, business_mode,agent_name,onShowBuyVolumePopup }) => { 14 | return ( 15 |
16 | 17 |
18 |
19 | 20 | 21 | 22 |
Agent Name
23 |
{agent_name}
24 |
25 |
26 | 27 |
28 |
29 | 30 | 31 | 32 |
Active Users
33 |
{activeUsers} / {totalUsers} ({remainingUsers} left)
34 |
35 |
36 | 37 | 38 | 39 |
Data Usage
40 |
{dataUsage} / {lifetime_volume}
41 |
42 |
43 |
44 | { business_mode == "0" && 45 |
46 | 47 | 48 | 49 |
Remaining Data
50 |
{remainingData}
51 |
52 | } 53 | 54 |
55 | 56 | 57 | 58 | 59 | 60 |
onShowBuyVolumePopup()} className="buy_volume_section"> 61 | plus 62 |
63 | 64 |
{business_mode=="0"?"Allocatable Data":"Remaining Data"}
65 |
{allocableData}
66 |
67 |
68 |
69 | ) 70 | } 71 | 72 | export default UsageStats; -------------------------------------------------------------------------------- /frontend/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { BrowserRouter, Routes, Route } from 'react-router-dom'; 3 | 4 | import AgentLogsPage from './pages/agent/AgentLogsPage'; 5 | import AgentSettingsPage from './pages/agent/AgentSettingsPage'; 6 | import AdminLogsPage from './pages/admin/AdminLogsPage'; 7 | import AdminSettingsPage from './pages/admin/AdminSettingsPage'; 8 | import UsersPage from './pages/agent/UsersPage'; 9 | import Login from './pages/Login'; 10 | import Navbar from './components/Navbar'; 11 | import UniversalLogout from './components/UniversalLogout'; 12 | import PanelsPage from './pages/admin/PanelsPage'; 13 | import AgentsPage from './pages/admin/AgentsPage'; 14 | import NotFoundPage from './pages/NotFoundPage'; 15 | import AdminHomePage from './pages/admin/AdminHomePage'; 16 | import AgentHomePage from './pages/agent/AgentHomePage'; 17 | import axios from './axiosConfig'; 18 | 19 | 20 | 21 | const App = () => { 22 | const [location, setLocation] = useState(window.location.pathname); 23 | const [isLoggedIn, setIsLoggedIn] = useState( 24 | sessionStorage.getItem("isLoggedIn") === "true" 25 | ); 26 | 27 | // Check if the current path belongs to Agent or Admin routes 28 | const isAgentPath = 29 | location.startsWith("/agent/users") || 30 | location.startsWith("/agent/settings") || 31 | location.startsWith("/agent/home") || 32 | location.startsWith("/agent/log"); 33 | 34 | const isAdminPath = 35 | location.startsWith("/admin/panels") || 36 | location.startsWith("/admin/agents") || 37 | location.startsWith("/admin/settings") || 38 | location.startsWith("/admin/home") || 39 | location.startsWith("/admin/log"); 40 | 41 | return ( 42 | 43 | {isLoggedIn && (isAdminPath || isAgentPath) && } 44 | {isLoggedIn && (isAdminPath || isAgentPath) && } 45 | 46 | } /> 47 | {/* Agent Routes */} 48 | {isAgentPath && isLoggedIn && } />} 49 | {isAgentPath && isLoggedIn && } />} 50 | {isAgentPath && isLoggedIn && } />} 51 | {isAgentPath && isLoggedIn && } />} 52 | {/* Admin Routes */} 53 | {isAdminPath && isLoggedIn && } />} 54 | {isAdminPath && isLoggedIn && } />} 55 | {isAdminPath && isLoggedIn && } />} 56 | {isAdminPath && isLoggedIn && } />} 57 | {isAdminPath && isLoggedIn && } />} 58 | 59 | {/* 404 - Not Found */} 60 | } /> 61 | 62 | 63 | ); 64 | }; 65 | 66 | export default App; -------------------------------------------------------------------------------- /amnezia_wrapper/decoder.py: -------------------------------------------------------------------------------- 1 | import collections 2 | import argparse 3 | import base64 4 | import json 5 | import zlib 6 | 7 | def encode_config(config): 8 | 9 | if "description" in config: 10 | config["description"] = config["description"].encode('utf-8').decode('unicode_escape') 11 | 12 | json_str = json.dumps(config, indent=4).encode() 13 | 14 | compressed_data = zlib.compress(json_str) 15 | 16 | original_data_len = len(json_str) 17 | header = original_data_len.to_bytes(4, byteorder='big') 18 | 19 | encoded_data = base64.urlsafe_b64encode(header + compressed_data).decode().rstrip("=") 20 | return f"vpn://{encoded_data}" 21 | 22 | def decode_config(encoded_string): 23 | encoded_data = encoded_string.replace("vpn://", "") 24 | padding = 4 - (len(encoded_data) % 4) 25 | encoded_data += "=" * padding 26 | compressed_data = base64.urlsafe_b64decode(encoded_data) 27 | 28 | try: 29 | original_data_len = int.from_bytes(compressed_data[:4], byteorder='big') 30 | 31 | decompressed_data = zlib.decompress(compressed_data[4:]) 32 | 33 | if len(decompressed_data) != original_data_len: 34 | raise ValueError("Invalid length of decompressed data") 35 | 36 | return json.loads(decompressed_data, object_pairs_hook=collections.OrderedDict) 37 | except zlib.error: 38 | return json.loads(compressed_data.decode(), object_pairs_hook=collections.OrderedDict) 39 | 40 | if __name__ == "__main__": 41 | parser = argparse.ArgumentParser( 42 | description='''Converts AmneziaVPN configuration between Base64 string and JSON. 43 | 44 | Usage examples: 45 | 46 | 1. Decode Base64 string to console: 47 | python amnezia-config-decoder.py vpn://AAAGX.. 48 | 49 | 2. Decode Base64 string and save to file: 50 | python amnezia-config-decoder.py vpn://AAAGX.. -o config.json 51 | 52 | 3. Encode JSON from file to Base64 string: 53 | python amnezia-config-decoder.py -i config.json''', 54 | formatter_class=argparse.RawDescriptionHelpFormatter 55 | ) 56 | parser.add_argument('encoded_string', metavar='vpn://...', type=str, nargs='?', 57 | help='Base64 string with "vpn://" prefix containing AmneziaVPN configuration.') 58 | parser.add_argument('-i', '--input', metavar='input.json', type=str, 59 | help='Path to JSON file to read configuration.') 60 | parser.add_argument('-o', '--output', metavar='output.json', type=str, 61 | help='Path to JSON file to write decoded configuration. ' 62 | 'If not specified, configuration will be printed to console.') 63 | 64 | args = parser.parse_args() 65 | 66 | if args.input and args.encoded_string: 67 | parser.print_help() 68 | print("\nError: Cannot specify both Base64 string and JSON file simultaneously.") 69 | elif args.input: 70 | with open(args.input, 'r') as f: 71 | config = json.load(f) 72 | encoded_string = encode_config(config) 73 | print(encoded_string) 74 | elif args.encoded_string: 75 | config = decode_config(args.encoded_string) 76 | if args.output: 77 | with open(args.output, 'w') as f: 78 | json.dump(config, f, indent=4) 79 | print(f"Configuration saved to {args.output}") 80 | else: 81 | print(json.dumps(config, indent=4)) 82 | else: 83 | parser.print_help() -------------------------------------------------------------------------------- /frontend/src/assets/svg/dldb.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 18 | 19 | 20 | 21 | --------------------------------------------------------------------------------