gitscope
← marzneshin / Diff · 4056c66 → 4056c66
Changes 4056c66..4056c66
Patch commit
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", "<missing>")}
+ 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 "<missing>"})
+
+ 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 (
- <Alert variant={variant}>
- <Icon className="mr-2" />
+ <Alert
+ variant={variant}
+ className={cn({
+ "flex flex-col justify-center items-center max-h-full h-[5.8rem] w-full gap-2 border-0":
+ size === "wide-full",
+ })}
+ >
+ <div>
+ <Icon
+ className={cn("size-8", {
+ "": (size = "inline"),
+ })}
+ />
+ </div>
<AlertTitle className="font-semibold">{title}</AlertTitle>
- <AlertDescription>
- {desc}
- </AlertDescription>
+ <AlertDescription>{desc}</AlertDescription>
</Alert>
- )
-}
-
+ );
+};
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 (
+ <FormField
+ control={form.control}
+ name={name}
+ render={({ field }) => (
+ <FormItem className="w-full">
+ <FormLabel>{label}</FormLabel>
+ <FormControl>
+ <div className="relative w-full flex items-center gap-2">
+ {value === null ? (
+ <div className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium">
+ null
+ </div>
+ ) : (
+ <Input placeholder={placeholder} {...field} />
+ )}
+ <Button
+ type="button"
+ aria-label="clear"
+ variant="ghost"
+ size="icon"
+ onClick={
+ value === null
+ ? handleRestore
+ : handleNullify
+ }
+ className="absolute hover:fg-destructive-background right-1 top-1/2 -translate-y-1/2 h-7"
+ >
+ <X className="h-4 w-4 " />
+ <span className="sr-only">Clear</span>
+ </Button>
+ </div>
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ );
+};
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<typeof AccordionPrimitive.Item>,
- React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
->(({ className, ...props }, ref) => (
- <AccordionPrimitive.Item
- ref={ref}
- className={cn("border-b", className)}
- {...props}
- />
-))
-AccordionItem.displayName = "AccordionItem"
-
-const AccordionTrigger = React.forwardRef<
- React.ElementRef<typeof AccordionPrimitive.Trigger>,
- React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
->(({ className, children, ...props }, ref) => (
- <AccordionPrimitive.Header className="flex">
- <AccordionPrimitive.Trigger
- ref={ref}
- className={cn(
- "flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
- className
- )}
- {...props}
- >
- {children}
- <ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
- </AccordionPrimitive.Trigger>
- </AccordionPrimitive.Header>
-))
-AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
-
-const AccordionContent = React.forwardRef<
- React.ElementRef<typeof AccordionPrimitive.Content>,
- React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
->(({ className, children, ...props }, ref) => (
- <AccordionPrimitive.Content
- ref={ref}
- className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
- {...props}
- >
- <div className={cn("pb-4 pt-0", className)}>{children}</div>
- </AccordionPrimitive.Content>
-))
-
-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<typeof AccordionPrimitive.Content>,
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
+>(({ className, children, ...props }, ref) => (
+ <AccordionPrimitive.Content
+ ref={ref}
+ className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
+ {...props}
+ >
+ <div className={cn("pb-4 pt-0", className)}>{children}</div>
+ </AccordionPrimitive.Content>
+))
+
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<typeof AccordionPrimitive.Item>,
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
+>(({ className, ...props }, ref) => (
+ <AccordionPrimitive.Item
+ ref={ref}
+ className={cn("border-b", className)}
+ {...props}
+ />
+))
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<typeof AccordionPrimitive.Trigger>,
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
+>(({ className, children, ...props }, ref) => (
+ <AccordionPrimitive.Header className="flex">
+ <AccordionPrimitive.Trigger
+ ref={ref}
+ className={cn(
+ "flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
+ className
+ )}
+ {...props}
+ >
+ {children}
+ <ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
+ </AccordionPrimitive.Trigger>
+ </AccordionPrimitive.Header>
+))
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<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
- asChild?: boolean
+ asChild?: boolean;
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
- const Comp = asChild ? Slot : "button"
+ const Comp = asChild ? Slot : "button";
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
- )
- }
-)
-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<DynamicFieldProps> = ({
+ fieldIndex,
+ parentFieldName,
+ onRemove,
+}) => {
+ const { control } = useFormContext();
+
+ return (
+ <div className="hstack gap-2 mt-1">
+ <div className="hstack gap-0 items-center border-ghost border-2 rounded-md">
+ <Controller
+ name={`${parentFieldName}.${fieldIndex}.name`}
+ control={control}
+ render={({ field }) => (
+ <Input
+ {...field}
+ className="border-none bg-transparent w-2/5 focus-visible:ring-offset-0 focus-visible:ring-0 text-end"
+ placeholder="Header Name"
+ />
+ )}
+ />
+ <Separator className="h-2/4" orientation="vertical" />
+ <Controller
+ name={`${parentFieldName}.${fieldIndex}.value`}
+ control={control}
+ render={({ field }) => (
+ <Input
+ {...field}
+ className="border-none bg-transparent w-3/5 focus-visible:ring-0 focus-visible:ring-offset-0 text-start"
+ placeholder="Header Value"
+ />
+ )}
+ />
+ </div>
+ <Button
+ variant="secondary-destructive"
+ size="icon"
+ onClick={onRemove}
+ >
+ <ListX />
+ </Button>
+ </div>
+ );
+};
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 (
<FormField
control={form.control}
- name="sni"
+ name="early_data"
render={({ field }) => (
- <FormItem >
- <FormLabel>{t('SNI')}</FormLabel>
+ <FormItem className="w-full">
+ <FormLabel>{t('page.hosts.early-data')}</FormLabel>
<FormControl>
- <Input {...field} />
+ <Input type="number" {...field} />
</FormControl>
<FormMessage />
</FormItem>
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 (
- <FormField
- control={form.control}
- name="host"
- render={({ field }) => (
- <FormItem className="w-full">
- <FormLabel>{t("host")}</FormLabel>
- <FormControl>
- <Input {...field} />
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
- );
-};
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 (
+ <div className="mt-2 flex flex-col gap-2">
+ <FormLabel className="hstack justify-between items-center">
+ {t("page.hosts.http_headers")}
+ <div className="hstack gap-1 items-center [*>button]:p-1">
+ <Button
+ variant="secondary-destructive"
+ size="icon"
+ disabled={!fields.length}
+ className="p-1"
+ onClick={() => replace([])}
+ >
+ <Trash />
+ </Button>
+ <Button
+ variant="ghost"
+ size="icon"
+ className="p-1"
+ onClick={(e) => {
+ e.preventDefault();
+ append({ name: "", value: "" });
+ }}
+ >
+ <ListPlus />
+ </Button>
+ </div>
+ </FormLabel>
+ <ScrollArea className="p-2 border-2 h-[7rem] max-h-[10rem] bg-background grid grid-cols-1 gap-2 rounded-md">
+ {fields.length !== 0 ? (
+ fields.map((field, index) => (
+ <DynamicField
+ key={field.id}
+ fieldIndex={index}
+ parentFieldName="http_headers"
+ onRemove={() => remove(index)}
+ />
+ ))
+ ) : (
+ <AlertCard
+ size="wide-full"
+ title={t("page.hosts.no-http-headers")}
+ ></AlertCard>
+ )}
+ </ScrollArea>
+ </div>
+ );
+};
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 (
+ <FormField
+ control={form.control}
+ name="mux_settings"
+ render={() => (
+ <>
+ <FormLabel className="flex items-center justify-between">
+ {t("page.hosts.mux-settings")}
+ <Switch
+ checked={!isMuxSettingsDisabled}
+ onCheckedChange={(checked) =>
+ form.setValue(
+ "mux_settings",
+ checked
+ ? {
+ protocol: "mux_cool",
+ mux_cool_settings: {
+ xudp_proxy_443: "reject",
+ },
+ }
+ : null,
+ )
+ }
+ />
+ </FormLabel>
+
+ {!isMuxSettingsDisabled && (
+ <>
+ <ProtocolField updater={updateFieldValue} />
+ <FormItem className="my-2 w-full">
+ {isMuxCool ? (
+ <div className="grid grid-cols-2 w-full gap-3">
+ {[
+ "concurrency",
+ "xudp_concurrency",
+ ].map((fieldName) => (
+ <FormField
+ key={fieldName}
+ name={`mux_settings.mux_cool_settings.${fieldName}`}
+ render={({ field }) => (
+ <FormItem className="w-full">
+ <FormLabel className="flex items-center justify-between capitalize">
+ {t(
+ `${fieldName.replace(/_/g, " ")}`,
+ )}
+ </FormLabel>
+ <FormControl>
+ <Input
+ value={
+ field.value
+ }
+ type="number"
+ onChange={(e) =>
+ updateFieldValue(
+ `mux_settings.mux_cool_settings.${fieldName}`,
+ e.target
+ .value ===
+ ""
+ ? null
+ : Number.parseInt(
+ e
+ .target
+ .value,
+ ),
+ )
+ }
+ className="w-full"
+ />
+ </FormControl>
+ </FormItem>
+ )}
+ />
+ ))}
+ <XudpProxy443Field
+ updater={updateFieldValue}
+ />
+ </div>
+ ) : (
+ <div className="flex flex-col w-full">
+ <div className="grid grid-cols-2 gap-3 w-full">
+ {[
+ "max_connections",
+ "max_streams",
+ "min_streams",
+ ].map((fieldName) => (
+ <FormField
+ key={fieldName}
+ name={`mux_settings.sing_box_mux_settings.${fieldName}`}
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel className="flex items-center justify-between capitalize">
+ {t(
+ `${fieldName.replace(/_/g, " ")}`,
+ )}
+ </FormLabel>
+ <FormControl>
+ <Input
+ value={
+ field.value
+ }
+ type="number"
+ onChange={(
+ e,
+ ) =>
+ updateFieldValue(
+ `mux_settings.sing_box_mux_settings.${fieldName}`,
+ e
+ .target
+ .value ===
+ ""
+ ? null
+ : Number.parseInt(
+ e
+ .target
+ .value,
+ ),
+ )
+ }
+ className="w-full"
+ />
+ </FormControl>
+ </FormItem>
+ )}
+ />
+ ))}
+ </div>
+ <CheckboxField
+ name="mux_settings.sing_box_mux_settings.padding"
+ label={t("page.hosts.padding")}
+ />
+ </div>
+ )}
+ </FormItem>
+ </>
+ )}
+ </>
+ )}
+ />
+ );
+};
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 (
+ <FormField
+ name="mux_settings.protocol"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>{t("protocol")}</FormLabel>
+ <Select
+ onValueChange={(value) =>
+ updater("mux_settings.protocol", value)
+ }
+ defaultValue={field.value ? field.value : "mux_cool"}
+ >
+ <FormControl>
+ <SelectTrigger>
+ <SelectValue placeholder="Select protocol" />
+ </SelectTrigger>
+ </FormControl>
+ <SelectContent>
+ <SelectItem value="mux_cool">Mux Cool</SelectItem>
+ <SelectItem value="h2mux">h2mux</SelectItem>
+ <SelectItem value="yamux">yamux</SelectItem>
+ <SelectItem value="smux">smux</SelectItem>
+ </SelectContent>
+ </Select>
+ </FormItem>
+ )}
+ />
+ );
+};
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 (
+ <FormField
+ name="mux_settings.mux_cool_settings.xudp_proxy_443"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>{t("Xudp Proxy 443")}</FormLabel>
+ <Select
+ onValueChange={(value) =>
+ updater(
+ "mux_settings.mux_cool_settings.xudp_proxy_443",
+ value,
+ )
+ }
+ defaultValue={field.value}
+ >
+ <FormControl>
+ <SelectTrigger>
+ <SelectValue placeholder="Select protocol" />
+ </SelectTrigger>
+ </FormControl>
+ <SelectContent>
+ <SelectItem value="reject">reject</SelectItem>
+ <SelectItem value="allow">allow</SelectItem>
+ <SelectItem value="skip">skip</SelectItem>
+ </SelectContent>
+ </Select>
+ </FormItem>
+ )}
+ />
+ );
+};
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 (
+ <Popover>
+ <PopoverTrigger>
+ <MailWarning className="p-0 size-5 text-destructive bg-primary-background" />
+ </PopoverTrigger>
+ <PopoverContent className="bg-destructive-accent text-sm">
+ <ul className="my-6 ml-3 list-disc [&>li]:mt-1 mt-0">
+ {errors.type?.message && (
+ <li>
+ <b>Type:</b> {t(errors.type.message as string)}
+ </li>
+ )}
+ {errors.packet?.message && (
+ <li>
+ <b>Packet:</b> {t(errors.packet.message as string)}
+ </li>
+ )}
+ {errors.delay?.message && (
+ <li>
+ <b>Interval:</b> {t(errors.delay.message as string)}
+ </li>
+ )}
+ </ul>
+ </PopoverContent>
+ </Popover>
+ );
+};
+
+export const NoiseField = () => {
+ const { t } = useTranslation();
+ const form = useFormContext();
+ const [forms, setForms] = useState<NoiseForm["noise"][number][]>(
+ form.getValues("noise") || [],
+ );
+
+ const errors = form.formState.errors as FieldErrors<NoiseForm>;
+
+ 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 (
+ <div>
+ {forms.length > 0 ? (
+ forms.map((formData, index) => (
+ <FormItem key={index} className="my-2 w-full">
+ {index === 0 && (
+ <FormLabel className="flex flex-row justify-between items-center">
+ {t("Noise")}
+ <div className="flex flex-row items-center gap-2">
+ {errors.noise?.[index] && (
+ <NoiseErrorPopover
+ // errors={errors.noise[index]}
+ />
+ )}
+ {forms.length < 5 && (
+ <Button
+ className="p-0 size-5 bg-primary-background"
+ variant="secondary"
+ onClick={(e) => {
+ e.preventDefault();
+ addForm();
+ }}
+ >
+ <ListPlus />
+ </Button>
+ )}
+ </div>
+ </FormLabel>
+ )}
+ <FormControl>
+ <div className="flex flex-row w-full items-center gap-2">
+ {formData ? (
+ <>
+ <FormField
+ name={`noise.${index}.type`}
+ render={({ field }) => (
+ <Select
+ onValueChange={
+ field.onChange
+ }
+ defaultValue={field.value}
+ >
+ <FormControl>
+ <SelectTrigger className="w-2/5">
+ <SelectValue placeholder="Noise Type" />
+ </SelectTrigger>
+ </FormControl>
+ <SelectContent>
+ {noiseTypes.map(
+ (option) => (
+ <SelectItem
+ value={
+ option
+ }
+ >
+ {option}
+ </SelectItem>
+ ),
+ )}
+ </SelectContent>
+ </Select>
+ )}
+ />
+ <div className="w-full hstack">
+ <FormField
+ name={`noise.${index}.packet`}
+ render={({ field }) => (
+ <Input
+ className="border rounded-none rounded-s-lg p-2 w-full"
+ placeholder="Packet"
+ {...field}
+ />
+ )}
+ />
+ <FormField
+ name={`noise.${index}.delay`}
+ render={({ field }) => (
+ <Input
+ placeholder="Delay"
+ className="border w-full rounded-none rounded-e-lg p-2"
+ {...field}
+ />
+ )}
+ />
+ </div>
+ <Button
+ variant="destructive"
+ className="p-0 size-5 bg-primary-background"
+ onClick={(e) => {
+ e.preventDefault();
+ removeForm(index);
+ }}
+ >
+ <TrashIcon className="text-red-400" />
+ </Button>
+ </>
+ ) : (
+ <Button
+ className="border w-full"
+ variant="ghost"
+ onClick={(e) => {
+ e.preventDefault();
+ enableForm(index);
+ }}
+ >
+ {t("Enable Form")}
+ </Button>
+ )}
+ </div>
+ </FormControl>
+ </FormItem>
+ ))
+ ) : (
+ <FormItem className="my-2 w-full">
+ <FormLabel className="flex flex-row justify-between items-center">
+ {t("Noise")}
+ </FormLabel>
+
+ <Button
+ className="border w-full"
+ variant="ghost"
+ onClick={(e) => {
+ e.preventDefault();
+ addForm();
+ }}
+ >
+ {t("disabled")}
+ </Button>
+ </FormItem>
+ )}
+ </div>
+ );
+};
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 (
- <FormField
- control={form.control}
- name="path"
- render={({ field }) => (
- <FormItem className="w-full">
- <FormLabel>{label ?? t("path")}</FormLabel>
- <FormControl>
- <Input {...field} />
- </FormControl>
- <FormMessage />
- </FormItem>
- )}
- />
- );
-};
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 }) => (
<FormItem>
<FormLabel>{t("security")}</FormLabel>
- <Select onValueChange={field.onChange} defaultValue={field.value}>
+ <Select
+ onValueChange={field.onChange}
+ defaultValue={field.value}
+ >
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a verified email to display" />
@@ -44,7 +48,9 @@ export const SecurityFields = () => {
<SelectContent>
<SelectItem value="none">None</SelectItem>
<SelectItem value="tls">TLS</SelectItem>
- <SelectItem value="inbound_default">Inbound Default</SelectItem>
+ <SelectItem value="inbound_default">
+ Inbound Default
+ </SelectItem>
</SelectContent>
</Select>
<FormMessage />
@@ -52,14 +58,14 @@ export const SecurityFields = () => {
)}
/>
{extraSecurity && (
- <>
- <SniField />
+ <div className="w-full flex flex-col gap-2">
+ <ClearableTextField name="sni" label={t("sni")} />
<div className="flex flex-row w-full gap-2">
<AlpnField />
<FingerprintField />
</div>
<AllowInsecureField />
- </>
+ </div>
)}
</>
);
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 (
+ <FormField
+ control={form.control}
+ name="splithttp_settings"
+ render={() => (
+ <FormItem className="my-2 w-full">
+ <FormLabel className="flex items-center justify-between">
+ {t("split_http")}
+ <Switch
+ checked={!isSplitHttpDisabled}
+ onCheckedChange={(checked) =>
+ form.setValue(
+ "splithttp_settings",
+ checked ? { xmux: {} } : null,
+ )
+ }
+ />
+ </FormLabel>
+
+ {!isSplitHttpDisabled && (
+ <>
+ <div className="grid grid-cols-2 gap-3">
+ <SplitHttpModeField
+ updater={updateFieldValue}
+ />
+ <SplitHttpPaddingBytesField
+ updater={updateFieldValue}
+ />
+ <CheckboxField
+ name="splithttp_settings.no_grpc_header"
+ label={t("No GRPC Header")}
+ />
+ </div>
+ <SplitHttpXmuxFields />
+ </>
+ )}
+ </FormItem>
+ )}
+ />
+ );
+};
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 (
+ <FormField
+ name="splithttp_settings.mode"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel >
+ {t("Mode")}
+ </FormLabel>
+ <Select onValueChange={(value) => updater("splithttp_settings.mode", value)} defaultValue={field.value}>
+ <FormControl>
+ <SelectTrigger>
+ <SelectValue placeholder="Select a verified email to display" />
+ </SelectTrigger>
+ </FormControl>
+ <SelectContent>
+ <SelectItem value="auto">auto</SelectItem>
+ <SelectItem value="packet-up">packet-up</SelectItem>
+ <SelectItem value="stream-up">stream-up</SelectItem>
+ <SelectItem value="stream-one">stream-one</SelectItem>
+ </SelectContent>
+ </Select>
+ </FormItem>
+ )}
+ />
+ )
+}
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 (
+ <FormField
+ name="splithttp_settings.padding_bytes"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>
+ {t("Padding Bytes")}
+ </FormLabel>
+ <FormControl>
+ <Input
+ value={field.value}
+ onChange={(e) =>
+ updater(
+ "splithttp_settings.padding_bytes",
+ e.target.value
+ )
+ }
+ className="w-full"
+ />
+ </FormControl>
+ </FormItem>
+ )}
+ />
+ )
+}
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 (
+ <div>
+ <FormLabel className="flex items-center justify-between">
+ {t("page.hosts.xmux-settings")}
+ <Switch
+ checked={!isXMuxDisabled}
+ onCheckedChange={(checked) =>
+ form.setValue(
+ "splithttp_settings.xmux",
+ checked ? { xmux: {} } : null,
+ )
+ }
+ />
+ </FormLabel>
+ {!isXMuxDisabled && (
+ <div className="grid grid-cols-2 gap-3">
+ {[
+ "max_concurrency",
+ "max_connections",
+ "max_reuse_times",
+ "max_lifetime",
+ "max_request_times",
+ ].map((fieldName) => (
+ <FormField
+ key={fieldName}
+ name={`splithttp_settings.xmux.${fieldName}`}
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel className="flex items-center justify-between capitalize">
+ {t(`${fieldName.replace(/_/g, " ")}`)}
+ </FormLabel>
+ <FormControl>
+ <Input
+ value={field.value}
+ onChange={(e) =>
+ updateFieldValue(
+ `splithttp_settings.xmux.${fieldName}`,
+ e.target.value,
+ )
+ }
+ className="w-full"
+ />
+ </FormControl>
+ </FormItem>
+ )}
+ />
+ ))}
+ <FormField
+ key="keep_alive_period"
+ name={`splithttp_settings.xmux.keep_alive_period`}
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel className="flex items-center justify-between capitalize">
+ Keep Alive Period
+ </FormLabel>
+ <FormControl>
+ <Input
+ value={field.value}
+ type="number"
+ onChange={(e) =>
+ updateFieldValue(
+ `splithttp_settings.xmux.keep_alive_period`,
+ e.target.value === ""
+ ? null
+ : Number.parseInt(
+ e.target.value,
+ ),
+ )
+ }
+ className="w-full"
+ />
+ </FormControl>
+ </FormItem>
+ )}
+ />
+ </div>
+ )}
+ </div>
+ );
+};
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<HostWithProfileType> {
+interface HostMutationDialogProps
+ extends MutationDialogProps<HostWithProfileType> {
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<HostMutationDialogProps> = ({
@@ -42,17 +63,21 @@ export const HostsMutationDialog: FC<HostMutationDialogProps> = ({
protocol,
}) => {
const [open, onOpenChange] = useDialog(true);
- const [Schema, ProfileFields, defaultValue] = useProfileStrategy(protocol)
+ const [Schema, ProfileFields, defaultValue] = useProfileStrategy(protocol);
const form = useForm<HostWithProfileSchemaType>({
- 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<HostMutationDialogProps> = ({
}, [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 (
<div className="space-y-2">
<CommonFields />
<Accordion className="space-y-2" type="single" collapsible>
- <SettingSection value="network" triggerText={t("page.hosts.network-settings")}>
+ <SettingSection
+ value="network"
+ triggerText={t("page.hosts.network-settings")}
+ >
<HStack>
- <HostField />
- <PathField />
+ <ClearableTextField name="host" label={t("host")} />
+ <ClearableTextField name="path" label={t("path")} />
</HStack>
+ <EarlyDataField />
+ <HttpHeadersDynamicFields />
+ </SettingSection>
+ <SettingSection
+ value="split-http"
+ triggerText={t("page.hosts.split-http-settings")}
+ >
+ <SplitHttpFields />
</SettingSection>
- <SettingSection value="camouflage" triggerText={t("page.hosts.camouflage-settings")}>
+ <SettingSection
+ value="camouflage"
+ triggerText={t("page.hosts.camouflage-settings")}
+ >
<FragmentField />
- <MuxField />
+ <NoiseField />
+ </SettingSection>
+ <SettingSection
+ value="mux"
+ triggerText={t("page.hosts.mux-settings")}
+ >
+ <MuxSettingsFields />
</SettingSection>
- <SettingSection value="security" triggerText={t("page.hosts.security-settings")}>
+ <SettingSection
+ value="security"
+ triggerText={t("page.hosts.security-settings")}
+ >
<SecurityFields />
</SettingSection>
</Accordion>
</div>
- )
-}
+ );
+};
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<typeof GeneralSchema>;
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 (
<div className="space-y-2">
<CommonFields />
- <PathField label={t("page.hosts.obfuscation-password")} />
+ <ClearableTextField
+ name="path"
+ label={t("page.hosts.obfuscation-password")}
+ />
<Accordion className="space-y-2" type="single" collapsible>
<TlsFields />
</Accordion>
</div>
- )
-}
+ );
+};
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<ProtocolType, ProfileConfig> = {
+ 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 = () => {
<Accordion type="single" collapsible>
<SettingSection value="wireguard" triggerText={t("wireguard")}>
<HStack>
- <PathField label={t("page.hosts.server-public-key")} />
+ <ClearableTextField
+ name="path"
+ label={t("page.hosts.server-public-key")}
+ />
<MtuField />
</HStack>
<DNSServersField />
@@ -28,8 +31,8 @@ export const WireguardProfileFields = () => {
</SettingSection>
</Accordion>
</>
- )
-}
+ );
+};
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 (
- <SettingSection value="tls-settings" triggerText={t("page.hosts.tls-config")}>
- <SniField />
+ <SettingSection
+ value="tls-settings"
+ triggerText={t("page.hosts.tls-config")}
+ >
+ <ClearableTextField name="sni" label={t("sni")} />
<AlpnField />
<AllowInsecureField />
</SettingSection>
- )
-}
+ );
+};
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: () => (
<AlertDialog open={true}>
- <AlertDialogContent>Host not found</AlertDialogContent>
+ <AlertDialogContent>
+ <AlertDialogTitle>
+ Host not found
+ </AlertDialogTitle>
+ </AlertDialogContent>
</AlertDialog>
),
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