diff --git a/app/db/crud.py b/app/db/crud.py
@@ -185,6 +185,7 @@ def add_host(db: Session, inbound: Inbound, host: InboundHostModify):
fragment=host.fragment.model_dump() if host.fragment else None,
mux=host.mux,
allowinsecure=host.allowinsecure,
+ weight=host.weight,
)
inbound.hosts.append(host)
db.commit()
@@ -206,6 +207,7 @@ def update_host(db: Session, db_host: InboundHost, host: InboundHostModify):
db_host.mux = host.mux
db_host.is_disabled = host.is_disabled
db_host.allowinsecure = host.allowinsecure
+ db_host.weight = host.weight
db.commit()
db.refresh(db_host)
return db_host
diff --git a/app/db/migrations/versions/fd423c19f682_add_weight_to_host.py b/app/db/migrations/versions/fd423c19f682_add_weight_to_host.py
@@ -0,0 +1,31 @@
+"""add weight to host
+
+Revision ID: fd423c19f682
+Revises: 35774c98f945
+Create Date: 2024-08-07 19:04:35.197465
+
+"""
+
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = "fd423c19f682"
+down_revision = "35774c98f945"
+branch_labels = None
+depends_on = None
+
+
+def upgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.add_column(
+ "hosts",
+ sa.Column("weight", sa.Integer(), nullable=False, server_default="1"),
+ )
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.drop_column("hosts", "weight")
+ # ### end Alembic commands ###
diff --git a/app/db/models.py b/app/db/models.py
@@ -348,6 +348,7 @@ class InboundHost(Base):
inbound = relationship("Inbound", back_populates="hosts")
allowinsecure = Column(Boolean, default=False)
is_disabled = Column(Boolean, default=False)
+ weight = Column(Integer, default=1, nullable=False, server_default="1")
class System(Base):
diff --git a/app/models/proxy.py b/app/models/proxy.py
@@ -77,6 +77,7 @@ class InboundHost(BaseModel):
is_disabled: bool | None = None
mux: bool = Field(False)
fragment: FragmentSettings | None = Field(None)
+ weight: int = 1
model_config = ConfigDict(from_attributes=True)
@field_validator("remark", "address", "path")
diff --git a/app/utils/share.py b/app/utils/share.py
@@ -5,7 +5,7 @@ import secrets
from collections import defaultdict
from datetime import datetime as dt, timedelta
from importlib import resources
-from typing import Literal, Union, List
+from typing import Literal, Union, List, Type
from uuid import UUID
from jdatetime import date as jd
@@ -16,6 +16,7 @@ from v2share import (
ClashMetaConfig,
XrayConfig,
)
+from v2share.base import BaseConfig
from v2share.links import LinksConfig
from app.config.env import (
@@ -41,7 +42,7 @@ ACTIVITY_EMOJIS = {
False: "❌",
}
-subscription_handlers = {
+subscription_handlers: dict[str, Type[BaseConfig]] = {
"links": LinksConfig,
"xray": XrayConfig,
"clash-meta": ClashMetaConfig,
@@ -117,11 +118,8 @@ def generate_subscription(
format_variables,
)
- if shuffle:
- random.shuffle(configs)
-
subscription_handler.add_proxies(configs)
- config = subscription_handler.render()
+ config = subscription_handler.render(sort=True, shuffle=shuffle)
return (
config if not as_base64 else base64.b64encode(config.encode()).decode()
@@ -285,6 +283,7 @@ def generate_user_configs(
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.3"
},
shadowsocks_method="chacha20-ietf-poly1305",
+ weight=host.weight,
)
if host.fragment:
data.fragment = True
diff --git a/dashboard/public/locales/en.json b/dashboard/public/locales/en.json
@@ -74,6 +74,7 @@
"owner": "Owner",
"running": "Running",
"down": "Down",
+ "weight": "Weight",
"support-us": {
"title": "Support Us",
"desc": "Marzneshin is free and open-source software (FOSS), join us and show your support by donating.",
diff --git a/dashboard/src/features/hosts/dialogs/mutation/fields/index.ts b/dashboard/src/features/hosts/dialogs/mutation/fields/index.ts
@@ -10,3 +10,4 @@ export * from './alpn'
export * from './fingerprint'
export * from './mux'
export * from './fragment'
+export * from './weight'
diff --git a/dashboard/src/features/hosts/dialogs/mutation/fields/weight.tsx b/dashboard/src/features/hosts/dialogs/mutation/fields/weight.tsx
@@ -0,0 +1,30 @@
+import {
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+ Input
+} from "@marzneshin/components";
+import { useFormContext } from "react-hook-form";
+import { useTranslation } from "react-i18next";
+
+export const WeightField = () => {
+ const { t } = useTranslation()
+ const form = useFormContext()
+ return (
+ <FormField
+ control={form.control}
+ name="weight"
+ render={({ field }) => (
+ <FormItem className="w-1/3">
+ <FormLabel>{t('weight')}</FormLabel>
+ <FormControl>
+ <Input type="number" {...field} />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ )
+}
diff --git a/dashboard/src/features/hosts/dialogs/mutation/index.tsx b/dashboard/src/features/hosts/dialogs/mutation/index.tsx
@@ -31,6 +31,7 @@ import {
MuxField,
FragmentField,
PathField,
+ WeightField,
SecurityFields,
} from "./fields";
import { useDialog, MutationDialogProps } from "@marzneshin/hooks";
@@ -108,6 +109,7 @@ export const HostsMutationDialog: FC<HostMutationDialogProps> = ({
<div className="flex flex-row gap-2 items-start">
<HostField />
<PathField />
+ <WeightField />
</div>
<FragmentField />
<MuxField />
diff --git a/dashboard/src/features/hosts/schema/hosts-schema.ts b/dashboard/src/features/hosts/schema/hosts-schema.ts
@@ -22,6 +22,10 @@ export const alpnOptions = [
export const HostSchema = z.object({
remark: z.string().min(1, "Remark is required"),
address: z.string().min(1, "Address is required"),
+ weight: z.coerce
+ .number().int()
+ .nullable()
+ .optional(),
port: z.coerce
.number()
.gte(1, "Port must be more than 1")
diff --git a/requirements.txt b/requirements.txt
@@ -23,5 +23,5 @@ aiohttp==3.9.4
xxhash==3.4.1
grpcio==1.65.2
psycopg==3.1.18
-v2share==0.1.0b12
+v2share==0.1.0b16
PyJWT~=2.8.0
\ No newline at end of file