From 0223dd817b226a1abd1ff8813e041dc84365dbc4 Mon Sep 17 00:00:00 2001 From: robonen Date: Sun, 9 Aug 2026 15:47:38 +0700 Subject: [PATCH] feat: add custom dns resolver --- CHANGELOG.md | 97 ++++ Cargo.lock | 2 +- Cargo.toml | 2 +- installer/setup.iss | 15 + src/dns.rs | 1001 +++++++++++++++++++++++++++++++++++++ src/dns_tests.rs | 646 ++++++++++++++++++++++++ src/main.rs | 337 ++++++++++++- src/netcfg.rs | 444 ++++++++++++++++ src/netcfg_tests.rs | 92 ++++ static/app.js | 134 ++++- static/index.html | 6 +- static/vue.global.prod.js | 14 + 12 files changed, 2780 insertions(+), 10 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 src/dns.rs create mode 100644 src/dns_tests.rs create mode 100644 src/netcfg.rs create mode 100644 src/netcfg_tests.rs create mode 100644 static/vue.global.prod.js diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b819393 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,97 @@ +# Changelog + +Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +versioning follows [Semantic Versioning](https://semver.org/). + +## [0.3.0] — 2026-08-09 + +"It now closes websites, not just programs." + +### Added + +- Website blocking through a built-in DNS resolver on `127.0.0.1:53` and + `[::1]:53`. One entry closes the domain and every subdomain under it — + `discord.com` also covers `gateway.discord.com`, so there is no list to + maintain by hand. +- A "Sites" section in the panel: resolver status, query counters and a table of + attempts, showing what was reached for and when. +- A separate on/off switch for website blocking. Turning it off frees port 53 + and hands the adapters back to the router's settings. +- Optional firewall rule that blocks queries to third-party name servers, so the + resolver cannot simply be bypassed. +- A `--restore-dns` switch that puts network settings back without starting the + service, for the case where the service will not come up. + +### Changed + +- Vue is compiled into the executable; it used to be fetched from `unpkg.com`. + The panel now works without an internet connection, and a name resolution + failure no longer takes away the only way to manage the service. +- The uninstaller restores DNS settings before removing the service. + +### Notes + +- **Website blocking is off by default.** Updates arrive on their own, without + anyone at the keyboard, and must not silently rewrite network settings. Turn + it on in the panel. +- The resolver needs port 53. If something else holds it — Docker, Internet + Connection Sharing, a third-party DNS proxy — the panel says so, network + settings are left alone, and program blocking keeps working. +- DNS over HTTPS in browsers and any VPN bypass this layer. Process termination + is what covers those, whitelist mode most reliably. +- Network settings are restored when the service stops. If that fails, it is + recorded in the log under `C:\ProgramData\SysHelper`. + +## [0.2.0] — 2026-08-02 + +"The panel can now be opened from another device on your home network — a phone +or a second computer." + +### Added + +- Service listens on all network interfaces, with a Windows Firewall rule for + port 8787 scoped to private and domain networks only. + +### Notes + +- Upgrading: the firewall rule is created automatically during installation and + updates. +- The panel is served over plain HTTP; passwords travel the network unencrypted. +- Local access at `http://127.0.0.1:8787` is unchanged and still password + protected. + +## [0.1.1] — 2026-08-02 + +Installer reliability improvements; service and panel unchanged. + +### Fixed + +- Setup now properly reports when the service fails to start. +- Setup verifies that the panel password was stored and warns if it was not. + +### Notes + +- Warnings appear during manual installation only; automatic updates stay + silent and log to `C:\ProgramData\SysHelper`. + +## [0.1.0] — 2026-08-02 + +"Parental control for Windows. Runs as a background service, managed from a +browser." + +### Added + +- Block programs by name, in blacklist or whitelist mode. +- Schedule termination at a random moment inside a time window. +- Live process list and event log. +- Automatic updates. + +### Notes + +- Runs as a Windows SYSTEM service and requires 64-bit Windows. +- Configuration is stored in `C:\ProgramData\SysHelper`. + +[0.3.0]: https://git.robonen.ru/robonen/syshelper/compare/v0.2.0...v0.3.0 +[0.2.0]: https://git.robonen.ru/robonen/syshelper/compare/v0.1.1...v0.2.0 +[0.1.1]: https://git.robonen.ru/robonen/syshelper/compare/v0.1.0...v0.1.1 +[0.1.0]: https://git.robonen.ru/robonen/syshelper/releases/tag/v0.1.0 diff --git a/Cargo.lock b/Cargo.lock index 2ff8ecf..0d29ae8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1224,7 +1224,7 @@ dependencies = [ [[package]] name = "syshelper" -version = "0.2.0" +version = "0.3.0" dependencies = [ "argon2", "axum", diff --git a/Cargo.toml b/Cargo.toml index 245b825..c6be596 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "syshelper" -version = "0.2.0" +version = "0.3.0" edition = "2021" [dependencies] diff --git a/installer/setup.iss b/installer/setup.iss index e9782fb..2a05bb0 100644 --- a/installer/setup.iss +++ b/installer/setup.iss @@ -289,12 +289,27 @@ begin 'Откройте «Службы» и посмотрите System Helper.', mbError, MB_OK); end; +// Пока служба работала, адаптеры смотрели на её резолвер (127.0.0.1). Если +// удалить программу и оставить эту настройку, машина останется без разрешения +// имён вовсе — то есть без интернета. Возврат делает сама программа: ключ +// --restore-dns снимает и настройку адаптеров, и правила брандмауэра. +procedure RestoreDns(); +var + Code: Integer; +begin + Exec(ExpandConstant('{app}\{#AppExeName}'), '--restore-dns', + '', SW_HIDE, ewWaitUntilTerminated, Code); +end; + procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); begin if CurUninstallStep <> usUninstall then Exit; + // Порядок обратный установке: сначала гасим службу, потом возвращаем сеть — + // работающая служба переставила бы настройку обратно на следующем такте. StopService(); + RestoreDns(); RunSc('delete {#ServiceName}'); RemoveFirewallRule(); end; diff --git a/src/dns.rs b/src/dns.rs new file mode 100644 index 0000000..073356c --- /dev/null +++ b/src/dns.rs @@ -0,0 +1,1001 @@ +//! Свой DNS-резолвер на 127.0.0.1:53. +//! +//! Файл hosts не умеет ни масок, ни журнала: `discord.com` пришлось бы +//! перечислять вместе со всеми поддоменами, а узнать, что ребёнок ломился в +//! запрещённое, было бы неоткуда. Поэтому имена разрешает служба: адаптеры +//! смотрят на 127.0.0.1, запрещённое отсекается здесь же, остальное уходит +//! наверх. +//! +//! Резолвер стоит на пути каждого соединения в системе, так что цена ошибки — +//! не «медленно», а «интернета нет вообще». Отсюда три решения, определяющие +//! устройство файла: +//! +//! * Узкое место здесь не пропускная способность (домашняя машина даёт десятки +//! запросов в секунду), а задержка. Поэтому запрос к вышестоящему серверу +//! никогда никого не ждёт: один сокет на всех, свой идентификатор в каждом +//! запросе и таблица незакрытых — как в dnsmasq. Задачи на запрос не +//! заводятся, сокеты на запрос не открываются. +//! * Запрещённое имя и попадание в кэш обслуживаются без единой аллокации и без +//! выхода в сеть — весь ответ собирается прямо в приёмном буфере. +//! * Всё, что резолвер не смог разобрать, уходит наверх как есть. Строгий +//! разбор — причина отказать в блокировке, но не причина сломать разрешение +//! имён. + +use std::{ + collections::HashMap, + net::SocketAddr, + sync::{ + atomic::{AtomicU16, AtomicU64, AtomicUsize, Ordering}, + Arc, Mutex, + }, + time::{Duration, Instant}, +}; + +use serde::Serialize; +use tokio::{ + io::{AsyncReadExt, AsyncWriteExt}, + net::{TcpListener, TcpStream, UdpSocket}, + sync::watch, +}; + +use crate::Shared; + +/// Слушаем только петлю: наружу резолвер отдавать нечего, а открытый DNS в +/// локальной сети — приглашение использовать машину как усилитель атак. +pub const BIND_ADDR: &str = "127.0.0.1:53"; +/// Тот же резолвер по IPv6. Оставить шестую версию без присмотра нельзя: +/// Windows предпочитает её адрес сервера, и запросы ушли бы мимо нас — то есть +/// блокировки бы просто не было. +pub const BIND_ADDR_V6: &str = "[::1]:53"; +/// Предельный размер сообщения DNS — столько адресует поле длины у варианта +/// поверх TCP. +/// +/// Буфер сделан по этому пределу, а не по обычным для EDNS0 четырём килобайтам, +/// из-за поведения приёма датаграмм: то, что не влезло, отбрасывается молча, и +/// признака обрезки не остаётся. Клиенту ушёл бы обломок пакета — то есть имя +/// переставало бы разрешаться без всякого объяснения. Буфер выделяется один раз +/// на задачу, поэтому запас ничего не стоит. +const BUF: usize = 65535; +/// Заголовок DNS: идентификатор, флаги и четыре счётчика секций. +const HEADER: usize = 12; +/// Предел длины имени по RFC 1035 вместе с точками. +const MAX_NAME: usize = 255; +/// Сколько места нужно под приписываемый ответ: указатель сжатия, тип, класс, +/// TTL, длина и шестнадцать байт адреса IPv6. +const ANSWER_MAX: usize = 2 + 2 + 2 + 4 + 2 + 16; + +const TYPE_A: u16 = 1; +const TYPE_AAAA: u16 = 28; +const CLASS_IN: u16 = 1; + +/// TTL ответа-заглушки. Ноль означал бы «не кэшировать», и клиент спрашивал бы +/// заново на каждое соединение; минута же не даёт разблокировке залежаться. +const BLOCK_TTL: u32 = 60; +/// Сколько ждать вышестоящий сервер, прежде чем считать запрос потерянным. +const UPSTREAM_TIMEOUT: Duration = Duration::from_secs(3); +/// Потолок незакрытых запросов: без него шторм из localhost раздул бы таблицу. +const MAX_INFLIGHT: usize = 2048; +const CACHE_CAP: usize = 4096; +/// Свой кэш живёт недолго: TTL записей мы не пересчитываем, а отдаём как есть, +/// поэтому долгое хранение возвращало бы клиенту заведомо неверный остаток +/// жизни. Тридцати секунд хватает, чтобы погасить всплеск от загрузки страницы. +const CACHE_TTL: Duration = Duration::from_secs(30); +/// Отрицательные ответы держим ещё меньше: домен мог только что появиться. +const NEG_TTL: Duration = Duration::from_secs(10); +/// Как часто чистить таблицу незакрытых и сбрасывать журнал попыток. +const SWEEP_INTERVAL: Duration = Duration::from_secs(1); +const REPORT_INTERVAL: Duration = Duration::from_secs(30); +/// После скольких подряд молчаний переходить на запасной сервер. +const FAILOVER_AFTER: u64 = 5; +/// Сколько доменов помнить для панели. +const STATS_CAP: usize = 256; +/// Потолок одновременных TCP-запросов. По TCP ходят единицы клиентов, но +/// принимать их без счёта — способ остаться без дескрипторов. +const MAX_TCP: usize = 32; +/// Оборванное TCP-соединение не должно занимать слот вечно. +const TCP_IDLE: Duration = Duration::from_secs(10); + +/* ── список запрещённого ───────────────────────────────────── */ + +/// Домены в нижнем регистре, без точки на конце. +/// +/// Совпадение ищется по суффиксу: запись `discord.com` закрывает и сам домен, и +/// любые поддомены — иначе список пришлось бы вести вручную, а Discord ходит +/// через десяток имён вида `gateway.discord.gg`. +#[derive(Default)] +pub struct Blocklist { + domains: std::collections::HashSet>, +} + +impl Blocklist { + pub fn new>(domains: I) -> Self { + Self { + domains: domains + .into_iter() + .filter_map(|d| normalize_domain(&d)) + .map(String::into_boxed_str) + .collect(), + } + } + + /// Имя уже в нижнем регистре — так его кладёт `parse_question`. + fn matches(&self, name: &str) -> bool { + if self.domains.is_empty() { + return false; + } + let mut tail = name; + loop { + if self.domains.contains(tail) { + return true; + } + match tail.split_once('.') { + // До последней метки не доходим намеренно: запись вида `com` + // (опечатка или лишняя строка) иначе отрезала бы пол-интернета. + Some((_, rest)) if rest.contains('.') => tail = rest, + _ => return false, + } + } + } +} + +/// Приводит то, что человек ввёл в панели, к имени домена: люди вставляют +/// адрес целиком, вместе со схемой, путём и `www`. +pub fn normalize_domain(raw: &str) -> Option { + let lower = raw.trim().to_ascii_lowercase(); + let host = lower.split_once("://").map_or(lower.as_str(), |(_, r)| r); + let host = host.split(['/', '?', '#']).next()?; + // Имя пользователя перед адресом встречается в скопированных ссылках. + let host = host.rsplit_once('@').map_or(host, |(_, h)| h); + let host = host.split(':').next()?.trim_matches('.'); + // `www` отбрасываем: запись `www.discord.com` иначе не закрыла бы сам + // `discord.com`, хотя человек имел в виду именно его. + let host = host.strip_prefix("www.").unwrap_or(host); + + let plausible = host.contains('.') + && host.len() <= MAX_NAME + && host.chars().any(|c| c.is_ascii_alphabetic()) + && host + .split('.') + .all(|label| !label.is_empty() && label.len() <= 63) + && host + .bytes() + .all(|b| b.is_ascii_alphanumeric() || b == b'.' || b == b'-'); + + plausible.then(|| host.to_string()) +} + +/* ── разбор пакета ─────────────────────────────────────────── */ + +struct Question { + /// Длина имени, записанного в переданный вызовом буфер. + len: usize, + qtype: u16, + /// Класс запроса. Почти всегда `IN`, но различать обязаны: без него записи + /// разных классов с одним именем схлопывались бы в кэше в одну. + qclass: u16, + /// Смещение сразу за секцией вопроса — оттуда дописывается ответ. + end: usize, +} + +/// Достаёт имя и тип из запроса. Имя кладётся в `out` в нижнем регистре. +/// +/// Разбираем только то, что нужно для решения «блокировать или нет»: имя, тип и +/// границу вопроса. Всё остальное — секции ответа, EDNS0 — не трогаем, и запрос +/// уходит наверх байт в байт, вместе с объявленным размером буфера клиента. +fn parse_question(pkt: &[u8], out: &mut [u8; MAX_NAME]) -> Option { + if pkt.len() < HEADER { + return None; + } + // Ответ на входе означает либо чужой пакет, либо попытку нас запутать. + if pkt[2] & 0x80 != 0 { + return None; + } + // Стандартный запрос (opcode 0) ровно с одним вопросом. Всё прочее — + // обновления зон, запросы состояния — пусть разбирает вышестоящий сервер. + if pkt[2] & 0x78 != 0 || u16::from_be_bytes([pkt[4], pkt[5]]) != 1 { + return None; + } + + let mut pos = HEADER; + let mut len = 0usize; + loop { + let label = *pkt.get(pos)? as usize; + pos += 1; + if label == 0 { + break; + } + // Сжатие в вопросе запрещено: указатель здесь либо от сломанного + // клиента, либо приглашение зациклить разбор. + if label > 63 { + return None; + } + let need = if len == 0 { label } else { label + 1 }; + if len + need > MAX_NAME { + return None; + } + let bytes = pkt.get(pos..pos + label)?; + if len > 0 { + out[len] = b'.'; + len += 1; + } + for &b in bytes { + out[len] = b.to_ascii_lowercase(); + len += 1; + } + pos += label; + } + + let qtype = u16::from_be_bytes([*pkt.get(pos)?, *pkt.get(pos + 1)?]); + let qclass = u16::from_be_bytes([*pkt.get(pos + 2)?, *pkt.get(pos + 3)?]); + let end = pos + 4; + (end <= pkt.len() && len > 0).then_some(Question { + len, + qtype, + qclass, + end, + }) +} + +/// Переделывает запрос в ответ «такого адреса нет» прямо в буфере запроса и +/// возвращает новую длину пакета. +/// +/// Отдаём 0.0.0.0, а не NXDOMAIN: подключение к нему обрывается сразу и браузер +/// показывает понятную ошибку, тогда как на «домена не существует» часть +/// программ уходит переспрашивать по кругу. +fn write_blocked(pkt: &mut [u8], q: &Question) -> Option { + if q.end + ANSWER_MAX > pkt.len() { + return None; + } + + pkt[2] |= 0x80; // QR: это ответ + pkt[2] &= !0x06; // снимаем AA и TC — ответ наш и целиком помещается + pkt[3] = 0x80; // рекурсия доступна, код ответа нулевой + + let rdata: &[u8] = match (q.qclass, q.qtype) { + (CLASS_IN, TYPE_A) => &[0, 0, 0, 0], + (CLASS_IN, TYPE_AAAA) => &[0; 16], + // На прочие типы отвечаем пустым успехом: подделывать MX или TXT незачем, + // а отказ отправил бы клиента спрашивать снова. Чужой класс сюда же — + // выдавать адрес из класса IN в ответ на вопрос про другой было бы + // прямой неправдой. + _ => &[], + }; + + let mut n = q.end; + if !rdata.is_empty() { + // Имя не повторяем: указатель сжатия на вопрос, он всегда со смещения 12. + pkt[n..n + 2].copy_from_slice(&[0xc0, 0x0c]); + pkt[n + 2..n + 4].copy_from_slice(&q.qtype.to_be_bytes()); + pkt[n + 4..n + 6].copy_from_slice(&q.qclass.to_be_bytes()); + pkt[n + 6..n + 10].copy_from_slice(&BLOCK_TTL.to_be_bytes()); + pkt[n + 10..n + 12].copy_from_slice(&(rdata.len() as u16).to_be_bytes()); + pkt[n + 12..n + 12 + rdata.len()].copy_from_slice(rdata); + n += 12 + rdata.len(); + } + + pkt[6..8].copy_from_slice(&(if rdata.is_empty() { 0u16 } else { 1 }).to_be_bytes()); + // Секции полномочий и дополнений отбрасываем вместе с EDNS0 клиента: + // заглушке они не нужны, а пересобирать их — лишняя работа на горячем пути. + pkt[8..12].copy_from_slice(&[0, 0, 0, 0]); + Some(n) +} + +fn packet_id(pkt: &[u8]) -> u16 { + u16::from_be_bytes([pkt[0], pkt[1]]) +} + +fn set_packet_id(pkt: &mut [u8], id: u16) { + pkt[0..2].copy_from_slice(&id.to_be_bytes()); +} + +fn rcode(pkt: &[u8]) -> u8 { + pkt[3] & 0x0f +} + +fn truncated(pkt: &[u8]) -> bool { + pkt[2] & 0x02 != 0 +} + +/* ── кэш ───────────────────────────────────────────────────── */ + +struct Entry { + /// Готовый ответ целиком. Идентификатор в первых двух байтах у каждого + /// клиента свой и подставляется при отправке. + packet: Box<[u8]>, + expires: Instant, +} + +/// Ключ — имя вместе с типом и классом запроса, собранный в стековом буфере: +/// искать в карте можно по срезу, не выделяя памяти на каждый запрос. +fn cache_key(name: &[u8], qtype: u16, qclass: u16, out: &mut [u8; MAX_NAME + 4]) -> usize { + let end = name.len(); + out[..end].copy_from_slice(name); + out[end..end + 2].copy_from_slice(&qtype.to_be_bytes()); + out[end + 2..end + 4].copy_from_slice(&qclass.to_be_bytes()); + end + 4 +} + +#[derive(Default)] +struct Cache { + map: HashMap, Entry>, +} + +impl Cache { + /// Копирует ответ в `out` и проставляет идентификатор клиента. + fn get(&self, key: &[u8], id: u16, out: &mut [u8], now: Instant) -> Option { + let entry = self.map.get(key)?; + if entry.expires <= now || entry.packet.len() > out.len() { + return None; + } + out[..entry.packet.len()].copy_from_slice(&entry.packet); + set_packet_id(out, id); + Some(entry.packet.len()) + } + + fn put(&mut self, key: &[u8], packet: &[u8], now: Instant) { + // Обрезанные ответы бессмысленны в кэше: клиент всё равно переспросит + // по TCP. Ошибки сервера не кэшируем, чтобы сбой не залипал. + if truncated(packet) { + return; + } + let ttl = match rcode(packet) { + 0 => CACHE_TTL, + 3 => NEG_TTL, + _ => return, + }; + + if self.map.len() >= CACHE_CAP { + // Сначала выбрасываем протухшее, и лишь если это не помогло — + // чистим целиком. Учёт порядка обращений стоил бы дороже, чем + // редкий поход наверх за тем, что мы только что забыли. + self.map.retain(|_, e| e.expires > now); + if self.map.len() >= CACHE_CAP { + self.map.clear(); + } + } + + self.map.insert( + key.into(), + Entry { + packet: packet.into(), + expires: now + ttl, + }, + ); + } + + fn sweep(&mut self, now: Instant) { + self.map.retain(|_, e| e.expires > now); + } +} + +/* ── учёт попыток ──────────────────────────────────────────── */ + +struct Hit { + count: u64, + /// Сколько уже ушло в журнал: пишем приростом, а не полным счётчиком. + reported: u64, + last: Instant, +} + +#[derive(Serialize)] +pub struct BlockedDomain { + pub name: String, + pub count: u64, + pub ago_secs: u64, +} + +#[derive(Default)] +struct Attempts { + domains: HashMap, Hit>, +} + +impl Attempts { + fn record(&mut self, name: &str, now: Instant) { + if let Some(hit) = self.domains.get_mut(name) { + hit.count += 1; + hit.last = now; + return; + } + if self.domains.len() >= STATS_CAP { + // Вытесняем то, к чему дольше всего не обращались: панели интересно + // происходящее сейчас, а не первые попытки полугодовой давности. + if let Some(oldest) = self + .domains + .iter() + .min_by_key(|(_, h)| h.last) + .map(|(k, _)| k.clone()) + { + self.domains.remove(&oldest); + } + } + self.domains.insert( + name.into(), + Hit { + count: 1, + reported: 0, + last: now, + }, + ); + } +} + +/// Счётчики для панели. Обновляются на горячем пути, поэтому атомарные: +/// блокировка ради статистики — худшая из возможных причин ждать. +#[derive(Default)] +struct Counters { + queries: AtomicU64, + blocked: AtomicU64, + cache_hits: AtomicU64, + timeouts: AtomicU64, + /// Подряд не отвеченных запросов — по ним переключаемся на запасной сервер. + misses_in_row: AtomicU64, +} + +#[derive(Serialize)] +pub struct DnsStatus { + pub listening: bool, + pub upstream: String, + pub queries: u64, + pub blocked: u64, + pub cache_hits: u64, + pub timeouts: u64, + pub error: Option, + pub attempts: Vec, +} + +/* ── общее состояние ───────────────────────────────────────── */ + +/// То, что видит остальная программа: список запрещённого и показания счётчиков. +pub struct Dns { + blocklist: watch::Sender>, + counters: Arc, + attempts: Arc>, + /// Строка с адресом вышестоящего сервера либо причина, почему резолвер не + /// работает. Панель должна показывать это раньше, чем родитель заметит, что + /// блокировка молчит. + state: Mutex<(bool, String, Option)>, +} + +impl Dns { + pub fn new() -> Self { + let (blocklist, _) = watch::channel(Arc::new(Blocklist::default())); + Self { + blocklist, + counters: Arc::default(), + attempts: Arc::default(), + state: Mutex::new((false, String::new(), None)), + } + } + + /// Подменяет список запрещённого. Работающие задачи подхватят его на + /// следующем же запросе — перезапускать резолвер не нужно. + /// + /// Именно `send_replace`, а не `send`: последний при отсутствии получателей + /// не только возвращает ошибку, но и **оставляет прежнее значение**. А + /// получателей нет ровно до того, как резолвер подпишется, — то есть при + /// загрузке правил и при первом включении. С `send` список молча оставался + /// бы пустым: адаптеры уведены на резолвер, панель показывает блокировку, + /// а не блокируется ничего. + pub fn set_domains>(&self, domains: I) { + self.blocklist.send_replace(Arc::new(Blocklist::new(domains))); + } + + /// Отмечает, что резолвер погашен. Без этого панель показывала бы «слушаю» + /// после выключения блокировки — то есть врала бы о состоянии системы. + pub fn mark_stopped(&self) { + if let Ok(mut s) = self.state.lock() { + *s = (false, String::new(), None); + } + } + + pub fn status(&self) -> DnsStatus { + let now = Instant::now(); + let mut attempts: Vec = self + .attempts + .lock() + .map(|a| { + a.domains + .iter() + .map(|(name, hit)| BlockedDomain { + name: name.to_string(), + count: hit.count, + ago_secs: now.saturating_duration_since(hit.last).as_secs(), + }) + .collect() + }) + .unwrap_or_default(); + attempts.sort_by_key(|d| d.ago_secs); + + let (listening, upstream, error) = self + .state + .lock() + .map(|s| s.clone()) + .unwrap_or((false, String::new(), None)); + + DnsStatus { + listening, + upstream, + queries: self.counters.queries.load(Ordering::Relaxed), + blocked: self.counters.blocked.load(Ordering::Relaxed), + cache_hits: self.counters.cache_hits.load(Ordering::Relaxed), + timeouts: self.counters.timeouts.load(Ordering::Relaxed), + error, + attempts, + } + } +} + +/* ── резолвер ──────────────────────────────────────────────── */ + +struct Pending { + client: SocketAddr, + /// Сокет, на который пришёл запрос: ответить нужно с того же адреса, иначе + /// клиент сочтёт ответ чужим. Клон `Arc` дешевле разбора адреса на ответе. + sock: Arc, + /// Идентификатор, с которым пришёл клиент: наружу уходит наш, обратно ему + /// должен вернуться его собственный. + client_id: u16, + /// Ключ кэша, посчитанный на запросе, — на ответе имя разбирать не придётся. + key: Option>, + deadline: Instant, +} + +struct Resolver { + upstream_sock: Arc, + upstreams: Vec, + active: AtomicUsize, + inflight: Mutex>, + next_id: AtomicU16, + cache: Mutex, + blocklist: watch::Receiver>, + counters: Arc, + attempts: Arc>, +} + +impl Resolver { + fn upstream(&self) -> SocketAddr { + self.upstreams[self.active.load(Ordering::Relaxed) % self.upstreams.len()] + } + + /// Решает судьбу запроса. Возвращает готовый ответ, если отвечаем сами. + /// + /// Здесь не должно появиться ни одного `await`: пока задача разбирает + /// пакет, она не читает сокет, и очередь ядра копится. + fn decide(&self, pkt: &mut [u8], len: usize, now: Instant) -> Decision { + let mut name = [0u8; MAX_NAME]; + let Some(q) = parse_question(&pkt[..len], &mut name) else { + // Разобрать не смогли — пусть отвечает вышестоящий сервер. + return Decision::Forward(None); + }; + // Имя собрано из меток, приведённых к нижнему регистру ASCII, так что + // проверка кодировки здесь не нужна. + let name = match std::str::from_utf8(&name[..q.len]) { + Ok(name) => name, + Err(_) => return Decision::Forward(None), + }; + + if self.blocklist.borrow().matches(name) { + if let Ok(mut attempts) = self.attempts.lock() { + attempts.record(name, now); + } + self.counters.blocked.fetch_add(1, Ordering::Relaxed); + return match write_blocked(pkt, &q) { + Some(n) => Decision::Answer(n), + None => Decision::Drop, + }; + } + + let mut key_buf = [0u8; MAX_NAME + 4]; + let key_len = cache_key(name.as_bytes(), q.qtype, q.qclass, &mut key_buf); + let key = &key_buf[..key_len]; + + // Вопрос спросившего сохраняем до того, как поверх ляжет ответ из кэша. + // В кэше лежит вопрос того, кто спрашивал первым, а регистр имени у них + // может отличаться: ключ приведён к нижнему, сам пакет — нет. Клиент + // сверяет возвращённый вопрос со своим, и чужой регистр он вправе счесть + // ответом не ему. Длины совпадают всегда — имена различаются только + // регистром, — поэтому подмена ровно на месте. + let mut asked = [0u8; MAX_NAME + 6]; + let asked_len = q.end - HEADER; + if asked_len > asked.len() { + return Decision::Forward(Some(key.into())); + } + asked[..asked_len].copy_from_slice(&pkt[HEADER..q.end]); + + if let Ok(cache) = self.cache.lock() { + if let Some(n) = cache.get(key, packet_id(pkt), pkt, now) { + pkt[HEADER..q.end].copy_from_slice(&asked[..asked_len]); + self.counters.cache_hits.fetch_add(1, Ordering::Relaxed); + return Decision::Answer(n); + } + } + Decision::Forward(Some(key.into())) + } +} + +enum Decision { + /// Отвечаем сами; в буфере лежит готовый ответ указанной длины. + Answer(usize), + /// Спрашиваем наверх; внутри — ключ кэша, если его удалось посчитать. + Forward(Option>), + Drop, +} + +/// Принимает запросы от системы. Одна задача на сокет: разбор занимает доли +/// микросекунды, а параллельные приёмники только толкались бы на нём. +async fn client_loop( + res: Arc, + sock: Arc, + mut shutdown: watch::Receiver, +) { + // В куче, а не на стеке: массив такого размера внутри `async fn` раздул бы + // само будущее задачи. Выделение одно на весь срок жизни цикла, на запросах + // памяти не трогаем вовсе. + let mut buf = vec![0u8; BUF]; + loop { + let (len, client) = tokio::select! { + r = sock.recv_from(&mut buf) => match r { + Ok(v) => v, + // Windows возвращает ошибку на сокет, если предыдущая отправка + // упёрлась в закрытый порт. Это не повод ронять резолвер. + Err(_) => continue, + }, + _ = shutdown.changed() => return, + }; + if len < HEADER { + continue; + } + res.counters.queries.fetch_add(1, Ordering::Relaxed); + + let now = Instant::now(); + match res.decide(&mut buf, len, now) { + Decision::Answer(n) => { + let _ = sock.send_to(&buf[..n], client).await; + } + Decision::Forward(key) => { + let client_id = packet_id(&buf); + let id = res.next_id.fetch_add(1, Ordering::Relaxed); + + let accepted = match res.inflight.lock() { + Ok(mut table) => { + // Переполнение таблицы означает, что наверху затор. + // Молча отбрасываем: клиент переспросит сам, а расти + // без предела нельзя. + if table.len() >= MAX_INFLIGHT { + false + } else { + table.insert( + id, + Pending { + client, + sock: Arc::clone(&sock), + client_id, + key, + deadline: now + UPSTREAM_TIMEOUT, + }, + ); + true + } + } + Err(_) => false, + }; + + if accepted { + set_packet_id(&mut buf, id); + let _ = res.upstream_sock.send_to(&buf[..len], res.upstream()).await; + } + } + Decision::Drop => {} + } + } +} + +/// Разбирает ответы вышестоящего сервера и раздаёт их тем, кто спрашивал. +async fn upstream_loop(res: Arc, mut shutdown: watch::Receiver) { + let mut buf = vec![0u8; BUF]; + loop { + let (len, from) = tokio::select! { + r = res.upstream_sock.recv_from(&mut buf) => match r { + Ok(v) => v, + Err(_) => continue, + }, + _ = shutdown.changed() => return, + }; + // Сокет открыт наружу, поэтому ответ от постороннего адреса — + // либо чужой пакет, либо попытка подсунуть свой адрес. + if len < HEADER || !res.upstreams.contains(&from) { + continue; + } + + let id = packet_id(&buf); + let Some(pending) = res.inflight.lock().ok().and_then(|mut t| t.remove(&id)) else { + continue; // опоздал: запрос уже сочли потерянным + }; + res.counters.misses_in_row.store(0, Ordering::Relaxed); + + if let Some(key) = &pending.key { + if let Ok(mut cache) = res.cache.lock() { + cache.put(key, &buf[..len], Instant::now()); + } + } + + set_packet_id(&mut buf, pending.client_id); + let _ = pending.sock.send_to(&buf[..len], pending.client).await; + } +} + +/// Чистит просроченное и переводит стрелку на запасной сервер, когда основной +/// замолчал. Без этого падение роутера означало бы, что имена не разрешаются +/// вовсе, — а виноватым выглядел бы родительский контроль. +async fn janitor(res: Arc, state: Shared, mut shutdown: watch::Receiver) { + let mut last_report = Instant::now(); + loop { + tokio::select! { + _ = tokio::time::sleep(SWEEP_INTERVAL) => {} + _ = shutdown.changed() => return, + } + let now = Instant::now(); + + let expired = match res.inflight.lock() { + Ok(mut table) => { + let before = table.len(); + table.retain(|_, p| p.deadline > now); + before - table.len() + } + Err(_) => 0, + }; + + if expired > 0 { + res.counters + .timeouts + .fetch_add(expired as u64, Ordering::Relaxed); + let row = res + .counters + .misses_in_row + .fetch_add(expired as u64, Ordering::Relaxed) + + expired as u64; + if row >= FAILOVER_AFTER && res.upstreams.len() > 1 { + res.counters.misses_in_row.store(0, Ordering::Relaxed); + let next = res.active.fetch_add(1, Ordering::Relaxed) + 1; + let addr = res.upstreams[next % res.upstreams.len()]; + if let Ok(mut s) = state.dns.state.lock() { + s.1 = addr.to_string(); + } + state + .log(format!("DNS: сервер не отвечает, перехожу на {addr}")) + .await; + } + } + + if let Ok(mut cache) = res.cache.lock() { + cache.sweep(now); + } + + // Журнал попыток пишем пачкой. Discord переспрашивает своё имя по + // нескольку раз в секунду, и построчная запись превратила бы и журнал в + // мусор, и запись в файл — в то самое узкое место. + if last_report.elapsed() >= REPORT_INTERVAL { + last_report = now; + let summary = res.attempts.lock().ok().map(|mut a| { + let mut fresh: Vec<(String, u64)> = a + .domains + .iter_mut() + .filter(|(_, h)| h.count > h.reported) + .map(|(name, h)| { + let delta = h.count - h.reported; + h.reported = h.count; + (name.to_string(), delta) + }) + .collect(); + fresh.sort_by_key(|(_, count)| std::cmp::Reverse(*count)); + fresh + }); + + if let Some(fresh) = summary.filter(|f| !f.is_empty()) { + let listed: Vec = fresh + .iter() + .take(5) + .map(|(name, n)| format!("{name} ×{n}")) + .collect(); + let tail = match fresh.len().saturating_sub(5) { + 0 => String::new(), + more => format!(" и ещё {more}"), + }; + state + .log(format!("DNS: отклонены запросы — {}{tail}", listed.join(", "))) + .await; + } + } + } +} + +/* ── DNS поверх TCP ────────────────────────────────────────── */ + +/// По TCP клиент приходит, когда ответ не влез в пакет. Таких запросов единицы, +/// поэтому здесь всё просто: задача на соединение и пересылка как есть. Не +/// поддержать TCP нельзя — иначе часть имён переставала бы разрешаться, и +/// причину искали бы неделю. +async fn tcp_loop(res: Arc, listener: TcpListener, mut shutdown: watch::Receiver) { + let live = Arc::new(AtomicUsize::new(0)); + loop { + let (mut stream, _) = tokio::select! { + r = listener.accept() => match r { + Ok(v) => v, + Err(_) => continue, + }, + _ = shutdown.changed() => return, + }; + + if live.load(Ordering::Relaxed) >= MAX_TCP { + continue; // соединение закроется само вместе с `stream` + } + live.fetch_add(1, Ordering::Relaxed); + + let res = Arc::clone(&res); + let live = Arc::clone(&live); + tokio::spawn(async move { + let _ = tokio::time::timeout(TCP_IDLE, serve_tcp(&res, &mut stream)).await; + live.fetch_sub(1, Ordering::Relaxed); + }); + } +} + +async fn serve_tcp(res: &Resolver, stream: &mut TcpStream) { + // На соединение, а не на задачу: по TCP приходят единицы клиентов, и + // держать под каждого постоянный буфер незачем. + let mut buf = vec![0u8; BUF]; + loop { + // По TCP сообщению предшествует его длина в двух байтах. + let mut len_bytes = [0u8; 2]; + if stream.read_exact(&mut len_bytes).await.is_err() { + return; + } + let len = u16::from_be_bytes(len_bytes) as usize; + if !(HEADER..=BUF).contains(&len) { + return; + } + if stream.read_exact(&mut buf[..len]).await.is_err() { + return; + } + res.counters.queries.fetch_add(1, Ordering::Relaxed); + + let now = Instant::now(); + let reply = match res.decide(&mut buf, len, now) { + Decision::Answer(n) => Some(n), + Decision::Drop => return, + // Кэш по TCP не наполняем: ответы здесь длинные, а приходят такие + // запросы единицами — экономить на них нечего. + Decision::Forward(_) => forward_tcp(res, len, &mut buf).await, + }; + + let Some(n) = reply else { return }; + if stream.write_all(&(n as u16).to_be_bytes()).await.is_err() + || stream.write_all(&buf[..n]).await.is_err() + { + return; + } + } +} + +/// Спрашивает вышестоящий сервер по TCP. Запрос лежит в начале `buf`, туда же +/// кладётся ответ; возвращается его длина. +async fn forward_tcp(res: &Resolver, query_len: usize, buf: &mut [u8]) -> Option { + let mut up = tokio::time::timeout(UPSTREAM_TIMEOUT, TcpStream::connect(res.upstream())) + .await + .ok()? + .ok()?; + + let header = (query_len as u16).to_be_bytes(); + up.write_all(&header).await.ok()?; + up.write_all(&buf[..query_len]).await.ok()?; + + let mut len_bytes = [0u8; 2]; + tokio::time::timeout(UPSTREAM_TIMEOUT, up.read_exact(&mut len_bytes)) + .await + .ok()? + .ok()?; + let len = u16::from_be_bytes(len_bytes) as usize; + if !(HEADER..=BUF).contains(&len) { + return None; + } + tokio::time::timeout(UPSTREAM_TIMEOUT, up.read_exact(&mut buf[..len])) + .await + .ok()? + .ok()?; + Some(len) +} + +/* ── запуск ────────────────────────────────────────────────── */ + +/// Сообщает, что резолвер не поднялся. Панель должна показать причину: молчащая +/// блокировка выглядит одинаково с выключенной. +async fn fail(state: &Shared, msg: String) { + if let Ok(mut s) = state.dns.state.lock() { + *s = (false, String::new(), Some(msg.clone())); + } + state.log(format!("DNS: {msg}")).await; +} + +/// Поднимает резолвер и возвращает признак «порт занят, запросы идут». +/// +/// Возвращаемое значение — не формальность: адаптеры переключаются на нас +/// только после успешного запуска, иначе неудачный старт оставил бы машину +/// вообще без разрешения имён. +pub async fn run(state: Shared, upstreams: Vec, shutdown: watch::Receiver) -> bool { + if upstreams.is_empty() { + fail(&state, "не нашёл ни одного вышестоящего сервера".to_string()).await; + return false; + } + + let client_v4 = match UdpSocket::bind(BIND_ADDR).await { + Ok(s) => Arc::new(s), + // Порт 53 занимают «Общий доступ к подключению» и Docker. Без него + // блокировка сайтов не работает, но правила по процессам — работают. + Err(e) => { + fail( + &state, + format!("{BIND_ADDR} занят ({e}), блокировка сайтов выключена"), + ) + .await; + return false; + } + }; + // Шестая версия необязательна: на машине без IPv6 сокет не поднимется, и + // это нормально — тогда и запросов по нему не будет. + let client_v6 = UdpSocket::bind(BIND_ADDR_V6).await.ok().map(Arc::new); + let tcp_v4 = TcpListener::bind(BIND_ADDR).await.ok(); + let tcp_v6 = TcpListener::bind(BIND_ADDR_V6).await.ok(); + + let upstream_sock = match UdpSocket::bind("0.0.0.0:0").await { + Ok(s) => Arc::new(s), + Err(e) => { + fail(&state, format!("нет исходящего сокета: {e}")).await; + return false; + } + }; + + let resolver = Arc::new(Resolver { + upstream_sock, + active: AtomicUsize::new(0), + inflight: Mutex::new(HashMap::with_capacity(256)), + // Начальный идентификатор случаен: предсказуемая нумерация облегчила бы + // подделку ответа тому, кто видит наш исходящий порт. + next_id: AtomicU16::new(rand::random()), + cache: Mutex::new(Cache::default()), + blocklist: state.dns.blocklist.subscribe(), + counters: Arc::clone(&state.dns.counters), + attempts: Arc::clone(&state.dns.attempts), + upstreams, + }); + + if let Ok(mut s) = state.dns.state.lock() { + *s = (true, resolver.upstream().to_string(), None); + } + state + .log(format!( + "DNS: слушаю {BIND_ADDR}, спрашиваю {}", + resolver.upstream() + )) + .await; + + for listener in [tcp_v4, tcp_v6].into_iter().flatten() { + tokio::spawn(tcp_loop(Arc::clone(&resolver), listener, shutdown.clone())); + } + if let Some(sock) = client_v6 { + tokio::spawn(client_loop(Arc::clone(&resolver), sock, shutdown.clone())); + } + tokio::spawn(upstream_loop(Arc::clone(&resolver), shutdown.clone())); + tokio::spawn(janitor( + Arc::clone(&resolver), + Arc::clone(&state), + shutdown.clone(), + )); + // Приём запросов уходит в фоновую задачу: вызвавшему нужно знать, что порт + // занят, и идти переключать адаптеры, а не ждать здесь до остановки службы. + tokio::spawn(client_loop(resolver, client_v4, shutdown)); + true +} + +#[cfg(test)] +#[path = "dns_tests.rs"] +mod tests; diff --git a/src/dns_tests.rs b/src/dns_tests.rs new file mode 100644 index 0000000..d533060 --- /dev/null +++ b/src/dns_tests.rs @@ -0,0 +1,646 @@ +//! Тесты DNS-резолвера. +//! +//! Оставлены дочерним модулем `dns`, а не вынесены в интеграционные: они лезут +//! в `parse_question`, `Cache` и `Resolver`, и делать всё это публичным только +//! ради проверок означало бы расширить внешнюю поверхность службы без всякой +//! пользы. + +use super::*; + +/// Собирает запрос: заголовок, имя метками, тип и класс. +fn query(name: &str, qtype: u16) -> Vec { + let mut pkt = vec![0x12, 0x34, 0x01, 0x00, 0, 1, 0, 0, 0, 0, 0, 0]; + for label in name.split('.') { + pkt.push(label.len() as u8); + pkt.extend_from_slice(label.as_bytes()); + } + pkt.push(0); + pkt.extend_from_slice(&qtype.to_be_bytes()); + pkt.extend_from_slice(&CLASS_IN.to_be_bytes()); + pkt +} + +fn parsed(name: &str, qtype: u16) -> Option { + let pkt = query(name, qtype); + let mut buf = [0u8; MAX_NAME]; + let q = parse_question(&pkt, &mut buf)?; + Some(String::from_utf8(buf[..q.len].to_vec()).unwrap()) +} + +/* ── разбор пакета ─────────────────────────────────────────── */ + +#[test] +fn name_is_read_and_lowercased() { + assert_eq!( + parsed("Gateway.Discord.GG", TYPE_A).as_deref(), + Some("gateway.discord.gg") + ); +} + +#[test] +fn truncated_packet_is_rejected() { + let pkt = query("discord.com", TYPE_A); + let mut buf = [0u8; MAX_NAME]; + for cut in HEADER..pkt.len() { + assert!( + parse_question(&pkt[..cut], &mut buf).is_none(), + "обрезка до {cut}" + ); + } +} + +#[test] +fn compression_pointer_in_question_is_rejected() { + let mut pkt = query("discord.com", TYPE_A); + pkt[HEADER] = 0xc0; // длина метки со старшими битами — это указатель + let mut buf = [0u8; MAX_NAME]; + assert!(parse_question(&pkt, &mut buf).is_none()); +} + +#[test] +fn a_response_on_input_is_not_parsed() { + let mut pkt = query("discord.com", TYPE_A); + pkt[2] |= 0x80; + let mut buf = [0u8; MAX_NAME]; + assert!(parse_question(&pkt, &mut buf).is_none()); +} + +/* ── совпадение по списку ──────────────────────────────────── */ + +#[test] +fn one_entry_covers_every_subdomain() { + let list = Blocklist::new(vec!["discord.com".into()]); + assert!(list.matches("discord.com")); + assert!(list.matches("cdn.discord.com")); + assert!(list.matches("a.b.c.discord.com")); + assert!(!list.matches("discord.com.evil.net")); + assert!(!list.matches("notdiscord.com")); + assert!(!list.matches("example.com")); +} + +#[test] +fn a_single_label_does_not_take_down_a_whole_zone() { + let list = Blocklist::new(vec!["com".into()]); + assert!(!list.matches("example.com")); +} + +#[test] +fn a_pasted_browser_address_becomes_a_domain() { + assert_eq!( + normalize_domain("https://www.Discord.com/app?x=1").as_deref(), + Some("discord.com") + ); + assert_eq!( + normalize_domain(" discord.gg. ").as_deref(), + Some("discord.gg") + ); + assert_eq!( + normalize_domain("discord.com:443").as_deref(), + Some("discord.com") + ); + assert_eq!(normalize_domain("discord"), None); + assert_eq!(normalize_domain(""), None); + assert_eq!(normalize_domain("a..b"), None); +} + +/* ── имена не из латиницы ──────────────────────────────────── */ + +// В сеть имя уходит только в punycode: браузер превращает `дискорд.рф` в +// `xn--d1aaliume.xn--p1ai` ещё до того, как обратиться к резолверу. Поэтому в +// списке обязана лежать именно эта форма — на кириллицу в запросе резолвер не +// наткнётся никогда. + +#[test] +fn a_punycode_address_is_accepted() { + assert_eq!( + normalize_domain("xn--d1aaliume.xn--p1ai").as_deref(), + Some("xn--d1aaliume.xn--p1ai") + ); + // Из адресной строки его копируют вместе со схемой, а путь при этом вполне + // может остаться кириллическим — до него разбор доходить не должен. + assert_eq!( + normalize_domain("https://XN--D1AALIUME.XN--P1AI/чат?x=1").as_deref(), + Some("xn--d1aaliume.xn--p1ai") + ); + assert_eq!( + normalize_domain("www.xn--h1alffa9f.xn--p1ai").as_deref(), + Some("xn--h1alffa9f.xn--p1ai") + ); +} + +/// Кириллица отвергается намеренно: до сети такое имя не дойдёт, и запись в +/// списке осталась бы мёртвой. Панель на это отвечает «не похоже на адрес +/// сайта», так что молчаливой потери не происходит. +#[test] +fn a_non_ascii_address_is_refused() { + assert_eq!(normalize_domain("дискорд.рф"), None); + assert_eq!(normalize_domain("https://дискорд.рф/чат"), None); + assert_eq!(normalize_domain("прив.ет"), None); +} + +#[test] +fn a_punycode_entry_covers_its_subdomains() { + let list = Blocklist::new(vec!["xn--d1aaliume.xn--p1ai".into()]); + assert!(list.matches("xn--d1aaliume.xn--p1ai")); + assert!(list.matches("www.xn--d1aaliume.xn--p1ai")); + assert!(list.matches("cdn.media.xn--d1aaliume.xn--p1ai")); + assert!(!list.matches("xn--d1aaliume.xn--p1ai.evil.net")); + // Соседнее имя в той же зоне закрывать не должно: `xn--p1ai` — это весь + // домен `.рф`. + assert!(!list.matches("xn--h1alffa9f.xn--p1ai")); +} + +/* ── синтезированные ответы ────────────────────────────────── */ + +#[test] +fn the_stub_answer_carries_a_null_address() { + let mut pkt = query("discord.com", TYPE_A); + pkt.resize(BUF, 0); + let mut name = [0u8; MAX_NAME]; + let q = parse_question(&pkt, &mut name).unwrap(); + let n = write_blocked(&mut pkt, &q).unwrap(); + + assert_eq!(packet_id(&pkt), 0x1234, "идентификатор клиента сохраняется"); + assert_eq!(pkt[2] & 0x80, 0x80, "это ответ"); + assert_eq!(rcode(&pkt), 0, "ошибки нет"); + assert_eq!( + u16::from_be_bytes([pkt[6], pkt[7]]), + 1, + "ровно одна запись в ответе" + ); + assert_eq!(&pkt[n - 4..n], &[0, 0, 0, 0], "адрес 0.0.0.0"); + assert_eq!( + &pkt[q.end..q.end + 2], + &[0xc0, 0x0c], + "имя записано ссылкой на вопрос" + ); +} + +#[test] +fn other_types_get_an_empty_success() { + let mut pkt = query("discord.com", 15); // MX + pkt.resize(BUF, 0); + let mut name = [0u8; MAX_NAME]; + let q = parse_question(&pkt, &mut name).unwrap(); + let n = write_blocked(&mut pkt, &q).unwrap(); + + assert_eq!(n, q.end, "ответ пуст"); + assert_eq!(u16::from_be_bytes([pkt[6], pkt[7]]), 0); + assert_eq!(rcode(&pkt), 0); +} + +/// Заглушка не имеет права выдавать адрес из класса IN в ответ на вопрос о +/// другом классе. +#[test] +fn the_stub_invents_no_address_for_a_foreign_class() { + const CLASS_CH: u16 = 3; + let mut pkt = query("discord.com", TYPE_A); + let class_at = pkt.len() - 2; + pkt[class_at..].copy_from_slice(&CLASS_CH.to_be_bytes()); + pkt.resize(BUF, 0); + + let mut name = [0u8; MAX_NAME]; + let q = parse_question(&pkt, &mut name).unwrap(); + let n = write_blocked(&mut pkt, &q).unwrap(); + + assert_eq!(n, q.end, "ответ пуст"); + assert_eq!(u16::from_be_bytes([pkt[6], pkt[7]]), 0); +} + +/* ── кэш ───────────────────────────────────────────────────── */ + +#[test] +fn the_cache_stamps_the_asking_client_id() { + let mut cache = Cache::default(); + let now = Instant::now(); + let mut key = [0u8; MAX_NAME + 4]; + let len = cache_key(b"example.com", TYPE_A, CLASS_IN, &mut key); + + let mut stored = query("example.com", TYPE_A); + stored[2] = 0x81; // ответ, рекурсия запрошена + stored[3] = 0x80; + cache.put(&key[..len], &stored, now); + + let mut out = [0u8; BUF]; + let n = cache.get(&key[..len], 0xbeef, &mut out, now).unwrap(); + assert_eq!(packet_id(&out[..n]), 0xbeef); + assert_eq!(&out[2..n], &stored[2..]); +} + +#[test] +fn expired_entries_are_not_served() { + let mut cache = Cache::default(); + let now = Instant::now(); + let mut key = [0u8; MAX_NAME + 4]; + let len = cache_key(b"example.com", TYPE_A, CLASS_IN, &mut key); + + let mut stored = query("example.com", TYPE_A); + stored[2] = 0x81; + stored[3] = 0x80; + cache.put(&key[..len], &stored, now); + + let mut out = [0u8; BUF]; + assert!(cache + .get(&key[..len], 1, &mut out, now + CACHE_TTL) + .is_none()); +} + +#[test] +fn server_failures_are_not_cached() { + let mut cache = Cache::default(); + let now = Instant::now(); + let mut key = [0u8; MAX_NAME + 4]; + let len = cache_key(b"example.com", TYPE_A, CLASS_IN, &mut key); + + let mut stored = query("example.com", TYPE_A); + stored[2] = 0x81; + stored[3] = 0x82; // SERVFAIL + cache.put(&key[..len], &stored, now); + + let mut out = [0u8; BUF]; + assert!(cache.get(&key[..len], 1, &mut out, now).is_none()); +} + +#[test] +fn record_types_do_not_share_a_cache_entry() { + let mut a = [0u8; MAX_NAME + 4]; + let mut aaaa = [0u8; MAX_NAME + 4]; + let la = cache_key(b"example.com", TYPE_A, CLASS_IN, &mut a); + let lq = cache_key(b"example.com", TYPE_AAAA, CLASS_IN, &mut aaaa); + assert_ne!(&a[..la], &aaaa[..lq]); +} + +/// Список задают до того, как резолвер на него подпишется: правила загружаются +/// при старте службы, а резолвер поднимается только при включении блокировки. +/// Значение обязано дожить до подписчика. +#[test] +fn a_list_set_before_the_resolver_starts_is_not_lost() { + let dns = Dns::new(); + dns.set_domains(["discord.com".to_string()]); + + let rx = dns.blocklist.subscribe(); + assert!(rx.borrow().matches("gateway.discord.com")); +} + +/* ── сквозная проверка ─────────────────────────────────────── */ + +/// Адрес, которым отвечает поддельный сервер: по нему видно, что ответ пришёл +/// сверху, а не собран здесь же. +const REAL_IP: [u8; 4] = [93, 184, 216, 34]; + +/// Поддельный вышестоящий сервер. Считает обращения — так видно, сходил ли +/// резолвер наверх или обошёлся своими силами. `pad` дописывает в конец ответа +/// лишние байты: так проверяется, что длинные ответы доходят целыми. +async fn fake_upstream(hits: Arc, pad: usize) -> SocketAddr { + let sock = UdpSocket::bind("127.0.0.1:0").await.unwrap(); + let addr = sock.local_addr().unwrap(); + tokio::spawn(async move { + let mut buf = vec![0u8; BUF]; + while let Ok((len, from)) = sock.recv_from(&mut buf).await { + hits.fetch_add(1, Ordering::Relaxed); + let mut name = [0u8; MAX_NAME]; + let Some(q) = parse_question(&buf[..len], &mut name) else { + continue; + }; + let n = write_blocked(&mut buf, &q).unwrap(); + buf[n - 4..n].copy_from_slice(&REAL_IP); + for (i, slot) in buf[n..n + pad].iter_mut().enumerate() { + *slot = (i % 251) as u8; + } + let _ = sock.send_to(&buf[..n + pad], from).await; + } + }); + addr +} + +async fn build_resolver(upstream: SocketAddr, blocked: &[&str]) -> Arc { + let domains: Vec = blocked.iter().map(|s| s.to_string()).collect(); + let (_tx, rx) = watch::channel(Arc::new(Blocklist::new(domains))); + // Отправителя нарочно роняем: `borrow` продолжает отдавать последнее + // значение, и это заодно проверяет, что резолвер не падает без него. + Arc::new(Resolver { + upstream_sock: Arc::new(UdpSocket::bind("127.0.0.1:0").await.unwrap()), + upstreams: vec![upstream], + active: AtomicUsize::new(0), + inflight: Mutex::new(HashMap::new()), + next_id: AtomicU16::new(1000), + cache: Mutex::new(Cache::default()), + blocklist: rx, + counters: Arc::default(), + attempts: Arc::default(), + }) +} + +/// Поднимает резолвер на случайном порту и возвращает его адрес. +async fn spawn_resolver(upstream: SocketAddr, blocked: &[&str]) -> (SocketAddr, watch::Sender) { + let client = Arc::new(UdpSocket::bind("127.0.0.1:0").await.unwrap()); + let addr = client.local_addr().unwrap(); + let resolver = build_resolver(upstream, blocked).await; + + let (stop_tx, stop_rx) = watch::channel(false); + tokio::spawn(client_loop(Arc::clone(&resolver), client, stop_rx.clone())); + tokio::spawn(upstream_loop(resolver, stop_rx)); + (addr, stop_tx) +} + +/// Спрашивает резолвер и возвращает ответ целиком. +async fn ask(to: SocketAddr, name: &str, id: u16) -> Vec { + ask_typed(to, name, TYPE_A, CLASS_IN, id).await +} + +async fn ask_typed(to: SocketAddr, name: &str, qtype: u16, qclass: u16, id: u16) -> Vec { + let sock = UdpSocket::bind("127.0.0.1:0").await.unwrap(); + let mut pkt = query(name, qtype); + set_packet_id(&mut pkt, id); + let end = pkt.len(); + pkt[end - 2..].copy_from_slice(&qclass.to_be_bytes()); + sock.send_to(&pkt, to).await.unwrap(); + + let mut buf = vec![0u8; BUF]; + let (len, _) = tokio::time::timeout(Duration::from_secs(2), sock.recv_from(&mut buf)) + .await + .expect("резолвер не ответил") + .unwrap(); + buf.truncate(len); + buf +} + +#[tokio::test] +async fn an_allowed_name_goes_upstream_and_comes_back_to_the_asker() { + let hits = Arc::new(AtomicU64::new(0)); + let upstream = fake_upstream(Arc::clone(&hits), 0).await; + let (addr, _stop) = spawn_resolver(upstream, &["discord.com"]).await; + + let reply = ask(addr, "example.com", 0xaaaa).await; + assert_eq!(packet_id(&reply), 0xaaaa, "клиенту вернулся его же номер"); + assert_eq!(&reply[reply.len() - 4..], &REAL_IP, "ответ пришёл сверху"); + assert_eq!(hits.load(Ordering::Relaxed), 1); +} + +#[tokio::test] +async fn a_blocked_name_never_reaches_the_upstream() { + let hits = Arc::new(AtomicU64::new(0)); + let upstream = fake_upstream(Arc::clone(&hits), 0).await; + let (addr, _stop) = spawn_resolver(upstream, &["discord.com"]).await; + + let reply = ask(addr, "gateway.discord.com", 0xbbbb).await; + assert_eq!(packet_id(&reply), 0xbbbb); + assert_eq!(&reply[reply.len() - 4..], &[0, 0, 0, 0], "адрес нулевой"); + assert_eq!( + hits.load(Ordering::Relaxed), + 0, + "запрещённое имя не должно доходить до вышестоящего сервера" + ); +} + +/// Кириллический домен приходит от браузера уже в punycode и в произвольном +/// регистре. Резолвер обязан узнать его и закрыть, ничего не спрашивая наверх. +#[tokio::test] +async fn a_punycode_name_is_blocked_on_the_wire() { + let hits = Arc::new(AtomicU64::new(0)); + let upstream = fake_upstream(Arc::clone(&hits), 0).await; + let (addr, _stop) = spawn_resolver(upstream, &["xn--d1aaliume.xn--p1ai"]).await; + + let reply = ask(addr, "WWW.XN--D1AALIUME.XN--P1AI", 0xcccc).await; + assert_eq!(packet_id(&reply), 0xcccc); + assert_eq!(&reply[reply.len() - 4..], &[0, 0, 0, 0], "адрес нулевой"); + assert_eq!( + hits.load(Ordering::Relaxed), + 0, + "запрещённое имя не должно доходить до вышестоящего сервера" + ); +} + +#[tokio::test] +async fn a_repeated_question_is_served_from_cache() { + let hits = Arc::new(AtomicU64::new(0)); + let upstream = fake_upstream(Arc::clone(&hits), 0).await; + let (addr, _stop) = spawn_resolver(upstream, &[]).await; + + let first = ask(addr, "example.com", 1).await; + let second = ask(addr, "example.com", 2).await; + + assert_eq!(packet_id(&second), 2, "номер второго спросившего"); + assert_eq!(first[2..], second[2..], "тело ответа то же"); + assert_eq!( + hits.load(Ordering::Relaxed), + 1, + "наверх сходили один раз на двоих" + ); +} + +#[tokio::test] +async fn answers_are_not_swapped_between_askers() { + let hits = Arc::new(AtomicU64::new(0)); + let upstream = fake_upstream(Arc::clone(&hits), 0).await; + let (addr, _stop) = spawn_resolver(upstream, &[]).await; + + // Разные имена, чтобы кэш не помог, и одновременно — чтобы наверх ушли оба + // запроса и вернулись вперемешку. + let (a, b) = tokio::join!( + ask(addr, "one.example", 0x0101), + ask(addr, "two.example", 0x0202) + ); + + assert_eq!(packet_id(&a), 0x0101); + assert_eq!(packet_id(&b), 0x0202); + + // Ответ повторяет вопрос, поэтому по секции вопроса видно, что каждому + // вернулось именно его имя, а не соседнее. + for (reply, name) in [(&a, "one.example"), (&b, "two.example")] { + let asked = query(name, TYPE_A); + assert_eq!(&reply[HEADER..asked.len()], &asked[HEADER..], "{name}"); + } +} + +/// Ответ длиннее четырёх килобайт обязан дойти целым. Приёмный буфер меньше +/// нужного отбросил бы хвост молча, и клиент получил бы обломок пакета. +#[tokio::test] +async fn a_long_reply_is_not_truncated() { + const PAD: usize = 5000; + let hits = Arc::new(AtomicU64::new(0)); + let upstream = fake_upstream(Arc::clone(&hits), PAD).await; + let (addr, _stop) = spawn_resolver(upstream, &[]).await; + + let reply = ask(addr, "big.example", 0x7777).await; + assert!(reply.len() > 4096, "ответ пришёл длиной {}", reply.len()); + assert_eq!(packet_id(&reply), 0x7777); + // Хвост-заполнитель проверяем побайтно: обрезка проявилась бы именно тут. + let tail = &reply[reply.len() - PAD..]; + assert!( + tail.iter().enumerate().all(|(i, b)| *b == (i % 251) as u8), + "хвост ответа испорчен" + ); +} + +/// В кэше ключ приведён к нижнему регистру, а сам пакет — нет. Второму +/// спросившему обязан вернуться его собственный вопрос: клиент вправе не +/// признать ответ, в котором имя написано иначе, чем он спрашивал. +#[tokio::test] +async fn the_cache_returns_the_asker_s_own_question() { + let hits = Arc::new(AtomicU64::new(0)); + let upstream = fake_upstream(Arc::clone(&hits), 0).await; + let (addr, _stop) = spawn_resolver(upstream, &[]).await; + + let _ = ask(addr, "Example.COM", 1).await; + let second = ask(addr, "example.com", 2).await; + + assert_eq!(hits.load(Ordering::Relaxed), 1, "второй ответ — из кэша"); + let asked = query("example.com", TYPE_A); + assert_eq!( + &second[HEADER..asked.len()], + &asked[HEADER..], + "вернулся чужой регистр имени" + ); +} + +/// Один и тот же вопрос в разных классах — разные записи. Общий ключ выдал бы +/// на вопрос про класс CH ответ, полученный для IN. +#[tokio::test] +async fn the_query_class_does_not_collide_in_the_cache() { + const CLASS_CH: u16 = 3; + let hits = Arc::new(AtomicU64::new(0)); + let upstream = fake_upstream(Arc::clone(&hits), 0).await; + let (addr, _stop) = spawn_resolver(upstream, &[]).await; + + let _ = ask_typed(addr, "version.bind", TYPE_A, CLASS_IN, 1).await; + let _ = ask_typed(addr, "version.bind", TYPE_A, CLASS_CH, 2).await; + + assert_eq!( + hits.load(Ordering::Relaxed), + 2, + "второй класс обязан уйти наверх, а не взяться из кэша первого" + ); +} + +/* ── замер производительности ──────────────────────────────── */ + +/// Считает, во что обходится решение по одному запросу на каждом из трёх путей, +/// и сколько запросов в секунду держит резолвер целиком. +/// +/// Из общего прогона исключён: меряет он машину не меньше, чем код, и на слабой +/// или занятой числа поплывут. Запуск: +/// `cargo test --release -- --ignored --nocapture benchmark` +#[tokio::test(flavor = "multi_thread")] +#[ignore] +async fn benchmark() { + const CPU_ROUNDS: u32 = 200_000; + const TRIPS: u32 = 20_000; + const CLIENTS: u32 = 32; + + let hits = Arc::new(AtomicU64::new(0)); + let upstream = fake_upstream(Arc::clone(&hits), 0).await; + + /* работа процессора без ввода-вывода — собственно цена решения */ + + let res = build_resolver(upstream, &["blocked.example"]).await; + let mut buf = vec![0u8; BUF]; + + // Кэш наполняем заранее: меряем попадание, а не промах. + let warm = query("cached.example", TYPE_A); + buf[..warm.len()].copy_from_slice(&warm); + let _ = res.decide(&mut buf, warm.len(), Instant::now()); + { + let mut name = [0u8; MAX_NAME]; + let q = parse_question(&warm, &mut name).unwrap(); + let mut key = [0u8; MAX_NAME + 4]; + let klen = cache_key(b"cached.example", q.qtype, q.qclass, &mut key); + let mut answer = warm.clone(); + answer[2] = 0x81; + answer[3] = 0x80; + res.cache + .lock() + .unwrap() + .put(&key[..klen], &answer, Instant::now()); + } + + let mut report = Vec::new(); + for (label, name) in [ + ("запрещённое имя", "blocked.example"), + ("попадание в кэш", "cached.example"), + ("уходит наверх", "fresh.example"), + ] { + let pkt = query(name, TYPE_A); + let start = std::time::Instant::now(); + for _ in 0..CPU_ROUNDS { + buf[..pkt.len()].copy_from_slice(&pkt); + std::hint::black_box(res.decide(&mut buf, pkt.len(), Instant::now())); + } + let each = start.elapsed() / CPU_ROUNDS; + report.push(format!(" {label:<16} {:>7} нс/запрос", each.as_nanos())); + } + + /* сквозной путь через сокеты */ + + let (addr, _stop) = spawn_resolver(upstream, &["blocked.example"]).await; + for (label, name, unique) in [ + ("запрещённое имя", "blocked.example", false), + ("попадание в кэш", "warm.example", false), + ("уходит наверх", "aaaa.example", true), + ] { + let sock = UdpSocket::bind("127.0.0.1:0").await.unwrap(); + let mut pkt = query(name, TYPE_A); + let mut got = vec![0u8; BUF]; + + // Для пути наверх имя обязано быть новым каждый раз, иначе со второго + // запроса отвечает кэш и меряется совсем не то. Меняем четыре байта + // первой метки — длина пакета при этом не плывёт. + let vary = |pkt: &mut [u8], i: u32| { + for slot in 0..4 { + pkt[HEADER + 1 + slot] = b'a' + ((i >> (slot * 5)) % 26) as u8; + } + }; + if !unique { + // прогрев: первый запрос наполняет кэш + sock.send_to(&pkt, addr).await.unwrap(); + sock.recv_from(&mut got).await.unwrap(); + } + + let start = std::time::Instant::now(); + for i in 0..TRIPS { + set_packet_id(&mut pkt, i as u16); + if unique { + vary(&mut pkt, i); + } + sock.send_to(&pkt, addr).await.unwrap(); + sock.recv_from(&mut got).await.unwrap(); + } + let each = start.elapsed() / TRIPS; + report.push(format!( + " {label:<16} {:>7} мкс туда-обратно", + each.as_micros() + )); + } + + /* пропускная способность: много клиентов сразу */ + + let start = std::time::Instant::now(); + let mut tasks = Vec::new(); + for c in 0..CLIENTS { + tasks.push(tokio::spawn(async move { + let sock = UdpSocket::bind("127.0.0.1:0").await.unwrap(); + let mut pkt = query("blocked.example", TYPE_A); + let mut got = vec![0u8; BUF]; + for i in 0..TRIPS / CLIENTS { + set_packet_id(&mut pkt, (c * 1000 + i) as u16); + sock.send_to(&pkt, addr).await.unwrap(); + sock.recv_from(&mut got).await.unwrap(); + } + })); + } + for t in tasks { + t.await.unwrap(); + } + let done = (TRIPS / CLIENTS) * CLIENTS; + let rate = done as f64 / start.elapsed().as_secs_f64(); + + println!("\nцена решения (без сокетов):"); + for line in &report[..3] { + println!("{line}"); + } + println!("\nсквозь сокеты, по одному запросу за раз:"); + for line in &report[3..] { + println!("{line}"); + } + println!("\nпропускная способность: {rate:.0} запросов/с при {CLIENTS} клиентах\n"); +} diff --git a/src/main.rs b/src/main.rs index 965077e..1d91479 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,6 +29,8 @@ use sysinfo::{Pid, System}; use tokio::sync::Mutex; mod auth; +mod dns; +mod netcfg; #[cfg(windows)] mod service; mod update; @@ -52,6 +54,11 @@ const LOG_FILE_LIMIT: u64 = 1024 * 1024; // фронт вшивается в exe на этапе компиляции const INDEX_HTML: &str = include_str!("../static/index.html"); const APP_JS: &str = include_str!("../static/app.js"); +/// Vue лежит рядом с исходниками, а не грузится с CDN. Причина не в скорости: +/// панель — единственный способ управлять службой, и брать её часть из сети +/// означало бы, что при поломке разрешения имён (а этим служба как раз и +/// занимается) чинить будет уже нечем. Заодно панель работает без интернета. +const VUE_JS: &str = include_str!("../static/vue.global.prod.js"); /// Нельзя убивать никогда — иначе BSOD или сломанный рабочий стол. /// Список имён — только первый рубеж, основной барьер в `is_system_path`. @@ -196,6 +203,24 @@ struct Rules { blocked: HashSet, // всегда в нижнем регистре allowed: HashSet, enforce: bool, + /// Домены, закрытые через свой DNS. Запись закрывает и поддомены. + /// + /// `serde(default)` здесь обязателен: без него правила, записанные прежней + /// версией, перестали бы читаться, а `Rules::load` молча подставил бы + /// пустые — то есть обновление стёрло бы все настройки родителя. + #[serde(default)] + blocked_sites: HashSet, + /// Включена ли блокировка сайтов целиком. + /// + /// По умолчанию выключена, и это осознанно: обновление приходит само, без + /// участия человека, и не должно молча переставлять настройки сети на + /// машине, где о новой возможности ещё не знают. Включает родитель в панели. + #[serde(default)] + sites_enforce: bool, + /// Запрещать ли обращения к чужим DNS-серверам мимо нас. По умолчанию нет: + /// правило трогает брандмауэр, и цена ошибки — машина без интернета. + #[serde(default)] + dns_lockdown: bool, } impl Default for Rules { @@ -205,6 +230,9 @@ impl Default for Rules { blocked: HashSet::new(), allowed: HashSet::new(), enforce: true, + blocked_sites: HashSet::new(), + sites_enforce: false, + dns_lockdown: false, } } } @@ -223,6 +251,12 @@ impl Rules { } } + /// Работает ли сейчас блокировка сайтов. Общая галочка «применять правила» + /// старше частной: выключив её, родитель ожидает, что выключилось всё. + fn sites_blocking(&self) -> bool { + self.enforce && self.sites_enforce + } + fn violates(&self, name_lower: &str) -> bool { match self.mode { Mode::Blacklist => self.blocked.contains(name_lower), @@ -246,10 +280,24 @@ struct AppState { log: Mutex>, update: Mutex, auth: auth::Auth, + dns: dns::Dns, } type Shared = Arc; +/// Передаёт резолверу то, что должно быть закрыто прямо сейчас. +/// +/// Снятая галочка «применять правила» обязана выключать и блокировку сайтов: +/// иначе панель показывала бы одно, а резолвер делал другое. Список правил при +/// этом сохраняется — выключение не должно стирать то, что родитель набирал. +fn sync_dns(rules: &Rules, dns: &dns::Dns) { + if rules.sites_blocking() { + dns.set_domains(rules.blocked_sites.iter().cloned()); + } else { + dns.set_domains(std::iter::empty()); + } +} + impl AppState { async fn log(&self, msg: impl Into) { let line = format!("[{}] {}", stamp(), msg.into()); @@ -340,6 +388,194 @@ async fn watchdog(state: Shared, mut shutdown: tokio::sync::watch::Receiver) { + // Каждый вызов netcfg — это запуск внешней программы на сотни миллисекунд. + // Прямо в задаче он занял бы поток рантайма, а вместе с ним встал бы и приём + // DNS-запросов на том же потоке: раз в пять минут вся машина замирала бы на + // полсекунды. Поэтому всё сюда — через `spawn_blocking`. + let Some(found) = blocking(netcfg::discover_upstreams).await else { + return; + }; + let upstreams = found.servers; + + // `intent` — чего мы хотели в прошлый раз, `applied` — что вышло. Различать + // их нужно, чтобы неудачная попытка не повторялась каждые пять секунд. + // Начальное значение берём из состояния системы, а не из нуля: адаптеры + // могли остаться переключёнными с прошлого запуска, и тогда выключенная + // блокировка обязана их вернуть. + let mut intent = found.redirected; + let mut applied = found.redirected; + let mut lockdown = false; + let mut ticks: u32 = 0; + // Пока фича выключена, резолвера нет вовсе: порт 53 свободен для чужих + // программ, которым он тоже бывает нужен. `Some` — сигнальный конец, по + // которому его задачи расходятся. + let mut resolver: Option> = None; + // Такт, раньше которого повторять попытку занять порт не нужно. + let mut retry_at: u32 = 0; + + loop { + let (asked, asked_lockdown) = { + let rules = state.rules.lock().await; + (rules.sites_blocking(), rules.dns_lockdown) + }; + let mut want = asked; + if !asked { + retry_at = 0; // после выключения пробуем сразу, а не через паузу + // Заодно убираем прошлую ошибку: выключенная фича не должна + // отчитываться о том, что у неё что-то не получилось. + state.dns.mark_stopped(); + } + + // Порядок на включении: сначала занять порт, и только потом уводить на + // себя адаптеры. Не поднялись — не трогаем сеть вовсе. + if want && resolver.is_none() { + if ticks < retry_at { + want = false; + } else { + let (stop_tx, stop_rx) = tokio::sync::watch::channel(false); + if dns::run(Arc::clone(&state), upstreams.clone(), stop_rx).await { + resolver = Some(stop_tx); + } else { + // Порт мог занять кто-то, кто его ещё освободит, поэтому + // попытки продолжаем — но редко: каждая пишет строку в + // журнал, и раз в пять секунд она вытеснила бы оттуда всё + // остальное. + retry_at = ticks + DNS_REAPPLY_EVERY; + want = false; + } + } + } + + // Запрет чужих серверов имеет смысл, только пока имена разрешаем мы + // сами. Оставить его при выключенной блокировке — верный способ + // получить машину, которая не разрешает имён вообще. + let want_lockdown = asked_lockdown && want; + + // Настройку переставляем и без изменений: адаптер могли добавить + // позже — поставить VPN или вторую сетевую карту, — и он ушёл бы мимо + // резолвера, а вместе с ним и весь запрещённый список. + // + // В повтор входит и обратный случай, `applied` без `want`: неудавшийся + // возврат настроек обязан повториться сам. Иначе он защёлкивался бы + // навсегда, и машина осталась бы направлена на резолвер, которого + // после остановки службы уже нет. + let periodic = ticks.is_multiple_of(DNS_REAPPLY_EVERY) && (want || applied); + if want != intent || periodic { + intent = want; + let outcome = blocking(move || { + if want { + netcfg::redirect_to_local() + } else { + netcfg::restore() + } + }) + .await + .unwrap_or_else(|| Err("задача настройки сети сорвалась".to_string())); + + match (outcome, want) { + (Ok(()), true) if !applied => { + applied = true; + state.log("DNS: адаптеры переведены на локальный резолвер").await; + } + (Ok(()), true) => applied = true, + (Ok(()), false) if applied => { + applied = false; + state.log("DNS: адаптеры возвращены к настройке роутера").await; + } + (Ok(()), false) => {} + (Err(e), _) => state.log(format!("DNS: настройка сети не удалась: {e}")).await, + } + } + + // Гасим резолвер только после того, как адаптеры перестали на него + // смотреть. Обратный порядок оставил бы промежуток, в котором система + // спрашивает имена у уже закрытого порта. + if !want && !applied { + if let Some(stop) = resolver.take() { + let _ = stop.send(true); + state.dns.mark_stopped(); + state.log("DNS: блокировка сайтов выключена, порт 53 свободен").await; + } + } + + if want_lockdown != lockdown { + lockdown = want_lockdown; + let list = upstreams.clone(); + let outcome = blocking(move || netcfg::set_lockdown(lockdown, &list)) + .await + .unwrap_or_else(|| Err("задача настройки брандмауэра сорвалась".to_string())); + + match outcome { + Ok(()) if lockdown => { + state.log("DNS: чужие серверы имён закрыты брандмауэром").await + } + Ok(()) => state.log("DNS: запрет чужих серверов имён снят").await, + Err(e) => state.log(format!("DNS: правило брандмауэра не удалось: {e}")).await, + } + } + + ticks = ticks.wrapping_add(1); + tokio::select! { + _ = tokio::time::sleep(DNS_TICK) => {} + _ = shutdown.changed() => break, + } + } + + // Служба останавливается — систему нужно оставить в рабочем виде. Забыть об + // этом означало бы машину без разрешения имён вообще. Резолвер гасим после + // возврата настроек, по той же причине, что и выше. + if applied || lockdown { + let outcome = blocking(move || { + let restored = if applied { netcfg::restore() } else { Ok(()) }; + if lockdown { + netcfg::clear_firewall(); + } + restored + }) + .await + .unwrap_or_else(|| Err("задача возврата настроек сорвалась".to_string())); + + match outcome { + Ok(()) => state.log("DNS: настройки сети возвращены").await, + // Метка на диске осталась, поэтому следующий запуск службы увидит + // незакрытое переключение и повторит возврат. Молчать здесь нельзя: + // до тех пор машина спрашивает имена у порта, которого уже нет. + Err(e) => { + state + .log(format!("DNS: ВЕРНУТЬ НАСТРОЙКИ НЕ УДАЛОСЬ ({e}) — имена не будут разрешаться, помогает syshelper.exe --restore-dns")) + .await + } + } + } + if let Some(stop) = resolver.take() { + let _ = stop.send(true); + state.dns.mark_stopped(); + } +} + +/// Уводит блокирующий вызов на отдельный поток. `None` означает, что поток +/// сорвался, — вызывающему решать, что это значит. +async fn blocking(work: F) -> Option +where + F: FnOnce() -> T + Send + 'static, + T: Send + 'static, +{ + tokio::task::spawn_blocking(work).await.ok() +} + /* ── статика ───────────────────────────────────────────────── */ async fn index() -> Html<&'static str> { @@ -347,9 +583,17 @@ async fn index() -> Html<&'static str> { } async fn app_js() -> impl IntoResponse { + javascript(APP_JS) +} + +async fn vue_js() -> impl IntoResponse { + javascript(VUE_JS) +} + +fn javascript(body: &'static str) -> impl IntoResponse { ( [(header::CONTENT_TYPE, "application/javascript; charset=utf-8")], - APP_JS, + body, ) } @@ -398,10 +642,20 @@ async fn set_rules( ) -> Json { new_rules.blocked = new_rules.blocked.iter().map(|s| s.to_lowercase()).collect(); new_rules.allowed = new_rules.allowed.iter().map(|s| s.to_lowercase()).collect(); + // Домены нормализуем и здесь: панель шлёт правила целиком, и через этот + // путь в список попал бы адрес со схемой и путём, который ни с чем не + // совпадёт. + new_rules.blocked_sites = new_rules + .blocked_sites + .iter() + .filter_map(|s| dns::normalize_domain(s)) + .collect(); + { let mut rules = state.rules.lock().await; *rules = new_rules; rules.save(); + sync_dns(&rules, &state.dns); } state.log("правила обновлены").await; Json(json!({ "ok": true })) @@ -459,6 +713,54 @@ async fn rule_remove( Json(json!({ "ok": true, "name": name })) } +/* ── сайты ─────────────────────────────────────────────────── */ + +/// Закрывает домен вместе со всеми поддоменами. +async fn site_block( + State(state): State, + Json(req): Json, +) -> Json { + let Some(domain) = dns::normalize_domain(&req.name) else { + return Json(json!({ "ok": false, "error": "не похоже на адрес сайта" })); + }; + + { + let mut rules = state.rules.lock().await; + rules.blocked_sites.insert(domain.clone()); + rules.save(); + // Резолвер подхватывает список сразу — ждать следующего обхода незачем. + sync_dns(&rules, &state.dns); + } + state.log(format!("сайт закрыт: {domain}")).await; + Json(json!({ "ok": true, "name": domain })) +} + +async fn site_remove( + State(state): State, + Json(req): Json, +) -> Json { + // Убираем и по нормализованному имени, и по введённому: в списке могла + // остаться запись, добавленная до того, как появилась нормализация. + let typed = req.name.trim().to_lowercase(); + let domain = dns::normalize_domain(&req.name); + + { + let mut rules = state.rules.lock().await; + rules.blocked_sites.remove(&typed); + if let Some(d) = &domain { + rules.blocked_sites.remove(d); + } + rules.save(); + sync_dns(&rules, &state.dns); + } + state.log(format!("сайт открыт: {typed}")).await; + Json(json!({ "ok": true, "name": typed })) +} + +async fn dns_status(State(state): State) -> Json { + Json(state.dns.status()) +} + async fn kill_now( State(state): State, Json(req): Json, @@ -579,6 +881,18 @@ fn main() { Err(e) => append_log_file(&format!("[{}] нет рантайма: {e}", stamp())), } } + // Возврат настроек сети без запуска службы. Нужен установщику при + // удалении и как ручная аварийная кнопка: если служба почему-то не + // поднимается, адаптеры остаются смотреть в никуда, и без этого ключа + // чинить машину пришлось бы вручную через свойства адаптера. + Some("--restore-dns") => { + let note = match netcfg::restore() { + Ok(()) => "настройки DNS возвращены".to_string(), + Err(e) => format!("не удалось вернуть DNS: {e}"), + }; + netcfg::clear_firewall(); + append_log_file(&format!("[{}] {note}", stamp())); + } // Установщик задаёт пароль так, а не аргументом: аргумент был бы виден // в списке процессов. Файл нам отдают во временном каталоге установки. Some("--set-password-file") => match args.get(1) { @@ -636,24 +950,31 @@ async fn bind_panel( /// Поднимает панель и фоновые задачи. Завершается по сигналу из `shutdown` — /// его шлёт SCM при остановке службы. async fn serve(shutdown: tokio::sync::watch::Receiver) { + let rules = Rules::load(); let state: Shared = Arc::new(AppState { - rules: Mutex::new(Rules::load()), + rules: Mutex::new(rules), sys: Mutex::new(System::new_all()), // журнал переживает перезапуск службы — иначе после обновления панель // показывала бы пустоту и было бы неясно, что вообще произошло log: Mutex::new(load_log_tail()), update: Mutex::new(update::UpdateStatus::new()), auth: auth::Auth::load(), + dns: dns::Dns::new(), }); + sync_dns(&*state.rules.lock().await, &state.dns); let app = Router::new() .route("/", get(index)) .route("/app.js", get(app_js)) + .route("/vue.js", get(vue_js)) .route("/processes", get(list_processes)) .route("/rules", get(get_rules).post(set_rules)) .route("/rules/block", post(rule_block)) .route("/rules/allow", post(rule_allow)) .route("/rules/remove", post(rule_remove)) + .route("/sites/block", post(site_block)) + .route("/sites/remove", post(site_remove)) + .route("/dns", get(dns_status)) .route("/kill", post(kill_now)) .route("/kill/pid", post(kill_pid)) .route("/kill/random", post(kill_random)) @@ -675,6 +996,9 @@ async fn serve(shutdown: tokio::sync::watch::Receiver) { // применяются, а панель дождётся освобождения порта. tokio::spawn(watchdog(Arc::clone(&state), shutdown.clone())); tokio::spawn(update::run(Arc::clone(&state))); + // Резолвер поднимаем здесь же: он тоже не должен зависеть от того, удалось + // ли занять порт панели. + let dns_task = tokio::spawn(dns_manager(Arc::clone(&state), shutdown.clone())); state.log("служба запущена").await; state @@ -682,7 +1006,11 @@ async fn serve(shutdown: tokio::sync::watch::Receiver) { .await; let Some(listener) = bind_panel(&state, shutdown.clone()).await else { - return; // службу остановили, пока ждали порт + // Службу остановили, пока ждали порт. Уйти молча нельзя: адаптеры уже + // могут смотреть на нас, и без возврата настроек машина осталась бы + // без разрешения имён. + let _ = dns_task.await; + return; }; state .log(format!("панель на {PANEL_URL} и на порту 8787 в локальной сети")) @@ -698,5 +1026,8 @@ async fn serve(shutdown: tokio::sync::watch::Receiver) { if let Err(e) = result { append_log_file(&format!("[{}] сервер остановлен: {e}", stamp())); } + // Ждём именно здесь: рантайм вот-вот остановится, и возврат настроек сети + // не успел бы доработать. + let _ = dns_task.await; state.log("служба остановлена").await; } \ No newline at end of file diff --git a/src/netcfg.rs b/src/netcfg.rs new file mode 100644 index 0000000..80251d7 --- /dev/null +++ b/src/netcfg.rs @@ -0,0 +1,444 @@ +//! Настройка сети Windows под свой резолвер. +//! +//! Резолвер сам по себе бесполезен: система должна начать спрашивать имена +//! именно у него. Здесь три отдельные обязанности — узнать, кого спрашивать +//! самим, перевести адаптеры на 127.0.0.1 и (по желанию) закрыть обход через +//! чужие DNS-серверы. +//! +//! Всё делается через `Get-NetAdapter`/`Set-DnsClientServerAddress`, а не через +//! разбор вывода `netsh`: последний переведён на язык системы, и на русской +//! Windows разбор колонок разъехался бы. Команды PowerShell стоят сотни +//! миллисекунд, но вызываются они на смене настроек и раз в несколько минут, +//! а не на запросах, — на путь разрешения имён это не влияет. + +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + +#[cfg(windows)] +use std::{os::windows::process::CommandExt, process::Command}; + +#[cfg(windows)] +use crate::CREATE_NO_WINDOW; + +/// Куда обращаться, если своих серверов найти не удалось. +const FALLBACK: [Ipv4Addr; 2] = [Ipv4Addr::new(1, 1, 1, 1), Ipv4Addr::new(8, 8, 8, 8)]; +/// Общее имя правил брандмауэра: по нему же они и снимаются. +#[cfg_attr(not(windows), allow(dead_code))] +pub const FIREWALL_RULE: &str = "SysHelper DNS"; + +fn upstream_file() -> std::path::PathBuf { + crate::data_dir().join("dns.json") +} + +/// Метка «адаптеры переключены на нас и ещё не возвращены». +/// +/// Выводить это косвенно нельзя. Пустой опрос системы означает и «адаптеры +/// смотрят на петлю», и «сети на машине нет вовсе» — а по второму признаку +/// программа сбросила бы на DHCP настройку, которую в глаза не видела. Файл +/// пишется в момент переключения и удаляется при возврате, так что врать ему +/// не о чем. +fn marker_file() -> std::path::PathBuf { + crate::data_dir().join("dns_active") +} + +#[cfg_attr(not(windows), allow(dead_code))] +fn set_marker(active: bool) { + if active { + let _ = std::fs::write(marker_file(), b"1"); + } else { + let _ = std::fs::remove_file(marker_file()); + } +} + +/* ── вышестоящие серверы ───────────────────────────────────── */ + +pub struct Upstreams { + pub servers: Vec, + /// Адаптеры переключены на нас и ещё не возвращены — так говорит метка, + /// оставленная прошлым запуском. Если закрывать больше нечего, настройку + /// надо снять: иначе адаптеры остались бы направлены на резолвер после + /// того, как родитель выключил блокировку. + pub redirected: bool, +} + +/// Находит серверы, которым переадресовывать запросы. +/// +/// Живому опросу верим больше, чем запомненному: сеть могла смениться. Но и +/// запоминаем каждый удачный опрос — на случай, если однажды он не даст ничего +/// (например, службу подняли раньше сетевой подсистемы). +pub fn discover_upstreams() -> Upstreams { + let live = probe_system(); + if !live.is_empty() { + remember(&live); + } + + let stored = std::fs::read_to_string(upstream_file()) + .ok() + .and_then(|raw| serde_json::from_str::>(&raw).ok()) + .map(|list| { + list.iter() + .filter_map(|s| s.parse::().ok()) + .filter(usable) + .collect::>() + }) + .unwrap_or_default(); + + Upstreams { + servers: with_port(with_fallback(if live.is_empty() { stored } else { live })), + redirected: marker_file().exists(), + } +} + +/// Дописывает публичные серверы в хвост списка. +/// +/// Две причины, и обе про машину, которую унесли из дома. Первая: запомненный +/// адрес роутера в чужой сети недостижим, и без запасного варианта имена +/// перестают разрешаться совсем. Вторая: переход на запасной сервер в +/// `janitor` включается только при длине списка больше единицы — с одним +/// адресом он был бы мёртвым кодом. +/// +/// Порядок важен: свои серверы идут первыми, потому что только они знают имена +/// внутри домашней сети. +fn with_fallback(mut addrs: Vec) -> Vec { + for extra in FALLBACK.map(IpAddr::V4) { + if !addrs.contains(&extra) { + addrs.push(extra); + } + } + addrs +} + +fn with_port(addrs: Vec) -> Vec { + addrs.into_iter().map(|ip| SocketAddr::new(ip, 53)).collect() +} + +/// Петля — это мы сами, и спрашивать себя означало бы вечный круг. Неуказанный +/// адрес и локальные адреса канала как сервер тоже смысла не имеют. +fn usable(ip: &IpAddr) -> bool { + match ip { + IpAddr::V4(v4) => !v4.is_loopback() && !v4.is_unspecified() && !v4.is_link_local(), + IpAddr::V6(v6) => !v6.is_loopback() && !v6.is_unspecified(), + } +} + +fn remember(addrs: &[IpAddr]) { + let list: Vec = addrs.iter().map(IpAddr::to_string).collect(); + if let Ok(raw) = serde_json::to_string(&list) { + let _ = std::fs::write(upstream_file(), raw); + } +} + +/// Спрашивает у системы, кому она сама отдаёт запросы имён. +/// +/// Три источника подряд, и третий — главный. `Get-DnsClientServerAddress` +/// возвращает действующую настройку, а после переключения она наша, то есть +/// 127.0.0.1, и опрос дал бы пустоту. Значения от DHCP лежат в реестре +/// отдельным ключом, наш статический адрес их не затирает — по ним и видно +/// настоящий сервер, в том числе после переезда машины в другую сеть. +#[cfg(windows)] +fn probe_system() -> Vec { + const SCRIPT: &str = "\ + $ErrorActionPreference='SilentlyContinue';\ + $i=(Get-NetRoute -DestinationPrefix '0.0.0.0/0'|Sort-Object RouteMetric|Select-Object -First 1).InterfaceIndex;\ + $a=@();\ + if($i){$a+=(Get-DnsClientServerAddress -InterfaceIndex $i -AddressFamily IPv4).ServerAddresses};\ + $a+=(Get-DnsClientServerAddress -AddressFamily IPv4).ServerAddresses;\ + foreach($k in Get-ChildItem 'HKLM:\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces'){\ + $v=(Get-ItemProperty $k.PSPath).DhcpNameServer;\ + if($v){$a+=($v -split '[ ,]')}\ + };\ + $a|Where-Object{$_}|Select-Object -Unique"; + + parse_addrs(&powershell(SCRIPT).unwrap_or_default()) +} + +#[cfg(not(windows))] +fn probe_system() -> Vec { + Vec::new() +} + +#[cfg_attr(not(windows), allow(dead_code))] +fn parse_addrs(text: &str) -> Vec { + let mut out = Vec::new(); + for line in text.lines() { + if let Ok(ip) = line.trim().parse::() { + if usable(&ip) && !out.contains(&ip) { + out.push(ip); + } + } + } + out +} + +/* ── переключение адаптеров ────────────────────────────────── */ + +/// Переводит адаптеры на локальный резолвер. +/// +/// Настройка ставится всем адаптерам, а не только поднятым: Wi-Fi, который +/// подключат через час, и VPN, который поставят завтра, иначе прошли бы мимо +/// блокировки. Статический адрес сервера переживает переподключение, так что +/// достаточно проставить его один раз и обновлять изредка. +#[cfg(windows)] +pub fn redirect_to_local() -> Result<(), String> { + // Счётчик обязателен. Отдельная попытка на адаптер завёрнута в `try`, иначе + // одна отключённая виртуальная карта срывала бы всю настройку, — но тогда + // PowerShell завершается успешно, даже не тронув ни одного адаптера, и + // вызывающий рапортовал бы о защите, которой нет. Шестая версия в счёт не + // идёт: на машине без IPv6 она падает законно. + const SCRIPT: &str = "\ + $ErrorActionPreference='Stop';\ + $n=0;\ + foreach($a in Get-NetAdapter){\ + try{Set-DnsClientServerAddress -InterfaceIndex $a.ifIndex -ServerAddresses ('127.0.0.1');$n++}catch{};\ + try{Set-DnsClientServerAddress -InterfaceIndex $a.ifIndex -ServerAddresses ('::1')}catch{}\ + };\ + Clear-DnsClientCache;\ + $n"; + + let done = count_from(&powershell(SCRIPT)?); + if done == 0 { + return Err("ни один адаптер не переключился".to_string()); + } + set_marker(true); + Ok(()) +} + +/// Последнее число в выводе скрипта. +#[cfg_attr(not(windows), allow(dead_code))] +fn count_from(text: &str) -> u32 { + text.lines() + .rev() + .find_map(|line| line.trim().parse::().ok()) + .unwrap_or(0) +} + +/// Возвращает адаптерам настройку от роутера. +/// +/// Возврат именно к DHCP, а не к тому, что стояло раньше: различить заданный +/// вручную адрес и полученный по DHCP штатными средствами нельзя, а домашняя +/// машина в подавляющем большинстве случаев получает DNS от роутера. Прежние +/// адреса лежат в `dns.json` — если настройка была ручной, её видно там. +#[cfg(windows)] +pub fn restore() -> Result<(), String> { + const SCRIPT: &str = "\ + $ErrorActionPreference='Stop';\ + $n=0;\ + foreach($a in Get-NetAdapter){\ + try{Set-DnsClientServerAddress -InterfaceIndex $a.ifIndex -ResetServerAddresses;$n++}catch{}\ + };\ + Clear-DnsClientCache;\ + $n"; + + let done = count_from(&powershell(SCRIPT)?); + if done == 0 { + return Err("ни один адаптер не вернулся к настройке роутера".to_string()); + } + // Метку снимаем только после удачного возврата: иначе следующий запуск + // счёл бы систему чистой и не повторил бы попытку. + set_marker(false); + Ok(()) +} + +#[cfg(not(windows))] +pub fn redirect_to_local() -> Result<(), String> { + Err("переключение DNS есть только в Windows".to_string()) +} + +#[cfg(not(windows))] +pub fn restore() -> Result<(), String> { + Ok(()) +} + +/* ── запрет обхода ─────────────────────────────────────────── */ + +/// Дополняет список адресов до полного диапазона IPv4. +/// +/// Брандмауэр Windows не умеет исключений: правило «запретить» всегда сильнее +/// правила «разрешить», поэтому «всем нельзя, а нам можно» одной парой правил не +/// выразить. Зато `remoteip` принимает диапазоны — значит, запрет пишется как +/// «весь IPv4, кроме вот этих адресов». +#[cfg_attr(not(windows), allow(dead_code))] +fn complement_ranges(allowed: &[Ipv4Addr]) -> Vec { + let mut points: Vec = allowed.iter().map(|ip| u32::from(*ip)).collect(); + points.sort_unstable(); + points.dedup(); + + let mut ranges = Vec::new(); + let mut start: u32 = 0; + for point in points { + if point > start { + ranges.push(format!( + "{}-{}", + Ipv4Addr::from(start), + Ipv4Addr::from(point - 1) + )); + } + // Разрешённый адрес может оказаться последним возможным — тогда + // продолжать некуда, и остаток диапазона пуст. + match point.checked_add(1) { + Some(next) => start = next, + None => return ranges, + } + } + ranges.push(format!("{}-{}", Ipv4Addr::from(start), Ipv4Addr::from(u32::MAX))); + ranges +} + +/// Закрывает обращения к чужим DNS-серверам мимо нас. +/// +/// Без этого запрет обходится в три клика: программа, знающая адрес 8.8.8.8, +/// спросит его напрямую. Правило рискованное — ошибка в диапазонах оставит +/// машину без разрешения имён, — поэтому включается отдельной галочкой, а не +/// само собой. +#[cfg(windows)] +pub fn set_lockdown(on: bool, upstreams: &[SocketAddr]) -> Result<(), String> { + clear_firewall(); + if !on { + return Ok(()); + } + + let allowed: Vec = upstreams + .iter() + .filter_map(|addr| match addr.ip() { + IpAddr::V4(v4) => Some(v4), + IpAddr::V6(_) => None, + }) + .collect(); + if allowed.is_empty() { + return Err("нет ни одного адреса, которому можно оставить доступ".to_string()); + } + let ranges = complement_ranges(&allowed).join(","); + + for (proto, port) in [("UDP", "53"), ("TCP", "53"), ("TCP", "853")] { + netsh(&[ + "advfirewall", + "firewall", + "add", + "rule", + &format!("name={FIREWALL_RULE}"), + "dir=out", + "action=block", + &format!("protocol={proto}"), + &format!("remoteport={port}"), + &format!("remoteip={ranges}"), + ])?; + } + + // Шестая версия закрывается целиком: наверх мы ходим по IPv4, так что + // исключать в ней нечего, а оставленная открытой она и была бы обходом. + for proto in ["UDP", "TCP"] { + netsh(&[ + "advfirewall", + "firewall", + "add", + "rule", + &format!("name={FIREWALL_RULE}"), + "dir=out", + "action=block", + &format!("protocol={proto}"), + "remoteport=53,853", + "remoteip=::/0", + ])?; + } + + // Windows 11 умеет сама уводить запросы в DNS поверх HTTPS — тогда они + // пройдут по 443-му порту мимо всех правил выше. + let _ = reg_add( + r"HKLM\SYSTEM\CurrentControlSet\Services\Dnscache\Parameters", + "EnableAutoDoh", + "0", + ); + Ok(()) +} + +#[cfg(windows)] +pub fn clear_firewall() { + let _ = netsh(&[ + "advfirewall", + "firewall", + "delete", + "rule", + &format!("name={FIREWALL_RULE}"), + ]); + // Значение вернуть в прежнее нельзя — по умолчанию его вообще нет, а + // оставленное после удаления программы оно молча меняло бы поведение + // Windows. Поэтому именно удаляем, а не пишем ноль. + let _ = reg_delete( + r"HKLM\SYSTEM\CurrentControlSet\Services\Dnscache\Parameters", + "EnableAutoDoh", + ); +} + +#[cfg(not(windows))] +pub fn set_lockdown(_on: bool, _upstreams: &[SocketAddr]) -> Result<(), String> { + Err("правила брандмауэра есть только в Windows".to_string()) +} + +#[cfg(not(windows))] +pub fn clear_firewall() {} + +/* ── запуск внешних команд ─────────────────────────────────── */ + +#[cfg(windows)] +fn powershell(script: &str) -> Result { + let out = Command::new("powershell.exe") + .args([ + "-NoProfile", + "-NonInteractive", + "-ExecutionPolicy", + "Bypass", + "-Command", + script, + ]) + .creation_flags(CREATE_NO_WINDOW) + .output() + .map_err(|e| e.to_string())?; + + if out.status.success() { + Ok(String::from_utf8_lossy(&out.stdout).into_owned()) + } else { + Err(String::from_utf8_lossy(&out.stderr).trim().to_string()) + } +} + +#[cfg(windows)] +fn netsh(args: &[&str]) -> Result<(), String> { + let out = Command::new("netsh.exe") + .args(args) + .creation_flags(CREATE_NO_WINDOW) + .output() + .map_err(|e| e.to_string())?; + + if out.status.success() { + Ok(()) + } else { + Err(String::from_utf8_lossy(&out.stdout).trim().to_string()) + } +} + +#[cfg(windows)] +fn reg_add(key: &str, name: &str, value: &str) -> Result<(), String> { + let ok = Command::new("reg.exe") + .args(["add", key, "/v", name, "/t", "REG_DWORD", "/d", value, "/f"]) + .creation_flags(CREATE_NO_WINDOW) + .status() + .map_err(|e| e.to_string())? + .success(); + + ok.then_some(()).ok_or_else(|| "reg add не сработал".to_string()) +} + +/// Убирает значение. Отсутствие тоже считаем успехом: убрать то, чего нет, — +/// ровно тот результат, которого добивались. +#[cfg(windows)] +fn reg_delete(key: &str, name: &str) -> Result<(), String> { + Command::new("reg.exe") + .args(["delete", key, "/v", name, "/f"]) + .creation_flags(CREATE_NO_WINDOW) + .status() + .map_err(|e| e.to_string()) + .map(|_| ()) +} + +#[cfg(test)] +#[path = "netcfg_tests.rs"] +mod tests; diff --git a/src/netcfg_tests.rs b/src/netcfg_tests.rs new file mode 100644 index 0000000..102c3e4 --- /dev/null +++ b/src/netcfg_tests.rs @@ -0,0 +1,92 @@ +//! Тесты настройки сети. +//! +//! Покрыто только то, что представляет собой чистую логику. Всё, что уходит в +//! PowerShell и netsh, требует живой Windows, поэтому проверять стоит ровно то, +//! что было бы неверно на любой платформе: арифметику адресов и разбор вывода +//! внешних команд. + +use super::*; + +fn v4(s: &str) -> Ipv4Addr { + s.parse().unwrap() +} + +/* ── диапазоны для брандмауэра ─────────────────────────────── */ + +#[test] +fn the_complement_steps_over_the_allowed_address() { + let ranges = complement_ranges(&[v4("1.1.1.1")]); + assert_eq!(ranges, vec!["0.0.0.0-1.1.1.0", "1.1.1.2-255.255.255.255"]); +} + +#[test] +fn the_complement_merges_adjacent_addresses() { + let ranges = complement_ranges(&[v4("8.8.8.8"), v4("8.8.8.9")]); + assert_eq!(ranges, vec!["0.0.0.0-8.8.8.7", "8.8.8.10-255.255.255.255"]); +} + +#[test] +fn the_complement_ignores_order_and_duplicates() { + let ordered = complement_ranges(&[v4("1.1.1.1"), v4("8.8.8.8")]); + let shuffled = complement_ranges(&[v4("8.8.8.8"), v4("1.1.1.1"), v4("8.8.8.8")]); + assert_eq!(ordered, shuffled); + assert_eq!( + ordered, + vec![ + "0.0.0.0-1.1.1.0", + "1.1.1.2-8.8.8.7", + "8.8.8.9-255.255.255.255" + ] + ); +} + +#[test] +fn the_edges_of_the_range_do_not_overflow() { + assert_eq!( + complement_ranges(&[v4("0.0.0.0")]), + vec!["0.0.0.1-255.255.255.255"] + ); + assert_eq!( + complement_ranges(&[v4("255.255.255.255")]), + vec!["0.0.0.0-255.255.255.254"] + ); + assert_eq!( + complement_ranges(&[v4("0.0.0.0"), v4("255.255.255.255")]), + vec!["0.0.0.1-255.255.255.254"] + ); +} + +/* ── поиск вышестоящих серверов ────────────────────────────── */ + +#[test] +fn public_servers_are_appended_at_the_end() { + let router: IpAddr = "192.168.1.1".parse().unwrap(); + let list = with_fallback(vec![router]); + + assert_eq!(list[0], router, "свой сервер остаётся первым"); + assert_eq!(list.len(), 3, "и есть куда переходить при отказе"); +} + +#[test] +fn no_duplicates_appear_in_the_server_list() { + let cloudflare: IpAddr = "1.1.1.1".parse().unwrap(); + assert_eq!(with_fallback(vec![cloudflare]).len(), 2); +} + +#[test] +fn loopback_and_the_null_address_make_no_upstream() { + let parsed = parse_addrs("127.0.0.1\n0.0.0.0\n192.168.1.1\n\n192.168.1.1\nмусор\n"); + assert_eq!(parsed, vec!["192.168.1.1".parse::().unwrap()]); +} + +/* ── вывод внешних команд ──────────────────────────────────── */ + +#[test] +fn the_adapter_count_is_read_from_the_tail_of_the_output() { + assert_eq!(count_from("WARNING: что-то\n3\n"), 3); + assert_eq!(count_from("0"), 0); + // Пустой или неожиданный вывод обязан читаться как «ничего не вышло»: + // иначе сбой PowerShell выглядел бы как успешное переключение. + assert_eq!(count_from(""), 0); + assert_eq!(count_from("Access is denied."), 0); +} diff --git a/static/app.js b/static/app.js index 351bd2c..8cf04b7 100644 --- a/static/app.js +++ b/static/app.js @@ -17,7 +17,12 @@ async function api(path, body) { /* ── composables ───────────────────────────────────────────── */ function useRules() { - const rules = ref({ mode: 'blacklist', blocked: [], allowed: [], enforce: true }); + // форма объекта должна совпадать с серверной с самого начала: `save` шлёт + // правила целиком, и отсутствующее здесь поле сервер прочитал бы как пустое + const rules = ref({ + mode: 'blacklist', blocked: [], allowed: [], enforce: true, + blocked_sites: [], sites_enforce: false, dns_lockdown: false, + }); const load = async () => { rules.value = await api('/rules'); }; const block = (name) => api('/rules/block', { name }).then(load); const allow = (name) => api('/rules/allow', { name }).then(load); @@ -54,6 +59,19 @@ function useLog() { return { lines, load }; } +function useSites() { + const dns = ref({ + listening: false, upstream: '', queries: 0, blocked: 0, + cache_hits: 0, timeouts: 0, error: null, attempts: [], + }); + const load = async () => { dns.value = await api('/dns'); }; + // сам список закрытых сайтов живёт в правилах, здесь — только состояние + // резолвера и попытки; поэтому после правки перечитываем и то, и другое + const block = (name) => api('/sites/block', { name }); + const remove = (name) => api('/sites/remove', { name }); + return { dns, load, block, remove }; +} + function useUpdate() { const info = ref({ current: '', latest: null, available: false, phase: 'idle', checked_at: null, error: null }); const load = async () => { info.value = await api('/update'); }; @@ -152,6 +170,95 @@ const RulesPanel = { `, }; +const SitesPanel = { + components: { NameChips }, + props: { + sites: { type: Array, default: () => [] }, + dns: { type: Object, required: true }, + // enabled — сама галочка, active — работает ли блокировка на деле: + // общий выключатель «применять правила» гасит и её + enabled: { type: Boolean, default: false }, + active: { type: Boolean, default: false }, + lockdown: { type: Boolean, default: false }, + }, + emits: ['block', 'remove', 'enable', 'lockdown'], + setup(props, { emit }) { + const input = ref(''); + const submit = () => { + const value = input.value.trim(); + if (!value) return; + emit('block', value); + input.value = ''; + }; + + const status = computed(() => { + const d = props.dns; + if (props.enabled && !props.active) return 'выключено общей галочкой «применять правила»'; + if (!props.active) return 'выключено — служба имена не разрешает'; + if (d.error) return d.error; + if (!d.listening) return 'резолвер запускается…'; + return `спрашиваю ${d.upstream} · запросов ${d.queries}, ` + + `отклонено ${d.blocked}, из кэша ${d.cache_hits}` + + (d.timeouts ? `, без ответа ${d.timeouts}` : ''); + }); + + const ago = (s) => (s < 60 ? `${s} с назад` : `${Math.floor(s / 60)} мин назад`); + + return { + input, submit, status, ago, + // тревожно только то, что должно работать, но не работает + bad: computed(() => props.active && (props.dns.error || !props.dns.listening)), + idle: computed(() => !props.active && props.sites.length > 0), + }; + }, + template: ` +
+
+

