├── 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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/frontend/src/assets/svg/accept.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
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 |
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 |
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 |
--------------------------------------------------------------------------------
/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 |

setEnabled(!enabled)} />
13 |
14 | )
15 | }
16 |
17 | export default AmneziaFilter
--------------------------------------------------------------------------------
/frontend/src/assets/svg/users.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/frontend/src/assets/svg/lockWhite.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/frontend/src/assets/svg/document.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/frontend/src/assets/svg/exclamation-mark.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/frontend/src/assets/svg/switch.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------
/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 | }
45 | renderValue={(selected) => selected.join(',')}
46 | MenuProps={MenuProps}
47 | >
48 | {names.map((name) => (
49 |
53 | ))}
54 |
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 | }
51 | renderValue={(selected) => selected.join(',')}
52 | MenuProps={MenuProps}
53 | >
54 | {names.map((name) => (
55 |
59 | ))}
60 |
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 |
--------------------------------------------------------------------------------
/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 | }
69 | renderValue={(selected) => selected.join(',')}
70 | MenuProps={MenuProps}
71 | >
72 | {names.map((name) => (
73 |
77 | ))}
78 |
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 |
--------------------------------------------------------------------------------
/frontend/src/assets/svg/powerWhite.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/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 |
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 | | Name |
33 | Status |
34 | Data Usage |
35 | Traffic |
36 | Active Users |
37 | capacity |
38 | Country |
39 |
40 |
41 |
42 | {items.length === 0
43 | ?
44 | : currentItems.map((item) => (
45 | onEditItem(item)} key={item.id}>
46 | {item.panel_name} {render_panel_type(item.panel_type) } {show_url(item.panel_url)} |
47 |
48 |
49 | {item.disable ? "Disabled" : "Active"}
50 |
51 | |
52 | item.panel_traffic?"panel_table_alarm_text":""}>{gbOrTb(item.panel_data_usage)} |
53 | {gbOrTb(item.panel_traffic)} |
54 | item.panel_user_max_count?"panel_table_alarm_text":""} >{item.active_users + " / " + item.total_users} |
55 | {item.panel_user_max_count} |
56 | {item.panel_country} |
57 |
58 | ))}
59 |
60 |
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 | | Name |
27 | Status |
28 | Active Users |
29 | Data Usage |
30 | Remaining Data |
31 | Allocatable Data |
32 | Prefix |
33 | Country |
34 |
35 |
36 |
37 | {items.length === 0
38 | ?
39 | : currentItems.map((item) => (
40 | onEditItem(item)} key={item.id} agent_id={item.id} >
41 | {item.name} |
42 |
43 |
44 | {item.disable ? "Disabled" : "Active"}
45 |
46 | |
47 | {item.active_users + " / " + item.total_users} |
48 | {gbOrTb(item.used_traffic) + " / " + gbOrTb(b2gb(item.lifetime_volume))} |
49 | {gbOrTb(b2gb(item.volume))} |
50 | {gbOrTb(item.allocatable_data)} |
51 | {item.prefix} |
52 |
53 | {showCountries(item.country)}
54 | |
55 |
56 | ))}
57 |
58 |
59 |
60 | )
61 | }
62 |
63 | export default AdminPanelsTable
--------------------------------------------------------------------------------
/frontend/src/assets/svg/death.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/frontend/src/assets/svg/cart2.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 |

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 |
--------------------------------------------------------------------------------