diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..e169e43 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,4 @@ +{ + "tabWidth": 4 +} + diff --git a/app/db/crud.py b/app/db/crud.py index 444141a..1f7272e 100644 --- a/app/db/crud.py +++ b/app/db/crud.py @@ -6,8 +6,8 @@ from enum import Enum from types import NoneType from typing import List, Optional, Tuple, Union -from sqlalchemy import and_, delete, update, select, func, cast, Date -from sqlalchemy.orm import Session +from sqlalchemy import and_, update, select, func, cast, Date, or_ +from sqlalchemy.orm import Session, joinedload from app.db.models import ( JWT, @@ -21,6 +21,7 @@ from app.db.models import ( System, User, Backend, + HostChain, ) from app.models.admin import AdminCreate, AdminPartialModify from app.models.node import ( @@ -149,14 +150,57 @@ def get_user_hosts(db: Session, user_id: int): ) -def get_inbound_hosts(db: Session, inbound_id: int) -> List[InboundHost]: +def get_inbounds_hosts( + db: Session, inbound_ids: list[int] +) -> list[InboundHost]: return ( db.query(InboundHost) - .filter(InboundHost.inbound_id == inbound_id) + .options( + joinedload(InboundHost.chain).joinedload(HostChain.chained_host) + ) + .filter(InboundHost.inbound_id.in_(inbound_ids)) + .filter(InboundHost.is_disabled == False) .all() ) +def get_hosts_for_user(session, user_id): + # Fetch the user object (assumes relationships are properly set up in the model) + user = session.query(User).filter(User.id == user_id).one() + + # Query for hosts linked through user's services and inbounds + result_query = session.query(InboundHost).filter( + and_( + # Exclude disabled hosts + InboundHost.is_disabled.is_(False), + or_( + # Case 1: Host has an inbound linked to a service of the user + InboundHost.inbound.has( + Inbound.services.any( + Service.id.in_([s.id for s in user.services]) + ) + ), + # Case 2: Host has no inbound + and_( + InboundHost.inbound_id.is_( + None + ), # Host does not have an inbound + or_( + # Host is directly related to a service of the user + InboundHost.services.any( + Service.id.in_([s.id for s in user.services]) + ), + # Host is available to all + InboundHost.universal.is_(True), + ), + ), + ), + ) + ) + + return result_query.all() + + def get_all_inbounds(db: Session): return db.query(Inbound).all() @@ -169,29 +213,63 @@ def get_host(db: Session, host_id) -> InboundHost: return db.query(InboundHost).filter(InboundHost.id == host_id).first() -def add_host(db: Session, inbound: Inbound, host: InboundHostModify): +def add_host(db: Session, inbound: Inbound | None, host: InboundHostModify): host = InboundHost( remark=host.remark, address=host.address, + host_network=host.network, + host_protocol=host.protocol, + uuid=host.uuid, + password=host.password, port=host.port, path=host.path, sni=host.sni, host=host.host, - inbound=inbound, security=host.security, alpn=host.alpn.value, fingerprint=host.fingerprint, fragment=host.fragment.model_dump() if host.fragment else None, - udp_noises=host.udp_noises, + udp_noises=( + [noise.model_dump() for noise in host.noise] + if host.noise + else None + ), + header_type=host.header_type, + reality_public_key=host.reality_public_key, + reality_short_ids=host.reality_short_ids, + flow=host.flow, + shadowtls_version=host.shadowtls_version, + shadowsocks_method=host.shadowsocks_method, + splithttp_settings=( + host.splithttp_settings.model_dump() + if host.splithttp_settings + else None + ), + early_data=host.early_data, http_headers=host.http_headers, mtu=host.mtu, dns_servers=host.dns_servers, allowed_ips=host.allowed_ips, - mux=host.mux, + mux_settings=( + host.mux_settings.model_dump() if host.mux_settings else None + ), allowinsecure=host.allowinsecure, weight=host.weight, + universal=host.universal, + services=( + db.query(Service).filter(Service.id.in_(host.service_ids)).all() + ), + chain=[ + HostChain(chained_host_id=ch[0]) + for ch in db.query(InboundHost.id) + .filter(InboundHost.id.in_(host.chain_ids)) + .all() + ], ) - inbound.hosts.append(host) + if inbound: + inbound.hosts.append(host) + else: + db.add(host) db.commit() db.refresh(host) return host @@ -200,6 +278,10 @@ def add_host(db: Session, inbound: Inbound, host: InboundHostModify): def update_host(db: Session, db_host: InboundHost, host: InboundHostModify): db_host.remark = host.remark db_host.address = host.address + db_host.uuid = host.uuid + db_host.password = host.password + db_host.host_network = host.network + db_host.host_protocol = host.protocol db_host.port = host.port db_host.path = host.path db_host.sni = host.sni @@ -208,14 +290,46 @@ def update_host(db: Session, db_host: InboundHost, host: InboundHostModify): db_host.alpn = host.alpn.value db_host.fingerprint = host.fingerprint db_host.fragment = host.fragment.model_dump() if host.fragment else None - db_host.mux = host.mux + db_host.mux_settings = ( + host.mux_settings.model_dump() if host.mux_settings else None + ) db_host.is_disabled = host.is_disabled db_host.allowinsecure = host.allowinsecure - db_host.udp_noises = host.udp_noises + db_host.udp_noises = ( + [noise.model_dump() for noise in host.noise] if host.noise else None + ) + db_host.header_type = host.header_type + db_host.reality_public_key = host.reality_public_key + db_host.reality_short_ids = host.reality_short_ids + db_host.flow = host.flow + db_host.shadowtls_version = host.shadowtls_version + db_host.shadowsocks_method = host.shadowsocks_method + db_host.splithttp_settings = ( + host.splithttp_settings.model_dump() + if host.splithttp_settings + else None + ) + db_host.early_data = host.early_data + + chain_ids = [ + int(i[0]) + for i in db.query(InboundHost.id) + .filter(InboundHost.id.in_(host.chain_ids)) + .all() + ] + chain_nodes = [ + HostChain(host_id=db_host.id, chained_host_id=chain_id) + for chain_id in chain_ids + ] + db_host.chain = chain_nodes db_host.http_headers = host.http_headers db_host.mtu = host.mtu db_host.dns_servers = host.dns_servers db_host.allowed_ips = host.allowed_ips + db_host.universal = host.universal + db_host.services = ( + db.query(Service).filter(Service.id.in_(host.service_ids)).all() + ) db_host.weight = host.weight db.commit() db.refresh(db_host) diff --git a/app/db/migrations/versions/20241215_c9cab0432029_implement_host_chains.py b/app/db/migrations/versions/20241215_c9cab0432029_implement_host_chains.py new file mode 100644 index 0000000..6170301 --- /dev/null +++ b/app/db/migrations/versions/20241215_c9cab0432029_implement_host_chains.py @@ -0,0 +1,42 @@ +"""implement host chains + +Revision ID: 982867b533e6 +Revises: 79271458a973 +Create Date: 2024-12-15 04:27:44.704707 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "982867b533e6" +down_revision = "79271458a973" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "host_chains", + sa.Column("host_id", sa.Integer(), nullable=False), + sa.Column("chained_host_id", sa.Integer(), nullable=True), + sa.Column("seq", sa.Integer(), nullable=False), + sa.ForeignKeyConstraint( + ["chained_host_id"], + ["hosts.id"], + ), + sa.ForeignKeyConstraint( + ["host_id"], + ["hosts.id"], + ), + sa.PrimaryKeyConstraint("host_id", "seq"), + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table("host_chains") + # ### end Alembic commands ### diff --git a/app/db/migrations/versions/20241218_57eba0a293f2_unbound_host.py b/app/db/migrations/versions/20241218_57eba0a293f2_unbound_host.py new file mode 100644 index 0000000..15f72a0 --- /dev/null +++ b/app/db/migrations/versions/20241218_57eba0a293f2_unbound_host.py @@ -0,0 +1,161 @@ +"""unbound host + +Revision ID: 57eba0a293f2 +Revises: 982867b533e6 +Create Date: 2024-12-18 20:22:11.860923 + +""" + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "57eba0a293f2" +down_revision = "982867b533e6" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "hosts_services", + sa.Column("host_id", sa.Integer(), nullable=False), + sa.Column("service_id", sa.Integer(), nullable=False), + sa.ForeignKeyConstraint( + ["host_id"], + ["hosts.id"], + ), + sa.ForeignKeyConstraint( + ["service_id"], + ["services.id"], + ), + sa.PrimaryKeyConstraint("host_id", "service_id"), + ) + op.add_column( + "hosts", + sa.Column("host_protocol", sa.String(length=32), nullable=True), + ) + op.add_column( + "hosts", sa.Column("host_network", sa.String(length=32), nullable=True) + ) + op.add_column( + "hosts", sa.Column("uuid", sa.String(length=36), nullable=True) + ) + op.add_column( + "hosts", sa.Column("password", sa.String(length=128), nullable=True) + ) + op.add_column( + "hosts", sa.Column("header_type", sa.String(length=32), nullable=True) + ) + op.add_column( + "hosts", + sa.Column("reality_public_key", sa.String(length=128), nullable=True), + ) + op.add_column( + "hosts", sa.Column("reality_short_ids", sa.JSON(), nullable=True) + ) + op.add_column( + "hosts", sa.Column("flow", sa.String(length=32), nullable=True) + ) + op.add_column( + "hosts", sa.Column("shadowtls_version", sa.Integer(), nullable=True) + ) + op.add_column( + "hosts", + sa.Column("shadowsocks_method", sa.String(length=32), nullable=True), + ) + op.add_column( + "hosts", sa.Column("splithttp_settings", sa.JSON(), nullable=True) + ) + op.drop_column("hosts", "mux") + op.add_column("hosts", sa.Column("mux_settings", sa.JSON(), nullable=True)) + op.add_column( + "hosts", sa.Column("early_data", sa.Integer(), nullable=True) + ) + op.add_column( + "hosts", + sa.Column( + "universal", + sa.Boolean(), + server_default=sa.sql.false(), + nullable=False, + ), + ) + if op.get_bind().dialect.name == "sqlite": + op.add_column( + "hosts", sa.Column("inbound_id_new", sa.Integer, nullable=True) + ) + + # Copy data from the old column to the new column + op.execute( + """ + UPDATE hosts + SET inbound_id_new = inbound_id + """ + ) + + # Drop the old column (SQLite requires recreating the table for this step) + with op.batch_alter_table("hosts") as batch_op: + batch_op.drop_column("inbound_id") + + # Rename the new column to the old column's name + op.alter_column( + "hosts", "inbound_id_new", new_column_name="inbound_id" + ) + else: + op.alter_column( + "hosts", "inbound_id", existing_type=sa.INTEGER(), nullable=True + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("hosts", "universal") + op.drop_column("hosts", "early_data") + op.add_column( + "hosts", + sa.Column( + "mux", sa.Boolean(), server_default=sa.sql.true(), nullable=False + ), + ) + op.drop_column("hosts", "mux_settings") + op.drop_column("hosts", "splithttp_settings") + op.drop_column("hosts", "shadowsocks_method") + op.drop_column("hosts", "shadowtls_version") + op.drop_column("hosts", "flow") + op.drop_column("hosts", "reality_short_ids") + op.drop_column("hosts", "reality_public_key") + op.drop_column("hosts", "header_type") + op.drop_column("hosts", "password") + op.drop_column("hosts", "uuid") + op.drop_column("hosts", "host_network") + op.drop_column("hosts", "host_protocol") + op.drop_table("hosts_services") + if op.get_bind().dialect.name == "sqlite": + op.add_column( + "hosts", sa.Column("inbound_id_old", sa.Integer, nullable=False) + ) + + # Copy data from the new column back to the old column + op.execute( + """ + UPDATE hosts + SET inbound_id_old = inbound_id + """ + ) + + # Drop the modified column + with op.batch_alter_table("hosts") as batch_op: + batch_op.drop_column("inbound_id") + + # Rename the old column back to the original name + op.alter_column( + "hosts", "inbound_id_old", new_column_name="inbound_id" + ) + else: + op.alter_column( + "hosts", "inbound_id", existing_type=sa.INTEGER(), nullable=False + ) + # ### end Alembic commands ### diff --git a/app/db/models.py b/app/db/models.py index f92770b..b6fb8da 100644 --- a/app/db/models.py +++ b/app/db/models.py @@ -22,6 +22,7 @@ from sqlalchemy import ( Text, ) from sqlalchemy.ext.hybrid import hybrid_property +from sqlalchemy.ext.orderinglist import ordering_list from sqlalchemy.orm import relationship, column_property from sqlalchemy.sql.expression import text @@ -60,6 +61,13 @@ users_services = Table( Column("service_id", ForeignKey("services.id"), primary_key=True), ) +hosts_services = Table( + "hosts_services", + Base.metadata, + Column("host_id", ForeignKey("hosts.id"), primary_key=True), + Column("service_id", ForeignKey("services.id"), primary_key=True), +) + class Admin(Base): __tablename__ = "admins" @@ -308,12 +316,33 @@ class Inbound(Base): return [service.id for service in self.services] +class HostChain(Base): + __tablename__ = "host_chains" + + host_id = Column(Integer, ForeignKey("hosts.id"), primary_key=True) + chained_host_id = Column(Integer, ForeignKey("hosts.id")) + seq = Column(Integer, primary_key=True) + + host = relationship( + "InboundHost", foreign_keys=[host_id], back_populates="chain" + ) + chained_host = relationship( + "InboundHost", + foreign_keys=[chained_host_id], + lazy="joined", + ) + + class InboundHost(Base): __tablename__ = "hosts" id = Column(Integer, primary_key=True) remark = Column(String(256), nullable=False) address = Column(String(256), nullable=False) + host_protocol = Column(String(32)) + host_network = Column(String(32)) + uuid = Column(String(36)) + password = Column(String(128)) port = Column(Integer) path = Column(String(256)) sni = Column(String(1024)) @@ -334,27 +363,63 @@ class InboundHost(Base): server_default=InboundHostSecurity.none.name, ) - mux = Column( - Boolean, - default=False, - nullable=False, - server_default=sqlalchemy.sql.false(), - ) fragment = Column(JSON()) udp_noises = Column(JSON()) http_headers = Column(JSON()) dns_servers = Column(String(128)) mtu = Column(Integer) allowed_ips = Column(Text()) - inbound_id = Column(Integer, ForeignKey("inbounds.id"), nullable=False) + header_type = Column(String(32)) + reality_public_key = Column(String(128)) + reality_short_ids = Column(JSON()) + flow = Column(String(32)) + shadowtls_version = Column(Integer) + shadowsocks_method = Column(String(32)) + splithttp_settings = Column(JSON()) + mux_settings = Column(JSON()) + early_data = Column(Integer) + inbound_id = Column(Integer, ForeignKey("inbounds.id"), nullable=True) inbound = relationship("Inbound", back_populates="hosts", lazy="joined") allowinsecure = Column(Boolean, default=False) is_disabled = Column(Boolean, default=False) weight = Column(Integer, default=1, nullable=False, server_default="1") + universal = Column( + Boolean, + default=False, + nullable=False, + server_default=sqlalchemy.sql.false(), + ) + services = relationship("Service", secondary=hosts_services) + + @property + def service_ids(self): + return [service.id for service in self.services] + + chain = relationship( + "HostChain", + foreign_keys="[HostChain.host_id]", + order_by=HostChain.seq, + collection_class=ordering_list("seq"), + lazy="joined", + cascade="all, delete-orphan", + ) + + @property + def chain_ids(self): + return [c.chained_host_id for c in self.chain] + @property def protocol(self): - return self.inbound.protocol if self.inbound else None + return self.inbound.protocol if self.inbound else self.host_protocol + + @property + def network(self): + return self.host_network + + @property + def noise(self): + return self.udp_noises class System(Base): diff --git a/app/models/proxy.py b/app/models/proxy.py index 6e67094..0873176 100644 --- a/app/models/proxy.py +++ b/app/models/proxy.py @@ -67,15 +67,60 @@ class FragmentSettings(BaseModel): interval: str = Field(pattern=r"^[\d-]{1,32}$") -class UDPNoiseSettings(BaseModel): +class XrayNoise(BaseModel): type: str = Field(pattern=r"^(:?rand|str|base64)$") packet: str = Field() - interval: str = Field(pattern=r"^[\d-]{1,32}$") + delay: str = Field(pattern=r"^\d{1,10}(-\d{1,10})?$") + + +class XMuxSettings(BaseModel): + max_concurrency: str | None = Field( + None, pattern=r"^\d{1,10}(-\d{1,10})?$" + ) + max_connections: str | None = Field( + None, pattern=r"^\d{1,10}(-\d{1,10})?$" + ) + max_reuse_times: str | None = Field( + None, pattern=r"^\d{1,10}(-\d{1,10})?$" + ) + max_lifetime: str | None = Field(None, pattern=r"^\d{1,10}(-\d{1,10})?$") + max_request_times: str | None = Field(None) + keep_alive_period: int | None = Field(None) + + +class SplitHttpSettings(BaseModel): + mode: str | None = None + no_grpc_header: bool | None = None + padding_bytes: str | None = None + xmux: XMuxSettings | None = None + + +class SingBoxMuxSettings(BaseModel): + max_connections: int | None = Field(None) + max_streams: int | None = Field(None) + min_streams: int | None = Field(None) + padding: bool | None = Field(None) + + +class MuxCoolSettings(BaseModel): + concurrency: int | None = None + xudp_concurrency: int | None = None + xudp_proxy_443: str | None = None + + +class MuxSettings(BaseModel): + protocol: str + sing_box_mux_settings: SingBoxMuxSettings | None = None + mux_cool_settings: MuxCoolSettings | None = None class InboundHost(BaseModel): remark: str address: str + uuid: str | None = None + password: str | None = None + protocol: ProxyTypes | str | None = None + network: str | None = None port: int | None = Field(None) sni: str | None = Field(None) host: str | None = Field(None) @@ -85,16 +130,27 @@ class InboundHost(BaseModel): fingerprint: InboundHostFingerprint = InboundHostFingerprint.none allowinsecure: bool | None = False is_disabled: bool | None = False - mux: bool = Field(False) fragment: FragmentSettings | None = Field(None) - udp_noises: list[UDPNoiseSettings] | None = [] - http_headers: dict[str, list[str]] | None = {} + noise: list[XrayNoise] | None = None + http_headers: dict[str, str] | None = Field(default_factory=dict) mtu: int | None = None dns_servers: str | None = None allowed_ips: str | None = None + header_type: str | None = None + reality_public_key: str | None = None + reality_short_ids: list[str] | None = None + flow: str | None = None + shadowtls_version: int | None = None + shadowsocks_method: str | None = None + splithttp_settings: SplitHttpSettings | None = None + early_data: int | None = None + mux_settings: MuxSettings | None = None + universal: bool = False + service_ids: list[int] = Field(default_factory=list) weight: int = 1 - protocol: ProxyTypes | None = None inbound_id: int | None = None + chain_ids: list[int] = Field(default_factory=list) + model_config = ConfigDict(from_attributes=True) @field_validator("remark", "address", "path") diff --git a/app/routes/inbounds.py b/app/routes/inbounds.py index 0c1bffc..911eb41 100644 --- a/app/routes/inbounds.py +++ b/app/routes/inbounds.py @@ -32,6 +32,14 @@ def get_hosts(db: DBDep): return paginate(db.query(DBInboundHost)) +@router.post("/hosts", response_model=InboundHostResponse) +def create_unbound_host(host: InboundHost, db: DBDep): + """ + Add a host without an inbound + """ + return crud.add_host(db, None, host) + + @router.get("/hosts/{id}", response_model=InboundHostResponse) def get_host(id: int, db: DBDep): """ diff --git a/app/utils/share.py b/app/utils/share.py index 51ea8be..880a8e7 100644 --- a/app/utils/share.py +++ b/app/utils/share.py @@ -19,6 +19,12 @@ from v2share import ( WireGuardConfig, ) from v2share.base import BaseConfig +from v2share.data import MuxCoolSettings as V2MuxCoolSettings +from v2share.data import MuxSettings as V2MuxSettings +from v2share.data import SingBoxMuxSettings as V2SingBoxMuxSettings +from v2share.data import SplitHttpSettings as V2SplitHttpSettings +from v2share.data import XMuxSettings as V2XMuxSettings +from v2share.data import XrayNoise from v2share.links import LinksConfig from app.config.env import ( @@ -27,9 +33,12 @@ from app.config.env import ( CLASH_SUBSCRIPTION_TEMPLATE, SUBSCRIPTION_PAGE_TEMPLATE, ) -from app.db import GetDB, crud +from app.db import GetDB +from app.db.crud import get_hosts_for_user from app.models.proxy import ( InboundHostSecurity, + SplitHttpSettings, + MuxSettings, ) from app.models.settings import SubscriptionSettings from app.models.user import UserResponse, UserExpireStrategy @@ -121,6 +130,7 @@ def generate_subscription( user.key, user.id, format_variables, + chaining_support=subscription_handler.chaining_support, ) subscription_handler.add_proxies(configs) @@ -231,102 +241,186 @@ def generate_user_configs( key: str, user_id: int, format_variables: dict, + chaining_support: bool, ) -> Union[List, str]: salt = secrets.token_hex(8) configs = [] - for inb in inbounds: - inb_id, inbound, protocol, tag = ( - inb.id, - json.loads(inb.config), - inb.protocol, - inb.tag, - ) + with GetDB() as db: + hosts = get_hosts_for_user(db, user_id) - format_variables.update({"PROTOCOL": protocol.name}) - if not inbound: + for host in hosts: + chained_hosts = [c.chained_host for c in host.chain] + if chained_hosts and not chaining_support: continue + data = create_config( + host, key, format_variables, salt, user_id, chained_hosts + ) + configs.append(data) + + return configs + + +def create_config( + host, key, format_variables, salt, user_id, next_hosts: list | None = None +): + if next_hosts is None: + next_hosts = [] - format_variables.update( - {"TRANSPORT": inbound.get("network", "")} + if host.inbound: + inbound, protocol = ( + json.loads(host.inbound.config), + host.inbound.protocol, ) - with GetDB() as db: - hosts = crud.get_inbound_hosts(db, inb_id) - - for host in hosts: - if host.is_disabled: - continue - host_snis = host.sni.split(",") if host.sni else [] - sni_list = host_snis or inbound.get("sni", []) - if sni_list: - sni = random.choice(sni_list).replace("*", salt) - else: - sni = "" - - host_hosts = host.host.split(",") if host.host else [] - req_host_list = host_hosts or inbound.get("host", []) - if req_host_list: - req_host = random.choice(req_host_list).replace("*", salt) - else: - req_host = "" - - host_tls = ( - None - if host.security == InboundHostSecurity.inbound_default - else host.security.value + network = inbound.get("network") + auth_uuid, auth_password = UUID(gen_uuid(key)), gen_password(key) + else: + inbound, protocol, network = {}, host.host_protocol, host.host_network + auth_uuid, auth_password = ( + UUID(host.uuid) if host.uuid else None + ), host.password + + format_variables.update( + { + "PROTOCOL": ( + host.inbound.protocol.value + if host.inbound + else host.host_protocol ) - data = V2Data( - protocol.value, - host.remark.format_map(format_variables), - host.address.format_map(format_variables), - host.port or inbound["port"], - transport_type=inbound.get("network"), - sni=sni, - host=req_host, - tls=host_tls or inbound.get("tls"), - header_type=inbound.get("header_type"), - alpn=host.alpn if host.alpn != "none" else None, - path=( - host.path.format_map(format_variables) - if host.path - else inbound.get("path") - ), - fingerprint=host.fingerprint.value or inbound.get("fp"), - reality_pbk=inbound.get("pbk"), - reality_sid=inbound.get("sid"), - client_address=calculate_client_address( - inbound.get("address"), user_id + } + ) + format_variables.update({"TRANSPORT": network or ""}) + + host_snis = host.sni.split(",") if host.sni else [] + sni_list = host_snis or inbound.get("sni", []) + if sni_list: + sni = random.choice(sni_list).replace("*", salt) + else: + sni = "" + + host_hosts = host.host.split(",") if host.host else [] + req_host_list = host_hosts or inbound.get("host", []) + if req_host_list: + req_host = random.choice(req_host_list).replace("*", salt) + else: + req_host = "" + + host_tls = ( + None + if host.security == InboundHostSecurity.inbound_default + else host.security.value + ) + splithttp_settings = ( + SplitHttpSettings.model_validate(host.splithttp_settings) + if host.splithttp_settings + else None + ) + mux_settings = ( + MuxSettings.model_validate(host.mux_settings) + if host.mux_settings + else None + ) + + data = V2Data( + host.inbound.protocol.value if host.inbound else host.host_protocol, + host.remark.format_map(format_variables), + host.address.format_map(format_variables), + host.port or inbound.get("port", 0), + transport_type=network, + sni=sni, + host=req_host, + tls=host_tls or inbound.get("tls"), + header_type=host.header_type or inbound.get("header_type"), + alpn=host.alpn if host.alpn != "none" else None, + path=( + host.path.format_map(format_variables) + if host.path + else inbound.get("path") + ), + fingerprint=host.fingerprint.value or inbound.get("fp"), + reality_pbk=inbound.get("pbk"), + reality_sid=inbound.get("sid"), + client_address=calculate_client_address( + inbound.get("address"), user_id + ), + flow=host.flow or inbound.get("flow"), + dns_servers=(host.dns_servers.split(",") if host.dns_servers else []), + mtu=host.mtu, + allowed_ips=( + list(map(str.strip, host.allowed_ips.split(","))) + if host.allowed_ips + else None + ), + allow_insecure=host.allowinsecure, + uuid=auth_uuid, + password=auth_password, + ed25519=generate_curve25519_pbk(key), + early_data=host.early_data, + splithttp_settings=( + V2SplitHttpSettings( + mode=splithttp_settings.mode, + no_grpc_header=splithttp_settings.no_grpc_header, + padding_bytes=splithttp_settings.padding_bytes, + xmux=( + V2XMuxSettings(**splithttp_settings.xmux.model_dump()) + if splithttp_settings.xmux + else None ), - flow=inbound.get("flow"), - dns_servers=( - host.dns_servers.split(",") if host.dns_servers else [] + ) + if splithttp_settings + else None + ), + mux_settings=( + V2MuxSettings( + protocol=mux_settings.protocol, + sing_box_mux_settings=( + V2SingBoxMuxSettings( + **mux_settings.sing_box_mux_settings.model_dump() + ) + if mux_settings.sing_box_mux_settings + else None ), - mtu=host.mtu, - allowed_ips=( - list(map(str.strip, host.allowed_ips.split(","))) - if host.allowed_ips + mux_cool_settings=( + V2MuxCoolSettings( + **mux_settings.mux_cool_settings.model_dump() + ) + if mux_settings.mux_cool_settings else None ), - allow_insecure=host.allowinsecure, - uuid=UUID(gen_uuid(key)), - password=gen_password(key), - ed25519=generate_curve25519_pbk(key), - enable_mux=host.mux, - http_headers={ - "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", - shadowtls_version=inbound.get("shadowtls_version"), - weight=host.weight, ) - if host.fragment: - data.fragment = True - data.fragment_packets = host.fragment["packets"] - data.fragment_length = host.fragment["length"] - data.fragment_interval = host.fragment["interval"] - configs.append(data) + if mux_settings + else None + ), + http_headers=host.http_headers, + shadowsocks_method=host.shadowsocks_method or "chacha20-ietf-poly1305", + shadowtls_version=host.shadowtls_version + or inbound.get("shadowtls_version"), + weight=host.weight, + xray_noises=( + [XrayNoise(**noise) for noise in host.udp_noises] + if host.udp_noises + else None + ), + next=( + create_config( + next_hosts[0], + key, + format_variables, + salt, + user_id, + next_hosts[1:], + ) + if next_hosts + else None + ), + ) + if host.fragment: + data.fragment = True + data.fragment_packets = host.fragment["packets"] + data.fragment_length = host.fragment["length"] + data.fragment_interval = host.fragment["interval"] - return configs + return data def encode_title(text: str) -> str: diff --git a/dashboard/package.json b/dashboard/package.json index e37e44c..80099d2 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -1,153 +1,156 @@ { - "name": "marzneshin-dashboard", - "private": true, - "version": "0.3.0", - "type": "module", - "prettier": { - "tabWidth": 4 - }, - "scripts": { - "dev": "vite", - "build": "tsc && vite build", - "lint": "eslint . --ext ts,tsx --max-warnings 0", - "preview": "vite preview", - "test": "vitest", - "test:ui": "vitest --ui", - "test:ui:coverage": "vitest --ui --coverage.enabled=true", - "test:watch": "vitest --watch", - "test:coverage": "vitest run --coverage", - "storybook": "storybook dev -p 6006", - "build-storybook": "storybook build", - "test-storybook": "test-storybook", - "chromatic": "npx chromatic" - }, - "dependencies": { - "@chbphone55/pretty-bytes": "^1.0.0", - "@codemirror/language": "^6.10.6", - "@dnd-kit/core": "^6.2.0", - "@dnd-kit/modifiers": "^7.0.0", - "@dnd-kit/sortable": "^8.0.0", - "@dnd-kit/utilities": "^3.2.2", - "@heroicons/react": "^2.2.0", - "@hookform/resolvers": "^3.9.1", - "@kodingdotninja/use-tailwind-breakpoint": "^0.0.5", - "@monaco-editor/react": "^4.6.0", - "@nextui-org/progress": "^2.0.34", - "@nextui-org/react": "^2.4.8", - "@nextui-org/system": "^2.2.6", - "@nextui-org/theme": "^2.2.11", - "@radix-ui/react-accordion": "^1.2.1", - "@radix-ui/react-alert-dialog": "^1.1.2", - "@radix-ui/react-aspect-ratio": "^1.1.0", - "@radix-ui/react-checkbox": "^1.1.2", - "@radix-ui/react-collapsible": "^1.1.1", - "@radix-ui/react-dialog": "^1.1.2", - "@radix-ui/react-dropdown-menu": "^2.1.2", - "@radix-ui/react-icons": "^1.3.2", - "@radix-ui/react-label": "^2.1.0", - "@radix-ui/react-popover": "^1.1.2", - "@radix-ui/react-progress": "^1.1.0", - "@radix-ui/react-radio-group": "^1.2.1", - "@radix-ui/react-scroll-area": "^1.2.1", - "@radix-ui/react-select": "^2.1.2", - "@radix-ui/react-separator": "^1.1.0", - "@radix-ui/react-slot": "^1.1.0", - "@radix-ui/react-switch": "^1.1.1", - "@radix-ui/react-tabs": "^1.1.1", - "@radix-ui/react-toggle": "^1.1.0", - "@radix-ui/react-toggle-group": "^1.1.0", - "@radix-ui/react-tooltip": "^1.1.4", - "@react-hook/media-query": "^1.1.1", - "@storybook/addon-actions": "^8.4.6", - "@tailwindcss/aspect-ratio": "^0.4.2", - "@tailwindcss/forms": "^0.5.9", - "@tailwindcss/typography": "^0.5.15", - "@tanstack/react-query": "^5.62.0", - "@tanstack/react-router": "^1.85.0", - "@tanstack/react-table": "^8.20.5", - "@testing-library/jest-dom": "^6.6.3", - "@testing-library/react": "^14.3.1", - "@testing-library/user-event": "^14.5.2", - "@uidotdev/usehooks": "^2.4.1", - "@visx/group": "^3.12.0", - "@visx/legend": "^3.12.0", - "@visx/scale": "^3.12.0", - "@visx/shape": "^3.12.0", - "@visx/text": "^3.12.0", - "@vitest/coverage-istanbul": "^1.6.0", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "cmdk": "^1.0.4", - "d3": "^7.9.0", - "date-fns": "^3.6.0", - "dayjs": "^1.11.13", - "eslint-plugin-react-compiler": "0.0.0-experimental-0998c1e-20240625", - "framer-motion": "^11.12.0", - "i18next": "^23.16.8", - "i18next-browser-languagedetector": "^7.2.1", - "i18next-http-backend": "^2.7.1", - "lodash": "^4.17.21", - "lucide-react": "^0.359.0", - "next-themes": "^0.3.0", - "ofetch": "^1.4.1", - "react": "^18.3.1", - "react-copy-to-clipboard": "^5.1.0", - "react-day-picker": "^8.10.1", - "react-dom": "^18.3.1", - "react-hook-form": "^7.53.2", - "react-i18next": "^14.1.3", - "react-qr-code": "^2.0.15", - "react-resizable-panels": "^2.1.7", - "react-use-websocket": "^4.11.1", - "recharts": "^2.13.3", - "remirror": "^2.0.40", - "sonner": "^1.7.0", - "tailwind-merge": "^2.5.5", - "tailwindcss-animate": "^1.0.7", - "use-debounce": "^10.0.4", - "vaul": "^0.9.9", - "yaml": "^2.6.1", - "zod": "^3.23.8", - "zustand": "^4.5.5" - }, - "devDependencies": { - "@chromatic-com/storybook": "^1.9.0", - "@hookform/devtools": "^4.3.1", - "@storybook/addon-a11y": "^8.4.6", - "@storybook/addon-essentials": "^8.4.6", - "@storybook/addon-interactions": "^8.4.6", - "@storybook/addon-links": "^8.4.6", - "@storybook/addon-onboarding": "^8.4.6", - "@storybook/blocks": "^8.4.6", - "@storybook/react": "^8.4.6", - "@storybook/react-vite": "^8.4.6", - "@storybook/test": "^8.4.6", - "@storybook/test-runner": "^0.18.2", - "@tanstack/router-cli": "^1.84.4", - "@tanstack/router-vite-plugin": "^1.84.4", - "@types/d3": "^7.4.3", - "@types/node": "^20.17.9", - "@types/react": "^18.3.12", - "@types/react-copy-to-clipboard": "^5.0.7", - "@types/react-dom": "^18.3.1", - "@typescript-eslint/eslint-plugin": "^7.18.0", - "@typescript-eslint/parser": "^7.18.0", - "@vitejs/plugin-react-swc": "^3.7.2", - "@vitest/ui": "^1.6.0", - "autoprefixer": "^10.4.20", - "chromatic": "^11.19.0", - "eslint": "^8.57.1", - "eslint-plugin-react-hooks": "^4.6.2", - "eslint-plugin-react-refresh": "^0.4.14", - "eslint-plugin-storybook": "^0.8.0", - "jsdom": "^24.1.3", - "postcss": "^8.4.49", - "storybook": "^8.4.6", - "tailwindcss": "^3.4.15", - "typescript": "^5.7.2", - "use-sync-external-store": "^1.2.2", - "vite": "^5.4.11", - "vite-tsconfig-paths": "^4.3.2", - "vitest": "^1.6.0" - } + "name": "marzneshin-dashboard", + "private": true, + "version": "0.3.0", + "type": "module", + "prettier": { + "tabWidth": 4 + }, + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "lint": "eslint . --ext ts,tsx --max-warnings 0", + "preview": "vite preview", + "test": "vitest", + "test:ui": "vitest --ui", + "test:ui:coverage": "vitest --ui --coverage.enabled=true", + "test:watch": "vitest --watch", + "test:coverage": "vitest run --coverage", + "storybook": "storybook dev -p 6006 --config-dir ./.storybook", + "build-storybook": "storybook build", + "test-storybook": "test-storybook", + "chromatic": "npx chromatic" + }, + "overrides": { + "esbuild": "0.24.0" + }, + "dependencies": { + "@chbphone55/pretty-bytes": "^1.0.0", + "@codemirror/language": "^6.10.6", + "@dnd-kit/core": "^6.2.0", + "@dnd-kit/modifiers": "^7.0.0", + "@dnd-kit/sortable": "^8.0.0", + "@dnd-kit/utilities": "^3.2.2", + "@heroicons/react": "^2.2.0", + "@hookform/resolvers": "^3.9.1", + "@kodingdotninja/use-tailwind-breakpoint": "^0.0.5", + "@monaco-editor/react": "^4.6.0", + "@nextui-org/progress": "^2.0.34", + "@nextui-org/react": "^2.4.8", + "@nextui-org/system": "^2.2.6", + "@nextui-org/theme": "^2.2.11", + "@radix-ui/react-accordion": "^1.2.1", + "@radix-ui/react-alert-dialog": "^1.1.2", + "@radix-ui/react-aspect-ratio": "^1.1.0", + "@radix-ui/react-checkbox": "^1.1.2", + "@radix-ui/react-collapsible": "^1.1.1", + "@radix-ui/react-dialog": "^1.1.2", + "@radix-ui/react-dropdown-menu": "^2.1.2", + "@radix-ui/react-icons": "^1.3.2", + "@radix-ui/react-label": "^2.1.0", + "@radix-ui/react-popover": "^1.1.2", + "@radix-ui/react-progress": "^1.1.0", + "@radix-ui/react-radio-group": "^1.2.1", + "@radix-ui/react-scroll-area": "^1.2.1", + "@radix-ui/react-select": "^2.1.2", + "@radix-ui/react-separator": "^1.1.0", + "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-switch": "^1.1.1", + "@radix-ui/react-tabs": "^1.1.1", + "@radix-ui/react-toggle": "^1.1.0", + "@radix-ui/react-toggle-group": "^1.1.0", + "@radix-ui/react-tooltip": "^1.1.4", + "@react-hook/media-query": "^1.1.1", + "@storybook/addon-actions": "^8.4.6", + "@tailwindcss/aspect-ratio": "^0.4.2", + "@tailwindcss/forms": "^0.5.9", + "@tailwindcss/typography": "^0.5.15", + "@tanstack/react-query": "^5.62.0", + "@tanstack/react-router": "^1.85.0", + "@tanstack/react-table": "^8.20.5", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^14.3.1", + "@testing-library/user-event": "^14.5.2", + "@uidotdev/usehooks": "^2.4.1", + "@visx/group": "^3.12.0", + "@visx/legend": "^3.12.0", + "@visx/scale": "^3.12.0", + "@visx/shape": "^3.12.0", + "@visx/text": "^3.12.0", + "@vitest/coverage-istanbul": "^1.6.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cmdk": "^1.0.4", + "d3": "^7.9.0", + "date-fns": "^3.6.0", + "dayjs": "^1.11.13", + "eslint-plugin-react-compiler": "0.0.0-experimental-0998c1e-20240625", + "framer-motion": "^11.12.0", + "i18next": "^23.16.8", + "i18next-browser-languagedetector": "^7.2.1", + "i18next-http-backend": "^2.7.1", + "lodash": "^4.17.21", + "lucide-react": "^0.359.0", + "next-themes": "^0.3.0", + "ofetch": "^1.4.1", + "react": "^18.3.1", + "react-copy-to-clipboard": "^5.1.0", + "react-day-picker": "^8.10.1", + "react-dom": "^18.3.1", + "react-hook-form": "^7.53.2", + "react-i18next": "^14.1.3", + "react-qr-code": "^2.0.15", + "react-resizable-panels": "^2.1.7", + "react-use-websocket": "^4.11.1", + "recharts": "^2.13.3", + "remirror": "^2.0.40", + "sonner": "^1.7.0", + "tailwind-merge": "^2.5.5", + "tailwindcss-animate": "^1.0.7", + "use-debounce": "^10.0.4", + "vaul": "^0.9.9", + "yaml": "^2.6.1", + "zod": "^3.23.8", + "zustand": "^4.5.5" + }, + "devDependencies": { + "@chromatic-com/storybook": "^1.9.0", + "@hookform/devtools": "^4.3.1", + "@storybook/addon-a11y": "^8.4.6", + "@storybook/addon-essentials": "^8.4.6", + "@storybook/addon-interactions": "^8.4.6", + "@storybook/addon-links": "^8.4.6", + "@storybook/addon-onboarding": "^8.4.6", + "@storybook/blocks": "^8.4.6", + "@storybook/react": "^8.4.6", + "@storybook/react-vite": "^8.4.6", + "@storybook/test": "^8.4.6", + "@storybook/test-runner": "^0.18.2", + "@tanstack/router-cli": "^1.84.4", + "@tanstack/router-vite-plugin": "^1.84.4", + "@types/d3": "^7.4.3", + "@types/node": "^20.17.9", + "@types/react": "^18.3.12", + "@types/react-copy-to-clipboard": "^5.0.7", + "@types/react-dom": "^18.3.1", + "@typescript-eslint/eslint-plugin": "^7.18.0", + "@typescript-eslint/parser": "^7.18.0", + "@vitejs/plugin-react-swc": "^3.7.2", + "@vitest/ui": "^1.6.0", + "autoprefixer": "^10.4.20", + "chromatic": "^11.19.0", + "eslint": "^8.57.1", + "eslint-plugin-react-hooks": "^4.6.2", + "eslint-plugin-react-refresh": "^0.4.14", + "eslint-plugin-storybook": "^0.8.0", + "jsdom": "^24.1.3", + "postcss": "^8.4.49", + "storybook": "^8.4.7", + "tailwindcss": "^3.4.15", + "typescript": "^5.7.2", + "use-sync-external-store": "^1.2.2", + "vite": "^5.4.11", + "vite-tsconfig-paths": "^4.3.2", + "vitest": "^1.6.0" + } } diff --git a/dashboard/pnpm-lock.yaml b/dashboard/pnpm-lock.yaml index c292b9d..8296879 100644 --- a/dashboard/pnpm-lock.yaml +++ b/dashboard/pnpm-lock.yaml @@ -118,7 +118,7 @@ importers: version: 1.1.1(react@18.3.1) '@storybook/addon-actions': specifier: ^8.4.6 - version: 8.4.6(storybook@8.4.6) + version: 8.4.6(storybook@8.4.7) '@tailwindcss/aspect-ratio': specifier: ^0.4.2 version: 0.4.2(tailwindcss@3.4.15) @@ -278,34 +278,34 @@ importers: version: 4.3.1(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@storybook/addon-a11y': specifier: ^8.4.6 - version: 8.4.6(storybook@8.4.6) + version: 8.4.6(storybook@8.4.7) '@storybook/addon-essentials': specifier: ^8.4.6 - version: 8.4.6(@types/react@18.3.12)(storybook@8.4.6) + version: 8.4.6(@types/react@18.3.12)(storybook@8.4.7) '@storybook/addon-interactions': specifier: ^8.4.6 - version: 8.4.6(storybook@8.4.6) + version: 8.4.6(storybook@8.4.7) '@storybook/addon-links': specifier: ^8.4.6 - version: 8.4.6(react@18.3.1)(storybook@8.4.6) + version: 8.4.6(react@18.3.1)(storybook@8.4.7) '@storybook/addon-onboarding': specifier: ^8.4.6 - version: 8.4.6(react@18.3.1)(storybook@8.4.6) + version: 8.4.6(react@18.3.1)(storybook@8.4.7) '@storybook/blocks': specifier: ^8.4.6 - version: 8.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.6) + version: 8.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.7) '@storybook/react': specifier: ^8.4.6 - version: 8.4.6(@storybook/test@8.4.6(storybook@8.4.6))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.6)(typescript@5.7.2) + version: 8.4.6(@storybook/test@8.4.6(storybook@8.4.7))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.7)(typescript@5.7.2) '@storybook/react-vite': specifier: ^8.4.6 - version: 8.4.6(@storybook/test@8.4.6(storybook@8.4.6))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.28.0)(storybook@8.4.6)(typescript@5.7.2)(vite@5.4.11(@types/node@20.17.9)) + version: 8.4.6(@storybook/test@8.4.6(storybook@8.4.7))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.28.0)(storybook@8.4.7)(typescript@5.7.2)(vite@5.4.11(@types/node@20.17.9)) '@storybook/test': specifier: ^8.4.6 - version: 8.4.6(storybook@8.4.6) + version: 8.4.6(storybook@8.4.7) '@storybook/test-runner': specifier: ^0.18.2 - version: 0.18.2(@swc/helpers@0.5.15)(@types/node@20.17.9)(babel-plugin-macros@3.1.0)(storybook@8.4.6) + version: 0.18.2(@swc/helpers@0.5.15)(@types/node@20.17.9)(babel-plugin-macros@3.1.0)(storybook@8.4.7) '@tanstack/router-cli': specifier: ^1.84.4 version: 1.84.4 @@ -364,8 +364,8 @@ importers: specifier: ^8.4.49 version: 8.4.49 storybook: - specifier: ^8.4.6 - version: 8.4.6 + specifier: ^8.4.7 + version: 8.4.7 tailwindcss: specifier: ^3.4.15 version: 3.4.15 @@ -735,8 +735,8 @@ packages: cpu: [ppc64] os: [aix] - '@esbuild/aix-ppc64@0.24.0': - resolution: {integrity: sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==} + '@esbuild/aix-ppc64@0.24.1': + resolution: {integrity: sha512-rHKbvBIQEe46PUybicWHsWmStdB3fvHi7CKa36Ip6xUqEr61BmGcnrYohXRNswU9zP/okYLXMzIW6D0neeNv6Q==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] @@ -753,8 +753,8 @@ packages: cpu: [arm64] os: [android] - '@esbuild/android-arm64@0.24.0': - resolution: {integrity: sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==} + '@esbuild/android-arm64@0.24.1': + resolution: {integrity: sha512-0jrWbRDWSPNSmt0HDp4qhWHeL69BL3YSSCGlwNn4logcOijz7nLMsHLT1g5Kb9A8T3dCjX+5qekr5zkH+gbqxQ==} engines: {node: '>=18'} cpu: [arm64] os: [android] @@ -771,8 +771,8 @@ packages: cpu: [arm] os: [android] - '@esbuild/android-arm@0.24.0': - resolution: {integrity: sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==} + '@esbuild/android-arm@0.24.1': + resolution: {integrity: sha512-gKHsqtULVpVfsffGLaU/W4Jx+DLU8BLOQtvBrg+R22tz422VMgBK5moQ/ELDTRPR7Tqt9gLNk13XlvOFinXSzQ==} engines: {node: '>=18'} cpu: [arm] os: [android] @@ -789,8 +789,8 @@ packages: cpu: [x64] os: [android] - '@esbuild/android-x64@0.24.0': - resolution: {integrity: sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==} + '@esbuild/android-x64@0.24.1': + resolution: {integrity: sha512-FafSki3AkovwnU7zSMDyMKP93Fre1E+c7/mVErP46Y+63RiU2fvKekgpMuLkABDGLGahB/DD0PkPm0YAINmH0Q==} engines: {node: '>=18'} cpu: [x64] os: [android] @@ -807,8 +807,8 @@ packages: cpu: [arm64] os: [darwin] - '@esbuild/darwin-arm64@0.24.0': - resolution: {integrity: sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==} + '@esbuild/darwin-arm64@0.24.1': + resolution: {integrity: sha512-hJU5uPOQ0SBZ+0OZVx9dkpNyS0Cj1O7sjmqFMeQdp5ATYzCHhMD0CREHgq59eB0AFtCYDBoNcZXNJPwEZ/XDpA==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] @@ -825,8 +825,8 @@ packages: cpu: [x64] os: [darwin] - '@esbuild/darwin-x64@0.24.0': - resolution: {integrity: sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==} + '@esbuild/darwin-x64@0.24.1': + resolution: {integrity: sha512-siNAX65WSBKU7c+XJEKByMruE/JsHY9HU+n5BIMiNlo5axVbWwGXN4HhJQzOlY4TtOwSt3LRbS0zuI5SOjgoyg==} engines: {node: '>=18'} cpu: [x64] os: [darwin] @@ -843,8 +843,8 @@ packages: cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-arm64@0.24.0': - resolution: {integrity: sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==} + '@esbuild/freebsd-arm64@0.24.1': + resolution: {integrity: sha512-Kkl8APGvkp1S1g9tttiicChe49p+A3198sISIVcUGECqDPFXk9hSmVrUmaoCZWKo/zGK9TgLczLRLXduuhLplw==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] @@ -861,8 +861,8 @@ packages: cpu: [x64] os: [freebsd] - '@esbuild/freebsd-x64@0.24.0': - resolution: {integrity: sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==} + '@esbuild/freebsd-x64@0.24.1': + resolution: {integrity: sha512-7hm+A84yjna/LTVLad+8iG5cB/Ik+M/ekSrN4ALs9GolbwcyvtjSD+xoPhFFAg8D7xVu0JdDIoNNZ6+KWLcPoQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] @@ -879,8 +879,8 @@ packages: cpu: [arm64] os: [linux] - '@esbuild/linux-arm64@0.24.0': - resolution: {integrity: sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==} + '@esbuild/linux-arm64@0.24.1': + resolution: {integrity: sha512-hjv91wG/3V8oKFa6yAew5wFYc+8usgOL/VH6cNEqFtqpWf8RDmwMIRnTmqwxGJ9/9H5ib3KZfgcIgYwoX7F9VQ==} engines: {node: '>=18'} cpu: [arm64] os: [linux] @@ -897,8 +897,8 @@ packages: cpu: [arm] os: [linux] - '@esbuild/linux-arm@0.24.0': - resolution: {integrity: sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==} + '@esbuild/linux-arm@0.24.1': + resolution: {integrity: sha512-USovmgDDpiWs16nRCH/NmRfQUJEaGGDPHqK6+pGzuMZOfoe0MAciJRMu1AKP3Ky4gnpuQcXv7aPHpX0IwLWRhA==} engines: {node: '>=18'} cpu: [arm] os: [linux] @@ -915,8 +915,8 @@ packages: cpu: [ia32] os: [linux] - '@esbuild/linux-ia32@0.24.0': - resolution: {integrity: sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==} + '@esbuild/linux-ia32@0.24.1': + resolution: {integrity: sha512-TwspFcPJpYyBqDcoqSLBBaoGRGiPWkjH5V4raiFQ6maAkBho/rfQvtVpNPkLHEwnPlVSdl4HkHZ3n7NvvtU10w==} engines: {node: '>=18'} cpu: [ia32] os: [linux] @@ -933,8 +933,8 @@ packages: cpu: [loong64] os: [linux] - '@esbuild/linux-loong64@0.24.0': - resolution: {integrity: sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==} + '@esbuild/linux-loong64@0.24.1': + resolution: {integrity: sha512-BS3gcpF33m9hiVFeMCF2+LTdkEr/JljXZGQrlR0Bb7B3pn+uQrAJebclIGar+r8BDJ2yvX9bN4GmMPIKdS20EA==} engines: {node: '>=18'} cpu: [loong64] os: [linux] @@ -951,8 +951,8 @@ packages: cpu: [mips64el] os: [linux] - '@esbuild/linux-mips64el@0.24.0': - resolution: {integrity: sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==} + '@esbuild/linux-mips64el@0.24.1': + resolution: {integrity: sha512-X35vI7EufAX17Nqo6XoD89/HSlPJUB5zJ1UKeTiGOLXpOaz7zo+t1trSQOoq2Gr8usOX++S77VUw6L2wTMc2cA==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] @@ -969,8 +969,8 @@ packages: cpu: [ppc64] os: [linux] - '@esbuild/linux-ppc64@0.24.0': - resolution: {integrity: sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==} + '@esbuild/linux-ppc64@0.24.1': + resolution: {integrity: sha512-I+XQCBhTIXKqyLFDcyMP9Dp0u0fx2TiH3BTh4iIg58/a5hmS3l3Yr2AHG8gEsmjUA7WGfKy2ZqxsaVud15iI1w==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] @@ -987,8 +987,8 @@ packages: cpu: [riscv64] os: [linux] - '@esbuild/linux-riscv64@0.24.0': - resolution: {integrity: sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==} + '@esbuild/linux-riscv64@0.24.1': + resolution: {integrity: sha512-wK7f0cK/Mq2x42ImYAr+OWzyv4OQUQj/RcKvbxcEoe46LFCa4w08Cqow9zX8vN9SE8BScm4NGYT7CO0G8UBrTA==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] @@ -1005,8 +1005,8 @@ packages: cpu: [s390x] os: [linux] - '@esbuild/linux-s390x@0.24.0': - resolution: {integrity: sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==} + '@esbuild/linux-s390x@0.24.1': + resolution: {integrity: sha512-47oImRwZavr5qEvEHNPcdly8LuFp3i4xrqT9mNbUn4ZKbwyegVp10k1E1YARiOim8ggfPAPABhPqXdS1NJOAnw==} engines: {node: '>=18'} cpu: [s390x] os: [linux] @@ -1023,12 +1023,18 @@ packages: cpu: [x64] os: [linux] - '@esbuild/linux-x64@0.24.0': - resolution: {integrity: sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==} + '@esbuild/linux-x64@0.24.1': + resolution: {integrity: sha512-8qkGHVK1hH819iH7c9OsQsfUhJ0cgznoGT6vHRNxvNFPhcn0Y7HXLS0ndpY1sUkSM+umIdknz6vEqgPk6pbyIA==} engines: {node: '>=18'} cpu: [x64] os: [linux] + '@esbuild/netbsd-arm64@0.24.1': + resolution: {integrity: sha512-lH+bWKi8aCvlDu0vDVcZV4ENiHjVus3SQFueeydJ/mSfKywQ3LnbSjJ8PUgj+3dllq1OTFCGgh+x/14hrULVrg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.21.5': resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} engines: {node: '>=12'} @@ -1041,8 +1047,8 @@ packages: cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.24.0': - resolution: {integrity: sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==} + '@esbuild/netbsd-x64@0.24.1': + resolution: {integrity: sha512-OdjqCVKtJuxRk9gPit/iI4/wSCGOnCcuPkgkT8Pt+0vM63QUOBd5oNX2umXUBr4bYTpSCL/aGxrAb3qENcqA4g==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] @@ -1053,8 +1059,8 @@ packages: cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-arm64@0.24.0': - resolution: {integrity: sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==} + '@esbuild/openbsd-arm64@0.24.1': + resolution: {integrity: sha512-wy2psEw0wc+xbSB4Et3XZaONClCagOlQTsqRJaLtCcPggnuZMfb17c5T5w6RO6pFF5J2SWoM7+MJuWUEzvQN+Q==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] @@ -1071,8 +1077,8 @@ packages: cpu: [x64] os: [openbsd] - '@esbuild/openbsd-x64@0.24.0': - resolution: {integrity: sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==} + '@esbuild/openbsd-x64@0.24.1': + resolution: {integrity: sha512-GClG42X5JYHoQU5Jry0u+uN2vmKOwrifl10IvDBXtkxyGr9oqOJyrd2U+H2ZoZGNIt21d7WcVJJmJq3I3fl+5g==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] @@ -1089,8 +1095,8 @@ packages: cpu: [x64] os: [sunos] - '@esbuild/sunos-x64@0.24.0': - resolution: {integrity: sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==} + '@esbuild/sunos-x64@0.24.1': + resolution: {integrity: sha512-a0VfBsFPrlFKxzXuJ4nP0ia3jEbzBk/JW2wEW44dwr0RDOr/Y1+d+EJgT6L3h8y9X8ctig7ks0rWlbjkPn6PcA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] @@ -1107,8 +1113,8 @@ packages: cpu: [arm64] os: [win32] - '@esbuild/win32-arm64@0.24.0': - resolution: {integrity: sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==} + '@esbuild/win32-arm64@0.24.1': + resolution: {integrity: sha512-HqeXG1ttUnENzcGlPr0ouQHk8PQIoWi3thXElmafH1pVxC94sYdBVQregb2Qz7l1BmooUIOnzCGPCT4Oma0yTg==} engines: {node: '>=18'} cpu: [arm64] os: [win32] @@ -1125,8 +1131,8 @@ packages: cpu: [ia32] os: [win32] - '@esbuild/win32-ia32@0.24.0': - resolution: {integrity: sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==} + '@esbuild/win32-ia32@0.24.1': + resolution: {integrity: sha512-uA0iNg5jSy9XMiugX8Qtm3p9uUl9hi4JbOY18KnFBNTB+GsfJIWrDpE1cRFZrSHePiZs9cAwnprILpAKJWuYig==} engines: {node: '>=18'} cpu: [ia32] os: [win32] @@ -1143,8 +1149,8 @@ packages: cpu: [x64] os: [win32] - '@esbuild/win32-x64@0.24.0': - resolution: {integrity: sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==} + '@esbuild/win32-x64@0.24.1': + resolution: {integrity: sha512-wekV0z60AyaD8yYgRtxckqvGzzVaQmQRAhNrR352KzXLfhc4peh3UBMMmtYHbOqml6KblKy7oihC1eaZS68vRw==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -3562,8 +3568,8 @@ packages: peerDependencies: storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - '@storybook/core@8.4.6': - resolution: {integrity: sha512-WeojVtHy0/t50tzw/15S+DLzKsj8BN9yWdo3vJMvm+nflLFvfq1XvD9WGOWeaFp8E/o3AP+4HprXG0r42KEJtA==} + '@storybook/core@8.4.7': + resolution: {integrity: sha512-7Z8Z0A+1YnhrrSXoKKwFFI4gnsLbWzr8fnDCU6+6HlDukFYh8GHRcZ9zKfqmy6U3hw2h8H5DrHsxWfyaYUUOoA==} peerDependencies: prettier: ^2 || ^3 peerDependenciesMeta: @@ -4589,10 +4595,22 @@ packages: resolution: {integrity: sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==} engines: {node: '>=8'} + call-bind-apply-helpers@1.0.1: + resolution: {integrity: sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==} + engines: {node: '>= 0.4'} + call-bind@1.0.7: resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} engines: {node: '>= 0.4'} + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + + call-bound@1.0.3: + resolution: {integrity: sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==} + engines: {node: '>= 0.4'} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -4999,6 +5017,15 @@ packages: supports-color: optional: true + debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decamelize@1.2.0: resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} engines: {node: '>=0.10.0'} @@ -5129,6 +5156,10 @@ packages: domutils@1.7.0: resolution: {integrity: sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==} + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -5179,6 +5210,10 @@ packages: resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} engines: {node: '>= 0.4'} + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + es-errors@1.3.0: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} @@ -5186,6 +5221,10 @@ packages: es-get-iterator@1.1.3: resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==} + es-object-atoms@1.0.0: + resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} + engines: {node: '>= 0.4'} + es6-error@4.1.1: resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} @@ -5204,8 +5243,8 @@ packages: engines: {node: '>=18'} hasBin: true - esbuild@0.24.0: - resolution: {integrity: sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==} + esbuild@0.24.1: + resolution: {integrity: sha512-bHNW57YAKNh1VSbXP33EL9DevtRuT10czGhL9ynKpOAeBMNAkzsP8FSNoFTbU3abQB7kOb+JqUc89FqlZNbEeQ==} engines: {node: '>=18'} hasBin: true @@ -5496,6 +5535,10 @@ packages: resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} engines: {node: '>= 0.4'} + get-intrinsic@1.2.6: + resolution: {integrity: sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==} + engines: {node: '>= 0.4'} + get-nonce@1.0.1: resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} engines: {node: '>=6'} @@ -5558,6 +5601,10 @@ packages: resolution: {integrity: sha512-FQoVQnqcdk4hVM4JN1eromaun4iuS34oStkdlLENLdpULsuQcTyXj8w7ayhuUfPwEYZ1ZOooOTT6fdA9Vmx/RA==} engines: {node: '>= 0.4'} + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -5586,6 +5633,10 @@ packages: resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} engines: {node: '>= 0.4'} + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + has-tostringtag@1.0.2: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} @@ -5720,6 +5771,10 @@ packages: resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} engines: {node: '>= 0.4'} + is-arguments@1.2.0: + resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==} + engines: {node: '>= 0.4'} + is-array-buffer@3.0.4: resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} engines: {node: '>= 0.4'} @@ -5846,8 +5901,8 @@ packages: resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} engines: {node: '>= 0.4'} - is-typed-array@1.1.13: - resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==} + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} engines: {node: '>= 0.4'} is-typedarray@1.0.0: @@ -6294,6 +6349,10 @@ packages: math-expression-evaluator@1.4.0: resolution: {integrity: sha512-4vRUvPyxdO8cWULGTh9dZWL2tZK6LDBvj+OGHBER7poH9Qdt7kXEoj20wiz4lQUbUXQZFjPbe5mVDo9nutizCw==} + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + memoizerific@1.11.3: resolution: {integrity: sha512-/EuHYwAPdLtXwAwSZkh/Gutery6pD2KYd44oQLhAvQp/50mpyduZh8Q7PYHXTCJ+wuXxt7oij2LXyIJOOYFPog==} @@ -6755,6 +6814,9 @@ packages: prosemirror-model@1.24.0: resolution: {integrity: sha512-Ft7epNnycoQSM+2ObF35SBbBX+5WY39v8amVlrtlAcpglhlHs2tCTnWl7RX5tbp/PsMKcRcWV9cXPuoBWq0AIQ==} + prosemirror-model@1.24.1: + resolution: {integrity: sha512-YM053N+vTThzlWJ/AtPtF1j0ebO36nvbmDy4U7qA2XQB8JVaQp1FmB9Jhrps8s+z+uxhhVTny4m20ptUvhk0Mg==} + prosemirror-paste-rules@2.0.8: resolution: {integrity: sha512-k3wBSc4fwtrx/UbCaf8LzMugB+P/R/TvACRfPiLruHtOP0a6FJWsZ/GTV2DJX5mQ/xyuS/vDX+q7jIsSV1D5AQ==} peerDependencies: @@ -6765,8 +6827,8 @@ packages: prosemirror-resizable-view@2.0.15: resolution: {integrity: sha512-xd/TEJD5D0XmiUUFQOMWV8ZCZjvZRiDiFmGMEXxaB5NtCZK2DqCPVZ2wxsEj1upE1ri4+6/EhHLAdV65GD2oBQ==} - prosemirror-schema-list@1.4.1: - resolution: {integrity: sha512-jbDyaP/6AFfDfu70VzySsD75Om2t3sXTOdl5+31Wlxlg62td1haUpty/ybajSfJ1pkGadlOfwQq9kgW5IMo1Rg==} + prosemirror-schema-list@1.5.0: + resolution: {integrity: sha512-gg1tAfH1sqpECdhIHOA/aLg2VH3ROKBWQ4m8Qp9mBKrOxQRW61zc+gMCI8nh22gnBzd1t2u1/NPLmO3nAa3ssg==} prosemirror-state@1.4.3: resolution: {integrity: sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==} @@ -6794,6 +6856,9 @@ packages: prosemirror-view@1.37.0: resolution: {integrity: sha512-z2nkKI1sJzyi7T47Ji/ewBPuIma1RNvQCCYVdV+MqWBV7o4Sa1n94UJCJJ1aQRF/xRkFfyqLGlGFWitIcCOtbg==} + prosemirror-view@1.37.1: + resolution: {integrity: sha512-MEAnjOdXU1InxEmhjgmEzQAikaS6lF3hD64MveTPpjOGNTl87iRLA1HupC/DEV6YuK7m4Q9DHFNTjwIVtqz5NA==} + proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} @@ -7220,8 +7285,8 @@ packages: resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==} engines: {node: '>= 0.4'} - storybook@8.4.6: - resolution: {integrity: sha512-J6juZSZT2u3PUW0QZYZZYxBq6zU5O0OrkSgkMXGMg/QrS9to9IHmt4FjEMEyACRbXo8POcB/fSXa3VpGe7bv3g==} + storybook@8.4.7: + resolution: {integrity: sha512-RP/nMJxiWyFc8EVMH5gp20ID032Wvk+Yr3lmKidoegto5Iy+2dVQnUoElZb2zpbVXNHWakGuAkfI0dY1Hfp/vw==} hasBin: true peerDependencies: prettier: ^2 || ^3 @@ -7759,6 +7824,10 @@ packages: resolution: {integrity: sha512-g+N+GAWiRj66DngFwHvISJd+ITsyphZvD1vChfVg6cEdnzy53GzB3oy0fUNlvhz7H7+MiqhYr26qxQShCpKTTQ==} engines: {node: '>= 0.4'} + which-typed-array@1.1.18: + resolution: {integrity: sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==} + engines: {node: '>= 0.4'} + which@1.3.1: resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} hasBin: true @@ -8350,7 +8419,7 @@ snapshots: '@esbuild/aix-ppc64@0.23.1': optional: true - '@esbuild/aix-ppc64@0.24.0': + '@esbuild/aix-ppc64@0.24.1': optional: true '@esbuild/android-arm64@0.21.5': @@ -8359,7 +8428,7 @@ snapshots: '@esbuild/android-arm64@0.23.1': optional: true - '@esbuild/android-arm64@0.24.0': + '@esbuild/android-arm64@0.24.1': optional: true '@esbuild/android-arm@0.21.5': @@ -8368,7 +8437,7 @@ snapshots: '@esbuild/android-arm@0.23.1': optional: true - '@esbuild/android-arm@0.24.0': + '@esbuild/android-arm@0.24.1': optional: true '@esbuild/android-x64@0.21.5': @@ -8377,7 +8446,7 @@ snapshots: '@esbuild/android-x64@0.23.1': optional: true - '@esbuild/android-x64@0.24.0': + '@esbuild/android-x64@0.24.1': optional: true '@esbuild/darwin-arm64@0.21.5': @@ -8386,7 +8455,7 @@ snapshots: '@esbuild/darwin-arm64@0.23.1': optional: true - '@esbuild/darwin-arm64@0.24.0': + '@esbuild/darwin-arm64@0.24.1': optional: true '@esbuild/darwin-x64@0.21.5': @@ -8395,7 +8464,7 @@ snapshots: '@esbuild/darwin-x64@0.23.1': optional: true - '@esbuild/darwin-x64@0.24.0': + '@esbuild/darwin-x64@0.24.1': optional: true '@esbuild/freebsd-arm64@0.21.5': @@ -8404,7 +8473,7 @@ snapshots: '@esbuild/freebsd-arm64@0.23.1': optional: true - '@esbuild/freebsd-arm64@0.24.0': + '@esbuild/freebsd-arm64@0.24.1': optional: true '@esbuild/freebsd-x64@0.21.5': @@ -8413,7 +8482,7 @@ snapshots: '@esbuild/freebsd-x64@0.23.1': optional: true - '@esbuild/freebsd-x64@0.24.0': + '@esbuild/freebsd-x64@0.24.1': optional: true '@esbuild/linux-arm64@0.21.5': @@ -8422,7 +8491,7 @@ snapshots: '@esbuild/linux-arm64@0.23.1': optional: true - '@esbuild/linux-arm64@0.24.0': + '@esbuild/linux-arm64@0.24.1': optional: true '@esbuild/linux-arm@0.21.5': @@ -8431,7 +8500,7 @@ snapshots: '@esbuild/linux-arm@0.23.1': optional: true - '@esbuild/linux-arm@0.24.0': + '@esbuild/linux-arm@0.24.1': optional: true '@esbuild/linux-ia32@0.21.5': @@ -8440,7 +8509,7 @@ snapshots: '@esbuild/linux-ia32@0.23.1': optional: true - '@esbuild/linux-ia32@0.24.0': + '@esbuild/linux-ia32@0.24.1': optional: true '@esbuild/linux-loong64@0.21.5': @@ -8449,7 +8518,7 @@ snapshots: '@esbuild/linux-loong64@0.23.1': optional: true - '@esbuild/linux-loong64@0.24.0': + '@esbuild/linux-loong64@0.24.1': optional: true '@esbuild/linux-mips64el@0.21.5': @@ -8458,7 +8527,7 @@ snapshots: '@esbuild/linux-mips64el@0.23.1': optional: true - '@esbuild/linux-mips64el@0.24.0': + '@esbuild/linux-mips64el@0.24.1': optional: true '@esbuild/linux-ppc64@0.21.5': @@ -8467,7 +8536,7 @@ snapshots: '@esbuild/linux-ppc64@0.23.1': optional: true - '@esbuild/linux-ppc64@0.24.0': + '@esbuild/linux-ppc64@0.24.1': optional: true '@esbuild/linux-riscv64@0.21.5': @@ -8476,7 +8545,7 @@ snapshots: '@esbuild/linux-riscv64@0.23.1': optional: true - '@esbuild/linux-riscv64@0.24.0': + '@esbuild/linux-riscv64@0.24.1': optional: true '@esbuild/linux-s390x@0.21.5': @@ -8485,7 +8554,7 @@ snapshots: '@esbuild/linux-s390x@0.23.1': optional: true - '@esbuild/linux-s390x@0.24.0': + '@esbuild/linux-s390x@0.24.1': optional: true '@esbuild/linux-x64@0.21.5': @@ -8494,7 +8563,10 @@ snapshots: '@esbuild/linux-x64@0.23.1': optional: true - '@esbuild/linux-x64@0.24.0': + '@esbuild/linux-x64@0.24.1': + optional: true + + '@esbuild/netbsd-arm64@0.24.1': optional: true '@esbuild/netbsd-x64@0.21.5': @@ -8503,13 +8575,13 @@ snapshots: '@esbuild/netbsd-x64@0.23.1': optional: true - '@esbuild/netbsd-x64@0.24.0': + '@esbuild/netbsd-x64@0.24.1': optional: true '@esbuild/openbsd-arm64@0.23.1': optional: true - '@esbuild/openbsd-arm64@0.24.0': + '@esbuild/openbsd-arm64@0.24.1': optional: true '@esbuild/openbsd-x64@0.21.5': @@ -8518,7 +8590,7 @@ snapshots: '@esbuild/openbsd-x64@0.23.1': optional: true - '@esbuild/openbsd-x64@0.24.0': + '@esbuild/openbsd-x64@0.24.1': optional: true '@esbuild/sunos-x64@0.21.5': @@ -8527,7 +8599,7 @@ snapshots: '@esbuild/sunos-x64@0.23.1': optional: true - '@esbuild/sunos-x64@0.24.0': + '@esbuild/sunos-x64@0.24.1': optional: true '@esbuild/win32-arm64@0.21.5': @@ -8536,7 +8608,7 @@ snapshots: '@esbuild/win32-arm64@0.23.1': optional: true - '@esbuild/win32-arm64@0.24.0': + '@esbuild/win32-arm64@0.24.1': optional: true '@esbuild/win32-ia32@0.21.5': @@ -8545,7 +8617,7 @@ snapshots: '@esbuild/win32-ia32@0.23.1': optional: true - '@esbuild/win32-ia32@0.24.0': + '@esbuild/win32-ia32@0.24.1': optional: true '@esbuild/win32-x64@0.21.5': @@ -8554,7 +8626,7 @@ snapshots: '@esbuild/win32-x64@0.23.1': optional: true - '@esbuild/win32-x64@0.24.0': + '@esbuild/win32-x64@0.24.1': optional: true '@eslint-community/eslint-utils@4.4.1(eslint@8.57.1)': @@ -12177,15 +12249,15 @@ snapshots: prosemirror-history: 1.4.1 prosemirror-inputrules: 1.4.0 prosemirror-keymap: 1.2.2 - prosemirror-model: 1.24.0 - prosemirror-paste-rules: 2.0.8(prosemirror-model@1.24.0)(prosemirror-state@1.4.3)(prosemirror-view@1.37.0) - prosemirror-schema-list: 1.4.1 + prosemirror-model: 1.24.1 + prosemirror-paste-rules: 2.0.8(prosemirror-model@1.24.1)(prosemirror-state@1.4.3)(prosemirror-view@1.37.1) + prosemirror-schema-list: 1.5.0 prosemirror-state: 1.4.3 - prosemirror-suggest: 2.0.7(prosemirror-model@1.24.0)(prosemirror-state@1.4.3)(prosemirror-view@1.37.0) + prosemirror-suggest: 2.0.7(prosemirror-model@1.24.1)(prosemirror-state@1.4.3)(prosemirror-view@1.37.1) prosemirror-tables: 1.6.1 - prosemirror-trailing-node: 2.0.9(prosemirror-model@1.24.0)(prosemirror-state@1.4.3)(prosemirror-view@1.37.0) + prosemirror-trailing-node: 2.0.9(prosemirror-model@1.24.1)(prosemirror-state@1.4.3)(prosemirror-view@1.37.1) prosemirror-transform: 1.10.2 - prosemirror-view: 1.37.0 + prosemirror-view: 1.37.1 '@remirror/preset-core@2.0.16(@remirror/pm@2.0.8)(@types/node@20.17.9)(jsdom@24.1.3)': dependencies: @@ -12356,148 +12428,148 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 - '@storybook/addon-a11y@8.4.6(storybook@8.4.6)': + '@storybook/addon-a11y@8.4.6(storybook@8.4.7)': dependencies: - '@storybook/addon-highlight': 8.4.6(storybook@8.4.6) + '@storybook/addon-highlight': 8.4.6(storybook@8.4.7) axe-core: 4.10.2 - storybook: 8.4.6 + storybook: 8.4.7 - '@storybook/addon-actions@8.4.6(storybook@8.4.6)': + '@storybook/addon-actions@8.4.6(storybook@8.4.7)': dependencies: '@storybook/global': 5.0.0 '@types/uuid': 9.0.8 dequal: 2.0.3 polished: 4.3.1 - storybook: 8.4.6 + storybook: 8.4.7 uuid: 9.0.1 - '@storybook/addon-backgrounds@8.4.6(storybook@8.4.6)': + '@storybook/addon-backgrounds@8.4.6(storybook@8.4.7)': dependencies: '@storybook/global': 5.0.0 memoizerific: 1.11.3 - storybook: 8.4.6 + storybook: 8.4.7 ts-dedent: 2.2.0 - '@storybook/addon-controls@8.4.6(storybook@8.4.6)': + '@storybook/addon-controls@8.4.6(storybook@8.4.7)': dependencies: '@storybook/global': 5.0.0 dequal: 2.0.3 - storybook: 8.4.6 + storybook: 8.4.7 ts-dedent: 2.2.0 - '@storybook/addon-docs@8.4.6(@types/react@18.3.12)(storybook@8.4.6)': + '@storybook/addon-docs@8.4.6(@types/react@18.3.12)(storybook@8.4.7)': dependencies: '@mdx-js/react': 3.1.0(@types/react@18.3.12)(react@18.3.1) - '@storybook/blocks': 8.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.6) - '@storybook/csf-plugin': 8.4.6(storybook@8.4.6) - '@storybook/react-dom-shim': 8.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.6) + '@storybook/blocks': 8.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.7) + '@storybook/csf-plugin': 8.4.6(storybook@8.4.7) + '@storybook/react-dom-shim': 8.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.7) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - storybook: 8.4.6 + storybook: 8.4.7 ts-dedent: 2.2.0 transitivePeerDependencies: - '@types/react' - '@storybook/addon-essentials@8.4.6(@types/react@18.3.12)(storybook@8.4.6)': - dependencies: - '@storybook/addon-actions': 8.4.6(storybook@8.4.6) - '@storybook/addon-backgrounds': 8.4.6(storybook@8.4.6) - '@storybook/addon-controls': 8.4.6(storybook@8.4.6) - '@storybook/addon-docs': 8.4.6(@types/react@18.3.12)(storybook@8.4.6) - '@storybook/addon-highlight': 8.4.6(storybook@8.4.6) - '@storybook/addon-measure': 8.4.6(storybook@8.4.6) - '@storybook/addon-outline': 8.4.6(storybook@8.4.6) - '@storybook/addon-toolbars': 8.4.6(storybook@8.4.6) - '@storybook/addon-viewport': 8.4.6(storybook@8.4.6) - storybook: 8.4.6 + '@storybook/addon-essentials@8.4.6(@types/react@18.3.12)(storybook@8.4.7)': + dependencies: + '@storybook/addon-actions': 8.4.6(storybook@8.4.7) + '@storybook/addon-backgrounds': 8.4.6(storybook@8.4.7) + '@storybook/addon-controls': 8.4.6(storybook@8.4.7) + '@storybook/addon-docs': 8.4.6(@types/react@18.3.12)(storybook@8.4.7) + '@storybook/addon-highlight': 8.4.6(storybook@8.4.7) + '@storybook/addon-measure': 8.4.6(storybook@8.4.7) + '@storybook/addon-outline': 8.4.6(storybook@8.4.7) + '@storybook/addon-toolbars': 8.4.6(storybook@8.4.7) + '@storybook/addon-viewport': 8.4.6(storybook@8.4.7) + storybook: 8.4.7 ts-dedent: 2.2.0 transitivePeerDependencies: - '@types/react' - '@storybook/addon-highlight@8.4.6(storybook@8.4.6)': + '@storybook/addon-highlight@8.4.6(storybook@8.4.7)': dependencies: '@storybook/global': 5.0.0 - storybook: 8.4.6 + storybook: 8.4.7 - '@storybook/addon-interactions@8.4.6(storybook@8.4.6)': + '@storybook/addon-interactions@8.4.6(storybook@8.4.7)': dependencies: '@storybook/global': 5.0.0 - '@storybook/instrumenter': 8.4.6(storybook@8.4.6) - '@storybook/test': 8.4.6(storybook@8.4.6) + '@storybook/instrumenter': 8.4.6(storybook@8.4.7) + '@storybook/test': 8.4.6(storybook@8.4.7) polished: 4.3.1 - storybook: 8.4.6 + storybook: 8.4.7 ts-dedent: 2.2.0 - '@storybook/addon-links@8.4.6(react@18.3.1)(storybook@8.4.6)': + '@storybook/addon-links@8.4.6(react@18.3.1)(storybook@8.4.7)': dependencies: '@storybook/csf': 0.1.12 '@storybook/global': 5.0.0 - storybook: 8.4.6 + storybook: 8.4.7 ts-dedent: 2.2.0 optionalDependencies: react: 18.3.1 - '@storybook/addon-measure@8.4.6(storybook@8.4.6)': + '@storybook/addon-measure@8.4.6(storybook@8.4.7)': dependencies: '@storybook/global': 5.0.0 - storybook: 8.4.6 + storybook: 8.4.7 tiny-invariant: 1.3.3 - '@storybook/addon-onboarding@8.4.6(react@18.3.1)(storybook@8.4.6)': + '@storybook/addon-onboarding@8.4.6(react@18.3.1)(storybook@8.4.7)': dependencies: react-confetti: 6.1.0(react@18.3.1) - storybook: 8.4.6 + storybook: 8.4.7 transitivePeerDependencies: - react - '@storybook/addon-outline@8.4.6(storybook@8.4.6)': + '@storybook/addon-outline@8.4.6(storybook@8.4.7)': dependencies: '@storybook/global': 5.0.0 - storybook: 8.4.6 + storybook: 8.4.7 ts-dedent: 2.2.0 - '@storybook/addon-toolbars@8.4.6(storybook@8.4.6)': + '@storybook/addon-toolbars@8.4.6(storybook@8.4.7)': dependencies: - storybook: 8.4.6 + storybook: 8.4.7 - '@storybook/addon-viewport@8.4.6(storybook@8.4.6)': + '@storybook/addon-viewport@8.4.6(storybook@8.4.7)': dependencies: memoizerific: 1.11.3 - storybook: 8.4.6 + storybook: 8.4.7 - '@storybook/blocks@8.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.6)': + '@storybook/blocks@8.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.7)': dependencies: '@storybook/csf': 0.1.12 '@storybook/icons': 1.2.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - storybook: 8.4.6 + storybook: 8.4.7 ts-dedent: 2.2.0 optionalDependencies: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@storybook/builder-vite@8.4.6(storybook@8.4.6)(vite@5.4.11(@types/node@20.17.9))': + '@storybook/builder-vite@8.4.6(storybook@8.4.7)(vite@5.4.11(@types/node@20.17.9))': dependencies: - '@storybook/csf-plugin': 8.4.6(storybook@8.4.6) + '@storybook/csf-plugin': 8.4.6(storybook@8.4.7) browser-assert: 1.2.1 - storybook: 8.4.6 + storybook: 8.4.7 ts-dedent: 2.2.0 vite: 5.4.11(@types/node@20.17.9) - '@storybook/components@8.4.6(storybook@8.4.6)': + '@storybook/components@8.4.6(storybook@8.4.7)': dependencies: - storybook: 8.4.6 + storybook: 8.4.7 - '@storybook/core-common@8.4.6(storybook@8.4.6)': + '@storybook/core-common@8.4.6(storybook@8.4.7)': dependencies: - storybook: 8.4.6 + storybook: 8.4.7 - '@storybook/core@8.4.6': + '@storybook/core@8.4.7': dependencies: '@storybook/csf': 0.1.12 better-opn: 3.0.2 browser-assert: 1.2.1 - esbuild: 0.24.0 - esbuild-register: 3.6.0(esbuild@0.24.0) + esbuild: 0.24.1 + esbuild-register: 3.6.0(esbuild@0.24.1) jsdoc-type-pratt-parser: 4.1.0 process: 0.11.10 recast: 0.23.9 @@ -12509,14 +12581,14 @@ snapshots: - supports-color - utf-8-validate - '@storybook/csf-plugin@8.4.6(storybook@8.4.6)': + '@storybook/csf-plugin@8.4.6(storybook@8.4.7)': dependencies: - storybook: 8.4.6 + storybook: 8.4.7 unplugin: 1.16.0 - '@storybook/csf-tools@8.4.6(storybook@8.4.6)': + '@storybook/csf-tools@8.4.6(storybook@8.4.7)': dependencies: - storybook: 8.4.6 + storybook: 8.4.7 '@storybook/csf@0.0.1': dependencies: @@ -12533,39 +12605,39 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@storybook/instrumenter@8.4.6(storybook@8.4.6)': + '@storybook/instrumenter@8.4.6(storybook@8.4.7)': dependencies: '@storybook/global': 5.0.0 '@vitest/utils': 2.1.6 - storybook: 8.4.6 + storybook: 8.4.7 - '@storybook/manager-api@8.4.6(storybook@8.4.6)': + '@storybook/manager-api@8.4.6(storybook@8.4.7)': dependencies: - storybook: 8.4.6 + storybook: 8.4.7 - '@storybook/preview-api@8.4.6(storybook@8.4.6)': + '@storybook/preview-api@8.4.6(storybook@8.4.7)': dependencies: - storybook: 8.4.6 + storybook: 8.4.7 - '@storybook/react-dom-shim@8.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.6)': + '@storybook/react-dom-shim@8.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.7)': dependencies: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - storybook: 8.4.6 + storybook: 8.4.7 - '@storybook/react-vite@8.4.6(@storybook/test@8.4.6(storybook@8.4.6))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.28.0)(storybook@8.4.6)(typescript@5.7.2)(vite@5.4.11(@types/node@20.17.9))': + '@storybook/react-vite@8.4.6(@storybook/test@8.4.6(storybook@8.4.7))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.28.0)(storybook@8.4.7)(typescript@5.7.2)(vite@5.4.11(@types/node@20.17.9))': dependencies: '@joshwooding/vite-plugin-react-docgen-typescript': 0.4.2(typescript@5.7.2)(vite@5.4.11(@types/node@20.17.9)) '@rollup/pluginutils': 5.1.3(rollup@4.28.0) - '@storybook/builder-vite': 8.4.6(storybook@8.4.6)(vite@5.4.11(@types/node@20.17.9)) - '@storybook/react': 8.4.6(@storybook/test@8.4.6(storybook@8.4.6))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.6)(typescript@5.7.2) + '@storybook/builder-vite': 8.4.6(storybook@8.4.7)(vite@5.4.11(@types/node@20.17.9)) + '@storybook/react': 8.4.6(@storybook/test@8.4.6(storybook@8.4.7))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.7)(typescript@5.7.2) find-up: 5.0.0 magic-string: 0.30.14 react: 18.3.1 react-docgen: 7.1.0 react-dom: 18.3.1(react@18.3.1) resolve: 1.22.8 - storybook: 8.4.6 + storybook: 8.4.7 tsconfig-paths: 4.2.0 vite: 5.4.11(@types/node@20.17.9) transitivePeerDependencies: @@ -12574,32 +12646,32 @@ snapshots: - supports-color - typescript - '@storybook/react@8.4.6(@storybook/test@8.4.6(storybook@8.4.6))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.6)(typescript@5.7.2)': + '@storybook/react@8.4.6(@storybook/test@8.4.6(storybook@8.4.7))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.7)(typescript@5.7.2)': dependencies: - '@storybook/components': 8.4.6(storybook@8.4.6) + '@storybook/components': 8.4.6(storybook@8.4.7) '@storybook/global': 5.0.0 - '@storybook/manager-api': 8.4.6(storybook@8.4.6) - '@storybook/preview-api': 8.4.6(storybook@8.4.6) - '@storybook/react-dom-shim': 8.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.6) - '@storybook/theming': 8.4.6(storybook@8.4.6) + '@storybook/manager-api': 8.4.6(storybook@8.4.7) + '@storybook/preview-api': 8.4.6(storybook@8.4.7) + '@storybook/react-dom-shim': 8.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.7) + '@storybook/theming': 8.4.6(storybook@8.4.7) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - storybook: 8.4.6 + storybook: 8.4.7 optionalDependencies: - '@storybook/test': 8.4.6(storybook@8.4.6) + '@storybook/test': 8.4.6(storybook@8.4.7) typescript: 5.7.2 - '@storybook/test-runner@0.18.2(@swc/helpers@0.5.15)(@types/node@20.17.9)(babel-plugin-macros@3.1.0)(storybook@8.4.6)': + '@storybook/test-runner@0.18.2(@swc/helpers@0.5.15)(@types/node@20.17.9)(babel-plugin-macros@3.1.0)(storybook@8.4.7)': dependencies: '@babel/core': 7.26.0 '@babel/generator': 7.26.2 '@babel/template': 7.25.9 '@babel/types': 7.26.0 '@jest/types': 29.6.3 - '@storybook/core-common': 8.4.6(storybook@8.4.6) + '@storybook/core-common': 8.4.6(storybook@8.4.7) '@storybook/csf': 0.1.12 - '@storybook/csf-tools': 8.4.6(storybook@8.4.6) - '@storybook/preview-api': 8.4.6(storybook@8.4.6) + '@storybook/csf-tools': 8.4.6(storybook@8.4.7) + '@storybook/preview-api': 8.4.6(storybook@8.4.7) '@swc/core': 1.5.7(@swc/helpers@0.5.15) '@swc/jest': 0.2.37(@swc/core@1.5.7(@swc/helpers@0.5.15)) expect-playwright: 0.8.0 @@ -12623,21 +12695,21 @@ snapshots: - supports-color - ts-node - '@storybook/test@8.4.6(storybook@8.4.6)': + '@storybook/test@8.4.6(storybook@8.4.7)': dependencies: '@storybook/csf': 0.1.12 '@storybook/global': 5.0.0 - '@storybook/instrumenter': 8.4.6(storybook@8.4.6) + '@storybook/instrumenter': 8.4.6(storybook@8.4.7) '@testing-library/dom': 10.4.0 '@testing-library/jest-dom': 6.5.0 '@testing-library/user-event': 14.5.2(@testing-library/dom@10.4.0) '@vitest/expect': 2.0.5 '@vitest/spy': 2.0.5 - storybook: 8.4.6 + storybook: 8.4.7 - '@storybook/theming@8.4.6(storybook@8.4.6)': + '@storybook/theming@8.4.6(storybook@8.4.7)': dependencies: - storybook: 8.4.6 + storybook: 8.4.7 '@svgmoji/blob@3.2.0': dependencies: @@ -13757,6 +13829,11 @@ snapshots: package-hash: 4.0.0 write-file-atomic: 3.0.3 + call-bind-apply-helpers@1.0.1: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + call-bind@1.0.7: dependencies: es-define-property: 1.0.0 @@ -13765,6 +13842,18 @@ snapshots: get-intrinsic: 1.2.4 set-function-length: 1.2.2 + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.1 + es-define-property: 1.0.1 + get-intrinsic: 1.2.6 + set-function-length: 1.2.2 + + call-bound@1.0.3: + dependencies: + call-bind-apply-helpers: 1.0.1 + get-intrinsic: 1.2.6 + callsites@3.1.0: {} camelcase-css@2.0.1: {} @@ -14182,6 +14271,10 @@ snapshots: dependencies: ms: 2.1.3 + debug@4.4.0: + dependencies: + ms: 2.1.3 + decamelize@1.2.0: {} decimal.js-light@2.5.1: {} @@ -14306,6 +14399,12 @@ snapshots: dom-serializer: 0.2.2 domelementtype: 1.3.1 + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + eastasianwidth@0.2.0: {} electron-to-chromium@1.5.67: {} @@ -14342,6 +14441,8 @@ snapshots: dependencies: get-intrinsic: 1.2.4 + es-define-property@1.0.1: {} + es-errors@1.3.0: {} es-get-iterator@1.1.3: @@ -14356,12 +14457,16 @@ snapshots: isarray: 2.0.5 stop-iteration-iterator: 1.0.0 + es-object-atoms@1.0.0: + dependencies: + es-errors: 1.3.0 + es6-error@4.1.1: {} - esbuild-register@3.6.0(esbuild@0.24.0): + esbuild-register@3.6.0(esbuild@0.24.1): dependencies: - debug: 4.3.7 - esbuild: 0.24.0 + debug: 4.4.0 + esbuild: 0.24.1 transitivePeerDependencies: - supports-color @@ -14418,32 +14523,33 @@ snapshots: '@esbuild/win32-ia32': 0.23.1 '@esbuild/win32-x64': 0.23.1 - esbuild@0.24.0: + esbuild@0.24.1: optionalDependencies: - '@esbuild/aix-ppc64': 0.24.0 - '@esbuild/android-arm': 0.24.0 - '@esbuild/android-arm64': 0.24.0 - '@esbuild/android-x64': 0.24.0 - '@esbuild/darwin-arm64': 0.24.0 - '@esbuild/darwin-x64': 0.24.0 - '@esbuild/freebsd-arm64': 0.24.0 - '@esbuild/freebsd-x64': 0.24.0 - '@esbuild/linux-arm': 0.24.0 - '@esbuild/linux-arm64': 0.24.0 - '@esbuild/linux-ia32': 0.24.0 - '@esbuild/linux-loong64': 0.24.0 - '@esbuild/linux-mips64el': 0.24.0 - '@esbuild/linux-ppc64': 0.24.0 - '@esbuild/linux-riscv64': 0.24.0 - '@esbuild/linux-s390x': 0.24.0 - '@esbuild/linux-x64': 0.24.0 - '@esbuild/netbsd-x64': 0.24.0 - '@esbuild/openbsd-arm64': 0.24.0 - '@esbuild/openbsd-x64': 0.24.0 - '@esbuild/sunos-x64': 0.24.0 - '@esbuild/win32-arm64': 0.24.0 - '@esbuild/win32-ia32': 0.24.0 - '@esbuild/win32-x64': 0.24.0 + '@esbuild/aix-ppc64': 0.24.1 + '@esbuild/android-arm': 0.24.1 + '@esbuild/android-arm64': 0.24.1 + '@esbuild/android-x64': 0.24.1 + '@esbuild/darwin-arm64': 0.24.1 + '@esbuild/darwin-x64': 0.24.1 + '@esbuild/freebsd-arm64': 0.24.1 + '@esbuild/freebsd-x64': 0.24.1 + '@esbuild/linux-arm': 0.24.1 + '@esbuild/linux-arm64': 0.24.1 + '@esbuild/linux-ia32': 0.24.1 + '@esbuild/linux-loong64': 0.24.1 + '@esbuild/linux-mips64el': 0.24.1 + '@esbuild/linux-ppc64': 0.24.1 + '@esbuild/linux-riscv64': 0.24.1 + '@esbuild/linux-s390x': 0.24.1 + '@esbuild/linux-x64': 0.24.1 + '@esbuild/netbsd-arm64': 0.24.1 + '@esbuild/netbsd-x64': 0.24.1 + '@esbuild/openbsd-arm64': 0.24.1 + '@esbuild/openbsd-x64': 0.24.1 + '@esbuild/sunos-x64': 0.24.1 + '@esbuild/win32-arm64': 0.24.1 + '@esbuild/win32-ia32': 0.24.1 + '@esbuild/win32-x64': 0.24.1 escalade@3.2.0: {} @@ -14758,6 +14864,19 @@ snapshots: has-symbols: 1.0.3 hasown: 2.0.2 + get-intrinsic@1.2.6: + dependencies: + call-bind-apply-helpers: 1.0.1 + dunder-proto: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + function-bind: 1.1.2 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + get-nonce@1.0.1: {} get-package-type@0.1.0: {} @@ -14829,6 +14948,8 @@ snapshots: dependencies: get-intrinsic: 1.2.4 + gopd@1.2.0: {} + graceful-fs@4.2.11: {} graphemer@1.4.0: {} @@ -14849,6 +14970,8 @@ snapshots: has-symbols@1.0.3: {} + has-symbols@1.1.0: {} + has-tostringtag@1.0.2: dependencies: has-symbols: 1.0.3 @@ -15003,6 +15126,11 @@ snapshots: call-bind: 1.0.7 has-tostringtag: 1.0.2 + is-arguments@1.2.0: + dependencies: + call-bound: 1.0.3 + has-tostringtag: 1.0.2 + is-array-buffer@3.0.4: dependencies: call-bind: 1.0.7 @@ -15110,9 +15238,9 @@ snapshots: dependencies: has-symbols: 1.0.3 - is-typed-array@1.1.13: + is-typed-array@1.1.15: dependencies: - which-typed-array: 1.1.16 + which-typed-array: 1.1.18 is-typedarray@1.0.0: {} @@ -15778,6 +15906,8 @@ snapshots: math-expression-evaluator@1.4.0: {} + math-intrinsics@1.1.0: {} + memoizerific@1.11.3: dependencies: map-or-similar: 1.5.0 @@ -16196,7 +16326,7 @@ snapshots: prosemirror-commands@1.6.2: dependencies: - prosemirror-model: 1.24.0 + prosemirror-model: 1.24.1 prosemirror-state: 1.4.3 prosemirror-transform: 1.10.2 @@ -16204,20 +16334,20 @@ snapshots: dependencies: prosemirror-state: 1.4.3 prosemirror-transform: 1.10.2 - prosemirror-view: 1.37.0 + prosemirror-view: 1.37.1 prosemirror-gapcursor@1.3.2: dependencies: prosemirror-keymap: 1.2.2 - prosemirror-model: 1.24.0 + prosemirror-model: 1.24.1 prosemirror-state: 1.4.3 - prosemirror-view: 1.37.0 + prosemirror-view: 1.37.1 prosemirror-history@1.4.1: dependencies: prosemirror-state: 1.4.3 prosemirror-transform: 1.10.2 - prosemirror-view: 1.37.0 + prosemirror-view: 1.37.1 rope-sequence: 1.3.4 prosemirror-inputrules@1.4.0: @@ -16234,15 +16364,19 @@ snapshots: dependencies: orderedmap: 2.1.1 - prosemirror-paste-rules@2.0.8(prosemirror-model@1.24.0)(prosemirror-state@1.4.3)(prosemirror-view@1.37.0): + prosemirror-model@1.24.1: + dependencies: + orderedmap: 2.1.1 + + prosemirror-paste-rules@2.0.8(prosemirror-model@1.24.1)(prosemirror-state@1.4.3)(prosemirror-view@1.37.1): dependencies: '@babel/runtime': 7.26.0 '@remirror/core-constants': 2.0.2 '@remirror/core-helpers': 3.0.0 escape-string-regexp: 4.0.0 - prosemirror-model: 1.24.0 + prosemirror-model: 1.24.1 prosemirror-state: 1.4.3 - prosemirror-view: 1.37.0 + prosemirror-view: 1.37.1 prosemirror-resizable-view@2.0.15(@remirror/pm@2.0.8)(@types/node@20.17.9)(jsdom@24.1.3): dependencies: @@ -16256,9 +16390,9 @@ snapshots: - '@types/node' - jsdom - prosemirror-schema-list@1.4.1: + prosemirror-schema-list@1.5.0: dependencies: - prosemirror-model: 1.24.0 + prosemirror-model: 1.24.1 prosemirror-state: 1.4.3 prosemirror-transform: 1.10.2 @@ -16268,32 +16402,32 @@ snapshots: prosemirror-transform: 1.10.2 prosemirror-view: 1.37.0 - prosemirror-suggest@2.0.7(prosemirror-model@1.24.0)(prosemirror-state@1.4.3)(prosemirror-view@1.37.0): + prosemirror-suggest@2.0.7(prosemirror-model@1.24.1)(prosemirror-state@1.4.3)(prosemirror-view@1.37.1): dependencies: '@babel/runtime': 7.26.0 '@remirror/core-constants': 2.0.2 '@remirror/core-helpers': 3.0.0 '@remirror/types': 1.0.1 escape-string-regexp: 4.0.0 - prosemirror-model: 1.24.0 + prosemirror-model: 1.24.1 prosemirror-state: 1.4.3 - prosemirror-view: 1.37.0 + prosemirror-view: 1.37.1 prosemirror-tables@1.6.1: dependencies: prosemirror-keymap: 1.2.2 - prosemirror-model: 1.24.0 + prosemirror-model: 1.24.1 prosemirror-state: 1.4.3 prosemirror-transform: 1.10.2 - prosemirror-view: 1.37.0 + prosemirror-view: 1.37.1 - prosemirror-trailing-node@2.0.9(prosemirror-model@1.24.0)(prosemirror-state@1.4.3)(prosemirror-view@1.37.0): + prosemirror-trailing-node@2.0.9(prosemirror-model@1.24.1)(prosemirror-state@1.4.3)(prosemirror-view@1.37.1): dependencies: '@remirror/core-constants': 2.0.2 escape-string-regexp: 4.0.0 - prosemirror-model: 1.24.0 + prosemirror-model: 1.24.1 prosemirror-state: 1.4.3 - prosemirror-view: 1.37.0 + prosemirror-view: 1.37.1 prosemirror-transform@1.10.2: dependencies: @@ -16305,6 +16439,12 @@ snapshots: prosemirror-state: 1.4.3 prosemirror-transform: 1.10.2 + prosemirror-view@1.37.1: + dependencies: + prosemirror-model: 1.24.1 + prosemirror-state: 1.4.3 + prosemirror-transform: 1.10.2 + proxy-from-env@1.1.0: {} psl@1.14.0: @@ -16820,9 +16960,9 @@ snapshots: dependencies: internal-slot: 1.0.7 - storybook@8.4.6: + storybook@8.4.7: dependencies: - '@storybook/core': 8.4.6 + '@storybook/core': 8.4.7 transitivePeerDependencies: - bufferutil - supports-color @@ -17167,10 +17307,10 @@ snapshots: util@0.12.5: dependencies: inherits: 2.0.4 - is-arguments: 1.1.1 + is-arguments: 1.2.0 is-generator-function: 1.0.10 - is-typed-array: 1.1.13 - which-typed-array: 1.1.16 + is-typed-array: 1.1.15 + which-typed-array: 1.1.18 uuid@8.3.2: {} @@ -17359,6 +17499,15 @@ snapshots: gopd: 1.1.0 has-tostringtag: 1.0.2 + which-typed-array@1.1.18: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.3 + for-each: 0.3.3 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + which@1.3.1: dependencies: isexe: 2.0.0 diff --git a/dashboard/public/locales/en.json b/dashboard/public/locales/en.json index 5ddf443..e07df9c 100644 --- a/dashboard/public/locales/en.json +++ b/dashboard/public/locales/en.json @@ -268,6 +268,13 @@ "users-data-usage": "Users Data Usage" }, "hosts": { + "xmux-settings": "Xmux Settings", + "split-http-settings": "Split HTTP Settings", + "no-grpc-header": "No GRPC Header", + "early-data": "Early Data", + "http_headers": "HTTP Headers", + "no-http-headers": "No HTTP headers", + "mux-settings": "Mux Settings", "dns-servers": "DNS Servers", "obfuscation-password": "Obfuscation Password", "server-public-key": "Server public key", @@ -285,6 +292,14 @@ "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" }, + "noise": { + "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", diff --git a/dashboard/src/common/components/alert-card/index.tsx b/dashboard/src/common/components/alert-card/index.tsx index c5fa02a..2fa25e5 100644 --- a/dashboard/src/common/components/alert-card/index.tsx +++ b/dashboard/src/common/components/alert-card/index.tsx @@ -1,20 +1,47 @@ -import { Info, TriangleAlert, OctagonAlert } from 'lucide-react'; +import { Info, OctagonAlert, TriangleAlert } from "lucide-react"; import { Alert, AlertDescription, AlertTitle, -} from '@marzneshin/common/components'; +} from "@marzneshin/common/components"; +import { cn } from "@marzneshin/common/utils"; -export const AlertCard = ({ title, desc, variant = "default" }: { variant?: "warning" | "default" | "destructive", title: string | JSX.Element, desc: string | JSX.Element }) => { - const Icon = variant === "warning" ? TriangleAlert : (variant === "destructive" ? OctagonAlert : Info) +interface AlertCardProps { + variant?: "warning" | "default" | "destructive"; + title: string | JSX.Element; + size?: "wide-full" | "inline"; + desc?: string | JSX.Element; +} + +export const AlertCard = ({ + title, + desc, + variant = "default", + size = "inline", +}: AlertCardProps) => { + const Icon = + variant === "warning" + ? TriangleAlert + : variant === "destructive" + ? OctagonAlert + : Info; return ( - - + +
+ +
{title} - - {desc} - + {desc}
- ) -} - + ); +}; diff --git a/dashboard/src/common/components/form-fields/clearable-text.tsx b/dashboard/src/common/components/form-fields/clearable-text.tsx new file mode 100644 index 0000000..65303a6 --- /dev/null +++ b/dashboard/src/common/components/form-fields/clearable-text.tsx @@ -0,0 +1,71 @@ +import { + Button, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, + Input, +} from "@marzneshin/common/components"; +import { useFormContext } from "react-hook-form"; +import { X } from "lucide-react"; + +export const ClearableTextField = ({ + name, + label, + placeholder = "", +}: { + name: string; + label: string; + placeholder?: string; +}) => { + const form = useFormContext(); + const value = form.watch(name); + + const handleNullify = () => { + form.setValue(name, null); + }; + + const handleRestore = () => { + form.setValue(name, ""); + }; + + return ( + ( + + {label} + +
+ {value === null ? ( +
+ null +
+ ) : ( + + )} + +
+
+ +
+ )} + /> + ); +}; diff --git a/dashboard/src/common/components/form-fields/index.ts b/dashboard/src/common/components/form-fields/index.ts index 264fc59..6774cd8 100644 --- a/dashboard/src/common/components/form-fields/index.ts +++ b/dashboard/src/common/components/form-fields/index.ts @@ -1,2 +1,3 @@ export * from "./date"; export * from "./checkbox"; +export * from "./clearable-text"; diff --git a/dashboard/src/common/components/ui/accordion.tsx b/dashboard/src/common/components/ui/accordion.tsx deleted file mode 100644 index 56d9648..0000000 --- a/dashboard/src/common/components/ui/accordion.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import * as React from "react" -import * as AccordionPrimitive from "@radix-ui/react-accordion" -import { ChevronDown } from "lucide-react" - -import { cn } from "@marzneshin/common/utils" - -const Accordion = AccordionPrimitive.Root - -const AccordionItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -AccordionItem.displayName = "AccordionItem" - -const AccordionTrigger = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - - svg]:rotate-180", - className - )} - {...props} - > - {children} - - - -)) -AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName - -const AccordionContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - -
{children}
-
-)) - -AccordionContent.displayName = AccordionPrimitive.Content.displayName - -export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } diff --git a/dashboard/src/common/components/ui/accordion/content.tsx b/dashboard/src/common/components/ui/accordion/content.tsx new file mode 100644 index 0000000..ea5d10f --- /dev/null +++ b/dashboard/src/common/components/ui/accordion/content.tsx @@ -0,0 +1,18 @@ +import * as React from "react" +import * as AccordionPrimitive from "@radix-ui/react-accordion" + +import { cn } from "@marzneshin/common/utils" + +export const AccordionContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + +
{children}
+
+)) + diff --git a/dashboard/src/common/components/ui/accordion/index.tsx b/dashboard/src/common/components/ui/accordion/index.tsx new file mode 100644 index 0000000..0123cfc --- /dev/null +++ b/dashboard/src/common/components/ui/accordion/index.tsx @@ -0,0 +1,17 @@ +import * as AccordionPrimitive from "@radix-ui/react-accordion" +import { AccordionContent } from "./content"; +import { AccordionTrigger } from "./trigger"; +import { AccordionItem } from "./item"; + +const Accordion = AccordionPrimitive.Root; + +AccordionItem.displayName = "AccordionItem" +AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName +AccordionContent.displayName = AccordionPrimitive.Content.displayName + +export { + Accordion, + AccordionItem, + AccordionTrigger, + AccordionContent +} diff --git a/dashboard/src/common/components/ui/accordion/item.tsx b/dashboard/src/common/components/ui/accordion/item.tsx new file mode 100644 index 0000000..8a31a43 --- /dev/null +++ b/dashboard/src/common/components/ui/accordion/item.tsx @@ -0,0 +1,15 @@ +import * as React from "react" +import * as AccordionPrimitive from "@radix-ui/react-accordion" + +import { cn } from "@marzneshin/common/utils"; + +export const AccordionItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) diff --git a/dashboard/src/common/components/ui/accordion/trigger.tsx b/dashboard/src/common/components/ui/accordion/trigger.tsx new file mode 100644 index 0000000..4c74f5c --- /dev/null +++ b/dashboard/src/common/components/ui/accordion/trigger.tsx @@ -0,0 +1,23 @@ +import * as React from "react"; +import * as AccordionPrimitive from "@radix-ui/react-accordion"; +import { cn } from "@marzneshin/common/utils"; +import { ChevronDown } from "lucide-react"; + +export const AccordionTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + svg]:rotate-180", + className + )} + {...props} + > + {children} + + + +)) diff --git a/dashboard/src/common/components/ui/button/index.tsx b/dashboard/src/common/components/ui/button/index.tsx index 3986677..ef9002c 100644 --- a/dashboard/src/common/components/ui/button/index.tsx +++ b/dashboard/src/common/components/ui/button/index.tsx @@ -1,17 +1,20 @@ -import * as React from "react" -import { Slot } from "@radix-ui/react-slot" -import { cva, type VariantProps } from "class-variance-authority" +import * as React from "react"; +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from "@marzneshin/common/utils" +import { cn } from "@marzneshin/common/utils"; const buttonVariants = cva( "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", { variants: { variant: { - default: "bg-primary text-primary-foreground hover:bg-primary/90", + default: + "bg-primary text-primary-foreground hover:bg-primary/90", destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90", + "secondary-destructive": + "hover:bg-accent text-destructive hover:bg-destructive/20", success: "bg-success text-success-foreground hover:bg-success/80", outline: @@ -32,27 +35,27 @@ const buttonVariants = cva( variant: "default", size: "default", }, - } -) + }, +); export interface ButtonProps extends React.ButtonHTMLAttributes, VariantProps { - asChild?: boolean + asChild?: boolean; } const Button = React.forwardRef( ({ className, variant, size, asChild = false, ...props }, ref) => { - const Comp = asChild ? Slot : "button" + const Comp = asChild ? Slot : "button"; return ( - ) - } -) -Button.displayName = "Button" + ); + }, +); +Button.displayName = "Button"; -export { Button, buttonVariants } +export { Button, buttonVariants }; diff --git a/dashboard/src/libs/dynamic-field/dynamic-field.tsx b/dashboard/src/libs/dynamic-field/dynamic-field.tsx new file mode 100644 index 0000000..642fe34 --- /dev/null +++ b/dashboard/src/libs/dynamic-field/dynamic-field.tsx @@ -0,0 +1,55 @@ +import React from "react"; +import { useFormContext, Controller } from "react-hook-form"; +import { Input, Button, Separator } from "@marzneshin/common/components"; +import { ListX } from "lucide-react"; + +interface DynamicFieldProps { + fieldIndex: number; + parentFieldName: string; + onRemove: () => void; +} + +export const DynamicField: React.FC = ({ + fieldIndex, + parentFieldName, + onRemove, +}) => { + const { control } = useFormContext(); + + return ( +
+
+ ( + + )} + /> + + ( + + )} + /> +
+ +
+ ); +}; diff --git a/dashboard/src/libs/dynamic-field/dynamic-field.type.ts b/dashboard/src/libs/dynamic-field/dynamic-field.type.ts new file mode 100644 index 0000000..bf35443 --- /dev/null +++ b/dashboard/src/libs/dynamic-field/dynamic-field.type.ts @@ -0,0 +1,8 @@ +export interface Field { + name: string; + value: string; +} + +export interface DynamicFieldsForm { + fields: Field[]; +} diff --git a/dashboard/src/libs/dynamic-field/index.ts b/dashboard/src/libs/dynamic-field/index.ts new file mode 100644 index 0000000..104e9fe --- /dev/null +++ b/dashboard/src/libs/dynamic-field/index.ts @@ -0,0 +1,4 @@ +export * from "./dynamic-field" +export * from "./dynamic-field.type" +export * from "./transform-to-dictionary" +export * from "./transform-to-fields" diff --git a/dashboard/src/libs/dynamic-field/transform-to-dictionary.ts b/dashboard/src/libs/dynamic-field/transform-to-dictionary.ts new file mode 100644 index 0000000..52457ca --- /dev/null +++ b/dashboard/src/libs/dynamic-field/transform-to-dictionary.ts @@ -0,0 +1,6 @@ +import { Field } from "./dynamic-field.type"; + +export const transformToFields = (dictionary: { + [key: string]: string; +}): Field[] => + Object.entries(dictionary).map(([name, value]) => ({ name, value })); diff --git a/dashboard/src/libs/dynamic-field/transform-to-fields.ts b/dashboard/src/libs/dynamic-field/transform-to-fields.ts new file mode 100644 index 0000000..64ee245 --- /dev/null +++ b/dashboard/src/libs/dynamic-field/transform-to-fields.ts @@ -0,0 +1,10 @@ +import { Field } from "./dynamic-field.type"; + +export const transformToDictionary = (fields: Field[]) => + fields.reduce( + (acc, field) => { + if (field.name) acc[field.name] = field.value; + return acc; + }, + {} as { [key: string]: string }, + ); diff --git a/dashboard/src/modules/hosts/dialogs/mutation/fields/sni.tsx b/dashboard/src/modules/hosts/dialogs/mutation/fields/early-data.tsx similarity index 55% rename from dashboard/src/modules/hosts/dialogs/mutation/fields/sni.tsx rename to dashboard/src/modules/hosts/dialogs/mutation/fields/early-data.tsx index 6dd440c..f76c552 100644 --- a/dashboard/src/modules/hosts/dialogs/mutation/fields/sni.tsx +++ b/dashboard/src/modules/hosts/dialogs/mutation/fields/early-data.tsx @@ -1,26 +1,19 @@ -import { - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, - Input -} from "@marzneshin/common/components"; +import { FormControl, FormField, FormItem, FormLabel, FormMessage, Input } from "@marzneshin/common/components"; import { useFormContext } from "react-hook-form"; import { useTranslation } from "react-i18next"; -export const SniField = () => { +export const EarlyDataField = () => { const { t } = useTranslation() const form = useFormContext() return ( ( - - {t('SNI')} + + {t('page.hosts.early-data')} - + diff --git a/dashboard/src/modules/hosts/dialogs/mutation/fields/host.tsx b/dashboard/src/modules/hosts/dialogs/mutation/fields/host.tsx deleted file mode 100644 index b8eb8ea..0000000 --- a/dashboard/src/modules/hosts/dialogs/mutation/fields/host.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, - Input, -} from "@marzneshin/common/components"; -import { useFormContext } from "react-hook-form"; -import { useTranslation } from "react-i18next"; - -export const HostField = () => { - const { t } = useTranslation(); - const form = useFormContext(); - return ( - ( - - {t("host")} - - - - - - )} - /> - ); -}; diff --git a/dashboard/src/modules/hosts/dialogs/mutation/fields/http-headers-dynamic-fields/index.tsx b/dashboard/src/modules/hosts/dialogs/mutation/fields/http-headers-dynamic-fields/index.tsx new file mode 100644 index 0000000..72fec39 --- /dev/null +++ b/dashboard/src/modules/hosts/dialogs/mutation/fields/http-headers-dynamic-fields/index.tsx @@ -0,0 +1,71 @@ +import { + AlertCard, + Button, + FormLabel, + ScrollArea, +} from "@marzneshin/common/components"; +import { useFieldArray, useFormContext } from "react-hook-form"; +import { DynamicField } from "@marzneshin/libs/dynamic-field"; +import { useTranslation } from "react-i18next"; +import { ListPlus, Trash } from "lucide-react"; + +// TODO: Default back to previous value +// TODO: Push up animation for adding new headers +// TODO: Remove animation for removing previous headers + +export const HttpHeadersDynamicFields = () => { + const { t } = useTranslation(); + const { control } = useFormContext(); + + const { fields, append, remove, replace } = useFieldArray({ + control, + name: "http_headers", + }); + + return ( +
+ + {t("page.hosts.http_headers")} +
+ + +
+
+ + {fields.length !== 0 ? ( + fields.map((field, index) => ( + remove(index)} + /> + )) + ) : ( + + )} + +
+ ); +}; diff --git a/dashboard/src/modules/hosts/dialogs/mutation/fields/index.ts b/dashboard/src/modules/hosts/dialogs/mutation/fields/index.ts index dcb184b..fa2afec 100644 --- a/dashboard/src/modules/hosts/dialogs/mutation/fields/index.ts +++ b/dashboard/src/modules/hosts/dialogs/mutation/fields/index.ts @@ -1,17 +1,19 @@ -export * from './remark' -export * from './address' -export * from './port' -export * from './path' -export * from './host' -export * from './allow-insecure' -export * from './security' -export * from './sni' -export * from './alpn' -export * from './fingerprint' -export * from './mux' -export * from './fragment' -export * from './weight' -export * from './common-fields' -export * from './mtu' -export * from './dns-servers' -export * from './allowed-ips' +export * from "./remark"; +export * from "./address"; +export * from "./port"; +export * from "./allow-insecure"; +export * from "./security"; +export * from "./alpn"; +export * from "./fingerprint"; +export * from "./mux"; +export * from "./fragment"; +export * from "./weight"; +export * from "./common-fields"; +export * from "./mtu"; +export * from "./dns-servers"; +export * from "./allowed-ips"; +export * from "./splithttp"; +export * from "./early-data"; +export * from "./noise"; +export * from "./http-headers-dynamic-fields"; +export * from "./mux-settings"; diff --git a/dashboard/src/modules/hosts/dialogs/mutation/fields/mux-settings/index.tsx b/dashboard/src/modules/hosts/dialogs/mutation/fields/mux-settings/index.tsx new file mode 100644 index 0000000..1c7f00b --- /dev/null +++ b/dashboard/src/modules/hosts/dialogs/mutation/fields/mux-settings/index.tsx @@ -0,0 +1,169 @@ +import { + CheckboxField, + FormControl, + FormField, + FormItem, + FormLabel, + Input, + Switch, +} from "@marzneshin/common/components"; +import { useFormContext, useWatch } from "react-hook-form"; +import { useTranslation } from "react-i18next"; +import { ProtocolField } from "./protocol"; +import { XudpProxy443Field } from "./xudp-proxy-443"; + +export const MuxSettingsFields = () => { + const { t } = useTranslation(); + const form = useFormContext(); + const mux_settings = useWatch({ name: "mux_settings" }); + const protocol = useWatch({ name: "mux_settings.protocol" }); + const isMuxCool = protocol === "mux_cool"; + const isMuxSettingsDisabled = mux_settings === null; + + const updateFieldValue = (path: string, value: any) => { + form.setValue(path, value === "" ? null : value, { + shouldDirty: true, + shouldValidate: true, + }); + }; + + return ( + ( + <> + + {t("page.hosts.mux-settings")} + + form.setValue( + "mux_settings", + checked + ? { + protocol: "mux_cool", + mux_cool_settings: { + xudp_proxy_443: "reject", + }, + } + : null, + ) + } + /> + + + {!isMuxSettingsDisabled && ( + <> + + + {isMuxCool ? ( +
+ {[ + "concurrency", + "xudp_concurrency", + ].map((fieldName) => ( + ( + + + {t( + `${fieldName.replace(/_/g, " ")}`, + )} + + + + updateFieldValue( + `mux_settings.mux_cool_settings.${fieldName}`, + e.target + .value === + "" + ? null + : Number.parseInt( + e + .target + .value, + ), + ) + } + className="w-full" + /> + + + )} + /> + ))} + +
+ ) : ( +
+
+ {[ + "max_connections", + "max_streams", + "min_streams", + ].map((fieldName) => ( + ( + + + {t( + `${fieldName.replace(/_/g, " ")}`, + )} + + + + updateFieldValue( + `mux_settings.sing_box_mux_settings.${fieldName}`, + e + .target + .value === + "" + ? null + : Number.parseInt( + e + .target + .value, + ), + ) + } + className="w-full" + /> + + + )} + /> + ))} +
+ +
+ )} +
+ + )} + + )} + /> + ); +}; diff --git a/dashboard/src/modules/hosts/dialogs/mutation/fields/mux-settings/protocol.tsx b/dashboard/src/modules/hosts/dialogs/mutation/fields/mux-settings/protocol.tsx new file mode 100644 index 0000000..2115f40 --- /dev/null +++ b/dashboard/src/modules/hosts/dialogs/mutation/fields/mux-settings/protocol.tsx @@ -0,0 +1,48 @@ +import { + FormControl, + FormField, + FormItem, + FormLabel, + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@marzneshin/common/components"; +import { useTranslation } from "react-i18next"; + +export const ProtocolField = ({ + updater, +}: { + updater: (field: string, value: any) => void; +}) => { + const { t } = useTranslation(); + return ( + ( + + {t("protocol")} + + + )} + /> + ); +}; diff --git a/dashboard/src/modules/hosts/dialogs/mutation/fields/mux-settings/xudp-proxy-443.tsx b/dashboard/src/modules/hosts/dialogs/mutation/fields/mux-settings/xudp-proxy-443.tsx new file mode 100644 index 0000000..85d7c0e --- /dev/null +++ b/dashboard/src/modules/hosts/dialogs/mutation/fields/mux-settings/xudp-proxy-443.tsx @@ -0,0 +1,50 @@ +import { + FormControl, + FormField, + FormItem, + FormLabel, + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@marzneshin/common/components"; +import { useTranslation } from "react-i18next"; + +export const XudpProxy443Field = ({ + updater, +}: { + updater: (field: string, value: any) => void; +}) => { + const { t } = useTranslation(); + return ( + ( + + {t("Xudp Proxy 443")} + + + )} + /> + ); +}; diff --git a/dashboard/src/modules/hosts/dialogs/mutation/fields/noise.tsx b/dashboard/src/modules/hosts/dialogs/mutation/fields/noise.tsx new file mode 100644 index 0000000..d1f7c6b --- /dev/null +++ b/dashboard/src/modules/hosts/dialogs/mutation/fields/noise.tsx @@ -0,0 +1,234 @@ +import { + Button, + FormControl, + FormField, + FormItem, + FormLabel, + Input, + Popover, + PopoverContent, + PopoverTrigger, + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@marzneshin/common/components"; +import type { FieldError, FieldErrors } from "react-hook-form"; +import { useFormContext } from "react-hook-form"; +import { useTranslation } from "react-i18next"; +import { ListPlus, MailWarning, TrashIcon } from "lucide-react"; +import type { FC } from "react"; +import { useState } from "react"; +import { noiseTypes } from "../profiles"; + +interface NoiseForm { + noise: Array<{ + type: string; + packet: string; + delay: string; + } | null>; +} + +const NoiseErrorPopover: FC<{ + errors?: { packet?: FieldError; type?: FieldError; delay?: FieldError }; +}> = ({ errors }) => { + const { t } = useTranslation(); + + if (!errors || (!errors.packet && !errors.type && !errors.delay)) + return null; + + return ( + + + + + +
    + {errors.type?.message && ( +
  • + Type: {t(errors.type.message as string)} +
  • + )} + {errors.packet?.message && ( +
  • + Packet: {t(errors.packet.message as string)} +
  • + )} + {errors.delay?.message && ( +
  • + Interval: {t(errors.delay.message as string)} +
  • + )} +
+
+
+ ); +}; + +export const NoiseField = () => { + const { t } = useTranslation(); + const form = useFormContext(); + const [forms, setForms] = useState( + form.getValues("noise") || [], + ); + + const errors = form.formState.errors as FieldErrors; + + const addForm = () => { + if (forms.length < 5) { + const newForms = [ + ...forms, + { type: "rand", packet: "", delay: "" }, + ]; + setForms(newForms); + form.setValue("noise", newForms, { shouldValidate: true }); + } + }; + + const removeForm = (index: number) => { + const updatedForms = forms.filter((_, i) => i !== index); + setForms(updatedForms); + form.setValue("noise", updatedForms, { shouldValidate: true }); + }; + + const enableForm = (index: number) => { + const updatedForms = [...forms]; + updatedForms[index] = { type: "", packet: "", delay: "" }; + setForms(updatedForms); + form.setValue("noise", updatedForms, { shouldValidate: true }); + }; + + return ( +
+ {forms.length > 0 ? ( + forms.map((formData, index) => ( + + {index === 0 && ( + + {t("Noise")} +
+ {errors.noise?.[index] && ( + + )} + {forms.length < 5 && ( + + )} +
+
+ )} + +
+ {formData ? ( + <> + ( + + )} + /> +
+ ( + + )} + /> + ( + + )} + /> +
+ + + ) : ( + + )} +
+
+
+ )) + ) : ( + + + {t("Noise")} + + + + + )} +
+ ); +}; diff --git a/dashboard/src/modules/hosts/dialogs/mutation/fields/path.tsx b/dashboard/src/modules/hosts/dialogs/mutation/fields/path.tsx deleted file mode 100644 index 612a42a..0000000 --- a/dashboard/src/modules/hosts/dialogs/mutation/fields/path.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, - Input, -} from "@marzneshin/common/components"; -import { useFormContext } from "react-hook-form"; -import { useTranslation } from "react-i18next"; - -export const PathField = ({ label }: { label?: string }) => { - const { t } = useTranslation(); - const form = useFormContext(); - return ( - ( - - {label ?? t("path")} - - - - - - )} - /> - ); -}; diff --git a/dashboard/src/modules/hosts/dialogs/mutation/fields/security.tsx b/dashboard/src/modules/hosts/dialogs/mutation/fields/security.tsx index 750fc37..8d4cd60 100644 --- a/dashboard/src/modules/hosts/dialogs/mutation/fields/security.tsx +++ b/dashboard/src/modules/hosts/dialogs/mutation/fields/security.tsx @@ -1,4 +1,5 @@ import { + ClearableTextField, FormControl, FormField, FormItem, @@ -13,7 +14,7 @@ import { import { useEffect, useState } from "react"; import { useFormContext } from "react-hook-form"; import { useTranslation } from "react-i18next"; -import { AllowInsecureField, AlpnField, FingerprintField, SniField } from "."; +import { AllowInsecureField, AlpnField, FingerprintField } from "."; export const SecurityFields = () => { const { t } = useTranslation(); @@ -35,7 +36,10 @@ export const SecurityFields = () => { render={({ field }) => ( {t("security")} - @@ -44,7 +48,9 @@ export const SecurityFields = () => { None TLS - Inbound Default + + Inbound Default + @@ -52,14 +58,14 @@ export const SecurityFields = () => { )} /> {extraSecurity && ( - <> - +
+
- +
)} ); diff --git a/dashboard/src/modules/hosts/dialogs/mutation/fields/splithttp/index.tsx b/dashboard/src/modules/hosts/dialogs/mutation/fields/splithttp/index.tsx new file mode 100644 index 0000000..6dd76f6 --- /dev/null +++ b/dashboard/src/modules/hosts/dialogs/mutation/fields/splithttp/index.tsx @@ -0,0 +1,67 @@ +import { + CheckboxField, + FormField, + FormItem, + FormLabel, + Switch, +} from "@marzneshin/common/components"; +import { useFormContext, useWatch } from "react-hook-form"; +import { useTranslation } from "react-i18next"; +import { SplitHttpModeField } from "./mode"; +import { SplitHttpPaddingBytesField } from "./padding-bytes"; +import { SplitHttpXmuxFields } from "./xmux"; + +export const SplitHttpFields = () => { + const { t } = useTranslation(); + const form = useFormContext(); + const split_http = useWatch({ name: "splithttp_settings" }); + const isSplitHttpDisabled = split_http === null; + + const updateFieldValue = (path: string, value: any) => { + form.setValue(path, value === "" ? null : value, { + shouldDirty: true, + shouldValidate: true, + }); + }; + + return ( + ( + + + {t("split_http")} + + form.setValue( + "splithttp_settings", + checked ? { xmux: {} } : null, + ) + } + /> + + + {!isSplitHttpDisabled && ( + <> +
+ + + +
+ + + )} +
+ )} + /> + ); +}; diff --git a/dashboard/src/modules/hosts/dialogs/mutation/fields/splithttp/mode.tsx b/dashboard/src/modules/hosts/dialogs/mutation/fields/splithttp/mode.tsx new file mode 100644 index 0000000..775ba4a --- /dev/null +++ b/dashboard/src/modules/hosts/dialogs/mutation/fields/splithttp/mode.tsx @@ -0,0 +1,43 @@ +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, + FormControl, + FormField, + FormItem, + FormLabel, +} from "@marzneshin/common/components"; +import { useTranslation } from "react-i18next"; + +export const SplitHttpModeField = ( + { updater }: { updater: (field: string, value: any) => void } +) => { + const { t } = useTranslation(); + return ( + ( + + + {t("Mode")} + + + + )} + /> + ) +} diff --git a/dashboard/src/modules/hosts/dialogs/mutation/fields/splithttp/padding-bytes.tsx b/dashboard/src/modules/hosts/dialogs/mutation/fields/splithttp/padding-bytes.tsx new file mode 100644 index 0000000..7e7a06d --- /dev/null +++ b/dashboard/src/modules/hosts/dialogs/mutation/fields/splithttp/padding-bytes.tsx @@ -0,0 +1,39 @@ +import { + FormControl, + FormField, + FormItem, + FormLabel, + Input, +} from "@marzneshin/common/components"; +import { useTranslation } from "react-i18next"; + + +export const SplitHttpPaddingBytesField = ( + { updater }: { updater: (field: string, value: any) => void } +) => { + const { t } = useTranslation(); + return ( + ( + + + {t("Padding Bytes")} + + + + updater( + "splithttp_settings.padding_bytes", + e.target.value + ) + } + className="w-full" + /> + + + )} + /> + ) +} diff --git a/dashboard/src/modules/hosts/dialogs/mutation/fields/splithttp/xmux.tsx b/dashboard/src/modules/hosts/dialogs/mutation/fields/splithttp/xmux.tsx new file mode 100644 index 0000000..4418db8 --- /dev/null +++ b/dashboard/src/modules/hosts/dialogs/mutation/fields/splithttp/xmux.tsx @@ -0,0 +1,104 @@ +import { + FormControl, + FormField, + FormItem, + FormLabel, + Input, + Switch, +} from "@marzneshin/common/components"; +import { useFormContext, useWatch } from "react-hook-form"; +import { useTranslation } from "react-i18next"; + +export const SplitHttpXmuxFields = () => { + const { t } = useTranslation(); + const form = useFormContext(); + const xmux = useWatch({ name: "splithttp_settings.xmux" }); + const isXMuxDisabled = xmux === null; + + const updateFieldValue = (path: string, value: any) => { + form.setValue(path, value === "" ? null : value, { + shouldDirty: true, + shouldValidate: true, + }); + }; + + return ( +
+ + {t("page.hosts.xmux-settings")} + + form.setValue( + "splithttp_settings.xmux", + checked ? { xmux: {} } : null, + ) + } + /> + + {!isXMuxDisabled && ( +
+ {[ + "max_concurrency", + "max_connections", + "max_reuse_times", + "max_lifetime", + "max_request_times", + ].map((fieldName) => ( + ( + + + {t(`${fieldName.replace(/_/g, " ")}`)} + + + + updateFieldValue( + `splithttp_settings.xmux.${fieldName}`, + e.target.value, + ) + } + className="w-full" + /> + + + )} + /> + ))} + ( + + + Keep Alive Period + + + + updateFieldValue( + `splithttp_settings.xmux.keep_alive_period`, + e.target.value === "" + ? null + : Number.parseInt( + e.target.value, + ), + ) + } + className="w-full" + /> + + + )} + /> +
+ )} +
+ ); +}; diff --git a/dashboard/src/modules/hosts/dialogs/mutation/index.tsx b/dashboard/src/modules/hosts/dialogs/mutation/index.tsx index 175c2b1..8dbb8f9 100644 --- a/dashboard/src/modules/hosts/dialogs/mutation/index.tsx +++ b/dashboard/src/modules/hosts/dialogs/mutation/index.tsx @@ -1,38 +1,59 @@ import { type FC, useEffect } from "react"; import { - DialogTitle, - DialogContent, + Button, Dialog, + DialogContent, DialogHeader, + DialogTitle, Form, - Button, ScrollArea, } from "@marzneshin/common/components"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { useTranslation } from "react-i18next"; import { + type HostWithProfileSchemaType, + type HostWithProfileType, useHostsCreationMutation, useHostsUpdateMutation, - type HostWithProfileType, - type HostWithProfileSchemaType } from "@marzneshin/modules/hosts"; -import { useDialog, type MutationDialogProps } from "@marzneshin/common/hooks"; +import { type MutationDialogProps, useDialog } from "@marzneshin/common/hooks"; import { ProtocolType } from "@marzneshin/modules/inbounds"; import { useProfileStrategy } from "./profiles"; +import { + transformToDictionary, + transformToFields, +} from "@marzneshin/libs/dynamic-field"; -interface HostMutationDialogProps extends MutationDialogProps { +interface HostMutationDialogProps + extends MutationDialogProps { inboundId?: number; protocol?: ProtocolType; } const transformFormValue = (values: any) => { - if (!(values.alpn && values.fingerprint)) - return values; + if (!(values.alpn && values.fingerprint)) return values; const port = values.port === "" ? null : values.port; const alpn = values.alpn === "none" ? "" : values.alpn; const fingerprint = values.fingerprint === "none" ? "" : values.fingerprint; - return { ...values, alpn, fingerprint, port }; + const http_headers = values.http_headers; + return { ...values, alpn, fingerprint, port, http_headers }; +}; + +const transformFormValueHttpHeaders = (values: any) => { + const http_headers = values.http_headers + ? transformToDictionary(values.http_headers) + : undefined; + return { ...values, http_headers }; +}; + +const formMutationToAPIAdapter = (values: HostWithProfileType) => { + if (values.http_headers) + return { + ...values, + http_headers: transformToFields(values.http_headers), + }; + return values; }; export const HostsMutationDialog: FC = ({ @@ -42,17 +63,21 @@ export const HostsMutationDialog: FC = ({ protocol, }) => { const [open, onOpenChange] = useDialog(true); - const [Schema, ProfileFields, defaultValue] = useProfileStrategy(protocol) + const [Schema, ProfileFields, defaultValue] = useProfileStrategy(protocol); const form = useForm({ - defaultValues: entity ? entity : defaultValue, + defaultValues: entity ? formMutationToAPIAdapter(entity) : defaultValue, resolver: zodResolver(Schema), }); const updateMutation = useHostsUpdateMutation(); const createMutation = useHostsCreationMutation(); const { t } = useTranslation(); + // TODO: Refactor value transformation for api using of + // function composition to pipes and port-adapter patterns. + // - [ ] ALPN and fingerprint + // - [ ] HTTP headers (duplex adaptation) const submit = (values: HostWithProfileSchemaType) => { - const host = transformFormValue(values); + const host = transformFormValueHttpHeaders(transformFormValue(values)); if (entity && entity.id !== undefined) { updateMutation.mutate({ hostId: entity.id, host }); onOpenChange(false); @@ -67,7 +92,7 @@ export const HostsMutationDialog: FC = ({ }, [open, onClose]); useEffect(() => { - if (entity) form.reset(entity); + if (entity) form.reset(formMutationToAPIAdapter(entity)); else form.reset(defaultValue); }, [entity, form, open, defaultValue]); diff --git a/dashboard/src/modules/hosts/dialogs/mutation/profiles/general/default.ts b/dashboard/src/modules/hosts/dialogs/mutation/profiles/general/default.ts index 916d0a0..4fab419 100644 --- a/dashboard/src/modules/hosts/dialogs/mutation/profiles/general/default.ts +++ b/dashboard/src/modules/hosts/dialogs/mutation/profiles/general/default.ts @@ -1,18 +1,20 @@ import { GeneralSchemaType } from "./schema"; export const generalProfileDefaultValue: GeneralSchemaType = { - host: '', - sni: '', + host: null, + sni: null, port: 8080, - path: '', - address: '', - remark: '', + path: null, + address: "", + remark: "", fragment: null, - mux: false, - security: 'inbound_default', - alpn: "", + splithttp_settings: null, + early_data: null, + http_headers: null, + mux_settings: null, + security: "inbound_default", + alpn: "none", allowinsecure: false, - fingerprint: "", + fingerprint: "none", is_disabled: false, }; - diff --git a/dashboard/src/modules/hosts/dialogs/mutation/profiles/general/index.tsx b/dashboard/src/modules/hosts/dialogs/mutation/profiles/general/index.tsx index fdbaf9f..7467126 100644 --- a/dashboard/src/modules/hosts/dialogs/mutation/profiles/general/index.tsx +++ b/dashboard/src/modules/hosts/dialogs/mutation/profiles/general/index.tsx @@ -1,41 +1,69 @@ import { CommonFields, - HostField, - MuxField, + EarlyDataField, FragmentField, - PathField, + HttpHeadersDynamicFields, + MuxSettingsFields, + NoiseField, SecurityFields, + SplitHttpFields, } from "../../fields"; import { Accordion, - HStack + ClearableTextField, + HStack, } from "@marzneshin/common/components"; -import { SettingSection } from "@marzneshin/modules/hosts" +import { SettingSection } from "@marzneshin/modules/hosts"; import { useTranslation } from "react-i18next"; export const GeneralProfileFields = () => { const { t } = useTranslation(); + return (
- + - - + + + + + + + - + - + + + + - +
- ) -} + ); +}; export * from "./schema"; +export * from "./split-http-settings.schema"; export * from "./default"; diff --git a/dashboard/src/modules/hosts/dialogs/mutation/profiles/general/mux-settings.schema.ts b/dashboard/src/modules/hosts/dialogs/mutation/profiles/general/mux-settings.schema.ts new file mode 100644 index 0000000..b161d26 --- /dev/null +++ b/dashboard/src/modules/hosts/dialogs/mutation/profiles/general/mux-settings.schema.ts @@ -0,0 +1,26 @@ +import { z } from "zod"; + +export const MuxSettingsSchema = z.object({ + protocol: z + .enum(["mux_cool", "h2mux", "yamux", "smux"]) + .default("mux_cool"), + sing_box_mux_settings: z + .object({ + max_connections: z.number().nullable().optional(), + max_streams: z.number().nullable().optional(), + min_streams: z.number().nullable().optional(), + padding: z.boolean().nullable().optional(), + }) + .nullable() + .optional(), + mux_cool_settings: z + .object({ + concurrency: z.number().nullable().optional(), + xudp_concurrency: z.number().nullable().optional(), + xudp_proxy_443: z + .enum(["reject", "allow", "skip"]) + .default("reject"), + }) + .nullable() + .optional(), +}); diff --git a/dashboard/src/modules/hosts/dialogs/mutation/profiles/general/schema.ts b/dashboard/src/modules/hosts/dialogs/mutation/profiles/general/schema.ts index a1ebd51..6fb63af 100644 --- a/dashboard/src/modules/hosts/dialogs/mutation/profiles/general/schema.ts +++ b/dashboard/src/modules/hosts/dialogs/mutation/profiles/general/schema.ts @@ -1,5 +1,7 @@ import { z } from "zod"; import { HostSchema, TlsSchema } from "@marzneshin/modules/hosts"; +import { SplitHttpSettingsSchema } from "./split-http-settings.schema"; +import { MuxSettingsSchema } from "./mux-settings.schema"; import i18n from "i18next"; const numberInterval = (v: string | null | undefined) => { @@ -17,57 +19,88 @@ export const alpnOptions = [ "h3,h2", "http/1.1", "h2,http/1.1", - "h3,h2,http/1.1" -] + "h3,h2,http/1.1", +]; + +export const noiseTypes = ["rand", "str", "base64"] as const; + +const FragmentSchema = z.object({ + interval: z + .string() + .refine( + (v) => numberInterval(v), + i18n.t("page.hosts.fragment.interval-error"), + ), + length: z + .string() + .refine( + (v) => numberInterval(v), + i18n.t("page.hosts.fragment.length-error"), + ), + packets: z + .string() + .refine( + (v) => packetsInterval(v), + i18n.t("page.hosts.fragment.packets-error"), + ), +}); + +const NoiseSchema = z.array( + z.object({ + delay: z + .string() + .refine( + (v) => numberInterval(v), + i18n.t("page.hosts.noise.delay-error"), + ), + type: z.enum(noiseTypes).default("rand"), + packet: z.string(), + }), +); + +const FingerprintSchema = z.enum([ + "", + "none", + "chrome", + "firefox", + "safari", + "ios", + "android", + "edge", + "360", + "qq", + "random", + "randomized", +]); export const GeneralSchema = HostSchema.merge(TlsSchema).extend({ - path: z.string().nullable().optional(), + path: z.string().nullable().optional().nullish().default(null), host: z.string().nullable().optional(), - mux: z.boolean().nullable().optional(), - fragment: z - .object({ - interval: z - .string() - .refine( - (v) => numberInterval(v), - i18n.t("page.hosts.fragment.interval-error"), - ), - length: z - .string() - .refine( - (v) => numberInterval(v), - i18n.t("page.hosts.fragment.length-error"), - ), - packets: z - .string() - .refine( - (v) => packetsInterval(v), - i18n.t("page.hosts.fragment.packets-error"), - ), - }) - .nullable() - .optional(), + mux_settings: MuxSettingsSchema.nullable().optional().default(null), + http_headers: z.any().nullable().optional().default(null), + fragment: FragmentSchema.nullable().optional().default(null), + fingerprint: FingerprintSchema.optional().default("none"), + splithttp_settings: SplitHttpSettingsSchema.nullable() + .optional() + .default(null), + noise: NoiseSchema.nullable().optional(), + early_data: z.preprocess( + (val) => + val === "" || val === undefined || val === null + ? null + : Number(val), + z.union([ + z + .number() + .int() + .gte(1, "Port must be more than 1") + .lte(65535, "Port cannot be more than 65535"), + z.null(), + ]), + ), security: z .enum(["inbound_default", "none", "tls"]) .default("inbound_default"), - fingerprint: z - .enum([ - "", - "none", - "chrome", - "firefox", - "safari", - "ios", - "android", - "edge", - "360", - "qq", - "random", - "randomized", - ]) - .optional() - .default("none"), }); - export type GeneralSchemaType = z.infer; diff --git a/dashboard/src/modules/hosts/dialogs/mutation/profiles/general/split-http-settings.schema.ts b/dashboard/src/modules/hosts/dialogs/mutation/profiles/general/split-http-settings.schema.ts new file mode 100644 index 0000000..eb54a14 --- /dev/null +++ b/dashboard/src/modules/hosts/dialogs/mutation/profiles/general/split-http-settings.schema.ts @@ -0,0 +1,13 @@ +import { z } from "zod"; +import { XMuxSettingsSchema } from "./x-mux-settings.schema"; + +export const splitHttpSettingsModes = ["auto", "packet-up", "stream-up", "stream-one"] + +export const SplitHttpSettingsSchema = z.object({ + mode: z.enum(["auto", "packet-up", "stream-up", "stream-one"]).nullable().optional(), + no_grpc_header: z.boolean().nullable().optional(), + padding_bytes: z.string().nullable().optional(), + xmux: XMuxSettingsSchema.nullable().optional(), + max_retries: z.number().nullable().optional(), + timeout: z.string().nullable().optional(), +}); diff --git a/dashboard/src/modules/hosts/dialogs/mutation/profiles/general/x-mux-settings.schema.ts b/dashboard/src/modules/hosts/dialogs/mutation/profiles/general/x-mux-settings.schema.ts new file mode 100644 index 0000000..21d0d91 --- /dev/null +++ b/dashboard/src/modules/hosts/dialogs/mutation/profiles/general/x-mux-settings.schema.ts @@ -0,0 +1,10 @@ +import { z } from "zod"; + +export const XMuxSettingsSchema = z.object({ + max_concurrency: z.string().nullable().optional(), + max_connections: z.string().nullable().optional(), + max_reuse_times: z.string().nullable().optional(), + max_lifetime: z.string().nullable().optional(), + max_request_times: z.string().nullable().optional(), + keep_alive_period: z.number().nullable().optional(), +}); diff --git a/dashboard/src/modules/hosts/dialogs/mutation/profiles/hysteria2/index.tsx b/dashboard/src/modules/hosts/dialogs/mutation/profiles/hysteria2/index.tsx index ee6dc9a..bd8833a 100644 --- a/dashboard/src/modules/hosts/dialogs/mutation/profiles/hysteria2/index.tsx +++ b/dashboard/src/modules/hosts/dialogs/mutation/profiles/hysteria2/index.tsx @@ -1,13 +1,6 @@ -import { - CommonFields, - PathField, -} from "../../fields"; -import { - TlsFields -} from "../../sections"; -import { - Accordion, -} from "@marzneshin/common/components"; +import { CommonFields } from "../../fields"; +import { TlsFields } from "../../sections"; +import { Accordion, ClearableTextField } from "@marzneshin/common/components"; import { useTranslation } from "react-i18next"; export const Hysteria2ProfileFields = () => { @@ -15,13 +8,16 @@ export const Hysteria2ProfileFields = () => { return (
- +
- ) -} + ); +}; export * from "./schema"; export * from "./default"; diff --git a/dashboard/src/modules/hosts/dialogs/mutation/profiles/profiles.ts b/dashboard/src/modules/hosts/dialogs/mutation/profiles/profiles.ts new file mode 100644 index 0000000..b2db6ee --- /dev/null +++ b/dashboard/src/modules/hosts/dialogs/mutation/profiles/profiles.ts @@ -0,0 +1,56 @@ +import { + GeneralSchema, + GeneralProfileFields, + generalProfileDefaultValue, +} from "./general"; +import { + WireguardSchema, + WireguardProfileFields, + wireguardProfileDefaultValue, +} from "./wireguard"; +import { + Hysteria2Schema, + Hysteria2ProfileFields, + hysteria2ProfileDefaultValue, +} from "./hysteria2"; +import { + ShadowTlsSchema, + ShadowTlsProfileFields, + shadowTlsProfileDefaultValue, +} from "./shadowtls"; +import { TuicSchema, TuicProfileFields, tuicProfileDefaultValue } from "./tuic"; +import { FC } from "react"; +import { z } from "zod"; +import { ProtocolType } from "@marzneshin/modules/inbounds"; + +export type ProfileConfig = [z.ZodTypeAny, FC, object]; + +export const generalProfile: ProfileConfig = [ + GeneralSchema, + GeneralProfileFields, + generalProfileDefaultValue, +]; + +export const profileByProtocol: Record = { + vless: generalProfile, + vmess: generalProfile, + trojan: generalProfile, + shadowsocks: generalProfile, + shadowsocks2022: generalProfile, + wireguard: [ + WireguardSchema, + WireguardProfileFields, + wireguardProfileDefaultValue, + ], + hysteria2: [ + Hysteria2Schema, + Hysteria2ProfileFields, + hysteria2ProfileDefaultValue, + ], + shadowtls: [ + ShadowTlsSchema, + ShadowTlsProfileFields, + shadowTlsProfileDefaultValue, + ], + tuic: [TuicSchema, TuicProfileFields, tuicProfileDefaultValue], +}; diff --git a/dashboard/src/modules/hosts/dialogs/mutation/profiles/use-profile-strategy.ts b/dashboard/src/modules/hosts/dialogs/mutation/profiles/use-profile-strategy.ts index f146f63..d07646b 100644 --- a/dashboard/src/modules/hosts/dialogs/mutation/profiles/use-profile-strategy.ts +++ b/dashboard/src/modules/hosts/dialogs/mutation/profiles/use-profile-strategy.ts @@ -1,36 +1,13 @@ import { ProtocolType } from "@marzneshin/modules/inbounds"; -import { FC } from "react"; -import { GeneralSchema, GeneralProfileFields, generalProfileDefaultValue } from "./general"; -import { WireguardSchema, WireguardProfileFields, wireguardProfileDefaultValue } from "./wireguard"; -import { Hysteria2Schema, Hysteria2ProfileFields, hysteria2ProfileDefaultValue } from "./hysteria2"; -import { ShadowTlsSchema, ShadowTlsProfileFields, shadowTlsProfileDefaultValue } from "./shadowtls"; -import { TuicSchema, TuicProfileFields, tuicProfileDefaultValue } from "./tuic"; - -type ProfileSchema = typeof GeneralSchema | typeof WireguardSchema | typeof Hysteria2Schema | typeof TuicSchema; -type ProfileFields = FC; +import { generalProfile, profileByProtocol, ProfileConfig } from "./profiles"; /** - * @param protocol - Inbound protocol of host - * @returns [schema, fields, default values] - An array of schema, profile fields, and their default + * @param {ProtocolType} protocol - Inbound protocol of host + * @returns {ProfileConfig} config - An array of schema, profile fields, and their default + * @default *General Profile* if no protocol is specified */ -export const useProfileStrategy = ( - protocol?: ProtocolType -): [ProfileSchema, ProfileFields, object] => { - if (!protocol) - return [GeneralSchema, GeneralProfileFields, generalProfileDefaultValue] - - const profileMap: { [key in ProtocolType]: [ProfileSchema, ProfileFields, object] } = { - vless: [GeneralSchema, GeneralProfileFields, generalProfileDefaultValue], - vmess: [GeneralSchema, GeneralProfileFields, generalProfileDefaultValue], - trojan: [GeneralSchema, GeneralProfileFields, generalProfileDefaultValue], - shadowsocks: [GeneralSchema, GeneralProfileFields, generalProfileDefaultValue], - shadowsocks2022: [GeneralSchema, GeneralProfileFields, generalProfileDefaultValue], - wireguard: [WireguardSchema, WireguardProfileFields, wireguardProfileDefaultValue], - hysteria2: [Hysteria2Schema, Hysteria2ProfileFields, hysteria2ProfileDefaultValue], - shadowtls: [ShadowTlsSchema, ShadowTlsProfileFields, shadowTlsProfileDefaultValue], - tuic: [TuicSchema, TuicProfileFields, tuicProfileDefaultValue], - }; - - const [Schema, Fields, defaultValues] = profileMap[protocol] || [GeneralSchema, GeneralProfileFields, generalProfileDefaultValue]; - return [Schema, Fields, defaultValues]; +export const useProfileStrategy = (protocol?: ProtocolType): ProfileConfig => { + return protocol && profileByProtocol[protocol] + ? profileByProtocol[protocol] + : generalProfile; }; diff --git a/dashboard/src/modules/hosts/dialogs/mutation/profiles/wireguard/index.tsx b/dashboard/src/modules/hosts/dialogs/mutation/profiles/wireguard/index.tsx index 02ee567..2c6305c 100644 --- a/dashboard/src/modules/hosts/dialogs/mutation/profiles/wireguard/index.tsx +++ b/dashboard/src/modules/hosts/dialogs/mutation/profiles/wireguard/index.tsx @@ -1,15 +1,15 @@ import { + AllowedIpsField, CommonFields, - MtuField, DNSServersField, - AllowedIpsField, - PathField, + MtuField, } from "../../fields"; import { Accordion, - HStack + ClearableTextField, + HStack, } from "@marzneshin/common/components"; -import { SettingSection } from "@marzneshin/modules/hosts" +import { SettingSection } from "@marzneshin/modules/hosts"; import { useTranslation } from "react-i18next"; export const WireguardProfileFields = () => { @@ -20,7 +20,10 @@ export const WireguardProfileFields = () => { - + @@ -28,8 +31,8 @@ export const WireguardProfileFields = () => { - ) -} + ); +}; export * from "./schema"; export * from "./default"; diff --git a/dashboard/src/modules/hosts/dialogs/mutation/sections/tls-fields.tsx b/dashboard/src/modules/hosts/dialogs/mutation/sections/tls-fields.tsx index 15e5515..de8e43f 100644 --- a/dashboard/src/modules/hosts/dialogs/mutation/sections/tls-fields.tsx +++ b/dashboard/src/modules/hosts/dialogs/mutation/sections/tls-fields.tsx @@ -1,15 +1,19 @@ -import { AllowInsecureField, AlpnField, SniField } from "../fields"; +import { AllowInsecureField, AlpnField } from "../fields"; import { SettingSection } from "@marzneshin/modules/hosts/components"; import { useTranslation } from "react-i18next"; -import { FC } from 'react' +import { FC } from "react"; +import { ClearableTextField } from "@marzneshin/common/components"; export const TlsFields: FC = () => { const { t } = useTranslation(); return ( - - + + - ) -} + ); +}; diff --git a/dashboard/src/modules/hosts/domain/host-with-profile.type.ts b/dashboard/src/modules/hosts/domain/host-with-profile.type.ts index 5fedf45..7717faf 100644 --- a/dashboard/src/modules/hosts/domain/host-with-profile.type.ts +++ b/dashboard/src/modules/hosts/domain/host-with-profile.type.ts @@ -2,17 +2,21 @@ import { GeneralSchemaType, WireguardSchemaType, Hysteria2SchemaType, - TuicSchemaType + TuicSchemaType, } from "../dialogs/mutation/profiles"; import { HostSchemaType } from "./host"; import { ProtocolType } from "@marzneshin/modules/inbounds"; export type HostWithProfileSchemaType = - HostSchemaType + | HostSchemaType | GeneralSchemaType | WireguardSchemaType | Hysteria2SchemaType | TuicSchemaType; - -export type HostWithProfileType = HostWithProfileSchemaType & { id?: number, inboundId?: number, protocol: ProtocolType }; +export type HostWithProfileType = HostWithProfileSchemaType & { + id?: number; + inboundId?: number; + protocol: ProtocolType; + http_headers?: { [key: string]: string }; +}; diff --git a/dashboard/src/routes/_dashboard/hosts/$hostId.tsx b/dashboard/src/routes/_dashboard/hosts/$hostId.tsx index dd5e785..00b8305 100644 --- a/dashboard/src/routes/_dashboard/hosts/$hostId.tsx +++ b/dashboard/src/routes/_dashboard/hosts/$hostId.tsx @@ -12,6 +12,7 @@ import { Suspense } from "react"; import { AlertDialog, AlertDialogContent, + AlertDialogTitle, Loading } from "@marzneshin/common/components"; @@ -44,7 +45,11 @@ export const Route = createFileRoute('/_dashboard/hosts/$hostId')({ }, errorComponent: () => ( - Host not found + + + Host not found + + ), component: HostProvider, diff --git a/requirements.txt b/requirements.txt index 85e70d0..b2de272 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,7 +22,7 @@ aiohttp==3.10.2 xxhash==3.4.1 grpcio==1.65.4 psycopg==3.1.18 -v2share==0.1.0b27 +v2share==0.1.0b30 PyJWT~=2.8.0 PyNaCl==1.5.0 starlette==0.40.0