diff --git a/app/db/crud.py b/app/db/crud.py index 0396d80..8a1ef2b 100644 --- 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 new file mode 100644 index 0000000..73bc29d --- /dev/null +++ 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 index 3b65530..99742bf 100644 --- 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 index e86bc9f..3385dbf 100644 --- 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 index d2c4d86..1bf9ed3 100644 --- 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 index 97aea22..40cf1f5 100644 --- 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 index 87d0e1b..19d857b 100644 --- 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 new file mode 100644 index 0000000..466bfa1 --- /dev/null +++ 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 ( + ( + + {t('weight')} + + + + + + )} + /> + ) +} diff --git a/dashboard/src/features/hosts/dialogs/mutation/index.tsx b/dashboard/src/features/hosts/dialogs/mutation/index.tsx index 5c19826..a49a26c 100644 --- 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 = ({
+
diff --git a/dashboard/src/features/hosts/schema/hosts-schema.ts b/dashboard/src/features/hosts/schema/hosts-schema.ts index 7cdaa4d..3de8055 100644 --- 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 index 4a59446..9abb9d6 100644 --- 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