Сайты

+ +
+
{{ status }}
+

+ Список составлен, но блокировка выключена — сайты открываются. +

+
+ + +
+

+ Запись закрывает и поддомены: discord.com закроет и + gateway.discord.com. +

+ + +

+ Правило брандмауэра закрывает 53-й порт всем, кроме службы. Если интернет + пропадёт — снимите галочку. +

+
+ + + + + + + + + +
Куда ломилисьРазКогда
{{ a.name }}{{ a.count }}{{ ago(a.ago_secs) }}
+
+
`, +}; + const RandomKill = { emits: ['schedule'], setup(_, { emit }) { @@ -247,15 +354,16 @@ const EventLog = { /* ── корневой компонент ────────────────────────────────────── */ const App = { - components: { UpdateBar, RulesPanel, RandomKill, ProcessTable, EventLog }, + components: { UpdateBar, RulesPanel, SitesPanel, RandomKill, ProcessTable, EventLog }, setup() { const { rules, load: loadRules, block, allow, remove, save } = useRules(); const { groups, load: loadProcesses, kill } = useProcesses(); const { lines, load: loadLog } = useLog(); const { info: update, load: loadUpdate, check: checkUpdate } = useUpdate(); + const { dns, load: loadDns, block: blockSite, remove: removeSite } = useSites(); onMounted(loadRules); - usePolling(() => { loadProcesses(); loadLog(); }, 3000); + usePolling(() => { loadProcesses(); loadLog(); loadDns(); }, 3000); // версия меняется раз в сутки в лучшем случае — частить незачем usePolling(loadUpdate, 30000); @@ -276,7 +384,18 @@ const App = { const whitelist = computed(() => rules.value.mode === 'whitelist'); - return { rules, groups, lines, update, whitelist, save, remove, toggle, kill, scheduleRandom, checkUpdate }; + // правка списка сайтов меняет и правила, и показания резолвера + const onSite = async (action, name) => { + await action(name); + await Promise.all([loadRules(), loadDns()]); + }; + + return { + rules, groups, lines, update, dns, whitelist, + save, remove, toggle, kill, scheduleRandom, checkUpdate, + blockSite: (name) => onSite(blockSite, name), + removeSite: (name) => onSite(removeSite, name), + }; }, template: `
@@ -285,6 +404,13 @@ const App = {
+ `, diff --git a/static/index.html b/static/index.html index 37a3ed6..7de154b 100644 --- a/static/index.html +++ b/static/index.html @@ -39,11 +39,15 @@ .warn { color:#e0b872; } pre { margin:0; font:12px/1.6 ui-monospace, Consolas, monospace; color:#9aa4b2; white-space:pre-wrap; max-height:200px; overflow:auto; } + code { font:12px/1.5 ui-monospace, Consolas, monospace; color:#c8d0da; + background:#232833; border-radius:4px; padding:1px 4px; }
- + + \ No newline at end of file diff --git a/static/vue.global.prod.js b/static/vue.global.prod.js new file mode 100644 index 0000000..31a7df4 --- /dev/null +++ b/static/vue.global.prod.js @@ -0,0 +1,14 @@ +/** +* vue v3.5.41 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/var Vue=function(e){"use strict";var t,n,r;let i,l,s,o,a,c,u,d,h,f,p,g,m;function y(e){let t=Object.create(null);for(let n of e.split(","))t[n]=1;return e=>e in t}let b={},_=[],S=()=>{},x=()=>!1,C=e=>111===e.charCodeAt(0)&&110===e.charCodeAt(1)&&(e.charCodeAt(2)>122||97>e.charCodeAt(2)),k=e=>e.startsWith("onUpdate:"),T=Object.assign,w=(e,t)=>{let n=e.indexOf(t);n>-1&&e.splice(n,1)},N=Object.prototype.hasOwnProperty,A=(e,t)=>N.call(e,t),E=Array.isArray,I=e=>"function"==typeof e,R=e=>"string"==typeof e,O=e=>"symbol"==typeof e,M=e=>null!==e&&"object"==typeof e,P=e=>(M(e)||I(e))&&I(e.then)&&I(e.catch),F=Object.prototype.toString,L=e=>R(e)&&"NaN"!==e&&"-"!==e[0]&&""+parseInt(e,10)===e,$=y(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),D=y("bind,cloak,else-if,else,for,html,if,model,on,once,pre,show,slot,text,memo"),V=e=>{let t=Object.create(null);return n=>t[n]||(t[n]=e(n))},B=/-\w/g,j=V(e=>e.replace(B,e=>e.slice(1).toUpperCase())),U=/\B([A-Z])/g,H=V(e=>e.replace(U,"-$1").toLowerCase()),q=V(e=>e.charAt(0).toUpperCase()+e.slice(1)),W=V(e=>e?`on${q(e)}`:""),K=(e,t)=>!Object.is(e,t),z=(e,...t)=>{for(let n=0;n{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,writable:r,value:n})},G=e=>{let t=parseFloat(e);return isNaN(t)?e:t},X=e=>{let t=R(e)?Number(e):0/0;return isNaN(t)?e:t},Q=()=>i||(i="u">typeof globalThis?globalThis:"u">typeof self?self:"u">typeof window?window:"u">typeof global?global:{}),Z=y("Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt,console,Error,Symbol");function Y(e){if(E(e)){let t={};for(let n=0;n{if(e){let n=e.split(et);n.length>1&&(t[n[0].trim()]=n[1].trim())}}),t}function ei(e){let t="";if(R(e))t=e;else if(E(e))for(let n=0;neu(e,t))}let eh=e=>!!(e&&!0===e.__v_isRef),ef=e=>R(e)?e:null==e?"":E(e)||M(e)&&(e.toString===F||!I(e.toString))?eh(e)?ef(e.value):JSON.stringify(e,ep,2):String(e),ep=(e,t)=>{let n;if(eh(t))return ep(e,t.value);if("[object Map]"===(n=t,F.call(n)))return{[`Map(${t.size})`]:[...t.entries()].reduce((e,[t,n],r)=>(e[eg(t,r)+" =>"]=n,e),{})};{let e;if("[object Set]"===(e=t,F.call(e)))return{[`Set(${t.size})`]:[...t.values()].map(e=>eg(e))};else{if(O(t))return eg(t);let e;if(M(t)&&!E(t)&&"[object Object]"!==(e=t,F.call(e)))return String(t)}}return t},eg=(e,t="")=>{var n;return O(e)?`Symbol(${null!=(n=e.description)?n:t})`:e};class em{constructor(e=!1){this.detached=e,this._active=!0,this._on=0,this.effects=[],this.cleanups=[],this._isPaused=!1,this._warnOnRun=!0,this.__v_skip=!0,!e&&l&&(l.active?(this.parent=l,this.index=(l.scopes||(l.scopes=[])).push(this)-1):(this._active=!1,this._warnOnRun=!1))}get active(){return this._active}pause(){if(this._active){let e,t;if(this._isPaused=!0,this.scopes){let n=this.scopes.slice();for(e=0,t=n.length;e0&&0==--this._on){if(l===this)l=this.prevScope;else{let e=l;for(;e;){if(e.prevScope===this){e.prevScope=this.prevScope;break}e=e.prevScope}}this.prevScope=void 0}}stop(e){if(this._active){let t,n;for(this._active=!1,t=0,n=this.effects.length;t0)){if(a){let e=a;for(a=void 0;e;){let t=e.next;e.next=void 0,e.flags&=-9,e=t}}for(;o;){let t=o;for(o=void 0;t;){let n=t.next;if(t.next=void 0,t.flags&=-9,1&t.flags)try{t.trigger()}catch(t){e||(e=t)}t=n}}if(e)throw e}}function ex(e){for(let t=e.deps;t;t=t.nextDep)t.version=-1,t.prevActiveLink=t.dep.activeLink,t.dep.activeLink=t}function eC(e){let t,n=e.depsTail,r=n;for(;r;){let e=r.prevDep;-1===r.version?(r===n&&(n=e),ew(r),function(e){let{prevDep:t,nextDep:n}=e;t&&(t.nextDep=n,e.prevDep=void 0),n&&(n.prevDep=t,e.nextDep=void 0)}(r)):t=r,r.dep.activeLink=r.prevActiveLink,r.prevActiveLink=void 0,r=e}e.deps=t,e.depsTail=n}function ek(e){for(let t=e.deps;t;t=t.nextDep)if(t.dep.version!==t.version||t.dep.computed&&(eT(t.dep.computed)||t.dep.version!==t.version))return!0;return!!e._dirty}function eT(e){if(4&e.flags&&!(16&e.flags)||(e.flags&=-17,e.globalVersion===eO)||(e.globalVersion=eO,!e.isSSR&&128&e.flags&&(!e.deps&&!e._dirty||!ek(e))))return;e.flags|=2;let t=e.dep,n=s,r=eN;s=e,eN=!0;try{ex(e);let n=e.fn(e._value);(0===t.version||K(n,e._value))&&(e.flags|=128,e._value=n,t.version++)}catch(e){throw t.version++,e}finally{s=n,eN=r,eC(e),e.flags&=-3}}function ew(e,t=!1){let{dep:n,prevSub:r,nextSub:i}=e;if(r&&(r.nextSub=i,e.prevSub=void 0),i&&(i.prevSub=r,e.nextSub=void 0),n.subs===e&&(n.subs=r,!r&&n.computed)){n.computed.flags&=-5;for(let e=n.computed.deps;e;e=e.nextDep)ew(e,!0)}t||--n.sc||!n.map||n.map.delete(n.key)}let eN=!0,eA=[];function eE(){eA.push(eN),eN=!1}function eI(){let e=eA.pop();eN=void 0===e||e}function eR(e){let{cleanup:t}=e;if(e.cleanup=void 0,t){let e=s;s=void 0;try{t()}finally{s=e}}}let eO=0;class eM{constructor(e,t){this.sub=e,this.dep=t,this.version=t.version,this.nextDep=this.prevDep=this.nextSub=this.prevSub=this.prevActiveLink=void 0}}class eP{constructor(e){this.computed=e,this.version=0,this.activeLink=void 0,this.subs=void 0,this.map=void 0,this.key=void 0,this.sc=0,this.__v_skip=!0}track(e){if(!s||!eN||s===this.computed)return;let t=this.activeLink;if(void 0===t||t.sub!==s)t=this.activeLink=new eM(s,this),s.deps?(t.prevDep=s.depsTail,s.depsTail.nextDep=t,s.depsTail=t):s.deps=s.depsTail=t,function e(t){if(t.dep.sc++,4&t.sub.flags){let n=t.dep.computed;if(n&&!t.dep.subs){n.flags|=20;for(let t=n.deps;t;t=t.nextDep)e(t)}let r=t.dep.subs;r!==t&&(t.prevSub=r,r&&(r.nextSub=t)),t.dep.subs=t}}(t);else if(-1===t.version&&(t.version=this.version,t.nextDep)){let e=t.nextDep;e.prevDep=t.prevDep,t.prevDep&&(t.prevDep.nextDep=e),t.prevDep=s.depsTail,t.nextDep=void 0,s.depsTail.nextDep=t,s.depsTail=t,s.deps===t&&(s.deps=e)}return t}trigger(e){this.version++,eO++,this.notify(e)}notify(e){eb++;try{for(let e=this.subs;e;e=e.prevSub)e.sub.notify()&&e.sub.dep.notify()}finally{eS()}}}let eF=new WeakMap,eL=Symbol(""),e$=Symbol(""),eD=Symbol("");function eV(e,t,n){if(eN&&s){let t=eF.get(e);t||eF.set(e,t=new Map);let r=t.get(n);r||(t.set(n,r=new eP),r.map=t,r.key=n),r.track()}}function eB(e,t,n,r,i,l){let s=eF.get(e);if(!s)return void eO++;let o=e=>{e&&e.trigger()};if(eb++,"clear"===t)s.forEach(o);else{let i=E(e),l=i&&L(n);if(i&&"length"===n){let e=Number(r);s.forEach((t,n)=>{("length"===n||n===eD||!O(n)&&n>=e)&&o(t)})}else switch((void 0!==n||s.has(void 0))&&o(s.get(n)),l&&o(s.get(eD)),t){case"add":if(i)l&&o(s.get("length"));else{let t;o(s.get(eL));"[object Map]"===(t=e,F.call(t))&&o(s.get(e$))}break;case"delete":if(!i){let t;o(s.get(eL));"[object Map]"===(t=e,F.call(t))&&o(s.get(e$))}break;case"set":let a;"[object Map]"===(a=e,F.call(a))&&o(s.get(eL))}}eS()}function ej(e){let t=tm(e);return t===e?t:(eV(t,"iterate",eD),tp(e)?t:t.map(ty))}function eU(e){return eV(e=tm(e),"iterate",eD),e}function eH(e,t){return tf(e)?th(e)?tb(ty(t)):tb(t):ty(t)}let eq={__proto__:null,[Symbol.iterator](){return eW(this,Symbol.iterator,e=>eH(this,e))},concat(...e){return ej(this).concat(...e.map(e=>E(e)?ej(e):e))},entries(){return eW(this,"entries",e=>(e[1]=eH(this,e[1]),e))},every(e,t){return ez(this,"every",e,t,void 0,arguments)},filter(e,t){return ez(this,"filter",e,t,e=>e.map(e=>eH(this,e)),arguments)},find(e,t){return ez(this,"find",e,t,e=>eH(this,e),arguments)},findIndex(e,t){return ez(this,"findIndex",e,t,void 0,arguments)},findLast(e,t){return ez(this,"findLast",e,t,e=>eH(this,e),arguments)},findLastIndex(e,t){return ez(this,"findLastIndex",e,t,void 0,arguments)},forEach(e,t){return ez(this,"forEach",e,t,void 0,arguments)},includes(...e){return eG(this,"includes",e)},indexOf(...e){return eG(this,"indexOf",e)},join(e){return ej(this).join(e)},lastIndexOf(...e){return eG(this,"lastIndexOf",e)},map(e,t){return ez(this,"map",e,t,void 0,arguments)},pop(){return eX(this,"pop")},push(...e){return eX(this,"push",e)},reduce(e,...t){return eJ(this,"reduce",e,t)},reduceRight(e,...t){return eJ(this,"reduceRight",e,t)},shift(){return eX(this,"shift")},some(e,t){return ez(this,"some",e,t,void 0,arguments)},splice(...e){return eX(this,"splice",e)},toReversed(){return ej(this).toReversed()},toSorted(e){return ej(this).toSorted(e)},toSpliced(...e){return ej(this).toSpliced(...e)},unshift(...e){return eX(this,"unshift",e)},values(){return eW(this,"values",e=>eH(this,e))}};function eW(e,t,n){let r=eU(e),i=r[t]();return r===e||tp(e)||(i._next=i.next,i.next=()=>{let e=i._next();return e.done||(e.value=n(e.value)),e}),i}let eK=Array.prototype;function ez(e,t,n,r,i,l){let s=eU(e),o=s!==e&&!tp(e),a=s[t];if(a!==eK[t]){let t=a.apply(e,l);return o?ty(t):t}let c=n;s!==e&&(o?c=function(t,r){return n.call(this,eH(e,t),r,e)}:n.length>2&&(c=function(t,r){return n.call(this,t,r,e)}));let u=a.call(s,c,r);return o&&i?i(u):u}function eJ(e,t,n,r){let i=eU(e),l=i!==e&&!tp(e),s=n,o=!1;i!==e&&(l?(o=0===r.length,s=function(t,r,i){return o&&(o=!1,t=eH(e,t)),n.call(this,t,eH(e,r),i,e)}):n.length>3&&(s=function(t,r,i){return n.call(this,t,r,i,e)}));let a=i[t](s,...r);return o?eH(e,a):a}function eG(e,t,n){let r=tm(e);eV(r,"iterate",eD);let i=r[t](...n);return(-1===i||!1===i)&&tg(n[0])?(n[0]=tm(n[0]),r[t](...n)):i}function eX(e,t,n=[]){eE(),eb++;let r=tm(e)[t].apply(e,n);return eS(),eI(),r}let eQ=y("__proto__,__v_isRef,__isVue"),eZ=new Set((((Object.getOwnPropertyNames(Symbol)).filter(e=>"arguments"!==e&&"caller"!==e)).map(e=>Symbol[e])).filter(O));function eY(e){O(e)||(e=String(e));let t=tm(this);return eV(t,"has",e),t.hasOwnProperty(e)}class e0{constructor(e=!1,t=!1){this._isReadonly=e,this._isShallow=t}get(e,t,n){if("__v_skip"===t)return e.__v_skip;let r=this._isReadonly,i=this._isShallow;if("__v_isReactive"===t)return!r;if("__v_isReadonly"===t)return r;if("__v_isShallow"===t)return i;if("__v_raw"===t)return n===(r?i?to:ts:i?tl:ti).get(e)||Object.getPrototypeOf(e)===Object.getPrototypeOf(n)?e:void 0;let l=E(e);if(!r){let e;if(l&&(e=eq[t]))return e;if("hasOwnProperty"===t)return eY}let s=Reflect.get(e,t,t_(e)?e:n);if((O(t)?eZ.has(t):eQ(t))||(r||eV(e,"get",t),i))return s;if(t_(s)){let e=l&&L(t)?s:s.value;return r&&M(e)?tu(e):e}return M(s)?r?tu(s):ta(s):s}}class e1 extends e0{constructor(e=!1){super(!1,e)}set(e,t,n,r){let i=e[t],l=E(e)&&L(t);if(!this._isShallow){let e=tf(i);if(tp(n)||tf(n)||(i=tm(i),n=tm(n)),!l&&t_(i)&&!t_(n))if(e)return!0;else return i.value=n,!0}let s=l?Number(t)e;function e9(e){return function(){return"delete"!==e&&("clear"===e?void 0:this)}}function e7(e,t){let n,r=(T(n={get(n){let r=this.__v_raw,i=tm(r),l=tm(n);e||(K(n,l)&&eV(i,"get",n),eV(i,"get",l));let{has:s}=Reflect.getPrototypeOf(i),o=t?e5:e?tb:ty;return s.call(i,n)?o(r.get(n)):s.call(i,l)?o(r.get(l)):void(r!==i&&r.get(n))},get size(){let t=this.__v_raw;return e||eV(tm(t),"iterate",eL),t.size},has(t){let n=this.__v_raw,r=tm(n),i=tm(t);return e||(K(t,i)&&eV(r,"has",t),eV(r,"has",i)),t===i?n.has(t):n.has(t)||n.has(i)},forEach(n,r){let i=this,l=i.__v_raw,s=tm(l),o=t?e5:e?tb:ty;return e||eV(s,"iterate",eL),l.forEach((e,t)=>n.call(r,o(e),o(t),i))}},e?{add:e9("add"),set:e9("set"),delete:e9("delete"),clear:e9("clear")}:{add(e){let n=tm(this),r=Reflect.getPrototypeOf(n),i=tm(e),l=t||tp(e)||tf(e)?e:i;return r.has.call(n,l)||K(e,l)&&r.has.call(n,e)||K(i,l)&&r.has.call(n,i)||(n.add(l),eB(n,"add",l,l)),this},set(e,n){t||tp(n)||tf(n)||(n=tm(n));let r=tm(this),{has:i,get:l}=Reflect.getPrototypeOf(r),s=i.call(r,e);s||(e=tm(e),s=i.call(r,e));let o=l.call(r,e);return r.set(e,n),s?K(n,o)&&eB(r,"set",e,n):eB(r,"add",e,n),this},delete(e){let t=tm(this),{has:n,get:r}=Reflect.getPrototypeOf(t),i=n.call(t,e);i||(e=tm(e),i=n.call(t,e)),r&&r.call(t,e);let l=t.delete(e);return i&&eB(t,"delete",e,void 0),l},clear(){let e=tm(this),t=0!==e.size,n=e.clear();return t&&eB(e,"clear",void 0,void 0),n}}),["keys","values","entries",Symbol.iterator].forEach(r=>{n[r]=function(...n){let i,l=this.__v_raw,s=tm(l),o="[object Map]"===(i=s,F.call(i)),a="entries"===r||r===Symbol.iterator&&o,c=l[r](...n),u=t?e5:e?tb:ty;return e||eV(s,"iterate","keys"===r&&o?e$:eL),T(Object.create(c),{next(){let{value:e,done:t}=c.next();return t?{value:e,done:t}:{value:a?[u(e[0]),u(e[1])]:u(e),done:t}}})}}),n);return(t,n,i)=>"__v_isReactive"===n?!e:"__v_isReadonly"===n?e:"__v_raw"===n?t:Reflect.get(A(r,n)&&n in t?r:t,n,i)}let te={get:e7(!1,!1)},tt={get:e7(!1,!0)},tn={get:e7(!0,!1)},tr={get:e7(!0,!0)},ti=new WeakMap,tl=new WeakMap,ts=new WeakMap,to=new WeakMap;function ta(e){return tf(e)?e:td(e,!1,e3,te,ti)}function tc(e){return td(e,!1,e4,tt,tl)}function tu(e){return td(e,!0,e6,tn,ts)}function td(e,t,n,r,i){let l;if(!M(e)||e.__v_raw&&!(t&&e.__v_isReactive)||e.__v_skip||!Object.isExtensible(e))return e;let s=i.get(e);if(s)return s;let o=function(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}((l=e,F.call(l)).slice(8,-1));if(0===o)return e;let a=new Proxy(e,2===o?r:n);return i.set(e,a),a}function th(e){return tf(e)?th(e.__v_raw):!!(e&&e.__v_isReactive)}function tf(e){return!!(e&&e.__v_isReadonly)}function tp(e){return!!(e&&e.__v_isShallow)}function tg(e){return!!e&&!!e.__v_raw}function tm(e){let t=e&&e.__v_raw;return t?tm(t):e}function tv(e){return!A(e,"__v_skip")&&Object.isExtensible(e)&&J(e,"__v_skip",!0),e}let ty=e=>M(e)?ta(e):e,tb=e=>M(e)?tu(e):e;function t_(e){return!!e&&!0===e.__v_isRef}function tS(e){return tC(e,!1)}function tx(e){return tC(e,!0)}function tC(e,t){return t_(e)?e:new tk(e,t)}class tk{constructor(e,t){this.dep=new eP,this.__v_isRef=!0,this.__v_isShallow=!1,this._rawValue=t?e:tm(e),this._value=t?e:ty(e),this.__v_isShallow=t}get value(){return this.dep.track(),this._value}set value(e){let t=this._rawValue,n=this.__v_isShallow||tp(e)||tf(e);K(e=n?e:tm(e),t)&&(this._rawValue=e,this._value=n?e:ty(e),this.dep.trigger())}}function tT(e){return t_(e)?e.value:e}let tw={get:(e,t,n)=>"__v_raw"===t?e:tT(Reflect.get(e,t,n)),set:(e,t,n,r)=>{let i=e[t];return t_(i)&&!t_(n)?(i.value=n,!0):Reflect.set(e,t,n,r)}};function tN(e){return th(e)?e:new Proxy(e,tw)}class tA{constructor(e){this.__v_isRef=!0,this._value=void 0;const t=this.dep=new eP,{get:n,set:r}=e(t.track.bind(t),t.trigger.bind(t));this._get=n,this._set=r}get value(){return this._value=this._get()}set value(e){this._set(e)}}function tE(e){return new tA(e)}class tI{constructor(e,t,n){this._object=e,this._defaultValue=n,this.__v_isRef=!0,this._value=void 0,this._key=O(t)?t:String(t),this._raw=tm(e);let r=!0,i=e;if(!E(e)||O(this._key)||!L(this._key))do r=!tg(i)||tp(i);while(r&&(i=i.__v_raw));this._shallow=r}get value(){let e=this._object[this._key];return this._shallow&&(e=tT(e)),this._value=void 0===e?this._defaultValue:e}set value(e){if(this._shallow&&t_(this._raw[this._key])){let t=this._object[this._key];if(t_(t)){t.value=e;return}}this._object[this._key]=e}get dep(){var e,t;let n;return e=this._raw,t=this._key,(n=eF.get(e))&&n.get(t)}}class tR{constructor(e){this._getter=e,this.__v_isRef=!0,this.__v_isReadonly=!0,this._value=void 0}get value(){return this._value=this._getter()}}class tO{constructor(e,t,n){this.fn=e,this.setter=t,this._value=void 0,this.dep=new eP(this),this.__v_isRef=!0,this.deps=void 0,this.depsTail=void 0,this.flags=16,this.globalVersion=eO-1,this.next=void 0,this.effect=this,this.__v_isReadonly=!t,this.isSSR=n}notify(){if(this.flags|=16,!(8&this.flags)&&s!==this)return e_(this,!0),!0}get value(){let e=this.dep.track();return eT(this),e&&(e.version=this.dep.version),this._value}set value(e){this.setter&&this.setter(e)}}let tM={},tP=new WeakMap;function tF(e,t=!1,n=g){if(n){let t=tP.get(n);t||tP.set(n,t=[]),t.push(e)}}function tL(e,t=1/0,n){if(t<=0||!M(e)||e.__v_skip||((n=n||new Map).get(e)||0)>=t)return e;if(n.set(e,t),t--,t_(e))tL(e.value,t,n);else if(E(e))for(let r=0;r{tL(e,t,n)});else{let r;if("[object Object]"===(r=e,F.call(r))){for(let r in e)tL(e[r],t,n);for(let r of Object.getOwnPropertySymbols(e))Object.prototype.propertyIsEnumerable.call(e,r)&&tL(e[r],t,n)}}}return e}function t$(e,t,n,r){try{return r?e(...r):e()}catch(e){tV(e,t,n)}}function tD(e,t,n,r){if(I(e)){let i=t$(e,t,n,r);return i&&P(i)&&i.catch(e=>{tV(e,t,n)}),i}if(E(e)){let i=[];for(let l=0;l=tY(n)?tB.push(e):tB.splice(function(e){let t=tj+1,n=tB.length;for(;t>>1,i=tB[r],l=tY(i);ltY(e)-tY(t));if(tU.length=0,tH){for(let t=0;tnull==e.id?2&e.flags?-1:1/0:e.id,t0=null,t1=null;function t2(e){let t=t0;return t0=e,t1=e&&e.type.__scopeId||null,t}function t3(e,t=t0,n){if(!t||e._n)return e;let r=(...n)=>{let i;r._d&&io(-1);let l=t2(t),s=it.length;try{i=e(...n)}finally{for(let e=it.length;e>s;e--)il();t2(l),r._d&&io(1)}return i};return r._n=!0,r._c=!0,r._d=!0,r}function t6(e,t,n,r){let i=e.dirs,l=t&&t.dirs;for(let s=0;s1)return n&&I(t)?t.call(r&&r.proxy):t}}let t5=Symbol.for("v-scx");function t9(e,t){return t7(e,null,{flush:"sync"})}function t7(e,t,n=b){let{flush:r}=n,i=T({},n),s=iA;i.call=(e,t,n)=>tD(e,s,t,n);let o=!1;return"post"===r?i.scheduler=e=>{rK(e,s&&s.suspense)}:"sync"!==r&&(o=!0,i.scheduler=(e,t)=>{t?e():tJ(e)}),i.augmentJob=e=>{t&&(e.flags|=4),o&&(e.flags|=2,s&&(e.id=s.uid,e.i=s))},function(e,t,n=b){let r,i,s,o,{immediate:a,deep:c,once:u,scheduler:d,augmentJob:h,call:f}=n,p=e=>c?e:tp(e)||!1===c||0===c?tL(e,1):tL(e),m=!1,y=!1;if(t_(e)?(i=()=>e.value,m=tp(e)):th(e)?(i=()=>p(e),m=!0):E(e)?(y=!0,m=e.some(e=>th(e)||tp(e)),i=()=>e.map(e=>t_(e)?e.value:th(e)?p(e):I(e)?f?f(e,2):e():void 0)):i=I(e)?t?f?()=>f(e,2):e:()=>{if(s){eE();try{s()}finally{eI()}}let t=g;g=r;try{return f?f(e,3,[o]):e(o)}finally{g=t}}:S,t&&c){let e=i,t=!0===c?1/0:c;i=()=>tL(e(),t)}let _=l,x=()=>{r.stop(),_&&_.active&&w(_.effects,r)};if(u&&t){let e=t;t=(...t)=>{let n=e(...t);return x(),n}}let C=y?Array(e.length).fill(tM):tM,k=e=>{if(1&r.flags&&(r.dirty||e))if(t){let n=r.run();if(e||c||m||(y?n.some((e,t)=>K(e,C[t])):K(n,C))){s&&s();let e=g;g=r;try{let e=[n,C===tM?void 0:y&&C[0]===tM?[]:C,o];C=n,f?f(t,3,e):t(...e)}finally{g=e}}}else r.run()};return h&&h(k),(r=new ey(i)).scheduler=d?()=>d(k,!1):k,o=e=>tF(e,!1,r),s=r.onStop=()=>{let e=tP.get(r);if(e){if(f)f(e,4);else for(let t of e)t();tP.delete(r)}},t?a?k(!0):C=r.run():d?d(k.bind(null,!0),!0):r.run(),x.pause=r.pause.bind(r),x.resume=r.resume.bind(r),x.stop=x,x}(e,t,i)}function ne(e,t,n){let r,i=this.proxy,l=R(e)?e.includes(".")?nt(i,e):()=>i[e]:e.bind(i,i);I(t)?r=t:(r=t.handler,n=t);let s=iI(this),o=t7(l,r.bind(i),n);return s(),o}function nt(e,t){let n=t.split(".");return()=>{let t=e;for(let e=0;ee&&(e.disabled||""===e.disabled),nl=e=>"u">typeof SVGElement&&e instanceof SVGElement,ns=e=>"function"==typeof MathMLElement&&e instanceof MathMLElement,no=(e,t)=>{let n=e&&e.to;return R(n)?t?t(n):null:n};function na(e,t,n,{o:{insert:r},m:i},l=2){0===l&&r(e.targetAnchor,t,n);let{el:s,anchor:o,shapeFlag:a,children:c,props:u}=e,d=2===l;if(d&&r(s,t,n),!nn.has(e)&&(!d||ni(u))&&16&a)for(let e=0;e{e.isMounted=!0}),n6(()=>{e.isUnmounting=!0}),e}let np=[Function,Array],ng={mode:String,appear:Boolean,persisted:Boolean,onBeforeEnter:np,onEnter:np,onAfterEnter:np,onEnterCancelled:np,onBeforeLeave:np,onLeave:np,onAfterLeave:np,onLeaveCancelled:np,onBeforeAppear:np,onAppear:np,onAfterAppear:np,onAppearCancelled:np},nm=e=>{let t=e.subTree;return t.component?nm(t.component):t};function nv(e){let t=e[0];if(e.length>1){for(let n of e)if(n.type!==r7){t=n;break}}return t}let ny={name:"BaseTransition",props:ng,setup(e,{slots:t}){let n=iE(),r=nf();return()=>{let i=t.default&&nk(t.default(),!0),l=i&&i.length?nv(i):n.subTree?i_():void 0;if(!l)return;let s=tm(e),{mode:o}=s;if(r.isLeaving)return nS(l);let a=nx(l);if(!a)return nS(l);let c=n_(a,s,r,n,e=>c=e);a.type!==r7&&nC(a,c);let u=n.subTree&&nx(n.subTree);if(u&&u.type!==r7&&!id(u,a)&&nm(n).type!==r7){let e=n_(u,s,r,n);if(nC(u,e),"out-in"===o&&a.type!==r7)return r.isLeaving=!0,e.afterLeave=()=>{r.isLeaving=!1,8&n.job.flags||n.update(),delete e.afterLeave,u=void 0},nS(l);"in-out"===o&&a.type!==r7?e.delayLeave=(e,t,n)=>{nb(r,u)[String(u.key)]=u,e[nd]=()=>{t(),e[nd]=void 0,delete c.delayedLeave,u=void 0},c.delayedLeave=()=>{n(),delete c.delayedLeave,u=void 0}}:u=void 0}else u&&(u=void 0);return l}}};function nb(e,t){let{leavingVNodes:n}=e,r=n.get(t.type);return r||(r=Object.create(null),n.set(t.type,r)),r}function n_(e,t,n,r,i){let{appear:l,mode:s,persisted:o=!1,onBeforeEnter:a,onEnter:c,onAfterEnter:u,onEnterCancelled:d,onBeforeLeave:h,onLeave:f,onAfterLeave:p,onLeaveCancelled:g,onBeforeAppear:m,onAppear:y,onAfterAppear:b,onAppearCancelled:_}=t,S=String(e.key),x=nb(n,e),C=(e,t)=>{e&&tD(e,r,9,t)},k=(e,t)=>{let n=t[1];C(e,t),E(e)?e.every(e=>e.length<=1)&&n():e.length<=1&&n()},T={mode:s,persisted:o,beforeEnter(t){let r=a;if(!n.isMounted)if(!l)return;else r=m||a;t[nd]&&t[nd](!0);let i=x[S];i&&id(e,i)&&i.el[nd]&&i.el[nd](),C(r,[t])},enter(t){if(x[S]===e)return;let r=c,i=u,s=d;if(!n.isMounted)if(!l)return;else r=y||c,i=b||u,s=_||d;let o=!1;t[nh]=e=>{o||(o=!0,e?C(s,[t]):C(i,[t]),T.delayedLeave&&T.delayedLeave(),t[nh]=void 0)};let a=t[nh].bind(null,!1);r?k(r,[t,a]):a()},leave(t,r){let i=String(e.key);if(t[nh]&&t[nh](!0),n.isUnmounting)return r();C(h,[t]);let l=!1;t[nd]=n=>{l||(l=!0,r(),n?C(g,[t]):C(p,[t]),t[nd]=void 0,x[i]===e&&delete x[i])};let s=t[nd].bind(null,!1);x[i]=e,f?k(f,[t,s]):s()},clone(e){let l=n_(e,t,n,r,i);return i&&i(l),l}};return T}function nS(e){if(nW(e))return(e=iy(e)).children=null,e}function nx(e){if(!nW(e))return e.type.__isTeleport&&e.children?nv(e.children):e;if(e.component)return e.component.subTree;let{shapeFlag:t,children:n}=e;if(n){if(16&t)return n[0];if(32&t&&I(n.default))return n.default()}}function nC(e,t){if(6&e.shapeFlag&&e.component){e.transition=t;let n=e.component.subTree;nC(n.type.__isTeleport&&nx(n)||n,t)}else 128&e.shapeFlag?(e.ssContent.transition=t.clone(e.ssContent),e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t}function nk(e,t=!1,n){let r=[],i=0;for(let l=0;l1)for(let e=0;enE(e,t&&(E(t)?t[l]:t),n,r,i));if(nH(r)&&!i){512&r.shapeFlag&&r.type.__asyncResolved&&r.component.subTree.component&&nE(e,t,n,r.component.subTree);return}let l=4&r.shapeFlag?iV(r.component):r.el,s=i?null:l,{i:o,r:a}=e,c=t&&t.r,u=o.refs===b?o.refs={}:o.refs,d=o.setupState,h=tm(d),f=d===b?x:e=>!nN(u,e)&&A(h,e),p=(e,t)=>!(t&&nN(u,t));if(null!=c&&c!==a&&(nI(t),R(c)?(u[c]=null,f(c)&&(d[c]=null)):t_(c)&&(p(c,t.k)&&(c.value=null),t.k&&(u[t.k]=null))),I(a))t$(a,o,12,[s,u]);else{let t=R(a),r=t_(a);if(t||r){let o=()=>{if(e.f){let n=t?f(a)?d[a]:u[a]:p()||!e.k?a.value:u[e.k];if(i)E(n)&&w(n,l);else if(E(n))n.includes(l)||n.push(l);else if(t)u[a]=[l],f(a)&&(d[a]=u[a]);else{let t=[l];p(a,e.k)&&(a.value=t),e.k&&(u[e.k]=t)}}else t?(u[a]=s,f(a)&&(d[a]=s)):r&&(p(a,e.k)&&(a.value=s),e.k&&(u[e.k]=s))};if(s){let t=()=>{o(),nA.delete(e)};t.id=-1,nA.set(e,t),rK(t,n)}else nI(e),o()}}}function nI(e){let t=nA.get(e);t&&(t.flags|=8,nA.delete(e))}let nR=!1,nO=()=>{nR||(console.error("Hydration completed but contains mismatches."),nR=!0)},nM=e=>{if(1===e.nodeType){if(e.namespaceURI.includes("svg")&&"foreignObject"!==e.tagName)return"svg";if(e.namespaceURI.includes("MathML"))return"mathml"}},nP=e=>8===e.nodeType;function nF(e){let{mt:t,p:n,o:{patchProp:r,createText:i,nextSibling:l,parentNode:s,remove:o,insert:a,createComment:c}}=e,u=(n,r,o,c,b,_=!1)=>{_=_||!!r.dynamicChildren;let S=nP(n)&&"["===n.data,x=()=>p(n,r,o,c,b,S),{type:C,ref:k,shapeFlag:T,patchFlag:w}=r,N=n.nodeType;r.el=n,-2===w&&(_=!1,r.dynamicChildren=null);let A=null;switch(C){case r9:3!==N?""===r.children?(a(r.el=i(""),s(n),n),A=n):A=x():(n.data!==r.children&&(nO(),n.data=r.children),A=l(n));break;case r7:y(n)?(A=l(n),m(r.el=n.content.firstChild,n,o)):A=8!==N||S?x():l(n);break;case ie:if(S&&(N=(n=l(n)).nodeType),1===N||3===N){A=n;let e=!r.children.length;for(let t=0;t{s=s||!!t.dynamicChildren;let{type:a,dynamicProps:c,props:u,patchFlag:d,shapeFlag:f,dirs:p,transition:g}=t,b="input"===a||"option"===a,_=!!c;if(b||_||-1!==d){let a;p&&t6(t,null,n,"created");let T=!1;if(y(e)){T=rQ(null,g)&&n&&n.vnode.props&&n.vnode.props.appear;let r=e.content.firstChild;if(T){let e=r.getAttribute("class");e&&(r.$cls=e),g.beforeEnter(r)}m(r,e,n),t.el=e=r}if(16&f&&!(u&&(u.innerHTML||u.textContent))){let r=h(e.firstChild,t,e,n,i,l,s);for(r&&!nV(e,1)&&nO();r;){let e=r;r=r.nextSibling,o(e)}}else if(8&f){let n=t.children;` +`===n[0]&&("PRE"===e.tagName||"TEXTAREA"===e.tagName)&&(n=n.slice(1));let{textContent:r}=e;r!==n&&r!==n.replace(/\r\n|\r/g,` +`)&&(nV(e,0)||nO(),e.textContent=t.children)}if(u){if(b||_||!s||48&d){let t=e.tagName.includes("-"),i=e.namespaceURI.includes("svg")?"svg":e.namespaceURI.includes("MathML")?"mathml":void 0;for(let l in u)if(b&&(l.endsWith("value")||"indeterminate"===l)||C(l)&&!$(l)||"."===l[0]||t&&!$(l)||c&&c.includes(l)){var S,x,k;if(S=e,x=l,k=u[l],nL.has(x)&&S.getAttribute(x)===(null==k?null:`${k}`))continue;r(e,l,null,u[l],i,n)}}else if(u.onClick)r(e,"onClick",null,u.onClick,void 0,n);else if(4&d&&th(u.style))for(let e in u.style)u.style[e]}(a=u&&u.onVnodeBeforeMount)&&iT(a,n,t),p&&t6(t,null,n,"beforeMount"),((a=u&&u.onVnodeMounted)||p||T)&&r4(()=>{a&&iT(a,n,t),T&&g.enter(e),p&&t6(t,null,n,"mounted")},i)}return e.nextSibling},h=(e,t,r,s,o,c,d)=>{d=d||!!t.dynamicChildren;let h=t.children,f=h.length,p=!1;for(let t=0;t{let{slotScopeIds:u}=t;u&&(i=i?i.concat(u):u);let d=s(e),f=h(l(e),t,d,n,r,i,o);return f&&nP(f)&&"]"===f.data?l(t.anchor=f):(nO(),a(t.anchor=c("]"),d,f),f)},p=(e,t,r,i,a,c)=>{var u,d,h;if(u=e,d=t,nV(u.parentElement,1)||1===(h=u).nodeType&&nB(h.getAttribute(n$),1)||function({props:e}){let t=e&&e[n$];return"string"==typeof t&&nB(t,1)}(d)||nO(),t.el=null,c){let t=g(e);for(;;){let n=l(e);if(n&&n!==t)o(n);else break}}let f=l(e),p=s(e);return o(e),n(null,t,p,f,r,i,nM(p),a),r&&(r.vnode.el=t.el,rM(r,t.el)),f},g=(e,t="[",n="]")=>{let r=0;for(;e;)if((e=l(e))&&nP(e)&&(e.data===t&&r++,e.data===n))if(0===r)return l(e);else r--;return e},m=(e,t,n)=>{let r=t.parentNode;r&&r.replaceChild(e,t);let i=n;for(;i;)i.vnode.el===t&&(i.vnode.el=i.subTree.el=e),i=i.parent},y=e=>1===e.nodeType&&"TEMPLATE"===e.tagName;return[(e,t)=>{if(!t.hasChildNodes()){n(null,e,t),tZ(),t._vnode=e;return}u(t.firstChild,e,null,null,null),tZ(),t._vnode=e},u]}let nL=new Set(["src","srcset","href","poster"]),n$="data-allow-mismatch",nD={0:"text",1:"children",2:"class",3:"style",4:"attribute"};function nV(e,t){if(0===t||1===t)for(;e&&!e.hasAttribute(n$);)e=e.parentElement;return nB(e&&e.getAttribute(n$),t)}function nB(e,t){if(null==e)return!1;{if(""===e)return!0;let n=e.split(",");return!!(0===t&&n.includes("children"))||n.includes(nD[t])}}let nj=Q().requestIdleCallback||(e=>setTimeout(e,1)),nU=Q().cancelIdleCallback||(e=>clearTimeout(e)),nH=e=>!!e.type.__asyncLoader;function nq(e,t){let{ref:n,props:r,children:i,ce:l}=t.vnode,s=im(e,r,i);return s.ref=n,s.ce=l,delete t.vnode.ce,s}let nW=e=>e.type.__isKeepAlive;function nK(e,t){let n;if(E(e))return e.some(e=>nK(e,t));if(R(e))return e.split(",").includes(t);return"[object RegExp]"===(n=e,F.call(n))&&(e.lastIndex=0,e.test(t))}function nz(e,t){nG(e,"a",t)}function nJ(e,t){nG(e,"da",t)}function nG(e,t,n=iA){let r=e.__wdc||(e.__wdc=()=>{let t=n;for(;t;){if(t.isDeactivated)return;t=t.parent}return e()});if(nZ(t,r,n),n){let e=n.parent;for(;e&&e.parent;)nW(e.parent.vnode)&&function(e,t,n,r){let i=nZ(t,e,r,!0);n4(()=>{w(r[t],i)},n)}(r,t,n,e),e=e.parent}}function nX(e){e.shapeFlag&=-257,e.shapeFlag&=-513}function nQ(e){return 128&e.shapeFlag?e.ssContent:e}function nZ(e,t,n=iA,r=!1){if(n){let i=n[e]||(n[e]=[]),l=t.__weh||(t.__weh=(...r)=>{eE();let i=iI(n),l=tD(t,n,e,r);return i(),eI(),l});return r?i.unshift(l):i.push(l),l}}let nY=e=>(t,n=iA)=>{iM&&"sp"!==e||nZ(e,(...e)=>t(...e),n)},n0=nY("bm"),n1=nY("m"),n2=nY("bu"),n3=nY("u"),n6=nY("bum"),n4=nY("um"),n8=nY("sp"),n5=nY("rtg"),n9=nY("rtc");function n7(e,t=iA){nZ("ec",e,t)}let re="components",rt=Symbol.for("v-ndc");function rn(e,t,n=!0,r=!1){let i=t0||iA;if(i){let n=i.type;if(e===re){let e=iB(n,!1);if(e&&(e===t||e===j(t)||e===q(j(t))))return n}let l=rr(i[e]||n[e],t)||rr(i.appContext[e],t);return!l&&r?n:l}}function rr(e,t){return e&&(e[t]||e[j(t)]||e[q(j(t))])}let ri=e=>e?iO(e)?iV(e):ri(e.parent):null,rl=T(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>ri(e.parent),$root:e=>ri(e.root),$host:e=>e.ce,$emit:e=>e.emit,$options:e=>rf(e),$forceUpdate:e=>e.f||(e.f=()=>{tJ(e.update)}),$nextTick:e=>e.n||(e.n=tz.bind(e.proxy)),$watch:e=>ne.bind(e)}),rs=(e,t)=>e!==b&&!e.__isScriptSetup&&A(e,t),ro={get({_:e},t){let n,r;if("__v_skip"===t)return!0;let{ctx:i,setupState:l,data:s,props:o,accessCache:a,type:c,appContext:u}=e;if("$"!==t[0]){let e=a[t];if(void 0!==e)switch(e){case 1:return l[t];case 2:return s[t];case 4:return i[t];case 3:return o[t]}else{if(rs(l,t))return a[t]=1,l[t];if(s!==b&&A(s,t))return a[t]=2,s[t];if(A(o,t))return a[t]=3,o[t];if(i!==b&&A(i,t))return a[t]=4,i[t];rd&&(a[t]=0)}}let d=rl[t];return d?("$attrs"===t&&eV(e.attrs,"get",""),d(e)):(n=c.__cssModules)&&(n=n[t])?n:i!==b&&A(i,t)?(a[t]=4,i[t]):A(r=u.config.globalProperties,t)?r[t]:void 0},set({_:e},t,n){let{data:r,setupState:i,ctx:l}=e;return rs(i,t)?(i[t]=n,!0):r!==b&&A(r,t)?(r[t]=n,!0):!A(e.props,t)&&!("$"===t[0]&&t.slice(1)in e)&&(l[t]=n,!0)},has({_:{data:e,setupState:t,accessCache:n,ctx:r,appContext:i,props:l,type:s}},o){let a;return!!(n[o]||e!==b&&"$"!==o[0]&&A(e,o)||rs(t,o)||A(l,o)||A(r,o)||A(rl,o)||A(i.config.globalProperties,o)||(a=s.__cssModules)&&a[o])},defineProperty(e,t,n){return null!=n.get?e._.accessCache[t]=0:A(n,"value")&&this.set(e,t,n.value,null),Reflect.defineProperty(e,t,n)}},ra=T({},ro,{get(e,t){if(t!==Symbol.unscopables)return ro.get(e,t,e)},has:(e,t)=>"_"!==t[0]&&!Z(t)});function rc(e){let t=iE();return t.setupContext||(t.setupContext=iD(t))}function ru(e){return E(e)?e.reduce((e,t)=>(e[t]=null,e),{}):e}let rd=!0;function rh(e,t,n){tD(E(e)?e.map(e=>e.bind(t.proxy)):e.bind(t.proxy),t,n)}function rf(e){let t,n=e.type,{mixins:r,extends:i}=n,{mixins:l,optionsCache:s,config:{optionMergeStrategies:o}}=e.appContext,a=s.get(n);return a?t=a:l.length||r||i?(t={},l.length&&l.forEach(e=>rp(t,e,o,!0)),rp(t,n,o)):t=n,M(n)&&s.set(n,t),t}function rp(e,t,n,r=!1){let{mixins:i,extends:l}=t;for(let s in l&&rp(e,l,n,!0),i&&i.forEach(t=>rp(e,t,n,!0)),t)if(r&&"expose"===s);else{let r=rg[s]||n&&n[s];e[s]=r?r(e[s],t[s]):t[s]}return e}let rg={data:rm,props:r_,emits:r_,methods:rb,computed:rb,beforeCreate:ry,created:ry,beforeMount:ry,mounted:ry,beforeUpdate:ry,updated:ry,beforeDestroy:ry,beforeUnmount:ry,destroyed:ry,unmounted:ry,activated:ry,deactivated:ry,errorCaptured:ry,serverPrefetch:ry,components:rb,directives:rb,watch:function(e,t){if(!e)return t;if(!t)return e;let n=T(Object.create(null),e);for(let r in t)n[r]=ry(e[r],t[r]);return n},provide:rm,inject:function(e,t){return rb(rv(e),rv(t))}};function rm(e,t){return t?e?function(){return T(I(e)?e.call(this,this):e,I(t)?t.call(this,this):t)}:t:e}function rv(e){if(E(e)){let t={};for(let n=0;n"modelValue"===t||"model-value"===t?e.modelModifiers:e[`${t}Modifiers`]||e[`${j(t)}Modifiers`]||e[`${H(t)}Modifiers`];function rT(e,t,...n){let r;if(e.isUnmounted)return;let i=e.vnode.props||b,l=n,s=t.startsWith("update:"),o=s&&rk(i,t.slice(7));o&&(o.trim&&(l=n.map(e=>R(e)?e.trim():e)),o.number&&(l=n.map(G)));let a=i[r=W(t)]||i[r=W(j(t))];!a&&s&&(a=i[r=W(H(t))]),a&&tD(a,e,6,l);let c=i[r+"Once"];if(c){if(e.emitted){if(e.emitted[r])return}else e.emitted={};e.emitted[r]=!0,tD(c,e,6,l)}}let rw=new WeakMap;function rN(e,t){return!!e&&!!C(t)&&(A(e,(t="Once"===(t=t.slice(2))?t:t.replace(/Once$/,""))[0].toLowerCase()+t.slice(1))||A(e,H(t))||A(e,t))}function rA(e){let t,n,{type:r,vnode:i,proxy:l,withProxy:s,propsOptions:[o],slots:a,attrs:c,emit:u,render:d,renderCache:h,props:f,data:p,setupState:g,ctx:m,inheritAttrs:y}=e,b=t2(e);try{if(4&i.shapeFlag){let e=s||l;t=iS(d.call(e,e,h,f,g,p,m)),n=c}else t=iS(r.length>1?r(f,{attrs:c,slots:a,emit:u}):r(f,null)),n=r.props?c:rE(c)}catch(n){it.length=0,tV(n,e,1),t=im(r7)}let _=t;if(n&&!1!==y){let e=Object.keys(n),{shapeFlag:t}=_;e.length&&7&t&&(o&&e.some(k)&&(n=rI(n,o)),_=iy(_,n,!1,!0))}return i.dirs&&((_=iy(_,null,!1,!0)).dirs=_.dirs?_.dirs.concat(i.dirs):i.dirs),i.transition&&nC(_.type.__isTeleport&&nx(_)||_,i.transition),t=_,t2(b),t}let rE=e=>{let t;for(let n in e)("class"===n||"style"===n||C(n))&&((t||(t={}))[n]=e[n]);return t},rI=(e,t)=>{let n={};for(let r in e)k(r)&&r.slice(9)in t||(n[r]=e[r]);return n};function rR(e,t,n){let r=Object.keys(t);if(r.length!==Object.keys(e).length)return!0;for(let i=0;iObject.getPrototypeOf(e)===rP;function rL(e,t,n,r){let i,[l,s]=e.propsOptions,o=!1;if(t)for(let a in t){let c;if($(a))continue;let u=t[a];l&&A(l,c=j(a))?s&&s.includes(c)?(i||(i={}))[c]=u:n[c]=u:rN(e.emitsOptions,a)||a in r&&u===r[a]||(r[a]=u,o=!0)}if(s){let t=tm(n),r=i||b;for(let i=0;i"_"===e||"_ctx"===e||"$stable"===e,rj=e=>E(e)?e.map(iS):[iS(e)],rU=(e,t,n)=>{if(t._n)return t;let r=t3((...e)=>rj(t(...e)),n);return r._c=!1,r},rH=(e,t,n)=>{let r=e._ctx;for(let n in e){if(rB(n))continue;let i=e[n];if(I(i))t[n]=rU(n,i,r);else if(null!=i){let e=rj(i);t[n]=()=>e}}},rq=(e,t)=>{let n=rj(t);e.slots.default=()=>n},rW=(e,t,n)=>{for(let r in t)(n||!rB(r))&&(e[r]=t[r])},rK=r4;function rz(e){return rJ(e,nF)}function rJ(e,t){var n;let r,i;Q().__VUE__=!0;let{insert:l,remove:s,patchProp:o,createElement:a,createText:c,createComment:d,setText:h,setElementText:f,parentNode:p,nextSibling:g,setScopeId:m=S,insertStaticContent:y}=e,x=(e,t,n,r=null,i=null,l=null,s,o=null,a=!!t.dynamicChildren)=>{if(e===t)return;e&&!id(e,t)&&(r=es(e),et(e,i,l,!0),e=null),-2===t.patchFlag&&(a=!1,t.dynamicChildren=null);let{type:c,ref:u,shapeFlag:d}=t;switch(c){case r9:C(e,t,n,r);break;case r7:k(e,t,n,r);break;case ie:null==e&&w(t,n,r,s);break;case r5:B(e,t,n,r,i,l,s,o,a);break;default:1&d?N(e,t,n,r,i,l,s,o,a):6&d?U(e,t,n,r,i,l,s,o,a):64&d?c.process(e,t,n,r,i,l,s,o,a,ec):128&d&&c.process(e,t,n,r,i,l,s,o,a,ec)}null!=u&&i?nE(u,e&&e.ref,l,t||e,!t):null==u&&e&&null!=e.ref&&nE(e.ref,null,l,e,!0)},C=(e,t,n,r)=>{if(null==e)l(t.el=c(t.children),n,r);else{let n=t.el=e.el;t.children!==e.children&&h(n,t.children)}},k=(e,t,n,r)=>{null==e?l(t.el=d(t.children||""),n,r):t.el=e.el},w=(e,t,n,r)=>{[e.el,e.anchor]=y(e.children,t,n,r,e.el,e.anchor)},N=(e,t,n,r,i,l,s,o,a)=>{if("svg"===t.type?s="svg":"math"===t.type&&(s="mathml"),null==e)R(t,n,r,i,l,s,o,a);else{let n=e.el&&e.el._isVueCE?e.el:null;try{n&&n._beginPatch(),L(e,t,i,l,s,o,a)}finally{n&&n._endPatch()}}},R=(e,t,n,r,i,s,c,u)=>{let d,h,{props:p,shapeFlag:g,transition:m,dirs:y}=e;if(d=e.el=a(e.type,s,p&&p.is,p),8&g?f(d,e.children):16&g&&F(e.children,d,null,r,i,rG(e,s),c,u),y&&t6(e,null,r,"created"),O(d,e,e.scopeId,c,r),p){for(let e in p)"value"===e||$(e)||o(d,e,null,p[e],s,r);"value"in p&&o(d,"value",null,p.value,s),(h=p.onVnodeBeforeMount)&&iT(h,r,e)}y&&t6(e,null,r,"beforeMount");let b=rQ(i,m);b&&m.beforeEnter(d),l(d,t,n),((h=p&&p.onVnodeMounted)||b||y)&&rK(()=>{h&&iT(h,r,e),b&&m.enter(d),y&&t6(e,null,r,"mounted")},i)},O=(e,t,n,r,i)=>{if(n&&m(e,n),r)for(let t=0;t{for(let c=a;c{let a,c=t.el=e.el,{patchFlag:u,dynamicChildren:d,dirs:h}=t;u|=16&e.patchFlag;let p=e.props||b,g=t.props||b;if(n&&rX(n,!1),(a=g.onVnodeBeforeUpdate)&&iT(a,n,t,e),h&&t6(t,e,n,"beforeUpdate"),n&&rX(n,!0),d&&(!e.dynamicChildren||e.dynamicChildren.length!==d.length)&&(u=0,s=!1,d=null),(p.innerHTML&&null==g.innerHTML||p.textContent&&null==g.textContent)&&f(c,""),d?D(e.dynamicChildren,d,c,n,r,rG(t,i),l):s||X(e,t,c,null,n,r,rG(t,i),l,!1),u>0){if(16&u)V(c,p,g,n,i);else if(2&u&&p.class!==g.class&&o(c,"class",null,g.class,i),4&u&&o(c,"style",p.style,g.style,i),8&u){let e=t.dynamicProps;for(let t=0;t{a&&iT(a,n,t,e),h&&t6(t,e,n,"updated")},r)},D=(e,t,n,r,i,l,s)=>{for(let o=0;o{if(t!==n){if(t!==b)for(let l in t)$(l)||l in n||o(e,l,t[l],null,i,r);for(let l in n){if($(l))continue;let s=n[l],a=t[l];s!==a&&"value"!==l&&o(e,l,a,s,i,r)}"value"in n&&o(e,"value",t.value,n.value,i)}},B=(e,t,n,r,i,s,o,a,u)=>{let d=t.el=e?e.el:c(""),h=t.anchor=e?e.anchor:c(""),{patchFlag:f,dynamicChildren:p,slotScopeIds:g}=t;g&&(a=a?a.concat(g):g),null==e?(l(d,n,r),l(h,n,r),F(t.children||[],n,h,i,s,o,a,u)):f>0&&64&f&&p&&e.dynamicChildren&&e.dynamicChildren.length===p.length?(D(e.dynamicChildren,p,n,i,s,o,a),(null!=t.key||i&&t===i.subTree)&&rZ(e,t,!0)):X(e,t,n,h,i,s,o,a,u)},U=(e,t,n,r,i,l,s,o,a)=>{t.slotScopeIds=o,null==e?512&t.shapeFlag?i.ctx.activate(t,n,r,s,a):q(t,n,r,i,l,s,a):W(e,t,a)},q=(e,t,n,r,i,l,s)=>{var o,a,c;let d,h,f,p=(o=e,a=r,c=i,d=o.type,h=(a?a.appContext:o.appContext)||iw,(f={uid:iN++,vnode:o,type:d,parent:a,appContext:h,root:null,next:null,subTree:null,effect:null,update:null,job:null,scope:new em(!0),render:null,proxy:null,exposed:null,exposeProxy:null,withProxy:null,provides:a?a.provides:Object.create(h.provides),ids:a?a.ids:["",0,0],accessCache:null,renderCache:[],components:null,directives:null,propsOptions:function e(t,n,r=!1){let i=r?rD:n.propsCache,l=i.get(t);if(l)return l;let s=t.props,o={},a=[],c=!1;if(!I(t)){let i=t=>{c=!0;let[r,i]=e(t,n,!0);T(o,r),i&&a.push(...i)};!r&&n.mixins.length&&n.mixins.forEach(i),t.extends&&i(t.extends),t.mixins&&t.mixins.forEach(i)}if(!s&&!c)return M(t)&&i.set(t,_),_;if(E(s))for(let e=0;e{let r=e(t,n,!0);r&&(a=!0,T(o,r))};!r&&n.mixins.length&&n.mixins.forEach(i),t.extends&&i(t.extends),t.mixins&&t.mixins.forEach(i)}return s||a?(E(s)?s.forEach(e=>o[e]=null):T(o,s),M(t)&&i.set(t,o),o):(M(t)&&i.set(t,null),null)}(d,h),emit:null,emitted:null,propsDefaults:b,inheritAttrs:d.inheritAttrs,ctx:b,data:b,props:b,attrs:b,slots:b,refs:b,setupState:b,setupContext:null,suspense:c,suspenseId:c?c.pendingId:0,asyncDep:null,asyncResolved:!1,isMounted:!1,isUnmounted:!1,isDeactivated:!1,bc:null,c:null,bm:null,m:null,bu:null,u:null,um:null,bum:null,da:null,a:null,rtg:null,rtc:null,ec:null,sp:null}).ctx={_:f},f.root=a?a.root:f,f.emit=rT.bind(null,f),o.ce&&o.ce(f),e.component=f);if(nW(e)&&(p.ctx.renderer=ec),function(e,t=!1,n=!1){t&&u(t);let{props:r,children:i}=e.vnode,l=iO(e);!function(e,t,n,r=!1){let i={},l=Object.create(rP);for(let n in e.propsDefaults=Object.create(null),rL(e,t,i,l),e.propsOptions[0])n in i||(i[n]=void 0);n?e.props=r?i:tc(i):e.type.props?e.props=i:e.props=l,e.attrs=l}(e,r,l,t);var s=n||t;let o=e.slots=Object.create(rP);if(32&e.vnode.shapeFlag){let e=i._;e?(rW(o,i,s),s&&J(o,"_",e,!0)):rH(i,o)}else i&&rq(e,i);l&&function(e,t){let n=e.type;e.accessCache=Object.create(null),e.proxy=new Proxy(e.ctx,ro);let{setup:r}=n;if(r){eE();let n=e.setupContext=r.length>1?iD(e):null,i=iI(e),l=t$(r,e,0,[e.props,n]),s=P(l);if(eI(),i(),(s||e.sp)&&!nH(e)&&nw(e),s){if(l.then(iR,iR),t)return l.then(n=>{u(!0);try{iP(e,n,t)}finally{u(!1)}}).catch(t=>{tV(t,e,0)});e.asyncDep=l}else iP(e,l,t)}else iL(e,t)}(e,t),t&&u(!1)}(p,!1,s),p.asyncDep){if(i&&i.registerDep(p,K,s),!e.el){let r=p.subTree=im(r7);k(null,r,t,n),e.placeholder=r.el}}else K(p,e,t,n,i,l,s)},W=(e,t,n)=>{let r=t.component=e.component;if(function(e,t,n){let{props:r,children:i,component:l}=e,{props:s,children:o,patchFlag:a}=t,c=l.emitsOptions;if(t.dirs||t.transition)return!0;if(!n||!(a>=0))return(!!i||!!o)&&(!o||!o.$stable)||r!==s&&(r?!s||rR(r,s,c):!!s);if(1024&a)return!0;if(16&a)return r?rR(r,s,c):!!s;if(8&a){let e=t.dynamicProps;for(let t=0;t{e.scope.on();let a=e.effect=new ey(()=>{if(e.isMounted){let t,{next:n,bu:r,u:i,parent:a,vnode:u}=e;{let t=function e(t){let n=t.subTree.component;if(n)if(n.asyncDep&&!n.asyncResolved)return n;else return e(n)}(e);if(t){n&&(n.el=u.el,G(e,n,o)),t.asyncDep.then(()=>{rK(()=>{e.isUnmounted||c()},l)});return}}let d=n;rX(e,!1),n?(n.el=u.el,G(e,n,o)):n=u,r&&z(r),(t=n.props&&n.props.onVnodeBeforeUpdate)&&iT(t,a,n,u),rX(e,!0);let h=rA(e),f=e.subTree;e.subTree=h,x(f,h,p(f.el),es(f),e,l,s),n.el=h.el,null===d&&rM(e,h.el),i&&rK(i,l),(t=n.props&&n.props.onVnodeUpdated)&&rK(()=>iT(t,a,n,u),l)}else{let o,{el:a,props:c}=t,{bm:u,m:d,parent:h,root:f,type:p}=e,g=nH(t);if(rX(e,!1),u&&z(u),!g&&(o=c&&c.onVnodeBeforeMount)&&iT(o,h,t),rX(e,!0),a&&i){let t=()=>{e.subTree=rA(e),i(a,e.subTree,e,l,null)};g&&p.__asyncHydrate?p.__asyncHydrate(a,e,t):t()}else{f.ce&&f.ce._hasShadowRoot()&&f.ce._injectChildStyle(p,e.parent?e.parent.type:void 0);let i=e.subTree=rA(e);x(null,i,n,r,e,l,s),t.el=i.el}if(d&&rK(d,l),!g&&(o=c&&c.onVnodeMounted)){let e=t;rK(()=>iT(o,h,e),l)}(256&t.shapeFlag||h&&nH(h.vnode)&&256&h.vnode.shapeFlag)&&e.a&&rK(e.a,l),e.isMounted=!0,t=n=r=null}});e.scope.off();let c=e.update=a.run.bind(a),u=e.job=a.runIfDirty.bind(a);u.i=e,u.id=e.uid,a.scheduler=()=>tJ(u),rX(e,!0),c()},G=(e,t,n)=>{t.component=e;let r=e.vnode.props;e.vnode=t,e.next=null,function(e,t,n,r){let{props:i,attrs:l,vnode:{patchFlag:s}}=e,o=tm(i),[a]=e.propsOptions,c=!1;if((r||s>0)&&!(16&s)){if(8&s){let n=e.vnode.dynamicProps;for(let r=0;r{let{vnode:r,slots:i}=e,l=!0,s=b;if(32&r.shapeFlag){let e=t._;e?n&&1===e?l=!1:rW(i,t,n):(l=!t.$stable,rH(t,i)),s=t}else t&&(rq(e,t),s={default:1});if(l)for(let e in i)rB(e)||null!=s[e]||delete i[e]})(e,t.children,n),eE(),tQ(e),eI()},X=(e,t,n,r,i,l,s,o,a=!1)=>{let c=e&&e.children,u=e?e.shapeFlag:0,d=t.children,{patchFlag:h,shapeFlag:p}=t;if(h>0){if(128&h)return void Y(c,d,n,r,i,l,s,o,a);else if(256&h)return void Z(c,d,n,r,i,l,s,o,a)}8&p?(16&u&&el(c,i,l),d!==c&&f(n,d)):16&u?16&p?Y(c,d,n,r,i,l,s,o,a):el(c,i,l,!0):(8&u&&f(n,""),16&p&&F(d,n,r,i,l,s,o,a))},Z=(e,t,n,r,i,l,s,o,a)=>{let c;e=e||_,t=t||_;let u=e.length,d=t.length,h=Math.min(u,d);for(c=0;cd?el(e,i,l,!0,!1,h):F(t,n,r,i,l,s,o,a,h)},Y=(e,t,n,r,i,l,s,o,a)=>{let c=0,u=t.length,d=e.length-1,h=u-1;for(;c<=d&&c<=h;){let r=e[c],u=t[c]=a?ix(t[c]):iS(t[c]);if(id(r,u))x(r,u,n,null,i,l,s,o,a);else break;c++}for(;c<=d&&c<=h;){let r=e[d],c=t[h]=a?ix(t[h]):iS(t[h]);if(id(r,c))x(r,c,n,null,i,l,s,o,a);else break;d--,h--}if(c>d){if(c<=h){let e=h+1,d=eh)for(;c<=d;)et(e[c],i,l,!0),c++;else{let f,p=c,g=c,m=new Map;for(c=g;c<=h;c++){let e=t[c]=a?ix(t[c]):iS(t[c]);null!=e.key&&m.set(e.key,c)}let y=0,b=h-g+1,S=!1,C=0,k=Array(b);for(c=0;c=b){et(u,i,l,!0);continue}if(null!=u.key)r=m.get(u.key);else for(f=g;f<=h;f++)if(0===k[f-g]&&id(u,t[f])){r=f;break}void 0===r?et(u,i,l,!0):(k[r-g]=c+1,r>=C?C=r:S=!0,x(u,t[r],n,null,i,l,s,o,a),y++)}let T=S?function(e){let t,n,r,i,l,s=e.slice(),o=[0],a=e.length;for(t=0;t>1]]0&&(s[t]=o[r-1]),o[r]=t)}}for(r=o.length,i=o[r-1];r-- >0;)o[r]=i,i=s[i];return o}(k):_;for(f=T.length-1,c=b-1;c>=0;c--){let e=g+c,d=t[e],h=t[e+1],p=e+1{let{el:o,type:a,transition:c,children:u,shapeFlag:d}=e;if(6&d)return void ee(e.component.subTree,t,n,r);if(128&d)return void e.suspense.move(t,n,r);if(64&d)return void a.move(e,t,n,ec);if(a===r5){l(o,t,n);for(let e=0;e{let i;for(;e&&e!==t;)i=g(e),l(e,n,r),e=i;l(t,n,r)})(e,t,n);if(2!==r&&1&d&&c)if(0===r)c.persisted&&!o[nd]?l(o,t,n):(c.beforeEnter(o),l(o,t,n),rK(()=>c.enter(o),i));else{let{leave:r,delayLeave:i,afterLeave:a}=c,u=()=>{e.ctx.isUnmounted?s(o):l(o,t,n)},d=()=>{let e=o._isLeaving||!!o[nd];o._isLeaving&&o[nd](!0),c.persisted&&!e?u():r(o,()=>{u(),a&&a()})};i?i(o,u,d):d()}else l(o,t,n)},et=(e,t,n,r=!1,i=!1)=>{let l,{type:s,props:o,ref:a,children:c,dynamicChildren:u,shapeFlag:d,patchFlag:h,dirs:f,cacheIndex:p,memo:g}=e;if(-2===h&&(i=!1),null!=a&&(eE(),nE(a,null,n,e,!0),eI()),null!=p&&(t.renderCache[p]=void 0),256&d)return void t.ctx.deactivate(e);let m=1&d&&f,y=!nH(e);if(y&&(l=o&&o.onVnodeBeforeUnmount)&&iT(l,t,e),6&d)ei(e.component,n,r);else{if(128&d)return void e.suspense.unmount(n,r);m&&t6(e,null,t,"beforeUnmount"),64&d?e.type.remove(e,t,n,ec,r):u&&!u.hasOnce&&(s!==r5||h>0&&64&h)?el(u,t,n,!1,!0):(s===r5&&384&h||!i&&16&d)&&el(c,t,n),r&&en(e)}let b=null!=g&&null==p;(y&&(l=o&&o.onVnodeUnmounted)||m||b)&&rK(()=>{l&&iT(l,t,e),m&&t6(e,null,t,"unmounted"),b&&(e.el=null)},n)},en=e=>{let{type:t,el:n,anchor:r,transition:i}=e;if(t===r5)return void er(n,r);if(t===ie)return void(({el:e,anchor:t})=>{let n;for(;e&&e!==t;)n=g(e),s(e),e=n;s(t)})(e);let l=()=>{s(n),i&&!i.persisted&&i.afterLeave&&i.afterLeave()};if(1&e.shapeFlag&&i&&!i.persisted){let{leave:t,delayLeave:r}=i,s=()=>t(n,l);r?r(e.el,l,s):s()}else l()},er=(e,t)=>{let n;for(;e!==t;)n=g(e),s(e),e=n;s(t)},ei=(e,t,n)=>{let{bum:r,scope:i,job:l,subTree:s,um:o,m:a,a:c}=e;rY(a),rY(c),r&&z(r),i.stop(),l&&(l.flags|=8,et(s,e,t,n)),o&&rK(o,t),rK(()=>{e.isUnmounted=!0},t)},el=(e,t,n,r=!1,i=!1,l=0)=>{for(let s=l;s{if(6&e.shapeFlag)return es(e.component.subTree);if(128&e.shapeFlag)return e.suspense.next();let t=g(e.anchor||e.el),n=t&&t[nr];return n?g(n):t},eo=!1,ea=(e,t,n)=>{let r;null==e?t._vnode&&(et(t._vnode,null,null,!0),r=t._vnode.component):x(t._vnode||null,e,t,null,null,null,n),t._vnode=e,eo||(eo=!0,tQ(r),tZ(),eo=!1)},ec={p:x,um:et,m:ee,r:en,mt:q,mc:F,pc:X,pbc:D,n:es,o:e};return t&&([r,i]=t(ec)),{render:ea,hydrate:r,createApp:(n=r,function(e,t=null){I(e)||(e=T({},e)),null==t||M(t)||(t=null);let r=rS(),i=new WeakSet,l=[],s=!1,o=r.app={_uid:rx++,_component:e,_props:t,_container:null,_context:r,_instance:null,version:iq,get config(){return r.config},set config(v){},use:(e,...t)=>(i.has(e)||(e&&I(e.install)?(i.add(e),e.install(o,...t)):I(e)&&(i.add(e),e(o,...t))),o),mixin:e=>(r.mixins.includes(e)||r.mixins.push(e),o),component:(e,t)=>t?(r.components[e]=t,o):r.components[e],directive:(e,t)=>t?(r.directives[e]=t,o):r.directives[e],mount(i,l,a){if(!s){let c=o._ceVNode||im(e,t);return c.appContext=r,!0===a?a="svg":!1===a&&(a=void 0),l&&n?n(c,i):ea(c,i,a),s=!0,o._container=i,i.__vue_app__=o,iV(c.component)}},onUnmount(e){l.push(e)},unmount(){s&&(tD(l,o._instance,16),ea(null,o._container),delete o._container.__vue_app__)},provide:(e,t)=>(r.provides[e]=t,o),runWithContext(e){let t=rC;rC=o;try{return e()}finally{rC=t}}};return o})}}function rG({type:e,props:t},n){return"svg"===n&&"foreignObject"===e||"mathml"===n&&"annotation-xml"===e&&t&&t.encoding&&t.encoding.includes("html")?void 0:n}function rX({effect:e,job:t},n){n?(e.flags|=32,t.flags|=4):(e.flags&=-33,t.flags&=-5)}function rQ(e,t){return(!e||e&&!e.pendingBranch)&&t&&!t.persisted}function rZ(e,t,n=!1){let r=e.children,i=t.children;if(E(r)&&E(i))for(let e=0;ee.__isSuspense,r1=0;function r2(e,t){let n=e.props&&e.props[t];I(n)&&n()}function r3(e,t,n,r,i,l,s,o,a,c,u=!1){var d;let h,f,{p:p,m:g,um:m,n:y,o:{parentNode:b,remove:_}}=c,S=null!=(h=(d=e).props&&d.props.suspensible)&&!1!==h;S&&t&&t.pendingBranch&&(f=t.pendingId,t.deps++);let x=e.props?X(e.props.timeout):void 0,C=l,k={vnode:e,parent:t,parentComponent:n,namespace:s,container:r,hiddenContainer:i,deps:0,pendingId:r1++,timeout:"number"==typeof x?x:-1,activeBranch:null,isFallbackMountPending:!1,pendingBranch:null,isInFallback:!u,isHydrating:u,isUnmounted:!1,effects:[],resolve(e=!1,n=!1){let{vnode:r,activeBranch:i,pendingBranch:s,pendingId:o,effects:a,parentComponent:c,container:u,isInFallback:d}=k,h=!1;if(k.isHydrating)k.isHydrating=!1;else if(!e){h=i&&s.transition&&"out-in"===s.transition.mode;let e=!1;h&&(i.transition.afterLeave=()=>{o===k.pendingId&&(g(s,u,l!==C||e?l:y(i),0),tX(a),d&&r.ssFallback&&(r.ssFallback.el=null))}),i&&!k.isFallbackMountPending&&(b(i.el)===u&&(l=y(i),e=!0),m(i,c,k,!0),!h&&d&&r.ssFallback&&rK(()=>r.ssFallback.el=null,k)),h||g(s,u,l,0)}k.isFallbackMountPending=!1,r8(k,s),k.pendingBranch=null,k.isInFallback=!1;let p=k.parent,_=!1;for(;p;){if(p.pendingBranch){for(let e=0;e{k.isFallbackMountPending=!1,k.isInFallback&&(p(null,e,i,s,r,null,l,o,a),r8(k,e))},u=e.transition&&"out-in"===e.transition.mode;u&&(k.isFallbackMountPending=!0,n.transition.afterLeave=c),k.isInFallback=!0,m(n,r,null,!0),u||c()},move(e,t,n){k.activeBranch&&g(k.activeBranch,e,t,n),k.container=e},next:()=>k.activeBranch&&y(k.activeBranch),registerDep(e,t,n){let r=!!k.pendingBranch;r&&k.deps++;let i=e.vnode.el;e.asyncDep.catch(t=>{tV(t,e,0)}).then(l=>{if(e.isUnmounted||k.isUnmounted||k.pendingId!==e.suspenseId)return;iR(),e.asyncResolved=!0;let{vnode:o}=e;iP(e,l,!1),i&&(o.el=i);let a=!i&&e.subTree.el;t(e,o,b(i||e.subTree.el),i?null:y(e.subTree),k,s,n),a&&(o.placeholder=null,_(a)),rM(e,o.el),r&&0==--k.deps&&k.resolve()})},unmount(e,t){k.isUnmounted=!0,k.activeBranch&&m(k.activeBranch,n,e,t),k.pendingBranch&&m(k.pendingBranch,n,e,t)}};return k}function r6(e){let t;if(I(e)){let n=is&&e._c;n&&(e._d=!1,ii()),e=e(),n&&(e._d=!0,t=ir,il())}return E(e)&&(e=function(e){let t;for(let n=0;nt!==e)),e}function r4(e,t){t&&t.pendingBranch?E(e)?t.effects.push(...e):t.effects.push(e):tX(e)}function r8(e,t){e.activeBranch=t;let{vnode:n,parentComponent:r}=e,i=t.el;for(;!i&&t.component;)i=(t=t.component.subTree).el;n.el=i,r&&r.subTree===n&&(r.vnode.el=i,rM(r,i))}let r5=Symbol.for("v-fgt"),r9=Symbol.for("v-txt"),r7=Symbol.for("v-cmt"),ie=Symbol.for("v-stc"),it=[],ir=null;function ii(e=!1){it.push(ir=e?null:[])}function il(){it.pop(),ir=it[it.length-1]||null}let is=1;function io(e,t=!1){is+=e,e<0&&ir&&t&&(ir.hasOnce=!0)}function ia(e){return e.dynamicChildren=is>0?ir||_:null,il(),is>0&&ir&&ir.push(e),e}function ic(e,t,n,r,i){return ia(im(e,t,n,r,i,!0))}function iu(e){return!!e&&!0===e.__v_isVNode}function id(e,t){return e.type===t.type&&e.key===t.key}let ih=({key:e})=>null!=e?e:null,ip=({ref:e,ref_key:t,ref_for:n})=>("number"==typeof e&&(e=""+e),null!=e?R(e)||t_(e)||I(e)?{i:t0,r:e,k:t,f:!!n}:e:null);function ig(e,t=null,n=null,r=0,i=null,l=+(e!==r5),s=!1,o=!1){let a={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&ih(t),ref:t&&ip(t),scopeId:t1,slotScopeIds:null,children:n,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetStart:null,targetAnchor:null,staticCount:0,shapeFlag:l,patchFlag:r,dynamicProps:i,dynamicChildren:null,appContext:null,ctx:t0};return o?(iC(a,n),128&l&&e.normalize(a)):n&&(a.shapeFlag|=R(n)?8:16),is>0&&!s&&ir&&(a.patchFlag>0||6&l)&&32!==a.patchFlag&&ir.push(a),a}let im=function(e,t=null,n=null,r=0,i=null,l=!1){var s;if(e&&e!==rt||(e=r7),iu(e)){let r=iy(e,t,!0);return n&&iC(r,n),is>0&&!l&&ir&&(6&r.shapeFlag?ir[ir.indexOf(e)]=r:ir.push(r)),r.patchFlag=-2,r}if(I(s=e)&&"__vccOpts"in s&&(e=e.__vccOpts),t){let{class:e,style:n}=t=iv(t);e&&!R(e)&&(t.class=ei(e)),M(n)&&(tg(n)&&!E(n)&&(n=T({},n)),t.style=Y(n))}let o=R(e)?1:r0(e)?128:e.__isTeleport?64:M(e)?4:2*!!I(e);return ig(e,t,n,r,i,o,l,!0)};function iv(e){return e?tg(e)||rF(e)?T({},e):e:null}function iy(e,t,n=!1,r=!1){let{props:i,ref:l,patchFlag:s,children:o,transition:a}=e,c=t?ik(i||{},t):i,u={__v_isVNode:!0,__v_skip:!0,type:e.type,props:c,key:c&&ih(c),ref:t&&t.ref?n&&l?E(l)?l.concat(ip(t)):[l,ip(t)]:ip(t):l,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:o,target:e.target,targetStart:e.targetStart,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==r5?-1===s?16:16|s:s,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:a,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&iy(e.ssContent),ssFallback:e.ssFallback&&iy(e.ssFallback),placeholder:e.placeholder,el:e.el,anchor:e.anchor,ctx:e.ctx,ce:e.ce};return a&&r&&nC(u,a.clone(u)),u}function ib(e=" ",t=0){return im(r9,null,e,t)}function i_(e="",t=!1){return t?(ii(),ic(r7,null,e)):im(r7,null,e)}function iS(e){return null==e||"boolean"==typeof e?im(r7):E(e)?im(r5,null,e.slice()):iu(e)?ix(e):im(r9,null,String(e))}function ix(e){return null===e.el&&-1!==e.patchFlag||e.memo?e:iy(e)}function iC(e,t){let n=0,{shapeFlag:r}=e;if(null==t)t=null;else if(E(t))n=16;else if("object"==typeof t)if(65&r){let n=t.default;n&&(n._c&&(n._d=!1),iC(e,n()),n._c&&(n._d=!0));return}else{n=32;let r=t._;r||rF(t)?3===r&&t0&&(1===t0.slots._?t._=1:(t._=2,e.patchFlag|=1024)):t._ctx=t0}else if(I(t)){if(65&r)return void iC(e,{default:t});t={default:t,_ctx:t0},n=32}else t=String(t),64&r?(n=16,t=[ib(t)]):n=8;e.children=t,e.shapeFlag|=n}function ik(...e){let t={};for(let n=0;niA||t0;c=e=>{iA=e},u=e=>{iM=e};let iI=e=>{let t=iA;return c(e),e.scope.on(),()=>{e.scope.off(),c(t)}},iR=()=>{iA&&iA.scope.off(),c(null)};function iO(e){return 4&e.vnode.shapeFlag}let iM=!1;function iP(e,t,n){I(t)?e.render=t:M(t)&&(e.setupState=tN(t)),iL(e,n)}function iF(e){d=e,h=e=>{e.render._rc&&(e.withProxy=new Proxy(e.ctx,ra))}}function iL(e,t,n){let r=e.type;if(!e.render){if(!t&&d&&!r.render){let t=r.template||rf(e).template;if(t){let{isCustomElement:n,compilerOptions:i}=e.appContext.config,{delimiters:l,compilerOptions:s}=r,o=T(T({isCustomElement:n,delimiters:l},i),s);r.render=d(t,o)}}e.render=r.render||S,h&&h(e)}{let t=iI(e);eE();try{!function(e){let t=rf(e),n=e.proxy,r=e.ctx;rd=!1,t.beforeCreate&&rh(t.beforeCreate,e,"bc");let{data:i,computed:l,methods:s,watch:o,provide:a,inject:c,created:u,beforeMount:d,mounted:h,beforeUpdate:f,updated:p,activated:g,deactivated:m,beforeUnmount:y,unmounted:b,render:_,renderTracked:x,renderTriggered:C,errorCaptured:k,serverPrefetch:T,expose:w,inheritAttrs:N,components:A,directives:O}=t;if(c&&function(e,t){for(let n in E(e)&&(e=rv(e)),e){let r,i=e[n];t_(r=M(i)?"default"in i?t8(i.from||n,i.default,!0):t8(i.from||n):t8(i))?Object.defineProperty(t,n,{enumerable:!0,configurable:!0,get:()=>r.value,set:e=>r.value=e}):t[n]=r}}(c,r),s)for(let e in s){let t=s[e];I(t)&&(r[e]=t.bind(n))}if(i){let t=i.call(n,n);M(t)&&(e.data=ta(t))}if(rd=!0,l)for(let e in l){let t=l[e],i=I(t)?t.bind(n,n):I(t.get)?t.get.bind(n,n):S,s=ij({get:i,set:!I(t)&&I(t.set)?t.set.bind(n):S});Object.defineProperty(r,e,{enumerable:!0,configurable:!0,get:()=>s.value,set:e=>s.value=e})}if(o)for(let e in o)!function e(t,n,r,i){let l=i.includes(".")?nt(r,i):()=>r[i];if(R(t)){let e=n[t];I(e)&&t7(l,e,void 0)}else if(I(t))t7(l,t.bind(r),void 0);else if(M(t))if(E(t))t.forEach(t=>e(t,n,r,i));else{let e=I(t.handler)?t.handler.bind(r):n[t.handler];I(e)&&t7(l,e,t)}}(o[e],r,n,e);if(a){let e=I(a)?a.call(n):a;Reflect.ownKeys(e).forEach(t=>{t4(t,e[t])})}function P(e,t){E(t)?t.forEach(t=>e(t.bind(n))):t&&e(t.bind(n))}if(u&&rh(u,e,"c"),P(n0,d),P(n1,h),P(n2,f),P(n3,p),P(nz,g),P(nJ,m),P(n7,k),P(n9,x),P(n5,C),P(n6,y),P(n4,b),P(n8,T),E(w))if(w.length){let t=e.exposed||(e.exposed={});w.forEach(e=>{Object.defineProperty(t,e,{get:()=>n[e],set:t=>n[e]=t,enumerable:!0})})}else e.exposed||(e.exposed={});_&&e.render===S&&(e.render=_),null!=N&&(e.inheritAttrs=N),A&&(e.components=A),O&&(e.directives=O)}(e)}finally{eI(),t()}}}let i$={get:(e,t)=>(eV(e,"get",""),e[t])};function iD(e){return{attrs:new Proxy(e.attrs,i$),slots:e.slots,emit:e.emit,expose:t=>{e.exposed=t||{}}}}function iV(e){return e.exposed?e.exposeProxy||(e.exposeProxy=new Proxy(tN(tv(e.exposed)),{get:(t,n)=>n in t?t[n]:n in rl?rl[n](e):void 0,has:(e,t)=>t in e||t in rl})):e.proxy}function iB(e,t=!0){return I(e)?e.displayName||e.name:e.name||t&&e.__name}let ij=(e,t)=>(function(e,t=!1){let n,r;return I(e)?n=e:(n=e.get,r=e.set),new tO(n,r,t)})(e,iM);function iU(e,t,n){try{io(-1);let r=arguments.length;if(2!==r)return r>3?n=Array.prototype.slice.call(arguments,2):3===r&&iu(n)&&(n=[n]),im(e,t,n);if(!M(t)||E(t))return im(e,null,t);if(iu(t))return im(e,null,[t]);return im(e,t)}finally{io(1)}}function iH(e,t){let n=e.memo;if(n.length!=t.length)return!1;for(let e=0;e0&&ir&&ir.push(e),!0}let iq="3.5.41",iW="u">typeof window&&window.trustedTypes;if(iW)try{m=iW.createPolicy("vue",{createHTML:e=>e})}catch(e){}let iK=m?e=>m.createHTML(e):e=>e,iz="u">typeof document?document:null,iJ=iz&&iz.createElement("template"),iG={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{let t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,r)=>{let i="svg"===t?iz.createElementNS("http://www.w3.org/2000/svg",e):"mathml"===t?iz.createElementNS("http://www.w3.org/1998/Math/MathML",e):n?iz.createElement(e,{is:n}):iz.createElement(e);return"select"===e&&r&&null!=r.multiple&&i.setAttribute("multiple",r.multiple),i},createText:e=>iz.createTextNode(e),createComment:e=>iz.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>iz.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,n,r,i,l){let s=n?n.previousSibling:t.lastChild;if(i&&(i===l||i.nextSibling))for(;t.insertBefore(i.cloneNode(!0),n),i!==l&&(i=i.nextSibling););else{iJ.innerHTML=iK("svg"===r?`${e}`:"mathml"===r?`${e}`:e);let i=iJ.content;if("svg"===r||"mathml"===r){let e=i.firstChild;for(;e.firstChild;)i.appendChild(e.firstChild);i.removeChild(e)}t.insertBefore(i,n)}return[s?s.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}},iX="transition",iQ="animation",iZ=Symbol("_vtc"),iY={name:String,type:String,css:{type:Boolean,default:!0},duration:[String,Number,Object],enterFromClass:String,enterActiveClass:String,enterToClass:String,appearFromClass:String,appearActiveClass:String,appearToClass:String,leaveFromClass:String,leaveActiveClass:String,leaveToClass:String},i0=T({},ng,iY),i1=((t=(e,{slots:t})=>iU(ny,i6(e),t)).displayName="Transition",t.props=i0,t),i2=(e,t=[])=>{E(e)?e.forEach(e=>e(...t)):e&&e(...t)},i3=e=>!!e&&(E(e)?e.some(e=>e.length>1):e.length>1);function i6(e){let t={};for(let n in e)n in iY||(t[n]=e[n]);if(!1===e.css)return t;let{name:n="v",type:r,duration:i,enterFromClass:l=`${n}-enter-from`,enterActiveClass:s=`${n}-enter-active`,enterToClass:o=`${n}-enter-to`,appearFromClass:a=l,appearActiveClass:c=s,appearToClass:u=o,leaveFromClass:d=`${n}-leave-from`,leaveActiveClass:h=`${n}-leave-active`,leaveToClass:f=`${n}-leave-to`}=e,p=function(e){if(null==e)return null;{if(M(e))return[function(e){return X(e)}(e.enter),function(e){return X(e)}(e.leave)];let t=function(e){return X(e)}(e);return[t,t]}}(i),g=p&&p[0],m=p&&p[1],{onBeforeEnter:y,onEnter:b,onEnterCancelled:_,onLeave:S,onLeaveCancelled:x,onBeforeAppear:C=y,onAppear:k=b,onAppearCancelled:w=_}=t,N=(e,t,n,r)=>{e._enterCancelled=r,i8(e,t?u:o),i8(e,t?c:s),n&&n()},A=(e,t)=>{e._isLeaving=!1,i8(e,d),i8(e,f),i8(e,h),t&&t()},E=e=>(t,n)=>{let i=e?k:b,s=()=>N(t,e,n);i2(i,[t,s]),i5(()=>{i8(t,e?a:l),i4(t,e?u:o),i3(i)||i7(t,r,g,s)})};return T(t,{onBeforeEnter(e){i2(y,[e]),i4(e,l),i4(e,s)},onBeforeAppear(e){i2(C,[e]),i4(e,a),i4(e,c)},onEnter:E(!1),onAppear:E(!0),onLeave(e,t){e._isLeaving=!0;let n=()=>A(e,t);i4(e,d),e._enterCancelled?(i4(e,h),lr(e)):(lr(e),i4(e,h)),i5(()=>{e._isLeaving&&(i8(e,d),i4(e,f),i3(S)||i7(e,r,m,n))}),i2(S,[e,n])},onEnterCancelled(e){N(e,!1,void 0,!0),i2(_,[e])},onAppearCancelled(e){N(e,!0,void 0,!0),i2(w,[e])},onLeaveCancelled(e){A(e),i2(x,[e])}})}function i4(e,t){t.split(/\s+/).forEach(t=>t&&e.classList.add(t)),(e[iZ]||(e[iZ]=new Set)).add(t)}function i8(e,t){t.split(/\s+/).forEach(t=>t&&e.classList.remove(t));let n=e[iZ];n&&(n.delete(t),n.size||(e[iZ]=void 0))}function i5(e){requestAnimationFrame(()=>{requestAnimationFrame(e)})}let i9=0;function i7(e,t,n,r){let i=e._endId=++i9,l=()=>{i===e._endId&&r()};if(null!=n)return setTimeout(l,n);let{type:s,timeout:o,propCount:a}=le(e,t);if(!s)return r();let c=s+"end",u=0,d=()=>{e.removeEventListener(c,h),l()},h=t=>{t.target===e&&++u>=a&&d()};setTimeout(()=>{u(n[e]||"").split(", "),i=r(`${iX}Delay`),l=r(`${iX}Duration`),s=lt(i,l),o=r(`${iQ}Delay`),a=r(`${iQ}Duration`),c=lt(o,a),u=null,d=0,h=0;t===iX?s>0&&(u=iX,d=s,h=l.length):t===iQ?c>0&&(u=iQ,d=c,h=a.length):h=(u=(d=Math.max(s,c))>0?s>c?iX:iQ:null)?u===iX?l.length:a.length:0;let f=u===iX&&/\b(?:transform|all)(?:,|$)/.test(r(`${iX}Property`).toString());return{type:u,timeout:d,propCount:h,hasTransform:f}}function lt(e,t){for(;e.lengthln(t)+ln(e[n])))}function ln(e){return"auto"===e?0:1e3*Number(e.slice(0,-1).replace(",","."))}function lr(e){return(e?e.ownerDocument:document).body.offsetHeight}let li=Symbol("_vod"),ll=Symbol("_vsh");function ls(e,t){e.style.display=t?e[li]:"none",e[ll]=!t}let lo=Symbol("");function la(e,t){if(1===e.nodeType){let r=e.style,i="";for(let e in t){var n;let l=null==(n=t[e])?"initial":"string"==typeof n?""===n?" ":n:String(n);r.setProperty(`--${e}`,l),i+=`--${e}: ${l};`}r[lo]=i}}let lc=/(?:^|;)\s*display\s*:/,lu=/\s*!important$/;function ld(e,t,n){if(E(n))n.forEach(n=>ld(e,t,n));else if(null==n&&(n=""),t.startsWith("--"))e.setProperty(t,n);else{let r=function(e,t){let n=lf[t];if(n)return n;let r=j(t);if("filter"!==r&&r in e)return lf[t]=r;r=q(r);for(let n=0;n111===e.charCodeAt(0)&&110===e.charCodeAt(1)&&e.charCodeAt(2)>96&&123>e.charCodeAt(2),lk=(e,t,n,r,i,l)=>{let s="svg"===i;if("class"===t){var o;let t;o=r,(t=e[iZ])&&(o=(o?[o,...t]:[...t]).join(" ")),null==o?e.removeAttribute("class"):s?e.setAttribute("class",o):e.className=o}else"style"===t?function(e,t,n){let r=e.style,i=R(n),l=!1;if(n&&!i){if(t)if(R(t))for(let e of t.split(";")){let t=e.slice(0,e.indexOf(":")).trim();null==n[t]&&ld(r,t,"")}else for(let e in t)null==n[e]&&ld(r,e,"");for(let i in n){var s,o,a,c;"display"===i&&(l=!0);let u=n[i];null!=u?(s=e,o=i,a=!R(t)&&t?t[i]:void 0,c=u,"TEXTAREA"===s.tagName&&("width"===o||"height"===o)&&R(c)&&a===c||ld(r,i,u)):ld(r,i,"")}}else if(i){if(t!==n){let e=r[lo];e&&(n+=";"+e),r.cssText=n,l=lc.test(n)}}else t&&e.removeAttribute("style");li in e&&(e[li]=l?r.display:"",e[ll]&&(r.display="none"))}(e,n,r):C(t)?k(t)||function(e,t,n,r=null){let i=e[ly]||(e[ly]={}),l=i[t];if(n&&l)l.value=n;else{let[a,c]=function(e){let t,n;for(;(n=e.match(lb))&&!l_.test(e);)t||(t={}),e=e.slice(0,e.length-n[1].length),t[n[1].toLowerCase()]=!0;return[":"===e[2]?e.slice(3):H(e.slice(2)),t]}(t);if(n){var s,o;let l;lv(e,a,i[t]=(s=n,o=r,(l=e=>{if(e._vts){if(e._vts<=l.attached)return}else e._vts=Date.now();let t=l.value;if(E(t)){let n=e.stopImmediatePropagation;e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0};let r=t.slice(),i=[e];for(let t=0;tlS=0),lS=Date.now()),l),c)}else l&&(e.removeEventListener(a,l,c),i[t]=void 0)}}(e,t,r,l):("."===t[0]?(t=t.slice(1),0):"^"===t[0]?(t=t.slice(1),1):!function(e,t,n,r){if(r)return!!("innerHTML"===t||"textContent"===t||t in e&&lC(t)&&I(n));if("spellcheck"===t||"draggable"===t||"translate"===t||"autocorrect"===t||"sandbox"===t&&"IFRAME"===e.tagName||"form"===t||"list"===t&&"INPUT"===e.tagName||"type"===t&&"TEXTAREA"===e.tagName)return!1;if("width"===t||"height"===t){let t=e.tagName;if("IMG"===t||"VIDEO"===t||"CANVAS"===t||"SOURCE"===t)return!1}return!(lC(t)&&R(n))&&t in e}(e,t,r,s))?e._isVueCE&&(function(e,t){let n=e._def.props;if(!n)return!1;let r=j(t);return Array.isArray(n)?n.some(e=>j(e)===r):Object.keys(n).some(e=>j(e)===r)}(e,t)||e._def.__asyncLoader&&(/[A-Z]/.test(t)||!R(r)))?lm(e,j(t),r,l,t):("true-value"===t?e._trueValue=r:"false-value"===t&&(e._falseValue=r),lg(e,t,r,s)):(lm(e,t,r),e.tagName.includes("-")||"value"!==t&&"checked"!==t&&"selected"!==t||lg(e,t,r,s,l,"value"!==t))},lT={};function lw(e,t,n){let r,i=nT(e,t);"[object Object]"===(r=i,F.call(r))&&(i=T({},i,t));class l extends lA{constructor(e){super(i,e,n)}}return l.def=i,l}let lN="u">typeof HTMLElement?HTMLElement:class{};class lA extends lN{constructor(e,t={},n=l5){super(),this._def=e,this._props=t,this._createApp=n,this._isVueCE=!0,this._instance=null,this._app=null,this._nonce=this._def.nonce,this._connected=!1,this._resolved=!1,this._patching=!1,this._dirty=!1,this._numberProps=null,this._styleChildren=new WeakSet,this._styleAnchors=new WeakMap,this._ob=null,this.shadowRoot&&n!==l5?this._root=this.shadowRoot:!1!==e.shadowRoot?(this.attachShadow(T({},e.shadowRootOptions,{mode:"open"})),this._root=this.shadowRoot):this._root=this}connectedCallback(){if(!this.isConnected)return;this.shadowRoot||this._resolved||this._parseSlots(),this._connected=!0;let e=this;for(;e=e&&(e.assignedSlot||e.parentNode||e.host);)if(e instanceof lA){this._parent=e;break}this._instance||(this._resolved?this._mount(this._def):e&&e._pendingResolve?this._pendingResolve=e._pendingResolve.then(()=>{if(this._pendingResolve=void 0,this.isConnected)return this._resolveDef()}):this._resolveDef())}_setParent(e=this._parent){e&&(this._instance.parent=e._instance,this._inheritParentContext(e))}_inheritParentContext(e=this._parent){e&&this._app&&Object.setPrototypeOf(this._app._context.provides,e._instance.provides)}disconnectedCallback(){this._connected=!1,tz(()=>{!this._connected&&(this._ob&&(this._ob.disconnect(),this._ob=null),this._app&&this._app.unmount(),this._instance&&(this._instance.ce=void 0),this._app=this._instance=null,this._teleportTargets&&(this._teleportTargets.clear(),this._teleportTargets=void 0))})}_processMutations(e){for(let t of e)this._setAttr(t.attributeName)}_resolveDef(){if(this._pendingResolve)return this._pendingResolve;for(let e=0;e{let n;this._resolved=!0,this._pendingResolve=void 0;let{props:r,styles:i}=e;if(r&&!E(r))for(let e in r){let t=r[e];(t===Number||t&&t.type===Number)&&(e in this._props&&(this._props[e]=X(this._props[e])),(n||(n=Object.create(null)))[j(e)]=!0)}this._numberProps=n,this._resolveProps(e),this.shadowRoot&&this._applyStyles(i),this._mount(e)},t=this._def.__asyncLoader;if(t)return this._pendingResolve=t().then(t=>{t.configureApp=this._def.configureApp,e(this._def=t,!0)}),this._pendingResolve;e(this._def)}_mount(e){this._app=this._createApp(e),this._inheritParentContext(),e.configureApp&&e.configureApp(this._app),this._app._ceVNode=this._createVNode(),this._app.mount(this._root);let t=this._instance&&this._instance.exposed;if(t)for(let e in t)A(this,e)||Object.defineProperty(this,e,{get:()=>tT(t[e])})}_resolveProps(e){let{props:t}=e,n=E(t)?t:Object.keys(t||{});for(let e of Object.keys(this))"_"!==e[0]&&n.includes(e)&&this._setProp(e,this[e]);for(let e of n.map(j))Object.defineProperty(this,e,{get(){return this._getProp(e)},set(t){this._setProp(e,t,!0,!this._patching)}})}_setAttr(e){if(e.startsWith("data-v-"))return;let t=this.hasAttribute(e),n=t?this.getAttribute(e):lT,r=j(e);t&&this._numberProps&&this._numberProps[r]&&(n=X(n)),this._setProp(r,n,!1,!0)}_getProp(e){return this._props[e]}_setProp(e,t,n=!0,r=!1){if(t!==this._props[e]&&(this._dirty=!0,t===lT?delete this._props[e]:(this._props[e]=t,"key"===e&&this._app&&(this._app._ceVNode.key=t)),r&&this._instance&&this._update(),n)){let n=this._ob;n&&(this._processMutations(n.takeRecords()),n.disconnect()),!0===t?this.setAttribute(H(e),""):"string"==typeof t||"number"==typeof t?this.setAttribute(H(e),t+""):t||this.removeAttribute(H(e)),n&&n.observe(this,{attributes:!0})}}_update(){let e=this._createVNode();this._app&&(e.appContext=this._app._context),l8(e,this._root)}_createVNode(){let e={};this.shadowRoot||(e.onVnodeMounted=e.onVnodeUpdated=this._renderSlots.bind(this));let t=im(this._def,T(e,this._props));return this._instance||(t.ce=e=>{this._instance=e,e.ce=this,e.isCE=!0;let t=(e,t)=>{let n;this.dispatchEvent(new CustomEvent(e,"[object Object]"===(n=t[0],F.call(n))?T({detail:t},t[0]):{detail:t}))};e.emit=(e,...n)=>{t(e,n),H(e)!==e&&t(H(e),n)},this._setParent()}),t}_applyStyles(e,t,n){if(!e)return;if(t){if(t===this._def||this._styleChildren.has(t))return;this._styleChildren.add(t)}let r=this._nonce,i=this.shadowRoot,l=n?this._getStyleAnchor(n)||this._getStyleAnchor(this._def):this._getRootStyleInsertionAnchor(i),s=null;for(let o=e.length-1;o>=0;o--){let a=document.createElement("style");r&&a.setAttribute("nonce",r),a.textContent=e[o],i.insertBefore(a,s||l),s=a,0===o&&(n||this._styleAnchors.set(this._def,a),t&&this._styleAnchors.set(t,a))}}_getStyleAnchor(e){if(!e)return null;let t=this._styleAnchors.get(e);return t&&t.parentNode===this.shadowRoot?t:(t&&this._styleAnchors.delete(e),null)}_getRootStyleInsertionAnchor(e){for(let t=0;t{if(!n.length)return;let t=e.moveClass||`${e.name||"v"}-move`;if(!function(e,t,n){let r=e.cloneNode(),i=e[iZ];i&&i.forEach(e=>{e.split(/\s+/).forEach(e=>e&&r.classList.remove(e))}),n.split(/\s+/).forEach(e=>e&&r.classList.add(e)),r.style.display="none";let l=1===t.nodeType?t:t.parentNode;l.appendChild(r);let{hasTransform:s}=le(r);return l.removeChild(r),s}(n[0].el,i.vnode.el,t)){n=[];return}n.forEach(lF),n.forEach(lL);let r=n.filter(l$);lr(i.vnode.el),r.forEach(e=>{let n=e.el,r=n.style;i4(n,t),r.transform=r.webkitTransform=r.transitionDuration="";let i=n[lO]=e=>{(!e||e.target===n)&&(!e||e.propertyName.endsWith("transform"))&&(n.removeEventListener("transitionend",i),n[lO]=null,i8(n,t))};n.addEventListener("transitionend",i)}),n=[]}),()=>{let s=tm(e),o=i6(s),a=s.tag||r5;if(n=[],r)for(let e=0;eMath.abs(s-1)&&(s=1),.01>Math.abs(o-1)&&(o=1),n.transform=n.webkitTransform=`translate(${r/s}px,${i/o}px)`,n.transitionDuration="0s",e}}function lD(e){let t=e.getBoundingClientRect();return{left:t.left,top:t.top}}let lV=e=>{let t=e.props["onUpdate:modelValue"]||!1;return E(t)?e=>z(t,e):t};function lB(e){e.target.composing=!0}function lj(e){let t=e.target;t.composing&&(t.composing=!1,t.dispatchEvent(new Event("input")))}let lU=Symbol("_assign"),lH=Symbol("_initialValue");function lq(e,t,n){return t&&(e=e.trim()),n&&(e=G(e)),e}let lW={created(e,{modifiers:{lazy:t,trim:n,number:r}},i){e.parentNode&&("text"===e.type?e[lH]=e.defaultValue.replace(/[\r\n]/g,""):"textarea"===e.type&&(e[lH]=e.defaultValue.replace(/\r\n?/g,` +`))),e[lU]=lV(i);let l=r||i.props&&"number"===i.props.type;lv(e,t?"change":"input",t=>{t.target.composing||e[lU](lq(e.value,n,l))}),(n||l)&&lv(e,"change",()=>{e.value=lq(e.value,n,l)}),t||(lv(e,"compositionstart",lB),lv(e,"compositionend",lj),lv(e,"change",lj))},mounted(e,{value:t,modifiers:{trim:n,number:r}}){let i=e[lH];delete e[lH],void 0!==i&&("text"===e.type||"textarea"===e.type)&&e.value!==i?e[lU](lq(e.value,n,r)):e.value=null==t?"":t},beforeUpdate(e,{value:t,oldValue:n,modifiers:{lazy:r,trim:i,number:l}},s){if(e[lU]=lV(s),e.composing)return;let o=(l||"number"===e.type)&&!/^0\d/.test(e.value)?G(e.value):e.value,a=null==t?"":t;if(o===a)return;let c=e.getRootNode();(c instanceof Document||c instanceof ShadowRoot)&&c.activeElement===e&&"range"!==e.type&&(r&&t===n||i&&e.value.trim()===a)||(e.value=a)}},lK={deep:!0,created(e,t,n){e[lU]=lV(n),lv(e,"change",()=>{let t=e._modelValue,n=lQ(e),r=e.checked,i=e[lU];if(E(t)){let e=ed(t,n),l=-1!==e;if(r&&!l)i(t.concat(n));else if(!r&&l){let n=[...t];n.splice(e,1),i(n)}}else{let l;if("[object Set]"===(l=t,F.call(l))){let e=new Set(t);r?e.add(n):e.delete(n),i(e)}else i(lZ(e,r))}})},mounted:lz,beforeUpdate(e,t,n){e[lU]=lV(n),lz(e,t,n)}};function lz(e,{value:t,oldValue:n},r){let i;if(e._modelValue=t,E(t))i=ed(t,r.props.value)>-1;else{let l;if("[object Set]"===(l=t,F.call(l)))i=t.has(r.props.value);else{if(t===n)return;i=eu(t,lZ(e,!0))}}e.checked!==i&&(e.checked=i)}let lJ={created(e,{value:t},n){e.checked=eu(t,n.props.value),e[lU]=lV(n),lv(e,"change",()=>{e[lU](lQ(e))})},beforeUpdate(e,{value:t,oldValue:n},r){e[lU]=lV(r),t!==n&&(e.checked=eu(t,r.props.value))}},lG={deep:!0,created(e,{value:t,modifiers:{number:n}},r){e._modelValue=t,lv(e,"change",()=>{let t,r=Array.prototype.filter.call(e.options,e=>e.selected).map(e=>n?G(lQ(e)):lQ(e));e[lU](e.multiple?"[object Set]"===(t=e._modelValue,F.call(t))?new Set(r):r:r[0]),e._assigning=!0,tz(()=>{e._assigning=!1})}),e[lU]=lV(r)},mounted(e,{value:t}){lX(e,t)},beforeUpdate(e,{value:t},n){e._modelValue=t,e[lU]=lV(n)},updated(e,{value:t}){e._assigning||lX(e,t)}};function lX(e,t){let n,r=e.multiple,i=E(t);if(!r||i||"[object Set]"===(n=t,F.call(n))){for(let n=0,l=e.options.length;nString(e)===String(s)):l.selected=ed(t,s)>-1}else l.selected=t.has(s);else if(eu(lQ(l),t)){e.selectedIndex!==n&&(e.selectedIndex=n);return}}r||-1===e.selectedIndex||(e.selectedIndex=-1)}}function lQ(e){return"_value"in e?e._value:e.value}function lZ(e,t){let n=t?"_trueValue":"_falseValue";return n in e?e[n]:t}function lY(e,t,n,r,i){let l=function(e,t){switch(e){case"SELECT":return lG;case"TEXTAREA":return lW;default:switch(t){case"checkbox":return lK;case"radio":return lJ;default:return lW}}}(e.tagName,n.props&&n.props.type)[i];l&&l(e,t,n,r)}let l0=["ctrl","shift","alt","meta"],l1={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>"button"in e&&0!==e.button,middle:e=>"button"in e&&1!==e.button,right:e=>"button"in e&&2!==e.button,exact:(e,t)=>l0.some(n=>e[`${n}Key`]&&!t.includes(n))},l2={esc:"escape",space:" ",up:"arrow-up",left:"arrow-left",right:"arrow-right",down:"arrow-down",delete:"backspace"},l3=T({patchProp:lk},iG),l6=!1;function l4(){return f=l6?f:rz(l3),l6=!0,f}let l8=(...e)=>{(f||(f=rJ(l3))).render(...e)},l5=(...e)=>{let t=(f||(f=rJ(l3))).createApp(...e),{mount:n}=t;return t.mount=e=>{let r=se(e);if(!r)return;let i=t._component;I(i)||i.render||i.template||(i.template=r.innerHTML),1===r.nodeType&&(r.textContent="");let l=n(r,!1,l7(r));return r instanceof Element&&(r.removeAttribute("v-cloak"),r.setAttribute("data-v-app","")),l},t},l9=(...e)=>{let t=l4().createApp(...e),{mount:n}=t;return t.mount=e=>{let t=se(e);if(t)return n(t,!0,l7(t))},t};function l7(e){return e instanceof SVGElement?"svg":"function"==typeof MathMLElement&&e instanceof MathMLElement?"mathml":void 0}function se(e){return R(e)?document.querySelector(e):e}let st=Symbol(""),sn=Symbol(""),sr=Symbol(""),si=Symbol(""),sl=Symbol(""),ss=Symbol(""),so=Symbol(""),sa=Symbol(""),sc=Symbol(""),su=Symbol(""),sd=Symbol(""),sh=Symbol(""),sf=Symbol(""),sp=Symbol(""),sg=Symbol(""),sm=Symbol(""),sv=Symbol(""),sy=Symbol(""),sb=Symbol(""),s_=Symbol(""),sS=Symbol(""),sx=Symbol(""),sC=Symbol(""),sk=Symbol(""),sT=Symbol(""),sw=Symbol(""),sN=Symbol(""),sA=Symbol(""),sE=Symbol(""),sI=Symbol(""),sR=Symbol(""),sO=Symbol(""),sM=Symbol(""),sP=Symbol(""),sF=Symbol(""),sL=Symbol(""),s$=Symbol(""),sD=Symbol(""),sV=Symbol(""),sB={[st]:"Fragment",[sn]:"Teleport",[sr]:"Suspense",[si]:"KeepAlive",[sl]:"BaseTransition",[ss]:"openBlock",[so]:"createBlock",[sa]:"createElementBlock",[sc]:"createVNode",[su]:"createElementVNode",[sd]:"createCommentVNode",[sh]:"createTextVNode",[sf]:"createStaticVNode",[sp]:"resolveComponent",[sg]:"resolveDynamicComponent",[sm]:"resolveDirective",[sv]:"resolveFilter",[sy]:"withDirectives",[sb]:"renderList",[s_]:"renderSlot",[sS]:"createSlots",[sx]:"toDisplayString",[sC]:"mergeProps",[sk]:"normalizeClass",[sT]:"normalizeStyle",[sw]:"normalizeProps",[sN]:"guardReactiveProps",[sA]:"toHandlers",[sE]:"camelize",[sI]:"capitalize",[sR]:"toHandlerKey",[sO]:"setBlockTracking",[sM]:"pushScopeId",[sP]:"popScopeId",[sF]:"withCtx",[sL]:"unref",[s$]:"isRef",[sD]:"withMemo",[sV]:"isMemoSame"},sj={start:{line:1,column:1,offset:0},end:{line:1,column:1,offset:0},source:""};function sU(e,t,n,r,i,l,s,o=!1,a=!1,c=!1,u=sj){var d,h,f,p;return e&&(o?(e.helper(ss),e.helper((d=e.inSSR,h=c,d||h?so:sa))):e.helper((f=e.inSSR,p=c,f||p?sc:su)),s&&e.helper(sy)),{type:13,tag:t,props:n,children:r,patchFlag:i,dynamicProps:l,directives:s,isBlock:o,disableTracking:a,isComponent:c,loc:u}}function sH(e,t=sj){return{type:17,loc:t,elements:e}}function sq(e,t=sj){return{type:15,loc:t,properties:e}}function sW(e,t){return{type:16,loc:sj,key:R(e)?sK(e,!0):e,value:t}}function sK(e,t=!1,n=sj,r=0){return{type:4,loc:n,content:e,isStatic:t,constType:t?3:r}}function sz(e,t=sj){return{type:8,loc:t,children:e}}function sJ(e,t=[],n=sj){return{type:14,loc:n,callee:e,arguments:t}}function sG(e,t,n=!1,r=!1,i=sj){return{type:18,params:e,returns:t,newline:n,isSlot:r,loc:i}}function sX(e,t,n,r=!0){return{type:19,test:e,consequent:t,alternate:n,newline:r,loc:sj}}function sQ(e,{helper:t,removeHelper:n,inSSR:r}){if(!e.isBlock){var i,l;e.isBlock=!0,n((i=e.isComponent,r||i?sc:su)),t(ss),t((l=e.isComponent,r||l?so:sa))}}let sZ=new Uint8Array([123,123]),sY=new Uint8Array([125,125]);function s0(e){return e>=97&&e<=122||e>=65&&e<=90}function s1(e){return 32===e||10===e||9===e||12===e||13===e}function s2(e){return 47===e||62===e||s1(e)}function s3(e){let t=new Uint8Array(e.length);for(let n=0;n4===e.type&&e.isStatic;function s7(e){switch(e){case"Teleport":case"teleport":return sn;case"Suspense":case"suspense":return sr;case"KeepAlive":case"keep-alive":return si;case"BaseTransition":case"base-transition":return sl}}let oe=/^$|^\d|[^\$\w\xA0-\uFFFF]/,ot=/[A-Za-z_$\xA0-\uFFFF]/,on=/[\.\?\w$\xA0-\uFFFF]/,or=/\s+[.[]\s*|\s*[.[]\s+/g,oi=e=>4===e.type?e.content:e.loc.source,ol=e=>{let t=oi(e).trim().replace(or,e=>e.trim()),n=0,r=[],i=0,l=0,s=null;for(let e=0;e|^\s*(?:async\s+)?function(?:\s+[\w$]+)?\s*\(/;function oo(e,t,n=!1){for(let r=0;r4===e.key.type&&e.key.content===r)}return n}function ob(e,t){return`_${t}_${e.replace(/[^\w]/g,(t,n)=>"-"===t?"_":e.charCodeAt(n).toString())}`}let o_=/([\s\S]*?)\s+(?:in|of)\s+(\S[\s\S]*)/;function oS(e){for(let t=0;t0,isVoidTag:x,isPreTag:x,isIgnoreNewlineTag:x,isCustomElement:x,onError:s4,onWarn:s8,comments:!1,prefixIdentifiers:!1},oT=ok,ow=null,oN="",oA=null,oE=null,oI="",oR=-1,oO=-1,oM=0,oP=!1,oF=null,oL=[],o$=new class{constructor(e,t){this.stack=e,this.cbs=t,this.state=1,this.buffer="",this.sectionStart=0,this.index=0,this.entityStart=0,this.baseState=1,this.inRCDATA=!1,this.inXML=!1,this.inVPre=!1,this.newlines=[],this.mode=0,this.delimiterOpen=sZ,this.delimiterClose=sY,this.delimiterIndex=-1,this.currentSequence=void 0,this.sequenceIndex=0}get inSFCRoot(){return 2===this.mode&&0===this.stack.length}reset(){this.state=1,this.mode=0,this.buffer="",this.sectionStart=0,this.index=0,this.baseState=1,this.inRCDATA=!1,this.currentSequence=void 0,this.newlines.length=0,this.delimiterOpen=sZ,this.delimiterClose=sY}getPos(e){let t=1,n=e+1,r=this.newlines.length,i=-1;if(r>100){let t=-1,n=r;for(;t+1>>1;this.newlines[r]=0;t--)if(e>this.newlines[t]){i=t;break}return i>=0&&(t=i+2,n=e-this.newlines[i]),{column:n,line:t,offset:e}}peek(){return this.buffer.charCodeAt(this.index+1)}stateText(e){60===e?(this.index>this.sectionStart&&this.cbs.ontext(this.sectionStart,this.index),this.state=5,this.sectionStart=this.index):this.inVPre||e!==this.delimiterOpen[0]||(this.state=2,this.delimiterIndex=0,this.stateInterpolationOpen(e))}stateInterpolationOpen(e){if(e===this.delimiterOpen[this.delimiterIndex])if(this.delimiterIndex===this.delimiterOpen.length-1){let e=this.index+1-this.delimiterOpen.length;e>this.sectionStart&&this.cbs.ontext(this.sectionStart,e),this.state=3,this.sectionStart=e}else this.delimiterIndex++;else this.inRCDATA?(this.state=32,this.stateInRCDATA(e)):(this.state=1,this.stateText(e))}stateInterpolation(e){e===this.delimiterClose[0]&&(this.state=4,this.delimiterIndex=0,this.stateInterpolationClose(e))}stateInterpolationClose(e){e===this.delimiterClose[this.delimiterIndex]?this.delimiterIndex===this.delimiterClose.length-1?(this.cbs.oninterpolation(this.sectionStart,this.index+1),this.inRCDATA?this.state=32:this.state=1,this.sectionStart=this.index+1):this.delimiterIndex++:(this.state=3,this.stateInterpolation(e))}stateSpecialStartSequence(e){let t=this.sequenceIndex===this.currentSequence.length;if(t?s2(e):(32|e)===this.currentSequence[this.sequenceIndex]){if(!t)return void this.sequenceIndex++}else this.inRCDATA=!1;this.sequenceIndex=0,this.state=6,this.stateInTagName(e)}stateInRCDATA(e){if(this.sequenceIndex===this.currentSequence.length){if(62===e||s1(e)){let t=this.index-this.currentSequence.length;if(this.sectionStart=e||(28===this.state?this.currentSequence===s6.CdataEnd?this.cbs.oncdata(this.sectionStart,e):this.cbs.oncomment(this.sectionStart,e):6===this.state||11===this.state||18===this.state||17===this.state||12===this.state||13===this.state||14===this.state||15===this.state||16===this.state||20===this.state||19===this.state||21===this.state||9===this.state||this.cbs.ontext(this.sectionStart,e))}emitCodePoint(e,t){}}(oL,{onerr:oY,ontext(e,t){oU(oB(e,t),e,t)},ontextentity(e,t,n){oU(e,t,n)},oninterpolation(e,t){if(oP)return oU(oB(e,t),e,t);let n=e+o$.delimiterOpen.length,r=t-o$.delimiterClose.length;for(;s1(oN.charCodeAt(n));)n++;for(;s1(oN.charCodeAt(r-1));)r--;let i=oB(n,r);i.includes("&")&&(i=oT.decodeEntities(i,!1)),oG({type:5,content:oZ(i,!1,oX(n,r)),loc:oX(e,t)})},onopentagname(e,t){let n=oB(e,t);oA={type:1,tag:n,ns:oT.getNamespace(n,oL[0],oT.ns),tagType:0,props:[],children:[],loc:oX(e-1,t),codegenNode:void 0}},onopentagend(e){oj(e)},onclosetag(e,t){let n=oB(e,t);if(!oT.isVoidTag(n)){let r=!1;for(let e=0;e0&&oL[0].loc.start.offset;for(let n=0;n<=e;n++)oH(oL.shift(),t,n(7===e.type?e.rawName:e.name)===t)},onattribend(e,t){oA&&oE&&(oQ(oE.loc,t),0!==e&&(oI.includes("&")&&(oI=oT.decodeEntities(oI,!0)),6===oE.type?("class"===oE.name&&(oI=oJ(oI).trim()),oE.value={type:2,content:oI,loc:1===e?oX(oR,oO):oX(oR-1,oO+1)},o$.inSFCRoot&&"template"===oA.tag&&"lang"===oE.name&&oI&&"html"!==oI&&o$.enterRCDATA(s3("{let i=t.start.offset+n,l=i+e.length;return oZ(e,!1,oX(i,l),0,+!!r)},o={source:s(l.trim(),n.indexOf(l,i.length)),value:void 0,key:void 0,index:void 0,finalized:!1},a=i.trim().replace(oV,"").trim(),c=i.indexOf(a),u=a.match(oD);if(u){let e;a=a.replace(oD,"").trim();let t=u[1].trim();if(t&&(e=n.indexOf(t,c+a.length),o.key=s(t,e,!0)),u[2]){let r=u[2].trim();r&&(o.index=s(r,n.indexOf(r,o.key?e+t.length:c+a.length),!0))}}return a&&(o.value=s(a,c,!0)),o}(oE.exp)))),(7!==oE.type||"pre"!==oE.name)&&oA.props.push(oE)),oI="",oR=oO=-1},oncomment(e,t){oT.comments&&oG({type:3,content:oB(e,t),loc:oX(e-4,t+3)})},onend(){let e=oN.length;for(let t=0;t64&&n<91||s7(e)||oT.isBuiltInComponent&&oT.isBuiltInComponent(e)||oT.isNativeTag&&!oT.isNativeTag(e))return!0;for(let e=0;e=0;)n--;return n}let oW=new Set(["if","else","else-if","for","slot"]),oK=/\r\n/g;function oz(e){let t="preserve"!==oT.whitespace,n=!1;for(let r=0;r3!==e.type);return 1!==t.length||1!==t[0].type||op(t[0])?null:t[0]}function o1(e,t){let{constantCache:n}=t;switch(e.type){case 1:if(0!==e.tagType)return 0;let r=n.get(e);if(void 0!==r)return r;let i=e.codegenNode;if(13!==i.type||i.isBlock&&"svg"!==e.tag&&"foreignObject"!==e.tag&&"math"!==e.tag)return 0;if(void 0!==i.patchFlag)return n.set(e,0),0;{let r=3,c=o3(e,t);if(0===c)return n.set(e,0),0;c1)for(let i=0;i{l--};for(;lt===e:t=>e.test(t);return(e,r)=>{if(1===e.type){let{props:i}=e;if(3===e.tagType&&i.some(oh))return;let l=[];for(let s=0;s`${sB[e]}: _${sB[e]}`;function o7(e,t,{helper:n,push:r,newline:i,isTS:l}){let s=n("component"===t?sp:sm);for(let n=0;n3;t.push("["),n&&t.indent(),at(e,t,n),n&&t.deindent(),t.push("]")}function at(e,t,n=!1,r=!0){let{push:i,newline:l}=t;for(let s=0;se||"null")}([a,c,u,i,h]),t),l(")"),p&&l(")"),f&&(l(", "),an(f,t),l(")"))}(e,t);break;case 14:!function(e,t){let{push:n,helper:r,pure:i}=t,l=R(e.callee)?e.callee:r(e.callee);i&&n(o5),n(l+"(",-2,e),at(e.arguments,t),n(")")}(e,t);break;case 15:!function(e,t){let{push:n,indent:r,deindent:i,newline:l}=t,{properties:s}=e;if(!s.length)return n("{}",-2,e);let o=s.length>1;n(o?"{":"{ "),o&&r();for(let e=0;e "),(a||o)&&(n("{"),r()),s?(a&&n("return "),E(s)?ae(s,t):an(s,t)):o&&an(o,t),(a||o)&&(i(),n("}")),c&&n(")")}(e,t);break;case 19:!function(e,t){let{test:n,consequent:r,alternate:i,newline:l}=e,{push:s,indent:o,deindent:a,newline:c}=t;if(4===n.type){let e,r=(e=n.content,!!oe.test(e));r&&s("("),ar(n,t),r&&s(")")}else s("("),an(n,t),s(")");l&&o(),t.indentLevel++,l||s(" "),s("? "),an(r,t),t.indentLevel--,l&&c(),l||s(" "),s(": ");let u=19===i.type;!u&&t.indentLevel++,an(i,t),!u&&t.indentLevel--,l&&a(!0)}(e,t);break;case 20:!function(e,t){let{push:n,helper:r,indent:i,deindent:l,newline:s}=t,{needPauseTracking:o,needArraySpread:a}=e;a&&n("[...("),n(`_cache[${e.index}] || (`),o&&(i(),n(`${r(sO)}(-1`),e.inVOnce&&n(", true"),n("),"),s(),n("(")),n(`_cache[${e.index}] = `),an(e.value,t),o&&(n(`).cacheIndex = ${e.index},`),s(),n(`${r(sO)}(1),`),s(),n(`_cache[${e.index}]`),l()),n(")"),a&&n(")]")}(e,t);break;case 21:at(e.body,t,!0,!1)}}function ar(e,t){let{content:n,isStatic:r}=e;t.push(r?JSON.stringify(n):n,-3,e)}function ai(e,t){for(let n=0;n(function(e,t,n,r){if("else"!==t.name&&(!t.exp||!t.exp.content.trim())){let r=t.exp?t.exp.loc:e.loc;n.onError(s5(28,t.loc)),t.exp=sK("true",!1,r)}if("if"===t.name){var i;let l=as(e,t),s={type:9,loc:oX((i=e.loc).start.offset,i.end.offset),branches:[l]};if(n.replaceNode(s),r)return r(s,l,!0)}else{let i=n.parent.children,l=i.indexOf(e);for(;l-- >=-1;){let s=i[l];if(s&&oC(s)){n.removeNode(s);continue}if(s&&9===s.type){("else-if"===t.name||"else"===t.name)&&void 0===s.branches[s.branches.length-1].condition&&n.onError(s5(30,e.loc)),n.removeNode();let i=as(e,t);s.branches.push(i);let l=r&&r(s,i,!1);o4(i,n),l&&l(),n.currentNode=null}else n.onError(s5(30,e.loc));break}}})(e,t,n,(e,t,r)=>{let i=n.parent.children,l=i.indexOf(e),s=0;for(;l-- >=0;){let e=i[l];e&&9===e.type&&(s+=e.branches.length)}return()=>{r?e.codegenNode=ao(t,s,n):function(e){for(;;)if(19===e.type)if(19!==e.alternate.type)return e;else e=e.alternate;else 20===e.type&&(e=e.value)}(e.codegenNode).alternate=ao(t,s+e.branches.length-1,n)}}));function as(e,t){let n=3===e.tagType;return{type:10,loc:e.loc,condition:"else"===t.name?void 0:t.exp,children:n&&!oo(e,"for")?e.children:[e],userKey:oa(e,"key"),isTemplateIf:n}}function ao(e,t,n){return e.condition?sX(e.condition,aa(e,t,n),sJ(n.helper(sd),['""',"true"])):aa(e,t,n)}function aa(e,t,n){let{helper:r}=n,i=sW("key",sK(`${t}`,!1,sj,2)),{children:l}=e,s=l[0];if(1!==l.length||1!==s.type)if(1!==l.length||11!==s.type)return sU(n,r(st),sq([i]),l,64,void 0,void 0,!0,!1,!1,e.loc);else{let e=s.codegenNode;return ov(e,i,n),e}{let e=s.codegenNode,t=14===e.type&&e.callee===sD?e.arguments[1].returns:e;return 13===t.type&&sQ(t,n),ov(t,i,n),e}}let ac=o8("for",(e,t,n)=>{let{helper:r,removeHelper:i}=n;return function(e,t,n,r){if(!t.exp)return void n.onError(s5(31,t.loc));let i=t.forParseResult;if(!i)return void n.onError(s5(32,t.loc));au(i);let{scopes:l}=n,{source:s,value:o,key:a,index:c}=i,u={type:11,loc:t.loc,source:s,valueAlias:o,keyAlias:a,objectIndexAlias:c,parseResult:i,children:of(e)?e.children:[e]};n.replaceNode(u),l.vFor++;let d=r&&r(u);return()=>{l.vFor--,d&&d()}}(e,t,n,t=>{let l=sJ(r(sb),[t.source]),s=of(e),o=oo(e,"memo"),a=oa(e,"key",!1,!0);a&&a.type;let c=a&&(6===a.type?a.value?sK(a.value.content,!0):void 0:a.exp),u=c?sW("key",c):null,d=4===t.source.type&&t.source.constType>0,h=d?64:a?128:256;return t.codegenNode=sU(n,r(st),void 0,l,h,void 0,void 0,!0,!d,!1,e.loc),()=>{var a,h,f,p,g,m,y,b,_;let S,{children:x}=t,C=1!==x.length||1!==x[0].type,k=op(e)?e:s&&1===e.children.length&&op(e.children[0])?e.children[0]:null;if(k)S=k.codegenNode,s&&u&&ov(S,u,n);else if(C)S=sU(n,r(st),u?sq([u]):void 0,e.children,64,void 0,void 0,!0,void 0,!1);else{S=x[0].codegenNode,s&&u&&ov(S,u,n);let e=!d||!0===S.isBlockRequired;S.isBlock!==e&&(S.isBlock?(i(ss),i((h=n.inSSR,f=S.isComponent,h||f?so:sa))):i((p=n.inSSR,g=S.isComponent,p||g?sc:su))),(S.isBlock=e,S.isBlock)?(r(ss),r((m=n.inSSR,y=S.isComponent,m||y?so:sa))):(r((b=n.inSSR,_=S.isComponent,b||_?sc:su)),S.needsPatch&&(S.patchFlag=(null!=(a=S.patchFlag)?a:0)|512))}if(o){let e=sG(ad(t.parseResult,[sK("_cached")]));e.body={type:21,body:[sz(["const _memo = (",o.exp,")"]),sz(["if (_cached && _cached.el",...c?[" && _cached.key === ",c]:[],` && ${n.helperString(sV)}(_cached, _memo)) return _cached`]),sz(["const _item = ",S]),sK("_item.memo = _memo"),sK("return _item")],loc:sj},l.arguments.push(e,sK("_cache"),sK(String(n.cached.length))),n.cached.push(null)}else l.arguments.push(sG(ad(t.parseResult),S,!0))}})});function au(e,t){e.finalized||(e.finalized=!0)}function ad({value:e,key:t,index:n},r=[]){var i=[e,t,n,...r];let l=i.length;for(;l--&&!i[l];);return i.slice(0,l+1).map((e,t)=>e||sK("_".repeat(t+1),!1))}let ah=sK("undefined",!1),af=(e,t)=>{if(1===e.type&&(1===e.tagType||3===e.tagType)){let n=oo(e,"slot");if(n)return n.exp,t.scopes.vSlot++,()=>{t.scopes.vSlot--}}};function ap(e,t,n){let r=[sW("name",e),sW("fn",t)];return null!=n&&r.push(sW("key",sK(String(n),!0))),sq(r)}let ag=new WeakMap,am=(e,t)=>function(){let n,r,i,l,s;if(1!==(e=t.currentNode).type||0!==e.tagType&&1!==e.tagType)return;let{tag:o,props:a}=e,c=1===e.tagType,u=c?function(e,t,n=!1){let{tag:r}=e,i=ab(r),l=oa(e,"is",!1,!0);if(l)if(i){let e;if(6===l.type?e=l.value&&sK(l.value.content,!0):(e=l.exp)||(e=sK("is",!1,l.arg.loc)),e)return sJ(t.helper(sg),[e])}else 6===l.type&&l.value.content.startsWith("vue:")&&(r=l.value.content.slice(4));let s=s7(r)||t.isBuiltInComponent(r);return s?(n||t.helper(s),s):(t.helper(sp),t.components.add(r),ob(r,"component"))}(e,t):`"${o}"`,d=M(u)&&u.callee===sg,h=0,f=!1,p=!1,g=d||u===sn||u===sr||!c&&("svg"===o||"foreignObject"===o||"math"===o);if(a.length>0){let r=av(e,t,void 0,c,d);n=r.props,h=r.patchFlag,l=r.dynamicPropNames,f=r.needsPatch,p=r.isBlockRequired;let i=r.directives;s=i&&i.length?sH(i.map(e=>(function(e,t){let n=[],r=ag.get(e);r?n.push(t.helperString(r)):(t.helper(sm),t.directives.add(e.name),n.push(ob(e.name,"directive")));let{loc:i}=e;if(e.exp&&n.push(e.exp),e.arg&&(e.exp||n.push("void 0"),n.push(e.arg)),Object.keys(e.modifiers).length){e.arg||(e.exp||n.push("void 0"),n.push("void 0"));let t=sK("true",!1,i);n.push(sq(e.modifiers.map(e=>sW(e,t)),i))}return sH(n,e.loc)})(e,t))):void 0,r.shouldUseBlock&&(g=!0)}if(e.children.length>0)if(u===si&&(g=!0,h|=1024),c&&u!==sn&&u!==si){let{slots:n,hasDynamicSlots:i}=function(e,t,n=(e,t,n,r)=>sG(e,n,!1,!0,n.length?n[0].loc:r)){t.helper(sF);let{children:r,loc:i}=e,l=[],s=[],o=t.scopes.vSlot>0||t.scopes.vFor>0,a=oo(e,"slot",!0);if(a){let{arg:e,exp:t}=a;e&&!s9(e)&&(o=!0),l.push(sW(e||sK("default",!0),n(t,void 0,r,i)))}let c=!1,u=!1,d=[],h=new Set,f=0;for(let e=0;esW("default",n(e,void 0,t,i));c?d.length&&!d.every(ox)&&(u?t.onError(s5(39,d[0].loc)):l.push(e(void 0,d))):l.push(e(void 0,r))}let p=o?2:!function e(t){for(let n=0;n0,p=!1,g=!1,m=0,y=!1,b=!1,_=!1,S=!1,x=!1,k=!1,T=[],w=e=>{u.length&&(d.push(sq(ay(u),a)),u=[]),e&&d.push(e)},N=()=>{t.scopes.vFor>0&&u.push(sW(sK("ref_for",!0),sK("true")))},A=({key:e,value:n})=>{if(s9(e)){let l=e.content,s=C(l);s&&(!r||i)&&"onclick"!==l.toLowerCase()&&"onUpdate:modelValue"!==l&&!$(l)&&(S=!0),s&&$(l)&&(k=!0),"ref"===l&&(y=!0),s&&14===n.type&&(n=n.arguments[0]),20===n.type||(4===n.type||8===n.type)&&o1(n,t)>0||("class"===l?b=!0:"style"===l?_=!0:"ref"===l||"key"===l||T.includes(l)||T.push(l),r&&("class"===l||"style"===l)&&!T.includes(l)&&T.push(l))}else x=!0};for(let i=0;i"prop"===e.content)&&(m|=32);let C=t.directiveTransforms[n];if(C){let{props:n,needRuntime:r}=C(s,e,t);l||n.forEach(A),S&&i&&!s9(i)?w(sq(n,a)):u.push(...n),r&&(h.push(s),O(r)&&ag.set(s,r))}else!D(n)&&(h.push(s),f&&(p=!0,g=!0))}}d.length?(w(),s=d.length>1?sJ(t.helper(sC),d,a):d[0]):u.length&&(s=sq(ay(u),a)),x?m|=16:(b&&!r&&(m|=2),_&&!r&&(m|=4),T.length&&(m|=8),S&&(m|=32));let E=(0===m||32===m)&&(y||k||h.length>0);if(!p&&E&&(m|=512),!t.inSSR&&s)switch(s.type){case 15:let I=-1,R=-1,M=!1;for(let e=0;e{if(op(e)){let{children:n,loc:r}=e,{slotName:i,slotProps:l}=function(e,t){let n,r='"default"',i=[];for(let t=0;t0){let{props:r,directives:l}=av(e,t,i,!1,!1);n=r,l.length&&t.onError(s5(36,l[0].loc))}return{slotName:r,slotProps:n}}(e,t),s=[t.prefixIdentifiers?"_ctx.$slots":"$slots",i,"{}","undefined","true"],o=2;l&&(s[2]=l,o=3),n.length&&(s[3]=sG([],n,!1,!1,r),o=4),t.scopeId&&!t.slotted&&(o=5),s.splice(o),e.codegenNode=sJ(t.helper(s_),s,r)}},aS=(e,t,n,r)=>{let i,{loc:l,modifiers:s,arg:o}=e;if(!e.exp&&!s.length,4===o.type)if(o.isStatic){let e=o.content;e.startsWith("vue:")&&(e=`vnode-${e.slice(4)}`),i=sK(0!==t.tagType||e.startsWith("vnode")||!/[A-Z]/.test(e)?W(j(e)):`on:${e}`,!0,o.loc)}else i=sz([`${n.helperString(sR)}(`,o,")"]);else(i=o).children.unshift(`${n.helperString(sR)}(`),i.children.push(")");let a=e.exp;a&&!a.content.trim()&&(a=void 0);let c=n.cacheHandlers&&!a&&!n.inVOnce;if(a){let e,t=ol(a),n=!(t||(e=a,os.test(oi(e)))),r=a.content.includes(";");(n||c&&t)&&(a=sz([`${n?"$event":"(...args)"} => ${r?"{":"("}`,a,r?"}":")"]))}let u={props:[sW(i,a||sK("() => {}",!1,l))]};return r&&(u=r(u)),c&&(u.props[0].value=n.cache(u.props[0].value)),u.props.forEach(e=>e.key.isHandlerKey=!0),u},ax=(e,t,n)=>{let{modifiers:r}=e,i=e.arg,{exp:l}=e;return l&&4===l.type&&!l.content.trim()&&(l=void 0),4!==i.type?(i.children.unshift("("),i.children.push(') || ""')):i.isStatic||(i.content=i.content?`${i.content} || ""`:'""'),r.some(e=>"camel"===e.content)&&(4===i.type?i.isStatic?i.content=j(i.content):i.content=`${n.helperString(sE)}(${i.content})`:(i.children.unshift(`${n.helperString(sE)}(`),i.children.push(")"))),!n.inSSR&&(r.some(e=>"prop"===e.content)&&aC(i,"."),r.some(e=>"attr"===e.content)&&aC(i,"^")),{props:[sW(i,l)]}},aC=(e,t)=>{4===e.type?e.isStatic?e.content=t+e.content:e.content=`\`${t}\${${e.content}}\``:(e.children.unshift(`'${t}' + (`),e.children.push(")"))},ak=(e,t)=>{if(0===e.type||1===e.type||11===e.type||10===e.type)return()=>{let n,r=e.children,i=!1;for(let e=0;e7===e.type&&!t.directiveTransforms[e.name]))))for(let e=0;e{if(1===e.type&&oo(e,"once",!0)&&!aT.has(e)&&!t.inVOnce&&!t.inSSR)return aT.add(e),t.inVOnce=!0,t.helper(sO),()=>{t.inVOnce=!1;let e=t.currentNode;e.codegenNode&&(e.codegenNode=t.cache(e.codegenNode,!0,!0))}},aN=(e,t,n)=>{let r,{exp:i,arg:l}=e;if(!i)return n.onError(s5(41,e.loc)),aA();let s=i.loc.source.trim(),o=4===i.type?i.content:s,a=n.bindingMetadata[s];if("props"===a||"props-aliased"===a||"literal-const"===a||"setup-const"===a)return i.loc,aA();if(!o.trim()||!ol(i))return n.onError(s5(42,i.loc)),aA();let c=l||sK("modelValue",!0),u=l?s9(l)?`onUpdate:${j(l.content)}`:sz(['"onUpdate:" + ',l]):"onUpdate:modelValue",d=n.isTS?"($event: any)":"$event";r=sz([`${d} => ((`,i,") = $event)"]);let h=[sW(c,e.exp),sW(u,r)];if(e.modifiers.length&&1===t.tagType){let t=e.modifiers.map(e=>e.content).map(e=>(oe.test(e)?JSON.stringify(e):e)+": true").join(", "),n=l?s9(l)?`${l.content}Modifiers`:sz([l,' + "Modifiers"']):"modelModifiers";h.push(sW(n,sK(`{ ${t} }`,!1,e.loc,2)))}return aA(h)};function aA(e=[]){return{props:e}}let aE=new WeakSet,aI=(e,t)=>{if(1===e.type){let n=oo(e,"memo");if(!(!n||aE.has(e))&&!t.inSSR)return aE.add(e),()=>{let r=e.codegenNode||t.currentNode.codegenNode;r&&13===r.type&&(1!==e.tagType&&sQ(r,t),e.codegenNode=sJ(t.helper(sD),[n.exp,sG(void 0,r),"_cache",String(t.cached.length)]),t.cached.push(null))}}},aR=(e,t)=>{if(1===e.type){for(let n of e.props)if(7===n.type&&"bind"===n.name&&(!n.exp||4===n.exp.type&&!n.exp.content.trim())&&n.arg){let e=n.arg;if(4===e.type&&e.isStatic){let t=j(e.content);(ot.test(t[0])||"-"===t[0])&&(n.exp=sK(t,!1,e.loc))}else t.onError(s5(53,e.loc)),n.exp=sK("",!0,e.loc)}}},aO=Symbol(""),aM=Symbol(""),aP=Symbol(""),aF=Symbol(""),aL=Symbol(""),a$=Symbol(""),aD=Symbol(""),aV=Symbol(""),aB=Symbol(""),aj=Symbol("");Object.getOwnPropertySymbols(r={[aO]:"vModelRadio",[aM]:"vModelCheckbox",[aP]:"vModelText",[aF]:"vModelSelect",[aL]:"vModelDynamic",[a$]:"withModifiers",[aD]:"withKeys",[aV]:"vShow",[aB]:"Transition",[aj]:"TransitionGroup"}).forEach(e=>{sB[e]=r[e]});let aU={parseMode:"html",isVoidTag:ea,isNativeTag:e=>el(e)||es(e)||eo(e),isPreTag:e=>"pre"===e,isIgnoreNewlineTag:e=>"pre"===e||"textarea"===e,decodeEntities:function(e,t=!1){return(p||(p=document.createElement("div")),t)?(p.innerHTML=`
`,p.children[0].getAttribute("foo")):(p.innerHTML=e,p.textContent)},isBuiltInComponent:e=>"Transition"===e||"transition"===e?aB:"TransitionGroup"===e||"transition-group"===e?aj:void 0,getNamespace(e,t,n){let r=t?t.ns:n;if(t&&2===r)if("annotation-xml"===t.tag){if("svg"===e)return 1;t.props.some(e=>6===e.type&&"encoding"===e.name&&null!=e.value&&("text/html"===e.value.content||"application/xhtml+xml"===e.value.content))&&(r=0)}else/^m(?:[ions]|text)$/.test(t.tag)&&"mglyph"!==e&&"malignmark"!==e&&(r=0);else t&&1===r&&("foreignObject"===t.tag||"desc"===t.tag||"title"===t.tag)&&(r=0);if(0===r){if("svg"===e)return 1;if("math"===e)return 2}return r}},aH=y("passive,once,capture"),aq=y("stop,prevent,self,ctrl,shift,alt,meta,exact,middle"),aW=y("left,right"),aK=y("onkeyup,onkeydown,onkeypress"),az=(e,t)=>s9(e)&&"onclick"===e.content.toLowerCase()?sK(t,!0):4!==e.type?sz(["(",e,`) === "onClick" ? "${t}" : (`,e,")"]):e,aJ=(e,t)=>{1===e.type&&0===e.tagType&&("script"===e.tag||"style"===e.tag)&&t.removeNode()},aG=[e=>{1===e.type&&e.props.forEach((t,n)=>{let r,i;6===t.type&&"style"===t.name&&t.value&&(e.props[n]={type:7,name:"bind",arg:sK("style",!0,t.loc),exp:(r=t.value.content,i=t.loc,sK(JSON.stringify(er(r)),!1,i,3)),modifiers:[],loc:t.loc})})}],aX={cloak:()=>({props:[]}),html:(e,t,n)=>{let{exp:r,loc:i}=e;return r||n.onError(s5(54,i)),t.children.length&&(n.onError(s5(55,i)),t.children.length=0),{props:[sW(sK("innerHTML",!0,i),r||sK("",!0))]}},text:(e,t,n)=>{let{exp:r,loc:i}=e;return r||n.onError(s5(56,i)),t.children.length&&(n.onError(s5(57,i)),t.children.length=0),{props:[sW(sK("textContent",!0),r?o1(r,n)>0?r:sJ(n.helperString(sx),[r],i):sK("",!0))]}},model:(e,t,n)=>{let r=aN(e,t,n);if(!r.props.length||1===t.tagType)return r;e.arg&&n.onError(s5(59,e.arg.loc));let{tag:i}=t,l=n.isCustomElement(i);if("input"===i||"textarea"===i||"select"===i||l){let s=aP,o=!1;if("input"===i||l){let r=oa(t,"type");if(r){if(7===r.type)s=aL;else if(r.value)switch(r.value.content){case"radio":s=aO;break;case"checkbox":s=aM;break;case"file":o=!0,n.onError(s5(60,e.loc))}}else t.props.some(e=>7===e.type&&"bind"===e.name&&(!e.arg||4!==e.arg.type||!e.arg.isStatic))&&(s=aL)}else"select"===i&&(s=aF);o||(r.needRuntime=n.helper(s))}else n.onError(s5(58,e.loc));return r.props=r.props.filter(e=>4!==e.key.type||"modelValue"!==e.key.content),r},on:(e,t,n)=>aS(e,t,n,t=>{let{modifiers:r}=e;if(!r.length)return t;let{key:i,value:l}=t.props[0],{keyModifiers:s,nonKeyModifiers:o,eventOptionModifiers:a}=((e,t,n)=>{let r=[],i=[],l=[];for(let n=0;n{let{exp:r,loc:i}=e;return r||n.onError(s5(62,i)),{props:[],needRuntime:n.helper(aV)}}},aQ=Object.create(null);function aZ(e,t){if(!R(e))if(!e.nodeType)return S;else e=e.innerHTML;let n=e+JSON.stringify(t,(e,t)=>"function"==typeof t?t.toString():t),r=aQ[n];if(r)return r;if("#"===e[0]){let t=document.querySelector(e);e=t?t.innerHTML:""}let i=T({hoistStatic:!0,onError:void 0,onWarn:S},t);!i.isCustomElement&&"u">typeof customElements&&(i.isCustomElement=e=>!!customElements.get(e));let{code:l}=function(e,t={}){return function(e,t={}){var n;let r,i=t.onError||s4,l="module"===t.mode;!0===t.prefixIdentifiers?i(s5(48)):l&&i(s5(49)),t.cacheHandlers&&i(s5(50)),t.scopeId&&!l&&i(s5(51));let s=T({},t,{prefixIdentifiers:!1}),o=R(e)?function(e,t){if(o$.reset(),oA=null,oE=null,oI="",oR=-1,oO=-1,oL.length=0,oN=e,oT=T({},ok),t){let e;for(e in t)null!=t[e]&&(oT[e]=t[e])}o$.mode="html"===oT.parseMode?1:2*("sfc"===oT.parseMode),o$.inXML=1===oT.ns||2===oT.ns;let n=t&&t.delimiters;n&&(o$.delimiterOpen=s3(n[0]),o$.delimiterClose=s3(n[1]));let r=ow=function(e,t=""){return{type:0,source:t,children:e,helpers:new Set,components:[],directives:[],hoists:[],imports:[],cached:[],temps:0,codegenNode:void 0,loc:sj}}([],e);return o$.parse(oN),r.loc=oX(0,e.length),r.children=oz(r.children),ow=null,r}(e,s):e,[a,c]=[[aR,aw,al,aI,ac,a_,am,af,ak],{on:aS,bind:ax,model:aN}];return r=function(e,{filename:t="",prefixIdentifiers:n=!1,hoistStatic:r=!1,hmr:i=!1,cacheHandlers:l=!1,nodeTransforms:s=[],directiveTransforms:o={},transformHoist:a=null,isBuiltInComponent:c=S,isCustomElement:u=S,expressionPlugins:d=[],scopeId:h=null,slotted:f=!0,ssr:p=!1,inSSR:g=!1,ssrCssVars:m="",bindingMetadata:y=b,inline:_=!1,isTS:x=!1,onError:C=s4,onWarn:k=s8,compatConfig:T}){let w=t.replace(/\?.*$/,"").match(/([^/\\]+)\.\w+$/),N={filename:t,selfName:w&&q(j(w[1])),prefixIdentifiers:n,hoistStatic:r,hmr:i,cacheHandlers:l,nodeTransforms:s,directiveTransforms:o,transformHoist:a,isBuiltInComponent:c,isCustomElement:u,expressionPlugins:d,scopeId:h,slotted:f,ssr:p,inSSR:g,ssrCssVars:m,bindingMetadata:y,inline:_,isTS:x,onError:C,onWarn:k,compatConfig:T,root:e,helpers:new Map,components:new Set,directives:new Set,hoists:[],imports:[],cached:[],constantCache:new WeakMap,vForMemoKeyedNodes:new WeakSet,temps:0,identifiers:Object.create(null),scopes:{vFor:0,vSlot:0,vPre:0,vOnce:0},parent:null,grandParent:null,currentNode:e,childIndex:0,inVOnce:!1,helper(e){let t=N.helpers.get(e)||0;return N.helpers.set(e,t+1),e},removeHelper(e){let t=N.helpers.get(e);if(t){let n=t-1;n?N.helpers.set(e,n):N.helpers.delete(e)}},helperString:e=>`_${sB[N.helper(e)]}`,replaceNode(e){N.parent.children[N.childIndex]=N.currentNode=e},removeNode(e){let t=N.parent.children,n=e?t.indexOf(e):N.currentNode?N.childIndex:-1;e&&e!==N.currentNode?N.childIndex>n&&(N.childIndex--,N.onNodeRemoved()):(N.currentNode=null,N.onNodeRemoved()),N.parent.children.splice(n,1)},onNodeRemoved:S,addIdentifiers(e){},removeIdentifiers(e){},hoist(e){R(e)&&(e=sK(e)),N.hoists.push(e);let t=sK(`_hoisted_${N.hoists.length}`,!1,e.loc,2);return t.hoisted=e,t},cache(e,t=!1,n=!1){let r=function(e,t,n=!1,r=!1){return{type:20,index:e,value:t,needPauseTracking:n,inVOnce:r,needArraySpread:!1,loc:sj}}(N.cached.length,e,t,n);return N.cached.push(r),r}};return N}(o,n=T({},s,{nodeTransforms:[...a,...t.nodeTransforms||[]],directiveTransforms:T({},c,t.directiveTransforms||{})})),o4(o,r),n.hoistStatic&&function e(t,n,r,i=!1,l=!1){let{children:s}=t,o=[];for(let n=0;n0){if(e>=2){a.codegenNode.patchFlag=-1,o.push(a);continue}}else{let e=a.codegenNode;if(13===e.type){let t=e.patchFlag;if((void 0===t||512===t||1===t)&&o3(a,r)>=2){let t=o6(a);t&&(e.props=r.hoist(t))}e.dynamicProps&&(e.dynamicProps=r.hoist(e.dynamicProps))}}}else if(12===a.type&&(i?0:o1(a,r))>=2){14===a.codegenNode.type&&a.codegenNode.arguments.length>0&&a.codegenNode.arguments.push("-1"),o.push(a);continue}if(1===a.type){let n=1===a.tagType;n&&r.scopes.vSlot++,e(a,t,r,!1,l),n&&r.scopes.vSlot--}else if(11===a.type)e(a,t,r,1===a.children.length,!0);else if(9===a.type)for(let n=0;ne.key===t||e.key.content===t);return n&&n.value}}o.length&&r.transformHoist&&r.transformHoist(s,r,t)}(o,void 0,r,!!o0(o)),n.ssr||function(e,t){let{helper:n}=t,{children:r}=e;if(1===r.length){let n=o0(e);if(n&&n.codegenNode){let r=n.codegenNode;13===r.type&&sQ(r,t),e.codegenNode=r}else e.codegenNode=r[0]}else r.length>1&&(e.codegenNode=sU(t,n(st),void 0,e.children,64,void 0,void 0,!0,void 0,!1))}(o,r),o.helpers=new Set([...r.helpers.keys()]),o.components=[...r.components],o.directives=[...r.directives],o.imports=r.imports,o.hoists=r.hoists,o.temps=r.temps,o.cached=r.cached,o.transformed=!0,function(e,t={}){let n=function(e,{mode:t="function",prefixIdentifiers:n="module"===t,sourceMap:r=!1,filename:i="template.vue.html",scopeId:l=null,optimizeImports:s=!1,runtimeGlobalName:o="Vue",runtimeModuleName:a="vue",ssrRuntimeModuleName:c="vue/server-renderer",ssr:u=!1,isTS:d=!1,inSSR:h=!1}){let f={mode:t,prefixIdentifiers:n,sourceMap:r,filename:i,scopeId:l,optimizeImports:s,runtimeGlobalName:o,runtimeModuleName:a,ssrRuntimeModuleName:c,ssr:u,isTS:d,inSSR:h,source:e.source,code:"",column:1,line:1,offset:0,indentLevel:0,pure:!1,map:void 0,helper:e=>`_${sB[e]}`,push(e,t=-2,n){f.code+=e},indent(){p(++f.indentLevel)},deindent(e=!1){e?--f.indentLevel:p(--f.indentLevel)},newline(){p(f.indentLevel)}};function p(e){f.push(` +`+" ".repeat(e),0)}return f}(e,t);t.onContextCreated&&t.onContextCreated(n);let{mode:r,push:i,prefixIdentifiers:l,indent:s,deindent:o,newline:a,ssr:c}=n,u=Array.from(e.helpers),d=u.length>0,h=!l&&"module"!==r;!function(e,t){let{push:n,newline:r,runtimeGlobalName:i}=t,l=Array.from(e.helpers);if(l.length>0&&(n(`const _Vue = ${i} +`,-1),e.hoists.length)){let e=[sc,su,sd,sh,sf].filter(e=>l.includes(e)).map(o9).join(", ");n(`const { ${e} } = _Vue +`,-1)}(function(e,t){if(!e.length)return;t.pure=!0;let{push:n,newline:r}=t;r();for(let i=0;i0)&&a()),e.directives.length&&(o7(e.directives,"directive",n),e.temps>0&&a()),e.temps>0){i("let ");for(let t=0;t0?", ":""}_temp${t}`)}return(e.components.length||e.directives.length||e.temps)&&(i(` +`,0),a()),c||i("return "),e.codegenNode?an(e.codegenNode,n):i("null"),h&&(o(),i("}")),o(),i("}"),{ast:e,code:n.code,preamble:"",map:n.map?n.map.toJSON():void 0}}(o,s)}(e,T({},aU,t,{nodeTransforms:[aJ,...aG,...t.nodeTransforms||[]],directiveTransforms:T({},aX,t.directiveTransforms||{}),transformHoist:null}))}(e,i),s=Function(l)();return s._rc=!0,aQ[n]=s}return iF(aZ),e.BaseTransition=ny,e.BaseTransitionPropsValidators=ng,e.Comment=r7,e.DeprecationTypes=null,e.EffectScope=em,e.ErrorCodes={SETUP_FUNCTION:0,0:"SETUP_FUNCTION",RENDER_FUNCTION:1,1:"RENDER_FUNCTION",NATIVE_EVENT_HANDLER:5,5:"NATIVE_EVENT_HANDLER",COMPONENT_EVENT_HANDLER:6,6:"COMPONENT_EVENT_HANDLER",VNODE_HOOK:7,7:"VNODE_HOOK",DIRECTIVE_HOOK:8,8:"DIRECTIVE_HOOK",TRANSITION_HOOK:9,9:"TRANSITION_HOOK",APP_ERROR_HANDLER:10,10:"APP_ERROR_HANDLER",APP_WARN_HANDLER:11,11:"APP_WARN_HANDLER",FUNCTION_REF:12,12:"FUNCTION_REF",ASYNC_COMPONENT_LOADER:13,13:"ASYNC_COMPONENT_LOADER",SCHEDULER:14,14:"SCHEDULER",COMPONENT_UPDATE:15,15:"COMPONENT_UPDATE",APP_UNMOUNT_CLEANUP:16,16:"APP_UNMOUNT_CLEANUP"},e.ErrorTypeStrings=null,e.Fragment=r5,e.KeepAlive={name:"KeepAlive",__isKeepAlive:!0,props:{include:[String,RegExp,Array],exclude:[String,RegExp,Array],max:[String,Number]},setup(e,{slots:t}){let n=iE(),r=n.ctx,i=new Map,l=new Set,s=null,o=n.suspense,{renderer:{p:a,m:c,um:u,o:{createElement:d}}}=r,h=d("div");function f(e){nX(e),u(e,n,o,!0)}function p(e){i.forEach((t,n)=>{let r=iB(nH(t)?t.type.__asyncResolved||{}:t.type);r&&!e(r)&&g(n)})}function g(e){let t=i.get(e);!t||s&&id(t,s)?s&&nX(s):f(t),i.delete(e),l.delete(e)}r.activate=(e,t,n,r,i)=>{let l=e.component;c(e,t,n,0,o),a(l.vnode,e,t,n,l,o,r,e.slotScopeIds,i),rK(()=>{l.isDeactivated=!1,l.a&&z(l.a);let t=e.props&&e.props.onVnodeMounted;t&&iT(t,l.parent,e)},o)},r.deactivate=e=>{let t=e.component;rY(t.m),rY(t.a),c(e,h,null,1,o),rK(()=>{t.da&&z(t.da);let n=e.props&&e.props.onVnodeUnmounted;n&&iT(n,t.parent,e),t.isDeactivated=!0},o)},t7(()=>[e.include,e.exclude],([e,t])=>{e&&p(t=>nK(e,t)),t&&p(e=>!nK(t,e))},{flush:"post",deep:!0});let m=null,y=()=>{null!=m&&(r0(n.subTree.type)?rK(()=>{i.set(m,nQ(n.subTree))},n.subTree.suspense):i.set(m,nQ(n.subTree)))};return n1(y),n3(y),n6(()=>{i.forEach(e=>{let{subTree:t,suspense:r}=n,i=nQ(t);if(e.type===i.type&&e.key===i.key){nX(i);let e=i.component.da;e&&rK(e,r);return}f(e)})}),()=>{if(m=null,!t.default)return s=null;let n=t.default(),r=n[0];if(n.length>1)return s=null,n;if(!iu(r)||!(4&r.shapeFlag)&&!(128&r.shapeFlag))return s=null,r;let o=nQ(r);if(o.type===r7)return s=null,o;let a=o.type,c=iB(nH(o)?o.type.__asyncResolved||{}:a),{include:u,exclude:d,max:h}=e;if(u&&(!c||!nK(u,c))||d&&c&&nK(d,c))return o.shapeFlag&=-257,s=o,r;let f=null==o.key?a:o.key,p=i.get(f);return o.el&&(o=iy(o),128&r.shapeFlag&&(r.ssContent=o)),m=f,p?(o.el=p.el,o.component=p.component,o.transition&&nC(o,o.transition),o.shapeFlag|=512,l.delete(f),l.add(f)):(l.add(f),h&&l.size>parseInt(h,10)&&g(l.values().next().value)),o.shapeFlag|=256,s=o,r0(r.type)?r:o}}},e.ReactiveEffect=ey,e.Static=ie,e.Suspense={name:"Suspense",__isSuspense:!0,process(e,t,n,r,i,l,s,o,a,c){if(null==e)!function(e,t,n,r,i,l,s,o,a){let{p:c,o:{createElement:u}}=a,d=u("div"),h=e.suspense=r3(e,i,r,t,d,n,l,s,o,a);c(null,h.pendingBranch=e.ssContent,d,null,r,h,l,s),h.deps>0?(r2(e,"onPending"),r2(e,"onFallback"),c(null,e.ssFallback,t,n,r,null,l,s),r8(h,e.ssFallback)):h.resolve(!1,!0)}(t,n,r,i,l,s,o,a,c);else{if(l&&l.deps>0&&!e.suspense.isInFallback){t.suspense=e.suspense,t.suspense.vnode=t,t.el=e.el;return}!function(e,t,n,r,i,l,s,o,{p:a,um:c,o:{createElement:u}}){let d=t.suspense=e.suspense;d.vnode=t,t.el=e.el;let h=t.ssContent,f=t.ssFallback,{activeBranch:p,pendingBranch:g,isInFallback:m,isHydrating:y}=d;if(g)d.pendingBranch=h,id(g,h)?(a(g,h,d.hiddenContainer,null,i,d,l,s,o),d.deps<=0?d.resolve():m&&!y&&(a(p,f,n,r,i,null,l,s,o),r8(d,f))):(d.pendingId=r1++,y?(d.isHydrating=!1,d.activeBranch=g):c(g,i,d),d.deps=0,d.effects.length=0,d.hiddenContainer=u("div"),m?(a(null,h,d.hiddenContainer,null,i,d,l,s,o),d.deps<=0?d.resolve():(a(p,f,n,r,i,null,l,s,o),r8(d,f))):p&&id(p,h)?(a(p,h,n,r,i,d,l,s,o),d.resolve(!0)):(a(null,h,d.hiddenContainer,null,i,d,l,s,o),d.deps<=0&&d.resolve()));else if(p&&id(p,h))a(p,h,n,r,i,d,l,s,o),r8(d,h);else if(r2(t,"onPending"),d.pendingBranch=h,512&h.shapeFlag?d.pendingId=h.component.suspenseId:d.pendingId=r1++,a(null,h,d.hiddenContainer,null,i,d,l,s,o),d.deps<=0)d.resolve();else{let{timeout:e,pendingId:t}=d;e>0?setTimeout(()=>{d.pendingId===t&&d.fallback(f)},e):0===e&&d.fallback(f)}}(e,t,n,r,i,s,o,a,c)}},hydrate:function(e,t,n,r,i,l,s,o,a){let c=t.suspense=r3(t,r,n,e.parentNode,document.createElement("div"),null,i,l,s,o,!0),u=a(e,c.pendingBranch=t.ssContent,n,c,l,s);return 0===c.deps&&c.resolve(!1,!0),u},normalize:function(e){let{shapeFlag:t,children:n}=e,r=32&t;e.ssContent=r6(r?n.default:n),e.ssFallback=r?r6(n.fallback):im(r7)}},e.Teleport={name:"Teleport",__isTeleport:!0,process(e,t,n,r,i,l,s,o,a,c){let{mc:u,pc:d,pbc:h,o:{insert:f,querySelector:p,createText:g,parentNode:m}}=c,y=ni(t.props),{dynamicChildren:b}=t,_=(e,t,n)=>{16&e.shapeFlag&&u(e.children,t,n,i,l,s,o,a)},S=(e=t)=>{let n=ni(e.props),r=e.target=no(e.props,p),l=nu(r,e,g,f);r&&("svg"!==s&&nl(r)?s="svg":"mathml"!==s&&ns(r)&&(s="mathml"),i&&i.isCE&&(i.ce._teleportTargets||(i.ce._teleportTargets=new Set)).add(r),n||(_(e,r,l),nc(e,!1)))},x=e=>{let t=()=>{if(nn.get(e)===t){if(nn.delete(e),ni(e.props)){let t=m(e.el)||n;_(e,t,e.anchor),nc(e,!0)}S(e)}};nn.set(e,t),rK(t,l)};if(null==e){let e,i=t.el=g(""),s=t.anchor=g("");if(f(i,n,r),f(s,n,r),(e=t.props)&&(e.defer||""===e.defer)||l&&l.pendingBranch)return void x(t);y&&(_(t,n,s),nc(t,!0)),S()}else{t.el=e.el;let r=t.anchor=e.anchor,u=nn.get(e);if(u){u.flags|=8,nn.delete(e),x(t);return}t.targetStart=e.targetStart;let f=t.target=e.target,g=t.targetAnchor=e.targetAnchor,m=ni(e.props),_=m?n:f,S=m?r:g;if("svg"===s||nl(f)?s="svg":("mathml"===s||ns(f))&&(s="mathml"),b?(h(e.dynamicChildren,b,_,i,l,s,o),rZ(e,t,!0)):a||d(e,t,_,S,i,l,s,o,!1),y)m?t.props&&e.props&&t.props.to!==e.props.to&&(t.props.to=e.props.to):na(t,n,r,c,1);else if((t.props&&t.props.to)!==(e.props&&e.props.to)){let e=no(t.props,p);e&&(t.target=e,na(t,e,null,c,0))}else m&&na(t,f,g,c,1);nc(t,y)}},remove(e,t,n,{um:r,o:{remove:i}},l){let{shapeFlag:s,children:o,anchor:a,targetStart:c,targetAnchor:u,target:d,props:h}=e,f=ni(h),p=l||!f,g=nn.get(e);if(g&&(g.flags|=8,nn.delete(e)),d&&(i(c),i(u)),l&&i(a),!g&&(f||d)&&16&s)for(let e=0;ee[r]});return n},e.createRenderer=function(e){return rJ(e)},e.createSSRApp=l9,e.createSlots=function(e,t){for(let n=0;n{let t=r.fn(...e);return t&&(t.key=r.key),t}:r.fn)}return e},e.createStaticVNode=function(e,t){let n=im(ie,null,e);return n.staticCount=t,n},e.createTextVNode=ib,e.createVNode=im,e.customRef=tE,e.defineAsyncComponent=function(e){let t;I(e)&&(e={loader:e});let{loader:n,loadingComponent:r,errorComponent:i,delay:l=200,hydrate:s,timeout:o,suspensible:a=!0,onError:c}=e,u=null,d=0,h=()=>{let e;return u||(e=u=n().catch(e=>{if(e=e instanceof Error?e:Error(String(e)),c)return new Promise((t,n)=>{c(e,()=>t((d++,u=null,h())),()=>n(e),d+1)});throw e}).then(n=>e!==u&&u?u:(n&&(n.__esModule||"Module"===n[Symbol.toStringTag])&&(n=n.default),t=n,n)))};return nT({name:"AsyncComponentWrapper",__asyncLoader:h,__asyncHydrate(e,n,r){let i=e.isConnected,l=!1;(n.bu||(n.bu=[])).push(()=>l=!0);let o=()=>{l||e.parentNode&&(!i||e.isConnected)&&r()},a=s?()=>{let t=s(o,t=>(function(e,t){if(nP(e)&&"["===e.data){let n=1,r=e.nextSibling;for(;r;){if(1===r.nodeType){if(!1===t(r))break}else if(nP(r))if("]"===r.data){if(0==--n)break}else"["===r.data&&n++;r=r.nextSibling}}else t(e)})(e,t));t&&(n.bum||(n.bum=[])).push(t)}:o;t?a():h().then(()=>!n.isUnmounted&&a())},get __asyncResolved(){return t},setup(){let e,n,s=iA;if(nw(s),t)return()=>nq(t,s);let c=e=>{u=null,tV(e,s,13,!i)};if(a&&s.suspense)return h().then(e=>()=>nq(e,s)).catch(e=>(c(e),()=>i?im(i,{error:e}):null));let d=tS(!1),f=tS(),p=tS(!!l);return n4(()=>{null!=e&&clearTimeout(e),null!=n&&clearTimeout(n)}),l&&(n=setTimeout(()=>{s.isUnmounted||(p.value=!1)},l)),null!=o&&(e=setTimeout(()=>{if(!s.isUnmounted&&!d.value&&!f.value){let e=Error(`Async component timed out after ${o}ms.`);c(e),f.value=e}},o)),h().then(()=>{!s.isUnmounted&&(d.value=!0,s.parent&&nW(s.parent.vnode)&&s.parent.update())}).catch(e=>{if(s.isUnmounted){u=null;return}c(e),f.value=e}),()=>d.value&&t?nq(t,s):f.value&&i?im(i,{error:f.value}):r&&!p.value?nq(r,s):void 0}})},e.defineComponent=nT,e.defineCustomElement=lw,e.defineEmits=function(){return null},e.defineExpose=function(e){},e.defineModel=function(){},e.defineOptions=function(e){},e.defineProps=function(){return null},e.defineSSRCustomElement=(e,t)=>lw(e,t,l9),e.defineSlots=function(){return null},e.devtools=void 0,e.effect=function(e,t){e.effect instanceof ey&&(e=e.effect.fn);let n=new ey(e);t&&T(n,t);try{n.run()}catch(e){throw n.stop(),e}let r=n.run.bind(n);return r.effect=n,r},e.effectScope=function(e){return new em(e)},e.getCurrentInstance=iE,e.getCurrentScope=function(){return l},e.getCurrentWatcher=function(){return g},e.getTransitionRawChildren=nk,e.guardReactiveProps=iv,e.h=iU,e.handleError=tV,e.hasInjectionContext=function(){return!!(iE()||rC)},e.hydrate=(...e)=>{l4().hydrate(...e)},e.hydrateOnIdle=(e=1e4)=>t=>{let n=nj(t,{timeout:e});return()=>nU(n)},e.hydrateOnInteraction=(e=[])=>(t,n)=>{R(e)&&(e=[e]);let r=!1,i=e=>{r||(r=!0,l(),t(),e.target.dispatchEvent(new e.constructor(e.type,e)))},l=()=>{n(t=>{for(let n of e)t.removeEventListener(n,i)})};return n(t=>{for(let n of e)t.addEventListener(n,i,{once:!0})}),l},e.hydrateOnMediaQuery=e=>t=>{if(e){let n=matchMedia(e);if(!n.matches)return n.addEventListener("change",t,{once:!0}),()=>n.removeEventListener("change",t);t()}},e.hydrateOnVisible=e=>(t,n)=>{let r=new IntersectionObserver(e=>{for(let n of e)if(n.isIntersecting){r.disconnect(),t();break}},e);return n(e=>{if(e instanceof Element){if(function(e){let{top:t,left:n,bottom:r,right:i}=e.getBoundingClientRect(),{innerHeight:l,innerWidth:s}=window;return(t>0&&t0&&r0&&n0&&ir.disconnect()},e.initCustomFormatter=function(){},e.initDirectivesForSSR=S,e.inject=t8,e.isMemoSame=iH,e.isProxy=tg,e.isReactive=th,e.isReadonly=tf,e.isRef=t_,e.isRuntimeOnly=()=>!d,e.isShallow=tp,e.isVNode=iu,e.markRaw=tv,e.mergeDefaults=function(e,t){let n=ru(e);for(let e in t){if(e.startsWith("__skip"))continue;let r=n[e];r?E(r)||I(r)?r=n[e]={type:r,default:t[e]}:r.default=t[e]:null===r&&(r=n[e]={default:t[e]}),r&&t[`__skip_${e}`]&&(r.skipFactory=!0)}return n},e.mergeModels=function(e,t){return e&&t?E(e)&&E(t)?e.concat(t):T({},ru(e),ru(t)):e||t},e.mergeProps=ik,e.nextTick=tz,e.nodeOps=iG,e.normalizeClass=ei,e.normalizeProps=function(e){if(!e)return null;let{class:t,style:n}=e;return t&&!R(t)&&(e.class=ei(t)),n&&(e.style=Y(n)),e},e.normalizeStyle=Y,e.onActivated=nz,e.onBeforeMount=n0,e.onBeforeUnmount=n6,e.onBeforeUpdate=n2,e.onDeactivated=nJ,e.onErrorCaptured=n7,e.onMounted=n1,e.onRenderTracked=n9,e.onRenderTriggered=n5,e.onScopeDispose=function(e,t=!1){l&&l.cleanups.push(e)},e.onServerPrefetch=n8,e.onUnmounted=n4,e.onUpdated=n3,e.onWatcherCleanup=tF,e.openBlock=ii,e.patchProp=lk,e.popScopeId=function(){t1=null},e.provide=t4,e.proxyRefs=tN,e.pushScopeId=function(e){t1=e},e.queuePostFlushCb=tX,e.reactive=ta,e.readonly=tu,e.ref=tS,e.registerRuntimeCompiler=iF,e.render=l8,e.renderList=function(e,t,n,r){let i,l=n&&n[r],s=E(e);if(s||R(e)){let n=s&&th(e),r=!1,o=!1;n&&(r=!tp(e),o=tf(e),e=eU(e)),i=Array(e.length);for(let n=0,s=e.length;nt(e,n,void 0,l&&l[n]));else{let n=Object.keys(e);i=Array(n.length);for(let r=0,s=n.length;r0;return"default"!==t&&(e.name=t),ii(),ic(r5,null,[im("slot",e,r&&r())],i?-2:64)}let o=e[t];o&&o._c&&(o._d=!1);let a=it.length;ii();try{let i=o&&function e(t){return t.some(t=>!iu(t)||t.type!==r7&&(t.type!==r5||!!e(t.children)))?t:null}(o(n)),a=n.key||l||i&&i.key;s=ic(r5,{key:(a&&!O(a)?a:`_${t}`)+(!i&&r?"_fb":"")},i||(r?r():[]),i&&1===e._?64:-2)}catch(e){for(let e=it.length;e>a;e--)il();throw e}finally{o&&o._c&&(o._d=!0)}return!i&&s.scopeId&&(s.slotScopeIds=[s.scopeId+"-s"]),s},e.resolveComponent=function(e,t){return rn(re,e,!0,t)||e},e.resolveDirective=function(e){return rn("directives",e)},e.resolveDynamicComponent=function(e){return R(e)?rn(re,e,!1)||e:e||rt},e.resolveFilter=null,e.resolveTransitionHooks=n_,e.setBlockTracking=io,e.setDevtoolsHook=S,e.setTransitionHooks=nC,e.shallowReactive=tc,e.shallowReadonly=function(e){return td(e,!0,e8,tr,to)},e.shallowRef=tx,e.ssrContextKey=t5,e.ssrUtils=null,e.stop=function(e){e.effect.stop()},e.toDisplayString=ef,e.toHandlerKey=W,e.toHandlers=function(e,t){let n={};for(let r in e)n[t&&/[A-Z]/.test(r)?`on:${r}`:W(r)]=e[r];return n},e.toRaw=tm,e.toRef=function(e,t,n){if(t_(e))return e;if(I(e))return new tR(e);if(!M(e)||!(arguments.length>1))return tS(e);return new tI(e,t,n)},e.toRefs=function(e){let t=E(e)?Array(e.length):{};for(let n in e)t[n]=new tI(e,n,void 0);return t},e.toValue=function(e){return I(e)?e():tT(e)},e.transformVNodeArgs=function(e){},e.triggerRef=function(e){e.dep&&e.dep.trigger()},e.unref=tT,e.useAttrs=function(){return rc().attrs},e.useCssModule=function(e="$style"){return b},e.useCssVars=function(e){let t=iE();if(!t)return;let n=t.ut=(n=e(t.proxy))=>{Array.from(document.querySelectorAll(`[data-v-owner="${t.uid}"]`)).forEach(e=>la(e,n))},r=()=>{let r=e(t.proxy);t.ce?la(t.ce,r):function e(t,n){if(128&t.shapeFlag){let r=t.suspense;t=r.activeBranch,r.pendingBranch&&!r.isHydrating&&r.effects.push(()=>{e(r.activeBranch,n)})}for(;t.component;)t=t.component.subTree;if(1&t.shapeFlag&&t.el)la(t.el,n);else if(t.type===r5)t.children.forEach(t=>e(t,n));else if(t.type===ie){let{el:e,anchor:r}=t;for(;e&&(la(e,n),e!==r);)e=e.nextSibling}}(t.subTree,r),n(r)};n2(()=>{tX(r)}),n1(()=>{t7(r,S,{flush:"post"});let e=new MutationObserver(r);e.observe(t.subTree.el.parentNode,{childList:!0}),n4(()=>e.disconnect())})},e.useHost=lE,e.useId=function(){let e=iE();return e?(e.appContext.config.idPrefix||"v")+"-"+e.ids[0]+e.ids[1]++:""},e.useModel=function(e,t,n=b){let r=iE(),i=j(t),l=H(t),s=rk(e,i),o=tE((s,o)=>{let a,c,u=b;return t9(()=>{let t=e[i];K(a,t)&&(a=t,o())}),{get:()=>(s(),n.get?n.get(a):a),set(e){let s=n.set?n.set(e):e;if(!K(s,a)&&!(u!==b&&K(e,u)))return;let d=r.vnode.props,h=!!(d&&(t in d||i in d||l in d)&&(`onUpdate:${t}`in d||`onUpdate:${i}`in d||`onUpdate:${l}`in d));h||(a=e,o()),r.emit(`update:${t}`,s),K(e,u)&&(K(e,s)&&!K(s,c)||h&&u!==b&&!K(s,a))&&o(),u=e,c=s}}});return o[Symbol.iterator]=()=>{let e=0;return{next:()=>e<2?{value:e++?s||b:o,done:!1}:{done:!0}}},o},e.useSSRContext=()=>{},e.useShadowRoot=function(){let e=lE();return e&&e.shadowRoot},e.useSlots=function(){return rc().slots},e.useTemplateRef=function(e){let t=iE(),n=tx(null);return t&&Object.defineProperty(t.refs===b?t.refs={}:t.refs,e,{enumerable:!0,get:()=>n.value,set:e=>n.value=e}),n},e.useTransitionState=nf,e.vModelCheckbox=lK,e.vModelDynamic={created(e,t,n){lY(e,t,n,null,"created")},mounted(e,t,n){lY(e,t,n,null,"mounted")},beforeUpdate(e,t,n,r){lY(e,t,n,r,"beforeUpdate")},updated(e,t,n,r){lY(e,t,n,r,"updated")}},e.vModelRadio=lJ,e.vModelSelect=lG,e.vModelText=lW,e.vShow={name:"show",beforeMount(e,{value:t},{transition:n}){e[li]="none"===e.style.display?"":e.style.display,n&&t?n.beforeEnter(e):ls(e,t)},mounted(e,{value:t},{transition:n}){n&&t&&n.enter(e)},updated(e,{value:t,oldValue:n},{transition:r}){!t!=!n&&(r?t?(r.beforeEnter(e),ls(e,!0),r.enter(e)):r.leave(e,()=>{ls(e,!1)}):ls(e,t))},beforeUnmount(e,{value:t}){ls(e,t)}},e.version=iq,e.warn=S,e.watch=function(e,t,n){return t7(e,t,n)},e.watchEffect=function(e,t){return t7(e,null,t)},e.watchPostEffect=function(e,t){return t7(e,null,{flush:"post"})},e.watchSyncEffect=t9,e.withAsyncContext=function(e){let t=iE(),n=iM,r=e();iR(),n&&u(!1);let i=()=>{iI(t),n&&u(!0)},l=()=>{iE()!==t&&t.scope.off(),iR(),n&&u(!1)};return P(r)&&(r=r.catch(e=>{throw i(),Promise.resolve().then(()=>Promise.resolve().then(l)),e})),[r,()=>{i(),Promise.resolve().then(l)}]},e.withCtx=t3,e.withDefaults=function(e,t){return null},e.withDirectives=function(e,t){if(null===t0)return e;let n=iV(t0),r=e.dirs||(e.dirs=[]);for(let e=0;e{let n=e._withKeys||(e._withKeys={}),r=t.join(".");return n[r]||(n[r]=n=>{if(!("key"in n))return;let r=H(n.key);if(t.some(e=>e===r||l2[e]===r))return e(n)})},e.withMemo=function(e,t,n,r){let i=n[r];if(i&&iH(i,e))return i;let l=t();return l.memo=e.slice(),l.cacheIndex=r,n[r]=l},e.withModifiers=(e,t)=>{if(!e)return e;let n=e._withMods||(e._withMods={}),r=t.join(".");return n[r]||(n[r]=(n,...r)=>{for(let e=0;et3,e}({});