diff --git a/app/db/crud.py b/app/db/crud.py
@@ -678,6 +678,9 @@ def update_node(db: Session, dbnode: Node, modify: NodeModify):
if modify.usage_coefficient:
dbnode.usage_coefficient = modify.usage_coefficient
+ if modify.connection_backend:
+ dbnode.connection_backend = modify.connection_backend
+
db.commit()
db.refresh(dbnode)
return dbnode
diff --git a/dashboard/public/locales/en.json b/dashboard/public/locales/en.json
@@ -89,7 +89,8 @@
}
},
"usage_coefficient": "Usage Coefficient",
- "add_as_new_host": "Add as new hosts",
+ "connection_backend": "Backend Connection",
+ "connection_backend_desc": "Used for remote tls connections",
"certificate-alert": {
"title": "Nodes Certificate Configuration",
"desc": "If you aim to configure your nodes certificate, checkout settings page.",
diff --git a/dashboard/public/locales/fa.json b/dashboard/public/locales/fa.json
@@ -1,164 +1,165 @@
{
- "home": "خانه",
- "name": "نام",
- "status": "حالت",
+ "home": "خاٿٿ",
+ "name": "ٿام",
+ "status": "حاٿت",
"address": "آدرس",
- "users": "کاربران",
- "nodes": "نود",
- "services": "سرویسها",
- "hosts": "هاست",
- "username": "یوزرنیم",
- "password": "پسوورد",
- "inbounds": "اینباند ها",
- "submit": "ارسال",
- "login": "ورود",
- "edit": "ویرایش",
- "actions": "فعالیتها",
- "settings": "تنظیمات",
- "logs": "لاگ",
- "note": "یادداشت",
- "config": "کانفیگ",
+ "users": "کاربراٿ",
+ "nodes": "ٿٿد",
+ "services": "سرٿۿسٿا",
+ "hosts": "ٿاست",
+ "username": "ۿٿزرٿۿم",
+ "password": "پسٿٿرد",
+ "inbounds": "اۿٿباٿد ٿا",
+ "submit": "ارساٿ",
+ "login": "ٿرٿد",
+ "edit": "ٿۿراۿش",
+ "actions": "ٿعاٿۿتٿا",
+ "settings": "تٿظۿمات",
+ "logs": "ٿاگ",
+ "note": "ۿادداشت",
+ "config": "کاٿٿۿگ",
"tag": "تگ",
- "protocol": "پروتکل",
- "online": "آنلاین",
- "offline": "آفلاین",
- "not_connected": "وصل نشده",
- "apply": "اعمال",
- "open": "باز کردن",
- "delete": "حذف",
- "port": "پورت",
+ "protocol": "پرٿتکٿ",
+ "online": "آٿٿاۿٿ",
+ "offline": "آٿٿاۿٿ",
+ "not_connected": "ٿصٿ ٿشدٿ",
+ "apply": "اعماٿ",
+ "open": "باز کردٿ",
+ "delete": "حذٿ",
+ "port": "پٿرت",
"create": "ساخت",
- "path": "مسیر",
- "host": "هاست",
+ "path": "مسۿر",
+ "host": "ٿاست",
"alpn": "ALPN",
- "security": "امنیت",
- "user_info": "اطلاعات کاربر",
+ "security": "امٿۿت",
+ "user_info": "اطٿاعات کاربر",
"subscription": "اشتراک",
- "subscription_link": "لینک اشتراک",
- "fingerprint": "فینگرپرینت",
+ "subscription_link": "ٿۿٿک اشتراک",
+ "fingerprint": "ٿۿٿگرپرۿٿت",
"page": {
"settings": {
"certificate": {
- "copy": "کپی سرتیفیکت",
- "error": "سرتیفیکت در دسترس نیست",
- "copied": "سرتیفیکت کپی شد"
+ "copy": "کپۿ سرتۿٿۿکت",
+ "error": "سرتۿٿۿکت در دسترس ٿۿست",
+ "copied": "سرتۿٿۿکت کپۿ شد"
}
},
"login": {
"username": {
"error": {
- "isRequired": "نام کاربری را وارد کنید"
+ "isRequired": "ٿام کاربرۿ را ٿارد کٿۿد"
}
},
"password": {
"error": {
- "is-required": "رمز عبور را وارد کنید"
+ "is-required": "رمز عبٿر را ٿارد کٿۿد"
}
}
},
"nodes": {
"settings": {
- "title": "تنظیمات نود",
- "detail": "جزئیات",
+ "title": "تٿظۿمات ٿٿد",
+ "detail": "جزئۿات",
"log-container": {
- "title": "لاگ اکسری"
+ "title": "ٿاگ اکسرۿ"
}
},
"dialogs": {
"creation": {
- "title": "افزودن نود"
+ "title": "اٿزٿدٿ ٿٿد"
},
"deletion": {
- "title": "حذف نود"
+ "title": "حذٿ ٿٿد"
},
"edition": {
- "title": "ویرایش نود"
+ "title": "ٿۿراۿش ٿٿد"
}
},
- "usage_coefficient": "ضریب مصرف",
- "add_as_new_host": "افزودن هاست های وابسته",
+ "usage_coefficient": "ضرۿب مصرٿ",
+ "connection_backend": "Backend Connection",
+ "connection_backend_desc": "Used for remote tls connections",
"certificate-alert": {
- "title": "کانفیگ سرتیفیکت نود",
- "desc": "اگر قصد برداشت سرتیفیکت نود را دارید، صفحه تنظیمات را بررسی کنید",
- "click": "اینجا کلیک کنید"
+ "title": "کاٿٿۿگ سرتۿٿۿکت ٿٿد",
+ "desc": "اگر ٿصد برداشت سرتۿٿۿکت ٿٿد را دارۿدؿ صٿحٿ تٿظۿمات را بررسۿ کٿۿد",
+ "click": "اۿٿجا کٿۿک کٿۿد"
}
},
"services": {
"settings": {
- "title": "تنظیمات سرویس",
- "detail": "جزئیات"
+ "title": "تٿظۿمات سرٿۿس",
+ "detail": "جزئۿات"
},
"dialogs": {
"creation": {
- "title": "ساخت سرویس"
+ "title": "ساخت سرٿۿس"
},
"deletion": {
- "title": "حذف سرویس"
+ "title": "حذٿ سرٿۿس"
},
"edition": {
- "title": "ویرایش سرویس"
+ "title": "ٿۿراۿش سرٿۿس"
}
}
},
"users": {
- "note-placeholder": "یادداشتی در مورد کاربر",
- "data_limit": "محدودیت حجم",
- "used_traffic": "ترافیک مصرفی",
- "data_limit_reset_strategy": "بازه ریست حجم مصرفی",
- "online_at": "آخرین استفاده",
- "expire_date": "تاریخ انقضا",
- "unlimited": "نامحدود",
+ "note-placeholder": "ۿادداشتۿ در مٿرد کاربر",
+ "data_limit": "محدٿدۿت حجم",
+ "used_traffic": "تراٿۿک مصرٿۿ",
+ "data_limit_reset_strategy": "بازٿ رۿست حجم مصرٿۿ",
+ "online_at": "آخرۿٿ استٿادٿ",
+ "expire_date": "تارۿخ اٿٿضا",
+ "unlimited": "ٿامحدٿد",
"determined_expire": "مشخص",
- "expire_method": "انقضا",
- "onhold_expire": "شروع از اولین اتصال",
- "reset_usage": "ریست حجم مصرفی",
- "revoke_subscription": "ابطال اشتراک",
- "on_hold_expire_duration": "مدت زمان",
- "on_hold_timeout": "انقضا",
+ "expire_method": "اٿٿضا",
+ "onhold_expire": "شرٿع از اٿٿۿٿ اتصاٿ",
+ "reset_usage": "رۿست حجم مصرٿۿ",
+ "revoke_subscription": "ابطاٿ اشتراک",
+ "on_hold_expire_duration": "مدت زماٿ",
+ "on_hold_timeout": "اٿٿضا",
"settings": {
"subscription_link": {
- "copied": "لینک اشتراک کپی شد",
- "copy": "کپی لینک اشتراک",
- "error": "لینک اشتراک کپی نشد"
+ "copied": "ٿۿٿک اشتراک کپۿ شد",
+ "copy": "کپۿ ٿۿٿک اشتراک",
+ "error": "ٿۿٿک اشتراک کپۿ ٿشد"
}
},
"services-alert": {
- "title": "سرویسی یافت نشد",
- "desc": "برای ساخت یوزر ابتدا سرویسی ایجاد کنید",
- "click": "اینجا را کلیک کنید"
+ "title": "سرٿۿسۿ ۿاٿت ٿشد",
+ "desc": "براۿ ساخت ۿٿزر ابتدا سرٿۿسۿ اۿجاد کٿۿد",
+ "click": "اۿٿجا را کٿۿک کٿۿد"
},
"dialogs": {
"edition": {
- "title": "ویرایش کاربر"
+ "title": "ٿۿراۿش کاربر"
},
"creation": {
- "title": "افزودن کاربر"
+ "title": "اٿزٿدٿ کاربر"
},
"deletion": {
- "title": "حذف کاربر"
+ "title": "حذٿ کاربر"
},
"mutation": {
"note": {
- "desc": "یادداشت"
+ "desc": "ۿادداشت"
}
}
}
},
"hosts": {
- "allowinsecure": "پذیرش کانکشن ناامن",
+ "allowinsecure": "پذۿرش کاٿکشٿ ٿاامٿ",
"dialogs": {
"edition": {
- "title": "ویرایش هاست"
+ "title": "ٿۿراۿش ٿاست"
},
"creation": {
- "title": "افزودن هاست"
+ "title": "اٿزٿدٿ ٿاست"
},
"deletion": {
- "title": "حذف هاست"
+ "title": "حذٿ ٿاست"
},
"mutation": {
"allowinsecure": {
- "alert": "فعال کردن این گزینه پیشنهاد نمÛشود."
+ "alert": "ٿعاٿ کردٿ اۿٿ گزۿٿٿ پۿشٿٿاد ٿمÛشٿد."
}
}
}
@@ -166,30 +167,30 @@
},
"events": {
"delete": {
- "error": "خطا در حذف {{name}}",
+ "error": "خطا در حذٿ {{name}}",
"success": {
- "title": "با موفقیت حذف شد {{name}}",
+ "title": "با مٿٿٿۿت حذٿ شد {{name}}",
"desc": ""
}
},
"create": {
- "error": "خطا در حذف {{name}}",
+ "error": "خطا در حذٿ {{name}}",
"success": {
- "title": "{{name}} با موفقیت حذف شد",
+ "title": "{{name}} با مٿٿٿۿت حذٿ شد",
"desc": ""
}
},
"update": {
- "error": "خطا در آپدیت {{name}}",
+ "error": "خطا در آپدۿت {{name}}",
"success": {
- "title": "{{name}} با موفقیت آپدیت شد",
+ "title": "{{name}} با مٿٿٿۿت آپدۿت شد",
"desc": ""
}
},
"user_revoke": {
- "error": "خطا در ابطال اشتراک {{name}}",
+ "error": "خطا در ابطاٿ اشتراک {{name}}",
"success": {
- "title": "با موفقیت ایجاد شد.{{name}}",
+ "title": "با مٿٿٿۿت اۿجاد شد.{{name}}",
"desc": ""
}
},
@@ -202,8 +203,8 @@
}
},
"delete-confirmation": {
- "title": "آیا از انجام این کار مطمئنید؟",
- "desc": "حذف اطلاعات بازگشت ناپذیر است."
+ "title": "آۿا از اٿجام اۿٿ کار مطمئٿۿدؿ",
+ "desc": "حذٿ اطٿاعات بازگشت ٿاپذۿر است."
},
"table": {
"row-per-page": "Rows per page",
@@ -213,15 +214,15 @@
"filter-placeholder": "Filter {{name}}"
},
"statuses": {
- "active": "فعال",
- "connected": "متصل شده",
- "healthy": "سالم",
- "disabled": "غیرفعال",
- "expired": "منقضی",
- "on_hold": "شروع از اولین اتصال",
- "connecting": "در حال اتصال",
- "unhealthy": "ناسالم",
- "limited": "محدود شده",
+ "active": "ٿعاٿ",
+ "connected": "متصٿ شدٿ",
+ "healthy": "ساٿم",
+ "disabled": "غۿرٿعاٿ",
+ "expired": "مٿٿضۿ",
+ "on_hold": "شرٿع از اٿٿۿٿ اتصاٿ",
+ "connecting": "در حاٿ اتصاٿ",
+ "unhealthy": "ٿاساٿم",
+ "limited": "محدٿد شدٿ",
"error": "خطا"
}
}
diff --git a/dashboard/public/locales/kur.json b/dashboard/public/locales/kur.json
@@ -1,290 +1,291 @@
{
- "name": "Naw",
- "status": "Dox",
- "address": "Edrês",
- "users": "Bekarhêneran",
- "nodes": "Girêkan",
- "services": "Rajekan",
- "hosts": "Hostkan",
- "username": "Nawî Bekarhêneran",
- "password": "Têperrewişe",
- "inbounds": "înbawendkan",
- "submit": "Nardin",
- "login": "Çûnejûrewe",
- "edit": "Desitkarîkirdn",
- "actions": "Fermankan",
- "settings": "Sazkarîyekan",
- "logs": "Tomarekan",
- "config": "Kanfîg",
- "open": "Kirnewe",
- "delete": "Sirrînewe",
- "port": "Port",
- "create": "Dirust bike",
- "path": "Path",
- "host": "Host",
- "alpn": "ALPN",
- "security": "Asayiş",
- "sni": "SNI",
- "user_info": "Zanyarî bekarhêneran",
- "subscription": "Beşdarî",
- "subscription_link": "Link Beşdarî",
- "fingerprint": "Fingerprint",
- "enable": "Çalakî ke",
- "disable": "Naçalakî ke",
- "close": "Dayixe",
- "save": "Seyv Bike",
- "days": "Roj",
- "home": "Malê",
- "light": "Runak",
- "dark": "Tarîk",
- "system": "Sîstim",
- "advanced-options": "Bijardikanî pêşkewtû",
- "page": {
- "settings": {
- "certificate": {
- "copy": "Kopî Birrwaname",
- "error": "Birrwaname berdest nîye",
- "copied": "Birrwaname Kopî kira"
- }
- },
- "login": {
- "username": {
- "error": {
- "isRequired": "Nawî bekarhêner pêwîste"
- }
- },
- "password": {
- "error": {
- "is-required": "Têperwişe pêwîste"
- }
- }
- },
- "nodes": {
- "settings": {
- "title": "Sazkarîyekanî Node",
- "detail": "Wurdekaryekan",
- "log-container": {
- "title": "Xray logs"
- }
- },
- "dialogs": {
- "creation": {
- "title": "Nodes Creation"
- },
- "deletion": {
- "title": "Nodes Deletion"
- },
- "edition": {
- "title": "Nodes Edition"
- }
- },
- "usage_coefficient": "Usage Coefficient",
- "add_as_new_host": "Add as new hosts",
- "certificate-alert": {
- "title": "Nodes Certificate Configuration",
- "desc": "If you aim to configure your nodes certificate, checkout settings page.",
- "click": "Click Here"
- }
- },
- "services": {
- "settings": {
- "title": "Servicecs Settings",
- "detail": "Details"
- },
- "dialogs": {
- "creation": {
- "title": "Services Creation"
- },
- "deletion": {
- "title": "Services Deletion"
- },
- "edition": {
- "title": "Services Edition"
- }
- }
- },
- "users": {
- "note-placeholder": "Têbînî leser bekarhêner",
- "data_limit": "Sinûrî data",
- "used_traffic": "Traffic bekarhêndiraw",
- "data_limit_reset_strategy": "Şêwazî sirrinewey sinûrî data",
- "online_at": "Online Le",
- "expire_date": "Katî beserçûn",
- "unlimited": "Bêsinûr",
- "limited": "Sinûrdar",
- "data_limit_method": "Çonêtî Sinûrî Data",
- "determined_expire": "Diyarî kiraw",
- "expire_method": "Sêwazî beserçûn",
- "onhold_expire": "Ragîraw",
- "reset_usage": "Rêsitkirdinî bekarhênraw",
- "revoke_subscription": "Hellweşandinewey Beşdarîkirdn",
- "on_hold_expire_duration": "Ragîraw",
- "on_hold_timeout": "Kotayî hatnî ragirtn",
- "settings": {
- "subscription_link": {
- "copied": "Subscription Link Copied",
- "copy": "Copy Subscription Link",
- "error": "Unable to copy Subscription Link"
- }
- },
- "services-alert": {
- "title": "No Service were found",
- "desc": "Make sure you create services to enable your users with inbounds to connect with.",
- "click": "Click Here"
- },
- "dialogs": {
- "edition": {
- "title": "Users Edition"
- },
- "creation": {
- "title": "Users Creation"
- },
- "deletion": {
- "title": "Users Deletion"
- },
- "mutation": {
- "note": {
- "desc": "Leave a note about your user"
- }
- }
- }
- },
- "hosts": {
- "fragment": {
- "interval": "Interval",
- "length": "Length",
- "packets": "Packets",
- "length-error": "Length can not exceed 32 char and must be number-number",
- "interval-error": "Interval can not exceed 32 char and must be number-number",
- "packets-error": "Packets can not exceed 32 char and must be number-number or be tlshello"
- },
- "popover-guide": {
- "title": "Using this setting, you are able to assign specific address for each inbound.",
- "desc": "Use these variables to make it dynamic",
- "username": "The username of the user",
- "dataUsage": "The current usage of the user",
- "remainingData": "Remaining data of the user",
- "dataLimit": "The usage limit of the user",
- "remainingDays": "Remaining days of the user",
- "expireDate": "Expiry date of the user",
- "jalaliExpireDate": "Expiry date of the user in solar calendar",
- "remainingTime": "Remaining time of the user",
- "statusEmoji": "User status as an emoji (✅,⌛,🪫,❌,🔌)",
- "proxyProtocol": "Proxy protocol (e.g. VMess)",
- "proxyMethod": "Proxy transport method (e.g. ws)",
- "currentServer": "Current server ip address",
- "security": "Security Layer",
- "host": "Request Host",
- "port": "Port",
- "path": "Path",
- "sni": "SNI",
- "advancedOptions": "Advanced options",
- "addHost": "Add host",
- "savedSuccess": "Hosts saved successfully",
- "loading": "loading...",
- "apply": "Apply",
- "port.info": "By default, a host uses the default port of the inbound. You can set a custom port in case this host is a server that forwards traffic from a port that differs from your server's port. For example, the server may forward traffic from port 8443 to the default port of your inbound server.",
- "path.info": "Set a path for host users, useful behind a reverse proxy.",
- "sni.info": "By default, a host uses the default SNI of the inbound. You can set a custom SNI in case this host is a server that has a different SNI. For example, the server may receive traffic with a different SSL certificate, perform SSL termination, and forward it to your inbound server.",
- "host.info": "By default, if a request host is set in the Xray config, this host is used. However, you can set a custom request host here if needed.",
- "security.info": "If the middleware server of this host uses a different security layer than the inbound's default, you can set a custom security layer here.",
- "alpn": "ALPN",
- "fingerprint": "Fingerprint",
- "host.multiHost": "To set multiple addresses, separate them with <badge>,</badge> Each time an address is chosen randomly.",
- "host.wildcard": "Use <badge>*</badge> to generate a random string (works for wildcard domain names)"
- },
- "mux": {
- "title": "Connection multiplexing",
- "desc": "Currently supported by v2rayng while using custom config"
- },
- "no-inbound-selection-alert": {
- "title": "Hîç înbawendêk hell nebijardirawe",
- "desc": "Debêt înbawendêk hell bibjêrî ta we ku hostêk dirust bikey."
- },
- "allowinsecure": "Allow insecure",
- "dialogs": {
- "edition": {
- "title": "Gorînî Host"
- },
- "creation": {
- "title": "Dirust kirdin Host"
- },
- "deletion": {
- "title": "Sirînewey Host"
- },
- "mutation": {
- "allowinsecure": {
- "alert": "Due to security concerns, it is not recommended to allow Insecure"
- }
- }
- }
- }
- },
- "events": {
- "delete": {
- "error": "Failed to delete {{name}}",
- "success": {
- "title": "{{name}} is successfuly deleted",
- "desc": ""
- }
- },
- "create": {
- "error": "Failed to create {{name}}",
- "success": {
- "title": "{{name}} is successfuly created",
- "desc": ""
- }
- },
- "update": {
- "error": "Failed to update {{name}}",
- "success": {
- "title": "{{name}} is successfuly updated",
- "desc": ""
- }
- },
- "user_revoke": {
- "error": "Failed to revoke {{name}} subscription",
- "success": {
- "title": "{{name}} is successfuly created",
- "desc": ""
- }
- },
- "user_reset": {
- "error": "Failed to reset {{name}} data usage",
- "success": {
- "title": "{{name}} data usage is successfuly reseted",
- "desc": ""
- }
- },
- "user_status": {
- "error": "Şikistî hêna le gorrînî doxî çalak kirdinî {{name}}",
- "success": {
- "title": "Doxî çalakî {{name}} nwêkirawe",
- "desc": "Doxî çalak kirdin be serkewtûyî nwêkirawe"
- }
- }
- },
- "delete-confirmation": {
- "title": "Are you sure of this?",
- "desc": "This action cannot be undone. This will permanently delete your account and remove your data from our servers."
- },
- "table": {
- "row-per-page": "Rows per page",
- "no-result": "No results",
- "selected": "Selected",
- "toggle-column": "Toggle columns",
- "filter-placeholder": "Filter {{name}}"
- },
- "statuses": {
- "active": "Active",
- "connected": "Connected",
- "healthy": "Healthy",
- "disabled": "Disabled",
- "expired": "Expired",
- "on_hold": "On Hold",
- "connecting": "Connecting",
- "unhealthy": "Unhealthy",
- "limited": "Limited",
- "error": "Error"
- }
+ "name": "Naw",
+ "status": "Dox",
+ "address": "Edrês",
+ "users": "Bekarhêneran",
+ "nodes": "Girêkan",
+ "services": "Rajekan",
+ "hosts": "Hostkan",
+ "username": "Nawî Bekarhêneran",
+ "password": "Têperrewişe",
+ "inbounds": "înbawendkan",
+ "submit": "Nardin",
+ "login": "Çûnejûrewe",
+ "edit": "Desitkarîkirdn",
+ "actions": "Fermankan",
+ "settings": "Sazkarîyekan",
+ "logs": "Tomarekan",
+ "config": "Kanfîg",
+ "open": "Kirnewe",
+ "delete": "Sirrînewe",
+ "port": "Port",
+ "create": "Dirust bike",
+ "path": "Path",
+ "host": "Host",
+ "alpn": "ALPN",
+ "security": "Asayiş",
+ "sni": "SNI",
+ "user_info": "Zanyarî bekarhêneran",
+ "subscription": "Beşdarî",
+ "subscription_link": "Link Beşdarî",
+ "fingerprint": "Fingerprint",
+ "enable": "Çalakî ke",
+ "disable": "Naçalakî ke",
+ "close": "Dayixe",
+ "save": "Seyv Bike",
+ "days": "Roj",
+ "home": "Malê",
+ "light": "Runak",
+ "dark": "Tarîk",
+ "system": "Sîstim",
+ "advanced-options": "Bijardikanî pêşkewtû",
+ "page": {
+ "settings": {
+ "certificate": {
+ "copy": "Kopî Birrwaname",
+ "error": "Birrwaname berdest nîye",
+ "copied": "Birrwaname Kopî kira"
+ }
+ },
+ "login": {
+ "username": {
+ "error": {
+ "isRequired": "Nawî bekarhêner pêwîste"
+ }
+ },
+ "password": {
+ "error": {
+ "is-required": "Têperwişe pêwîste"
+ }
+ }
+ },
+ "nodes": {
+ "settings": {
+ "title": "Sazkarîyekanî Node",
+ "detail": "Wurdekaryekan",
+ "log-container": {
+ "title": "Xray logs"
+ }
+ },
+ "dialogs": {
+ "creation": {
+ "title": "Nodes Creation"
+ },
+ "deletion": {
+ "title": "Nodes Deletion"
+ },
+ "edition": {
+ "title": "Nodes Edition"
+ }
+ },
+ "usage_coefficient": "Usage Coefficient",
+ "connection_backend": "Backend Connection",
+ "connection_backend_desc": "Used for remote tls connections",
+ "certificate-alert": {
+ "title": "Nodes Certificate Configuration",
+ "desc": "If you aim to configure your nodes certificate, checkout settings page.",
+ "click": "Click Here"
+ }
+ },
+ "services": {
+ "settings": {
+ "title": "Servicecs Settings",
+ "detail": "Details"
+ },
+ "dialogs": {
+ "creation": {
+ "title": "Services Creation"
+ },
+ "deletion": {
+ "title": "Services Deletion"
+ },
+ "edition": {
+ "title": "Services Edition"
+ }
+ }
+ },
+ "users": {
+ "note-placeholder": "Têbînî leser bekarhêner",
+ "data_limit": "Sinûrî data",
+ "used_traffic": "Traffic bekarhêndiraw",
+ "data_limit_reset_strategy": "Şêwazî sirrinewey sinûrî data",
+ "online_at": "Online Le",
+ "expire_date": "Katî beserçûn",
+ "unlimited": "Bêsinûr",
+ "limited": "Sinûrdar",
+ "data_limit_method": "Çonêtî Sinûrî Data",
+ "determined_expire": "Diyarî kiraw",
+ "expire_method": "Sêwazî beserçûn",
+ "onhold_expire": "Ragîraw",
+ "reset_usage": "Rêsitkirdinî bekarhênraw",
+ "revoke_subscription": "Hellweşandinewey Beşdarîkirdn",
+ "on_hold_expire_duration": "Ragîraw",
+ "on_hold_timeout": "Kotayî hatnî ragirtn",
+ "settings": {
+ "subscription_link": {
+ "copied": "Subscription Link Copied",
+ "copy": "Copy Subscription Link",
+ "error": "Unable to copy Subscription Link"
+ }
+ },
+ "services-alert": {
+ "title": "No Service were found",
+ "desc": "Make sure you create services to enable your users with inbounds to connect with.",
+ "click": "Click Here"
+ },
+ "dialogs": {
+ "edition": {
+ "title": "Users Edition"
+ },
+ "creation": {
+ "title": "Users Creation"
+ },
+ "deletion": {
+ "title": "Users Deletion"
+ },
+ "mutation": {
+ "note": {
+ "desc": "Leave a note about your user"
+ }
+ }
+ }
+ },
+ "hosts": {
+ "fragment": {
+ "interval": "Interval",
+ "length": "Length",
+ "packets": "Packets",
+ "length-error": "Length can not exceed 32 char and must be number-number",
+ "interval-error": "Interval can not exceed 32 char and must be number-number",
+ "packets-error": "Packets can not exceed 32 char and must be number-number or be tlshello"
+ },
+ "popover-guide": {
+ "title": "Using this setting, you are able to assign specific address for each inbound.",
+ "desc": "Use these variables to make it dynamic",
+ "username": "The username of the user",
+ "dataUsage": "The current usage of the user",
+ "remainingData": "Remaining data of the user",
+ "dataLimit": "The usage limit of the user",
+ "remainingDays": "Remaining days of the user",
+ "expireDate": "Expiry date of the user",
+ "jalaliExpireDate": "Expiry date of the user in solar calendar",
+ "remainingTime": "Remaining time of the user",
+ "statusEmoji": "User status as an emoji (✅,⌛,🪫,❌,🔌)",
+ "proxyProtocol": "Proxy protocol (e.g. VMess)",
+ "proxyMethod": "Proxy transport method (e.g. ws)",
+ "currentServer": "Current server ip address",
+ "security": "Security Layer",
+ "host": "Request Host",
+ "port": "Port",
+ "path": "Path",
+ "sni": "SNI",
+ "advancedOptions": "Advanced options",
+ "addHost": "Add host",
+ "savedSuccess": "Hosts saved successfully",
+ "loading": "loading...",
+ "apply": "Apply",
+ "port.info": "By default, a host uses the default port of the inbound. You can set a custom port in case this host is a server that forwards traffic from a port that differs from your server's port. For example, the server may forward traffic from port 8443 to the default port of your inbound server.",
+ "path.info": "Set a path for host users, useful behind a reverse proxy.",
+ "sni.info": "By default, a host uses the default SNI of the inbound. You can set a custom SNI in case this host is a server that has a different SNI. For example, the server may receive traffic with a different SSL certificate, perform SSL termination, and forward it to your inbound server.",
+ "host.info": "By default, if a request host is set in the Xray config, this host is used. However, you can set a custom request host here if needed.",
+ "security.info": "If the middleware server of this host uses a different security layer than the inbound's default, you can set a custom security layer here.",
+ "alpn": "ALPN",
+ "fingerprint": "Fingerprint",
+ "host.multiHost": "To set multiple addresses, separate them with <badge>,</badge> Each time an address is chosen randomly.",
+ "host.wildcard": "Use <badge>*</badge> to generate a random string (works for wildcard domain names)"
+ },
+ "mux": {
+ "title": "Connection multiplexing",
+ "desc": "Currently supported by v2rayng while using custom config"
+ },
+ "no-inbound-selection-alert": {
+ "title": "Hîç înbawendêk hell nebijardirawe",
+ "desc": "Debêt înbawendêk hell bibjêrî ta we ku hostêk dirust bikey."
+ },
+ "allowinsecure": "Allow insecure",
+ "dialogs": {
+ "edition": {
+ "title": "Gorînî Host"
+ },
+ "creation": {
+ "title": "Dirust kirdin Host"
+ },
+ "deletion": {
+ "title": "Sirînewey Host"
+ },
+ "mutation": {
+ "allowinsecure": {
+ "alert": "Due to security concerns, it is not recommended to allow Insecure"
+ }
+ }
+ }
+ }
+ },
+ "events": {
+ "delete": {
+ "error": "Failed to delete {{name}}",
+ "success": {
+ "title": "{{name}} is successfuly deleted",
+ "desc": ""
+ }
+ },
+ "create": {
+ "error": "Failed to create {{name}}",
+ "success": {
+ "title": "{{name}} is successfuly created",
+ "desc": ""
+ }
+ },
+ "update": {
+ "error": "Failed to update {{name}}",
+ "success": {
+ "title": "{{name}} is successfuly updated",
+ "desc": ""
+ }
+ },
+ "user_revoke": {
+ "error": "Failed to revoke {{name}} subscription",
+ "success": {
+ "title": "{{name}} is successfuly created",
+ "desc": ""
+ }
+ },
+ "user_reset": {
+ "error": "Failed to reset {{name}} data usage",
+ "success": {
+ "title": "{{name}} data usage is successfuly reseted",
+ "desc": ""
+ }
+ },
+ "user_status": {
+ "error": "Şikistî hêna le gorrînî doxî çalak kirdinî {{name}}",
+ "success": {
+ "title": "Doxî çalakî {{name}} nwêkirawe",
+ "desc": "Doxî çalak kirdin be serkewtûyî nwêkirawe"
+ }
+ }
+ },
+ "delete-confirmation": {
+ "title": "Are you sure of this?",
+ "desc": "This action cannot be undone. This will permanently delete your account and remove your data from our servers."
+ },
+ "table": {
+ "row-per-page": "Rows per page",
+ "no-result": "No results",
+ "selected": "Selected",
+ "toggle-column": "Toggle columns",
+ "filter-placeholder": "Filter {{name}}"
+ },
+ "statuses": {
+ "active": "Active",
+ "connected": "Connected",
+ "healthy": "Healthy",
+ "disabled": "Disabled",
+ "expired": "Expired",
+ "on_hold": "On Hold",
+ "connecting": "Connecting",
+ "unhealthy": "Unhealthy",
+ "limited": "Limited",
+ "error": "Error"
+ }
}
diff --git a/dashboard/src/features/nodes/dialogs/mutation.dialog.tsx b/dashboard/src/features/nodes/dialogs/mutation.dialog.tsx
@@ -1,150 +1,170 @@
-import { FC } from "react";
+import type { FC } from "react";
import {
- DialogTitle,
- DialogContent,
- Dialog,
- DialogHeader,
- Form,
- FormItem,
- FormControl,
- FormMessage,
- FormLabel,
- Input,
- FormField,
- Checkbox,
- Button
-} from '@marzneshin/components';
-import { useTranslation } from 'react-i18next';
-import { NodeType, NodeSchema, useNodesCreationMutation, useNodesUpdateMutation } from "..";
+ DialogTitle,
+ DialogContent,
+ Dialog,
+ DialogHeader,
+ Form,
+ FormItem,
+ FormControl,
+ FormMessage,
+ FormLabel,
+ Input,
+ FormField,
+ Button,
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+ FormDescription,
+ HStack,
+} from "@marzneshin/components";
+import { useTranslation } from "react-i18next";
+import {
+ NodeSchema,
+ useNodesCreationMutation,
+ useNodesUpdateMutation,
+} from "..";
+import type { NodeType } from "..";
import { useMutationDialog } from "@marzneshin/hooks";
interface MutationDialogProps {
- entity: NodeType | null;
- open: boolean;
- onOpenChange: (state: boolean) => void;
+ entity: NodeType | null;
+ open: boolean;
+ onOpenChange: (state: boolean) => void;
}
const getDefaultValue = (): NodeType => ({
- name: '',
- address: '',
- status: 'none',
- port: 62050,
- usage_coefficient: 1,
- add_as_new_host: true,
+ name: "",
+ address: "",
+ status: "none",
+ port: 62050,
+ usage_coefficient: 1,
+ connection_backend: "grpclib",
});
export const MutationDialog: FC<MutationDialogProps> = ({
- entity,
- open,
- onOpenChange,
+ entity,
+ open,
+ onOpenChange,
}) => {
- const updateMutation = useNodesUpdateMutation();
- const createMutation = useNodesCreationMutation();
- const { t } = useTranslation();
- const { form, handleSubmit } = useMutationDialog({
- onOpenChange,
- entity,
- schema: NodeSchema,
- createMutation,
- updateMutation,
- getDefaultValue
- })
- return (
- <Dialog open={open} onOpenChange={onOpenChange} defaultOpen={true}>
- <DialogContent>
- <DialogHeader>
- <DialogTitle className="text-primary">
- {entity
- ? t('page.nodes.dialogs.edition.title')
- : t('page.nodes.dialogs.creation.title')}
- </DialogTitle>
- </DialogHeader>
- <Form {...form}>
- <form onSubmit={handleSubmit}>
- <FormField
- control={form.control}
- name="name"
- render={({ field }) => (
- <FormItem>
- <FormLabel>{t('name')}</FormLabel>
- <FormControl>
- <Input {...field} />
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
- <div className="flex flex-row gap-2 items-center">
- <FormField
- control={form.control}
- name="address"
- render={({ field }) => (
- <FormItem className="w-2/4">
- <FormLabel>{t('address')}</FormLabel>
- <FormControl>
- <Input {...field} />
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
- <FormField
- control={form.control}
- name="port"
- render={({ field }) => (
- <FormItem className="w-1/4">
- <FormLabel>{t('port')}</FormLabel>
- <FormControl>
- <Input type="number" {...field} />
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
- <FormField
- control={form.control}
- name="usage_coefficient"
- render={({ field }) => (
- <FormItem className="w-1/4">
- <FormLabel>{t('page.nodes.usage_coefficient')}</FormLabel>
- <FormControl>
- <Input {...field} />
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
- </div>
- <FormField
- control={form.control}
- name="add_as_new_host"
- render={({ field }) => (
- <FormItem className="mt-4">
- <FormControl>
- <div className="flex flex-row gap-1 items-center">
- <Checkbox
- checked={field.value}
- onCheckedChange={field.onChange}
- />
- <FormLabel>
- {t('page.nodes.add_as_new_host')}
- </FormLabel>
- </div>
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
- <Button
- className="mt-3 w-full font-semibold"
- type="submit"
- disabled={form.formState.isSubmitting}
- >
- {t('submit')}
- </Button>
- </form>
- </Form>
- </DialogContent>
- </Dialog>
- );
+ const updateMutation = useNodesUpdateMutation();
+ const createMutation = useNodesCreationMutation();
+ const { t } = useTranslation();
+ const { form, handleSubmit } = useMutationDialog({
+ onOpenChange,
+ entity,
+ schema: NodeSchema,
+ createMutation,
+ updateMutation,
+ getDefaultValue,
+ });
+ return (
+ <Dialog open={open} onOpenChange={onOpenChange} defaultOpen={true}>
+ <DialogContent>
+ <DialogHeader>
+ <DialogTitle className="text-primary">
+ {entity
+ ? t("page.nodes.dialogs.edition.title")
+ : t("page.nodes.dialogs.creation.title")}
+ </DialogTitle>
+ </DialogHeader>
+ <Form {...form}>
+ <form onSubmit={handleSubmit}>
+ <FormField
+ control={form.control}
+ name="name"
+ render={({ field }) => (
+ <FormItem className="w-full">
+ <FormLabel>{t("name")}</FormLabel>
+ <FormControl>
+ <Input {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ <div className="flex flex-row gap-2 items-center">
+ <FormField
+ control={form.control}
+ name="address"
+ render={({ field }) => (
+ <FormItem className="w-2/3">
+ <FormLabel>{t("address")}</FormLabel>
+ <FormControl>
+ <Input {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ <FormField
+ control={form.control}
+ name="port"
+ render={({ field }) => (
+ <FormItem className="w-1/3">
+ <FormLabel>{t("port")}</FormLabel>
+ <FormControl>
+ <Input type="number" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ </div>
+ <HStack>
+ <FormField
+ control={form.control}
+ name="connection_backend"
+ render={({ field }) => (
+ <FormItem className="w-full">
+ <FormLabel>{t("page.nodes.connection_backend")}</FormLabel>
+ <Select
+ onValueChange={field.onChange}
+ defaultValue={field.value}
+ >
+ <FormControl>
+ <SelectTrigger>
+ <SelectValue placeholder="Nodes Connection" />
+ </SelectTrigger>
+ </FormControl>
+ <SelectContent>
+ <SelectItem value="grpclib">grpclib</SelectItem>
+ <SelectItem value="grpcio">grpcio</SelectItem>
+ </SelectContent>
+ </Select>
+ <FormDescription>
+ {t("page.nodes.connection_backend_desc")}
+ </FormDescription>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ <FormField
+ control={form.control}
+ name="usage_coefficient"
+ render={({ field }) => (
+ <FormItem className="w-1/2">
+ <FormLabel>{t("page.nodes.usage_coefficient")}</FormLabel>
+ <FormControl>
+ <Input {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ </HStack>
+ <Button
+ className="mt-3 w-full font-semibold"
+ type="submit"
+ disabled={form.formState.isSubmitting}
+ >
+ {t("submit")}
+ </Button>
+ </form>
+ </Form>
+ </DialogContent>
+ </Dialog>
+ );
};
diff --git a/dashboard/src/features/nodes/types/index.ts b/dashboard/src/features/nodes/types/index.ts
@@ -1,40 +1,45 @@
-import { StatusType } from "@marzneshin/types/status";
+import type { StatusType } from "@marzneshin/types/status";
import { PowerOff, Zap, ZapOff } from "lucide-react";
import { z } from "zod";
-
export const NodesStatus = {
- healthy: {
- label: 'healthy',
- icon: Zap,
- },
- unhealthy: {
- label: 'unhealthy',
- icon: ZapOff,
- },
- disabled: {
- label: 'disabled',
- icon: PowerOff,
- },
- none: {
- label: 'none',
- icon: null,
- }
+ healthy: {
+ label: "healthy",
+ icon: Zap,
+ },
+ unhealthy: {
+ label: "unhealthy",
+ icon: ZapOff,
+ },
+ disabled: {
+ label: "disabled",
+ icon: PowerOff,
+ },
+ none: {
+ label: "none",
+ icon: null,
+ },
} as Record<string, StatusType>;
export const NodeSchema = z.object({
- name: z.string().min(1),
- address: z.string().min(1),
- port: z
- .number()
- .min(1)
- .or(z.string().transform((v) => parseFloat(v))),
- id: z.number().nullable().optional(),
- status: z
- .enum([NodesStatus.healthy.label, NodesStatus.unhealthy.label, 'none', NodesStatus.disabled.label]),
- usage_coefficient: z.number().default(1.0)
- .or(z.string().transform((v) => parseFloat(v))),
- add_as_new_host: z.boolean().optional(),
+ name: z.string().min(1),
+ address: z.string().min(1),
+ port: z
+ .number()
+ .min(1)
+ .or(z.string().transform((v) => Number.parseFloat(v))),
+ id: z.number().nullable().optional(),
+ status: z.enum([
+ NodesStatus.healthy.label,
+ NodesStatus.unhealthy.label,
+ "none",
+ NodesStatus.disabled.label,
+ ]),
+ usage_coefficient: z
+ .number()
+ .default(1.0)
+ .or(z.string().transform((v) => Number.parseFloat(v))),
+ connection_backend: z.enum(["grpclib", "grpcio"]).default("grpclib"),
});
export type NodeType = z.infer<typeof NodeSchema>;