diff --git a/apfree-wifidog/Makefile b/apfree-wifidog/Makefile index 92d31bdc8..06e9d3049 100644 --- a/apfree-wifidog/Makefile +++ b/apfree-wifidog/Makefile @@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=apfree-wifidog -PKG_RELEASE:=11 +PKG_RELEASE:=12 PKG_SOURCE_PROTO:=git PKG_SOURCE_URL:=https://github.com/liudf0716/apfree-wifidog.git diff --git a/apfree-wifidog/files/wifidogx.init b/apfree-wifidog/files/wifidogx.init index 89761abc1..5a6047183 100644 --- a/apfree-wifidog/files/wifidogx.init +++ b/apfree-wifidog/files/wifidogx.init @@ -19,6 +19,7 @@ handle_gateway() { config_get gateway_channel "$section" gateway_channel config_get gateway_id "$section" gateway_id config_get gateway_auth_enabled "$section" gateway_auth_enabled 1 + config_get gateway_subnetv4 "$section" gateway_subnetv4 if [ -z "$gateway_name" ] || [ -z "$gateway_channel" ] ; then @@ -36,23 +37,26 @@ handle_gateway() { uci set wifidogx."$section".gateway_id="$gateway_id" uci commit wifidogx fi - # according to the gateway_name to get the subnetv4 - local gateway_ipv4=$(ifconfig "$gateway_name" | awk '/inet addr:/ {print $2}' | cut -d: -f2) - local gateway_maskv4=$(ifconfig "$gateway_name" | awk '/Mask:/ {print $4}' | cut -d: -f2) - [ -z "$gateway_ipv4" ] && echo "Failed to get gateway_ipv4 for $gateway_name" >&2 && return - [ -z "$gateway_maskv4" ] && echo "Failed to get gateway_maskv4 for $gateway_name" >&2 && return - # change the gateway_ip4/gateway_maskv4 to CIDR format - local mask_bits=0 - local mask_value=$(printf '%d' "0x$(echo $gateway_maskv4 | tr '.' ' ' | awk '{printf "%02x%02x%02x%02x",$1,$2,$3,$4}')") - while [ $mask_value -ne 0 ]; do - mask_bits=$((mask_bits + (mask_value & 1))) - mask_value=$((mask_value >> 1)) - done - gateway_subnetv4="$gateway_ipv4/$mask_bits" - - uci set wifidogx."$section".gateway_subnetv4="$gateway_subnetv4" - uci commit wifidogx + # If gateway_subnetv4 is not configured in UCI, detect it from interface + if [ -z "$gateway_subnetv4" ]; then + local gateway_ipv4=$(ifconfig "$gateway_name" | awk '/inet addr:/ {print $2}' | cut -d: -f2) + local gateway_maskv4=$(ifconfig "$gateway_name" | awk '/Mask:/ {print $4}' | cut -d: -f2) + [ -z "$gateway_ipv4" ] && echo "Failed to get gateway_ipv4 for $gateway_name" >&2 && return + [ -z "$gateway_maskv4" ] && echo "Failed to get gateway_maskv4 for $gateway_name" >&2 && return + + # change the gateway_ip4/gateway_maskv4 to CIDR format + local mask_bits=0 + local mask_value=$(printf '%d' "0x$(echo $gateway_maskv4 | tr '.' ' ' | awk '{printf "%02x%02x%02x%02x",$1,$2,$3,$4}')") + while [ $mask_value -ne 0 ]; do + mask_bits=$((mask_bits + (mask_value & 1))) + mask_value=$((mask_value >> 1)) + done + gateway_subnetv4="$gateway_ipv4/$mask_bits" + + uci set wifidogx."$section".gateway_subnetv4="$gateway_subnetv4" + uci commit wifidogx + fi printf "GatewaySetting {\n\tGatewayAuthEnabled %s\n\tGatewayInterface %s\n\tGatewayChannel %s\n\tGatewayID %s\n\tGatewaySubnetV4 %s\n}\n" \ "${gateway_auth_enabled}" "${gateway_name}" "${gateway_channel}" "${gateway_id}" "${gateway_subnetv4}" >> "$CONFIGFILE" @@ -141,11 +145,16 @@ prepare_auth_server_settings() { fi case "$auth_server_mode" in - cloud|bypass) + cloud) printf "AuthServerMode 0\n" >> "$CONFIGFILE" printf "DeviceID %s\nAuthServer {\n\tHostname %s\n\tHTTPPort %s\n\tPath %s\n}\n" \ "$device_id" "$auth_server_hostname" "$auth_server_port" "$auth_server_path" >> "$CONFIGFILE" ;; + bypass) + printf "AuthServerMode 1\n" >> "$CONFIGFILE" + printf "DeviceID %s\nAuthServer {\n\tHostname %s\n\tHTTPPort %s\n\tPath %s\n}\n" \ + "$device_id" "$auth_server_hostname" "$auth_server_port" "$auth_server_path" >> "$CONFIGFILE" + ;; local) printf "AuthServerMode 2\n" >> "$CONFIGFILE" [ -n "$device_id" ] && printf "DeviceID %s\n" "$device_id" >> "$CONFIGFILE" @@ -155,6 +164,7 @@ prepare_auth_server_settings() { esac [ -n "$internet_offline_file" ] && printf "InternetOfflineFile %s\n" "$internet_offline_file" >> "$CONFIGFILE" + return 0 } prepare_longconn_settings() { @@ -266,11 +276,12 @@ prepare_wifidog_conf() { [ -n "$wildcard_white_list" ] && add_white_list_entries "wildcard_white_list" "wildcard_domain" "trusted_wildcard_domains" prepare_external_interface - prepare_auth_server_settings + prepare_auth_server_settings || return 1 prepare_longconn_settings prepare_device_info config_foreach handle_gateway gateway prepare_common_settings + return 0 } service_triggers() { @@ -280,7 +291,7 @@ service_triggers() { start_service() { config_load "$NAME" - prepare_wifidog_conf + prepare_wifidog_conf || return 1 if [ "$enabled" -eq 0 ]; then echo "wifidogx is disabled, exit..." >&2 diff --git a/gately/Makefile b/gately/Makefile index b3edf4184..34ae72e51 100644 --- a/gately/Makefile +++ b/gately/Makefile @@ -5,8 +5,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=gately -PKG_VERSION:=0.6.4.1 -PKG_RELEASE:=26 +PKG_VERSION:=0.6.5 +PKG_RELEASE:=27 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/wangwanjie/gately/tar.gz/v$(PKG_VERSION)? diff --git a/ks-cli/Makefile b/ks-cli/Makefile index 98eeb886f..ac53eca16 100644 --- a/ks-cli/Makefile +++ b/ks-cli/Makefile @@ -5,8 +5,8 @@ include $(TOPDIR)/rules.mk PKG_ARCH_KS_CLI:=$(ARCH) PKG_NAME:=ks-cli -PKG_VERSION:=0.8.8 -PKG_RELEASE:=5 +PKG_VERSION:=0.8.9 +PKG_RELEASE:=6 PKG_SOURCE:=ks-cli-binary-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://github.com/kspeeder/docker_kspeeder/releases/download/v$(PKG_VERSION)/ diff --git a/luci-app-apfree-wifidog/Makefile b/luci-app-apfree-wifidog/Makefile index b41e905b7..5eb0eb9a5 100644 --- a/luci-app-apfree-wifidog/Makefile +++ b/luci-app-apfree-wifidog/Makefile @@ -3,7 +3,7 @@ include $(TOPDIR)/rules.mk LUCI_TITLE:=LuCI Support for apfree-wifidog -LUCI_DEPENDS:=+apfree-wifidog +LUCI_DEPENDS:=+apfree-wifidog +curl PKG_LICENSE:=Apache-2.0 PKG_MAINTAINER:=Dengfeng Liu diff --git a/luci-app-apfree-wifidog/htdocs/luci-static/resources/view/wifidogx.js b/luci-app-apfree-wifidog/htdocs/luci-static/resources/view/wifidogx.js index 40d7038a9..bcb7f8bd2 100644 --- a/luci-app-apfree-wifidog/htdocs/luci-static/resources/view/wifidogx.js +++ b/luci-app-apfree-wifidog/htdocs/luci-static/resources/view/wifidogx.js @@ -72,6 +72,30 @@ function renderStatus(isRunning) { return renderHTML; } +function validateWildcardDomain(section_id, value) { + if (!value || value.length === 0) + return true; + if (/^(\*\.|\.)?[a-zA-Z0-9_]([a-zA-Z0-9-_]*\.)*[a-zA-Z0-9-_]+$/.test(value)) + return true; + return _('Expecting a valid wildcard domain pattern like *.example.com or .example.com'); +} + +function checkAndCleanGhostSection(config, type, name, excludeSid) { + var sections = uci.sections(config, type); + for (var i = 0; i < sections.length; i++) { + var s = sections[i]; + var sname = s['.name']; + if (sname === name && sname !== excludeSid) { + if (s['.create'] != null) { + uci.remove(config, sname); + continue; + } + return true; + } + } + return false; +} + return view.extend({ render: function () { var m, s, o, ss; @@ -106,10 +130,11 @@ return view.extend({ // 添加一个下拉框,列出当前定义的认证服务列表,并选择当前选中的认证服务 o = s.taboption('basic', form.ListValue, 'selected_auth_server', _('Authentication Server'), - _('Select the authentication server to use for cloud authentication.')); + _('Select the authentication server to use for cloud or bypass authentication.')); o.rmempty = false; o.optional = false; o.depends('auth_server_mode', 'cloud'); + o.depends('auth_server_mode', 'bypass'); o.load = function (section_id) { this.keylist = []; this.vallist = []; @@ -218,10 +243,10 @@ return view.extend({ o.optional = true; o = ss.option(form.Value, 'gateway_subnetv4', _('Gateway Subnetv4'), - _('The ipv4 subnet of the gateway.')); + _('The ipv4 subnet of the gateway. Leave empty to automatically detect from interface.')); o.datatype = 'cidr4'; - o.rmempty = false; - o.optional = false; + o.rmempty = true; + o.optional = true; o.placeholder = '192.168.80.0/24'; // advanced settings @@ -260,7 +285,7 @@ return view.extend({ o.optional = false; o.default = 'chawrt@2026'; - o = s.taboption('advanced', form.Value, 'ttl_value', _('TTL Value'), + o = s.taboption('advanced', form.Value, 'ttl_values', _('TTL Value'), _('The TTL value of the gateway support.')); o.datatype = 'string'; o.rmempty = false; @@ -282,19 +307,13 @@ return view.extend({ ss.handleAdd = function (ev, name) { var self = this; if (name) { - var longconnSections = uci.sections('wifidogx', 'longconn'); - for (var i = 0; i < longconnSections.length; i++) { - if (longconnSections[i]['.name'] === name) { - ui.addNotification(null, E('p', _('The name "%s" already exists in Long Connection profiles. Please choose a different name.').format(name)), 'error'); - return Promise.resolve(); - } + if (checkAndCleanGhostSection('wifidogx', 'longconn', name)) { + ui.addNotification(null, E('p', _('The name "%s" already exists in Long Connection profiles. Please choose a different name.').format(name)), 'error'); + return Promise.resolve(); } - var authSections = uci.sections('wifidogx', 'auth'); - for (var i = 0; i < authSections.length; i++) { - if (authSections[i]['.name'] === name) { - ui.addNotification(null, E('p', _('The name "%s" already exists in Auth Server profiles. Please choose a different name.').format(name)), 'error'); - return Promise.resolve(); - } + if (checkAndCleanGhostSection('wifidogx', 'auth', name)) { + ui.addNotification(null, E('p', _('The name "%s" already exists in Auth Server profiles. Please choose a different name.').format(name)), 'error'); + return Promise.resolve(); } } return this.super('handleAdd', [ev, name]); @@ -332,19 +351,13 @@ return view.extend({ ui.hideModal(); return; } - var longconnSections = uci.sections('wifidogx', 'longconn'); - for (var i = 0; i < longconnSections.length; i++) { - if (longconnSections[i]['.name'] === newName) { - ui.addNotification(null, E('p', _('The name "%s" already exists in Long Connection profiles. Please choose a different name.').format(newName)), 'error'); - return; - } + if (checkAndCleanGhostSection('wifidogx', 'longconn', newName, oldName)) { + ui.addNotification(null, E('p', _('The name "%s" already exists in Long Connection profiles. Please choose a different name.').format(newName)), 'error'); + return; } - var authSections = uci.sections('wifidogx', 'auth'); - for (var i = 0; i < authSections.length; i++) { - if (authSections[i]['.name'] === newName && authSections[i]['.name'] !== oldName) { - ui.addNotification(null, E('p', _('The name "%s" already exists in Auth Server profiles. Please choose a different name.').format(newName)), 'error'); - return; - } + if (checkAndCleanGhostSection('wifidogx', 'auth', newName, oldName)) { + ui.addNotification(null, E('p', _('The name "%s" already exists in Auth Server profiles. Please choose a different name.').format(newName)), 'error'); + return; } ui.hideModal(); uci.rename('wifidogx', section_id, newName); @@ -420,7 +433,7 @@ return view.extend({ ]); } - // Helper function to get MAC address with fallback + // Helper function to get MAC address function getMacAddressPromise(results) { return callGetWanMac().then(function (response) { if (response && response.status === 'success') { @@ -434,40 +447,17 @@ return view.extend({ results.locationId = generateLocationId(); document.getElementById('location-id-status').innerHTML = _('✓ Location ID generated: ') + results.locationId; } else { - document.getElementById('mac-status').innerHTML = _('RPC returned error, trying fallback...'); - return getMacAddressFallback(results); - } - }).catch(function (error) { - document.getElementById('mac-status').innerHTML = _('RPC call failed, trying fallback...'); - return getMacAddressFallback(results); - }); - } - - // Helper function for MAC address fallback - function getMacAddressFallback(results) { - return fs.exec('/bin/sh', ['-c', 'ip route | grep default | awk \'{print $5}\' | head -n1 | xargs -I {} cat /sys/class/net/{}/address 2>/dev/null || echo "failed"']).then(function (response) { - if (response.code === 0 && response.stdout && response.stdout.trim() !== 'failed') { - var macAddr = response.stdout.trim().toUpperCase().replace(/:/g, '-'); - results.macAddress = macAddr; - document.getElementById('mac-status').innerHTML = _('✓ WAN MAC address obtained (fallback): ') + macAddr; - - results.deviceId = generateDeviceId(macAddr); - document.getElementById('device-id-status').innerHTML = _('✓ Device ID generated: ') + results.deviceId; - - results.locationId = generateLocationId(); - document.getElementById('location-id-status').innerHTML = _('✓ Location ID generated: ') + results.locationId; - } else { - var errorMsg = (response && response.message) ? response.message : 'Unknown error'; + var errorMsg = (response && response.message) ? response.message : _('Unknown error'); document.getElementById('mac-status').innerHTML = _('✗ Failed to get WAN MAC address: ') + errorMsg; document.getElementById('device-id-status').innerHTML = _('✗ Cannot generate Device ID without MAC address'); } }).catch(function (error) { - document.getElementById('mac-status').innerHTML = _('✗ Error getting WAN MAC address: ') + error.message; + document.getElementById('mac-status').innerHTML = _('✗ Error getting WAN MAC address: ') + (error.message || error); document.getElementById('device-id-status').innerHTML = _('✗ Cannot generate Device ID due to MAC error'); }); } - // Helper function to get location with fallback + // Helper function to get location function getLocationPromise(results) { return callGetLocation().then(function (response) { if (response && response.status === 'success') { @@ -477,66 +467,29 @@ return view.extend({ formatAndSetCoordinates(results, lat, lon); document.getElementById('location-status').innerHTML = _('✓ Location obtained: ') + results.latitude + ', ' + results.longitude; } else { - document.getElementById('location-status').innerHTML = _('RPC returned error, trying fallback...'); - return getLocationFallback(results); + var errorMsg = (response && response.message) ? response.message : _('Location services unavailable'); + document.getElementById('location-status').innerHTML = _('✗ Failed to get location data: ') + errorMsg; } }).catch(function (error) { - document.getElementById('location-status').innerHTML = _('RPC failed, trying direct method...'); - return getLocationFallback(results); - }); - } - - // Helper function for location fallback - function getLocationFallback(results) { - return fs.exec('/bin/sh', ['-c', 'curl -s --connect-timeout 10 --max-time 30 "https://ipapi.co/json" 2>/dev/null || curl -s --connect-timeout 10 --max-time 30 "http://ipinfo.io/json" 2>/dev/null || echo "failed"']).then(function (response) { - if (response.code === 0 && response.stdout && response.stdout.trim() !== 'failed') { - try { - var data = JSON.parse(response.stdout); - var lat, lon; - - if (data.latitude && data.longitude) { - lat = parseFloat(data.latitude).toFixed(6); - lon = parseFloat(data.longitude).toFixed(6); - } else if (data.loc) { - var coords = data.loc.split(','); - lat = parseFloat(coords[0]).toFixed(6); - lon = parseFloat(coords[1]).toFixed(6); - } - - if (lat && lon) { - formatAndSetCoordinates(results, lat, lon); - document.getElementById('location-status').innerHTML = _('✓ Location obtained (fallback): ') + results.latitude + ', ' + results.longitude; - } else { - document.getElementById('location-status').innerHTML = _('✗ No valid coordinates found'); - } - } catch (e) { - document.getElementById('location-status').innerHTML = _('✗ Failed to parse location data'); - } - } else { - document.getElementById('location-status').innerHTML = _('✗ Failed to get location data'); - } - }).catch(function (fallbackError) { - document.getElementById('location-status').innerHTML = _('✗ Error getting location: ') + fallbackError.message; + document.getElementById('location-status').innerHTML = _('✗ Error getting location: ') + (error.message || error); }); } // Helper function to format coordinates function formatAndSetCoordinates(results, lat, lon) { - // Pad coordinates to required format - if (lat >= 0) { - lat = lat.padStart(10, '0'); - } else { - lat = '-' + Math.abs(lat).toFixed(6).padStart(9, '0'); + function formatCoord(val) { + var num = parseFloat(val); + if (isNaN(num)) return ''; + var isNeg = num < 0; + var absFixed = Math.abs(num).toFixed(6); + var parts = absFixed.split('.'); + var intPart = parts[0].padStart(3, '0'); + var decPart = parts[1]; + return (isNeg ? '-' : '') + intPart + '.' + decPart; } - if (lon >= 0) { - lon = lon.padStart(10, '0'); - } else { - lon = '-' + Math.abs(lon).toFixed(6).padStart(9, '0'); - } - - results.longitude = lon; - results.latitude = lat; + results.longitude = formatCoord(lon); + results.latitude = formatCoord(lat); } // Helper function to fill form fields @@ -790,19 +743,13 @@ return view.extend({ ss.handleAdd = function (ev, name) { var self = this; if (name) { - var authSections = uci.sections('wifidogx', 'auth'); - for (var i = 0; i < authSections.length; i++) { - if (authSections[i]['.name'] === name) { - ui.addNotification(null, E('p', _('The name "%s" already exists in Auth Server profiles. Please choose a different name.').format(name)), 'error'); - return Promise.resolve(); - } + if (checkAndCleanGhostSection('wifidogx', 'auth', name)) { + ui.addNotification(null, E('p', _('The name "%s" already exists in Auth Server profiles. Please choose a different name.').format(name)), 'error'); + return Promise.resolve(); } - var longconnSections = uci.sections('wifidogx', 'longconn'); - for (var i = 0; i < longconnSections.length; i++) { - if (longconnSections[i]['.name'] === name) { - ui.addNotification(null, E('p', _('The name "%s" already exists in Long Connection profiles. Please choose a different name.').format(name)), 'error'); - return Promise.resolve(); - } + if (checkAndCleanGhostSection('wifidogx', 'longconn', name)) { + ui.addNotification(null, E('p', _('The name "%s" already exists in Long Connection profiles. Please choose a different name.').format(name)), 'error'); + return Promise.resolve(); } } return this.super('handleAdd', [ev, name]); @@ -840,19 +787,13 @@ return view.extend({ ui.hideModal(); return; } - var authSections = uci.sections('wifidogx', 'auth'); - for (var i = 0; i < authSections.length; i++) { - if (authSections[i]['.name'] === newName) { - ui.addNotification(null, E('p', _('The name "%s" already exists in Auth Server profiles. Please choose a different name.').format(newName)), 'error'); - return; - } + if (checkAndCleanGhostSection('wifidogx', 'auth', newName, oldName)) { + ui.addNotification(null, E('p', _('The name "%s" already exists in Auth Server profiles. Please choose a different name.').format(newName)), 'error'); + return; } - var longconnSections = uci.sections('wifidogx', 'longconn'); - for (var i = 0; i < longconnSections.length; i++) { - if (longconnSections[i]['.name'] === newName && longconnSections[i]['.name'] !== oldName) { - ui.addNotification(null, E('p', _('The name "%s" already exists in Long Connection profiles. Please choose a different name.').format(newName)), 'error'); - return; - } + if (checkAndCleanGhostSection('wifidogx', 'longconn', newName, oldName)) { + ui.addNotification(null, E('p', _('The name "%s" already exists in Long Connection profiles. Please choose a different name.').format(newName)), 'error'); + return; } ui.hideModal(); uci.rename('wifidogx', section_id, newName); @@ -940,7 +881,7 @@ return view.extend({ _('Requires aw-bpf DNS XDP (dns_ringbuf_portal). Patterns like .example.com; resolved IPs are added to the captive portal firewall (inet wifidogx), not inet awbpf.')); o.rmempty = true; o.optional = true; - o.datatype = 'wildcard'; + o.validate = validateWildcardDomain; o.placeholder = '.example.com'; o = s.taboption('rule', form.DynamicList, 'trusted_domains', _('Trusted Domains'), @@ -983,8 +924,9 @@ return view.extend({ s.nodescriptions = true; s.handleRemove = function (section_id, ev) { - // according section_id to check whether it is used by app_white_list or mac_white_list - var group = uci.get('wifidogx', section_id, 'g_type') === '1' ? 'app_white_list' : 'mac_white_list'; + // according section_id to check whether it is used by app_white_list, mac_white_list or wildcard_white_list + var gtype = uci.get('wifidogx', section_id, 'g_type'); + var group = (gtype === '1') ? 'app_white_list' : (gtype === '3' ? 'wildcard_white_list' : 'mac_white_list'); var groupList = uci.get('wifidogx', 'common', group); if (groupList) { for (var i = 0; i < groupList.length; i++) { @@ -1024,7 +966,7 @@ return view.extend({ o = s.option(form.DynamicList, 'wildcard_domain', _('Wildcard Domain'), _('The wildcard domain of the group.')); o.depends('g_type', '3'); - o.datatype = 'wildcard'; + o.validate = validateWildcardDomain; o.rmempty = false; o.optional = false; o.placeholder = '.example.com'; diff --git a/luci-app-apfree-wifidog/root/usr/libexec/rpcd/luci.wifidogx b/luci-app-apfree-wifidog/root/usr/libexec/rpcd/luci.wifidogx index 9c4794010..96d2f8fb5 100755 --- a/luci-app-apfree-wifidog/root/usr/libexec/rpcd/luci.wifidogx +++ b/luci-app-apfree-wifidog/root/usr/libexec/rpcd/luci.wifidogx @@ -2,6 +2,17 @@ . /usr/share/libubox/jshn.sh +http_fetch() { + local url="$1" + if command -v curl >/dev/null 2>&1; then + curl -s --connect-timeout 10 --max-time 30 "$url" 2>/dev/null + elif command -v uclient-fetch >/dev/null 2>&1; then + uclient-fetch -q -O - --timeout=10 "$url" 2>/dev/null + elif command -v wget >/dev/null 2>&1; then + wget -q -O - -T 10 "$url" 2>/dev/null + fi +} + case "$1" in list) json_init @@ -79,7 +90,7 @@ case "$1" in # Try multiple location APIs for better reliability # First try ipapi.co (HTTPS, more reliable) - result=$(curl -s --connect-timeout 10 --max-time 30 "https://ipapi.co/json" 2>/dev/null) + result=$(http_fetch "https://ipapi.co/json") if [ $? -eq 0 ] && [ -n "$result" ]; then # Check if we got valid latitude/longitude @@ -99,7 +110,7 @@ case "$1" in fi # If first API failed, try ipinfo.io as backup - result=$(curl -s --connect-timeout 10 --max-time 30 "http://ipinfo.io/json" 2>/dev/null) + result=$(http_fetch "http://ipinfo.io/json") if [ $? -eq 0 ] && [ -n "$result" ]; then # Extract lat,lon from loc field (format: "lat,lon") diff --git a/luci-app-fchomo/htdocs/luci-static/resources/fchomo.js b/luci-app-fchomo/htdocs/luci-static/resources/fchomo.js index 1a98789f0..f6878483f 100644 --- a/luci-app-fchomo/htdocs/luci-static/resources/fchomo.js +++ b/luci-app-fchomo/htdocs/luci-static/resources/fchomo.js @@ -268,7 +268,7 @@ const preset_outbound = { ['COMPATIBLE'] ], direct: [ - ['', _('null')], + ['', _('none')], ['DIRECT'], ['GLOBAL'] ], @@ -711,16 +711,17 @@ const CBIGenValue = form.Value.extend({ __name__: 'CBI.GenValue', renderWidget(section_id, option_index, cfgvalue) { - let node = form.Value.prototype.renderWidget.call(this, section_id, option_index, cfgvalue); + let node = form.Value.prototype.renderWidget.apply(this, arguments); if (!this.password) node.classList.add('control-group'); - (node.querySelector('.control-group') || node).appendChild(E('button', { - class: 'cbi-button cbi-button-add', - title: _('Generate'), - click: ui.createHandlerFn(this, handleGenKey, section_id, this.hm_options || this.option) - }, [ _('Generate') ])); + if (this.section.section) // modalonly + (node.querySelector('.control-group') || node).appendChild(E('button', { + class: 'cbi-button cbi-button-add', + title: _('Generate'), + click: ui.createHandlerFn(this, handleGenKey, section_id, this.et_options || this.option) + }, [ _('Generate') ])); return node; } @@ -730,13 +731,14 @@ const CBIGenText = CBITextValue.extend({ __name__: 'CBI.GenText', renderWidget(section_id, option_index, cfgvalue) { - let node = CBITextValue.prototype.renderWidget.call(this, section_id, option_index, cfgvalue); + let node = CBITextValue.prototype.renderWidget.apply(this, arguments); - node.appendChild(E('button', { - class: 'cbi-button cbi-button-add', - title: _('Generate'), - click: ui.createHandlerFn(this, handleGenKey, section_id, this.hm_options || this.option) - }, [ _('Generate') ])); + if (this.section.section) // modalonly + (node.querySelector('.control-group') || node).appendChild(E('button', { + class: 'cbi-button cbi-button-add', + title: _('Generate'), + click: ui.createHandlerFn(this, handleGenKey, section_id, this.et_options || this.option) + }, [ _('Generate') ])); return node; } @@ -748,28 +750,29 @@ const CBICopyValue = form.Value.extend({ readonly: true, renderWidget(section_id, option_index, cfgvalue) { - let node = form.Value.prototype.renderWidget.call(this, section_id, option_index, cfgvalue); + let node = form.Value.prototype.renderWidget.apply(this, arguments); node.classList.add('control-group'); - node.appendChild(E('button', { - class: 'cbi-button cbi-button-add', - click: ui.createHandlerFn(this, async (section_id) => { - try { - await navigator.clipboard.writeText(this.formvalue(section_id)); - console.log('Content copied to clipboard!'); - } catch (e) { - console.error('Failed to copy: ', e); - } - /* Deprecated - let inputEl = document.getElementById(this.cbid(section_id)).querySelector('input'); - inputEl.select(); - document.execCommand("copy"); - inputEl.blur(); - */ - return alert(_('Content copied to clipboard!')); - }, section_id) - }, [ _('Copy') ])); + if (this.section.section) // modalonly + node.appendChild(E('button', { + class: 'cbi-button cbi-button-add', + click: ui.createHandlerFn(this, async (section_id) => { + try { + await navigator.clipboard.writeText(this.formvalue(section_id)); + console.log('Content copied to clipboard!'); + } catch (e) { + console.error('Failed to copy: ', e); + } + /* Deprecated + let inputEl = document.getElementById(this.cbid(section_id)).querySelector('input'); + inputEl.select(); + document.execCommand("copy"); + inputEl.blur(); + */ + return alert(_('Content copied to clipboard!')); + }, section_id) + }, [ _('Copy') ])); return node; } diff --git a/luci-app-fchomo/htdocs/luci-static/resources/fchomo/listeners.js b/luci-app-fchomo/htdocs/luci-static/resources/fchomo/listeners.js index ffad73724..cf5497ae2 100644 --- a/luci-app-fchomo/htdocs/luci-static/resources/fchomo/listeners.js +++ b/luci-app-fchomo/htdocs/luci-static/resources/fchomo/listeners.js @@ -264,7 +264,7 @@ function renderListeners(s, uciconfig, isClient) { click: ui.createHandlerFn(this, () => { this.hm_options.type = document.getElementById(cbid).value; - return hm.handleGenKey.call(this, this.hm_options); + return hm.handleGenKey.call(this, section_id, this.hm_options); }) }, [ _('Generate') ]) ])); @@ -311,7 +311,7 @@ function renderListeners(s, uciconfig, isClient) { (node.querySelector('.control-group') || node).appendChild(E('button', { class: 'cbi-button cbi-button-positive', title: _('Generate'), - click: ui.createHandlerFn(this, hm.handleGenKey, this.hm_options || this.option) + click: ui.createHandlerFn(this, hm.handleGenKey, section_id, this.hm_options || this.option) }, [ _('Generate') ])); return node; @@ -616,10 +616,10 @@ function renderListeners(s, uciconfig, isClient) { o = s.taboption('field_general', hm.ListValue, 'rule', _('Sub rule'), _('Name of the Sub rule used for inbound matching.')); - o.value.apply(o, ['', _('null')]); + o.value.apply(o, ['', _('none')]); o.load = function(section_id) { return hm.loadLabel.call(this, [ - ['', _('null')], + ['', _('none')], ...hm.loadLabelValues(this.config, 'subrule-group') ], section_id); } @@ -960,7 +960,7 @@ function renderListeners(s, uciconfig, isClient) { click: ui.createHandlerFn(this, () => { this.hm_options.type = document.getElementById(cbid).value; - return hm.handleGenKey.call(this, this.hm_options); + return hm.handleGenKey.call(this, section_id, this.hm_options); }) }, [ _('Generate') ]) ])); @@ -1213,7 +1213,7 @@ function renderListeners(s, uciconfig, isClient) { click: ui.createHandlerFn(this, () => { this.hm_options.params = document.getElementById(cbid).value; - return hm.handleGenKey.call(this, this.hm_options); + return hm.handleGenKey.call(this, section_id, this.hm_options); }) }, [ _('Generate') ]) ])); diff --git a/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/global.js b/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/global.js index 83af3c6e7..63735986c 100644 --- a/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/global.js +++ b/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/global.js @@ -611,7 +611,7 @@ return view.extend({ click: ui.createHandlerFn(this, () => { this.hm_options.params = document.getElementById(cbid).value; - return hm.handleGenKey.call(this, this.hm_options); + return hm.handleGenKey.call(this, section_id, this.hm_options); }) }, [ _('Generate') ]) ])); diff --git a/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/node.js b/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/node.js index a95ac116a..7e81b1525 100644 --- a/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/node.js +++ b/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/node.js @@ -1071,16 +1071,19 @@ return view.extend({ so.depends('type', 'easytier'); so.modalonly = true; - so = ss.taboption('field_general', form.Flag, 'easytier_secure_mode', _('Secure Mode')); + so = ss.taboption('field_general', form.Flag, 'easytier_secure_mode', _('Secure mode')); so.default = so.disabled; so.validate = function(section_id, value) { const secure_mode = this.getUIElement(section_id).node.querySelector('input'); const peers = this.section.getOption('easytier_peers').formvalue(section_id); - const local_private_key = this.section.getOption('easytier_local_private_key').formvalue(section_id); - const local_public_key = this.section.getOption('easytier_local_public_key').formvalue(section_id); // Force enabled - if ((local_private_key && local_public_key) || peers.filter(e => new URL(e).searchParams.get('peer-public-key')).length > 0) { + if (peers.filter(e => { + try { + return new URL(e).searchParams.get('peer-public-key'); + } catch {} + }).length > 0 + ) { secure_mode.checked = true; secure_mode.disabled = true; } else @@ -1104,12 +1107,13 @@ return view.extend({ } so.password = true; so.validate = L.bind(hm.validateBase64Key, so, 44); - so.depends('type', 'easytier'); + so.rmempty = false; + so.depends('easytier_secure_mode', '1'); so.modalonly = true; so = ss.taboption('field_general', hm.CopyValue, 'easytier_local_public_key', _('Client public key'), _('Base64 encoded X25519 public key.')); - so.depends('type', 'easytier'); + so.depends('easytier_secure_mode', '1'); so.modalonly = true; so = ss.taboption('field_general', form.DynamicList, 'easytier_exit_nodes', _('Exit nodes'), @@ -2464,7 +2468,7 @@ return view.extend({ else this.hm_options.params = ''; - return hm.handleGenKey.call(this, this.hm_options); + return hm.handleGenKey.call(this, section_id, this.hm_options); }) }, [ _('Generate') ]) ])); diff --git a/luci-app-fchomo/po/templates/fchomo.pot b/luci-app-fchomo/po/templates/fchomo.pot index a1ded7eaa..85eb48a9c 100644 --- a/luci-app-fchomo/po/templates/fchomo.pot +++ b/luci-app-fchomo/po/templates/fchomo.pot @@ -3247,7 +3247,7 @@ msgid "Secondary" msgstr "" #: htdocs/luci-static/resources/view/fchomo/node.js:1074 -msgid "Secure Mode" +msgid "Secure mode" msgstr "" #: htdocs/luci-static/resources/view/fchomo/global.js:531 diff --git a/luci-app-fchomo/po/zh_Hans/fchomo.po b/luci-app-fchomo/po/zh_Hans/fchomo.po index 737be9553..c6d18dcf1 100644 --- a/luci-app-fchomo/po/zh_Hans/fchomo.po +++ b/luci-app-fchomo/po/zh_Hans/fchomo.po @@ -3282,7 +3282,7 @@ msgid "Secondary" msgstr "次要" #: htdocs/luci-static/resources/view/fchomo/node.js:1074 -msgid "Secure Mode" +msgid "Secure mode" msgstr "安全模式" #: htdocs/luci-static/resources/view/fchomo/global.js:531 diff --git a/luci-app-fchomo/po/zh_Hant/fchomo.po b/luci-app-fchomo/po/zh_Hant/fchomo.po index 5b17f10f8..05d209f03 100644 --- a/luci-app-fchomo/po/zh_Hant/fchomo.po +++ b/luci-app-fchomo/po/zh_Hant/fchomo.po @@ -3282,7 +3282,7 @@ msgid "Secondary" msgstr "次要" #: htdocs/luci-static/resources/view/fchomo/node.js:1074 -msgid "Secure Mode" +msgid "Secure mode" msgstr "安全模式" #: htdocs/luci-static/resources/view/fchomo/global.js:531 diff --git a/luci-app-passwall/Makefile b/luci-app-passwall/Makefile index 1f62f0736..8f8392095 100644 --- a/luci-app-passwall/Makefile +++ b/luci-app-passwall/Makefile @@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-passwall PKG_VERSION:=26.11.3 -PKG_RELEASE:=367 +PKG_RELEASE:=368 PKG_PO_VERSION:=$(PKG_VERSION) PKG_CONFIG_DEPENDS:= \ diff --git a/luci-app-passwall/luasrc/view/passwall/node_config/link_share_man.htm b/luci-app-passwall/luasrc/view/passwall/node_config/link_share_man.htm index afb1218fb..bf9c5b5d9 100644 --- a/luci-app-passwall/luasrc/view/passwall/node_config/link_share_man.htm +++ b/luci-app-passwall/luasrc/view/passwall/node_config/link_share_man.htm @@ -973,19 +973,6 @@ local current_node = map:get(section) } } - // 判断密码是否经过url编码 - const isURLEncodedPassword = function(pwd) { - if (!/%[0-9A-Fa-f]{2}/.test(pwd)) return false; - try { - const decoded = decodeURIComponent(pwd.replace(/\+/g, "%20")); - const reencoded = encodeURIComponent(decoded); - return reencoded === pwd; - } catch (e) { - return false; - } - } - password = isURLEncodedPassword(password) ? decodeURIComponent(password) : password; - if (queryParam.plugin) { var pluginParams = decodeURIComponent(queryParam.plugin).split(";"); plugin = pluginParams.shift(); @@ -1775,13 +1762,10 @@ local current_node = map:get(section) var port = _parsedUrl.port; var search = _parsedUrl.search; var hash = _parsedUrl.hash; - if (!username || !password) { //某些链接会把username和password之间的:进行编码 - const decoded = decodeURIComponent(username || password || ""); - const i = decoded.indexOf(":"); - if (i > -1) { - username = decoded.slice(0, i); - password = decoded.slice(i + 1); - } + // URL 已按明文冒号拆分;没有分隔符时整个 userinfo 是密码。 + if (link_split[1].split("@", 1)[0].indexOf(":") === -1) { + password = username; + username = ""; } opt.set(dom_prefix + 'username', decodeURIComponent(username)); opt.set(dom_prefix + 'password', decodeURIComponent(password)); diff --git a/luci-app-passwall/root/usr/share/passwall/subscribe.lua b/luci-app-passwall/root/usr/share/passwall/subscribe.lua index 4d5f57ca4..5df90cd8f 100755 --- a/luci-app-passwall/root/usr/share/passwall/subscribe.lua +++ b/luci-app-passwall/root/usr/share/passwall/subscribe.lua @@ -876,20 +876,6 @@ local function processData(szType, content, add_mode, group, sub_cfg) password = hostInfo[1] --一些链接用明文uuid做密码 end - -- 判断密码是否经过url编码 - local function isURLEncodedPassword(pwd) - if not pwd:find("%%[0-9A-Fa-f][0-9A-Fa-f]") then - return false - end - local ok, decoded = pcall(UrlDecode, pwd) - return ok and UrlEncode(decoded) == pwd - end - - local decoded = UrlDecode(password) - if isURLEncodedPassword(password) and decoded then - password = decoded - end - local _method = (method or "none"):lower() method = (_method == "chacha20-poly1305" and "chacha20-ietf-poly1305") or (_method == "xchacha20-poly1305" and "xchacha20-ietf-poly1305") or _method @@ -1682,13 +1668,13 @@ local function processData(szType, content, add_mode, group, sub_cfg) local contents = split(content, "@") local auth = contents[1] or "" local idx = auth:find(":", 1, true) - if not idx then --修正某些链接会把username和password之间的:进行编码 - auth = UrlDecode(auth) - idx = auth:find(":", 1, true) - end + -- 先按明文冒号拆分,再分别解码;没有分隔符时整个 userinfo 是密码。 if idx then result.username = UrlDecode(auth:sub(1, idx - 1)) result.password = UrlDecode(auth:sub(idx + 1)) + else + result.username = "" + result.password = UrlDecode(auth) end Info = (contents[2] or ""):gsub("/%?", "?") end diff --git a/luci-app-passwall2/Makefile b/luci-app-passwall2/Makefile index 14137ba5b..a22d6df3a 100644 --- a/luci-app-passwall2/Makefile +++ b/luci-app-passwall2/Makefile @@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-passwall2 PKG_VERSION:=26.11.3 -PKG_RELEASE:=169 +PKG_RELEASE:=170 PKG_PO_VERSION:=$(PKG_VERSION) PKG_CONFIG_DEPENDS:= \ diff --git a/luci-app-passwall2/luasrc/view/passwall2/node_config/link_share_man.htm b/luci-app-passwall2/luasrc/view/passwall2/node_config/link_share_man.htm index e9cfa3500..e3a748bc3 100644 --- a/luci-app-passwall2/luasrc/view/passwall2/node_config/link_share_man.htm +++ b/luci-app-passwall2/luasrc/view/passwall2/node_config/link_share_man.htm @@ -961,19 +961,6 @@ local current_node = map:get(section) } } - // Determine if the password is URL encoded - const isURLEncodedPassword = function(pwd) { - if (!/%[0-9A-Fa-f]{2}/.test(pwd)) return false; - try { - const decoded = decodeURIComponent(pwd.replace(/\+/g, "%20")); - const reencoded = encodeURIComponent(decoded); - return reencoded === pwd; - } catch (e) { - return false; - } - } - password = isURLEncodedPassword(password) ? decodeURIComponent(password) : password; - if (queryParam.plugin) { var pluginParams = decodeURIComponent(queryParam.plugin).split(";"); plugin = pluginParams.shift(); @@ -1821,13 +1808,10 @@ local current_node = map:get(section) var port = _parsedUrl.port; var search = _parsedUrl.search; var hash = _parsedUrl.hash; - if (!username || !password) { //某些链接会把username和password之间的:进行编码 - const decoded = decodeURIComponent(username || password || ""); - const i = decoded.indexOf(":"); - if (i > -1) { - username = decoded.slice(0, i); - password = decoded.slice(i + 1); - } + // URL splits on literal colons; without a separator, the entire userinfo is the password. + if (link_split[1].split("@", 1)[0].indexOf(":") === -1) { + password = username; + username = ""; } opt.set(dom_prefix + 'username', decodeURIComponent(username)); opt.set(dom_prefix + 'password', decodeURIComponent(password)); diff --git a/luci-app-passwall2/root/usr/share/passwall2/subscribe.lua b/luci-app-passwall2/root/usr/share/passwall2/subscribe.lua index 949f3f2e7..323d040e8 100755 --- a/luci-app-passwall2/root/usr/share/passwall2/subscribe.lua +++ b/luci-app-passwall2/root/usr/share/passwall2/subscribe.lua @@ -1197,20 +1197,6 @@ local function processData(szType, content, add_mode, group, sub_cfg) password = hostInfo[1] -- Some links use plaintext UUIDs as passwords. end - -- Determine if the password is URL encoded - local function isURLEncodedPassword(pwd) - if not pwd:find("%%[0-9A-Fa-f][0-9A-Fa-f]") then - return false - end - local ok, decoded = pcall(UrlDecode, pwd) - return ok and UrlEncode(decoded) == pwd - end - - local decoded = UrlDecode(password) - if isURLEncodedPassword(password) and decoded then - password = decoded - end - local _method = (method or "none"):lower() method = (_method == "chacha20-poly1305" and "chacha20-ietf-poly1305") or (_method == "xchacha20-poly1305" and "xchacha20-ietf-poly1305") or _method @@ -2039,13 +2025,13 @@ local function processData(szType, content, add_mode, group, sub_cfg) local contents = split(content, "@") local auth = contents[1] or "" local idx = auth:find(":", 1, true) - if not idx then -- Fixed some links that encode the colon between username and password. - auth = UrlDecode(auth) - idx = auth:find(":", 1, true) - end + -- Split on the literal colon before decoding; without a separator, the entire userinfo is the password. if idx then result.username = UrlDecode(auth:sub(1, idx - 1)) result.password = UrlDecode(auth:sub(idx + 1)) + else + result.username = "" + result.password = UrlDecode(auth) end Info = (contents[2] or ""):gsub("/%?", "?") end diff --git a/luci-app-qemu-vms/Makefile b/luci-app-qemu-vms/Makefile index aaf35ba5d..40dbffb87 100644 --- a/luci-app-qemu-vms/Makefile +++ b/luci-app-qemu-vms/Makefile @@ -4,7 +4,7 @@ LUCI_TITLE:=Web UI for QEMU Virtual Machine Simple (VMS) LUCI_DEPENDS:=@TARGET_x86_64 +qemu-vms +ttyd +usbutils +pciutils PKG_LICENSE:=GPLv3 PKG_VERSION:=0.0.2 -PKG_RELEASE:=11 +PKG_RELEASE:=12 include $(TOPDIR)/feeds/luci/luci.mk diff --git a/luci-app-qemu-vms/root/usr/libexec/rpcd/luci.qemu-vms b/luci-app-qemu-vms/root/usr/libexec/rpcd/luci.qemu-vms index 4dfc19477..bc1952f9b 100755 --- a/luci-app-qemu-vms/root/usr/libexec/rpcd/luci.qemu-vms +++ b/luci-app-qemu-vms/root/usr/libexec/rpcd/luci.qemu-vms @@ -116,7 +116,7 @@ console_open() { return } - ttyd -p "$port" -o /usr/bin/vm-console "$vm" >/dev/null 2>&1 & + ttyd -W -p "$port" -o /usr/bin/vm-console "$vm" >/dev/null 2>&1 & echo $! > "$pidfile" echo "$port" > "$portfile" diff --git a/luci-app-smartsafehub/CHANGELOG.md b/luci-app-smartsafehub/CHANGELOG.md index b8b598c75..2e51a62dd 100644 --- a/luci-app-smartsafehub/CHANGELOG.md +++ b/luci-app-smartsafehub/CHANGELOG.md @@ -1,9 +1,27 @@ # 변경 기록 +## [0.2.33-r1] - 2026-11-06 + +### 개선 +- 펌웨어 업데이트 후에 기존의 앱이 복원이 될 수 있도록 개선하였습니다. + + +## [0.2.32-r2] - 2026-11-02 + +### 수정 +- 펌웨어를 업데이트한 뒤 앱 버전이 바뀌었을 때 기존 앱이 자동으로 다시 설치되지 않던 문제를 수정했습니다. +- 앱을 복원할 때 공유기의 연결 추적 기능(conntrack)과 기존 시스템 패키지를 변경하지 않도록 보호했습니다. + +### 개선 +- 이전에 설치했던 앱만 새 펌웨어에서 승인된 버전으로 안전하게 복원합니다. + +### 테스트 +- 버전 변경 후 복원, 승인되지 않은 앱 차단, 기존 패키지 보호를 확인했습니다. + + ## [0.2.32-r1] - 2026-11-02 ### 추가 - - Network Watch 기능을 출시하였습니다. 의심 징후를 탐지하고 감지하고 알립니다. ### 개선 diff --git a/luci-app-smartsafehub/Makefile b/luci-app-smartsafehub/Makefile index eb2956a7d..acc0728a3 100644 --- a/luci-app-smartsafehub/Makefile +++ b/luci-app-smartsafehub/Makefile @@ -6,8 +6,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-smartsafehub -PKG_VERSION:=0.2.32 -PKG_RELEASE:=12 +PKG_VERSION:=0.2.33 +PKG_RELEASE:=13 PKG_MAINTAINER:=Beomjun Kang PKG_LICENSE:=GPL-3.0-or-later diff --git a/luci-app-smartsafehub/frontend/package-lock.json b/luci-app-smartsafehub/frontend/package-lock.json index 29bc9541d..2fe7ab69b 100644 --- a/luci-app-smartsafehub/frontend/package-lock.json +++ b/luci-app-smartsafehub/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "luci-app-smartsafehub-frontend", - "version": "0.2.32", + "version": "0.2.33", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "luci-app-smartsafehub-frontend", - "version": "0.2.32", + "version": "0.2.33", "dependencies": { "chart.js": "4.6.1", "preact": "11.1.0" diff --git a/luci-app-smartsafehub/frontend/package.json b/luci-app-smartsafehub/frontend/package.json index 81fd0292d..28ca0bdbe 100644 --- a/luci-app-smartsafehub/frontend/package.json +++ b/luci-app-smartsafehub/frontend/package.json @@ -1,7 +1,7 @@ { "name": "luci-app-smartsafehub-frontend", "private": true, - "version": "0.2.32", + "version": "0.2.33", "type": "module", "engines": { "node": ">=24.0" diff --git a/luci-app-smartsafehub/frontend/src/main.tsx b/luci-app-smartsafehub/frontend/src/main.tsx index e37550935..9904d7a2e 100644 --- a/luci-app-smartsafehub/frontend/src/main.tsx +++ b/luci-app-smartsafehub/frontend/src/main.tsx @@ -44,7 +44,7 @@ function installBootstrap(sessionId: string, host: HTMLElement): void { sessionId, rpcUrl: luciUrl('/admin/ubus'), assetBase: host.dataset.assetBase ?? '/luci-static/smartsafehub/', - assetVersion: host.dataset.assetVersion ?? '0.2.32-r1', + assetVersion: host.dataset.assetVersion ?? '0.2.33-r1', locale: document.documentElement.lang || 'ko', }); } diff --git a/luci-app-smartsafehub/root/usr/libexec/smartsafehub-apps-manager b/luci-app-smartsafehub/root/usr/libexec/smartsafehub-apps-manager index ca13e0ad4..a0ea5c81b 100755 --- a/luci-app-smartsafehub/root/usr/libexec/smartsafehub-apps-manager +++ b/luci-app-smartsafehub/root/usr/libexec/smartsafehub-apps-manager @@ -45,7 +45,7 @@ valid_sha() { } protected_package() { case "$1" in - base-files|busybox|apk|procd|netifd|dnsmasq*|firewall*|odhcp*|dropbear|ubus*|rpcd*|uhttpd*|safeshield*|luci-app-smartsafehub|kernel|kmod-*) return 0;; + base-files|busybox|apk|procd|netifd|conntrack|dnsmasq*|firewall*|odhcp*|dropbear|ubus*|rpcd*|uhttpd*|safeshield*|luci-app-smartsafehub|kernel|kmod-*) return 0;; esac return 1 } @@ -650,20 +650,45 @@ prepare_signed_index() { install_app() { preflight_install || return $? manifest="$(owned_manifest "$app")" - # Existing app ownership must match the reviewed pins. - if [ -e "$manifest" ]; then - [ -s "$manifest" ] || return 28 + # Old sysupgrade ownership proves which package *names* Apps managed, + # not which version/hash a newly published firmware has approved. A restore + # may re-pin missing owned APKs only after validating the exact new catalog. + # A user install and every already-installed APK remain strict: no adoption, + # implicit upgrade, downgrade or replacement of existing system packages. + owned_repin=0 + owned_approved="$stage/owned-approved" + : > "$owned_approved" + if [ -e "$manifest" ] || [ -L "$manifest" ]; then + [ -f "$manifest" ] && [ ! -L "$manifest" ] && [ -s "$manifest" ] || return 28 + owned_count=0 while IFS="$(printf '\t')" read -r name version hash; do valid_package "$name" && valid_version "$version" && valid_sha "$hash" || return 28 + protected_package "$name" && return 28 + shared_travelmate_dep "$name" && return 28 + # Duplicate or unapproved ownership must not authorize even a missing APK. + awk -F '\t' -v name="$name" '$1 == name { found=1 } END { exit !found }' "$owned_approved" && return 28 + reviewed_pin="$(awk -F '\t' -v name="$name" '$1 == name {print; exit}' "$pins")" + [ -n "$reviewed_pin" ] || return 28 pin="$(printf '%s\t%s\t%s' "$name" "$version" "$hash")" - grep -Fqx "$pin" "$pins" || return 28 + if [ "$pin" != "$reviewed_pin" ]; then + [ "$source" = restore ] || return 28 + # An old installed version must never be silently upgraded or adopted. + installed "$name" && return 28 + owned_repin=1 + fi + printf '%s\n' "$reviewed_pin" >> "$owned_approved" + owned_count=$((owned_count + 1)); [ "$owned_count" -le 32 ] || return 28 done < "$manifest" + # read skips an unterminated last line; reject truncated ownership instead + # of treating it as permission for a different approved package set. + [ "$owned_count" -gt 0 ] && + [ "$owned_count" -eq "$(awk 'END {print NR+0}' "$manifest")" ] || return 28 fi need_install=0 # Never adopt a package previously installed outside SmartSafeHub Apps. while IFS="$(printf '\t')" read -r name version hash; do pin="$(printf '%s\t%s\t%s' "$name" "$version" "$hash")" - if [ -e "$manifest" ] && grep -Fqx "$pin" "$manifest"; then + if [ -e "$manifest" ] && grep -Fqx "$pin" "$owned_approved"; then installed "$name" || need_install=1 elif shared_travelmate_dep "$name" && installed "$name"; then # A system package was already present. Never adopt it or silently @@ -687,6 +712,11 @@ install_app() { prepare_signed_index || return $? "$APK" --repositories-file /dev/null -X "$local_index" add --simulate "$@" > "$stage/plan.log" 2>&1 || return 34 review_install_plan "$stage/plan.log" "$base_names" || return 34 + # Only after index, archive hash and simulated install are validated may + # missing packages be re-pinned. Keep the change atomic and retry-safe. + if [ "$owned_repin" -eq 1 ]; then + cp "$owned_approved" "$manifest.new" && mv -f "$manifest.new" "$manifest" || return 36 + fi # Write ownership immediately before modifying packages. If APK partially # succeeds, the UI offers repair/removal without adopting unrelated packages. while IFS="$(printf '\t')" read -r name version hash; do @@ -766,7 +796,7 @@ remove_app() { "$APK" del --simulate "$@" > "$stage/plan.log" 2>&1 || return 44 # Removing an optional app must never implicitly remove system/network, # SafeShield or firmware kernel-module packages. - if grep -Ei '(Purging|Removing|Upgrading|Downgrading) (base-files|busybox|apk|procd|netifd|dnsmasq[^ ]*|firewall[^ ]*|odhcp[^ ]*|dropbear|ubus[^ ]*|rpcd[^ ]*|uhttpd[^ ]*|safeshield[^ ]*|luci-app-smartsafehub|kernel|kmod-[^ ]*) ' "$stage/plan.log" >/dev/null; then + if grep -Ei '(Purging|Removing|Upgrading|Downgrading) (base-files|busybox|apk|procd|netifd|conntrack|dnsmasq[^ ]*|firewall[^ ]*|odhcp[^ ]*|dropbear|ubus[^ ]*|rpcd[^ ]*|uhttpd[^ ]*|safeshield[^ ]*|luci-app-smartsafehub|kernel|kmod-[^ ]*) ' "$stage/plan.log" >/dev/null; then return 44 fi review_remove_plan "$stage/plan.log" "$*" || return 44 diff --git a/luci-app-smartsafehub/root/usr/share/ucode/luci/template/smartsafehub/login.ut b/luci-app-smartsafehub/root/usr/share/ucode/luci/template/smartsafehub/login.ut index de83934f7..b50624477 100644 --- a/luci-app-smartsafehub/root/usr/share/ucode/luci/template/smartsafehub/login.ut +++ b/luci-app-smartsafehub/root/usr/share/ucode/luci/template/smartsafehub/login.ut @@ -10,16 +10,16 @@ SmartSafeHub - +
- + diff --git a/luci-app-smartsafehub/root/www/luci-static/smartsafehub/app.js b/luci-app-smartsafehub/root/www/luci-static/smartsafehub/app.js index 6a637ac31..79b2f2222 100644 --- a/luci-app-smartsafehub/root/www/luci-static/smartsafehub/app.js +++ b/luci-app-smartsafehub/root/www/luci-static/smartsafehub/app.js @@ -1,3 +1,3 @@ var e,t,n,r,i,a,o,s,c,l,u=Symbol.for(`pv`),d={},f=[],p=/^m(i|n|o|s|text|space)$/,m=Array.isArray,h=f.slice,g=Object.assign;function _(e){e&&e.parentNode&&e.remove()}function v(e,t,n){var r,i,a,o={},s=arguments.length;for(a in t)a==`key`?r=t[a]:a==`ref`&&typeof e!=`function`?i=t[a]:o[a]=t[a];return s>2&&(o.children=s>3?h.call(arguments,2):n),y(e,o,r,i,null)}function y(n,r,i,a,o){var s={type:n,props:r,key:i,ref:a,__k:null,__:null,__b:0,__e:null,__c:null,constructor:u,__v:o||++t,__i:-1,__u:0};return!o&&e.vnode&&e.vnode(s),s}function b(e){return e.children}function x(e,t){this.props=e,this.context=t,this.__g=0}function S(e,t){if(t==null)return e.__&&S(e.__,e.__i+1);for(var n;to&&n.sort(a),t=n.shift(),o=n.length,8&t.__g&&(c=void 0,l=void 0,d=(l=(s=t).__v).__e,f=[],p=[],(m=s.__P)&&((c=g({constructor:u},l)).__v=l.__v+1,e.vnode&&e.vnode(c),P(m,c,l,s.__n,m.namespaceURI,32&l.__u?[d]:null,f,d||S(l),32&l.__u,p),c.__v=l.__v,c.__.__k[c.__i]=c,ee(f,c,p),l.__=l.__e=null,c.__e!=d&&C(c)))}finally{(i=n.length)&&(r||queueMicrotask)(T)}}function E(e,t,n,r,i,a,o,s,c,l,u){var p,m,h,g,_,v,y=r.__k||f,b=t.length;for(c=D(n,t,y,c,b),p=0;pc?v--:v++,x=1)):(~l||(i>g?v--:i>1]+!!c){for(i=n-1;i>=0;i--)if((s=t[i])&&!(2&s.__u)&&a==s.key&&o==s.type)return i;for(i=n+1;i=0?e.slice(0,t+13):pe}function he(){let e=me();return e.endsWith(`/luci`)?e.slice(0,-5):fe}function ge(e){let t=e.startsWith(`/`)?e:`/${e}`;return`${he()}${t}`}function _e(e){let t=e.startsWith(`/`)?e:`/${e}`;return`${me()}${t}`}function ve(e){return _e(e)}function ye(e=``){return`/${e?e.startsWith(`#`)?e:`#${e}`:``}`}var be=/^[0-9a-f]{32}$/i;function xe(){return _e(`/smartsafehub/session`)}function Se(e){return e.headers.get(`X-LuCI-Login-Required`)===`yes`}async function Ce(e){if(e.status===401||e.status===403||Se(e))return null;if(!e.ok)throw Error(`LuCI session endpoint returned HTTP ${e.status}`);let t=(await e.text()).trim();if(be.test(t))return t;if(e.redirected||/^(?:access|permission)\s+denied\.?$/i.test(t)||/^)/i.test(t))return null;throw Error(`LuCI session endpoint returned an invalid session id`)}async function we(e={}){return Ce(await fetch(xe(),{cache:`no-store`,credentials:`same-origin`,redirect:`follow`,...e}))}function Te(){return we()}async function Ee(){await fetch(_e(`/admin/logout`),{cache:`no-store`,credentials:`same-origin`,redirect:`manual`})}function De(e,t){let n=new URLSearchParams;return n.set(`luci_username`,e),n.set(`luci_password`,t),we({method:`POST`,headers:{"Content-Type":`application/x-www-form-urlencoded;charset=UTF-8`},body:n})}var Oe=`smartsafehub:session-expired`,ke=`로그인 세션이 만료되었습니다. 계속하려면 다시 로그인해 주세요.`,Ae=null;function je(){Ae=null}function Me(e){Ae!==e&&(Ae=e,window.dispatchEvent(new CustomEvent(Oe)))}var Ne,Pe,Fe,Ie,Le=Object.is,Re=0,ze=[],Be=[],Ve=e,He=Ve.__b,Ue=Ve.__r,We=Ve.diffed,Ge=Ve.__c,Ke=Ve.unmount,qe=Ve.__;function Je(e,t){Ve.__h&&Ve.__h(Pe,e,Re||t),Re=0;var n=Pe.__H||(Pe.__H={__:[],__h:[]});return e>=n.__.length&&n.__.push({}),n.__[e]}function L(e){return Re=1,Ye(at,e)}function Ye(e,t,n){var r=Je(Ne++,2);if(r.t=e,!r.__c&&(r.__=[n?n(t):at(void 0,t),function(e){var t=r.__N?r.__N[0]:r.__[0],n=r.t(t,e);Le(t,n)||(r.__N=[n,r.__[1]],r.__c.setState({}))}],r.__c=Pe,!Pe.__f)){Pe.__f=!0;var i=Pe.shouldComponentUpdate;Pe.shouldComponentUpdate=function(e,t,n){var r=this.__H;if(!r)return!0;var a=!1,o=this.props!=e;if(r.__.some(function(e){e.__N&&(a=!0,Le(e.__[0],e.__N[0])||(o=!0))}),i){var s=i.call(this,e,t,n);return a?s||o:s}return!a||o}}return r.__}function R(e,t){var n=Je(Ne++,3);!Ve.__s&&it(n.__H,t)&&(n.__P=!0,n.__=e,n.u=t,Pe.__H.__h.push(n))}function z(e){return Re=5,Xe(function(){return{current:e}},[])}function Xe(e,t){var n=Je(Ne++,7);return it(n.__H,t)&&(n.__=e(),n.__H=t),n.__}function B(e,t){return Re=8,Xe(function(){return e},t)}function Ze(){var e=Je(Ne++,11);if(!e.__){for(var t=Pe.__v;!t.__m&&t.__;)t=t.__;var n=t.__m||(t.__m=[0,0]);e.__=`P`+n[0]+`-`+n[1]++}return e.__}function Qe(){var e;do{for(;e=Be.shift();)try{nt(e)}catch(t){Ve.__e(t,{__:(e=e.__P)&&e.__v})}for(;e=ze.shift();){var t=e.__H;if(e.__P&&t)try{t.__h.some(nt),t.__h.some(rt),t.__h=[]}catch(n){t.__h=[],Ve.__e(n,e.__v)}}}while(Be.length)}Ve.__b=function(e){Pe=null,He&&He(e)},Ve.__=function(e,t){e&&t.__k&&t.__k.__m&&(e.__m=t.__k.__m),qe&&qe(e,t)},Ve.__r=function(e){Ue&&Ue(e),Ne=0;var t=(Pe=e.__c).__H;t&&(Fe==Pe?Pe.__h=[]:(t.__h.some(nt),t.__h.some(rt),Ne=0),t.__h=[],t.__.some(function(e){e.__N&&(e.__=e.__N),e.u=e.__N=void 0})),Fe=Pe},Ve.diffed=function(e){We&&We(e);var t=e.__c;t&&t.__H&&(t.__H.__h.length&&tt(ze.push(t)),t.__H.__.some(function(e){e.u&&(e.__H=e.u)})),Fe=Pe=null},Ve.__c=function(e,t){t.some(function(e){try{e.__h.some(nt),e.__h=e.__h.filter(function(e){return!e.__||rt(e)})}catch(t){e.__h=[function(){throw t}]}}),Ge&&Ge(e,t)},Ve.unmount=function(e){Ke&&Ke(e);var t,n,r=e.__c;r&&r.__H&&(r.__H.__.some(function(r){try{if(r.__P&&r.__c){if(n===void 0){for(n=e.__;n&&(!n.__c||!n.__c.__P);)n=n.__;n=n&&n.__c}r.__P=n,tt(Be.push(r))}else nt(r)}catch(e){t=e}}),r.__H=void 0,t&&Ve.__e(t,r.__v))};var $e=typeof requestAnimationFrame==`function`;function et(e){var t,n=function(){clearTimeout(r),$e&&cancelAnimationFrame(t),setTimeout(e)},r=setTimeout(n,35);$e&&(t=requestAnimationFrame(n))}function tt(e){e!=1&&Ie==Ve.requestAnimationFrame||((Ie=Ve.requestAnimationFrame)||et)(Qe)}function nt(e){var t=Pe,n=e.__c;typeof n==`function`&&(e.__c=void 0,n()),Pe=t}function rt(e){var t=Pe;e.__c=e.__(),Pe=t}function it(e,t){return!e||e.length!=t.length||t.some(function(t,n){return!Le(t,e[n])})}function at(e,t){return typeof t==`function`?t(e):t}var ot=1500,st=60,ct=64,lt=new Map,ut=[];function dt(e){return e===null?null:Math.max(0,Math.round(e))}function ft(e){let t=`${e.object}.${e.method}`,n=dt(e.elapsedMs)??0,r=n>=ot,i=e.result!==`ok`,a=lt.get(t);return!a&<.sizest&&ut.shift()),r}function pt(){return{slowThresholdMs:ot,summary:[...lt.values()].map(({totalMs:e,...t})=>({...t})),recent:ut.map(e=>({...e}))}}function mt(){lt.clear(),ut.length=0}var ht={snapshot:pt,reset:mt};function gt(){var e;(e=window).__SMARTHUB_RPC_DIAGNOSTICS__??(e.__SMARTHUB_RPC_DIAGNOSTICS__=ht)}var _t=4,vt=3,yt=0,bt=0,xt=[];function St(){for(;yt<_t;){let e=xt.findIndex(e=>e.priority!==`low`||bt{--yt,t.priority===`low`&&--bt,St()}))}}function Ct(e,t){return t?.aborted?Promise.reject(new DOMException(`Cancelled`,`AbortError`)):new Promise((n,r)=>{let i={priority:e,start:e=>{t?.removeEventListener(`abort`,i.abort),n(e)},abort:()=>{let e=xt.indexOf(i);e!==-1&&xt.splice(e,1),t?.removeEventListener(`abort`,i.abort),r(new DOMException(`Cancelled`,`AbortError`))}};t?.addEventListener(`abort`,i.abort,{once:!0});let a=xt.findIndex(t=>e===`high`&&t.priority!==`high`||e===`normal`&&t.priority===`low`);a===-1?xt.push(i):xt.splice(a,0,i),St()})}function wt(e){"@babel/helpers - typeof";return wt=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},wt(e)}function Tt(e,t){if(wt(e)!=`object`||!e)return e;var n=e[Symbol.toPrimitive];if(n!==void 0){var r=n.call(e,t||`default`);if(wt(r)!=`object`)return r;throw TypeError(`@@toPrimitive must return a primitive value.`)}return(t===`string`?String:Number)(e)}function Et(e){var t=Tt(e,`string`);return wt(t)==`symbol`?t:t+``}function V(e,t,n){return(t=Et(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}var Dt={1:`잘못된 명령입니다.`,2:`요청 인자가 올바르지 않습니다.`,3:`요청한 메서드를 찾을 수 없습니다.`,4:`요청한 리소스를 찾을 수 없습니다.`,5:`응답 데이터가 없습니다.`,6:`접근 권한이 없습니다.`,7:`요청 시간이 초과되었습니다.`,8:`지원하지 않는 기능입니다.`,9:`장치에서 알 수 없는 오류가 발생했습니다.`,10:`장치 연결이 끊어졌습니다.`},Ot=2e4,kt=3e3,At=1e3,jt=6e4,Mt=1,H=class extends Error{constructor(e,t){super(t),V(this,`code`,void 0),this.name=`RpcError`,this.code=e}};function Nt(){let e=window.__SMARTHUB_BOOTSTRAP__;if(!e?.sessionId||!e.rpcUrl)throw new H(`BOOTSTRAP_MISSING`,`LuCI 세션 정보를 찾을 수 없습니다. 페이지를 새로고침해 주세요.`);return e}function Pt(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function Ft(){return new H(`INVALID_RESPONSE`,`장치 API가 올바르지 않은 응답을 반환했습니다.`)}function It(e){return typeof e!=`number`||!Number.isFinite(e)?Ot:Math.min(jt,Math.max(At,Math.trunc(e)))}function Lt(e){let t=e.message.trim().toLowerCase();return e.code===`UBUS_6`||e.code===`JSON_RPC_-32002`||e.code===`HTTP_ERROR`&&/HTTP (401|403)\b/.test(e.message)||t.includes(`access denied`)||t.includes(`permission denied`)||t.includes(`접근 권한`)}function Rt(e){return window.__SMARTHUB_BOOTSTRAP__?.sessionId===e}async function zt(e){let t=Mt++,n=new AbortController,r=window.setTimeout(()=>n.abort(),kt);try{let r=await fetch(e.rpcUrl,{method:`POST`,credentials:`same-origin`,headers:{"Content-Type":`application/json`},body:JSON.stringify({jsonrpc:`2.0`,id:t,method:`call`,params:[e.sessionId,`smartsafehub`,`system_root_password_status`,{}]}),signal:n.signal});if(r.status===401||r.status===403)return`expired`;if(!r.ok)return`unknown`;let i=await r.json();if(!Pt(i)||i.jsonrpc!==`2.0`||i.id!==t)return`unknown`;if(`error`in i){let e=i.error;return Pt(e)&&typeof e.code==`number`&&e.code===-32002?`expired`:`unknown`}let a=i.result;return!Array.isArray(a)||a.length<1?`unknown`:a[0]===0?`active`:a[0]===6?`expired`:`unknown`}catch{return`unknown`}finally{window.clearTimeout(r)}}async function Bt(e,t){if(!Lt(e)||!Rt(t.sessionId))return e;let n=await zt(t);return Rt(t.sessionId)?n===`active`?new H(`RPC_PERMISSION_DENIED`,`현재 로그인 세션에 이 기능 권한이 없습니다. SmartSafeHub 업데이트 직후라면 로그아웃 후 다시 로그인해 주세요.`):n===`expired`?(Me(t.sessionId),new H(`SESSION_EXPIRED`,ke)):e:e}function Vt(e,t){if(!Pt(e)||e.jsonrpc!==`2.0`||e.id!==t)throw Ft();if(`error`in e){let t=e.error;throw!Pt(t)||typeof t.code!=`number`?Ft():new H(`JSON_RPC_${t.code}`,typeof t.message==`string`&&t.message.length>0?t.message:`JSON-RPC 요청에 실패했습니다.`)}let n=e.result;if(!Array.isArray(n)||n.length<1||n.length>2)throw Ft();let r=n[0];if(typeof r!=`number`||!Number.isInteger(r))throw Ft();if(r!==0)throw new H(`UBUS_${r}`,Dt[r]??`ubus 오류 코드 ${r}가 반환되었습니다.`);if(n.length<2||n[1]===void 0)throw new H(`EMPTY_RESPONSE`,`장치 API 응답이 비어 있습니다.`);return n[1]}async function Ht(e,t,n={},r={}){let i=Nt();gt();let a=performance.now(),o=null,s=null,c=`ok`,l=Mt++,u=null,d=null,f=new AbortController,p=!1,m=()=>{p=!0,f.abort()};r.signal?.addEventListener(`abort`,m,{once:!0});try{if(u=await Ct(r.priority??`normal`,r.signal),p||r.signal?.aborted)throw new H(`RPC_CANCELLED`,`요청이 취소되었습니다.`);d=window.setTimeout(()=>f.abort(),It(r.timeoutMs));let c=await fetch(i.rpcUrl,{method:`POST`,credentials:`same-origin`,headers:{"Content-Type":`application/json`},body:JSON.stringify({jsonrpc:`2.0`,id:l,method:`call`,params:[i.sessionId,e,t,n]}),signal:f.signal});if(o=performance.now()-a,!c.ok)throw new H(`HTTP_ERROR`,`장치 API가 HTTP ${c.status} 오류를 반환했습니다.`);let m;try{m=await c.json(),s=performance.now()-a-(o??0)}catch{throw Ft()}return Vt(m,l)}catch(e){if(p||r.signal?.aborted)throw c=`RPC_CANCELLED`,new H(`RPC_CANCELLED`,`요청이 취소되었습니다.`);if(f.signal.aborted)throw c=`RPC_TIMEOUT`,new H(`RPC_TIMEOUT`,`장치 응답 시간이 초과되었습니다. 잠시 후 다시 시도해 주세요.`);if(e instanceof DOMException&&e.name===`AbortError`)throw c=`RPC_CANCELLED`,new H(`RPC_CANCELLED`,`요청이 취소되었습니다.`);if(e instanceof H){let t=await Bt(e,i);throw c=t.code,t}throw c=`NETWORK_ERROR`,new H(`NETWORK_ERROR`,`공유기와 통신할 수 없습니다. 네트워크 연결을 확인해 주세요.`)}finally{d!==null&&window.clearTimeout(d),r.signal?.removeEventListener(`abort`,m),u?.();let n=Math.round(performance.now()-a);(ft({object:e,method:t,elapsedMs:n,responseMs:o,decodeMs:s,result:c})||c===`RPC_TIMEOUT`)&&console.warn(`[SmartSafeHub RPC] ${e}.${t}: ${n}ms (${c})`)}}async function U(e,t,n={},r={}){let i=await Ht(e,t,n,r);if(!i.ok)throw new H(i.error.code,i.error.message);return i.data}function Ut(){return U(`smartsafehub`,`system_root_password_status`)}function Wt(e){return U(`smartsafehub`,`system_root_password_set`,{password:e})}var Gt=Symbol.for(`pv`),Kt=0;Array.isArray;function W(t,n,r,i,a,o){n||(n={});var s,c,l=n;if(`ref`in l&&typeof t!=`function`)for(c in l={},n)c==`ref`?s=n[c]:l[c]=n[c];var u={type:t,props:l,key:r,ref:s,__k:null,__:null,__b:0,__e:null,__c:null,constructor:Gt,__v:--Kt,__i:-1,__u:0};return(a||o)&&(u.__source=a,u.__self=o),e.vnode&&e.vnode(u),u}function G({children:e,title:t,...n}){return W(`svg`,{"aria-hidden":!t||void 0,fill:`none`,role:t?`img`:void 0,stroke:`currentColor`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"stroke-width":`1.8`,viewBox:`0 0 24 24`,...n,children:[t?W(`title`,{children:t}):null,e]})}function qt(e){return W(G,{...e,children:[W(`path`,{d:`M12 3 5 6v5c0 4.6 2.8 8.1 7 10 4.2-1.9 7-5.4 7-10V6l-7-3Z`}),W(`path`,{d:`m9 12 2 2 4-4`})]})}function Jt(e){return W(G,{...e,children:[W(`rect`,{height:`7`,rx:`2`,width:`18`,x:`3`,y:`13`}),W(`path`,{d:`M7 16.5h.01M11 16.5h.01M7 10a7 7 0 0 1 10 0M9.5 7.5a3.5 3.5 0 0 1 5 0`})]})}function Yt(e){return W(G,{...e,children:[W(`rect`,{height:`11`,rx:`2`,width:`16`,x:`4`,y:`3`}),W(`path`,{d:`M8 21h8M12 14v7`})]})}function Xt(e){return W(G,{...e,children:[W(`path`,{d:`m3 11 9-8 9 8`}),W(`path`,{d:`M5 10v10h14V10M9 20v-6h6v6`})]})}function Zt(e){return W(G,{...e,children:[W(`circle`,{cx:`12`,cy:`8`,r:`4`}),W(`path`,{d:`M4.5 21a7.5 7.5 0 0 1 15 0`})]})}function Qt(e){return W(G,{...e,children:[W(`rect`,{x:`3`,y:`3`,width:`7`,height:`7`,rx:`1.5`}),W(`rect`,{x:`14`,y:`3`,width:`7`,height:`7`,rx:`1.5`}),W(`rect`,{x:`3`,y:`14`,width:`7`,height:`7`,rx:`1.5`}),W(`path`,{d:`M17.5 14v7M14 17.5h7`})]})}function $t(e){return W(G,{...e,children:[W(`rect`,{x:`2.5`,y:`3.5`,width:`7`,height:`6`,rx:`1.5`}),W(`rect`,{x:`14.5`,y:`3.5`,width:`7`,height:`6`,rx:`1.5`}),W(`rect`,{x:`8.5`,y:`15`,width:`7`,height:`6`,rx:`1.5`}),W(`path`,{d:`M6 9.5v2.5h12V9.5M12 12v3`})]})}function en(e){return W(G,{...e,children:[W(`path`,{d:`M7.5 7h9a5 5 0 0 1 4.9 4l.8 5a2.5 2.5 0 0 1-4 2.3L15.5 16h-7l-2.7 2.3a2.5 2.5 0 0 1-4-2.3l.8-5A5 5 0 0 1 7.5 7Z`}),W(`path`,{d:`M6.5 11v4M4.5 13h4M16 11.5h.01M18.5 13.5h.01`})]})}function tn(e){return W(G,{...e,children:W(`path`,{d:`M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.287l-7.91 7.91a2.121 2.121 0 0 1-3-3l7.91-7.91a6 6 0 0 1 7.287-8.259c.438.12.54.662.218.984z`})})}function nn(e){return W(G,{...e,children:[W(`circle`,{cx:`12`,cy:`12`,r:`10`}),W(`path`,{d:`M8 12h.01M12 12h.01M16 12h.01`})]})}function rn(e){return W(G,{...e,children:[W(`path`,{d:`M12 16V4`}),W(`path`,{d:`m7 9 5-5 5 5`}),W(`path`,{d:`M5 14v5h14v-5`})]})}function an(e){return W(G,{...e,children:[W(`circle`,{cx:`12`,cy:`12`,r:`3`}),W(`path`,{d:`M19.4 15a1.7 1.7 0 0 0 .3 1.9l.1.1-2.8 2.8-.1-.1a1.7 1.7 0 0 0-1.9-.3 1.7 1.7 0 0 0-1 1.6v.2h-4V21a1.7 1.7 0 0 0-1-1.6 1.7 1.7 0 0 0-1.9.3l-.1.1L4.2 17l.1-.1a1.7 1.7 0 0 0 .3-1.9A1.7 1.7 0 0 0 3 14H2.8v-4H3a1.7 1.7 0 0 0 1.6-1 1.7 1.7 0 0 0-.3-1.9L4.2 7 7 4.2l.1.1a1.7 1.7 0 0 0 1.9.3A1.7 1.7 0 0 0 10 3V2.8h4V3a1.7 1.7 0 0 0 1 1.6 1.7 1.7 0 0 0 1.9-.3l.1-.1L19.8 7l-.1.1a1.7 1.7 0 0 0-.3 1.9 1.7 1.7 0 0 0 1.6 1h.2v4H21a1.7 1.7 0 0 0-1.6 1Z`})]})}function on(e){return W(G,{...e,children:[W(`path`,{d:`M5 9.5a10 10 0 0 1 14 0M8 13a6 6 0 0 1 8 0M11 16.5a2 2 0 0 1 2 0`}),W(`path`,{d:`M12 20h.01`})]})}function sn(e){return W(G,{...e,children:[W(`path`,{d:`M4 4h6v6H4zM14 4h6v6h-6zM4 14h6v6H4z`}),W(`path`,{d:`M6.5 6.5h1M16.5 6.5h1M6.5 16.5h1`}),W(`path`,{d:`M14 14h2v2h-2zM18 14h2v2h-2zM16 16h2v2h-2zM18 18h2v2h-2zM14 18h1`})]})}function cn(e){return W(G,{...e,children:[W(`path`,{d:`M7 3v4M17 3v4M5 7h14v4a7 7 0 0 1-14 0V7Z`}),W(`path`,{d:`M12 18v3`})]})}function ln(e){return W(G,{...e,children:[W(`circle`,{cx:`12`,cy:`12`,r:`9`}),W(`path`,{d:`M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18`})]})}function un(e){return W(G,{...e,children:[W(`rect`,{height:`13`,rx:`2`,width:`18`,x:`3`,y:`6`}),W(`path`,{d:`m8 2 4 4 4-4M8 22h8`})]})}function dn(e){return W(G,{...e,children:[W(`circle`,{cx:`12`,cy:`12`,r:`9`}),W(`path`,{d:`M12 7v5l3 2`})]})}function fn(e){return W(G,{...e,children:[W(`rect`,{height:`14`,rx:`2`,width:`14`,x:`5`,y:`5`}),W(`path`,{d:`M9 9h6v6H9zM9 2v3M15 2v3M9 19v3M15 19v3M2 9h3M2 15h3M19 9h3M19 15h3`})]})}function pn(e){return W(G,{...e,children:[W(`path`,{d:`M20 11a8 8 0 1 0-2.3 5.7`}),W(`path`,{d:`M20 4v7h-7`})]})}function mn(e){return W(G,{...e,children:[W(`path`,{d:`M12 2v10`}),W(`path`,{d:`M18.4 6.6a8 8 0 1 1-12.8 0`})]})}function hn(e){return W(G,{...e,children:[W(`path`,{d:`M12 3v12`}),W(`path`,{d:`m7 10 5 5 5-5`}),W(`path`,{d:`M5 21h14`})]})}function gn(e){return W(G,{...e,children:[W(`ellipse`,{cx:`12`,cy:`5`,rx:`8`,ry:`3`}),W(`path`,{d:`M4 5v6c0 1.7 3.6 3 8 3s8-1.3 8-3V5M4 11v6c0 1.7 3.6 3 8 3s8-1.3 8-3v-6`})]})}function _n(e){return W(G,{...e,children:[W(`circle`,{cx:`8`,cy:`15`,r:`4`}),W(`path`,{d:`m11 12 8-8M15 8l2 2M17 6l2 2`})]})}function vn(e){return W(G,{...e,children:[W(`rect`,{height:`13`,rx:`2`,width:`13`,x:`8`,y:`8`}),W(`path`,{d:`M16 8V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h3`})]})}function yn(e){return W(G,{...e,children:[W(`path`,{d:`M2.5 12s3.5-6 9.5-6 9.5 6 9.5 6-3.5 6-9.5 6-9.5-6-9.5-6Z`}),W(`circle`,{cx:`12`,cy:`12`,r:`2.5`})]})}function bn(e){return W(G,{...e,children:[W(`path`,{d:`m3 3 18 18`}),W(`path`,{d:`M10.6 6.2A8.8 8.8 0 0 1 12 6c6 0 9.5 6 9.5 6a15.5 15.5 0 0 1-2.6 3.2`}),W(`path`,{d:`M6.2 6.3C3.8 8 2.5 12 2.5 12s3.5 6 9.5 6a9.8 9.8 0 0 0 3.1-.5`}),W(`path`,{d:`M10.3 10.3a2.5 2.5 0 0 0 3.4 3.4`})]})}function xn(e){return W(G,{...e,children:[W(`rect`,{height:`17`,rx:`2`,width:`18`,x:`3`,y:`4`}),W(`path`,{d:`M8 2v4M16 2v4M3 9h18`})]})}function Sn(e){return W(G,{...e,children:[W(`circle`,{cx:`12`,cy:`12`,r:`9`}),W(`path`,{d:`m8 12 2.5 2.5L16 9`})]})}function Cn(e){return W(G,{...e,children:[W(`path`,{d:`M10.3 4.2 2.7 17.5A2 2 0 0 0 4.4 20h15.2a2 2 0 0 0 1.7-2.5L13.7 4.2a2 2 0 0 0-3.4 0Z`}),W(`path`,{d:`M12 9v4M12 17h.01`})]})}function wn(e){return W(G,{...e,children:W(`path`,{d:`M12 5v14M5 12h14`})})}function Tn(e){return W(G,{...e,children:W(`path`,{d:`M4 7h16M9 7V4h6v3M7 7l1 13h8l1-13M10 11v5M14 11v5`})})}function En(e){return W(G,{...e,children:[W(`circle`,{cx:`11`,cy:`11`,r:`7`}),W(`path`,{d:`m20 20-4-4`})]})}function Dn(e){return W(G,{...e,children:[W(`path`,{d:`M8 6h12M8 12h12M8 18h12`}),W(`path`,{d:`M4 6h.01M4 12h.01M4 18h.01`})]})}function On(e){return W(G,{...e,children:[W(`path`,{d:`M10 5H6a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h4`}),W(`path`,{d:`M14 8l4 4-4 4M8 12h10`})]})}function kn(e){return W(G,{...e,children:W(`path`,{d:`M4 7h16M4 12h16M4 17h16`})})}function An(e){return W(G,{...e,children:W(`path`,{d:`m6 9 6 6 6-6`})})}function jn(e){return W(G,{...e,children:W(`path`,{d:`m6 6 12 12M18 6 6 18`})})}function Mn(e){return W(G,{...e,children:W(`path`,{d:`m15 18-6-6 6-6`})})}function Nn(e){return W(G,{...e,children:W(`path`,{d:`m9 18 6-6-6-6`})})}function Pn(e){return W(G,{...e,children:W(`path`,{d:`M20.7 13.2A8.5 8.5 0 1 1 10.8 3.3 6.7 6.7 0 0 0 20.7 13.2Z`})})}function Fn(e){return W(G,{...e,children:[W(`circle`,{cx:`12`,cy:`12`,r:`4`}),W(`path`,{d:`M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41`})]})}var In=`smartsafehub.theme`;function Ln(){if(typeof window>`u`)return`light`;try{let e=window.localStorage.getItem(In);if(e===`dark`||e===`light`)return e}catch{}return window.matchMedia(`(prefers-color-scheme: dark)`).matches?`dark`:`light`}function Rn(e){try{window.localStorage.setItem(In,e)}catch{}}function zn(e){let t=document.querySelector(`meta[name="theme-color"]`);t&&(t.content=e===`dark`?`#0f172a`:`#f8fafc`),document.documentElement.style.colorScheme=e,document.body.style.backgroundColor=e===`dark`?`#020617`:`#f8fafc`}function Bn(e){return{length:e.length>=8,letter:/[A-Za-z]/.test(e),number:/[0-9]/.test(e)}}function Vn(e){return e.length&&e.letter&&e.number}function Hn({theme:e}){return W(e===`dark`?Fn:Pn,{})}function Un({met:e,children:t}){return W(`li`,{class:e?`ssh-password-requirement is-met`:`ssh-password-requirement`,children:[W(Sn,{"aria-hidden":`true`}),W(`span`,{children:t})]})}function Wn({onCompleted:e,recovery:t}){let n=z(null),r=z(null),[i,a]=L(``),[o,s]=L(``),[c,l]=L(!1),[u,d]=L(!1),[f,p]=L(!1),[m,h]=L(null),[g,_]=L(Ln),v=Bn(i),y=Vn(v),b=o.length>0&&i===o,x=g===`dark`?`라이트 모드로 전환`:`다크 모드로 전환`;R(()=>{Rn(g),zn(g)},[g]),R(()=>{let e=window.requestAnimationFrame(()=>n.current?.focus());return()=>window.cancelAnimationFrame(e)},[]);let S=e=>{d(e.getModifierState?.(`CapsLock`)??!1)};return W(`main`,{class:`ssh-login-page`,"data-theme":g,children:[W(`section`,{class:`ssh-login-brand`,"aria-labelledby":`ssh-password-setup-brand-title`,children:[W(`div`,{class:`ssh-login-brand-orb ssh-login-brand-orb-one`,"aria-hidden":`true`}),W(`div`,{class:`ssh-login-brand-orb ssh-login-brand-orb-two`,"aria-hidden":`true`}),W(`div`,{class:`ssh-login-brand-content`,children:[W(`span`,{class:`ssh-login-brand-mark`,"aria-hidden":`true`,children:W(qt,{})}),W(`p`,{class:`ssh-login-eyebrow`,children:t?`PASSWORD RECOVERY`:`FIRST SECURITY SETUP`}),W(`h1`,{id:`ssh-password-setup-brand-title`,children:[W(`span`,{class:`ssh-password-setup-brand-title-line`,children:`SmartSafeHub`}),W(`span`,{class:`ssh-password-setup-brand-title-line`,children:t?`비밀번호 복구`:`보호 시작`})]}),W(`p`,{class:`ssh-login-brand-description`,children:t?`물리 Reset 버튼으로 관리자 비밀번호 복구가 요청되었습니다. 네트워크, Wi-Fi, SafeShield 등 기존 설정은 그대로 유지됩니다.`:`관리자 비밀번호가 없는 상태에서는 SmartSafeHub 관리 기능을 사용할 수 없습니다. 먼저 이 공유기의 root 관리자 비밀번호를 설정해 주세요.`}),W(`div`,{class:`ssh-password-setup-summary`,children:[W(qt,{"aria-hidden":`true`}),W(`div`,{children:[W(`strong`,{children:t?`새 관리자 비밀번호를 설정해 주세요`:`초기 보안 설정이 필요합니다`}),W(`span`,{children:t?`새 비밀번호를 설정하기 전까지 SSH 접근은 비활성화되고 다른 관리 기능도 잠겨 있습니다.`:`설정이 완료되기 전까지 다른 SmartSafeHub 기능은 잠겨 있습니다.`})]})]})]})]}),W(`section`,{class:`ssh-login-panel`,"aria-labelledby":`ssh-password-setup-title`,children:[W(`button`,{"aria-label":x,class:`ssh-login-theme-toggle`,onClick:()=>_(e=>e===`dark`?`light`:`dark`),title:x,type:`button`,children:W(Hn,{theme:g})}),W(`div`,{class:`ssh-login-panel-inner`,children:[W(`div`,{class:`ssh-login-card`,children:[W(`div`,{class:`ssh-login-card-heading`,children:[W(`span`,{class:`ssh-login-mobile-mark`,"aria-hidden":`true`,children:W(_n,{})}),W(`div`,{children:[W(`p`,{class:`ssh-login-kicker`,children:`ADMIN PASSWORD`}),W(`h2`,{id:`ssh-password-setup-title`,children:t?`관리자 비밀번호 복구`:`관리자 비밀번호 설정`}),W(`p`,{class:`ssh-login-subtitle`,children:`비밀번호 설정 후 새 비밀번호로 다시 로그인해야 합니다.`})]})]}),m?W(`div`,{class:`ssh-login-alert`,role:`alert`,"aria-live":`polite`,children:m}):null,W(`form`,{class:`ssh-login-form`,onSubmit:async t=>{if(t.preventDefault(),h(null),!y)h(`비밀번호는 8자 이상이며 영문과 숫자를 각각 하나 이상 포함해야 합니다.`),window.requestAnimationFrame(()=>n.current?.focus());else if(i!==o)h(`비밀번호 확인 값이 일치하지 않습니다.`),window.requestAnimationFrame(()=>r.current?.focus());else{p(!0);try{await Wt(i),a(``),s(``);try{await Ee()}catch{}e()}catch(e){h(e instanceof H?e.message:`관리자 비밀번호를 설정하지 못했습니다. 잠시 후 다시 시도해 주세요.`)}finally{p(!1)}}},noValidate:!0,children:[W(`label`,{class:`ssh-login-field`,for:`smartsafehub-new-root-password`,children:[W(`span`,{class:`ssh-login-label`,children:`새 비밀번호`}),W(`span`,{class:`ssh-login-input-wrap`,children:[W(`span`,{class:`ssh-login-field-icon`,"aria-hidden":`true`,children:W(_n,{})}),W(`input`,{autoComplete:`new-password`,disabled:f,id:`smartsafehub-new-root-password`,name:`new-password`,onBlur:()=>d(!1),onInput:e=>a(e.currentTarget.value),onKeyDown:S,onKeyUp:S,placeholder:`새 관리자 비밀번호`,ref:n,...c?{type:`text`}:{type:`password`},value:i}),W(`button`,{"aria-label":c?`비밀번호 숨기기`:`비밀번호 표시`,"aria-pressed":c,class:`ssh-login-password-toggle`,disabled:f,onClick:()=>l(e=>!e),title:c?`비밀번호 숨기기`:`비밀번호 표시`,type:`button`,children:W(c?bn:yn,{})})]})]}),W(`ul`,{class:`ssh-password-requirements`,"aria-label":`비밀번호 요구 사항`,children:[W(Un,{met:v.length,children:`8자 이상`}),W(Un,{met:v.letter,children:`영문자 1자 이상 포함`}),W(Un,{met:v.number,children:`숫자 1자 이상 포함`})]}),W(`label`,{class:`ssh-login-field`,for:`smartsafehub-confirm-root-password`,children:[W(`span`,{class:`ssh-login-label`,children:`비밀번호 확인`}),W(`span`,{class:`ssh-login-input-wrap`,children:[W(`span`,{class:`ssh-login-field-icon`,"aria-hidden":`true`,children:W(_n,{})}),W(`input`,{autoComplete:`new-password`,disabled:f,id:`smartsafehub-confirm-root-password`,name:`confirm-password`,onBlur:()=>d(!1),onInput:e=>s(e.currentTarget.value),onKeyDown:S,onKeyUp:S,placeholder:`새 관리자 비밀번호 다시 입력`,ref:r,...c?{type:`text`}:{type:`password`},value:o})]})]}),o.length>0?W(`p`,{class:b?`ssh-password-match is-met`:`ssh-password-match`,role:`status`,children:b?`비밀번호가 일치합니다.`:`비밀번호가 일치하지 않습니다.`}):null,u?W(`p`,{class:`ssh-login-caps`,role:`status`,children:`Caps Lock이 켜져 있습니다.`}):null,W(`button`,{class:`ssh-login-submit`,disabled:f,type:`submit`,children:[f?W(pn,{class:`ssh-login-submit-spinner`,"aria-hidden":`true`}):null,W(`span`,{children:f?`설정 중…`:`비밀번호 설정`})]})]}),W(`div`,{class:`ssh-login-security-note`,children:[W(qt,{"aria-hidden":`true`}),W(`span`,{children:`비밀번호는 현재 공유기에 직접 설정되며 외부 서버로 전송되지 않습니다.`})]})]}),W(`footer`,{class:`ssh-login-footer`,children:[W(`span`,{children:`SmartSafeHub`}),W(`span`,{"aria-hidden":`true`,children:`·`}),W(`span`,{children:window.location.host})]})]})]})]})}var K=`smartsafehub`,Gn=`smartsafehub_guest`,Kn=`smartsafehub_network`;function qn(){return U(K,`connected_devices`)}async function Jn(){let e=await U(K,`activity_history`);return{...e,cloud:{...e.cloud,enabled:e.cloud.enabled??!1}}}function Yn(e){return U(K,`activity_cloud_sync_update`,{enabled:e})}function Xn(){return U(K,`status`)}function Zn(e){return U(K,`device_recommended_rules_status`,{},{...e?{signal:e}:{},priority:`low`,timeoutMs:5e3})}function Qn(){return U(K,`device_recommended_rules_refresh`,{},{priority:`high`,timeoutMs:6e3})}function $n(e){return U(K,`device_recommended_rules_revoke`,{rule_id:e},{priority:`high`,timeoutMs:6e3})}function er(e){return U(K,`device_registration_status`,{},{...e?{signal:e}:{},priority:`low`,timeoutMs:5e3})}function tr(){return U(K,`device_registration_refresh`,{},{priority:`high`,timeoutMs:6e3})}function nr(){return U(K,`device_pairing_refresh`,{},{priority:`high`,timeoutMs:6e3})}function rr(){return U(K,`health_status`)}function ir(){return U(K,`health_run`)}function ar(e){return U(K,`health_reporter_update`,{enabled:e})}function or(e){return U(K,`apps_catalog`,{},{...e?{signal:e}:{},priority:`low`,timeoutMs:6e3})}function sr(){return U(K,`apps_catalog`,{refresh:!0},{priority:`high`,timeoutMs:6e3})}function cr(e){return U(K,`apps_status`,{},{...e?{signal:e}:{},priority:`low`,timeoutMs:6e3})}function lr(){return U(K,`apps_status`,{refresh:!0},{priority:`high`,timeoutMs:6e3})}function ur(e){return U(K,`apps_metrics`,{},{...e?{signal:e}:{},priority:`low`,timeoutMs:6e3})}function dr(){return U(K,`apps_metrics`,{refresh:!0},{priority:`high`,timeoutMs:6e3})}function fr(e){return U(K,`apps_install`,{app_id:e},{timeoutMs:8e3})}function pr(e){return U(K,`apps_update`,{app_id:e},{timeoutMs:8e3})}function mr(e){return U(K,`apps_remove`,{app_id:e},{timeoutMs:8e3})}function hr(){return U(K,`updates_status`)}function gr(){return U(K,`firmware_status`)}function _r(){return U(K,`firmware_check`)}function vr(){return U(K,`firmware_prepare`)}function yr(e){return U(K,`firmware_validate_upload`,{filename:e})}function br(e){return U(K,`firmware_install`,{confirm:`install`,keep_settings:e})}function xr(){return U(K,`firmware_discard`)}function Sr(){return U(K,`updates_check`)}function Cr(){return U(K,`updates_install`,{confirm:`install`})}function wr(e){return U(K,`updates_settings_update`,{check_enabled:e.checkEnabled,check_interval_s:e.checkIntervalSeconds,auto_install:e.autoInstall,auto_install_time:e.autoInstallTime})}function Tr(){return U(K,`system_time_settings`)}function Er(e){return U(K,`system_timezone_initialize`,{zonename:e})}function Dr(e){return U(K,`system_timezone_update`,{zonename:e})}function Or(){return U(K,`system_time_sync`)}function kr(){return U(K,`system_scheduled_reboot_settings`)}function Ar(e){return U(K,`system_scheduled_reboot_update`,{enabled:e.enabled,frequency:e.frequency,day_of_week:e.dayOfWeek,time:e.time})}function jr(e){return U(K,`system_backup_validate`,{filename:e})}function Mr(){return U(K,`system_backup_restore`,{confirm:`restore`},{timeoutMs:35e3})}function Nr(){return U(K,`system_backup_discard`)}function Pr(){return U(K,`system_reboot`,{confirm:`reboot`})}function Fr(){return U(Kn,`wan_settings`)}function Ir(e){return U(Kn,`wan_update`,{protocol:e.protocol,pppoe_username:e.pppoeUsername,pppoe_password:e.pppoePassword,pppoe_password_changed:e.pppoePasswordChanged,static_address:e.staticAddress,static_prefix_length:e.staticPrefixLength,static_gateway:e.staticGateway,dns_primary:e.dnsPrimary,dns_secondary:e.dnsSecondary,confirm:`apply`},{timeoutMs:1e4})}function Lr(){return U(Kn,`wan_reconnect`,{confirm:`reconnect`},{timeoutMs:1e4})}function Rr(){return U(Kn,`lan_settings`)}function zr(e){return U(Kn,`lan_update`,{ip_address:e.ipAddress,prefix_length:e.prefixLength,dhcp_enabled:e.dhcpEnabled,dhcp_start:e.dhcpStart,dhcp_end:e.dhcpEnd,lease_time:e.leaseTime,confirm:`apply`},{timeoutMs:1e4})}function Br(){return U(Kn,`lan_auto_subnet`,{confirm:`apply`},{timeoutMs:1e4})}function Vr(){return U(Kn,`iptv_settings`)}function Hr(e){return U(Kn,`iptv_update`,{enabled:e.enabled,provider:e.provider,confirm:`apply`},{timeoutMs:2e4})}function Ur(e){return U(K,`wifi_qr`,{section:e},{timeoutMs:8e3})}function Wr(){return U(Gn,`wifi_guest_qr`,{},{timeoutMs:8e3})}async function Gr(){let e=await U(K,`wifi_summary`);try{let t=await U(Gn,`wifi_guest_summary`);return{...e,guest:t,guestError:!1}}catch{return{...e,guest:null,guestError:!0}}}function Kr(e){return U(K,`wifi_update`,{...e},{timeoutMs:35e3})}function qr(e){return U(Gn,`wifi_guest_update`,{...e},{timeoutMs:95e3})}var Jr=Object.fromEntries([{route:`home`,hash:`#home`,label:`대시보드`,title:`대시보드`,description:`네트워크, 장치와 시스템 상태를 한눈에 확인합니다.`},{route:`activity`,hash:`#activity`,label:`최근 활동`,title:`최근 활동`,description:`현재 부팅 이후 인터넷, 보호, 업데이트와 진단 상태 변화를 확인합니다.`},{route:`network`,hash:`#network`,label:`네트워크`,title:`네트워크`,description:`인터넷 연결과 내부 네트워크 주소, DHCP 및 네트워크 충돌을 한 곳에서 관리합니다.`},{route:`wifi`,hash:`#wifi`,label:`Wi-Fi`,title:`Wi-Fi`,description:`기본 무선 네트워크의 이름, 보안과 사용 상태를 관리합니다.`},{route:`iptv`,hash:`#iptv`,label:`IPTV`,title:`IPTV`,description:`SK Broadband와 LG U+ 멀티캐스트 IPTV를 설정합니다.`},{route:`devices`,hash:`#devices`,label:`연결된 기기`,title:`연결된 기기`,description:`네트워크에서 확인된 기기와 연결 방식을 살펴봅니다.`},{route:`safeshield`,hash:`#safeshield`,label:`SafeShield`,title:`SafeShield`,description:`DNS 보호 상태를 확인하고 차단 목록을 관리합니다.`},{route:`netwatch`,hash:`#netwatch`,label:`Network Watch`,title:`Network Watch`,description:`연결된 기기의 외부 통신 변화를 가볍게 관찰하고 주의 신호를 확인합니다.`},{route:`rules`,hash:`#rules`,label:`사용자 규칙`,title:`사용자 규칙`,description:`직접 허용하거나 차단할 도메인을 관리합니다.`},{route:`account`,hash:`#account`,label:`SmartSafeHub 계정`,title:`SmartSafeHub 계정`,description:`이 기기의 SmartSafeHub 계정 연결 상태와 구독 플랜을 확인합니다.`},{route:`apps`,hash:`#apps`,label:`Apps`,title:`SmartSafeHub Apps`,description:`SmartSafeHub에서 검증한 앱과 확장 기능을 살펴봅니다.`},{route:`system`,hash:`#system`,label:`업데이트`,title:`업데이트`,description:`기기의 펌웨어와 관리 소프트웨어 업데이트를 관리합니다.`},{route:`settings`,hash:`#settings`,label:`설정`,title:`설정`,description:`시스템 상태와 시간대를 확인하고 장치 관리 및 진단 기능을 설정합니다.`}].map(e=>[e.route,e])),Yr=[`home`,`activity`,`network`,`wifi`,`devices`,`safeshield`,`netwatch`,`rules`,`account`,`apps`,`system`];function Xr({description:e,loading:t,onRefresh:n,onToggleTheme:r,refreshing:i,theme:a,title:o}){return W(`header`,{class:`ssh-product-hero border-b border-slate-200 bg-white px-4 py-4 sm:px-6 md:h-[4.5rem] md:py-0 lg:px-8 xl:px-10`,children:W(`div`,{class:`flex w-full items-center justify-between gap-4 md:h-full`,children:[W(`div`,{class:`min-w-0`,children:[W(`p`,{class:`m-0 text-[0.68rem] font-extrabold uppercase tracking-[0.18em] text-teal-700`,children:`SmartSafeHub`}),W(`div`,{class:`mt-1 flex min-w-0 items-baseline gap-3`,children:[W(`h1`,{class:`m-0 truncate text-xl font-black tracking-tight text-slate-950 sm:text-2xl`,children:o}),W(`p`,{class:`m-0 hidden truncate text-sm text-slate-500 lg:block`,children:e})]})]}),W(`div`,{class:`flex shrink-0 items-center gap-2`,children:[W(`button`,{"aria-label":a===`dark`?`라이트 모드로 전환`:`다크 모드로 전환`,class:`ssh-product-header-action hidden h-10 w-10 shrink-0 items-center justify-center rounded-xl border border-slate-200 bg-white text-slate-600 shadow-sm transition hover:border-teal-300 hover:text-teal-700 focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-100 md:inline-flex`,onClick:r,title:a===`dark`?`라이트 모드`:`다크 모드`,type:`button`,children:W(a===`dark`?Fn:Pn,{class:`size-5`})}),W(`button`,{"aria-busy":i,"aria-label":i?`새로고침 중`:`현재 화면 새로고침`,class:`ssh-product-header-action hidden h-10 w-10 shrink-0 items-center justify-center rounded-xl border border-slate-200 bg-white shadow-sm transition hover:border-teal-300 hover:text-teal-700 focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-100 disabled:cursor-wait disabled:opacity-60 md:inline-flex ${i?`text-teal-700`:`text-slate-600`}`,disabled:t||i,onClick:n,title:i?`새로고침 중`:`새로고침`,type:`button`,children:i?W(pn,{class:`size-5 animate-spin`}):W(pn,{class:`size-5`})})]})]})})}var Zr=[{label:`Overview`,routes:[`home`,`activity`]},{label:`Network`,routes:[`network`,`wifi`,`iptv`,`devices`]},{label:`Security`,routes:[`safeshield`,`netwatch`,`rules`]},{label:`Explore`,routes:[`apps`]},{label:`System`,routes:[`account`,`system`,`settings`]}];function Qr({route:e}){switch(e){case`home`:return W(Xt,{class:`size-5`});case`activity`:return W(dn,{class:`size-5`});case`network`:return W(ln,{class:`size-5`});case`wifi`:return W(on,{class:`size-5`});case`iptv`:return W(un,{class:`size-5`});case`devices`:return W(Yt,{class:`size-5`});case`safeshield`:return W(qt,{class:`size-5`});case`netwatch`:return W($t,{class:`size-5`});case`rules`:return W(Zt,{class:`size-5`});case`account`:return W(_n,{class:`size-5`});case`apps`:return W(Qt,{class:`size-5`});case`system`:return W(rn,{class:`size-5`});case`settings`:return W(an,{class:`size-5`})}}function $r({theme:e}){return W(e===`dark`?Fn:Pn,{class:`size-5`})}function ei(e){return e===`dark`?`라이트 모드`:`다크 모드`}function ti(e,t=!1,n){let r=t?`justify-center px-2`:`gap-3 px-3`;return e?`relative flex min-h-11 min-w-0 items-center rounded-xl py-2.5 text-sm font-extrabold no-underline transition ${r} bg-teal-50 text-teal-800 ring-1 ring-inset ring-teal-200`:n===`apps`?`ssh-apps-nav-link relative flex min-h-11 min-w-0 items-center rounded-xl py-2.5 text-sm font-extrabold no-underline transition ${r}`:`relative flex min-h-11 min-w-0 items-center rounded-xl py-2.5 text-sm font-extrabold text-slate-600 no-underline transition ${r} hover:bg-slate-50 hover:text-slate-950`}function ni(e){return e===`apps`?`앱으로 확장하기`:null}function ri({accountRegistered:e,collapsed:t=!1,route:n,updateCount:r,demo:i=!1}){return W(`div`,{class:t?`space-y-3`:`space-y-5`,children:Zr.filter(e=>!i||e.routes.some(e=>Yr.includes(e))).map((a,o)=>W(`section`,{"aria-label":a.label,class:t&&o>0?`border-t border-slate-100 pt-3`:void 0,children:[t?null:W(`p`,{class:`mb-2 px-3 text-[0.65rem] font-black uppercase tracking-[0.18em] text-slate-400`,children:a.label}),W(`div`,{class:`space-y-1`,children:a.routes.filter(e=>!i||Yr.includes(e)).map(i=>{let a=Jr[i],o=n===i;return W(`a`,{"aria-current":o?`page`:void 0,"aria-label":i===`account`&&e===!1?`${a.label} (계정 연결 필요)`:t?a.label:void 0,class:ti(o,t,i),href:a.hash,title:i===`account`&&e===!1?`SmartSafeHub 계정 연결 필요`:t?a.label:void 0,children:[W(`span`,{class:`grid size-9 shrink-0 place-items-center rounded-lg ${o?`bg-teal-700 text-white`:`bg-slate-100 text-slate-500`}`,children:W(Qr,{route:i})}),t?null:W(`span`,{class:`flex min-w-0 flex-1 items-center gap-2`,children:[W(`span`,{class:`min-w-0 flex-1`,children:[W(`span`,{class:`block truncate`,children:a.label}),ni(i)?W(`span`,{class:`ssh-apps-nav-description mt-0.5 block truncate text-[11px] font-semibold tracking-normal`,children:ni(i)}):null]}),i===`iptv`?W(`span`,{class:`rounded-full border border-amber-300 bg-amber-100 px-1.5 py-0.5 text-[9px] font-black uppercase tracking-[0.08em] text-amber-800`,children:`Beta`}):null]}),t&&i===`iptv`?W(`span`,{"aria-label":`Beta 기능`,class:`absolute right-0.5 top-0.5 inline-flex size-5 items-center justify-center rounded-full border border-amber-300 bg-amber-100 text-[13px] font-extrabold leading-none text-amber-900 shadow-sm ring-2 ring-white`,children:`β`}):null,i===`account`&&e===!1?W(`span`,{"aria-label":`계정 연결 필요`,class:`ssh-account-nav-dot ${t?`ssh-account-nav-dot-collapsed`:``}`,"aria-hidden":`true`,title:`계정 연결 필요`}):null,i===`system`&&r>0?t?W(`span`,{"aria-label":`${r}개의 업데이트 유형`,class:`ssh-update-nav-badge`,"data-collapsed":`true`,children:r}):W(`span`,{class:`ssh-update-nav-badge`,"data-collapsed":`false`,children:r}):null]},i)})})]},a.label))})}function ii({accountRegistered:e,collapsed:t,loading:n,onRefresh:r,onToggleCollapsed:i,onToggleTheme:a,refreshing:o,route:s,theme:c,title:l,updateCount:u,demo:d=!1}){let[f,p]=L(!1),m=ei(c);return R(()=>{p(!1)},[s]),R(()=>{if(!f)return;let e=e=>{e.key===`Escape`&&p(!1)};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[f]),W(b,{children:[W(`nav`,{"aria-label":`SmartSafeHub 모바일 메뉴`,class:`ssh-mobile-navigation sticky top-0 z-40 border-b border-slate-200 bg-white/95 px-4 shadow-sm shadow-slate-900/5 backdrop-blur md:hidden`,children:[W(`div`,{class:`flex min-h-16 items-center justify-between gap-3`,children:[W(`a`,{class:`flex min-w-0 items-center gap-3 no-underline`,href:`#home`,children:[W(`span`,{class:`grid size-10 shrink-0 place-items-center rounded-xl bg-slate-950 text-teal-300`,children:W(qt,{class:`size-5`})}),W(`span`,{class:`min-w-0`,children:[W(`strong`,{class:`block truncate text-sm font-black text-slate-950`,children:`SmartSafeHub`}),W(`span`,{class:`block truncate text-xs font-semibold text-slate-500`,children:l})]})]}),W(`div`,{class:`flex shrink-0 items-center gap-2`,children:[W(`button`,{"aria-label":`${m}로 전환`,class:`inline-flex size-11 shrink-0 items-center justify-center rounded-xl border border-slate-200 bg-white text-slate-600 transition hover:border-slate-300 hover:bg-slate-50 hover:text-teal-700 focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-100`,onClick:a,title:m,type:`button`,children:W($r,{theme:c})}),W(`button`,{"aria-busy":o,"aria-label":o?`새로고침 중`:`현재 화면 새로고침`,class:`inline-flex size-11 shrink-0 items-center justify-center rounded-xl border border-slate-200 bg-white transition hover:border-slate-300 hover:bg-slate-50 hover:text-teal-700 focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-100 disabled:cursor-wait disabled:opacity-60 ${o?`text-teal-700`:`text-slate-600`}`,disabled:n||o,onClick:r,title:o?`새로고침 중`:`새로고침`,type:`button`,children:o?W(pn,{class:`size-5 animate-spin`}):W(pn,{class:`size-5`})}),W(`button`,{"aria-controls":`smartsafehub-mobile-menu`,"aria-expanded":f,"aria-label":f?`모바일 메뉴 닫기`:`모바일 메뉴 열기`,class:`inline-flex size-11 shrink-0 items-center justify-center rounded-xl border border-slate-200 bg-white text-slate-700 transition hover:bg-slate-50 focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-100`,onClick:()=>p(e=>!e),type:`button`,children:W(f?jn:kn,{class:`size-5`})})]})]}),f?W(`div`,{class:`ssh-mobile-menu border-t border-slate-100 py-4`,id:`smartsafehub-mobile-menu`,children:[W(ri,{accountRegistered:e,demo:d,route:s,updateCount:u}),d?null:W(`div`,{class:`mt-5 space-y-1 border-t border-slate-100 pt-4`,children:W(`a`,{class:`${ti(!1)} text-rose-700 hover:bg-rose-50 hover:text-rose-800`,href:ve(`/admin/logout`),children:[W(`span`,{class:`grid size-9 shrink-0 place-items-center rounded-lg bg-rose-50 text-rose-600`,children:W(On,{class:`size-5`})}),`로그아웃`]})})]}):null]}),W(`aside`,{class:`hidden min-h-screen border-r border-slate-200 bg-white md:flex md:flex-col`,"data-collapsed":t?`true`:`false`,children:W(`div`,{class:`sticky top-0 flex h-screen flex-col`,children:[W(`div`,{class:`flex h-[4.5rem] min-h-[4.5rem] shrink-0 items-center border-b border-slate-100 ${t?`justify-center px-2`:`gap-3 px-4`}`,children:[W(`a`,{"aria-label":`SmartSafeHub Dashboard`,class:`flex items-center no-underline ${t?`justify-center`:`min-w-0 flex-1 gap-3`}`,href:`#home`,title:t?`SmartSafeHub`:void 0,children:[W(`span`,{class:`grid size-10 shrink-0 place-items-center rounded-xl bg-slate-950 text-teal-300 shadow-sm shadow-slate-950/10`,children:W(qt,{class:`size-5`})}),t?null:W(`div`,{class:`min-w-0`,children:[W(`strong`,{class:`block truncate text-sm font-black tracking-tight text-slate-950`,children:`SmartSafeHub`}),W(`span`,{class:`block text-[0.68rem] font-bold uppercase tracking-[0.14em] text-slate-400`,children:`Home Gateway`})]})]}),W(`button`,{"aria-controls":`smartsafehub-desktop-navigation`,"aria-expanded":!t,"aria-label":t?`사이드바 펼치기`:`사이드바 접기`,class:`ssh-sidebar-toggle absolute right-0 top-[4.5rem] z-20 inline-flex size-7 translate-x-1/2 -translate-y-1/2 shrink-0 items-center justify-center rounded-full transition focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-100`,onClick:i,title:t?`사이드바 펼치기`:`사이드바 접기`,type:`button`,children:W(t?Nn:Mn,{class:`size-4`})})]}),W(`nav`,{"aria-label":`SmartSafeHub 메뉴`,class:`flex-1 overflow-y-auto py-5 ${t?`px-2`:`px-3`}`,id:`smartsafehub-desktop-navigation`,children:W(ri,{accountRegistered:e,collapsed:t,demo:d,route:s,updateCount:u})}),d?null:W(`div`,{class:`border-t border-slate-100 ${t?`p-2`:`p-3`}`,children:W(`a`,{"aria-label":`SmartSafeHub에서 로그아웃`,class:`${ti(!1,t)} mt-1 text-rose-700 hover:bg-rose-50 hover:text-rose-800`,href:ve(`/admin/logout`),title:t?`로그아웃`:void 0,children:[W(`span`,{class:`grid size-9 shrink-0 place-items-center rounded-lg bg-rose-50 text-rose-600`,children:W(On,{class:`size-5`})}),t?null:`로그아웃`]})})]})})]})}var ai=`smartsafehub.sidebar.collapsed`;function oi(){if(typeof window>`u`)return!1;try{return window.localStorage.getItem(ai)===`1`}catch{return!1}}function si({accountRegistered:e,children:t,loading:n,onRefresh:r,refreshing:i,route:a,updateCount:o,demo:s=!1}){let c=Jr[a],[l,u]=L(oi),[d,f]=L(Ln);return R(()=>{try{window.localStorage.setItem(ai,l?`1`:`0`)}catch{}},[l]),R(()=>{Rn(d),zn(d)},[d]),W(`div`,{class:`ssh-app min-h-screen bg-slate-50 text-slate-950 md:grid md:transition-[grid-template-columns] md:duration-200 md:ease-out ${l?`md:grid-cols-[5rem_minmax(0,1fr)]`:`md:grid-cols-[16rem_minmax(0,1fr)]`}`,"data-theme":d,children:[W(ii,{accountRegistered:e,demo:s,collapsed:l,loading:n,onRefresh:r,onToggleCollapsed:()=>u(e=>!e),onToggleTheme:()=>f(e=>e===`dark`?`light`:`dark`),refreshing:i,route:a,theme:d,title:c.title,updateCount:o}),W(`div`,{class:`min-w-0 md:min-h-screen`,children:[W(Xr,{description:c.description,loading:n,onRefresh:r,onToggleTheme:()=>f(e=>e===`dark`?`light`:`dark`),refreshing:i,theme:d,title:c.title}),W(`main`,{class:`ssh-product-main w-full max-w-[1600px] bg-slate-50 px-4 pb-8 sm:px-6 sm:pb-10 lg:px-8 lg:pb-12 xl:px-10 ${n?`pt-2 sm:pt-2 lg:pt-3`:`pt-5 sm:pt-6 lg:pt-8`}`,children:t})]})]})}function ci({active:e,label:t,tabs:n}){return W(`nav`,{"aria-label":t,class:`mb-5 min-w-0`,role:`tablist`,children:W(`div`,{class:`grid w-full min-w-0 gap-2 ${n.length===3?`grid-cols-3 sm:max-w-[592px]`:`grid-cols-2 sm:max-w-[392px]`}`,children:n.map(t=>{let r=t.id===e;return W(`a`,{"aria-controls":`${t.id}-panel`,"aria-selected":r,class:`flex min-h-11 min-w-0 items-center justify-center rounded-xl border px-2 py-2.5 text-center text-[11px] font-extrabold leading-5 transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-teal-500 sm:px-4 sm:text-sm ${r?`border-teal-200 bg-teal-50 text-teal-800`:`border-slate-200 bg-white text-slate-600 hover:border-teal-200 hover:text-teal-700`}`,href:t.href,id:`${t.id}-tab`,role:`tab`,tabIndex:r?0:-1,onKeyDown:e=>{if(![`ArrowLeft`,`ArrowRight`,`Home`,`End`].includes(e.key))return;e.preventDefault();let r=n.findIndex(e=>e.id===t.id),i=e.key===`Home`?0:e.key===`End`?n.length-1:(r+(e.key===`ArrowRight`?1:-1)+n.length)%n.length,a=e.currentTarget.parentElement?.children[i];a?.focus(),a?.click()},children:t.label},t.id)})})})}function q(e,t){return e instanceof Error?e.message:t}function li({active:e=!0,fallbackError:t,loader:n,pollInterval:r,refreshOnFocus:i=!1,refreshOnVisible:a=!1,staleTimeMs:o=3e4}){let[s,c]=L({data:null,error:null,loading:e,refreshing:!1}),l=z(!1),u=z(!0),d=z(null),f=z(null),p=z(0),m=z(null),h=z(null),g=z(0);R(()=>(u.current=!0,()=>{u.current=!1,p.current+=1,f.current?.abort()}),[]);let _=B((e=!1)=>{if(d.current)return d.current;u.current&&c(t=>({...t,error:null,loading:t.data===null,refreshing:e}));let r,i=++p.current,a=new AbortController;return f.current=a,m.current=Date.now(),r=n(a.signal).then(e=>{i!==p.current||a.signal.aborted||(h.current=Date.now(),g.current=0,u.current&&c({data:e,error:null,loading:!1,refreshing:!1}))}).catch(e=>{i!==p.current||a.signal.aborted||(g.current+=1,u.current&&c(n=>({...n,error:q(e,t),loading:!1,refreshing:!1})))}).finally(()=>{d.current===r&&(d.current=null),f.current===a&&(f.current=null)}),d.current=r,r},[t,n]);R(()=>{e?l.current||(l.current=!0,s.data!==null&&h.current!==null&&Date.now()-h.current{if(!e||v==null||v<=0)return;let t=!1,n=null,r=()=>{n!==null&&(window.clearTimeout(n),n=null)},o=()=>{let e=Date.now();if(g.current>0&&m.current!==null){let t=Math.min(3e4,3e3*2**Math.min(g.current-1,4));return Math.max(0,m.current+t-e)}return h.current===null?0:Math.max(0,h.current+v-e)},s=()=>{r(),!(t||document.visibilityState===`hidden`)&&(n=window.setTimeout(()=>{n=null,!(t||document.visibilityState===`hidden`)&&(o()>0?s():_(!1).finally(s))},o()))},c=()=>{r(),!(t||document.visibilityState===`hidden`)&&(o()>0?s():_(!1).finally(s))},l=()=>{document.visibilityState===`hidden`?r():a?_(!1).finally(s):c()},u=()=>{c()};return document.addEventListener(`visibilitychange`,l),i&&window.addEventListener(`focus`,u),s(),()=>{t=!0,r(),document.removeEventListener(`visibilitychange`,l),i&&window.removeEventListener(`focus`,u)}},[e,v,_,i,a]);let y=B(()=>_(!0),[_]),b=B(e=>{u.current&&(h.current=Date.now(),g.current=0,c(t=>({...t,data:e,error:null,loading:!1,refreshing:!1})))},[]);return{...s,refresh:y,replaceData:b}}var ui=6e4;function di(e){let t=li({active:e,fallbackError:`최근 활동을 불러오지 못했습니다.`,loader:()=>Jn(),pollInterval:ui,refreshOnFocus:!0}),[n,r]=L({savingCloudSync:!1,actionError:null,actionMessage:null}),i=B(async e=>{r({savingCloudSync:!0,actionError:null,actionMessage:null});try{let n=await Yn(e);return t.replaceData(n),r({savingCloudSync:!1,actionError:null,actionMessage:e?`Cloud 활동 기록 전송을 켰습니다. 지금부터 발생하는 활동을 Cloud에 전송합니다.`:`Cloud 활동 기록 전송을 껐습니다. 로컬 최근 활동은 계속 기록됩니다.`}),!0}catch(e){return r({savingCloudSync:!1,actionError:q(e,`Cloud 활동 기록 설정을 변경하지 못했습니다.`),actionMessage:null}),!1}},[t.replaceData]),a=B(()=>{r(e=>({...e,actionError:null,actionMessage:null}))},[]);return{...t,...n,dismissActionFeedback:a,setCloudSyncEnabled:i}}var fi=`/tmp/smartsafehub/config-backup.tar.gz`,pi=`/cgi-backup`,mi=`/cgi-upload`;function hi(){let e=window.__SMARTHUB_BOOTSTRAP__?.sessionId;if(!e)throw new H(`BOOTSTRAP_MISSING`,`LuCI 세션 정보를 찾을 수 없습니다.`);return e}function gi(e){return(e?.match(/filename="?([^";]+)"?/i))?.[1]?.trim()||`backup-openwrt.tar.gz`}function _i(e,t){let n=URL.createObjectURL(t),r=document.createElement(`a`);r.href=n,r.download=e,r.style.display=`none`,(document.body??document.documentElement).appendChild(r),r.click(),r.remove(),window.setTimeout(()=>URL.revokeObjectURL(n),1e3)}async function vi(){let e=ge(pi),t=await fetch(e,{method:`POST`,credentials:`same-origin`,headers:{"Content-Type":`application/x-www-form-urlencoded;charset=UTF-8`},body:new URLSearchParams({sessionid:hi()}).toString()});if(!t.ok)throw new H(`SYSTEM_BACKUP_DOWNLOAD_FAILED`,`설정 백업 생성에 실패했습니다. (HTTP ${t.status})`);let n=await t.blob();if(n.size<=0)throw new H(`SYSTEM_BACKUP_DOWNLOAD_EMPTY`,`설정 백업 파일이 비어 있습니다.`);_i(gi(t.headers.get(`Content-Disposition`)),n)}function yi(e,t){let n;try{n=hi()}catch(e){return Promise.reject(e)}return new Promise((r,i)=>{let a=new XMLHttpRequest,o=new FormData;o.append(`sessionid`,n),o.append(`filename`,fi),o.append(`filedata`,e),a.open(`POST`,ge(mi)),a.timeout=0,a.upload.addEventListener(`progress`,e=>{!e.lengthComputable||e.total<=0||t?.(Math.min(100,Math.max(0,e.loaded/e.total*100)))}),a.addEventListener(`error`,()=>{i(new H(`SYSTEM_BACKUP_UPLOAD_FAILED`,`설정 백업 파일을 공유기에 업로드하지 못했습니다.`))}),a.addEventListener(`load`,()=>{if(a.status<200||a.status>=300){i(new H(`SYSTEM_BACKUP_UPLOAD_HTTP_ERROR`,`설정 백업 업로드가 HTTP ${a.status} 오류를 반환했습니다.`));return}let n;try{n=JSON.parse(a.responseText)}catch{i(new H(`SYSTEM_BACKUP_UPLOAD_INVALID_RESPONSE`,`설정 백업 업로드 응답을 확인하지 못했습니다.`));return}n.failure?i(new H(n.failure,n.message||`설정 백업 파일 업로드가 거부되었습니다.`)):(t?.(100),r({...n,name:e.name}))}),a.send(o)})}var bi=16777216;function xi(){let[e,t]=L(null),[n,r]=L(null),[i,a]=L(null),[o,s]=L(null),[c,l]=L(null),[u,d]=L(!1),f=B(async()=>{t(`download`),r(null),a(null);try{await vi(),a(`현재 설정 백업을 다운로드했습니다.`)}catch(e){r(q(e,`설정 백업을 다운로드하지 못했습니다.`))}finally{t(null)}},[]),p=B(async e=>{if(r(null),a(null),s(null),d(!1),e.size<=0||e.size>bi)return r(`설정 백업 파일은 16MB 이하여야 합니다.`),!1;try{t(`upload`),l(0),await yi(e,l),t(`validate`);let n=await jr(e.name);return s(n),a(`백업 파일 검증이 완료되었습니다. 복원 내용을 확인해 주세요.`),!0}catch(e){return r(q(e,`설정 백업 파일을 검증하지 못했습니다.`)),!1}finally{t(null),l(null)}},[]),m=B(async()=>{t(`discard`),r(null),a(null);try{return await Nr(),s(null),d(!1),a(`업로드한 백업 파일을 삭제했습니다.`),!0}catch(e){return r(q(e,`업로드한 백업 파일을 삭제하지 못했습니다.`)),!1}finally{t(null)}},[]),h=B(async()=>{if(!o)return r(`먼저 복원할 백업 파일을 업로드하고 검증해 주세요.`),!1;t(`restore`),r(null),a(null);try{let e=await Mr();return d(e.accepted&&e.rebooting),s(null),a(e.accepted?`설정을 복원했습니다. 공유기가 재부팅되며 네트워크 설정에 따라 다시 연결해야 할 수 있습니다.`:`설정 복원 요청이 접수되지 않았습니다.`),e.accepted}catch(e){return r(q(e,`설정 백업을 복원하지 못했습니다.`)),!1}finally{t(null)}},[o]);return{action:e,discard:m,dismissFeedback:B(()=>{r(null),a(null)},[]),download:f,error:n,message:i,restore:h,restoreAccepted:u,upload:p,uploadProgress:c,validated:o}}var Si=15e3;function Ci(e,t=!0){return li({active:e,fallbackError:`연결된 기기 목록을 불러오지 못했습니다.`,loader:qn,...t?{pollInterval:Si}:{}})}var wi=`/tmp/smartsafehub/firmware.bin`,Ti=`/cgi-upload`;function Ei(e,t){let n=window.__SMARTHUB_BOOTSTRAP__;return n?.sessionId?new Promise((r,i)=>{let a=new XMLHttpRequest,o=new FormData;o.append(`sessionid`,n.sessionId),o.append(`filename`,wi),o.append(`filedata`,e);let s=ge(Ti);a.open(`POST`,s),a.timeout=0,a.upload.addEventListener(`progress`,e=>{!e.lengthComputable||e.total<=0||t?.(Math.min(100,Math.max(0,e.loaded/e.total*100)))}),a.addEventListener(`error`,()=>{console.error(`[SmartSafeHub] firmware upload network error`,{endpoint:s,destination:wi,fileName:e.name,fileSize:e.size,readyState:a.readyState,status:a.status}),i(new H(`FIRMWARE_UPLOAD_FAILED`,`펌웨어 파일 업로드에 실패했습니다. (${s}, 네트워크 오류)`))}),a.addEventListener(`load`,()=>{if(a.status<200||a.status>=300){console.error(`[SmartSafeHub] firmware upload HTTP error`,{endpoint:s,destination:wi,fileName:e.name,fileSize:e.size,readyState:a.readyState,status:a.status,response:a.responseText}),i(new H(`FIRMWARE_UPLOAD_HTTP_ERROR`,`펌웨어 업로드가 HTTP ${a.status} 오류를 반환했습니다. (${s})`));return}let n;try{n=JSON.parse(a.responseText)}catch{console.error(`[SmartSafeHub] firmware upload returned invalid JSON`,{endpoint:s,destination:wi,fileName:e.name,fileSize:e.size,readyState:a.readyState,status:a.status,response:a.responseText}),i(new H(`FIRMWARE_UPLOAD_INVALID_RESPONSE`,`펌웨어 업로드 응답을 확인하지 못했습니다. (${s})`));return}n.failure?i(new H(n.failure,n.message||`펌웨어 파일 업로드가 거부되었습니다.`)):(t?.(100),r({...n,name:e.name}))}),a.send(o)}):Promise.reject(new H(`BOOTSTRAP_MISSING`,`LuCI 세션 정보를 찾을 수 없습니다.`))}var Di=3e5,Oi=1e3,ki=500,Ai=3e3,ji=3e4,Mi=15e3,Ni=5e3;function Pi(e){return new Promise(t=>window.setTimeout(t,e))}function Fi(e){return e===`checking`||e===`downloading`||e===`verifying`}function Ii(e=!0){let[t,n]=L(null),r=li({active:e||Fi(t??void 0),fallbackError:`펌웨어 업데이트 상태를 불러오지 못했습니다.`,loader:gr,pollInterval:e=>Fi(e?.phase)?Oi:Di,refreshOnFocus:!0,refreshOnVisible:Fi(t??void 0)});R(()=>{n(r.data?.phase??null)},[r.data?.phase]);let[i,a]=L(null),[o,s]=L(null),[c,l]=L(null),[u,d]=L(null),[f,p]=L(!1),m=z(null);R(()=>()=>{m.current!==null&&window.clearTimeout(m.current)},[]);let h=B(e=>{n(e),r.data&&r.replaceData({...r.data,phase:e,lastError:null})},[r]),g=B(async(e,t,n,i)=>{a(e),s(null),l(null);try{if(!(await n()).accepted)throw Error(`펌웨어 작업 요청이 접수되지 않았습니다.`);h(t),a(null),window.setTimeout(()=>void r.refresh(),400)}catch(e){a(null),s(q(e,i))}},[h,r]),_=B(()=>g(`check`,`checking`,_r,`펌웨어 업데이트 확인을 시작하지 못했습니다.`),[g]),v=B(()=>g(`prepare`,`downloading`,vr,`펌웨어 다운로드를 시작하지 못했습니다.`),[g]),y=B(async()=>{let e=Date.now(),t=null;for(;Date.now()-e{a(`upload`),s(null),l(null),d(0);try{if(await Ei(e,d),a(`validate-upload`),!(await yr(e.name)).accepted)throw Error(`업로드한 펌웨어 검증 요청이 접수되지 않았습니다.`);h(`verifying`),d(null);let t=await y();if(a(null),t.phase===`error`)return!1;if(!t.prepared)throw Error(`검증된 펌웨어의 설치 준비 정보를 확인하지 못했습니다. 다시 시도해 주세요.`);return l(`펌웨어 검증이 완료되었습니다. 설치 옵션을 확인해 주세요.`),!0}catch(e){return a(null),d(null),s(q(e,`펌웨어 파일을 업로드하지 못했습니다.`)),!1}},[h,y]),x=B(()=>{p(!0);let e=async()=>{try{if((await fetch(window.location.pathname,{cache:`no-store`,credentials:`same-origin`})).ok){window.location.reload();return}}catch{}m.current=window.setTimeout(e,Ni)};m.current=window.setTimeout(e,Mi)},[]),S=B(async e=>{a(`install`),s(null),l(null);try{if(!(await br(e)).accepted)throw Error(`펌웨어 설치 요청이 접수되지 않았습니다.`);return h(`flashing`),a(null),x(),!0}catch(e){return a(null),s(q(e,`펌웨어 설치를 시작하지 못했습니다.`)),!1}},[h,x]),C=B(async()=>{a(`discard`),s(null),l(null);try{if(!(await xr()).accepted)throw Error(`준비된 펌웨어 정리 요청이 접수되지 않았습니다.`);return a(null),l(`준비된 펌웨어 파일을 삭제했습니다.`),await r.refresh(),!0}catch(e){return a(null),s(q(e,`준비된 펌웨어를 정리하지 못했습니다.`)),!1}},[r]),w=B(()=>{s(null),l(null)},[]);return{...r,action:i,actionError:o,message:c,uploadProgress:u,reconnecting:f,check:_,prepare:v,upload:b,install:S,discard:C,dismissFeedback:w}}var Li={"#home":`home`,"#activity":`activity`,"#network":`network`,"#network/internet":`network`,"#network/lan":`network`,"#wan":`network`,"#lan":`network`,"#wifi":`wifi`,"#iptv":`iptv`,"#devices":`devices`,"#safeshield":`safeshield`,"#rules":`rules`,"#netwatch":`netwatch`,"#account":`account`,"#apps":`apps`,"#system":`system`,"#settings":`settings`,"#settings/diagnostics":`settings`,"#settings/time":`settings`,"#settings/management":`settings`};function Ri(){return Li[window.location.hash]??`home`}function zi(){let[e,t]=L(Ri);return R(()=>{let e=()=>t(Ri());return window.addEventListener(`hashchange`,e),()=>window.removeEventListener(`hashchange`,e)},[]),e}function Bi(e){return e===`#lan`||e===`#network/lan`?`lan`:`internet`}function Vi(e){return e===`#settings/time`?`time`:e===`#settings/management`?`management`:`diagnostics`}function Hi(){let e=window.location.hash;return{network:Bi(e),settings:Vi(e)}}function Ui(){let[e,t]=L(Hi);return R(()=>{let e=()=>t(Hi());return window.addEventListener(`hashchange`,e),()=>window.removeEventListener(`hashchange`,e)},[]),e}var Wi=6e4,Gi=1e3,Ki=[400,800,1200,1600],qi=[400,800,1200,2e3,4e3];function Ji(e){return new Promise(t=>window.setTimeout(t,e))}function Yi(e){let t=li({active:e,fallbackError:`장치 진단 상태를 불러오지 못했습니다.`,loader:rr,pollInterval:e=>e?.generatedAt?Wi:Gi,refreshOnFocus:!0}),n=z(0),[r,i]=L({running:!1,savingReporter:!1,actionError:null,actionMessage:null}),a=B(async()=>{i({running:!0,savingReporter:!1,actionError:null,actionMessage:null});try{let e=t.data?.generatedAt??0;if(!(await ir()).accepted)throw Error(`장치가 진단 요청을 접수하지 않았습니다.`);let n=t.data;for(let t of Ki)if(await Ji(t),n=await rr(),n.generatedAt>0&&n.generatedAt!==e)break;return n&&t.replaceData(n),i({running:!1,savingReporter:!1,actionError:null,actionMessage:`장치 상태를 다시 진단했습니다.`}),!0}catch(e){return i({running:!1,savingReporter:!1,actionError:q(e,`장치 상태 진단을 실행하지 못했습니다.`),actionMessage:null}),!1}},[t.data,t.replaceData]),o=B(async(e,r)=>{for(let i of qi){if(await Ji(i),n.current!==r)return;try{let i=await rr();if(n.current!==r)return;if(t.replaceData(i),i.reporter.enabled!==e)continue;if(!e||![`disabled`,`never`,`initializing`].includes(i.reporter.lastResult))return}catch{}}},[t.replaceData]),s=B(async e=>{let r=n.current+1;n.current=r;let a=t.data;a&&t.replaceData({...a,reporter:{...a.reporter,enabled:e,lastResult:e?`initializing`:`disabled`,lastErrorCode:null,nextReportAt:e?a.reporter.nextReportAt:0}}),i({running:!1,savingReporter:!0,actionError:null,actionMessage:null});try{let a=await ar(e);return n.current===r&&(t.replaceData(a),i({running:!1,savingReporter:!1,actionError:null,actionMessage:e?`원격 상태 보고를 켰습니다. 첫 서버 보고를 준비합니다.`:`원격 상태 보고를 껐습니다. 로컬 진단은 계속 동작합니다.`}),o(e,r)),!0}catch(e){return n.current===r&&(a&&t.replaceData(a),i({running:!1,savingReporter:!1,actionError:q(e,`원격 상태 보고 설정을 변경하지 못했습니다.`),actionMessage:null})),!1}},[o,t.data,t.replaceData]),c=B(()=>{i(e=>({...e,actionError:null,actionMessage:null}))},[]);return{...t,...r,dismissActionFeedback:c,runDiagnostic:a,setReporterEnabled:s}}function Xi(e){let t=li({active:e,fallbackError:`IPTV 설정을 불러오지 못했습니다.`,loader:Vr,refreshOnFocus:!0}),[n,r]=L(!1),[i,a]=L(null),o=B(async e=>{r(!0),a(null);try{let n=await Hr(e);return t.replaceData(n.settings),r(!1),a({kind:`success`,message:e.enabled?n.changed?`IPTV 설정을 적용했습니다.`:`IPTV 설정을 다시 적용했습니다.`:`IPTV 기능을 껐습니다.`}),!0}catch(e){return r(!1),a({kind:`error`,message:q(e,`IPTV 설정을 적용하지 못했습니다.`)}),!1}},[t.replaceData]),s=B(()=>a(null),[]);return{...t,saving:n,feedback:i,save:o,dismissFeedback:s}}var Zi=4e3;function Qi(e){let t=li({active:e,fallbackError:`내부 네트워크 설정을 불러오지 못했습니다.`,loader:Rr}),n=z(e),[r,i]=L({action:null,feedback:null});R(()=>{n.current=e},[e]);let a=B((e,r)=>{t.replaceData(e.settings),e.changed?e.addressChanged?i({action:null,feedback:{kind:`warning`,message:r?`추천 대역 ${e.settings.lan.subnet}을 저장했습니다. 네트워크가 다시 적용되면 새 공유기 주소로 다시 연결해 주세요.`:`내부 네트워크 주소를 저장했습니다. 네트워크가 다시 적용되면 새 공유기 주소로 다시 연결해 주세요.`,newAddress:e.newAddress??e.settings.lan.address}}):(i({action:null,feedback:{kind:`success`,message:r?`추천 내부 네트워크 설정을 적용했습니다.`:`내부 네트워크 설정을 저장했습니다.`}}),e.reloadScheduled&&window.setTimeout(()=>{n.current&&t.refresh()},Zi)):i({action:null,feedback:{kind:`success`,message:`변경된 내부 네트워크 설정이 없습니다.`}})},[t.refresh,t.replaceData]),o=B(async e=>{i({action:`saving`,feedback:null});try{let t=await zr(e);return a(t,!1),!0}catch(e){return i({action:null,feedback:{kind:`error`,message:q(e,`내부 네트워크 설정을 저장하지 못했습니다.`)}}),!1}},[a]),s=B(async()=>{i({action:`auto`,feedback:null});try{let e=await Br();return a(e,!0),!0}catch(e){return i({action:null,feedback:{kind:`error`,message:q(e,`추천 내부 네트워크를 적용하지 못했습니다.`)}}),!1}},[a]),c=B(()=>{i(e=>({...e,feedback:null}))},[]);return{...t,...r,save:o,applyRecommendation:s,dismissFeedback:c}}var $i=5e3;function ea(e){let t=li({active:e,fallbackError:`인터넷 연결 설정을 불러오지 못했습니다.`,loader:Fr}),n=z(e),[r,i]=L({action:null,feedback:null});R(()=>{n.current=e},[e]);let a=B(()=>{window.setTimeout(()=>{n.current&&t.refresh()},$i)},[t.refresh]),o=B(async e=>{i({action:`saving`,feedback:null});try{let n=await Ir(e);return t.replaceData(n.settings),i({action:null,feedback:{kind:n.changed?`warning`:`success`,message:n.changed?`인터넷 연결 설정을 저장했습니다. WAN을 다시 연결하는 동안 잠시 인터넷이 끊길 수 있습니다.`:`변경된 인터넷 연결 설정이 없습니다.`}}),n.reconnectScheduled&&a(),!0}catch(e){return i({action:null,feedback:{kind:`error`,message:q(e,`인터넷 연결 설정을 저장하지 못했습니다.`)}}),!1}},[t.replaceData,a]),s=B(async()=>{i({action:`reconnecting`,feedback:null});try{let e=await Lr();t.replaceData(e.settings),i({action:null,feedback:{kind:`warning`,message:`WAN 재연결을 시작했습니다. 인터넷 연결이 다시 올라오면 상태를 자동으로 새로고침합니다.`}}),e.reconnectScheduled&&a()}catch(e){i({action:null,feedback:{kind:`error`,message:q(e,`WAN 재연결을 시작하지 못했습니다.`)}})}},[t.replaceData,a]),c=B(()=>{i(e=>({...e,feedback:null}))},[]);return{...t,...r,save:o,reconnect:s,dismissFeedback:c}}var ta=`safeshield`,na=`smartsafehub`;function ra(e,t){return typeof e==`string`&&e.length>0?e:t}function ia(e){return typeof e==`string`?e:``}function aa(e){return typeof e==`number`&&Number.isFinite(e)?e:0}function oa(e){return e===!0}function sa(e){return Array.isArray(e)?e.length:0}function ca(e){return Array.isArray(e)?e.filter(e=>typeof e==`string`):[]}function la(e){return typeof e==`object`&&e&&!Array.isArray(e)?e:{}}function ua(e){let t=la(e.error),n=ia(t.code)||`UNKNOWN_ERROR`,r=ia(t.message)||`SafeShield API 요청을 처리하지 못했습니다.`;return new H(`SAFESHIELD_${n.toUpperCase()}`,r)}async function da(e,t={},n){let r=await Ht(ta,e,t,{...n?{signal:n}:{},priority:n?`low`:`normal`}),i=la(r);if(i.ok===!1)throw ua(i);return r}async function fa(e,t,n,r=t){try{return await U(na,e,t)}catch(e){if(e instanceof H&&[`RPC_PERMISSION_DENIED`,`UBUS_3`,`UBUS_6`].includes(e.code))return da(n,r);throw e}}function pa(){return{enabled:!1,collectorRunning:!1,available:!1,schemaVersion:0,volatile:!0,startedAt:0,updatedAt:0,retentionHours:0,totals:{queries:0,blocked:0},hourly:[],deviceLimit:0,devicesTruncated:!1,devices:[]}}function ma(e){let t=la(e);if(Object.keys(t).length===0)return pa();let n=la(t.schema),r=la(t.totals),i=Array.isArray(t.hourly)?t.hourly.map(e=>la(e)).map(e=>({bucketStart:aa(e.bucket_start),queries:aa(e.queries),blocked:aa(e.blocked)})).filter(e=>e.bucketStart>0).sort((e,t)=>e.bucketStart-t.bucketStart):[],a=Array.isArray(t.devices)?t.devices.map(e=>la(e)).map(e=>{let t=ia(e.ip),n=ia(e.mac);return{id:ia(e.id)||n||(t?`ip:${t}`:``),mac:n,ip:t,hostname:ia(e.hostname),identified:oa(e.identified),queries:aa(e.queries),blocked:aa(e.blocked)}}).filter(e=>e.id.length>0):[];return{enabled:oa(t.enabled),collectorRunning:oa(t.collector_running),available:t.available!==!1,schemaVersion:aa(n.version),volatile:t.volatile!==!1,startedAt:aa(t.started_at),updatedAt:aa(t.updated_at),retentionHours:aa(t.retention_hours),totals:{queries:aa(r.queries),blocked:aa(r.blocked)},hourly:i,deviceLimit:aa(t.device_limit),devicesTruncated:oa(t.devices_truncated),devices:a}}function ha(){return{available:!1,version:null,enabled:!1,active:!1,status:`unavailable`,stage:null,summary:{label:`Unavailable`,message:`SafeShield status API is unavailable`,severity:`warning`},runtime:{refreshdRunning:!1,dnsmasqRunning:!1,dnsRuntimeOk:!1,lastResult:null,lastErrorCode:null},entitlement:{plan:null,status:null},artifact:{resolved:!1,tier:null,version:null,uniqueDomains:0,rules:0},localOverrides:{enabled:!1,allowlistPath:null,blocklistPath:null},blocklist:{installed:!1,validLineCount:0,fileSizeKb:0,verificationOk:!1},health:{overall:`unavailable`},issueCounts:{warnings:0,errors:0},timestamps:{lastAttempt:0,lastSuccess:0,lastFailure:0,lastLocalApply:0,lastLocalApplyFailure:0,nextRefreshAt:0,refreshIntervalS:0}}}function ga(e){let t=la(e);if(Object.keys(t).length===0)return ha();let n=la(t.summary),r=la(t.runtime),i=la(t.entitlement),a=la(t.artifact),o=la(t.local_overrides),s=la(t.blocklist),c=la(t.health),l=la(t.timestamps);return{available:!0,version:ra(t.version,null),enabled:oa(t.enabled),active:oa(t.active),status:ra(t.status,`unknown`)??`unknown`,stage:ra(t.stage,null),summary:{label:ra(n.label,`Unknown`)??`Unknown`,message:ra(n.message,`SafeShield status is unknown`)??`SafeShield status is unknown`,severity:ra(n.severity,`warning`)??`warning`},runtime:{refreshdRunning:oa(r.refreshd_running),dnsmasqRunning:oa(r.dnsmasq_running),dnsRuntimeOk:oa(r.dns_runtime_ok),lastResult:ra(r.last_result,null),lastErrorCode:ra(r.last_error_code,null)},entitlement:{plan:ra(i.plan,null),status:ra(i.status,null)},artifact:{resolved:oa(a.resolved),tier:ra(a.tier,null),version:ra(a.version,null),uniqueDomains:aa(a.unique_domains),rules:aa(a.rules)},localOverrides:{enabled:oa(o.enabled),allowlistPath:ra(o.allowlist_path,null),blocklistPath:ra(o.blocklist_path,null)},blocklist:{installed:oa(s.installed),validLineCount:aa(s.valid_line_count),fileSizeKb:aa(s.file_size_kb),verificationOk:oa(s.verification_ok)},health:{overall:ra(c.overall,`unknown`)??`unknown`},issueCounts:{warnings:sa(t.warnings),errors:sa(t.errors)},timestamps:{lastAttempt:aa(l.last_attempt),lastSuccess:aa(l.last_success),lastFailure:aa(l.last_failure),lastLocalApply:aa(l.last_local_apply),lastLocalApplyFailure:aa(l.last_local_apply_failure),nextRefreshAt:aa(l.next_refresh_at),refreshIntervalS:aa(l.refresh_interval_s)}}}function _a(e){let t=la(la(e).values);return{enabled:oa(t.enabled),applyLocalOverrides:oa(t.apply_local_overrides)}}function va(e){let t=la(e);return{requested:oa(t.requested),accepted:oa(t.accepted),reason:ia(t.reason)}}async function ya(){try{return ma(await da(`statistics`))}catch(e){if(e instanceof H&&[`UBUS_3`,`UBUS_4`,`UBUS_5`].includes(e.code))return pa();throw e}}async function ba(e){let t=await fa(`safeshield_statistics_update`,{enabled:e},`config_update`,{values:{statistics_enabled:e}});return{changed:Array.isArray(t.changed)?t.changed.some(e=>e===`statistics_enabled`):!1,enabled:e,restarted:oa(t.restarted),reconciled:oa(t.reconciled)}}async function xa(){try{return ga(await da(`status`))}catch(e){if(e instanceof H&&[`UBUS_3`,`UBUS_4`,`UBUS_5`].includes(e.code))return ha();throw e}}async function Sa(e){return _a(await da(`config`,{},e))}async function Ca(e){let t=await fa(`safeshield_set_enabled`,{enabled:e},`set_enabled`);return{changed:oa(t.changed),accepted:oa(t.accepted),targetEnabled:oa(t.target_enabled),reconciled:oa(t.reconciled)}}async function wa(){let e=await fa(`safeshield_refresh`,{},`refresh`);return{accepted:oa(e.accepted),reason:ia(e.reason),status:ga(e.status)}}async function Ta(e){let[t,n]=await Promise.all([da(`rules_list`,{},e),Sa(e)]),r=ca(t.allow),i=ca(t.block);return{allow:r,block:i,counts:{allow:r.length,block:i.length,total:r.length+i.length},safeshieldEnabled:n.enabled,applyLocalOverrides:n.applyLocalOverrides}}async function Ea(e,t,n,r=!0){let i=await fa(e===`rule_add`?`safeshield_rule_add`:`safeshield_rule_delete`,{action:t,domain:n,refresh:r},e),a=oa(e===`rule_add`?i.added:i.deleted);return{action:t,domain:ia(i.domain)||n,changed:a,refresh:va(i.refresh),rules:await Ta()}}function Da(e,t,n=!0){return Ea(`rule_add`,e,t,n)}function Oa(e,t,n=!0){return Ea(`rule_delete`,e,t,n)}var ka=4500;function Aa(e,t){let[n,r]=L({action:null,error:null,message:null}),i=z([]),a=z([]),o=z(null),s=B(()=>{for(let e of i.current)window.clearTimeout(e);i.current=[]},[]),c=B(()=>{for(let e of a.current)window.clearTimeout(e);a.current=[]},[]),l=B(()=>{o.current!==null&&(window.clearTimeout(o.current),o.current=null)},[]),u=B(e=>{l(),r({action:e,error:null,message:null})},[l]),d=B(e=>{l(),r({action:null,error:null,message:e}),o.current=window.setTimeout(()=>{o.current=null,r(e=>e.error!==null||e.action!==null?e:{...e,message:null})},ka)},[l]),f=B(t=>{s(),i.current=t.map(t=>window.setTimeout(()=>{e()},t))},[s,e]),p=B(e=>{c(),a.current=e.map(e=>window.setTimeout(()=>{t()},e))},[c,t]);R(()=>()=>{s(),c(),l()},[l,c,s]);let m=B(async t=>{u(t?`enable`:`disable`);try{let n=await Ca(t);d(n.changed?t?`SafeShield 보호 활성화 요청을 적용했습니다.`:`SafeShield 보호 비활성화 요청을 적용했습니다.`:t?`SafeShield 보호가 이미 활성화 상태입니다.`:`SafeShield 보호가 이미 비활성화 상태입니다.`),await e(),f([800,2e3,5e3])}catch(e){r({action:null,error:q(e,`SafeShield 작업을 수행하지 못했습니다.`),message:null})}},[u,e,f,d]),h=B(async e=>{u(e?`statistics-enable`:`statistics-disable`);try{let n=await ba(e);await t(),d(n.changed?n.reconciled?e?`차단 통계 수집을 활성화했습니다.`:`차단 통계 수집을 비활성화했습니다.`:`차단 통계 설정을 저장했습니다.`:e?`차단 통계 수집이 이미 활성화되어 있습니다.`:`차단 통계 수집이 이미 비활성화되어 있습니다.`),n.changed&&p([500,1500])}catch(e){r({action:null,error:q(e,`차단 통계 설정을 변경하지 못했습니다.`),message:null})}},[u,t,p,d]),g=B(async()=>{u(`refresh`);try{await wa(),r({action:null,error:null,message:null}),f([700,2500,6e3,12e3])}catch(e){r({action:null,error:q(e,`SafeShield 작업을 수행하지 못했습니다.`),message:null})}},[u,f]),_=B(()=>{l(),r(e=>({...e,error:null,message:null}))},[l]);return{...n,dismissFeedback:_,refreshBlocklist:g,setEnabled:m,setStatisticsEnabled:h}}var ja=500,Ma=45e3;function Na(e){return new Promise(t=>window.setTimeout(t,e))}function Pa(e){return e===`allow`?`허용`:`차단`}function Fa(e,t){if(!t.changed)return e===`add`?t.action===`allow`?`이미 허용 목록에 있는 도메인입니다.`:`이미 차단 목록에 있는 도메인입니다.`:`이미 삭제된 규칙입니다.`;let n=Pa(t.action),r=e===`add`?`저장`:`삭제`;return t.rules.safeshieldEnabled?t.rules.applyLocalOverrides?`${t.domain} ${n} 규칙을 ${r}했습니다.`:`${t.domain} ${n} 규칙을 ${r}했습니다. 로컬 규칙 적용 설정이 꺼져 있어 아직 DNS에는 반영되지 않습니다.`:`${t.domain} ${n} 규칙을 ${r}했습니다. SafeShield를 켜면 다음 갱신에서 적용됩니다.`}function Ia(e,t){let n=e===`add`?`추가`:`삭제`;return`${t.domain} ${Pa(t.action)} 규칙 ${n}를 저장했습니다. 로컬 규칙을 DNS에 적용하고 있습니다…`}function La(e,t){let n=e===`add`?`추가`:`삭제`;return`${t.domain} ${Pa(t.action)} 규칙 ${n}와 DNS 적용이 완료되었습니다.`}async function Ra(e){let t=Date.now()+Ma,n=null;for(;Date.now()e.timestamps.lastLocalApply)return{kind:`success`,status:n};if(n.timestamps.lastLocalApplyFailure>e.timestamps.lastLocalApplyFailure)return{kind:`failure`,status:n};await Na(ja)}return{kind:`timeout`,status:n}}function za(e){let t=li({active:e,fallbackError:`사용자 규칙을 불러오지 못했습니다.`,loader:Ta}),[n,r]=L({action:null,error:null,feedback:null}),i=z(0),a=B(async(e,n,a,o)=>{let s=++i.current,c=e=>{i.current===s&&r(e)};c({action:{operation:e,action:n,domain:a},error:null,feedback:null});try{let r=await xa(),i=e===`add`?await Da(n,a,!0):await Oa(n,a,!0);if(t.replaceData(i.rules),o?.(),!(i.changed&&i.rules.safeshieldEnabled&&i.rules.applyLocalOverrides))return c({action:null,error:null,feedback:Fa(e,i)}),!0;if(!i.refresh.accepted)return c({action:null,error:`규칙은 저장되었지만 로컬 DNS 적용 요청을 시작하지 못했습니다${i.refresh.reason?` (${i.refresh.reason})`:``}.`,feedback:null}),!0;c({action:null,error:null,feedback:Ia(e,i)});let s;try{s=await Ra(r)}catch{return c({action:null,error:`규칙은 저장되었지만 DNS 적용 상태를 확인하지 못했습니다. SafeShield 상태를 확인해 주세요.`,feedback:null}),!0}return s.kind===`success`?(c({action:null,error:null,feedback:La(e,i)}),!0):s.kind===`disabled`?(c({action:null,error:null,feedback:`규칙은 저장되었지만 SafeShield가 꺼져 있어 DNS 적용을 대기하고 있습니다.`}),!0):s.kind===`failure`?(c({action:null,error:`규칙은 저장되었지만 로컬 DNS 적용이 실패했습니다. SafeShield 상태를 확인해 주세요.`,feedback:null}),!0):(c({action:null,error:`규칙은 저장되었지만 제한 시간 안에 로컬 DNS 적용 완료를 확인하지 못했습니다.`,feedback:null}),!0)}catch(e){return c({action:null,error:q(e,`사용자 규칙을 변경하지 못했습니다.`),feedback:null}),!1}},[t.replaceData]),o=B((e,t,n)=>a(`add`,e,t,n),[a]),s=B((e,t)=>a(`delete`,e,t),[a]),c=B(async()=>{r(e=>({...e,error:null})),await t.refresh()},[t.refresh]),l=B(()=>{r(e=>({...e,feedback:null}))},[]);return{...t,...n,error:n.error??t.error,addRule:o,deleteRule:s,dismissFeedback:l,refresh:c}}var Ba=6e4;function Va(e,t=!0){return li({active:e,fallbackError:`SafeShield 통계를 불러오지 못했습니다.`,loader:ya,...t?{pollInterval:Ba}:{}})}var Ha=[{label:`갱신 준비`,description:`차단 목록 갱신을 준비하고 있습니다.`,stages:[`starting`,`boot_delay`,`boot_refresh`,`boot_refresh_skipped`,`scheduled_refresh`,`init`]},{label:`최신 차단 목록 확인`,description:`서버에서 최신 차단 목록 정보를 확인하고 있습니다.`,stages:[`resolve_api`]},{label:`차단 목록 다운로드`,description:`최신 차단 목록을 안전하게 다운로드하고 있습니다.`,stages:[`download_artifact`]},{label:`사용자 규칙 적용`,description:`허용·차단 사용자 규칙을 새 차단 목록에 반영하고 있습니다.`,stages:[`local_overrides`,`merge`]},{label:`보호 규칙 적용`,description:`새 차단 규칙을 설치하고 DNS 보호 서비스를 갱신하고 있습니다.`,stages:[`install`,`restart_dnsmasq`]},{label:`보호 상태 확인`,description:`새 차단 목록과 DNS 보호가 정상적으로 동작하는지 확인하고 있습니다.`,stages:[`runtime_check`,`blocklist_verify`]}],Ua=new Map;Ha.forEach((e,t)=>{e.stages.forEach(e=>Ua.set(e,t))});function Wa(e,t){return e===`running`||t!==null&&Ua.has(t)}function Ga(e){let t=e===null?0:Ua.get(e)??0,n=Ha[t];return{description:n.description,label:n.label,number:t+1,total:Ha.length}}function Ka(e){return(e??``).trim().toLowerCase()}function qa(e,t){let n=Ka(e);if(n.includes(`upgrade`)||n.includes(`version`))return`현재 SafeShield 버전으로는 갱신을 계속할 수 없습니다. 시스템 업데이트에서 SafeShield를 최신 버전으로 업데이트해 주세요.`;if(n.includes(`entitlement`)||n.includes(`license`)||n.includes(`unauthorized`)||n.includes(`forbidden`))return`차단 목록을 사용할 권한을 확인하지 못했습니다. SmartSafeHub 계정과 요금제 상태를 확인해 주세요.`;if(n.includes(`dnsmasq`)||n.includes(`restart`)||n.includes(`runtime`))return`DNS 보호 서비스를 정상적으로 갱신하지 못했습니다. 잠시 후 다시 갱신해 보고 문제가 계속되면 상태 점검 정보를 확인해 주세요.`;if(n.includes(`verify`)||n.includes(`sanity`))return`새 차단 목록을 적용한 뒤 보호 상태 확인을 완료하지 못했습니다. 다시 갱신해 주세요.`;if(n.includes(`download`)||n.includes(`fetch`)||n.includes(`timeout`)||n.includes(`network`)||n.includes(`wan`))return`차단 목록을 다운로드하지 못했습니다. 인터넷 연결을 확인한 뒤 다시 갱신해 주세요.`;if(n.includes(`resolve`)||n.includes(`manifest`)||n.includes(`artifact`)||n.includes(`api`))return`최신 차단 목록 정보를 확인하지 못했습니다. 인터넷 연결을 확인한 뒤 다시 갱신해 주세요.`;if(n.includes(`override`)||n.includes(`allowlist`)||n.includes(`merge`)||n.includes(`rule`))return`사용자 허용·차단 규칙을 새 차단 목록에 반영하지 못했습니다. 사용자 규칙을 확인한 뒤 다시 갱신해 주세요.`;if(n.includes(`install`)||n.includes(`write`)||n.includes(`space`))return`새 차단 규칙을 장치에 적용하지 못했습니다. 저장 공간과 SafeShield 상태를 확인한 뒤 다시 갱신해 주세요.`;let r=Ga(t);return r.number===2?`최신 차단 목록 정보를 확인하는 중 문제가 발생했습니다. 인터넷 연결을 확인한 뒤 다시 갱신해 주세요.`:r.number===3?`차단 목록을 다운로드하는 중 문제가 발생했습니다. 인터넷 연결을 확인한 뒤 다시 갱신해 주세요.`:r.number===4?`사용자 규칙을 적용하는 중 문제가 발생했습니다. 사용자 규칙을 확인한 뒤 다시 갱신해 주세요.`:r.number===5?`새 보호 규칙을 적용하는 중 문제가 발생했습니다. 잠시 후 다시 갱신해 주세요.`:r.number===6?`새 차단 목록의 보호 상태를 확인하지 못했습니다. 다시 갱신해 주세요.`:`차단 목록 갱신을 완료하지 못했습니다. 잠시 후 다시 갱신해 주세요.`}var Ja=3e3;function Ya(e){return e?Wa(e.status,e.stage):!1}function Xa(e){return li({active:e,fallbackError:`SafeShield 상태를 불러오지 못했습니다.`,loader:xa,pollInterval:e=>Ya(e)?Ja:null})}var Za=3e5,Qa=1e3,$a=3e3,eo=`luci-app-smartsafehub`;function to(e=!0){let[t,n]=L(null),r=li({active:e||t===`checking`||t===`installing`,fallbackError:`소프트웨어 업데이트 상태를 불러오지 못했습니다.`,loader:hr,pollInterval:e=>e?.phase===`checking`?Qa:e?.phase===`installing`?$a:Za,refreshOnFocus:!0,refreshOnVisible:t===`checking`||t===`installing`});R(()=>{n(r.data?.phase??null)},[r.data?.phase]);let[i,a]=L(null),[o,s]=L(null),[c,l]=L(null),u=z(!1),d=z(void 0);R(()=>{if(u.current||!r.data)return;let e=r.data.lastInstallAt??null,t=d.current;if(t===void 0){d.current=e;return}if(d.current=e,e===null||e===t||r.data.phase!==`idle`)return;let n=r.data.packages.find(e=>e.name===eo)?.installedVersion,i=window.__SMARTHUB_BOOTSTRAP__?.assetVersion;n&&i&&n!==i&&(u.current=!0,window.location.reload())},[r.data]);let f=B(e=>{n(e),r.data&&r.replaceData({...r.data,phase:e,lastError:null})},[r]),p=B(async()=>{a(`check`),s(null),l(null);try{if(!(await Sr()).accepted)throw Error(`업데이트 확인 요청이 접수되지 않았습니다.`);f(`checking`),a(null),window.setTimeout(()=>void r.refresh(),500)}catch(e){a(null),s(q(e,`업데이트 확인을 시작하지 못했습니다.`))}},[f,r]),m=B(async()=>{a(`install`),s(null),l(null);try{if(!(await Cr()).accepted)throw Error(`업데이트 설치 요청이 접수되지 않았습니다.`);f(`installing`),a(null),window.setTimeout(()=>void r.refresh(),750)}catch(e){a(null),s(q(e,`업데이트 설치를 시작하지 못했습니다.`))}},[f,r]),h=B(async e=>{a(`settings`),s(null),l(null);try{let t=await wr(e);return r.data&&r.replaceData({...r.data,settings:t}),a(null),l(`자동 업데이트 설정을 저장했습니다.`),!0}catch(e){return a(null),s(q(e,`자동 업데이트 설정을 저장하지 못했습니다.`)),!1}},[r]),g=B(()=>{s(null),l(null)},[]);return{...r,action:i,actionError:o,message:c,check:p,dismissFeedback:g,install:m,saveSettings:h}}var no=6e4;function ro(e){return li({active:e,fallbackError:`장치 상태를 불러오지 못했습니다.`,loader:Xn,pollInterval:no})}function io(e){let t=li({active:e,fallbackError:`예약 재부팅 설정을 불러오지 못했습니다.`,loader:kr,refreshOnFocus:!0}),[n,r]=L({saving:!1,saveError:null,saveMessage:null}),i=B(async e=>{r({saving:!0,saveError:null,saveMessage:null});try{let n=await Ar(e);return t.replaceData(n),r({saving:!1,saveError:null,saveMessage:e.enabled?`예약 재부팅 일정을 저장했습니다.`:`예약 재부팅을 껐습니다.`}),!0}catch(e){return r({saving:!1,saveError:q(e,`예약 재부팅 설정을 저장하지 못했습니다.`),saveMessage:null}),!1}},[t.replaceData]),a=B(()=>{r(e=>({...e,saveError:null,saveMessage:null}))},[]);return{...t,...n,dismissSaveFeedback:a,saveSettings:i}}function ao(e){return e.trim().toLowerCase().replace(/[^a-z0-9._-]+/g,`-`)||`openwrt`}function oo(e,t){let n=new Date(t*1e3).toISOString().replace(/[:.]/g,`-`);return`smartsafehub-diagnostics-${ao(e)}-${n}.json`}function so(e,t){let n=new Blob([`${JSON.stringify(t,null,2)}\n`],{type:`application/json;charset=utf-8`}),r=URL.createObjectURL(n),i=document.createElement(`a`);i.href=r,i.download=e,i.style.display=`none`,(document.body??document.documentElement).appendChild(i),i.click(),i.remove(),window.setTimeout(()=>URL.revokeObjectURL(r),1e3)}function co(e){return{enabled:e.available?e.enabled:null,status:e.status,lastResult:e.runtime.lastResult,validLineCount:e.blocklist.validLineCount,blocklistInstalled:e.blocklist.installed,dnsmasqOk:e.runtime.dnsRuntimeOk}}function lo(){return{networks:[],totalClients:0,guest:null}}function uo(){return{enabled:null,status:`unavailable`,lastResult:null,validLineCount:0,blocklistInstalled:!1,dnsmasqOk:!1}}async function fo(e){if(!e)throw Error(`시스템 상태가 준비되지 않았습니다. 새로고침한 뒤 다시 시도해 주세요.`);let[t,n,r]=await Promise.allSettled([Gr(),xa(),rr()]);return{generatedAt:Math.floor(Date.now()/1e3),health:r.status===`fulfilled`?r.value:null,system:e,wifi:t.status===`fulfilled`?t.value:lo(),safeshield:n.status===`fulfilled`?co(n.value):uo()}}function po(e){let[t,n]=L({action:null,error:null,message:null,rebootAccepted:!1}),r=B(async()=>{n(e=>({...e,action:`diagnostics`,error:null,message:null}));try{let t=await fo(e);so(oo(t.system.device.hostname,t.generatedAt),t),n(e=>({...e,action:null,message:`진단 정보 파일을 만들었습니다.`}))}catch(e){n(t=>({...t,action:null,error:q(e,`시스템 작업을 완료하지 못했습니다.`)}))}},[e]),i=B(async()=>{n(e=>({...e,action:`reboot`,error:null,message:null}));try{let e=await Pr();n({action:null,error:null,message:e.accepted?`재부팅을 시작했습니다. 잠시 후 공유기에 다시 연결해 주세요.`:`재부팅 요청이 접수되지 않았습니다.`,rebootAccepted:e.accepted})}catch(e){n(t=>({...t,action:null,error:q(e,`시스템 작업을 완료하지 못했습니다.`)}))}},[]),a=B(()=>{n(e=>({...e,error:null,message:null}))},[]);return{...t,dismissFeedback:a,downloadDiagnostics:r,reboot:i}}function mo(e){let t=li({active:e,fallbackError:`시간대 설정을 불러오지 못했습니다.`,loader:Tr,refreshOnFocus:!0}),[n,r]=L({saving:!1,syncing:!1,saveError:null,saveMessage:null}),i=B(async e=>{r({saving:!0,syncing:!1,saveError:null,saveMessage:null});try{let n=await Dr(e);return t.replaceData(n),r({saving:!1,syncing:!1,saveError:null,saveMessage:`시간대를 저장하고 시스템 시간 설정에 적용했습니다.`}),!0}catch(e){return r({saving:!1,syncing:!1,saveError:q(e,`시간대 설정을 저장하지 못했습니다.`),saveMessage:null}),!1}},[t.replaceData]),a=B(async()=>{r({saving:!1,syncing:!0,saveError:null,saveMessage:null});try{await Or(),await new Promise(e=>{window.setTimeout(e,1500)});let e=await Tr();return t.replaceData(e),r({saving:!1,syncing:!1,saveError:null,saveMessage:`NTP 시간 동기화를 요청하고 장치 시간을 다시 확인했습니다.`}),!0}catch(e){return r({saving:!1,syncing:!1,saveError:q(e,`시간을 동기화하지 못했습니다.`),saveMessage:null}),!1}},[t.replaceData]),o=B(()=>{r(e=>({...e,saveError:null,saveMessage:null}))},[]);return{...t,...n,dismissSaveFeedback:o,saveTimezone:i,syncTime:a}}var ho=5e3,go=5e3;function _o(e){if(!e?.pairingCode||e.accountRegistered===!0)return!1;if(!e.pairingExpiresAt)return!0;let t=Date.parse(e.pairingExpiresAt);return!Number.isFinite(t)||t>Date.now()}function vo(e){let[t,n]=L(null),[r,i]=L(null),[a,o]=L(null),[s,c]=L(e),[l,u]=L(!1),[d,f]=L(!1),p=z(null),m=z(0),h=z(!0),g=B((e,t)=>!h.current||t!==m.current?!1:(n(e),i(null),c(!1),!0),[]),_=B(()=>{if(!e)return Promise.resolve(null);if(p.current)return p.current;let t=++m.current;o(null),u(!0);let n=(async()=>{try{let e=await tr();return g(e,t)&&o(null),e}catch{let e=await er().catch(()=>null);return e&&g(e,t),h.current&&t===m.current&&o(`SmartSafeHub 서버의 최신 상태 확인이 지연되고 있습니다.`),e}finally{h.current&&u(!1),p.current=null}})();return p.current=n,n},[e,g]),v=B(async()=>{let e=++m.current;f(!0);try{let t=await nr();return g(t,e)&&o(null),t}finally{f(!1)}},[g]);R(()=>{if(!e)return;let t=!0;h.current=!0;let n=++m.current;return c(!0),i(null),o(null),er().then(e=>{t&&(g(e,n)&&c(!1),_())}).catch(()=>{t&&(n===m.current&&(i(`기기 등록 상태를 확인하지 못했습니다.`),c(!1)),_())}),()=>{t=!1,m.current+=1}},[e,_,g]),R(()=>{if(!e)return;let t=!0,n=window.setInterval(()=>{if(document.visibilityState===`hidden`||p.current)return;let e=++m.current;er().then(n=>{t&&g(n,e)&&o(null)}).catch(()=>{})},go);return()=>{t=!1,window.clearInterval(n)}},[e,g]),R(()=>()=>{h.current=!1},[]);let y=Xe(()=>_o(t)||t?.pairingOperation?.phase===`starting`||t?.pairingOperation?.phase===`running`,[t]);return R(()=>{if(!e||!y)return;let t=!0,n=0,r=async()=>{let e=++m.current,i=await er().catch(()=>null);t&&i&&g(i,e),t&&(n=window.setTimeout(()=>void r(),ho))};return n=window.setTimeout(()=>void r(),ho),()=>{t=!1,window.clearTimeout(n)}},[e,g,y]),{data:t,error:r,loading:s,pairingBusy:d,refresh:_,refreshing:l,requestPairing:v,syncError:a}}var yo=6e4;function bo(e,t){let[n,r]=L(null),i=z(0);return R(()=>{e&&t!==null&&(i.current+=1,r(t))},[e,t]),R(()=>{if(e)return;let t=!1,n=null,a=!1,o=0,s=async(e=!1)=>{if(t||document.visibilityState===`hidden`||a||!e&&Date.now()-oe??null)}finally{a=!1}},c=()=>{document.visibilityState===`visible`&&s()},l=()=>void s();return s(!0),n=window.setInterval(()=>void s(),yo),document.addEventListener(`visibilitychange`,c),window.addEventListener(`focus`,l),()=>{t=!0,i.current+=1,n!==null&&window.clearInterval(n),document.removeEventListener(`visibilitychange`,c),window.removeEventListener(`focus`,l)}},[e]),n}var xo=[2e3,3e3];function So(e){return new Promise(t=>window.setTimeout(t,e))}function Co(e){let t=li({active:e,fallbackError:`Wi-Fi 설정을 처리하지 못했습니다.`,loader:Gr}),n=z(!0),r=z(e),[i,a]=L({updatingSection:null,feedback:null});R(()=>(n.current=!0,()=>{n.current=!1}),[]),R(()=>{r.current=e},[e]);let o=B(async()=>{for(let e of xo){if(await So(e),!n.current||!r.current)return;await t.refresh()}},[t.refresh]),s=B(async e=>{a({updatingSection:e.section,feedback:null});try{let n=await Kr(e);return t.replaceData({...n.summary,guest:t.data?.guest??null,guestError:t.data?.guestError??!1}),a({updatingSection:null,feedback:{kind:`success`,message:n.changed?`Wi-Fi 설정을 저장하고 무선 네트워크를 다시 불러왔습니다.`:`변경된 Wi-Fi 설정이 없습니다.`}}),n.reloaded&&o(),!0}catch(e){let t=e instanceof H&&[`NETWORK_ERROR`,`RPC_TIMEOUT`].includes(e.code);return a({updatingSection:null,feedback:{kind:t?`warning`:`error`,message:t?`Wi-Fi가 다시 시작되면서 연결이 끊어졌을 수 있습니다. 변경한 SSID로 다시 연결한 뒤 화면을 새로고침해 주세요.`:q(e,`Wi-Fi 설정을 처리하지 못했습니다.`)}}),!1}},[o,t.replaceData,t.data]),c=B(async e=>{a({updatingSection:`ssh_guest`,feedback:null});try{let n=await qr(e);return await t.refresh(),a({updatingSection:null,feedback:{kind:`success`,message:n.changed?`게스트 Wi-Fi 설정이 저장되었습니다.`:`변경된 게스트 Wi-Fi 설정이 없습니다.`}}),n.reloaded&&o(),!0}catch(e){let t=e instanceof H&&[`NETWORK_ERROR`,`RPC_TIMEOUT`].includes(e.code);return a({updatingSection:null,feedback:{kind:t?`warning`:`error`,message:t?`무선 네트워크가 다시 시작되어 연결이 끊어졌을 수 있습니다. 다시 연결한 뒤 확인해 주세요.`:q(e,`게스트 Wi-Fi 설정을 처리하지 못했습니다.`)}}),!1}},[t.refresh,o]),l=B(()=>{a(e=>({...e,feedback:null}))},[]);return{...t,...i,update:s,updateGuest:c,dismissFeedback:l}}function J({action:e,description:t,eyebrow:n,id:r,title:i}){return W(`div`,{class:`ssh-section-heading mb-4 min-w-0`,children:W(`div`,{class:e?`grid min-w-0 items-start gap-x-4 gap-y-3 sm:grid-cols-[minmax(0,1fr)_auto]`:``,children:[W(`div`,{class:`min-w-0`,children:[W(`p`,{class:`m-0 text-[0.68rem] font-black uppercase tracking-[0.18em] text-teal-700`,children:n}),W(`h2`,{class:`mt-2 mb-0 min-w-0 text-xl font-black tracking-tight text-slate-950`,id:r,children:i}),W(`p`,{class:`mt-2 mb-0 text-sm leading-6 text-slate-500`,children:t})]}),e?W(`div`,{class:`min-w-0 justify-self-start sm:mt-3 sm:justify-self-end`,children:e}):null]})})}var wo=new Intl.NumberFormat(`ko-KR`),To=new Intl.DateTimeFormat(`ko-KR`,{dateStyle:`medium`,timeStyle:`short`});function Eo(e){if(!Number.isFinite(e)||e<0)return`확인 불가`;let t=Math.floor(e/86400),n=Math.floor(e%86400/3600),r=Math.floor(e%3600/60);return t>0?`${t}일 ${n}시간`:n>0?`${n}시간 ${r}분`:`${Math.max(r,1)}분`}function Do(e){if(!Number.isFinite(e)||e<0)return`확인 불가`;let t=[`B`,`KB`,`MB`,`GB`],n=e,r=0;for(;n>=1024&&r864e10?`기록 없음`:!Number.isFinite(t)||e>t+300||t-e>=604800?Ao(e):jo(e,t)}function No(e){let t=/^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})Z$/.exec(e??``);if(!t)return null;let[,n,r,i,a,o,s]=t,c=Date.UTC(Number(n),Number(r)-1,Number(i),Number(a),Number(o),Number(s)),l=new Date(c);return l.getUTCFullYear()!==Number(n)||l.getUTCMonth()+1!==Number(r)||l.getUTCDate()!==Number(i)||l.getUTCHours()!==Number(a)||l.getUTCMinutes()!==Number(o)||l.getUTCSeconds()!==Number(s)||c<=0?null:c/1e3}function Po(e,t){if(!Number.isFinite(e)||e<=0)return`확인 불가`;let n=t.replaceAll(` `,`_`);try{return new Intl.DateTimeFormat(`ko-KR`,{dateStyle:`medium`,timeStyle:`medium`,timeZone:n}).format(new Date(e*1e3))}catch{return To.format(new Date(e*1e3))}}function Fo(e){if(!Number.isFinite(e)||e<=0)return`확인 불가`;let t=Math.floor(e/3600),n=Math.floor(e%3600/60);return t>0?n>0?`${t}시간 ${n}분`:`${t}시간`:`${n}분`}function Io(e){let t=Math.max(0,e.total),n=e.free+e.buffered+e.cached,r=Math.min(t,Math.max(0,e.available>0?e.available:n)),i=Math.max(0,t-r);return{used:i,percent:t>0?i/t*100:0}}function Lo(e,t){let n=e.metadata[t];return typeof n==`string`&&n.trim().length>0?n.trim():null}function Ro(e,t){let n=e.metadata[t];return typeof n==`number`&&Number.isFinite(n)?n:null}function zo(e,t){let n=e.metadata[t];return typeof n==`boolean`?n:null}function Bo(e,t){let n=Lo(e,`error_code`);return n?`${t} (오류 코드: ${n})`:t}function Vo(e){let t=Math.max(0,Math.floor(e));if(t<60)return`${t}초`;let n=Math.floor(t/60),r=t%60;if(n<60)return r>0?`${n}분 ${r}초`:`${n}분`;let i=Math.floor(n/60),a=n%60;return a>0?`${i}시간 ${a}분`:`${i}시간`}function Ho(e){let t=Lo(e,`from_version`),n=Lo(e,`to_version`);return t&&n?`${t} → ${n}`:n?`${n} 버전이 적용되었습니다.`:null}function Uo(e){return e?e.toUpperCase():null}function Wo(e){switch(e){case`dhcp`:return`자동 IP(DHCP)`;case`pppoe`:return`PPPoE`;case`static`:return`고정 IPv4`;default:return e.toUpperCase()}}function Go(e){switch(e.eventType){case`system.booted`:return{title:`공유기 부팅 감지`,description:`공유기가 켜지거나 재시작된 것을 확인했습니다.`};case`network.internet.disconnected`:return{title:`인터넷 연결 끊김 감지`,description:`공유기에서 인터넷 연결을 확인할 수 없었습니다.`};case`network.internet.recovered`:{let t=Ro(e,`downtime_seconds`);return{title:`인터넷 연결 복구`,description:t!==null&&t>0?`약 ${Vo(t)} 동안 인터넷 연결이 확인되지 않았습니다.`:`공유기에서 인터넷 연결을 다시 확인했습니다.`}}case`safeshield.protection.enabled`:return{title:`SafeShield 보호 활성화`,description:`DNS 보호 기능이 활성화되었습니다.`};case`safeshield.protection.disabled`:return{title:`SafeShield 보호 비활성화`,description:`DNS 보호 기능이 비활성화되었습니다.`};case`safeshield.blocklist.updated`:{let t=Ro(e,`domain_count`),n=Lo(e,`artifact_version`),r=[];return t!==null&&t>0&&r.push(`${Y(t)}개 도메인 적용`),n&&r.push(`목록 ${n}`),{title:`SafeShield 차단 목록 업데이트 완료`,description:r.length>0?r.join(` · `):`최신 차단 목록을 적용했습니다.`}}case`safeshield.blocklist.update_failed`:return{title:`SafeShield 차단 목록 업데이트 실패`,description:Bo(e,`차단 목록을 업데이트하지 못했습니다.`)};case`safeshield.statistics.enabled`:return{title:`SafeShield 차단 통계 수집 활성화`,description:`기기별 차단 통계 수집을 활성화했습니다.`};case`safeshield.statistics.disabled`:return{title:`SafeShield 차단 통계 수집 비활성화`,description:`기기별 차단 통계 수집을 비활성화했습니다.`};case`safeshield.rule.added`:return{title:Lo(e,`rule_action`)===`allow`?`사용자 허용 규칙 추가`:`사용자 차단 규칙 추가`,description:`SafeShield 사용자 규칙이 변경되었습니다.`};case`safeshield.rule.removed`:return{title:Lo(e,`rule_action`)===`allow`?`사용자 허용 규칙 삭제`:`사용자 차단 규칙 삭제`,description:`SafeShield 사용자 규칙이 변경되었습니다.`};case`network_watch.anomaly.warning`:{let t=Lo(e,`source_ip`),n=Ro(e,`connections`);return{title:`Network Watch 통신 증가 감지`,description:`${t?`${t} 기기에서 `:`연결된 기기에서 `}외부 연결이 연속해서 높은 수준으로 관찰되었습니다${n===null?``:` (동시 연결 ${Y(n)}개)`}. 악성 통신으로 확정된 것은 아닙니다.`}}case`settings.netwatch.enabled`:return{title:`Network Watch 켜짐`,description:`네트워크 연결 관찰 기능을 활성화했습니다.`};case`settings.netwatch.disabled`:return{title:`Network Watch 꺼짐`,description:`네트워크 연결 관찰 기능을 비활성화했습니다.`};case`settings.scheduled_reboot.updated`:{let t=zo(e,`enabled`);return{title:`예약 재부팅 설정 변경`,description:t===!0?`예약 재부팅 설정을 저장하고 새 일정에 적용했습니다.`:t===!1?`예약 재부팅을 비활성화했습니다.`:`예약 재부팅 설정을 변경했습니다.`}}case`settings.software_updates.updated`:return{title:`관리 소프트웨어 업데이트 설정 변경`,description:zo(e,`auto_install`)===!0?`자동 설치를 포함한 업데이트 설정을 저장했습니다.`:`관리 소프트웨어 업데이트 설정을 저장했습니다.`};case`settings.health_reporter.enabled`:return{title:`원격 상태 보고 활성화`,description:`장치 상태를 SmartSafeHub Cloud에 보고하도록 설정했습니다.`};case`settings.health_reporter.disabled`:return{title:`원격 상태 보고 비활성화`,description:`SmartSafeHub Cloud로의 장치 상태 보고를 중지했습니다.`};case`settings.activity_cloud_sync.enabled`:return{title:`Cloud 활동 기록 전송 활성화`,description:`이후 발생하는 활동을 SmartSafeHub Cloud에 전송하도록 설정했습니다.`};case`settings.activity_cloud_sync.disabled`:return{title:`Cloud 활동 기록 전송 비활성화`,description:`Cloud 전송을 중지했습니다. 로컬 최근 활동은 계속 기록됩니다.`};case`settings.timezone.updated`:{let t=Lo(e,`zonename`);return{title:`시간대 설정 변경`,description:t?`공유기 시간대를 ${t}(으)로 변경했습니다.`:`공유기 시간대를 변경했습니다.`}}case`settings.wifi.updated`:return{title:`Wi-Fi 설정 변경`,description:`Wi-Fi 설정을 저장하고 무선 네트워크에 적용했습니다.`};case`settings.wan.updated`:{let t=Lo(e,`protocol`);return{title:`인터넷 설정 변경`,description:t?`${Wo(t)} 방식으로 WAN 설정을 변경했습니다.`:`WAN 인터넷 연결 설정을 변경했습니다.`}}case`settings.lan.updated`:return{title:`LAN 설정 변경`,description:`내부 네트워크 및 DHCP 설정을 변경했습니다.`};case`settings.iptv.updated`:{let t=zo(e,`enabled`),n=Lo(e,`provider`),r=n===`skb`?`SK Broadband`:n===`lgu`?`LG U+`:null;return{title:`IPTV 설정 변경`,description:t===!1?`IPTV 기능을 비활성화했습니다.`:r?`${r} IPTV 설정을 적용했습니다.`:`IPTV 설정을 적용했습니다.`}}case`software.update.completed`:return{title:`관리 소프트웨어 업데이트 완료`,description:Ho(e)};case`software.update.failed`:return{title:`관리 소프트웨어 업데이트 실패`,description:Bo(e,`관리 소프트웨어를 업데이트하지 못했습니다.`)};case`firmware.update.started`:return{title:`펌웨어 업데이트 시작`,description:`펌웨어 설치를 시작했습니다. 완료 과정에서 공유기가 재부팅됩니다.`};case`firmware.update.failed`:return{title:`펌웨어 업데이트 실패`,description:Bo(e,`펌웨어 설치 준비 또는 검증에 실패했습니다.`)};case`license.activated`:{let t=Uo(Lo(e,`to_plan`));return{title:`요금제 활성화`,description:t?`${t} 요금제가 활성화되었습니다.`:`요금제가 활성화되었습니다.`}}case`license.changed`:{let t=Uo(Lo(e,`from_plan`)),n=Uo(Lo(e,`to_plan`));return{title:`요금제 변경`,description:t&&n?`${t} → ${n}`:n?`${n} 요금제로 변경되었습니다.`:`요금제 상태가 변경되었습니다.`}}case`license.cleared`:return{title:`계정 권한 해제`,description:Lo(e,`reason`)===`manual`?`이 공유기에서 계정 권한 연결을 해제했습니다.`:`서버의 계정 권한 상태가 반영되어 이 공유기의 권한 연결이 해제되었습니다.`};case`health.issue.started`:{let t=Ro(e,`warning_count`)??0,n=Ro(e,`critical_count`)??0,r=[];return n>0&&r.push(`이상 ${Y(n)}건`),t>0&&r.push(`주의 ${Y(t)}건`),{title:`확인이 필요한 장치 상태 발견`,description:r.length>0?r.join(` · `):`장치 상태에서 확인이 필요한 항목이 발견되었습니다.`}}case`health.issue.changed`:{let t=Ro(e,`warning_count`)??0,n=Ro(e,`critical_count`)??0,r=[];return n>0&&r.push(`이상 ${Y(n)}건`),t>0&&r.push(`주의 ${Y(t)}건`),{title:`확인이 필요한 장치 상태 변경`,description:r.length>0?r.join(` · `):`확인이 필요한 장치 상태 항목이 변경되었습니다.`}}case`health.issue.resolved`:return{title:`장치 상태 정상으로 복구`,description:`이전에 확인이 필요했던 장치 상태가 정상으로 돌아왔습니다.`};default:return{title:`SmartSafeHub 활동`,description:`장치에서 새로운 활동이 기록되었습니다.`}}}function Ko(e){return e===`error`?{icon:W(Cn,{class:`size-4`}),surface:`bg-rose-50 text-rose-700 ring-rose-200`}:e===`warning`?{icon:W(Cn,{class:`size-4`}),surface:`bg-amber-50 text-amber-800 ring-amber-200`}:e===`success`?{icon:W(Sn,{class:`size-4`}),surface:`bg-emerald-50 text-emerald-700 ring-emerald-200`}:{icon:W(dn,{class:`size-4`}),surface:`bg-slate-100 text-slate-600 ring-slate-200`}}function qo(e){let t=new Date(e*1e3);return new Intl.DateTimeFormat(`ko-KR`,{hour:`2-digit`,minute:`2-digit`,hour12:!1}).format(t)}function Jo(e){let t=new Date(e*1e3);return`${t.getFullYear()}-${t.getMonth()+1}-${t.getDate()}`}function Yo(e){let t=new Date(e*1e3),n=new Date,r=new Date(n.getFullYear(),n.getMonth(),n.getDate()),i=new Date(t.getFullYear(),t.getMonth(),t.getDate()),a=Math.round((r.getTime()-i.getTime())/864e5);return a===0?`오늘`:a===1?`어제`:new Intl.DateTimeFormat(`ko-KR`,{month:`long`,day:`numeric`,weekday:`short`}).format(t)}function Xo({compact:e=!1,events:t}){if(t.length===0)return W(`div`,{class:`rounded-xl border border-slate-200 bg-slate-50 px-4 py-5 text-center`,children:[W(dn,{class:`mx-auto size-5 text-slate-400`}),W(`p`,{class:`mt-2 mb-0 text-sm font-extrabold text-slate-700`,children:`아직 기록된 활동이 없습니다.`}),W(`p`,{class:`mt-1 mb-0 text-xs leading-5 text-slate-500`,children:`상태가 실제로 변경되면 이곳에 시간순으로 기록됩니다.`})]});let n=``;return W(`div`,{class:e?`space-y-2`:`space-y-1`,children:t.map(t=>{let r=Go(t),i=Ko(t.severity),a=Jo(t.occurredAt),o=!e&&a!==n;return n=a,W(`div`,{children:[o?W(`p`,{class:`mt-5 mb-2 first:mt-0 text-xs font-black text-slate-500`,children:Yo(t.occurredAt)}):null,W(`article`,{class:`relative flex min-w-0 gap-3 ${e?`border-b border-slate-100 py-3 first:pt-0 last:border-b-0 last:pb-0`:`rounded-xl px-2 py-3 hover:bg-slate-50`}`,children:[W(`div`,{class:`w-12 shrink-0 pt-1 text-xs font-bold tabular-nums text-slate-400`,children:W(`time`,{dateTime:new Date(t.occurredAt*1e3).toISOString(),title:Ao(t.occurredAt),children:qo(t.occurredAt)})}),W(`span`,{class:`mt-0.5 grid size-8 shrink-0 place-items-center rounded-full ring-1 ring-inset ${i.surface}`,children:i.icon}),W(`div`,{class:`min-w-0 flex-1`,children:[W(`p`,{class:`m-0 text-sm font-black leading-5 text-slate-950`,children:r.title}),r.description?W(`p`,{class:`mt-1 mb-0 break-words text-xs font-medium leading-5 text-slate-500`,children:r.description}):null]})]})]},t.eventId)})})}function Zo({error:e,loading:t,onRetry:n}){return t?W(`div`,{class:`rounded-xl border border-slate-200 bg-slate-50 px-4 py-5 text-center`,children:[W(pn,{class:`mx-auto size-5 animate-spin text-teal-700`}),W(`p`,{class:`mt-2 mb-0 text-sm font-bold text-slate-500`,children:`최근 활동을 불러오는 중입니다.`})]}):e?W(`div`,{class:`rounded-xl border border-amber-200 bg-amber-50/70 px-4 py-4`,children:W(`div`,{class:`flex items-start gap-3`,children:[W(Cn,{class:`mt-0.5 size-5 shrink-0 text-amber-700`}),W(`div`,{class:`min-w-0 flex-1`,children:[W(`p`,{class:`m-0 text-sm font-black text-slate-950`,children:`최근 활동을 불러오지 못했습니다.`}),W(`p`,{class:`mt-1 mb-0 text-xs leading-5 text-slate-500`,children:e}),n?W(`button`,{class:`mt-3 inline-flex min-h-9 items-center justify-center rounded-lg border border-amber-300 bg-white px-3 text-xs font-extrabold text-amber-800 transition hover:bg-amber-50`,onClick:n,type:`button`,children:`다시 시도`}):null]})]})}):null}function Qo(e){return e.enabled?e.eligible===!1||e.phase===`ineligible`?{label:`계정 연결 필요`,description:`SmartSafeHub 계정을 연결하면 이후 발생하는 활동을 Cloud에 자동으로 동기화할 수 있습니다.`,tone:`bg-slate-100 text-slate-600 ring-slate-200`}:e.phase===`error`?{label:`동기화 확인 필요`,description:`최근 Cloud 전송을 완료하지 못했습니다. 로컬 활동 기록은 계속 보관됩니다.`,tone:`bg-amber-50 text-amber-800 ring-amber-200`}:e.phase===`syncing`?{label:`동기화 중`,description:`새 활동을 SmartSafeHub Cloud에 전송하고 있습니다.`,tone:`bg-teal-50 text-teal-800 ring-teal-200`}:e.eligible===!0?{label:`자동 동기화`,description:`새 활동이 생기면 SmartSafeHub Cloud에 자동으로 안전하게 전송합니다.`,tone:`bg-emerald-50 text-emerald-800 ring-emerald-200`}:{label:`확인 중`,description:`Cloud 활동 기록 사용 가능 여부를 확인하고 있습니다.`,tone:`bg-slate-100 text-slate-600 ring-slate-200`}:e.eligible===!1?{label:`계정 연결 필요`,description:`SmartSafeHub 계정을 연결하면 Free에서도 Cloud 활동 기록을 사용할 수 있습니다.`,tone:`bg-slate-100 text-slate-600 ring-slate-200`}:{label:`Cloud 전송 꺼짐`,description:`활동 기록은 이 공유기에만 저장됩니다. Cloud에는 새로운 활동을 전송하지 않습니다.`,tone:`bg-slate-100 text-slate-700 ring-slate-200`}}function $o(e){if(e.eligible!==!0)return`—`;if(e.retentionDays>0)return`${e.retentionDays}일`;switch(e.plan?.toLowerCase()){case`free`:return`7일`;case`pro`:case`ultimate`:return`90일`;default:return`확인 중`}}function es({actionError:e,actionMessage:t,cloud:n,onDismissFeedback:r,onSetEnabled:i,saving:a}){let o=Qo(n),s=n.plan?.toUpperCase()??null,c=n.eligible===!0,l=n.enabled||c;return W(`section`,{"aria-label":`Cloud 활동 기록 설정`,class:`rounded-2xl border border-slate-200 bg-white p-4 shadow-sm shadow-slate-900/5 sm:p-5`,children:[W(`div`,{class:`flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between`,children:[W(`div`,{class:`min-w-0 flex-1`,children:[W(`div`,{class:`flex flex-wrap items-center gap-2`,children:W(`span`,{class:`rounded-full px-2.5 py-1 text-xs font-extrabold ring-1 ring-inset ${o.tone}`,children:a?`설정 중`:o.label})}),W(`p`,{class:`mt-2 mb-0 text-sm leading-6 text-slate-600`,children:o.description})]}),W(`div`,{class:`flex shrink-0 items-center justify-end gap-3 sm:justify-start`,children:[W(`span`,{class:`text-xs font-bold text-slate-600`,children:[`Cloud 전송 `,n.enabled?`켜짐`:`꺼짐`]}),W(`button`,{"aria-checked":n.enabled,"aria-label":`Cloud 활동 기록 전송 사용`,class:`ssh-switch-control relative inline-flex shrink-0 rounded-full border transition focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-100 disabled:cursor-not-allowed disabled:opacity-50 ${n.enabled?`border-teal-600 bg-teal-600`:`border-slate-300 bg-slate-200`}`,disabled:a||!l,onClick:()=>i(!n.enabled),role:`switch`,type:`button`,children:W(`span`,{"aria-hidden":`true`,class:`ssh-switch-thumb absolute top-1 shadow-sm transition-[left] ${n.enabled?`left-6`:`left-1`}`})})]})]}),(e||t)&&W(`div`,{class:`mt-4 flex min-w-0 items-start justify-between gap-3 rounded-xl border px-4 py-3 text-sm font-bold ${e?`border-rose-200 bg-rose-50 text-rose-800`:`border-emerald-200 bg-emerald-50 text-emerald-800`}`,role:e?`alert`:`status`,children:[W(`span`,{class:`min-w-0`,children:e||t}),W(`button`,{class:`shrink-0 rounded-lg px-2 py-1 text-xs font-extrabold hover:bg-black/5`,onClick:r,type:`button`,children:`닫기`})]}),n.enabled?W(`p`,{class:`mt-3 mb-0 text-xs leading-5 text-slate-500`,children:`Cloud 전송을 끄면 아직 전송하지 않은 대기 기록이 삭제됩니다. 공유기의 로컬 기록은 유지됩니다.`}):null,W(`div`,{class:`mt-4 flex flex-wrap items-center gap-x-5 gap-y-1 border-t border-slate-200 pt-3 text-xs font-semibold text-slate-500`,children:[W(`span`,{children:[`마지막 동기화: `,n.lastSuccessAt>0?jo(n.lastSuccessAt):`아직 없음`]}),W(`span`,{children:[`Cloud 보관: `,$o(n)]}),n.pendingEvents>0?W(`span`,{children:[`전송 대기: `,Y(n.pendingEvents),`건`]}):null]}),n.enabled&&n.phase===`error`&&n.lastErrorCode?W(`p`,{class:`mt-3 mb-0 text-xs font-semibold text-amber-700`,children:[`오류 코드: `,W(`code`,{children:n.lastErrorCode})]}):null,W(`details`,{class:`mt-3 border-t border-slate-200 pt-3`,children:[W(`summary`,{class:`cursor-pointer text-xs font-extrabold text-teal-700 hover:text-teal-800 focus-visible:rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-teal-500`,children:`Cloud 동기화 상세 정보`}),W(`dl`,{class:`mt-3 grid gap-3 sm:grid-cols-4`,children:[W(`div`,{class:`rounded-xl bg-slate-50 px-4 py-3 ring-1 ring-inset ring-slate-200`,children:[W(`dt`,{class:`text-xs font-bold text-slate-500`,children:`멤버십`}),W(`dd`,{class:`mt-1 text-sm font-black text-slate-900`,children:s??(n.eligible===!1?`Free`:`확인 중`)})]}),W(`div`,{class:`rounded-xl bg-slate-50 px-4 py-3 ring-1 ring-inset ring-slate-200`,children:[W(`dt`,{class:`text-xs font-bold text-slate-500`,children:`전송 대기`}),W(`dd`,{class:`mt-1 text-sm font-black text-slate-900`,children:[Y(n.pendingEvents),`건`]})]}),W(`div`,{class:`rounded-xl bg-slate-50 px-4 py-3 ring-1 ring-inset ring-slate-200`,children:[W(`dt`,{class:`text-xs font-bold text-slate-500`,children:`마지막 동기화`}),W(`dd`,{class:`mt-1 text-sm font-black text-slate-900`,children:n.lastSuccessAt>0?jo(n.lastSuccessAt):`아직 없음`})]}),W(`div`,{class:`rounded-xl bg-slate-50 px-4 py-3 ring-1 ring-inset ring-slate-200`,children:[W(`dt`,{class:`text-xs font-bold text-slate-500`,children:`Cloud 보관`}),W(`dd`,{class:`mt-1 text-sm font-black text-slate-900`,children:$o(n)})]})]}),W(`p`,{class:`mt-3 mb-0 text-xs leading-5 text-slate-500`,children:`SmartSafeHub 계정에 연결된 Free는 최근 7일, Pro와 Ultimate는 최근 90일 동안 Cloud 활동 기록을 보관합니다. 끄면 전송 대기 데이터와 임시 인증 정보를 삭제하며, 다시 켠 뒤 새로 발생한 활동부터 Cloud에 전송합니다. 로컬 최근 활동은 항상 유지됩니다.`})]})]})}function ts({actionError:e,actionMessage:t,data:n,error:r,loading:i,onDismissActionFeedback:a,onRetry:o,onSetCloudSync:s,savingCloudSync:c}){let[l,u]=L(20),d=n?.events??[],f=d.slice(0,l),p=d.length-f.length;return W(`div`,{class:`min-w-0 space-y-6`,children:[W(`section`,{"aria-labelledby":`activity-cloud-title`,class:`min-w-0`,children:[W(J,{id:`activity-cloud-title`,eyebrow:`Cloud history`,title:`Cloud 활동 기록`,description:`활동 기록을 Cloud에 자동으로 전송할지 설정합니다. 공유기 내 최근 활동은 항상 기록됩니다.`}),W(es,{actionError:e,actionMessage:t,cloud:n?.cloud??{enabled:!1,phase:`preparing`,eligible:null,plan:null,retentionDays:0,pendingEvents:0,lastAttemptAt:0,lastSuccessAt:0,lastUploadedCount:0,lastErrorCode:null,nextSyncAt:0},onDismissFeedback:a,onSetEnabled:s,saving:c})]}),W(`section`,{"aria-labelledby":`activity-history-title`,class:`min-w-0`,children:[W(J,{id:`activity-history-title`,eyebrow:`Local history`,title:`현재 부팅 이후의 최근 활동`,description:`인터넷, SafeShield, 업데이트, 계정 권한과 주요 설정에서 실제 상태가 변경된 시점만 기록합니다.`,action:W(`span`,{class:`rounded-full bg-slate-100 px-3 py-1.5 text-xs font-extrabold text-slate-600 ring-1 ring-inset ring-slate-200`,children:[`최대 `,n?.maxEvents??128,`건`]})}),W(`div`,{class:`rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5 sm:p-6`,children:i||r&&!n?W(Zo,{error:r,loading:i,onRetry:o}):W(b,{children:[W(Xo,{events:f}),d.length>20?W(`div`,{class:`mt-5 flex flex-wrap items-center justify-between gap-3 border-t border-slate-200 pt-4`,children:[W(`p`,{class:`m-0 text-xs font-semibold text-slate-500`,children:[Y(f.length),` / `,Y(d.length),`건 표시 중`]}),W(`div`,{class:`flex flex-wrap items-center gap-2`,children:[l>20?W(`button`,{class:`inline-flex min-h-10 items-center justify-center rounded-xl border border-slate-200 bg-white px-4 py-2 text-xs font-extrabold text-slate-700 transition hover:bg-slate-50 focus-visible:outline focus-visible:outline-2 focus-visible:outline-teal-500`,onClick:()=>u(20),type:`button`,children:`최신 20건만 보기`}):null,p>0?W(`button`,{class:`inline-flex min-h-10 items-center justify-center rounded-xl border border-teal-200 bg-teal-50 px-4 py-2 text-xs font-extrabold text-teal-800 transition hover:bg-teal-100 focus-visible:outline focus-visible:outline-2 focus-visible:outline-teal-500`,onClick:()=>u(e=>Math.min(e+20,d.length)),type:`button`,children:[`이전 활동 더 보기 (`,Y(Math.min(20,p)),`건)`]}):null]})]}):null]})})]}),W(`aside`,{class:`rounded-2xl border border-slate-200 bg-slate-50 px-5 py-4`,children:[W(`p`,{class:`m-0 text-sm font-extrabold text-slate-800`,children:`로컬 활동 기록 안내`}),W(`p`,{class:`mt-1 mb-0 text-xs leading-5 text-slate-500`,children:`이 기록은 공유기의 메모리 영역에만 보관되어 재부팅하면 초기화됩니다. 직접 설정을 변경한 작업은 성공 시점에 즉시 기록하고, 인터넷 및 장치 진단처럼 외부 상태를 확인해야 하는 항목은 실제 상태 변화가 관찰된 경우에만 추가합니다.`})]})]})}function ns(e){return!Number.isFinite(e)||e<0?`—`:e<1024?`${Math.round(e)} B`:e<1024**2?`${(e/1024).toFixed(1)} KB`:e<1024**3?`${(e/1024**2).toFixed(1)} MB`:`${(e/1024**3).toFixed(1)} GB`}function rs(e){if(!e||!Number.isSafeInteger(e.totalBytes)||!Number.isSafeInteger(e.availableBytes)||e.totalBytes<=0||e.availableBytes<0||e.availableBytes>e.totalBytes||e.mount!==`/overlay`&&e.mount!==`/`)return null;let t=e.availableBytes/e.totalBytes;return{availablePercent:Math.round(t*100),availableRatio:t,availableText:ns(e.availableBytes),usedText:ns(e.totalBytes-e.availableBytes),totalText:ns(e.totalBytes),level:t<=.1?`critical`:t<=.2?`warning`:`normal`}}var is=2*Math.PI*39;function as({storage:e}){let t=rs(e),n=t?(t.availableRatio*is).toFixed(2):`0`;return W(`aside`,{"aria-label":`앱 설치 저장 공간`,"data-level":t?.level??`unavailable`,class:`ssh-apps-storage-card flex min-w-0 items-center gap-3 rounded-2xl border border-slate-200 bg-white p-4 shadow-sm shadow-slate-900/5`,children:[W(`div`,{class:`ssh-apps-storage-ring relative grid size-24 shrink-0 place-items-center`,"data-level":t?.level??`unavailable`,children:[W(`svg`,{"aria-hidden":`true`,class:`size-24`,viewBox:`0 0 96 96`,children:[W(`circle`,{class:`ssh-apps-storage-track`,cx:`48`,cy:`48`,r:`39`}),t&&t.availableRatio>0?W(`circle`,{class:`ssh-apps-storage-free`,cx:`48`,cy:`48`,r:`39`,"stroke-dasharray":`${n} ${is.toFixed(2)}`}):null]}),W(`div`,{class:`absolute inset-0 flex flex-col items-center justify-center text-center`,children:[W(`strong`,{class:`text-lg leading-6 font-black text-slate-950`,children:t?`${t.availablePercent}%`:`—`}),W(`span`,{class:`text-[10px] font-bold text-slate-500`,children:`여유`})]})]}),W(`div`,{"aria-live":`polite`,class:`min-w-0 flex-1`,children:[W(`h3`,{class:`m-0 text-sm font-black text-slate-950`,children:`저장 공간`}),t?W(b,{children:[W(`p`,{class:`ssh-apps-storage-available mb-0 mt-1 text-base font-extrabold text-teal-700`,children:[t.availableText,` 남음`]}),W(`p`,{class:`mb-0 mt-1 text-[11px] leading-4 text-slate-500`,title:`앱 설치 가능 용량을 기준으로 계산하며, 파일시스템 예약 공간이 사용량에 포함될 수 있습니다.`,children:[`사용 `,t.usedText,` / 전체 `,t.totalText]})]}):W(`p`,{class:`mb-0 mt-1 text-xs leading-5 text-slate-500`,children:`용량 정보를 확인할 수 없습니다.`})]})]})}function os(e){switch(e){case`network`:return`network`;case`security`:return`security`;case`entertainment`:return`entertainment`;case`utilities`:return`utilities`;case`other`:return`other`;case`smart_home`:case`iot`:return`smart_home`;default:return`other`}}function ss({category:e,class:t}){switch(e){case`all`:return W(Qt,{class:t});case`network`:return W($t,{class:t});case`security`:return W(qt,{class:t});case`smart_home`:return W(Xt,{class:t});case`entertainment`:return W(en,{class:t});case`utilities`:return W(tn,{class:t});case`other`:return W(nn,{class:t});default:return W(nn,{class:t})}}function cs(e){return!e||typeof e.path!=`string`||e.path.length>160?null:e.type===`static`?e.path===`/retro-arcade/`?e.path:null:e.type===`luci`&&/^\/cgi-bin\/luci\/admin\/[a-z0-9_-]+(?:\/[a-z0-9_-]+)*$/.test(e.path)?e.path:null}function ls(e){return e===`first_party`}var us=`smartsafehub:apps:dismissed-operation:v1`;function ds(e){return!e||e.phase===`idle`?``:e.operationId?`operation:${e.operationId}`:`legacy:${e.phase}:${e.action}:${e.appId}:${e.code}`}function fs(){try{return typeof window>`u`?null:window.localStorage.getItem(us)}catch{return null}}function ps(e){if(e)try{typeof window<`u`&&window.localStorage.setItem(us,e)}catch{}}var ms={E21:`서버에서 승인 정보를 확인하지 못했습니다. 인터넷 연결을 확인해 주세요.`,E22:`이 펌웨어에서 승인되지 않은 앱입니다.`,E24:`공유기의 패키지 아키텍처와 앱이 일치하지 않습니다.`,E25:`배포 저장소가 승인 당시와 달라졌거나 접근할 수 없습니다. 관리자 확인 후 다시 시도해 주세요.`,E26:`앱 설치에 필요한 메모리 또는 저장 공간이 부족합니다.`,E27:`승인된 패키지 정보가 올바르지 않습니다.`,E28:`이전에 설치한 앱의 구성이 현재 승인 정보와 다릅니다.`,E29:`동일한 패키지가 이미 다른 방법으로 설치되어 있습니다.`,E31:`앱 파일을 다운로드하지 못했습니다.`,E32:`다운로드된 앱 파일이 올바르지 않습니다.`,E33:`앱 파일의 검증 값이 일치하지 않습니다.`,E34:`패키지 설치 사전 확인에 실패했습니다.`,E35:`패키지를 설치하지 못했습니다.`,E37:`공식 OpenWrt LuCI 저장소가 승인 당시와 다릅니다. 관리자에게 확인해 주세요.`,E38:`기존 공유 라이브러리의 버전이 승인된 버전과 달라 설치할 수 없습니다.`,E51:`SmartSafeHub Apps에서 관리하는 앱만 업데이트할 수 있습니다.`,E52:`현재 설치 버전과 승인된 업데이트가 일치하지 않습니다. 앱 구성과 업데이트 버전을 확인해 주세요.`,E53:`다른 시스템 패키지 변경이 필요하여 앱 업데이트를 중단했습니다.`,E54:`앱 업데이트에 실패했습니다. 일부 패키지가 변경되었을 수 있으니 관리자에게 확인해 주세요.`,E55:`업데이트 후 설치 기록을 저장하지 못했습니다. 관리자에게 확인해 주세요.`,E41:`SmartSafeHub Apps에서 설치한 앱만 여기서 제거할 수 있습니다.`,E44:`기존 시스템에 영향을 줄 수 있어 앱 제거가 차단됐습니다.`,E45:`앱 제거에 실패했습니다.`},hs=[{id:`all`,label:`전체`},{id:`network`,label:`네트워크`},{id:`security`,label:`보안`},{id:`smart_home`,label:`스마트홈`},{id:`entertainment`,label:`엔터테인먼트`},{id:`utilities`,label:`도구`},{id:`other`,label:`기타`}];function gs({action:e}){let t=e===`remove`;return W(`span`,{"aria-live":`polite`,class:`inline-flex items-center gap-1.5 text-xs font-bold ${t?`text-red-700`:`text-teal-700`}`,role:`status`,children:[W(`svg`,{"aria-hidden":`true`,class:`size-4 animate-spin`,fill:`none`,viewBox:`0 0 24 24`,children:[W(`circle`,{cx:`12`,cy:`12`,r:`9`,stroke:`currentColor`,"stroke-width":`3`,opacity:`0.25`}),W(`path`,{d:`M21 12a9 9 0 0 0-9-9`,stroke:`currentColor`,"stroke-width":`3`,"stroke-linecap":`round`})]}),t?`제거 중…`:e===`update`?`업데이트 중…`:`설치 중…`]})}function _s({data:e,error:t,loading:n,refreshing:r,onRetry:i,installStatus:a,metrics:o,statusError:s,onRefreshStatus:c,onInstall:l,onUpdate:u,onRemove:d,demo:f=!1,onDemoAction:p}){let[m,h]=L(`all`),[g,_]=L(``),[v,y]=L(null),[x,S]=L(null),C=z(null),w=z(null),[T,E]=L(null),[D,O]=L(fs),k=e,A=k?.apps??[],j=k&&!t&&!n&&!r&&!s?Object.entries(a?.installed??{}).filter(([e,t])=>t.retired===!0&&t.managed&&!A.some(t=>t.id===e)):[],M=a?.restore,N=a?.job,P=N?.phase===`starting`||N?.phase===`running`,F=M?.phase===`starting`||M?.phase===`running`,I=ds(N),ee=e=>{O(e),ps(e)},te=N&&N.phase!==`idle`&&I!==D;R(()=>{if(N?.phase!==`success`||!te)return;let e=window.setTimeout(()=>ee(I),7e3);return()=>window.clearTimeout(e)},[I,N?.phase,te]);let ne=N?.action===`install`||N?.action===`update`||N?.action===`remove`?N.action:null,re=Xe(()=>A.filter(e=>(m===`all`||os(e.category)===m)&&`${e.name} ${e.summary}`.toLocaleLowerCase().includes(g.trim().toLocaleLowerCase())),[A,m,g]);R(()=>{if(!x)return;C.current?.focus();let e=e=>{e.key===`Escape`&&(e.preventDefault(),S(null),w.current?.focus())};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[x]);let ie=()=>{S(null),w.current?.focus()},ae=(e,t)=>{!a||s||v!==null||P||F||(w.current=t,S(e))},oe=async(e,t)=>{if(!(!a||s||v!==null||P||F)){if(f)t===`remove`&&ie(),p?.();else if((t!==`update`||window.confirm(`${e.name} 앱을 최신 승인 버전으로 업데이트하시겠습니까?\n\n업데이트 중 공유기를 재부팅하지 마세요.`))&&(t!==`install`||window.confirm(`${e.name} 앱을 설치하시겠습니까?\n\n앱을 변경하는 동안 공유기를 재부팅하지 마세요.`))){t===`remove`&&ie(),E(null),y({appId:e.id,action:t});try{t===`install`?await l(e.id):t===`update`?await u(e.id):await d(e.id),c()}catch(t){E(q(t,`${e.name} 앱 작업을 시작하지 못했습니다.`))}finally{y(null)}}}};return W(`section`,{class:`min-w-0 space-y-6`,children:[x?W(`div`,{class:`fixed inset-0 z-50 flex items-center justify-center bg-slate-950/70 p-4`,onClick:e=>{e.target===e.currentTarget&&ie()},role:`presentation`,children:W(`div`,{"aria-describedby":`apps-remove-description`,"aria-labelledby":`apps-remove-title`,"aria-modal":`true`,class:`w-full max-w-md rounded-2xl border border-slate-200 bg-white p-5 shadow-xl`,onKeyDown:e=>{e.key===`Tab`&&(e.shiftKey&&e.target===C.current?(e.preventDefault(),e.currentTarget.querySelector(`[data-confirm-remove]`)?.focus()):!e.shiftKey&&e.target instanceof HTMLButtonElement&&e.target.hasAttribute(`data-confirm-remove`)&&(e.preventDefault(),C.current?.focus()))},role:`dialog`,children:[W(`h2`,{class:`m-0 text-lg font-extrabold text-slate-950`,id:`apps-remove-title`,children:`앱 제거 확인`}),W(`p`,{class:`mt-3 break-words text-sm text-slate-700`,id:`apps-remove-description`,children:[W(`strong`,{children:x.name}),` 앱을 이 공유기에서 제거하시겠습니까?`]}),W(`p`,{class:`mt-2 text-xs leading-5 text-slate-600`,children:`앱에서 제공하던 기능을 더 이상 사용할 수 없습니다. 제거하는 동안 공유기를 재부팅하지 마세요.`}),x.id===`travelmate`?W(`p`,{class:`mt-2 text-xs leading-5 text-slate-600`,children:`다른 공유기 기능에서도 사용하는 공용 패키지는 안전을 위해 유지됩니다.`}):null,W(`div`,{class:`mt-5 flex justify-end gap-2`,children:[W(`button`,{class:`min-h-10 rounded-lg border border-slate-200 bg-white px-4 text-sm font-bold text-slate-700 transition hover:bg-slate-50`,onClick:ie,ref:C,type:`button`,children:`취소`}),W(`button`,{class:`min-h-10 rounded-lg bg-red-700 px-4 text-sm font-bold text-white transition hover:bg-red-800 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-red-100 disabled:opacity-50`,"data-confirm-remove":!0,disabled:!a||!!s||v!==null||P||F,onClick:()=>void oe(x,`remove`),type:`button`,children:`앱 제거`})]})]})}):null,W(`div`,{class:`min-w-0`,children:[W(J,{eyebrow:`SmartSafeHub Apps`,title:`Apps`,description:`SmartSafeHub에서 직접 검증한 앱으로 공유기의 기능을 확장하세요.`}),W(`div`,{class:`grid min-w-0 gap-3 lg:grid-cols-[minmax(0,1fr)_minmax(0,18.5rem)]`,children:[W(`div`,{class:`rounded-2xl border border-slate-200 bg-white p-4 shadow-sm shadow-slate-900/5 sm:p-5`,children:[W(`div`,{class:`flex flex-wrap items-center justify-between gap-3`,children:[W(`div`,{class:`flex min-w-0 items-center gap-3`,children:[W(`span`,{class:`grid size-12 shrink-0 place-items-center rounded-2xl bg-teal-50 text-teal-700`,children:W(Qt,{class:`size-6`})}),W(`div`,{class:`min-w-0`,children:[W(`h3`,{class:`m-0 text-base font-black text-slate-950`,children:`검증된 앱`}),W(`p`,{class:`mt-1 mb-0 text-xs leading-5 text-slate-500`,children:`현재 펌웨어에서 확인된 앱만 표시합니다.`})]})]}),W(`div`,{class:`flex flex-wrap items-center gap-2`,children:[W(`span`,{class:`rounded-full border border-teal-200 bg-teal-50 px-3 py-1 text-xs font-bold text-teal-800`,children:k?`${A.length}개`:`확인 전`}),t&&k?W(`span`,{class:`rounded-full border border-amber-200 bg-amber-50 px-2 py-1 text-[11px] font-semibold text-amber-800`,title:t,role:`status`,children:`이전 목록`}):null,W(`button`,{class:`inline-flex min-h-10 items-center gap-2 rounded-xl border border-slate-200 bg-white px-3 text-xs font-bold text-slate-700 transition hover:border-teal-300 hover:text-teal-700 disabled:opacity-50`,disabled:n||r,onClick:i,type:`button`,children:[W(pn,{class:`size-4 ${r?`animate-spin`:``}`}),r?`확인 중`:`목록 새로고침`]})]})]}),W(`p`,{class:`mt-3 mb-0 text-xs leading-5 text-slate-500`,children:`검증된 APK를 설치·업데이트·제거할 수 있습니다. 작업 중에는 다른 앱 작업을 시작할 수 없습니다.`})]}),W(as,{storage:o?.storage})]})]}),M&&(M.phase===`starting`||M.phase===`running`||M.phase===`failed`)?W(`div`,{"aria-live":`polite`,class:`rounded-2xl border p-4 text-sm ${M.phase===`failed`?`border-amber-300 bg-amber-50 text-amber-900`:`border-teal-200 bg-teal-50 text-teal-900`}`,role:`status`,children:[W(`strong`,{children:M.phase===`failed`?`Apps 자동 복원을 완료하지 못했습니다.`:`펌웨어 업데이트 후 Apps를 복원하는 중입니다.`}),M.appId?W(`span`,{children:[` · `,M.appId]}):null,M.phase===`failed`?W(`p`,{class:`mb-0 mt-1 text-xs`,children:[ms[M.code]??`승인·연결·설치 상태를 확인해 주세요.`,` 필요한 경우 앱 카드에서 설치 마무리를 진행할 수 있습니다.`]}):W(`p`,{class:`mb-0 mt-1 text-xs`,children:`검증된 패키지만 다시 설치합니다. 완료될 때까지 앱 설치·제거를 시작하지 마세요.`})]}):null,te&&N?W(`div`,{"aria-live":`polite`,class:`rounded-2xl border p-4 text-sm ${N.phase===`failed`?`border-rose-200 bg-rose-50 text-rose-800`:P&&N.action===`remove`?`border-red-200 bg-red-50 text-red-800`:`border-teal-200 bg-teal-50 text-teal-900`}`,role:`status`,children:W(`div`,{class:`flex items-start justify-between gap-3`,children:[W(`div`,{class:`min-w-0`,children:[P?N.action===`remove`?`앱 제거 중`:N.action===`update`?`앱 업데이트 중`:`앱 설치 중`:N.phase===`success`?`마지막 앱 작업 완료`:`마지막 앱 작업 실패`,N.appId?` · ${N.appId}`:``,N.code&&N.code!==`NONE`?` · 오류 코드 ${N.code}`:``,N.phase===`failed`&&N.code&&ms[N.code]?W(`p`,{class:`mb-0 mt-1 text-xs`,children:ms[N.code]}):null,P?W(`p`,{class:`mb-0 mt-1 text-xs`,children:[N.action===`remove`?`앱 제거와 패키지 정리가 진행 중입니다.`:N.action===`update`?`승인된 버전으로 앱을 업데이트하고 있습니다.`:`설치 확인과 패키지 처리가 진행 중입니다.`,` 화면을 닫아도 작업은 계속됩니다.`]}):null]}),P?null:W(`button`,{"aria-label":`앱 작업 알림 닫기`,class:`shrink-0 rounded-lg px-2 py-1 text-xs font-bold hover:bg-black/5 focus-visible:outline-none focus-visible:ring-2`,onClick:()=>ee(I),type:`button`,children:`닫기 ×`})]})}):null,T?W(`p`,{class:`rounded-xl border border-rose-200 bg-rose-50 p-4 text-sm text-rose-800`,role:`alert`,children:T}):null,s?W(`div`,{class:`rounded-xl border border-amber-200 bg-amber-50 p-4 text-sm text-amber-900`,role:`alert`,children:[`설치 상태를 확인하지 못했습니다. 설치 및 제거 버튼이 비활성화됩니다. `,W(`button`,{class:`underline`,onClick:c,type:`button`,children:`다시 확인`})]}):null,t&&!k?W(`div`,{class:`rounded-2xl border border-rose-200 bg-rose-50 p-5`,role:`alert`,children:[W(`p`,{class:`m-0 text-sm font-bold text-rose-800`,children:`앱 목록을 불러오지 못했습니다.`}),W(`p`,{class:`mt-2 mb-0 text-sm text-rose-700`,children:t}),W(`button`,{class:`mt-3 rounded-lg border border-rose-300 px-3 py-2 text-xs font-bold text-rose-800`,onClick:i,type:`button`,children:`다시 시도`})]}):null,n&&!k?W(`div`,{"aria-live":`polite`,class:`rounded-2xl border border-slate-200 bg-white p-8 text-center text-sm text-slate-500`,role:`status`,children:`검증된 앱 목록을 확인하는 중입니다.`}):null,k?W(b,{children:[A.length>0?W(`div`,{class:`space-y-4`,children:[W(`div`,{class:`flex flex-wrap items-center justify-between gap-3`,children:[W(`div`,{class:`flex flex-wrap items-center gap-2`,role:`group`,"aria-label":`앱 카테고리`,children:hs.map(e=>W(`button`,{class:`inline-flex min-h-10 items-center gap-1.5 rounded-xl border px-3 text-xs font-bold ${m===e.id?`border-teal-400 bg-teal-50 text-teal-800`:`border-slate-200 bg-white text-slate-600`}`,onClick:()=>h(e.id),type:`button`,children:[W(ss,{category:e.id,class:`size-4 shrink-0`}),e.label]},e.id))}),W(`input`,{"aria-label":`앱 검색`,class:`min-h-10 w-full rounded-xl border border-slate-200 bg-white px-3 text-sm text-slate-900 outline-none focus:border-teal-500 sm:w-52`,onInput:e=>_(e.currentTarget.value),placeholder:`앱 검색`,type:`search`,value:g})]}),W(`div`,{class:`grid min-w-0 gap-3 sm:grid-cols-2 xl:grid-cols-3`,children:re.map(e=>{let t=a?.installed[e.id],n=t?.installed??!1,r=o?.installedSizes?.[e.id],i=n&&typeof r==`number`&&Number.isSafeInteger(r)&&r>=0?ns(r):null,c=t?.managed??!1,l=!!(n&&c&&t?.updateAvailable),u=n?t?.installedVersion:null,d=!!(n&&u&&(l||u!==e.version)),m=v?.appId===e.id?v.action:P&&N?.appId===e.id?ne:null,h=!a||!!s||v!==null||P||F,g=c&&(n||!!t?.partial),_=n&&t?.webUiReady?cs(e.webUi):null;return W(`article`,{class:`flex min-w-0 flex-col rounded-2xl border border-slate-200 bg-white p-5 shadow-sm`,children:[W(`div`,{class:`flex min-w-0 items-start gap-3`,children:[W(`span`,{class:`grid size-14 shrink-0 place-items-center rounded-xl bg-slate-100 text-slate-600`,children:W(ss,{category:os(e.category),class:`size-7`})}),W(`div`,{class:`min-w-0 flex-1`,children:[W(`div`,{class:`flex min-w-0 flex-wrap items-start justify-between gap-x-3 gap-y-2`,children:[W(`h3`,{class:`m-0 min-w-0 flex-1 break-words text-sm font-black text-slate-950`,children:e.name}),n&&c?W(`span`,{class:`ssh-apps-installed-badge shrink-0 rounded-full border px-2 py-0.5 text-[11px] font-bold`,children:`✓ 설치됨`}):null]}),W(`div`,{class:`mt-1 flex flex-wrap items-center gap-x-2 gap-y-1`,children:[W(`span`,{class:`text-xs text-slate-500`,title:n?`공유기에 설치된 대표 APK 패키지 버전입니다.`:`현재 제공 중인 승인 버전입니다.`,children:n?u?`설치 v${u}`:`설치 버전 확인 불가`:`v${e.version}`}),l?W(`span`,{class:`ssh-apps-update-badge rounded-full border px-2 py-0.5 text-[11px] font-bold`,children:`업데이트 가능`}):null]}),d||i!==null?W(`p`,{class:`mb-0 mt-1 flex min-w-0 flex-wrap items-center gap-x-1.5 gap-y-0.5 text-[11px] text-slate-500`,children:[d?W(`span`,{title:`현재 서버가 승인한 앱 릴리스 버전입니다. 설치된 APK 패키지의 버전과 다를 수 있습니다.`,children:[`제공 v`,e.version]}):null,d&&i!==null?W(`span`,{"aria-hidden":`true`,children:`·`}):null,i===null?null:W(`span`,{title:`설치된 APK 패키지의 용량 합계입니다. 앱 데이터 및 캐시는 포함되지 않습니다.`,children:[`설치 용량 약 `,i]})]}):null]})]}),W(`p`,{class:`mt-4 mb-4 min-h-10 break-words text-sm leading-5 text-slate-600`,children:e.summary}),W(`div`,{class:`mt-auto flex flex-wrap items-center justify-between gap-2 border-t border-slate-100 pt-3`,children:[ls(e.publisherType)?W(`span`,{class:`ssh-apps-official-badge inline-flex items-center gap-1 rounded-lg border px-2 py-1 text-xs font-bold`,title:`SmartSafeHub가 직접 개발하고 제공하는 앱입니다.`,children:[W(`span`,{"aria-hidden":`true`,children:`✦`}),` SmartSafeHub 공식`]}):W(`span`,{class:`text-xs font-bold text-slate-500`,children:`✓ 검증된 앱`}),m?W(gs,{action:m}):W(`span`,{class:`flex flex-wrap items-center gap-2`,children:[c&&t?.partial&&!n?W(`button`,{class:`min-h-9 rounded-lg border border-teal-300 bg-white px-3 text-xs font-bold text-teal-800 hover:bg-teal-50 disabled:opacity-50`,disabled:h,onClick:()=>void oe(e,`install`),type:`button`,children:`설치 마무리`}):null,l?W(`button`,{class:`ssh-apps-update-button min-h-9 rounded-lg border px-3 text-xs font-bold transition disabled:opacity-50`,disabled:h,onClick:()=>void oe(e,`update`),type:`button`,children:`업데이트`}):null,_?f?W(`button`,{"aria-label":`${e.name} 데모 실행 안내`,class:`inline-flex min-h-9 items-center rounded-lg border border-teal-700 bg-teal-700 px-3 text-xs font-bold text-white transition hover:bg-teal-800`,onClick:p,type:`button`,children:`열기 ↗`}):W(`a`,{"aria-label":`${e.name} 새 탭에서 열기`,class:`inline-flex min-h-9 items-center rounded-lg border border-teal-700 bg-teal-700 px-3 text-xs font-bold text-white transition hover:bg-teal-800`,href:_,rel:`noopener noreferrer`,target:`_blank`,children:`열기 ↗`}):n&&e.webUi?W(`span`,{class:`text-xs text-amber-700`,title:`이 앱의 웹 UI 패키지를 설치해야 열 수 있습니다.`,children:`웹 UI 미설치`}):null,n&&!c?W(`span`,{class:`text-xs text-slate-500`,title:`공유기 소프트웨어 메뉴 또는 SSH로 설치한 앱은 Apps에서 제거하지 않습니다.`,children:`기존에 설치됨`}):t?.partial&&!c?W(`span`,{class:`text-xs text-amber-700`,children:`일부 패키지 설치됨`}):W(`button`,{class:`min-h-9 rounded-lg border bg-white px-3 text-xs font-bold transition focus-visible:outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-50 ${g?`border-red-300 text-red-700 hover:bg-red-50 focus-visible:ring-red-100`:`border-teal-300 text-teal-800 hover:bg-teal-50 focus-visible:ring-teal-200`}`,disabled:h||!!t?.partial&&!c,onClick:t=>g?ae(e,t.currentTarget):void oe(e,`install`),type:`button`,children:g?`제거`:c?`재설치`:`설치`})]})]})]},e.id)})}),re.length===0?W(`p`,{class:`rounded-xl bg-slate-100 p-5 text-center text-sm text-slate-600`,children:`검색 조건에 맞는 앱이 없습니다.`}):null]}):W(`div`,{class:`rounded-2xl border border-dashed border-slate-300 bg-white px-5 py-12 text-center`,children:[W(Qt,{class:`mx-auto size-10 text-slate-400`}),W(`h3`,{class:`mt-4 mb-2 text-base font-black text-slate-900`,children:`아직 등록된 앱이 없습니다.`}),W(`p`,{class:`m-0 text-sm leading-6 text-slate-500`,children:`현재 기기와 펌웨어에 대해 승인된 앱이 준비되면 이곳에서 확인할 수 있습니다.`})]}),W(`p`,{class:`text-xs text-slate-400`,children:[`기기 `,k.deviceCode,` · 펌웨어 `,k.firmwareBuildId,` · `,k.channel,` 채널`]})]}):null,j.length>0?W(`div`,{class:`rounded-2xl border border-amber-200 bg-amber-50 p-5`,children:[W(`h3`,{class:`m-0 text-sm font-black text-amber-900`,children:`제공이 중단된 앱`}),W(`p`,{class:`mb-3 mt-1 text-xs text-amber-800`,children:`현재 승인 목록에서 제외되었지만 기기에 남아 있는 앱입니다. 설치는 불가능하며 제거할 수 있습니다.`}),W(`div`,{class:`space-y-2`,children:j.map(([e])=>{let t=v?.appId===e?v.action:P&&N?.appId===e?ne:null;return W(`div`,{class:`flex flex-wrap items-center justify-between gap-3`,children:[W(`span`,{class:`break-all text-sm text-amber-900`,children:e}),t?W(gs,{action:t}):W(`button`,{class:`min-h-9 rounded-lg border border-red-300 bg-white px-3 text-xs font-bold text-red-700 transition hover:bg-red-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-red-100 disabled:cursor-not-allowed disabled:opacity-50`,disabled:!a||P||F||v!==null||!!s,onClick:t=>ae({id:e,name:e,summary:``,description:``,category:``,version:``},t.currentTarget),type:`button`,children:`제거`})]},e)})})]}):null]})}function vs(){return W(`section`,{"aria-busy":`true`,"aria-live":`polite`,class:`rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5 sm:p-6`,children:W(`div`,{class:`flex items-center gap-4`,children:[W(pn,{class:`size-8 shrink-0 animate-spin text-teal-700`}),W(`div`,{class:`min-w-0 text-left`,children:W(`h2`,{class:`m-0 text-lg font-extrabold text-slate-950`,children:`장치 상태를 확인하고 있습니다`})})]})})}function ys({message:e,onRetry:t}){return W(`section`,{"aria-live":`assertive`,class:`rounded-2xl border border-red-200 bg-red-50 p-5 sm:p-6`,children:[W(`p`,{class:`m-0 text-xs font-bold uppercase tracking-[0.16em] text-red-700`,children:`연결 오류`}),W(`h2`,{class:`mt-2 mb-0 text-lg font-extrabold text-red-950`,children:`장치 상태를 불러오지 못했습니다`}),W(`p`,{class:`mt-2 mb-0 text-sm leading-6 text-red-800`,children:e}),W(`button`,{class:`mt-5 inline-flex min-h-10 items-center justify-center gap-2 rounded-xl border-0 bg-red-700 px-4 py-2 text-sm font-bold text-white shadow-sm transition hover:bg-red-800 focus:outline-none focus-visible:ring-4 focus-visible:ring-red-200`,onClick:t,type:`button`,children:[W(pn,{class:`size-4`}),`다시 시도`]})]})}var bs=[{value:`all`,label:`전체`},{value:`online`,label:`현재 연결`},{value:`wifi`,label:`Wi-Fi`},{value:`ethernet`,label:`유선/기타`},{value:`offline`,label:`최근 기기`}],xs=new Intl.Collator(`ko`),Ss=new Intl.DateTimeFormat(`ko-KR`,{month:`numeric`,day:`numeric`,hour:`2-digit`,minute:`2-digit`});function Cs(e){switch(e){case`wifi`:return`Wi-Fi`;case`ethernet`:return`유선/기타`;default:return`연결 방식 미확인`}}function ws(e){return e.hostname??e.ipv4Address??`이름 없는 기기`}function Ts(e){return e===null?null:e>=-50?`매우 좋음`:e>=-60?`좋음`:e>=-70?`보통`:`약함`}function Es(e){if(e===null||e<0)return null;let t=Math.floor(e/86400),n=Math.floor(e%86400/3600),r=Math.floor(e%3600/60);return t>0?`${t}일 ${n}시간`:n>0?`${n}시간 ${r}분`:`${Math.max(r,1)}분`}function Ds(e){return e.leaseActive?e.leaseExpiresAt===null?`DHCP 임대 활성`:`DHCP ${Ss.format(new Date(e.leaseExpiresAt*1e3))}까지`:null}function Os(e,t){switch(t){case`online`:return e.online;case`wifi`:return e.online&&e.connection===`wifi`;case`ethernet`:return e.online&&e.connection===`ethernet`;case`offline`:return!e.online;default:return!0}}function ks({device:e}){return W(`span`,{class:`inline-flex items-center gap-2 rounded-full px-3 py-1.5 text-xs font-extrabold ring-1 ring-inset ${e.online?`bg-emerald-50 text-emerald-700 ring-emerald-200`:`bg-slate-100 text-slate-600 ring-slate-200`}`,children:[W(`span`,{class:`size-2 rounded-full ${e.online?`bg-emerald-500`:`bg-slate-400`}`}),e.online?`현재 연결`:`최근 임대`]})}function As({device:e}){let t=Ts(e.signalDbm),n=Es(e.connectedSeconds),r=Ds(e);return W(`article`,{class:`rounded-2xl border border-slate-200 bg-white p-4 shadow-sm shadow-slate-900/5 sm:p-5`,children:[W(`div`,{class:`flex min-w-0 flex-col items-start gap-4 sm:flex-row sm:justify-between`,children:[W(`div`,{class:`flex min-w-0 items-start gap-3`,children:[W(`span`,{class:`grid size-11 shrink-0 place-items-center rounded-xl ${e.connection===`wifi`?`bg-teal-50 text-teal-700`:e.connection===`ethernet`?`bg-blue-50 text-blue-700`:`bg-slate-100 text-slate-600`}`,children:e.connection===`wifi`?W(on,{class:`size-6`}):e.connection===`ethernet`?W(cn,{class:`size-6`}):W(Yt,{class:`size-6`})}),W(`div`,{class:`min-w-0`,children:[W(`h3`,{class:`m-0 break-words text-base font-extrabold text-slate-950`,children:ws(e)}),W(`p`,{class:`mt-1 mb-0 break-all font-mono text-xs leading-5 text-slate-500`,children:e.mac})]})]}),W(`span`,{class:`shrink-0 self-start`,children:W(ks,{device:e})})]}),W(`dl`,{class:`mt-5 grid gap-3 text-sm sm:grid-cols-2`,children:[W(`div`,{class:`rounded-xl bg-slate-50 p-3.5`,children:[W(`dt`,{class:`text-xs font-bold text-slate-500`,children:`IP 주소`}),W(`dd`,{class:`mt-1.5 mb-0 ml-0 font-extrabold text-slate-900`,children:e.ipv4Address??`확인되지 않음`})]}),W(`div`,{class:`rounded-xl bg-slate-50 p-3.5`,children:[W(`dt`,{class:`text-xs font-bold text-slate-500`,children:`연결 방식`}),W(`dd`,{class:`mt-1.5 mb-0 ml-0 font-extrabold text-slate-900`,children:Cs(e.connection)})]})]}),W(`div`,{class:`mt-4 flex flex-wrap gap-2 text-xs font-bold text-slate-600`,children:[e.ssid?W(`span`,{class:`rounded-full bg-teal-50 px-3 py-1.5 text-teal-800`,children:[e.ssid,e.bandLabel?` · ${e.bandLabel}`:``]}):null,t?W(`span`,{class:`rounded-full bg-slate-100 px-3 py-1.5`,children:[`신호 `,t,` · `,e.signalDbm,` dBm`]}):null,n?W(`span`,{class:`rounded-full bg-slate-100 px-3 py-1.5`,children:[`연결 `,n]}):null,r?W(`span`,{class:`rounded-full bg-slate-100 px-3 py-1.5`,children:r}):null,e.interface?W(`span`,{class:`rounded-full bg-slate-100 px-3 py-1.5`,children:e.interface}):null]})]})}function js({data:e,error:t,loading:n,onRetry:r}){let[i,a]=L(``),[o,s]=L(`all`),c=Xe(()=>{if(!e)return[];let t=i.trim().toLocaleLowerCase();return[...e.devices].filter(e=>Os(e,o)).filter(e=>!t||[e.hostname,e.ipv4Address,e.mac,e.ssid].filter(e=>!!e).some(e=>e.toLocaleLowerCase().includes(t))).sort((e,t)=>e.online===t.online?e.connection===t.connection?xs.compare(ws(e),ws(t)):e.connection===`wifi`?-1:1:e.online?-1:1)},[e,o,i]);return n?W(vs,{}):t?W(ys,{message:t,onRetry:r}):e?W(b,{children:[W(J,{eyebrow:`Overview`,title:`연결 기기 현황`,description:`네트워크에 연결되었거나 최근 확인된 기기를 요약합니다.`}),W(`section`,{"aria-label":`연결 기기 요약`,class:`grid gap-4 sm:grid-cols-2 xl:grid-cols-4`,children:[W(Ms,{description:`${e.totals.offline}대는 최근 DHCP 임대에서 확인`,icon:W(Yt,{class:`size-6`}),label:`확인된 기기`,value:e.totals.known}),W(Ms,{description:`Wi-Fi 또는 ARP에서 현재 확인됨`,icon:W(dn,{class:`size-6`}),label:`현재 연결`,value:e.totals.online}),W(Ms,{description:`무선 AP에 연결된 기기`,icon:W(on,{class:`size-6`}),label:`Wi-Fi`,value:e.totals.wireless}),W(Ms,{description:`LAN ARP에서 확인된 비무선 기기`,icon:W(cn,{class:`size-6`}),label:`유선/기타`,value:e.totals.ethernet})]}),W(`section`,{class:`mt-7`,"aria-label":`기기 목록 및 필터`,children:[W(J,{eyebrow:`Device inventory`,title:`기기 목록`,description:`연결된 기기를 검색하고 무선·유선 상태별로 확인합니다.`}),W(`div`,{class:`rounded-2xl border border-slate-200 bg-white p-4 shadow-sm shadow-slate-900/5 sm:p-5`,children:W(`div`,{class:`flex min-w-0 flex-col gap-3 lg:flex-row lg:items-center lg:justify-between`,children:[W(`div`,{class:`ssh-touch-scroll flex min-w-0 gap-2 overflow-x-auto pb-1 lg:flex-1`,role:`group`,"aria-label":`기기 필터`,children:bs.map(e=>W(`button`,{"aria-pressed":o===e.value,class:`min-h-10 shrink-0 rounded-xl border px-4 py-2 text-sm font-extrabold transition ${o===e.value?`border-teal-700 bg-teal-700 text-white`:`border-slate-200 bg-white text-slate-600 hover:bg-slate-50 hover:text-slate-950`}`,onClick:()=>s(e.value),type:`button`,children:e.label},e.value))}),W(`label`,{class:`relative block w-full lg:w-72 lg:shrink-0 xl:w-80`,children:[W(`span`,{class:`sr-only`,children:`기기 검색`}),W(`span`,{class:`pointer-events-none absolute inset-y-0 left-0 flex w-11 items-center justify-center text-slate-400`,children:W(En,{class:`size-5`})}),W(`input`,{class:`min-h-11 w-full rounded-xl border-2 border-slate-300 bg-slate-50 py-2.5 pr-4 pl-11 text-sm font-semibold text-slate-950 shadow-inner outline-none transition placeholder:text-slate-400 focus:border-teal-500 focus:bg-white focus:ring-4 focus:ring-teal-100`,onInput:e=>a(e.currentTarget.value),placeholder:`이름, IP, MAC 또는 SSID 검색`,type:`search`,value:i})]})]})})]}),c.length?W(`section`,{"aria-live":`polite`,class:`mt-5 grid gap-4 lg:grid-cols-2`,children:c.map(e=>W(As,{device:e},e.id))}):W(`section`,{class:`mt-5 rounded-2xl border border-dashed border-slate-300 bg-white p-5 text-center sm:p-8`,children:[W(Yt,{class:`mx-auto size-10 text-slate-400`}),W(`h2`,{class:`mt-4 mb-0 text-lg font-extrabold text-slate-950`,children:`조건에 맞는 기기가 없습니다`}),W(`p`,{class:`mt-2 mb-0 text-sm text-slate-600`,children:`검색어 또는 필터를 변경해 주세요.`})]}),W(`p`,{class:`mt-5 mb-0 rounded-xl bg-slate-100 px-4 py-3 text-xs leading-5 text-slate-600`,children:`유선/기타 상태는 ARP 테이블을 기준으로 하며, 최근 DHCP 임대는 기기가 연결 해제된 뒤에도 일정 시간 남을 수 있습니다.`})]}):null}function Ms({description:e,icon:t,label:n,value:r}){return W(`article`,{class:`rounded-2xl border border-slate-200 bg-white p-4 shadow-sm shadow-slate-900/5 sm:p-5`,children:[W(`div`,{class:`flex items-start justify-between gap-4`,children:[W(`div`,{children:[W(`p`,{class:`m-0 text-xs font-bold uppercase tracking-[0.14em] text-slate-500`,children:n}),W(`p`,{class:`mt-3 mb-0 text-3xl font-black tracking-tight text-slate-950`,children:r})]}),W(`span`,{class:`grid size-11 shrink-0 place-items-center rounded-xl bg-teal-50 text-teal-700`,children:t})]}),W(`p`,{class:`mt-3 mb-0 text-xs leading-5 text-slate-500`,children:e})]})}function Ns(e){return e+.5|0}var Ps=(e,t,n)=>Math.max(Math.min(e,n),t);function Fs(e){return Ps(Ns(e*2.55),0,255)}function Is(e){return Ps(Ns(e*255),0,255)}function Ls(e){return Ps(Ns(e/2.55)/100,0,1)}function Rs(e){return Ps(Ns(e*100),0,100)}var zs={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,A:10,B:11,C:12,D:13,E:14,F:15,a:10,b:11,c:12,d:13,e:14,f:15},Bs=[...`0123456789ABCDEF`],Vs=e=>Bs[e&15],Hs=e=>Bs[(e&240)>>4]+Bs[e&15],Us=e=>(e&240)>>4==(e&15),Ws=e=>Us(e.r)&&Us(e.g)&&Us(e.b)&&Us(e.a);function Gs(e){var t=e.length,n;return e[0]===`#`&&(t===4||t===5?n={r:255&zs[e[1]]*17,g:255&zs[e[2]]*17,b:255&zs[e[3]]*17,a:t===5?zs[e[4]]*17:255}:(t===7||t===9)&&(n={r:zs[e[1]]<<4|zs[e[2]],g:zs[e[3]]<<4|zs[e[4]],b:zs[e[5]]<<4|zs[e[6]],a:t===9?zs[e[7]]<<4|zs[e[8]]:255})),n}var Ks=(e,t)=>e<255?t(e):``;function qs(e){var t=Ws(e)?Vs:Hs;return e?`#`+t(e.r)+t(e.g)+t(e.b)+Ks(e.a,t):void 0}var Js=/^(hsla?|hwb|hsv)\(\s*([-+.e\d]+)(?:deg)?[\s,]+([-+.e\d]+)%[\s,]+([-+.e\d]+)%(?:[\s,]+([-+.e\d]+)(%)?)?\s*\)$/;function Ys(e,t,n){let r=t*Math.min(n,1-n),i=(t,i=(t+e/30)%12)=>n-r*Math.max(Math.min(i-3,9-i,1),-1);return[i(0),i(8),i(4)]}function Xs(e,t,n){let r=(r,i=(r+e/60)%6)=>n-n*t*Math.max(Math.min(i,4-i,1),0);return[r(5),r(3),r(1)]}function Zs(e,t,n){let r=Ys(e,1,.5),i;for(t+n>1&&(i=1/(t+n),t*=i,n*=i),i=0;i<3;i++)r[i]*=1-t-n,r[i]+=t;return r}function Qs(e,t,n,r,i){return e===i?(t-n)/r+(t.5?l/(2-i-a):l/(i+a),s=Qs(t,n,r,l,i),s=s*60+.5),[s|0,c||0,o]}function ec(e,t,n,r){return(Array.isArray(t)?e(t[0],t[1],t[2]):e(t,n,r)).map(Is)}function tc(e,t,n){return ec(Ys,e,t,n)}function nc(e,t,n){return ec(Zs,e,t,n)}function rc(e,t,n){return ec(Xs,e,t,n)}function ic(e){return(e%360+360)%360}function ac(e){let t=Js.exec(e),n=255,r;if(!t)return;t[5]!==r&&(n=t[6]?Fs(+t[5]):Is(+t[5]));let i=ic(+t[2]),a=t[3]/100,o=t[4]/100;return r=t[1]===`hwb`?nc(i,a,o):t[1]===`hsv`?rc(i,a,o):tc(i,a,o),{r:r[0],g:r[1],b:r[2],a:n}}function oc(e,t){var n=$s(e);n[0]=ic(n[0]+t),n=tc(n),e.r=n[0],e.g=n[1],e.b=n[2]}function sc(e){if(!e)return;let t=$s(e),n=t[0],r=Rs(t[1]),i=Rs(t[2]);return e.a<255?`hsla(${n}, ${r}%, ${i}%, ${Ls(e.a)})`:`hsl(${n}, ${r}%, ${i}%)`}var cc={x:`dark`,Z:`light`,Y:`re`,X:`blu`,W:`gr`,V:`medium`,U:`slate`,A:`ee`,T:`ol`,S:`or`,B:`ra`,C:`lateg`,D:`ights`,R:`in`,Q:`turquois`,E:`hi`,P:`ro`,O:`al`,N:`le`,M:`de`,L:`yello`,F:`en`,K:`ch`,G:`arks`,H:`ea`,I:`ightg`,J:`wh`},lc={OiceXe:`f0f8ff`,antiquewEte:`faebd7`,aqua:`ffff`,aquamarRe:`7fffd4`,azuY:`f0ffff`,beige:`f5f5dc`,bisque:`ffe4c4`,black:`0`,blanKedOmond:`ffebcd`,Xe:`ff`,XeviTet:`8a2be2`,bPwn:`a52a2a`,burlywood:`deb887`,caMtXe:`5f9ea0`,KartYuse:`7fff00`,KocTate:`d2691e`,cSO:`ff7f50`,cSnflowerXe:`6495ed`,cSnsilk:`fff8dc`,crimson:`dc143c`,cyan:`ffff`,xXe:`8b`,xcyan:`8b8b`,xgTMnPd:`b8860b`,xWay:`a9a9a9`,xgYF:`6400`,xgYy:`a9a9a9`,xkhaki:`bdb76b`,xmagFta:`8b008b`,xTivegYF:`556b2f`,xSange:`ff8c00`,xScEd:`9932cc`,xYd:`8b0000`,xsOmon:`e9967a`,xsHgYF:`8fbc8f`,xUXe:`483d8b`,xUWay:`2f4f4f`,xUgYy:`2f4f4f`,xQe:`ced1`,xviTet:`9400d3`,dAppRk:`ff1493`,dApskyXe:`bfff`,dimWay:`696969`,dimgYy:`696969`,dodgerXe:`1e90ff`,fiYbrick:`b22222`,flSOwEte:`fffaf0`,foYstWAn:`228b22`,fuKsia:`ff00ff`,gaRsbSo:`dcdcdc`,ghostwEte:`f8f8ff`,gTd:`ffd700`,gTMnPd:`daa520`,Way:`808080`,gYF:`8000`,gYFLw:`adff2f`,gYy:`808080`,honeyMw:`f0fff0`,hotpRk:`ff69b4`,RdianYd:`cd5c5c`,Rdigo:`4b0082`,ivSy:`fffff0`,khaki:`f0e68c`,lavFMr:`e6e6fa`,lavFMrXsh:`fff0f5`,lawngYF:`7cfc00`,NmoncEffon:`fffacd`,ZXe:`add8e6`,ZcSO:`f08080`,Zcyan:`e0ffff`,ZgTMnPdLw:`fafad2`,ZWay:`d3d3d3`,ZgYF:`90ee90`,ZgYy:`d3d3d3`,ZpRk:`ffb6c1`,ZsOmon:`ffa07a`,ZsHgYF:`20b2aa`,ZskyXe:`87cefa`,ZUWay:`778899`,ZUgYy:`778899`,ZstAlXe:`b0c4de`,ZLw:`ffffe0`,lime:`ff00`,limegYF:`32cd32`,lRF:`faf0e6`,magFta:`ff00ff`,maPon:`800000`,VaquamarRe:`66cdaa`,VXe:`cd`,VScEd:`ba55d3`,VpurpN:`9370db`,VsHgYF:`3cb371`,VUXe:`7b68ee`,VsprRggYF:`fa9a`,VQe:`48d1cc`,VviTetYd:`c71585`,midnightXe:`191970`,mRtcYam:`f5fffa`,mistyPse:`ffe4e1`,moccasR:`ffe4b5`,navajowEte:`ffdead`,navy:`80`,Tdlace:`fdf5e6`,Tive:`808000`,TivedBb:`6b8e23`,Sange:`ffa500`,SangeYd:`ff4500`,ScEd:`da70d6`,pOegTMnPd:`eee8aa`,pOegYF:`98fb98`,pOeQe:`afeeee`,pOeviTetYd:`db7093`,papayawEp:`ffefd5`,pHKpuff:`ffdab9`,peru:`cd853f`,pRk:`ffc0cb`,plum:`dda0dd`,powMrXe:`b0e0e6`,purpN:`800080`,YbeccapurpN:`663399`,Yd:`ff0000`,Psybrown:`bc8f8f`,PyOXe:`4169e1`,saddNbPwn:`8b4513`,sOmon:`fa8072`,sandybPwn:`f4a460`,sHgYF:`2e8b57`,sHshell:`fff5ee`,siFna:`a0522d`,silver:`c0c0c0`,skyXe:`87ceeb`,UXe:`6a5acd`,UWay:`708090`,UgYy:`708090`,snow:`fffafa`,sprRggYF:`ff7f`,stAlXe:`4682b4`,tan:`d2b48c`,teO:`8080`,tEstN:`d8bfd8`,tomato:`ff6347`,Qe:`40e0d0`,viTet:`ee82ee`,JHt:`f5deb3`,wEte:`ffffff`,wEtesmoke:`f5f5f5`,Lw:`ffff00`,LwgYF:`9acd32`};function uc(){let e={},t=Object.keys(lc),n=Object.keys(cc),r,i,a,o,s;for(r=0;r>16&255,a>>8&255,a&255]}return e}var dc;function fc(e){dc||(dc=uc(),dc.transparent=[0,0,0,0]);let t=dc[e.toLowerCase()];return t&&{r:t[0],g:t[1],b:t[2],a:t.length===4?t[3]:255}}var pc=/^rgba?\(\s*([-+.\d]+)(%)?[\s,]+([-+.e\d]+)(%)?[\s,]+([-+.e\d]+)(%)?(?:[\s,/]+([-+.e\d]+)(%)?)?\s*\)$/;function mc(e){let t=pc.exec(e),n=255,r,i,a;if(t){if(t[7]!==r){let e=+t[7];n=t[8]?Fs(e):Ps(e*255,0,255)}return r=+t[1],i=+t[3],a=+t[5],r=255&(t[2]?Fs(r):Ps(r,0,255)),i=255&(t[4]?Fs(i):Ps(i,0,255)),a=255&(t[6]?Fs(a):Ps(a,0,255)),{r,g:i,b:a,a:n}}}function hc(e){return e&&(e.a<255?`rgba(${e.r}, ${e.g}, ${e.b}, ${Ls(e.a)})`:`rgb(${e.r}, ${e.g}, ${e.b})`)}var gc=e=>e<=.0031308?e*12.92:e**(1/2.4)*1.055-.055,_c=e=>e<=.04045?e/12.92:((e+.055)/1.055)**2.4;function vc(e,t,n){let r=_c(Ls(e.r)),i=_c(Ls(e.g)),a=_c(Ls(e.b));return{r:Is(gc(r+n*(_c(Ls(t.r))-r))),g:Is(gc(i+n*(_c(Ls(t.g))-i))),b:Is(gc(a+n*(_c(Ls(t.b))-a))),a:e.a+n*(t.a-e.a)}}function yc(e,t,n){if(e){let r=$s(e);r[t]=Math.max(0,Math.min(r[t]+r[t]*n,t===0?360:1)),r=tc(r),e.r=r[0],e.g=r[1],e.b=r[2]}}function bc(e,t){return e&&Object.assign(t||{},e)}function xc(e){var t={r:0,g:0,b:0,a:255};return Array.isArray(e)?e.length>=3&&(t={r:e[0],g:e[1],b:e[2],a:255},e.length>3&&(t.a=Is(e[3]))):(t=bc(e,{r:0,g:0,b:0,a:1}),t.a=Is(t.a)),t}function Sc(e){return e.charAt(0)===`r`?mc(e):ac(e)}var Cc=class e{constructor(t){if(t instanceof e)return t;let n=typeof t,r;n===`object`?r=xc(t):n===`string`&&(r=Gs(t)||fc(t)||Sc(t)),this._rgb=r,this._valid=!!r}get valid(){return this._valid}get rgb(){var e=bc(this._rgb);return e&&(e.a=Ls(e.a)),e}set rgb(e){this._rgb=xc(e)}rgbString(){return this._valid?hc(this._rgb):void 0}hexString(){return this._valid?qs(this._rgb):void 0}hslString(){return this._valid?sc(this._rgb):void 0}mix(e,t){if(e){let n=this.rgb,r=e.rgb,i,a=t===i?.5:t,o=2*a-1,s=n.a-r.a,c=((o*s===-1?o:(o+s)/(1+o*s))+1)/2;i=1-c,n.r=255&c*n.r+i*r.r+.5,n.g=255&c*n.g+i*r.g+.5,n.b=255&c*n.b+i*r.b+.5,n.a=a*n.a+(1-a)*r.a,this.rgb=n}return this}interpolate(e,t){return e&&(this._rgb=vc(this._rgb,e._rgb,t)),this}clone(){return new e(this.rgb)}alpha(e){return this._rgb.a=Is(e),this}clearer(e){let t=this._rgb;return t.a*=1-e,this}greyscale(){let e=this._rgb;return e.r=e.g=e.b=Ns(e.r*.3+e.g*.59+e.b*.11),this}opaquer(e){let t=this._rgb;return t.a*=1+e,this}negate(){let e=this._rgb;return e.r=255-e.r,e.g=255-e.g,e.b=255-e.b,this}lighten(e){return yc(this._rgb,2,e),this}darken(e){return yc(this._rgb,2,-e),this}saturate(e){return yc(this._rgb,1,e),this}desaturate(e){return yc(this._rgb,1,-e),this}rotate(e){return oc(this._rgb,e),this}};function wc(){}var Tc=(()=>{let e=0;return()=>e++})();function X(e){return e==null}function Ec(e){if(Array.isArray&&Array.isArray(e))return!0;let t=Object.prototype.toString.call(e);return t.slice(0,7)===`[object`&&t.slice(-6)===`Array]`}function Z(e){return e!==null&&Object.prototype.toString.call(e)===`[object Object]`}function Dc(e){return(typeof e==`number`||e instanceof Number)&&isFinite(+e)}function Oc(e,t){return Dc(e)?e:t}function Q(e,t){return e===void 0?t:e}var kc=(e,t)=>typeof e==`string`&&e.endsWith(`%`)?parseFloat(e)/100:+e/t,Ac=(e,t)=>typeof e==`string`&&e.endsWith(`%`)?parseFloat(e)/100*t:+e;function jc(e,t,n){if(e&&typeof e.call==`function`)return e.apply(n,t)}function Mc(e,t,n,r){let i,a,o;if(Ec(e)){if(a=e.length,r)for(i=a-1;i>=0;i--)t.call(n,e[i],i);else for(i=0;ie,x:e=>e.x,y:e=>e.y};function Vc(e){let t=e.split(`.`),n=[],r=``;for(let e of t)r+=e,r.endsWith(`\\`)?r=r.slice(0,-1)+`.`:(n.push(r),r=``);return n}function Hc(e){let t=Vc(e);return e=>{for(let n of t){if(n===``)break;e=e&&e[n]}return e}}function Uc(e,t){return(Bc[t]||(Bc[t]=Hc(t)))(e)}function Wc(e){return e.charAt(0).toUpperCase()+e.slice(1)}var Gc=e=>e!==void 0,Kc=e=>typeof e==`function`,qc=(e,t)=>{if(e.size!==t.size)return!1;for(let n of e)if(!t.has(n))return!1;return!0};function Jc(e){return e.type===`mouseup`||e.type===`click`||e.type===`contextmenu`}var $=Math.PI,Yc=2*$,Xc=Yc+$,Zc=1/0,Qc=$/180,$c=$/2,el=$/4,tl=$*2/3,nl=Math.log10,rl=Math.sign;function il(e,t,n){return Math.abs(e-t)e-t).pop(),t}function sl(e){return typeof e==`symbol`||typeof e==`object`&&!!e&&!(Symbol.toPrimitive in e||`toString`in e||`valueOf`in e)}function cl(e){return!sl(e)&&!isNaN(parseFloat(e))&&isFinite(e)}function ll(e,t){let n=Math.round(e);return n-t<=e&&n+t>=e}function ul(e,t,n){let r,i,a;for(r=0,i=e.length;rc&&l=Math.min(t,n)-r&&e<=Math.max(t,n)+r}function Sl(e,t,n){n=n||(n=>e[n]1;)a=i+r>>1,n(a)?i=a:r=a;return{lo:i,hi:r}}var Cl=(e,t,n,r)=>Sl(e,n,r?r=>{let i=e[r][t];return ie[r][t]Sl(e,n,r=>e[r][t]>=n);function Tl(e,t,n){let r=0,i=e.length;for(;rr&&e[i-1]>n;)i--;return r>0||i{let n=`_onData`+Wc(t),r=e[t];Object.defineProperty(e,t,{configurable:!0,enumerable:!1,value(...t){let i=r.apply(this,t);return e._chartjs.listeners.forEach(e=>{typeof e[n]==`function`&&e[n](...t)}),i}})}))}function Ol(e,t){let n=e._chartjs;if(!n)return;let r=n.listeners,i=r.indexOf(t);i!==-1&&r.splice(i,1),!(r.length>0)&&(El.forEach(t=>{delete e[t]}),delete e._chartjs)}function kl(e){let t=new Set(e);return t.size===e.length?e:Array.from(t)}var Al=function(){return typeof window>`u`?function(e){return e()}:window.requestAnimationFrame}();function jl(e,t){let n=[],r=!1;return function(...i){n=i,r||(r=!0,Al.call(window,()=>{r=!1,e.apply(t,n)}))}}function Ml(e,t){let n;return function(...r){return t?(clearTimeout(n),n=setTimeout(e,t,r)):e.apply(this,r),t}}var Nl=e=>e===`start`?`left`:e===`end`?`right`:`center`,Pl=(e,t,n)=>e===`start`?t:e===`end`?n:(t+n)/2;function Fl(e,t,n){let r=t.length,i=0,a=r;if(e._sorted){let{iScale:o,vScale:s,_parsed:c}=e,l=e.dataset&&e.dataset.options?e.dataset.options.spanGaps:null,u=o.axis,{min:d,max:f,minDefined:p,maxDefined:m}=o.getUserBounds();if(p){if(i=Math.min(Cl(c,u,d).lo,n?r:Cl(t,u,o.getPixelForValue(d)).lo),l){let e=c.slice(0,i+1).reverse().findIndex(e=>!X(e[s.axis]));i-=Math.max(0,e)}i=yl(i,0,r-1)}if(m){let e=Math.max(Cl(c,o.axis,f,!0).hi+1,n?0:Cl(t,u,o.getPixelForValue(f),!0).hi+1);if(l){let t=c.slice(e-1).findIndex(e=>!X(e[s.axis]));e+=Math.max(0,t)}a=yl(e,i,r)-i}else a=r-i}return{start:i,count:a}}function Il(e){let{xScale:t,yScale:n,_scaleRanges:r}=e,i={xmin:t.min,xmax:t.max,ymin:n.min,ymax:n.max};if(!r)return e._scaleRanges=i,!0;let a=r.xmin!==t.min||r.xmax!==t.max||r.ymin!==n.min||r.ymax!==n.max;return Object.assign(r,i),a}var Ll=e=>e===0||e===1,Rl=(e,t,n)=>-(2**(10*--e)*Math.sin((e-t)*Yc/n)),zl=(e,t,n)=>2**(-10*e)*Math.sin((e-t)*Yc/n)+1,Bl={linear:e=>e,easeInQuad:e=>e*e,easeOutQuad:e=>-e*(e-2),easeInOutQuad:e=>(e/=.5)<1?.5*e*e:-.5*(--e*(e-2)-1),easeInCubic:e=>e*e*e,easeOutCubic:e=>--e*e*e+1,easeInOutCubic:e=>(e/=.5)<1?.5*e*e*e:.5*((e-=2)*e*e+2),easeInQuart:e=>e*e*e*e,easeOutQuart:e=>-(--e*e*e*e-1),easeInOutQuart:e=>(e/=.5)<1?.5*e*e*e*e:-.5*((e-=2)*e*e*e-2),easeInQuint:e=>e*e*e*e*e,easeOutQuint:e=>--e*e*e*e*e+1,easeInOutQuint:e=>(e/=.5)<1?.5*e*e*e*e*e:.5*((e-=2)*e*e*e*e+2),easeInSine:e=>-Math.cos(e*$c)+1,easeOutSine:e=>Math.sin(e*$c),easeInOutSine:e=>-.5*(Math.cos($*e)-1),easeInExpo:e=>e===0?0:2**(10*(e-1)),easeOutExpo:e=>e===1?1:-(2**(-10*e))+1,easeInOutExpo:e=>Ll(e)?e:e<.5?.5*2**(10*(e*2-1)):.5*(-(2**(-10*(e*2-1)))+2),easeInCirc:e=>e>=1?e:-(Math.sqrt(1-e*e)-1),easeOutCirc:e=>Math.sqrt(1- --e*e),easeInOutCirc:e=>(e/=.5)<1?-.5*(Math.sqrt(1-e*e)-1):.5*(Math.sqrt(1-(e-=2)*e)+1),easeInElastic:e=>Ll(e)?e:Rl(e,.075,.3),easeOutElastic:e=>Ll(e)?e:zl(e,.075,.3),easeInOutElastic(e){let t=.1125,n=.45;return Ll(e)?e:e<.5?.5*Rl(e*2,t,n):.5+.5*zl(e*2-1,t,n)},easeInBack(e){return e*e*(2.70158*e-1.70158)},easeOutBack(e){return--e*e*(2.70158*e+1.70158)+1},easeInOutBack(e){let t=1.70158;return(e/=.5)<1?.5*(e*e*(((t*=1.525)+1)*e-t)):.5*((e-=2)*e*(((t*=1.525)+1)*e+t)+2)},easeInBounce:e=>1-Bl.easeOutBounce(1-e),easeOutBounce(e){let t=7.5625,n=2.75;return e<1/n?t*e*e:e<2/n?t*(e-=1.5/n)*e+.75:e<2.5/n?t*(e-=2.25/n)*e+.9375:t*(e-=2.625/n)*e+.984375},easeInOutBounce:e=>e<.5?Bl.easeInBounce(e*2)*.5:Bl.easeOutBounce(e*2-1)*.5+.5};function Vl(e){if(e&&typeof e==`object`){let t=e.toString();return t===`[object CanvasPattern]`||t===`[object CanvasGradient]`}return!1}function Hl(e){return Vl(e)?e:new Cc(e)}function Ul(e){return Vl(e)?e:new Cc(e).saturate(.5).darken(.1).hexString()}var Wl=[`x`,`y`,`borderWidth`,`radius`,`tension`],Gl=[`color`,`borderColor`,`backgroundColor`];function Kl(e){e.set(`animation`,{delay:void 0,duration:1e3,easing:`easeOutQuart`,fn:void 0,from:void 0,loop:void 0,to:void 0,type:void 0}),e.describe(`animation`,{_fallback:!1,_indexable:!1,_scriptable:e=>e!==`onProgress`&&e!==`onComplete`&&e!==`fn`}),e.set(`animations`,{colors:{type:`color`,properties:Gl},numbers:{type:`number`,properties:Wl}}),e.describe(`animations`,{_fallback:`animation`}),e.set(`transitions`,{active:{animation:{duration:400}},resize:{animation:{duration:0}},show:{animations:{colors:{from:`transparent`},visible:{type:`boolean`,duration:0}}},hide:{animations:{colors:{to:`transparent`},visible:{type:`boolean`,easing:`linear`,fn:e=>e|0}}}})}function ql(e){e.set(`layout`,{autoPadding:!0,padding:{top:0,right:0,bottom:0,left:0}})}var Jl=new Map;function Yl(e,t){t=t||{};let n=e+JSON.stringify(t),r=Jl.get(n);return r||(r=new Intl.NumberFormat(e,t),Jl.set(n,r)),r}function Xl(e,t,n){return Yl(t,n).format(e)}var Zl={values(e){return Ec(e)?e:``+e},numeric(e,t,n){if(e===0)return`0`;let r=this.chart.options.locale,i,a=e;if(n.length>1){let t=Math.max(Math.abs(n[0].value),Math.abs(n[n.length-1].value));(t<1e-4||t>0x38d7ea4c68000)&&(i=`scientific`),a=Ql(e,n)}let o=nl(Math.abs(a)),s=isNaN(o)?1:Math.max(Math.min(-1*Math.floor(o),20),0),c={notation:i,minimumFractionDigits:s,maximumFractionDigits:s};return Object.assign(c,this.options.ticks.format),Xl(e,r,c)},logarithmic(e,t,n){if(e===0)return`0`;let r=n[t].significand||e/10**Math.floor(nl(e));return[1,2,3,5,10,15].includes(r)||t>.8*n.length?Zl.numeric.call(this,e,t,n):``}};function Ql(e,t){let n=t.length>3?t[2].value-t[1].value:t[1].value-t[0].value;return Math.abs(n)>=1&&e!==Math.floor(e)&&(n=e-Math.floor(e)),n}var $l={formatters:Zl};function eu(e){e.set(`scale`,{display:!0,offset:!1,reverse:!1,beginAtZero:!1,bounds:`ticks`,clip:!0,grace:0,grid:{display:!0,lineWidth:1,drawOnChartArea:!0,drawTicks:!0,tickLength:8,tickWidth:(e,t)=>t.lineWidth,tickColor:(e,t)=>t.color,offset:!1},border:{display:!0,dash:[],dashOffset:0,width:1},title:{display:!1,text:``,padding:{top:4,bottom:4}},ticks:{minRotation:0,maxRotation:50,mirror:!1,textStrokeWidth:0,textStrokeColor:``,padding:3,display:!0,autoSkip:!0,autoSkipPadding:3,labelOffset:0,callback:$l.formatters.values,minor:{},major:{},align:`center`,crossAlign:`near`,showLabelBackdrop:!1,backdropColor:`rgba(255, 255, 255, 0.75)`,backdropPadding:2}}),e.route(`scale.ticks`,`color`,``,`color`),e.route(`scale.grid`,`color`,``,`borderColor`),e.route(`scale.border`,`color`,``,`borderColor`),e.route(`scale.title`,`color`,``,`color`),e.describe(`scale`,{_fallback:!1,_scriptable:e=>!e.startsWith(`before`)&&!e.startsWith(`after`)&&e!==`callback`&&e!==`parser`,_indexable:e=>e!==`borderDash`&&e!==`tickBorderDash`&&e!==`dash`}),e.describe(`scales`,{_fallback:`scale`}),e.describe(`scale.ticks`,{_scriptable:e=>e!==`backdropPadding`&&e!==`callback`,_indexable:e=>e!==`backdropPadding`})}var tu=Object.create(null),nu=Object.create(null);function ru(e,t){if(!t)return e;let n=t.split(`.`);for(let t=0,r=n.length;te.chart.platform.getDevicePixelRatio(),this.elements={},this.events=[`mousemove`,`mouseout`,`click`,`touchstart`,`touchmove`],this.font={family:`'Helvetica Neue', 'Helvetica', 'Arial', sans-serif`,size:12,style:`normal`,lineHeight:1.2,weight:null},this.hover={},this.hoverBackgroundColor=(e,t)=>Ul(t.backgroundColor),this.hoverBorderColor=(e,t)=>Ul(t.borderColor),this.hoverColor=(e,t)=>Ul(t.color),this.indexAxis=`x`,this.interaction={mode:`nearest`,intersect:!0,includeInvisible:!1},this.maintainAspectRatio=!0,this.onHover=null,this.onClick=null,this.parsing=!0,this.plugins={},this.responsive=!0,this.scale=void 0,this.scales={},this.showLine=!0,this.drawActiveElementsOnTop=!0,this.describe(e),this.apply(t)}set(e,t){return iu(this,e,t)}get(e){return ru(this,e)}describe(e,t){return iu(nu,e,t)}override(e,t){return iu(tu,e,t)}route(e,t,n,r){let i=ru(this,e),a=ru(this,n),o=`_`+t;Object.defineProperties(i,{[o]:{value:i[t],writable:!0},[t]:{enumerable:!0,get(){let e=this[o],t=a[r];return Z(e)?Object.assign({},t,e):Q(e,t)},set(e){this[o]=e}}})}apply(e){e.forEach(e=>e(this))}}({_scriptable:e=>!e.startsWith(`on`),_indexable:e=>e!==`events`,hover:{_fallback:`interaction`},interaction:{_scriptable:!1,_indexable:!1}},[Kl,ql,eu]);function ou(e){return!e||X(e.size)||X(e.family)?null:(e.style?e.style+` `:``)+(e.weight?e.weight+` `:``)+e.size+`px `+e.family}function su(e,t,n,r,i){let a=t[i];return a||(a=t[i]=e.measureText(i).width,n.push(i)),a>r&&(r=a),r}function cu(e,t,n,r){r=r||{};let i=r.data=r.data||{},a=r.garbageCollect=r.garbageCollect||[];r.font!==t&&(i=r.data={},a=r.garbageCollect=[],r.font=t),e.save(),e.font=t;let o=0,s=n.length,c,l,u,d,f;for(c=0;cn.length){for(c=0;c0&&e.stroke()}}function pu(e,t,n){return n=n||.5,!t||e&&e.x>t.left-n&&e.xt.top-n&&e.y0&&a.strokeColor!==``,c,l;for(e.save(),e.font=i.string,vu(e,a),c=0;c+e||0;function Du(e,t){let n={},r=Z(t),i=r?Object.keys(t):t,a=Z(e)?r?n=>Q(e[n],e[t[n]]):t=>e[t]:()=>e;for(let e of i)n[e]=Eu(a(e));return n}function Ou(e){return Du(e,{top:`y`,right:`x`,bottom:`y`,left:`x`})}function ku(e){return Du(e,[`topLeft`,`topRight`,`bottomLeft`,`bottomRight`])}function Au(e){let t=Ou(e);return t.width=t.left+t.right,t.height=t.top+t.bottom,t}function ju(e,t){e=e||{},t=t||au.font;let n=Q(e.size,t.size);typeof n==`string`&&(n=parseInt(n,10));let r=Q(e.style,t.style);r&&!(``+r).match(wu)&&(console.warn(`Invalid font style specified: "`+r+`"`),r=void 0);let i={family:Q(e.family,t.family),lineHeight:Tu(Q(e.lineHeight,t.lineHeight),n),size:n,style:r,weight:Q(e.weight,t.weight),string:``};return i.string=ou(i),i}function Mu(e,t,n,r){let i=!0,a,o,s;for(a=0,o=e.length;an&&e===0?0:e+t;return{min:o(r,-Math.abs(a)),max:o(i,a)}}function Pu(e,t){return Object.assign(Object.create(e),t)}function Fu(e,t=[``],n,r,i=()=>e[0]){let a=n||e;return r===void 0&&(r=Zu(`_fallback`,e)),new Proxy({[Symbol.toStringTag]:`Object`,_cacheable:!0,_scopes:e,_rootScopes:a,_fallback:r,_getTarget:i,override:n=>Fu([n,...e],t,a,r)},{deleteProperty(t,n){return delete t[n],delete t._keys,delete e[0][n],!0},get(n,r){return Bu(n,r,()=>Xu(r,t,e,n))},getOwnPropertyDescriptor(e,t){return Reflect.getOwnPropertyDescriptor(e._scopes[0],t)},getPrototypeOf(){return Reflect.getPrototypeOf(e[0])},has(e,t){return Qu(e).includes(t)},ownKeys(e){return Qu(e)},set(e,t,n){let r=e._storage||(e._storage=i());return e[t]=r[t]=n,delete e._keys,!0}})}function Iu(e,t,n,r){let i={_cacheable:!1,_proxy:e,_context:t,_subProxy:n,_stack:new Set,_descriptors:Lu(e,r),setContext:t=>Iu(e,t,n,r),override:i=>Iu(e.override(i),t,n,r)};return new Proxy(i,{deleteProperty(t,n){return delete t[n],delete e[n],!0},get(e,t,n){return Bu(e,t,()=>Vu(e,t,n))},getOwnPropertyDescriptor(t,n){return t._descriptors.allKeys?Reflect.has(e,n)?{enumerable:!0,configurable:!0}:void 0:Reflect.getOwnPropertyDescriptor(e,n)},getPrototypeOf(){return Reflect.getPrototypeOf(e)},has(t,n){return Reflect.has(e,n)},ownKeys(){return Reflect.ownKeys(e)},set(t,n,r){return e[n]=r,delete t[n],!0}})}function Lu(e,t={scriptable:!0,indexable:!0}){let{_scriptable:n=t.scriptable,_indexable:r=t.indexable,_allKeys:i=t.allKeys}=e;return{allKeys:i,scriptable:n,indexable:r,isScriptable:Kc(n)?n:()=>n,isIndexable:Kc(r)?r:()=>r}}var Ru=(e,t)=>e?e+Wc(t):t,zu=(e,t)=>Z(t)&&e!==`adapters`&&(Object.getPrototypeOf(t)===null||t.constructor===Object);function Bu(e,t,n){if(Object.prototype.hasOwnProperty.call(e,t)||t===`constructor`)return e[t];let r=n();return e[t]=r,r}function Vu(e,t,n){let{_proxy:r,_context:i,_subProxy:a,_descriptors:o}=e,s=r[t];return Kc(s)&&o.isScriptable(t)&&(s=Hu(t,s,e,n)),Ec(s)&&s.length&&(s=Uu(t,s,e,o.isIndexable)),zu(t,s)&&(s=Iu(s,i,a&&a[t],o)),s}function Hu(e,t,n,r){let{_proxy:i,_context:a,_subProxy:o,_stack:s}=n;if(s.has(e))throw Error(`Recursion detected: `+Array.from(s).join(`->`)+`->`+e);s.add(e);let c=t(a,o||r);return s.delete(e),zu(e,c)&&(c=qu(i._scopes,i,e,c)),c}function Uu(e,t,n,r){let{_proxy:i,_context:a,_subProxy:o,_descriptors:s}=n;if(a.index!==void 0&&r(e))return t[a.index%t.length];if(Z(t[0])){let n=t,r=i._scopes.filter(e=>e!==n);t=[];for(let c of n){let n=qu(r,i,e,c);t.push(Iu(n,a,o&&o[e],s))}}return t}function Wu(e,t,n){return Kc(e)?e(t,n):e}var Gu=(e,t)=>e===!0?t:typeof e==`string`?Uc(t,e):void 0;function Ku(e,t,n,r,i){for(let a of t){let t=Gu(n,a);if(t){e.add(t);let a=Wu(t._fallback,n,i);if(a!==void 0&&a!==n&&a!==r)return a}else if(t===!1&&r!==void 0&&n!==r)return null}return!1}function qu(e,t,n,r){let i=t._rootScopes,a=Wu(t._fallback,n,r),o=[...e,...i],s=new Set;s.add(r);let c=Ju(s,o,n,a||n,r);return c===null||a!==void 0&&a!==n&&(c=Ju(s,o,a,c,r),c===null)?!1:Fu(Array.from(s),[``],i,a,()=>Yu(t,n,r))}function Ju(e,t,n,r,i){for(;n;)n=Ku(e,t,n,r,i);return n}function Yu(e,t,n){let r=e._getTarget();t in r||(r[t]={});let i=r[t];return Ec(i)&&Z(n)?n:i||{}}function Xu(e,t,n,r){let i;for(let a of t)if(i=Zu(Ru(a,e),n),i!==void 0)return zu(e,i)?qu(n,r,e,i):i}function Zu(e,t){for(let n of t){if(!n)continue;let t=n[e];if(t!==void 0)return t}}function Qu(e){let t=e._keys;return t||(t=e._keys=$u(e._scopes)),t}function $u(e){let t=new Set;for(let n of e)for(let e of Object.keys(n).filter(e=>!e.startsWith(`_`)))t.add(e);return Array.from(t)}function ed(e,t,n,r){let{iScale:i}=e,{key:a=`r`}=this._parsing,o=Array(r),s,c,l,u;for(s=0,c=r;ste===`x`?`y`:`x`;function id(e,t,n,r){let i=e.skip?t:e,a=t,o=n.skip?t:n,s=hl(a,i),c=hl(o,a),l=s/(s+c),u=c/(s+c);l=isNaN(l)?0:l,u=isNaN(u)?0:u;let d=r*l,f=r*u;return{previous:{x:a.x-d*(o.x-i.x),y:a.y-d*(o.y-i.y)},next:{x:a.x+f*(o.x-i.x),y:a.y+f*(o.y-i.y)}}}function ad(e,t,n){let r=e.length,i,a,o,s,c,l=nd(e,0);for(let u=0;u!e.skip)),t.cubicInterpolationMode===`monotone`)sd(e,i);else{let n=r?e[e.length-1]:e[0];for(a=0,o=e.length;ae.ownerDocument.defaultView.getComputedStyle(e,null);function hd(e,t){return md(e).getPropertyValue(t)}var gd=[`top`,`right`,`bottom`,`left`];function _d(e,t,n){let r={};n=n?`-`+n:``;for(let i=0;i<4;i++){let a=gd[i];r[a]=parseFloat(e[t+`-`+a+n])||0}return r.width=r.left+r.right,r.height=r.top+r.bottom,r}var vd=(e,t,n)=>(e>0||t>0)&&(!n||!n.shadowRoot);function yd(e,t){let n=e.touches,r=n&&n.length?n[0]:e,{offsetX:i,offsetY:a}=r,o=!1,s,c;if(vd(i,a,e.target))s=i,c=a;else{let e=t.getBoundingClientRect();s=r.clientX-e.left,c=r.clientY-e.top,o=!0}return{x:s,y:c,box:o}}function bd(e,t){if(`native`in e)return e;let{canvas:n,currentDevicePixelRatio:r}=t,i=md(n),a=i.boxSizing===`border-box`,o=_d(i,`padding`),s=_d(i,`border`,`width`),{x:c,y:l,box:u}=yd(e,n),d=o.left+(u&&s.left),f=o.top+(u&&s.top),{width:p,height:m}=t;return a&&(p-=o.width+s.width,m-=o.height+s.height),{x:Math.round((c-d)/p*n.width/r),y:Math.round((l-f)/m*n.height/r)}}function xd(e,t,n){let r,i;if(t===void 0||n===void 0){let a=e&&fd(e);if(!a)t=e.clientWidth,n=e.clientHeight;else{let e=a.getBoundingClientRect(),o=md(a),s=_d(o,`border`,`width`),c=_d(o,`padding`);t=e.width-c.width-s.width,n=e.height-c.height-s.height,r=pd(o.maxWidth,a,`clientWidth`),i=pd(o.maxHeight,a,`clientHeight`)}}return{width:t,height:n,maxWidth:r||Zc,maxHeight:i||Zc}}var Sd=e=>Math.round(e*10)/10;function Cd(e,t,n,r){let i=md(e),a=_d(i,`margin`),o=pd(i.maxWidth,e,`clientWidth`)||Zc,s=pd(i.maxHeight,e,`clientHeight`)||Zc,c=xd(e,t,n),{width:l,height:u}=c;if(i.boxSizing===`content-box`){let e=_d(i,`border`,`width`),t=_d(i,`padding`);l-=t.width+e.width,u-=t.height+e.height}return l=Math.max(0,l-a.width),u=Math.max(0,r?l/r:u-a.height),l=Sd(Math.min(l,o,c.maxWidth)),u=Sd(Math.min(u,s,c.maxHeight)),l&&!u&&(u=Sd(l/2)),(t!==void 0||n!==void 0)&&r&&c.height&&u>c.height&&(u=c.height,l=Sd(Math.floor(u*r))),{width:l,height:u}}function wd(e,t,n){let r=t||1,i=Sd(e.height*r),a=Sd(e.width*r);e.height=Sd(e.height),e.width=Sd(e.width);let o=e.canvas;o.style&&(n||!o.style.height&&!o.style.width)&&(o.style.height=`${e.height}px`,o.style.width=`${e.width}px`);let s=Math.floor(i),c=Math.floor(a);return e.currentDevicePixelRatio!==r||o.height!==s||o.width!==c?(e.currentDevicePixelRatio=r,o.height=s,o.width=c,e.ctx.setTransform(r,0,0,r,0,0),!0):!1}var Td=function(){let e=!1;try{let t={get passive(){return e=!0,!1}};dd()&&(window.addEventListener(`test`,null,t),window.removeEventListener(`test`,null,t))}catch{}return e}();function Ed(e,t){let n=hd(e,t),r=n&&n.match(/^(\d+)(\.\d+)?px$/);return r?+r[1]:void 0}function Dd(e,t,n,r){return{x:e.x+n*(t.x-e.x),y:e.y+n*(t.y-e.y)}}function Od(e,t,n,r){return{x:e.x+n*(t.x-e.x),y:r===`middle`?n<.5?e.y:t.y:r===`after`?n<1?e.y:t.y:n>0?t.y:e.y}}function kd(e,t,n,r){let i={x:e.cp2x,y:e.cp2y},a={x:t.cp1x,y:t.cp1y},o=Dd(e,i,n),s=Dd(i,a,n),c=Dd(a,t,n);return Dd(Dd(o,s,n),Dd(s,c,n),n)}var Ad=function(e,t){return{x(n){return e+e+t-n},setWidth(e){t=e},textAlign(e){return e===`center`?e:e===`right`?`left`:`right`},xPlus(e,t){return e-t},leftForLtr(e,t){return e-t}}},jd=function(){return{x(e){return e},setWidth(e){},textAlign(e){return e},xPlus(e,t){return e+t},leftForLtr(e,t){return e}}};function Md(e,t,n){return e?Ad(t,n):jd()}function Nd(e,t){let n,r;(t===`ltr`||t===`rtl`)&&(n=e.canvas.style,r=[n.getPropertyValue(`direction`),n.getPropertyPriority(`direction`)],n.setProperty(`direction`,t,`important`),e.prevTextDirection=r)}function Pd(e,t){t!==void 0&&(delete e.prevTextDirection,e.canvas.style.setProperty(`direction`,t[0],t[1]))}function Fd(e){return e===`angle`?{between:vl,compare:gl,normalize:_l}:{between:xl,compare:(e,t)=>e-t,normalize:e=>e}}function Id({start:e,end:t,count:n,loop:r,style:i}){return{start:e%n,end:t%n,loop:r&&(t-e+1)%n===0,style:i}}function Ld(e,t,n){let{property:r,start:i,end:a}=n,{between:o,normalize:s}=Fd(r),c=t.length,{start:l,end:u,loop:d}=e,f,p;if(d){for(l+=c,u+=c,f=0,p=c;fc(i,y,_)&&s(i,y)!==0,x=()=>s(a,_)===0||c(a,y,_),S=()=>h||b(),C=()=>!h||x();for(let e=u,n=u;e<=d;++e)v=t[e%o],!v.skip&&(_=l(v[r]),_!==y&&(h=c(_,i,a),g===null&&S()&&(g=s(_,i)===0?e:n),g!==null&&C()&&(m.push(Id({start:g,end:e,loop:f,count:o,style:p})),g=null),n=e,y=_));return g!==null&&m.push(Id({start:g,end:d,loop:f,count:o,style:p})),m}function zd(e,t){let n=[],r=e.segments;for(let i=0;ii&&e[a%t].skip;)a--;return a%=t,{start:i,end:a}}function Vd(e,t,n,r){let i=e.length,a=[],o=t,s=e[t],c;for(c=t+1;c<=n;++c){let n=e[c%i];n.skip||n.stop?s.skip||(r=!1,a.push({start:t%i,end:(c-1)%i,loop:r}),t=o=n.stop?c:null):(o=c,s.skip&&(t=c)),s=n}return o!==null&&a.push({start:t%i,end:o%i,loop:r}),a}function Hd(e,t){let n=e.points,r=e.options.spanGaps,i=n.length;if(!i)return[];let a=!!e._loop,{start:o,end:s}=Bd(n,i,a,r);return r===!0?Ud(e,[{start:o,end:s,loop:a}],n,t):Ud(e,Vd(n,o,sr({chart:e,initial:t.initial,numSteps:a,currentStep:Math.min(n-t.start,a)}))}_refresh(){this._request||(this._running=!0,this._request=Al.call(window,()=>{this._update(),this._request=null,this._running&&this._refresh()}))}_update(e=Date.now()){let t=0;this._charts.forEach((n,r)=>{if(!n.running||!n.items.length)return;let i=n.items,a=i.length-1,o=!1,s;for(;a>=0;--a)s=i[a],s._active?(s._total>n.duration&&(n.duration=s._total),s.tick(e),o=!0):(i[a]=i[i.length-1],i.pop());o&&(r.draw(),this._notify(r,n,e,`progress`)),i.length||(n.running=!1,this._notify(r,n,e,`complete`),n.initial=!1),t+=i.length}),this._lastDate=e,t===0&&(this._running=!1)}_getAnims(e){let t=this._charts,n=t.get(e);return n||(n={running:!1,initial:!0,items:[],listeners:{complete:[],progress:[]}},t.set(e,n)),n}listen(e,t,n){this._getAnims(e).listeners[t].push(n)}add(e,t){t&&t.length&&this._getAnims(e).items.push(...t)}has(e){return this._getAnims(e).items.length>0}start(e){let t=this._charts.get(e);t&&(t.running=!0,t.start=Date.now(),t.duration=t.items.reduce((e,t)=>Math.max(e,t._duration),0),this._refresh())}running(e){if(!this._running)return!1;let t=this._charts.get(e);return!(!t||!t.running||!t.items.length)}stop(e){let t=this._charts.get(e);if(!t||!t.items.length)return;let n=t.items,r=n.length-1;for(;r>=0;--r)n[r].cancel();t.items=[],this._notify(e,t,Date.now(),`complete`)}remove(e){return this._charts.delete(e)}},Zd=`transparent`,Qd={boolean(e,t,n){return n>.5?t:e},color(e,t,n){let r=Hl(e||Zd),i=r.valid&&Hl(t||Zd);return i&&i.valid?i.mix(r,n).hexString():t},number(e,t,n){return e+(t-e)*n}},$d=class{constructor(e,t,n,r){let i=t[n];r=Mu([e.to,r,i,e.from]);let a=Mu([e.from,i,r]);this._active=!0,this._fn=e.fn||Qd[e.type||typeof a],this._easing=Bl[e.easing]||Bl.linear,this._start=Math.floor(Date.now()+(e.delay||0)),this._duration=this._total=Math.floor(e.duration),this._loop=!!e.loop,this._target=t,this._prop=n,this._from=a,this._to=r,this._promises=void 0}active(){return this._active}update(e,t,n){if(this._active){this._notify(!1);let r=this._target[this._prop],i=n-this._start,a=this._duration-i;this._start=n,this._duration=Math.floor(Math.max(a,e.duration)),this._total+=i,this._loop=!!e.loop,this._to=Mu([e.to,t,r,e.from]),this._from=Mu([e.from,r,t])}}cancel(){this._active&&(this.tick(Date.now()),this._active=!1,this._notify(!1))}tick(e){let t=e-this._start,n=this._duration,r=this._prop,i=this._from,a=this._loop,o=this._to,s;this._active=i!==o&&(a||t1?2-s:s,s=this._easing(Math.min(1,Math.max(0,s))),this._target[r]=this._fn(i,o,s)):(this._target[r]=o,this._notify(!0))}wait(){let e=this._promises||(this._promises=[]);return new Promise((t,n)=>{e.push({res:t,rej:n})})}_notify(e){let t=e?`res`:`rej`,n=this._promises||[];for(let e=0;e{let i=e[r];if(!Z(i))return;let a={};for(let e of t)a[e]=i[e];(Ec(i.properties)&&i.properties||[r]).forEach(e=>{e!==r&&n.has(e)||n.set(e,a)})})}_animateOptions(e,t){let n=t.options,r=nf(e,n);if(!r)return[];let i=this._createAnimations(r,n);return n.$shared&&tf(e.options.$animations,n).then(()=>{e.options=n},()=>{}),i}_createAnimations(e,t){let n=this._properties,r=[],i=e.$animations||(e.$animations={}),a=Object.keys(t),o=Date.now(),s;for(s=a.length-1;s>=0;--s){let c=a[s];if(c.charAt(0)===`$`)continue;if(c===`options`){r.push(...this._animateOptions(e,t));continue}let l=t[c],u=i[c],d=n.get(c);if(u){if(d&&u.active()){u.update(d,l,o);continue}u.cancel()}!d||!d.duration?e[c]=l:(i[c]=u=new $d(d,e,c,l),r.push(u))}return r}update(e,t){if(this._properties.size===0){Object.assign(e,t);return}let n=this._createAnimations(e,t);if(n.length)return Xd.add(this._chart,n),!0}};function tf(e,t){let n=[],r=Object.keys(t);for(let t=0;t0||!n&&t<0)return i.index}return null}function hf(e,t){let{chart:n,_cachedMeta:r}=e,i=n._stacks||(n._stacks={}),{iScale:a,vScale:o,index:s}=r,c=a.axis,l=o.axis,u=df(a,o,r),d=t.length,f;for(let e=0;en[e].axis===t).shift()}function _f(e,t){return Pu(e,{active:!1,dataset:void 0,datasetIndex:t,index:t,mode:`default`,type:`dataset`})}function vf(e,t,n){return Pu(e,{active:!1,dataIndex:t,parsed:void 0,raw:void 0,element:n,index:t,mode:`default`,type:`data`})}function yf(e,t){let n=e.controller.index,r=e.vScale&&e.vScale.axis;if(r){t=t||e._parsed;for(let e of t){let t=e._stacks;t&&t[r]!==void 0&&t[r][n]!==void 0&&(delete t[r][n],t[r]._visualValues!==void 0&&t[r]._visualValues[n]!==void 0&&delete t[r]._visualValues[n])}}}var bf=e=>e===`reset`||e===`none`,xf=(e,t)=>t?e:Object.assign({},e),Sf=(e,t,n)=>e&&!t.hidden&&t._stacked&&{keys:sf(n,!0),values:null},Cf=class{constructor(e,t){this.chart=e,this._ctx=e.ctx,this.index=t,this._cachedDataOpts={},this._cachedMeta=this.getMeta(),this._type=this._cachedMeta.type,this.options=void 0,this._parsing=!1,this._data=void 0,this._objectData=void 0,this._sharedOptions=void 0,this._drawStart=void 0,this._drawCount=void 0,this.enableOptionSharing=!1,this.supportsDecimation=!1,this.$context=void 0,this._syncList=[],this.datasetElementType=new.target.datasetElementType,this.dataElementType=new.target.dataElementType,this.initialize()}initialize(){let e=this._cachedMeta;this.configure(),this.linkScales(),e._stacked=uf(e.vScale,e),this.addElements(),this.options.fill&&!this.chart.isPluginEnabled(`filler`)&&console.warn(`Tried to use the 'fill' option without the 'Filler' plugin enabled. Please import and register the 'Filler' plugin and make sure it is not disabled in the options`)}updateIndex(e){this.index!==e&&yf(this._cachedMeta),this.index=e}linkScales(){let e=this.chart,t=this._cachedMeta,n=this.getDataset(),r=(e,t,n,r)=>e===`x`?t:e===`r`?r:n,i=t.xAxisID=Q(n.xAxisID,gf(e,`x`)),a=t.yAxisID=Q(n.yAxisID,gf(e,`y`)),o=t.rAxisID=Q(n.rAxisID,gf(e,`r`)),s=t.indexAxis,c=t.iAxisID=r(s,i,a,o),l=t.vAxisID=r(s,a,i,o);t.xScale=this.getScaleForId(i),t.yScale=this.getScaleForId(a),t.rScale=this.getScaleForId(o),t.iScale=this.getScaleForId(c),t.vScale=this.getScaleForId(l)}getDataset(){return this.chart.data.datasets[this.index]}getMeta(){return this.chart.getDatasetMeta(this.index)}getScaleForId(e){return this.chart.scales[e]}_getOtherScale(e){let t=this._cachedMeta;return e===t.iScale?t.vScale:t.iScale}reset(){this._update(`reset`)}_destroy(){let e=this._cachedMeta;this._data&&Ol(this._data,this),e._stacked&&yf(e)}_dataCheck(){let e=this.getDataset(),t=e.data||(e.data=[]),n=this._data;if(Z(t)){let e=this._cachedMeta;this._data=lf(t,e)}else if(n!==t){if(n){Ol(n,this);let e=this._cachedMeta;yf(e),e._parsed=[]}t&&Object.isExtensible(t)&&Dl(t,this),this._syncList=[],this._data=t}}addElements(){let e=this._cachedMeta;this._dataCheck(),this.datasetElementType&&(e.dataset=new this.datasetElementType)}buildOrUpdateElements(e){let t=this._cachedMeta,n=this.getDataset(),r=!1;this._dataCheck();let i=t._stacked;t._stacked=uf(t.vScale,t),t.stack!==n.stack&&(r=!0,yf(t),t.stack=n.stack),this._resyncElements(e),(r||i!==t._stacked)&&(hf(this,t._parsed),t._stacked=uf(t.vScale,t))}configure(){let e=this.chart.config,t=e.datasetScopeKeys(this._type),n=e.getOptionScopes(this.getDataset(),t,!0);this.options=e.createResolver(n,this.getContext()),this._parsing=this.options.parsing,this._cachedDataOpts={}}parse(e,t){let{_cachedMeta:n,_data:r}=this,{iScale:i,_stacked:a}=n,o=i.axis,s=e===0&&t===r.length||n._sorted,c=e>0&&n._parsed[e-1],l,u,d;if(this._parsing===!1)n._parsed=r,n._sorted=!0,d=r;else{d=Ec(r[e])?this.parseArrayData(n,r,e,t):Z(r[e])?this.parseObjectData(n,r,e,t):this.parsePrimitiveData(n,r,e,t);let i=()=>u[o]===null||c&&u[o]t||u=0;--d)if(!p()){this.updateRangeFromParsed(c,e,f,s);break}}return c}getAllParsedValues(e){let t=this._cachedMeta._parsed,n=[],r,i,a;for(r=0,i=t.length;r=0&&ethis.getContext(n,r,t),u);return p.$shared&&(p.$shared=s,i[a]=Object.freeze(xf(p,s))),p}_resolveAnimations(e,t,n){let r=this.chart,i=this._cachedDataOpts,a=`animation-${t}`,o=i[a];if(o)return o;let s;if(r.options.animation!==!1){let r=this.chart.config,i=r.datasetAnimationScopeKeys(this._type,t),a=r.getOptionScopes(this.getDataset(),i);s=r.createResolver(a,this.getContext(e,n,t))}let c=new ef(r,s&&s.animations);return s&&s._cacheable&&(i[a]=Object.freeze(c)),c}getSharedOptions(e){if(e.$shared)return this._sharedOptions||(this._sharedOptions=Object.assign({},e))}includeOptions(e,t){return!t||bf(e)||this.chart._animationsDisabled}_getSharedOptions(e,t){let n=this.resolveDataElementOptions(e,t),r=this._sharedOptions,i=this.getSharedOptions(n),a=this.includeOptions(t,i)||i!==r;return this.updateSharedOptions(i,t,n),{sharedOptions:i,includeOptions:a}}updateElement(e,t,n,r){bf(r)?Object.assign(e,n):this._resolveAnimations(t,r).update(e,n)}updateSharedOptions(e,t,n){e&&!bf(t)&&this._resolveAnimations(void 0,t).update(e,n)}_setStyle(e,t,n,r){e.active=r;let i=this.getStyle(t,r);this._resolveAnimations(t,n,r).update(e,{options:!r&&this.getSharedOptions(i)||i})}removeHoverStyle(e,t,n){this._setStyle(e,n,`active`,!1)}setHoverStyle(e,t,n){this._setStyle(e,n,`active`,!0)}_removeDatasetHoverStyle(){let e=this._cachedMeta.dataset;e&&this._setStyle(e,void 0,`active`,!1)}_setDatasetHoverStyle(){let e=this._cachedMeta.dataset;e&&this._setStyle(e,void 0,`active`,!0)}_resyncElements(e){let t=this._data,n=this._cachedMeta.data;for(let[e,t,n]of this._syncList)this[e](t,n);this._syncList=[];let r=n.length,i=t.length,a=Math.min(i,r);a&&this.parse(0,a),i>r?this._insertElements(r,i-r,e):i{for(e.length+=t,o=e.length-1;o>=a;o--)e[o]=e[o-t]};for(s(i),o=e;oe-t))}return e._cache.$bar}function Tf(e){let t=e.iScale,n=wf(t,e.type),r=t._length,i,a,o,s,c=()=>{o!==32767&&o!==-32768&&(Gc(s)&&(r=Math.min(r,Math.abs(o-s)||r)),s=o)};for(i=0,a=n.length;i0?i[e-1]:null,s=eMath.abs(s)&&(c=s,l=o),t[n.axis]=l,t._custom={barStart:c,barEnd:l,start:i,end:a,min:o,max:s}}function kf(e,t,n,r){return Ec(e)?Of(e,t,n,r):t[n.axis]=n.parse(e,r),t}function Af(e,t,n,r){let i=e.iScale,a=e.vScale,o=i.getLabels(),s=i===a,c=[],l,u,d,f;for(l=n,u=n+r;l=n?1:-1):rl(e)}function Nf(e){let t,n,r,i,a;return e.horizontal?(t=e.base>e.x,n=`left`,r=`right`):(t=e.basee.controller.options.grouped),i=n.options.stacked,a=[],o=this._cachedMeta.controller.getParsed(t),s=o&&o[n.axis],c=e=>{let t=e._parsed.find(e=>e[n.axis]===s),r=t&&t[e.vScale.axis];if(X(r)||isNaN(r))return!0};for(let n of r)if(!(t!==void 0&&c(n))&&((i===!1||a.indexOf(n.stack)===-1||i===void 0&&n.stack===void 0)&&a.push(n.stack),n.index===e))break;return a.length||a.push(void 0),a}_getStackCount(e){return this._getStacks(void 0,e).length}_getAxisCount(){return this._getAxis().length}getFirstScaleIdForIndexAxis(){let e=this.chart.scales,t=this.chart.options.indexAxis;return Object.keys(e).filter(n=>e[n].axis===t).shift()}_getAxis(){let e={},t=this.getFirstScaleIdForIndexAxis();for(let n of this.chart.data.datasets)e[Q(this.chart.options.indexAxis===`x`?n.xAxisID:n.yAxisID,t)]=!0;return Object.keys(e)}_getStackIndex(e,t,n){let r=this._getStacks(e,n),i=t===void 0?-1:r.indexOf(t);return i===-1?r.length-1:i}_getRuler(){let e=this.options,t=this._cachedMeta,n=t.iScale,r=[],i,a;for(i=0,a=t.data.length;i=0;--n)t=Math.max(t,e[n].size(this.resolveDataElementOptions(n))/2);return t>0&&t}getLabelAndValue(e){let t=this._cachedMeta,n=this.chart.data.labels||[],{xScale:r,yScale:i}=t,a=this.getParsed(e),o=r.getLabelForValue(a.x),s=i.getLabelForValue(a.y),c=a._custom;return{label:n[e]||``,value:`(`+o+`, `+s+(c?`, `+c:``)+`)`}}update(e){let t=this._cachedMeta.data;this.updateElements(t,0,t.length,e)}updateElements(e,t,n,r){let i=r===`reset`,{iScale:a,vScale:o}=this._cachedMeta,{sharedOptions:s,includeOptions:c}=this._getSharedOptions(t,r),l=a.axis,u=o.axis;for(let d=t;dvl(e,s,c,!0)?1:Math.max(t,t*n,r,r*n),m=(e,t,r)=>vl(e,s,c,!0)?-1:Math.min(t,t*n,r,r*n),h=p(0,l,d),g=p($c,u,f),_=m($,l,d),v=m($+$c,u,f);r=(h-_)/2,i=(g-v)/2,a=-(h+_)/2,o=-(g+v)/2}return{ratioX:r,ratioY:i,offsetX:a,offsetY:o}}var Hf=class extends Cf{constructor(e,t){super(e,t),this.enableOptionSharing=!0,this.innerRadius=void 0,this.outerRadius=void 0,this.offsetX=void 0,this.offsetY=void 0}linkScales(){}parse(e,t){let n=this.getDataset().data,r=this._cachedMeta;if(this._parsing===!1)r._parsed=n;else{let i=e=>+n[e];if(Z(n[e])){let{key:e=`value`}=this._parsing;i=t=>+Uc(n[t],e)}let a,o;for(a=e,o=e+t;a0&&!isNaN(e)?Math.abs(e)/t*Yc:0}getLabelAndValue(e){let t=this._cachedMeta,n=this.chart,r=n.data.labels||[],i=Xl(t._parsed[e],n.options.locale);return{label:r[e]||``,value:i}}getMaxBorderWidth(e){let t=0,n=this.chart,r,i,a,o,s;if(!e){for(r=0,i=n.data.datasets.length;re!==`spacing`&&e!==`spacingMode`,_indexable:e=>e!==`spacing`&&e!==`spacingMode`&&!e.startsWith(`borderDash`)&&!e.startsWith(`hoverBorderDash`)}),V(Hf,`overrides`,{aspectRatio:1,plugins:{legend:{labels:{generateLabels(e){let t=e.data,{labels:{pointStyle:n,textAlign:r,color:i,useBorderRadius:a,borderRadius:o}}=e.legend.options;return t.labels.length&&t.datasets.length?t.labels.map((t,s)=>{let c=e.getDatasetMeta(0).controller.getStyle(s);return{text:t,fillStyle:c.backgroundColor,fontColor:i,hidden:!e.getDataVisibility(s),lineDash:c.borderDash,lineDashOffset:c.borderDashOffset,lineJoin:c.borderJoinStyle,lineWidth:c.borderWidth,strokeStyle:c.borderColor,textAlign:r,pointStyle:n,borderRadius:a&&(o||c.borderRadius),index:s}}):[]}},onClick(e,t,n){n.chart.toggleDataVisibility(t.index),n.chart.update()}}}});var Uf=class extends Cf{initialize(){this.enableOptionSharing=!0,this.supportsDecimation=!0,super.initialize()}update(e){let t=this._cachedMeta,{dataset:n,data:r=[],_dataset:i}=t,a=this.chart._animationsDisabled,{start:o,count:s}=Fl(t,r,a);this._drawStart=o,this._drawCount=s,Il(t)&&(o=0,s=r.length),n._chart=this.chart,n._datasetIndex=this.index,n._decimated=!!i._decimated,n.points=r;let c=this.resolveDatasetElementOptions(e);this.options.showLine||(c.borderWidth=0),c.segment=this.options.segment,this.updateElement(n,void 0,{animated:!a,options:c},e),this.updateElements(r,o,s,e)}updateElements(e,t,n,r){let i=r===`reset`,{iScale:a,vScale:o,_stacked:s,_dataset:c}=this._cachedMeta,{sharedOptions:l,includeOptions:u}=this._getSharedOptions(t,r),d=a.axis,f=o.axis,{spanGaps:p,segment:m}=this.options,h=cl(p)?p:1/0,g=this.chart._animationsDisabled||i||r===`none`,_=t+n,v=e.length,y=t>0&&this.getParsed(t-1);for(let n=0;n=_){v.skip=!0;continue}let b=this.getParsed(n),x=X(b[f]),S=v[d]=a.getPixelForValue(b[d],n),C=v[f]=i||x?o.getBasePixel():o.getPixelForValue(s?this.applyStack(o,b,s):b[f],n);v.skip=isNaN(S)||isNaN(C)||x,v.stop=n>0&&Math.abs(b[d]-y[d])>h,m&&(v.parsed=b,v.raw=c.data[n]),u&&(v.options=l||this.resolveDataElementOptions(n,p.active?`active`:r)),g||this.updateElement(p,n,v,r),y=b}}getMaxOverflow(){let e=this._cachedMeta,t=e.dataset,n=t.options&&t.options.borderWidth||0,r=e.data||[];if(!r.length)return n;let i=r[0].size(this.resolveDataElementOptions(0)),a=r[r.length-1].size(this.resolveDataElementOptions(r.length-1));return Math.max(n,i,a)/2}draw(){let e=this._cachedMeta;e.dataset.updateControlPoints(this.chart.chartArea,e.iScale.axis),super.draw()}};V(Uf,`id`,`line`),V(Uf,`defaults`,{datasetElementType:`line`,dataElementType:`point`,showLine:!0,spanGaps:!1}),V(Uf,`overrides`,{scales:{_index_:{type:`category`},_value_:{type:`linear`}}});var Wf=class extends Cf{constructor(e,t){super(e,t),this.innerRadius=void 0,this.outerRadius=void 0}getLabelAndValue(e){let t=this._cachedMeta,n=this.chart,r=n.data.labels||[],i=Xl(t._parsed[e].r,n.options.locale);return{label:r[e]||``,value:i}}parseObjectData(e,t,n,r){return ed.bind(this)(e,t,n,r)}update(e){let t=this._cachedMeta.data;this._updateRadius(),this.updateElements(t,0,t.length,e)}getMinMax(){let e=this._cachedMeta,t={min:1/0,max:-1/0};return e.data.forEach((e,n)=>{let r=this.getParsed(n).r;!isNaN(r)&&this.chart.getDataVisibility(n)&&(rt.max&&(t.max=r))}),t}_updateRadius(){let e=this.chart,t=e.chartArea,n=e.options,r=Math.min(t.right-t.left,t.bottom-t.top),i=Math.max(r/2,0),a=(i-Math.max(n.cutoutPercentage?i/100*n.cutoutPercentage:1,0))/e.getVisibleDatasetCount();this.outerRadius=i-a*this.index,this.innerRadius=this.outerRadius-a}updateElements(e,t,n,r){let i=r===`reset`,a=this.chart,o=a.options.animation,s=this._cachedMeta.rScale,c=s.xCenter,l=s.yCenter,u=s.getIndexAngle(0)-.5*$,d=u,f,p=360/this.countVisibleElements();for(f=0;f{!isNaN(this.getParsed(n).r)&&this.chart.getDataVisibility(n)&&t++}),t}_computeAngle(e,t,n){return this.chart.getDataVisibility(e)?dl(this.resolveDataElementOptions(e,t).angle||n):0}};V(Wf,`id`,`polarArea`),V(Wf,`defaults`,{dataElementType:`arc`,animation:{animateRotate:!0,animateScale:!0},animations:{numbers:{type:`number`,properties:[`x`,`y`,`startAngle`,`endAngle`,`innerRadius`,`outerRadius`]}},indexAxis:`r`,startAngle:0,spacing:0,spacingMode:`proportional`}),V(Wf,`overrides`,{aspectRatio:1,plugins:{legend:{labels:{generateLabels(e){let t=e.data;if(t.labels.length&&t.datasets.length){let{labels:{pointStyle:n,color:r}}=e.legend.options;return t.labels.map((t,i)=>{let a=e.getDatasetMeta(0).controller.getStyle(i);return{text:t,fillStyle:a.backgroundColor,strokeStyle:a.borderColor,fontColor:r,lineWidth:a.borderWidth,pointStyle:n,hidden:!e.getDataVisibility(i),index:i}})}return[]}},onClick(e,t,n){n.chart.toggleDataVisibility(t.index),n.chart.update()}}},scales:{r:{type:`radialLinear`,angleLines:{display:!1},beginAtZero:!0,grid:{circular:!0},pointLabels:{display:!1},startAngle:0}}});var Gf=class extends Hf{};V(Gf,`id`,`pie`),V(Gf,`defaults`,{cutout:0,rotation:0,circumference:360,radius:`100%`});var Kf=class extends Cf{getLabelAndValue(e){let t=this._cachedMeta.vScale,n=this.getParsed(e);return{label:t.getLabels()[e],value:``+t.getLabelForValue(n[t.axis])}}parseObjectData(e,t,n,r){return ed.bind(this)(e,t,n,r)}update(e){let t=this._cachedMeta,n=t.dataset,r=t.data||[],i=t.iScale.getLabels();if(n.points=r,e!==`resize`){let t=this.resolveDatasetElementOptions(e);this.options.showLine||(t.borderWidth=0);let a={_loop:!0,_fullLoop:i.length===r.length,options:t};this.updateElement(n,void 0,a,e)}this.updateElements(r,0,r.length,e)}updateElements(e,t,n,r){let i=this._cachedMeta.rScale,a=r===`reset`;for(let o=t;o0&&this.getParsed(t-1);for(let l=t;l0&&Math.abs(n[f]-v[f])>g,h&&(m.parsed=n,m.raw=c.data[l]),d&&(m.options=u||this.resolveDataElementOptions(l,t.active?`active`:r)),_||this.updateElement(t,l,m,r),v=n}this.updateSharedOptions(u,r,l)}getMaxOverflow(){let e=this._cachedMeta,t=e.data||[];if(!this.options.showLine){let e=0;for(let n=t.length-1;n>=0;--n)e=Math.max(e,t[n].size(this.resolveDataElementOptions(n))/2);return e>0&&e}let n=e.dataset,r=n.options&&n.options.borderWidth||0;if(!t.length)return r;let i=t[0].size(this.resolveDataElementOptions(0)),a=t[t.length-1].size(this.resolveDataElementOptions(t.length-1));return Math.max(r,i,a)/2}};V(qf,`id`,`scatter`),V(qf,`defaults`,{datasetElementType:!1,dataElementType:`point`,showLine:!1,fill:!1}),V(qf,`overrides`,{interaction:{mode:`point`},scales:{x:{type:`linear`},y:{type:`linear`}}});function Jf(){throw Error(`This method is not implemented: Check that a complete date adapter is provided.`)}var Yf={_date:class e{static override(t){Object.assign(e.prototype,t)}constructor(e){V(this,`options`,void 0),this.options=e||{}}init(){}formats(){return Jf()}parse(){return Jf()}format(){return Jf()}add(){return Jf()}diff(){return Jf()}startOf(){return Jf()}endOf(){return Jf()}}};function Xf(e,t,n,r){let{controller:i,data:a,_sorted:o}=e,s=i._cachedMeta.iScale,c=e.dataset&&e.dataset.options?e.dataset.options.spanGaps:null;if(s&&t===s.axis&&t!==`r`&&o&&a.length){let o=s._reversePixels?wl:Cl;if(!r){let r=o(a,t,n);if(c){let{vScale:t}=i._cachedMeta,{_parsed:n}=e,a=n.slice(0,r.lo+1).reverse().findIndex(e=>!X(e[t.axis]));r.lo-=Math.max(0,a);let o=n.slice(r.hi).findIndex(e=>!X(e[t.axis]));r.hi+=Math.max(0,o)}return r}if(i._sharedOptions){let e=a[0],r=typeof e.getRange==`function`&&e.getRange(t);if(r){let e=o(a,t,n-r),i=o(a,t,n+r);return{lo:e.lo,hi:i.hi}}}}return{lo:0,hi:a.length-1}}function Zf(e,t,n,r,i){let a=e.getSortedVisibleDatasetMetas(),o=n[t];for(let e=0,n=a.length;e{e[o]&&e[o](t[n],i)&&(a.push({element:e,datasetIndex:r,index:c}),s=s||e.inRange(t.x,t.y,i))}),r&&!s?[]:a}var ip={evaluateInteractionItems:Zf,modes:{index(e,t,n,r){let i=bd(t,e),a=n.axis||`x`,o=n.includeInvisible||!1,s=n.intersect?$f(e,i,a,r,o):np(e,i,a,!1,r,o),c=[];return s.length?(e.getSortedVisibleDatasetMetas().forEach(e=>{let t=s[0].index,n=e.data[t];n&&!n.skip&&c.push({element:n,datasetIndex:e.index,index:t})}),c):[]},dataset(e,t,n,r){let i=bd(t,e),a=n.axis||`xy`,o=n.includeInvisible||!1,s=n.intersect?$f(e,i,a,r,o):np(e,i,a,!1,r,o);if(s.length>0){let t=s[0].datasetIndex,n=e.getDatasetMeta(t).data;s=[];for(let e=0;ee.pos===t)}function sp(e,t){return e.filter(e=>ap.indexOf(e.pos)===-1&&e.box.axis===t)}function cp(e,t){return e.sort((e,n)=>{let r=t?n:e,i=t?e:n;return r.weight===i.weight?r.index-i.index:r.weight-i.weight})}function lp(e){let t=[],n,r,i,a,o,s;for(n=0,r=(e||[]).length;ne.box.fullSize),!0),r=cp(op(t,`left`),!0),i=cp(op(t,`right`)),a=cp(op(t,`top`),!0),o=cp(op(t,`bottom`)),s=sp(t,`x`),c=sp(t,`y`);return{fullSize:n,leftAndTop:r.concat(a),rightAndBottom:i.concat(c).concat(o).concat(s),chartArea:op(t,`chartArea`),vertical:r.concat(i).concat(c),horizontal:a.concat(o).concat(s)}}function pp(e,t,n,r){return Math.max(e[n],t[n])+Math.max(e[r],t[r])}function mp(e,t){e.top=Math.max(e.top,t.top),e.left=Math.max(e.left,t.left),e.bottom=Math.max(e.bottom,t.bottom),e.right=Math.max(e.right,t.right)}function hp(e,t,n,r){let{pos:i,box:a}=n,o=e.maxPadding;if(!Z(i)){n.size&&(e[i]-=n.size);let t=r[n.stack]||{size:0,count:1};t.size=Math.max(t.size,n.horizontal?a.height:a.width),n.size=t.size/t.count,e[i]+=n.size}a.getPadding&&mp(o,a.getPadding());let s=Math.max(0,t.outerWidth-pp(o,e,`left`,`right`)),c=Math.max(0,t.outerHeight-pp(o,e,`top`,`bottom`)),l=s!==e.w,u=c!==e.h;return e.w=s,e.h=c,n.horizontal?{same:l,other:u}:{same:u,other:l}}function gp(e){let t=e.maxPadding;function n(n){let r=Math.max(t[n]-e[n],0);return e[n]+=r,r}e.y+=n(`top`),e.x+=n(`left`),n(`right`),n(`bottom`)}function _p(e,t){let n=t.maxPadding;function r(e){let r={left:0,top:0,right:0,bottom:0};return e.forEach(e=>{r[e]=Math.max(t[e],n[e])}),r}return r(e?[`left`,`right`]:[`top`,`bottom`])}function vp(e,t,n,r){let i=[],a,o,s,c,l,u;for(a=0,o=e.length,l=0;a{typeof e.beforeLayout==`function`&&e.beforeLayout()});let u=c.reduce((e,t)=>t.box.options&&t.box.options.display===!1?e:e+1,0)||1,d=Object.freeze({outerWidth:t,outerHeight:n,padding:i,availableWidth:a,availableHeight:o,vBoxMaxWidth:a/2/u,hBoxMaxHeight:o/2}),f=Object.assign({},i);mp(f,Au(r));let p=Object.assign({maxPadding:f,w:a,h:o,x:i.left,y:i.top},i),m=dp(c.concat(l),d);vp(s.fullSize,p,d,m),vp(c,p,d,m),vp(l,p,d,m)&&vp(c,p,d,m),gp(p),bp(s.leftAndTop,p,d,m),p.x+=p.w,p.y+=p.h,bp(s.rightAndBottom,p,d,m),e.chartArea={left:p.left,top:p.top,right:p.left+p.w,bottom:p.top+p.h,height:p.h,width:p.w},Mc(s.chartArea,t=>{let n=t.box;Object.assign(n,e.chartArea),n.update(p.w,p.h,{left:0,top:0,right:0,bottom:0})})}},Sp=class{acquireContext(e,t){}releaseContext(e){return!1}addEventListener(e,t,n){}removeEventListener(e,t,n){}getDevicePixelRatio(){return 1}getMaximumSize(e,t,n,r){return t=Math.max(0,t||e.width),n=n||e.height,{width:t,height:Math.max(0,r?Math.floor(t/r):n)}}isAttached(e){return!0}updateConfig(e){}},Cp=class extends Sp{acquireContext(e){return e&&e.getContext&&e.getContext(`2d`)||null}updateConfig(e){e.options.animation=!1}},wp=`$chartjs`,Tp={touchstart:`mousedown`,touchmove:`mousemove`,touchend:`mouseup`,pointerenter:`mouseenter`,pointerdown:`mousedown`,pointermove:`mousemove`,pointerup:`mouseup`,pointerleave:`mouseout`,pointerout:`mouseout`},Ep=e=>e===null||e===``;function Dp(e,t){let n=e.style,r=e.getAttribute(`height`),i=e.getAttribute(`width`);if(e[wp]={initial:{height:r,width:i,style:{display:n.display,height:n.height,width:n.width}}},n.display=n.display||`block`,n.boxSizing=n.boxSizing||`border-box`,Ep(i)){let t=Ed(e,`width`);t!==void 0&&(e.width=t)}if(Ep(r)){if(e.style.height===``)e.height=e.width/(t||2);else{let t=Ed(e,`height`);t!==void 0&&(e.height=t)}}return e}var Op=Td?{passive:!0}:!1;function kp(e,t,n){e&&e.addEventListener(t,n,Op)}function Ap(e,t,n){e&&e.canvas&&e.canvas.removeEventListener(t,n,Op)}function jp(e,t){let n=Tp[e.type]||e.type,{x:r,y:i}=bd(e,t);return{type:n,chart:t,native:e,x:r===void 0?null:r,y:i===void 0?null:i}}function Mp(e,t){for(let n of e)if(n===t||n.contains(t))return!0}function Np(e,t,n){let r=e.canvas,i=new MutationObserver(e=>{let t=!1;for(let n of e)t=t||Mp(n.addedNodes,r),t=t&&!Mp(n.removedNodes,r);t&&n()});return i.observe(document,{childList:!0,subtree:!0}),i}function Pp(e,t,n){let r=e.canvas,i=new MutationObserver(e=>{let t=!1;for(let n of e)t=t||Mp(n.removedNodes,r),t=t&&!Mp(n.addedNodes,r);t&&n()});return i.observe(document,{childList:!0,subtree:!0}),i}var Fp=new Map,Ip=0;function Lp(){let e=window.devicePixelRatio;e!==Ip&&(Ip=e,Fp.forEach((t,n)=>{n.currentDevicePixelRatio!==e&&t()}))}function Rp(e,t){Fp.size||window.addEventListener(`resize`,Lp),Fp.set(e,t)}function zp(e){Fp.delete(e),Fp.size||window.removeEventListener(`resize`,Lp)}function Bp(e,t,n){let r=e.canvas,i=r&&fd(r);if(!i)return;let a=jl((e,t)=>{let r=i.clientWidth;n(e,t),r{let t=e[0],n=t.contentRect.width,r=t.contentRect.height;(n!==0||r!==0)&&a(n,r)});return o.observe(i),Rp(e,a),o}function Vp(e,t,n){n&&n.disconnect(),t===`resize`&&zp(e)}function Hp(e,t,n){let r=e.canvas,i=jl(t=>{e.ctx!==null&&n(jp(t,e))},e);return kp(r,t,i),i}var Up=class extends Sp{acquireContext(e,t){let n=e&&e.getContext&&e.getContext(`2d`);return n&&n.canvas===e?(Dp(e,t),n):null}releaseContext(e){let t=e.canvas;if(!t[wp])return!1;let n=t[wp].initial;[`height`,`width`].forEach(e=>{let r=n[e];X(r)?t.removeAttribute(e):t.setAttribute(e,r)});let r=n.style||{};return Object.keys(r).forEach(e=>{t.style[e]=r[e]}),t.width=t.width,delete t[wp],!0}addEventListener(e,t,n){this.removeEventListener(e,t);let r=e.$proxies||(e.$proxies={});r[t]=({attach:Np,detach:Pp,resize:Bp}[t]||Hp)(e,t,n)}removeEventListener(e,t){let n=e.$proxies||(e.$proxies={}),r=n[t];r&&(({attach:Vp,detach:Vp,resize:Vp}[t]||Ap)(e,t,r),n[t]=void 0)}getDevicePixelRatio(){return window.devicePixelRatio}getMaximumSize(e,t,n,r){return Cd(e,t,n,r)}isAttached(e){let t=e&&fd(e);return!!(t&&t.isConnected)}};function Wp(e){return!dd()||typeof OffscreenCanvas<`u`&&e instanceof OffscreenCanvas?Cp:Up}var Gp=class{constructor(){V(this,`x`,void 0),V(this,`y`,void 0),V(this,`active`,!1),V(this,`options`,void 0),V(this,`$animations`,void 0)}tooltipPosition(e){let{x:t,y:n}=this.getProps([`x`,`y`],e);return{x:t,y:n}}hasValue(){return cl(this.x)&&cl(this.y)}getProps(e,t){let n=this.$animations;if(!t||!n)return this;let r={};return e.forEach(e=>{r[e]=n[e]&&n[e].active()?n[e]._to:this[e]}),r}};V(Gp,`defaults`,{}),V(Gp,`defaultRoutes`,void 0);function Kp(e,t){let n=e.options.ticks,r=qp(e),i=Math.min(n.maxTicksLimit||r,r),a=n.major.enabled?Yp(t):[],o=a.length,s=a[0],c=a[o-1],l=[];if(o>i)return Xp(t,l,a,o/i),l;let u=Jp(a,t,i);if(o>0){let e,n,r=o>1?Math.round((c-s)/(o-1)):null;for(Zp(t,l,u,X(r)?0:s-r,s),e=0,n=o-1;ei)return t}return Math.max(i,1)}function Yp(e){let t=[],n,r;for(n=0,r=e.length;ne===`left`?`right`:e===`right`?`left`:e,em=(e,t,n)=>t===`top`||t===`left`?e[t]+n:e[t]-n,tm=(e,t)=>Math.min(t||e,e);function nm(e,t){let n=[],r=e.length/t,i=e.length,a=0;for(;ao+s)))return c}function im(e,t){Mc(e,e=>{let n=e.gc,r=n.length/2,i;if(r>t){for(i=0;in?n:t,n=r&&t>n?t:n,{min:Oc(t,Oc(n,t)),max:Oc(n,Oc(t,n))}}getPadding(){return{left:this.paddingLeft||0,top:this.paddingTop||0,right:this.paddingRight||0,bottom:this.paddingBottom||0}}getTicks(){return this.ticks}getLabels(){let e=this.chart.data;return this.options.labels||(this.isHorizontal()?e.xLabels:e.yLabels)||e.labels||[]}getLabelItems(e=this.chart.chartArea){return this._labelItems||(this._labelItems=this._computeLabelItems(e))}beforeLayout(){this._cache={},this._dataLimitsCached=!1}beforeUpdate(){jc(this.options.beforeUpdate,[this])}update(e,t,n){let{beginAtZero:r,grace:i,ticks:a}=this.options,o=a.sampleSize;this.beforeUpdate(),this.maxWidth=e,this.maxHeight=t,this._margins=n=Object.assign({left:0,right:0,top:0,bottom:0},n),this.ticks=null,this._labelSizes=null,this._gridLineItems=null,this._labelItems=null,this.beforeSetDimensions(),this.setDimensions(),this.afterSetDimensions(),this._maxLength=this.isHorizontal()?this.width+n.left+n.right:this.height+n.top+n.bottom,this._dataLimitsCached||(this.beforeDataLimits(),this.determineDataLimits(),this.afterDataLimits(),this._range=Nu(this,i,r),this._dataLimitsCached=!0),this.beforeBuildTicks(),this.ticks=this.buildTicks()||[],this.afterBuildTicks();let s=o=i||n<=1||!this.isHorizontal()){this.labelRotation=r;return}let l=this._getLabelSizes(),u=l.widest.width,d=l.highest.height,f=yl(this.chart.width-u,0,this.maxWidth);o=e.offset?this.maxWidth/n:f/(n-1),u+6>o&&(o=f/(n-(e.offset?.5:1)),s=this.maxHeight-am(e.grid)-t.padding-om(e.title,this.chart.options.font),c=Math.sqrt(u*u+d*d),a=fl(Math.min(Math.asin(yl((l.highest.height+6)/o,-1,1)),Math.asin(yl(s/c,-1,1))-Math.asin(yl(d/c,-1,1)))),a=Math.max(r,Math.min(i,a))),this.labelRotation=a}afterCalculateLabelRotation(){jc(this.options.afterCalculateLabelRotation,[this])}afterAutoSkip(){}beforeFit(){jc(this.options.beforeFit,[this])}fit(){let e={width:0,height:0},{chart:t,options:{ticks:n,title:r,grid:i}}=this,a=this._isVisible(),o=this.isHorizontal();if(a){let a=om(r,t.options.font);if(o?(e.width=this.maxWidth,e.height=am(i)+a):(e.height=this.maxHeight,e.width=am(i)+a),n.display&&this.ticks.length){let{first:t,last:r,widest:i,highest:a}=this._getLabelSizes(),s=n.padding*2,c=dl(this.labelRotation),l=Math.cos(c),u=Math.sin(c);if(o){let t=n.mirror?0:u*i.width+l*a.height;e.height=Math.min(this.maxHeight,e.height+t+s)}else{let t=n.mirror?0:l*i.width+u*a.height;e.width=Math.min(this.maxWidth,e.width+t+s)}this._calculatePadding(t,r,u,l)}}this._handleMargins(),o?(this.width=this._length=t.width-this._margins.left-this._margins.right,this.height=e.height):(this.width=e.width,this.height=this._length=t.height-this._margins.top-this._margins.bottom)}_calculatePadding(e,t,n,r){let{ticks:{align:i,padding:a},position:o}=this.options,s=this.labelRotation!==0,c=o!==`top`&&this.axis===`x`;if(this.isHorizontal()){let o=this.getPixelForTick(0)-this.left,l=this.right-this.getPixelForTick(this.ticks.length-1),u=0,d=0;s?c?(u=r*e.width,d=n*t.height):(u=n*e.height,d=r*t.width):i===`start`?d=t.width:i===`end`?u=e.width:i!==`inner`&&(u=e.width/2,d=t.width/2),this.paddingLeft=Math.max((u-o+a)*this.width/(this.width-o),0),this.paddingRight=Math.max((d-l+a)*this.width/(this.width-l),0)}else{let n=t.height/2,r=e.height/2;i===`start`?(n=0,r=e.height):i===`end`&&(n=t.height,r=0),this.paddingTop=n+a,this.paddingBottom=r+a}}_handleMargins(){this._margins&&(this._margins.left=Math.max(this.paddingLeft,this._margins.left),this._margins.top=Math.max(this.paddingTop,this._margins.top),this._margins.right=Math.max(this.paddingRight,this._margins.right),this._margins.bottom=Math.max(this.paddingBottom,this._margins.bottom))}afterFit(){jc(this.options.afterFit,[this])}isHorizontal(){let{axis:e,position:t}=this.options;return t===`top`||t===`bottom`||e===`x`}isFullSize(){return this.options.fullSize}_convertTicksToLabels(e){this.beforeTickToLabelConversion(),this.generateTickLabels(e);let t,n;for(t=0,n=e.length;t({width:a[e]||0,height:o[e]||0});return{first:C(0),last:C(t-1),widest:C(x),highest:C(S),widths:a,heights:o}}getLabelForValue(e){return e}getPixelForValue(e,t){return NaN}getValueForPixel(e){}getPixelForTick(e){let t=this.ticks;return e<0||e>t.length-1?null:this.getPixelForValue(t[e].value)}getPixelForDecimal(e){this._reversePixels&&(e=1-e);let t=this._startPixel+e*this._length;return bl(this._alignToPixels?lu(this.chart,t,0):t)}getDecimalForPixel(e){let t=(e-this._startPixel)/this._length;return this._reversePixels?1-t:t}getBasePixel(){return this.getPixelForValue(this.getBaseValue())}getBaseValue(){let{min:e,max:t}=this;return e<0&&t<0?t:e>0&&t>0?e:0}getContext(e){let t=this.ticks||[];if(e>=0&&eo*r?o/n:s/r:s*r0:!!e}_computeGridLineItems(e){let t=this.axis,n=this.chart,r=this.options,{grid:i,position:a,border:o}=r,s=i.offset,c=this.isHorizontal(),l=this.ticks.length+ +!!s,u=am(i),d=[],f=o.setContext(this.getContext()),p=f.display?f.width:0,m=p/2,h=function(e){return lu(n,e,p)},g,_,v,y,b,x,S,C,w,T,E,D;if(a===`top`)g=h(this.bottom),x=this.bottom-u,C=g-m,T=h(e.top)+m,D=e.bottom;else if(a===`bottom`)g=h(this.top),T=e.top,D=h(e.bottom)-m,x=g+m,C=this.top+u;else if(a===`left`)g=h(this.right),b=this.right-u,S=g-m,w=h(e.left)+m,E=e.right;else if(a===`right`)g=h(this.left),w=e.left,E=h(e.right)-m,b=g+m,S=this.left+u;else if(t===`x`){if(a===`center`)g=h((e.top+e.bottom)/2+.5);else if(Z(a)){let e=Object.keys(a)[0],t=a[e];g=h(this.chart.scales[e].getPixelForValue(t))}T=e.top,D=e.bottom,x=g+m,C=x+u}else if(t===`y`){if(a===`center`)g=h((e.left+e.right)/2);else if(Z(a)){let e=Object.keys(a)[0],t=a[e];g=h(this.chart.scales[e].getPixelForValue(t))}b=g-m,S=b-u,w=e.left,E=e.right}let O=Q(r.ticks.maxTicksLimit,l),k=Math.max(1,Math.ceil(l/O));for(_=0;_0&&(a-=r/2)}f={left:a,top:i,width:r+t.width,height:n+t.height,color:e.backdropColor}}h.push({label:y,font:w,textOffset:D,options:{rotation:m,color:n,strokeColor:s,strokeWidth:l,textAlign:d,textBaseline:O,translation:[b,x],backdrop:f}})}return h}_getXAxisLabelAlignment(){let{position:e,ticks:t}=this.options;if(-dl(this.labelRotation))return e===`top`?`left`:`right`;let n=`center`;return t.align===`start`?n=`left`:t.align===`end`?n=`right`:t.align===`inner`&&(n=`inner`),n}_getYAxisLabelAlignment(e){let{position:t,ticks:{crossAlign:n,mirror:r,padding:i}}=this.options,a=this._getLabelSizes(),o=e+i,s=a.widest.width,c,l;return t===`left`?r?(l=this.right+i,n===`near`?c=`left`:n===`center`?(c=`center`,l+=s/2):(c=`right`,l+=s)):(l=this.right-o,n===`near`?c=`right`:n===`center`?(c=`center`,l-=s/2):(c=`left`,l=this.left)):t===`right`?r?(l=this.left+i,n===`near`?c=`right`:n===`center`?(c=`center`,l-=s/2):(c=`left`,l-=s)):(l=this.left+o,n===`near`?c=`left`:n===`center`?(c=`center`,l+=s/2):(c=`right`,l=this.right)):c=`right`,{textAlign:c,x:l}}_computeLabelArea(){if(this.options.ticks.mirror)return;let e=this.chart,t=this.options.position;if(t===`left`||t===`right`)return{top:0,left:this.left,bottom:e.height,right:this.right};if(t===`top`||t===`bottom`)return{top:this.top,left:0,bottom:this.bottom,right:e.width}}drawBackground(){let{ctx:e,options:{backgroundColor:t},left:n,top:r,width:i,height:a}=this;t&&(e.save(),e.fillStyle=t,e.fillRect(n,r,i,a),e.restore())}getLineWidthForValue(e){let t=this.options.grid;if(!this._isVisible()||!t.display)return 0;let n=this.ticks.findIndex(t=>t.value===e);return n>=0?t.setContext(this.getContext(n)).lineWidth:0}drawGrid(e){let t=this.options.grid,n=this.ctx,r=this._gridLineItems||(this._gridLineItems=this._computeGridLineItems(e)),i,a,o=(e,t,r)=>{r.width&&r.color&&(n.save(),n.lineWidth=r.width,n.strokeStyle=r.color,n.setLineDash(r.borderDash||[]),n.lineDashOffset=r.borderDashOffset,n.beginPath(),n.moveTo(e.x,e.y),n.lineTo(t.x,t.y),n.stroke(),n.restore())};if(t.display)for(i=0,a=r.length;i{this.draw(e)}}]:[{z:r,draw:e=>{this.drawBackground(),this.drawGrid(e),this.drawTitle()}},{z:i,draw:()=>{this.drawBorder()}},{z:n,draw:e=>{this.drawLabels(e)}}]}getMatchingVisibleMetas(e){let t=this.chart.getSortedVisibleDatasetMetas(),n=this.axis+`AxisID`,r=[],i,a;for(i=0,a=t.length;i{let r=n.split(`.`),i=r.pop(),a=[e].concat(r).join(`.`),o=t[n].split(`.`),s=o.pop(),c=o.join(`.`);au.route(a,i,c,s)})}function hm(e){return`id`in e&&`defaults`in e}var gm=new class{constructor(){this.controllers=new fm(Cf,`datasets`,!0),this.elements=new fm(Gp,`elements`),this.plugins=new fm(Object,`plugins`),this.scales=new fm(dm,`scales`),this._typedRegistries=[this.controllers,this.scales,this.elements]}add(...e){this._each(`register`,e)}remove(...e){this._each(`unregister`,e)}addControllers(...e){this._each(`register`,e,this.controllers)}addElements(...e){this._each(`register`,e,this.elements)}addPlugins(...e){this._each(`register`,e,this.plugins)}addScales(...e){this._each(`register`,e,this.scales)}getController(e){return this._get(e,this.controllers,`controller`)}getElement(e){return this._get(e,this.elements,`element`)}getPlugin(e){return this._get(e,this.plugins,`plugin`)}getScale(e){return this._get(e,this.scales,`scale`)}removeControllers(...e){this._each(`unregister`,e,this.controllers)}removeElements(...e){this._each(`unregister`,e,this.elements)}removePlugins(...e){this._each(`unregister`,e,this.plugins)}removeScales(...e){this._each(`unregister`,e,this.scales)}_each(e,t,n){[...t].forEach(t=>{let r=n||this._getRegistryForType(t);n||r.isForType(t)||r===this.plugins&&t.id?this._exec(e,r,t):Mc(t,t=>{let r=n||this._getRegistryForType(t);this._exec(e,r,t)})})}_exec(e,t,n){let r=Wc(e);jc(n[`before`+r],[],n),t[e](n),jc(n[`after`+r],[],n)}_getRegistryForType(e){for(let t=0;te.filter(e=>!t.some(t=>e.plugin.id===t.plugin.id));this._notify(r(t,n),e,`stop`),this._notify(r(n,t),e,`start`)}};function vm(e){let t={},n=[],r=Object.keys(gm.plugins.items);for(let e=0;e1&&Tm(e[0].toLowerCase());if(t)return t}throw Error(`Cannot determine type of '${e}' axis. Please provide 'axis' or 'position' option.`)}function Om(e,t,n){if(n[t+`AxisID`]===e)return{axis:t}}function km(e,t){if(t.data&&t.data.datasets){let n=t.data.datasets.filter(t=>t.xAxisID===e||t.yAxisID===e);if(n.length)return Om(e,`x`,n[0])||Om(e,`y`,n[0])}return{}}function Am(e,t){let n=tu[e.type]||{scales:{}},r=t.scales||{},i=Sm(e.type,t),a=Object.create(null);return Object.keys(r).forEach(t=>{let o=r[t];if(!Z(o))return console.error(`Invalid scale configuration for scale: ${t}`);if(o._proxy)return console.warn(`Ignoring resolver passed as options for scale: ${t}`);let s=Dm(t,o,km(t,e),au.scales[o.type]),c=wm(s,i),l=n.scales||{};a[t]=Rc(Object.create(null),[{axis:s},o,l[s],l[c]])}),e.data.datasets.forEach(n=>{let i=n.type||e.type,o=n.indexAxis||Sm(i,t),s=(tu[i]||{}).scales||{};Object.keys(s).forEach(e=>{let t=Cm(e,o),i=n[t+`AxisID`]||t;a[i]=a[i]||Object.create(null),Rc(a[i],[{axis:t},r[i],s[e]])})}),Object.keys(a).forEach(e=>{let t=a[e];Rc(t,[au.scales[t.type],au.scale])}),a}function jm(e){let t=e.options||(e.options={});t.plugins=Q(t.plugins,{}),t.scales=Am(e,t)}function Mm(e){return e=e||{},e.datasets=e.datasets||[],e.labels=e.labels||[],e}function Nm(e){return e=e||{},e.data=Mm(e.data),jm(e),e}var Pm=new Map,Fm=new Set;function Im(e,t){let n=Pm.get(e);return n||(n=t(),Pm.set(e,n),Fm.add(n)),n}var Lm=(e,t,n)=>{let r=Uc(t,n);r!==void 0&&e.add(r)},Rm=class{constructor(e){this._config=Nm(e),this._scopeCache=new Map,this._resolverCache=new Map}get platform(){return this._config.platform}get type(){return this._config.type}set type(e){this._config.type=e}get data(){return this._config.data}set data(e){this._config.data=Mm(e)}get options(){return this._config.options}set options(e){this._config.options=e}get plugins(){return this._config.plugins}update(){let e=this._config;this.clearCache(),jm(e)}clearCache(){this._scopeCache.clear(),this._resolverCache.clear()}datasetScopeKeys(e){return Im(e,()=>[[`datasets.${e}`,``]])}datasetAnimationScopeKeys(e,t){return Im(`${e}.transition.${t}`,()=>[[`datasets.${e}.transitions.${t}`,`transitions.${t}`],[`datasets.${e}`,``]])}datasetElementScopeKeys(e,t){return Im(`${e}-${t}`,()=>[[`datasets.${e}.elements.${t}`,`datasets.${e}`,`elements.${t}`,``]])}pluginScopeKeys(e){let t=e.id,n=this.type;return Im(`${n}-plugin-${t}`,()=>[[`plugins.${t}`,...e.additionalOptionScopes||[]]])}_cachedScopes(e,t){let n=this._scopeCache,r=n.get(e);return(!r||t)&&(r=new Map,n.set(e,r)),r}getOptionScopes(e,t,n){let{options:r,type:i}=this,a=this._cachedScopes(e,n),o=a.get(t);if(o)return o;let s=new Set;t.forEach(t=>{e&&(s.add(e),t.forEach(t=>Lm(s,e,t))),t.forEach(e=>Lm(s,r,e)),t.forEach(e=>Lm(s,tu[i]||{},e)),t.forEach(e=>Lm(s,au,e)),t.forEach(e=>Lm(s,nu,e))});let c=Array.from(s);return c.length===0&&c.push(Object.create(null)),Fm.has(t)&&a.set(t,c),c}chartOptionScopes(){let{options:e,type:t}=this;return[e,tu[t]||{},au.datasets[t]||{},{type:t},au,nu]}resolveNamedOptions(e,t,n,r=[``]){let i={$shared:!0},{resolver:a,subPrefixes:o}=zm(this._resolverCache,e,r),s=a;if(Vm(a,t)){i.$shared=!1,n=Kc(n)?n():n;let t=this.createResolver(e,n,o);s=Iu(a,n,t)}for(let e of t)i[e]=s[e];return i}createResolver(e,t,n=[``],r){let{resolver:i}=zm(this._resolverCache,e,n);return Z(t)?Iu(i,t,void 0,r):i}};function zm(e,t,n){let r=e.get(t);r||(r=new Map,e.set(t,r));let i=n.join(),a=r.get(i);return a||(a={resolver:Fu(t,n),subPrefixes:n.filter(e=>!e.toLowerCase().includes(`hover`))},r.set(i,a)),a}var Bm=e=>Z(e)&&Object.getOwnPropertyNames(e).some(t=>Kc(e[t]));function Vm(e,t){let{isScriptable:n,isIndexable:r}=Lu(e);for(let i of t){let t=n(i),a=r(i),o=(a||t)&&e[i];if(t&&(Kc(o)||Bm(o))||a&&Ec(o))return!0}return!1}var Hm=`4.6.1`,Um=[`top`,`bottom`,`left`,`right`,`chartArea`];function Wm(e,t){return e===`top`||e===`bottom`||Um.indexOf(e)===-1&&t===`x`}function Gm(e,t){return function(n,r){return n[e]===r[e]?n[t]-r[t]:n[e]-r[e]}}function Km(e){let t=e.chart,n=t.options.animation;t.notifyPlugins(`afterRender`),jc(n&&n.onComplete,[e],t)}function qm(e){let t=e.chart,n=t.options.animation;jc(n&&n.onProgress,[e],t)}function Jm(e){return dd()&&typeof e==`string`?e=document.getElementById(e):e&&e.length&&(e=e[0]),e&&e.canvas&&(e=e.canvas),e}var Ym={},Xm=e=>{let t=Jm(e);return Object.values(Ym).filter(e=>e.canvas===t).pop()};function Zm(e,t,n){let r=Object.keys(e);for(let i of r){let r=+i;if(r>=t){let a=e[i];delete e[i],(n>0||r>t)&&(e[r+n]=a)}}}function Qm(e,t,n,r){return!n||e.type===`mouseout`?null:r?t:e}var $m=class{static register(...e){gm.add(...e),eh()}static unregister(...e){gm.remove(...e),eh()}constructor(e,t){let n=this.config=new Rm(t),r=Jm(e),i=Xm(r);if(i)throw Error(`Canvas is already in use. Chart with ID '`+i.id+`' must be destroyed before the canvas with ID '`+i.canvas.id+`' can be reused.`);let a=n.createResolver(n.chartOptionScopes(),this.getContext());this.platform=new(n.platform||(Wp(r))),this.platform.updateConfig(n);let o=this.platform.acquireContext(r,a.aspectRatio),s=o&&o.canvas,c=s&&s.height,l=s&&s.width;this.id=Tc(),this.ctx=o,this.canvas=s,this.width=l,this.height=c,this._options=a,this._aspectRatio=this.aspectRatio,this._layers=[],this._metasets=[],this._stacks=void 0,this.boxes=[],this.currentDevicePixelRatio=void 0,this.chartArea=void 0,this._active=[],this._lastEvent=void 0,this._listeners={},this._responsiveListeners=void 0,this._sortedMetasets=[],this.scales={},this._plugins=new _m,this.$proxies={},this._hiddenIndices={},this.attached=!1,this._animationsDisabled=void 0,this.$context=void 0,this._doResize=Ml(e=>this.update(e),a.resizeDelay||0),this._dataChanges=[],Ym[this.id]=this,!o||!s?console.error(`Failed to create chart: can't acquire context from the given item`):(Xd.listen(this,`complete`,Km),Xd.listen(this,`progress`,qm),this._initialize(),this.attached&&this.update())}get aspectRatio(){let{options:{aspectRatio:e,maintainAspectRatio:t},width:n,height:r,_aspectRatio:i}=this;return X(e)?t&&i?i:r?n/r:null:e}get data(){return this.config.data}set data(e){this.config.data=e}get options(){return this._options}set options(e){this.config.options=e}get registry(){return gm}_initialize(){return this.notifyPlugins(`beforeInit`),this.options.responsive?this.resize():wd(this,this.options.devicePixelRatio),this.bindEvents(),this.notifyPlugins(`afterInit`),this}clear(){return uu(this.canvas,this.ctx),this}stop(){return Xd.stop(this),this}resize(e,t){Xd.running(this)?this._resizeBeforeDraw={width:e,height:t}:this._resize(e,t)}_resize(e,t){let n=this.options,r=this.canvas,i=n.maintainAspectRatio&&this.aspectRatio,a=this.platform.getMaximumSize(r,e,t,i),o=n.devicePixelRatio||this.platform.getDevicePixelRatio(),s=this.width?`resize`:`attach`;this.width=a.width,this.height=a.height,this._aspectRatio=this.aspectRatio,wd(this,o,!0)&&(this.notifyPlugins(`resize`,{size:a}),jc(n.onResize,[this,a],this),this.attached&&this._doResize(s)&&this.render())}ensureScalesHaveIDs(){Mc(this.options.scales||{},(e,t)=>{e.id=t})}buildOrUpdateScales(){let e=this.options,t=e.scales,n=this.scales,r=Object.keys(n).reduce((e,t)=>(e[t]=!1,e),{}),i=[];t&&(i=i.concat(Object.keys(t).map(e=>{let n=t[e],r=Dm(e,n),i=r===`r`,a=r===`x`;return{options:n,dposition:i?`chartArea`:a?`bottom`:`left`,dtype:i?`radialLinear`:a?`category`:`linear`}}))),Mc(i,t=>{let i=t.options,a=i.id,o=Dm(a,i),s=Q(i.type,t.dtype);(i.position===void 0||Wm(i.position,o)!==Wm(t.dposition))&&(i.position=t.dposition),r[a]=!0;let c=null;a in n&&n[a].type===s?c=n[a]:(c=new(gm.getScale(s))({id:a,type:s,ctx:this.ctx,chart:this}),n[c.id]=c),c.init(i,e)}),Mc(r,(e,t)=>{e||delete n[t]}),Mc(n,e=>{xp.configure(this,e,e.options),xp.addBox(this,e)})}_updateMetasets(){let e=this._metasets,t=this.data.datasets.length,n=e.length;if(e.sort((e,t)=>e.index-t.index),n>t){for(let e=t;et.length&&delete this._stacks,e.forEach((e,n)=>{t.filter(t=>t===e._dataset).length===0&&this._destroyDatasetMeta(n)})}buildOrUpdateControllers(){let e=[],t=this.data.datasets,n,r;for(this._removeUnreferencedMetasets(),n=0,r=t.length;n{this.getDatasetMeta(t).controller.reset()},this)}reset(){this._resetElements(),this.notifyPlugins(`reset`)}update(e){let t=this.config;t.update();let n=this._options=t.createResolver(t.chartOptionScopes(),this.getContext()),r=this._animationsDisabled=!n.animation;if(this._updateScales(),this._checkEventBindings(),this._updateHiddenIndices(),this._plugins.invalidate(),this.notifyPlugins(`beforeUpdate`,{mode:e,cancelable:!0})===!1)return;let i=this.buildOrUpdateControllers();this.notifyPlugins(`beforeElementsUpdate`);let a=0;for(let e=0,t=this.data.datasets.length;e{e.reset()}),this._updateDatasets(e),this.notifyPlugins(`afterUpdate`,{mode:e}),this._layers.sort(Gm(`z`,`_idx`));let{_active:o,_lastEvent:s}=this;s?this._eventHandler(s,!0):o.length&&this._updateHoverStyles(o,o,!0),this.render()}_updateScales(){Mc(this.scales,e=>{xp.removeBox(this,e)}),this.ensureScalesHaveIDs(),this.buildOrUpdateScales()}_checkEventBindings(){let e=this.options;qc(new Set(Object.keys(this._listeners)),new Set(e.events))&&!!this._responsiveListeners===e.responsive||(this.unbindEvents(),this.bindEvents())}_updateHiddenIndices(){let{_hiddenIndices:e}=this,t=this._getUniformDataChanges()||[];for(let{method:n,start:r,count:i}of t)Zm(e,r,n===`_removeElements`?-i:i)}_getUniformDataChanges(){let e=this._dataChanges;if(!e||!e.length)return;this._dataChanges=[];let t=this.data.datasets.length,n=t=>new Set(e.filter(e=>e[0]===t).map((e,t)=>t+`,`+e.splice(1).join(`,`))),r=n(0);for(let e=1;ee.split(`,`)).map(e=>({method:e[1],start:+e[2],count:+e[3]}))}_updateLayout(e){if(this.notifyPlugins(`beforeLayout`,{cancelable:!0})===!1)return;xp.update(this,this.width,this.height,e);let t=this.chartArea,n=t.width<=0||t.height<=0;this._layers=[],Mc(this.boxes,e=>{n&&e.position===`chartArea`||(e.configure&&e.configure(),this._layers.push(...e._layers()))},this),this._layers.forEach((e,t)=>{e._idx=t}),this.notifyPlugins(`afterLayout`)}_updateDatasets(e){if(this.notifyPlugins(`beforeDatasetsUpdate`,{mode:e,cancelable:!0})!==!1){for(let e=0,t=this.data.datasets.length;e=0;--t)this._drawDataset(e[t]);this.notifyPlugins(`afterDatasetsDraw`)}_drawDataset(e){let t=this.ctx,n={meta:e,index:e.index,cancelable:!0},r=Yd(this,e);this.notifyPlugins(`beforeDatasetDraw`,n)!==!1&&(r&&mu(t,r),e.controller.draw(),r&&hu(t),n.cancelable=!1,this.notifyPlugins(`afterDatasetDraw`,n))}isPointInArea(e){return pu(e,this.chartArea,this._minPadding)}getElementsAtEventForMode(e,t,n,r){let i=ip.modes[t];return typeof i==`function`?i(this,e,n,r):[]}getDatasetMeta(e){let t=this.data.datasets[e],n=this._metasets,r=n.filter(e=>e&&e._dataset===t).pop();return r||(r={type:null,data:[],dataset:null,controller:null,hidden:null,xAxisID:null,yAxisID:null,order:t&&t.order||0,index:e,_dataset:t,_parsed:[],_sorted:!1},n.push(r)),r}getContext(){return this.$context||(this.$context=Pu(null,{chart:this,type:`chart`}))}getVisibleDatasetCount(){return this.getSortedVisibleDatasetMetas().length}isDatasetVisible(e){let t=this.data.datasets[e];if(!t)return!1;let n=this.getDatasetMeta(e);return typeof n.hidden==`boolean`?!n.hidden:!t.hidden}setDatasetVisibility(e,t){let n=this.getDatasetMeta(e);n.hidden=!t}toggleDataVisibility(e){this._hiddenIndices[e]=!this._hiddenIndices[e]}getDataVisibility(e){return!this._hiddenIndices[e]}_updateVisibility(e,t,n){let r=n?`show`:`hide`,i=this.getDatasetMeta(e),a=i.controller._resolveAnimations(void 0,r);Gc(t)?(i.data[t].hidden=!n,this.update()):(this.setDatasetVisibility(e,n),a.update(i,{visible:n}),this.update(t=>t.datasetIndex===e?r:void 0))}hide(e,t){this._updateVisibility(e,t,!1)}show(e,t){this._updateVisibility(e,t,!0)}_destroyDatasetMeta(e){let t=this._metasets[e];t&&t.controller&&t.controller._destroy(),delete this._metasets[e]}_stop(){let e,t;for(this.stop(),Xd.remove(this),e=0,t=this.data.datasets.length;e{t.addEventListener(this,n,r),e[n]=r},r=(e,t,n)=>{e.offsetX=t,e.offsetY=n,this._eventHandler(e)};Mc(this.options.events,e=>n(e,r))}bindResponsiveEvents(){this._responsiveListeners||(this._responsiveListeners={});let e=this._responsiveListeners,t=this.platform,n=(n,r)=>{t.addEventListener(this,n,r),e[n]=r},r=(n,r)=>{e[n]&&(t.removeEventListener(this,n,r),delete e[n])},i=(e,t)=>{this.canvas&&this.resize(e,t)},a,o=()=>{r(`attach`,o),this.attached=!0,this.resize(),n(`resize`,i),n(`detach`,a)};a=()=>{this.attached=!1,r(`resize`,i),this._stop(),this._resize(0,0),n(`attach`,o)},t.isAttached(this.canvas)?o():a()}unbindEvents(){Mc(this._listeners,(e,t)=>{this.platform.removeEventListener(this,t,e)}),this._listeners={},Mc(this._responsiveListeners,(e,t)=>{this.platform.removeEventListener(this,t,e)}),this._responsiveListeners=void 0}updateHoverStyle(e,t,n){let r=n?`set`:`remove`,i,a,o,s;for(t===`dataset`&&(i=this.getDatasetMeta(e[0].datasetIndex),i.controller[`_`+r+`DatasetHoverStyle`]()),o=0,s=e.length;o{let n=this.getDatasetMeta(e);if(!n)throw Error(`No dataset found at index `+e);return{datasetIndex:e,element:n.data[t],index:t}});Nc(n,t)||(this._active=n,this._lastEvent=null,this._updateHoverStyles(n,t))}notifyPlugins(e,t,n){return this._plugins.notify(this,e,t,n)}isPluginEnabled(e){return this._plugins._cache.filter(t=>t.plugin.id===e).length===1}_updateHoverStyles(e,t,n){let r=this.options.hover,i=(e,t)=>e.filter(e=>!t.some(t=>e.datasetIndex===t.datasetIndex&&e.index===t.index)),a=i(t,e),o=n?e:i(e,t);a.length&&this.updateHoverStyle(a,r.mode,!1),o.length&&r.mode&&this.updateHoverStyle(o,r.mode,!0)}_eventHandler(e,t){let n={event:e,replay:t,cancelable:!0,inChartArea:this.isPointInArea(e)},r=t=>(t.options.events||this.options.events).includes(e.native.type);if(this.notifyPlugins(`beforeEvent`,n,r)===!1)return;let i=this._handleEvent(e,t,n.inChartArea);return n.cancelable=!1,this.notifyPlugins(`afterEvent`,n,r),(i||n.changed)&&this.render(),this}_handleEvent(e,t,n){let{_active:r=[],options:i}=this,a=t,o=this._getActiveElements(e,r,n,a),s=Jc(e),c=Qm(e,this._lastEvent,n,s);n&&(this._lastEvent=null,jc(i.onHover,[e,o,this],this),s&&jc(i.onClick,[e,o,this],this));let l=!Nc(o,r);return(l||t)&&(this._active=o,this._updateHoverStyles(o,r,t)),this._lastEvent=c,l}_getActiveElements(e,t,n,r){if(e.type===`mouseout`)return[];if(!n)return t;let i=this.options.hover;return this.getElementsAtEventForMode(e,i.mode,i,r)}};V($m,`defaults`,au),V($m,`instances`,Ym),V($m,`overrides`,tu),V($m,`registry`,gm),V($m,`version`,Hm),V($m,`getChart`,Xm);function eh(){return Mc($m.instances,e=>e._plugins.invalidate())}function th(e,t,n){let{startAngle:r,x:i,y:a,outerRadius:o,innerRadius:s,options:c}=t,{borderWidth:l,borderJoinStyle:u}=c,d=Math.min(l/o,_l(r-n));if(e.beginPath(),e.arc(i,a,o-l/2,r+d/2,n-d/2),s>0){let t=Math.min(l/s,_l(r-n));e.arc(i,a,s+l/2,n-t/2,r+t/2,!0)}else{let t=Math.min(l/2,o*_l(r-n));if(u===`round`)e.arc(i,a,t,n-$/2,r+$/2,!0);else if(u===`bevel`){let o=2*t*t,s=-o*Math.cos(n+$/2)+i,c=-o*Math.sin(n+$/2)+a,l=o*Math.cos(r+$/2)+i,u=o*Math.sin(r+$/2)+a;e.lineTo(s,c),e.lineTo(l,u)}}e.closePath(),e.moveTo(0,0),e.rect(0,0,e.canvas.width,e.canvas.height),e.clip(`evenodd`)}function nh(e,t,n){let{startAngle:r,pixelMargin:i,x:a,y:o,outerRadius:s,innerRadius:c}=t,l=i/s;e.beginPath(),e.arc(a,o,s,r-l,n+l),c>i?(l=i/c,e.arc(a,o,c,n+l,r-l,!0)):e.arc(a,o,i,n+$c,r-$c),e.closePath(),e.clip()}function rh(e){return Du(e,[`outerStart`,`outerEnd`,`innerStart`,`innerEnd`])}function ih(e,t,n,r){let i=rh(e.options.borderRadius),a=(n-t)/2,o=Math.min(a,r*t/2),s=e=>{let t=(n-Math.min(a,e))*r/2;return yl(e,0,Math.min(a,t))};return{outerStart:s(i.outerStart),outerEnd:s(i.outerEnd),innerStart:yl(i.innerStart,0,o),innerEnd:yl(i.innerEnd,0,o)}}function ah(e,t,n,r){return{x:n+e*Math.cos(t),y:r+e*Math.sin(t)}}function oh(e,t,n,r){let{x:i,y:a,startAngle:o,pixelMargin:s,innerRadius:c}=t,l=Math.max(t.outerRadius+r+n-s,0),u=c>0?c+r+n+s:0;e.beginPath(),e.arc(i,a,l,o,o+Yc),u>0&&(e.moveTo(i+Math.cos(o)*u,a+Math.sin(o)*u),e.arc(i,a,u,o+Yc,o,!0)),e.closePath()}function sh(e,t,n,r,i,a){let{x:o,y:s,startAngle:c,pixelMargin:l,innerRadius:u}=t,{spacingMode:d=`angular`}=t.options,f=i-c;if(a&&t.options.selfJoin&&Math.abs(f)>=Yc-1e-4){oh(e,t,n,r);return}let p=Math.max(t.outerRadius+r+n-l,0),m=u>0?u+r+n+l:0,h=0,g=0,_=p>0?Math.max(.001,f*p-n/$)/p:.001,v=(f-_)/2;if(r){let e=((u>0?u-r:0)+(p>0?p-r:0))/2,t=(()=>{let t=e===0?f:f*e/(e+r);return(f-t)/2})(),n=e>0?Math.asin(Math.min(1,r/e)):0,i=Math.max(0,_/2-.001),a=Math.sin(i);if(d===`parallel`){if(m===0&&a>0){let e=r/a,t=Math.max(0,p-.001);m=Math.min(e,t)}let e=Math.min(p>0?p*a:1/0,m>0?m*a:1/0),t=Math.min(r,e);h=p>0?Math.asin(Math.min(1,t/p)):Math.min(i,n),g=m>0?Math.asin(Math.min(1,t/m)):h}else d===`proportional`?(h=Math.min(i,t),g=Math.min(i,t)):(h=Math.min(i,n),g=Math.min(i,n))}let y=c+v+h,b=i-v-h,x=c+v+g,S=i-v-g,C=Math.min(b-y,S-x),{outerStart:w,outerEnd:T,innerStart:E,innerEnd:D}=ih(t,m,p,C),O=p-w,k=p-T,A=y+w/O,j=b-T/k,M=m+E,N=m+D,P=x+E/M,F=S-D/N;if(e.beginPath(),a){let t=(A+j)/2;if(e.arc(o,s,p,A,t),e.arc(o,s,p,t,j),T>0){let t=ah(k,j,o,s);e.arc(t.x,t.y,T,j,b+$c)}let n=ah(N,S,o,s);if(e.lineTo(n.x,n.y),D>0){let t=ah(N,F,o,s);e.arc(t.x,t.y,D,S+$c,F+Math.PI)}let r=(S-D/m+(x+E/m))/2;if(e.arc(o,s,m,S-D/m,r,!0),e.arc(o,s,m,r,x+E/m,!0),E>0){let t=ah(M,P,o,s);e.arc(t.x,t.y,E,P+Math.PI,x-$c)}let i=ah(O,y,o,s);if(e.lineTo(i.x,i.y),w>0){let t=ah(O,A,o,s);e.arc(t.x,t.y,w,y-$c,A)}}else{e.moveTo(o,s);let t=Math.cos(A)*p+o,n=Math.sin(A)*p+s;e.lineTo(t,n);let r=Math.cos(j)*p+o,i=Math.sin(j)*p+s;e.lineTo(r,i)}e.closePath()}function ch(e,t,n,r,i){let{fullCircles:a,startAngle:o,circumference:s}=t,c=t.endAngle;if(a){sh(e,t,n,r,c,i);for(let t=0;t=Yc-1e-4;if(a){sh(e,t,n,r,h,i);for(let t=0;t0)&&c.selfJoin&&h-o>=$&&p===0&&u!==`miter`&&th(e,t,h),a||(sh(e,t,n,r,h,i),e.stroke())}var uh=class extends Gp{constructor(e){super(),V(this,`circumference`,void 0),V(this,`endAngle`,void 0),V(this,`fullCircles`,void 0),V(this,`innerRadius`,void 0),V(this,`outerRadius`,void 0),V(this,`pixelMargin`,void 0),V(this,`startAngle`,void 0),V(this,`circular`,void 0),this.options=void 0,this.circumference=void 0,this.startAngle=void 0,this.endAngle=void 0,this.innerRadius=void 0,this.outerRadius=void 0,this.pixelMargin=0,this.fullCircles=0,this.circular=!1,e&&Object.assign(this,e)}inRange(e,t,n){let{angle:r,distance:i}=ml(this.getProps([`x`,`y`],n),{x:e,y:t}),{startAngle:a,endAngle:o,innerRadius:s,outerRadius:c,circumference:l}=this.getProps([`startAngle`,`endAngle`,`innerRadius`,`outerRadius`,`circumference`],n),u=(this.options.spacing+this.options.borderWidth)/2,d=Q(l,o-a),f=vl(r,a,o)&&a!==o,p=d>=Yc||f,m=xl(i,s+u,c+u);return p&&m}getCenterPoint(e){let{x:t,y:n,startAngle:r,endAngle:i,innerRadius:a,outerRadius:o}=this.getProps([`x`,`y`,`startAngle`,`endAngle`,`innerRadius`,`outerRadius`],e),{offset:s,spacing:c}=this.options,l=(r+i)/2,u=(a+o+c+s)/2;return{x:t+Math.cos(l)*u,y:n+Math.sin(l)*u}}tooltipPosition(e){return this.getCenterPoint(e)}draw(e){let{options:t,circumference:n}=this,r=(t.offset||0)/4,i=(t.spacing||0)/2,a=t.circular;if(this.pixelMargin=t.borderAlign===`inner`?.33:0,this.fullCircles=n>Yc?Math.floor(n/Yc):0,n===0||this.innerRadius<0||this.outerRadius<0)return;e.save();let o=(this.startAngle+this.endAngle)/2;e.translate(Math.cos(o)*r,Math.sin(o)*r);let s=r*(1-Math.sin(Math.min($,n||0)));e.fillStyle=t.backgroundColor,e.strokeStyle=t.borderColor,ch(e,this,s,i,a),lh(e,this,s,i,a),e.restore()}};V(uh,`id`,`arc`),V(uh,`defaults`,{borderAlign:`center`,borderColor:`#fff`,borderDash:[],borderDashOffset:0,borderJoinStyle:void 0,borderRadius:0,borderWidth:2,offset:0,spacing:0,spacingMode:`angular`,angle:void 0,circular:!0,selfJoin:!1}),V(uh,`defaultRoutes`,{backgroundColor:`backgroundColor`}),V(uh,`descriptors`,{_scriptable:!0,_indexable:e=>e!==`borderDash`});function dh(e,t,n=t){e.lineCap=Q(n.borderCapStyle,t.borderCapStyle),e.setLineDash(Q(n.borderDash,t.borderDash)),e.lineDashOffset=Q(n.borderDashOffset,t.borderDashOffset),e.lineJoin=Q(n.borderJoinStyle,t.borderJoinStyle),e.lineWidth=Q(n.borderWidth,t.borderWidth),e.strokeStyle=Q(n.borderColor,t.borderColor)}function fh(e,t,n){e.lineTo(n.x,n.y)}function ph(e){return e.stepped?gu:e.tension||e.cubicInterpolationMode===`monotone`?_u:fh}function mh(e,t,n={}){let r=e.length,{start:i=0,end:a=r-1}=n,{start:o,end:s}=t,c=Math.max(i,o),l=Math.min(a,s),u=is&&a>s;return{count:r,start:c,loop:t.loop,ilen:l(o+(l?s-e:e))%a,y=()=>{h!==g&&(e.lineTo(u,g),e.lineTo(u,h),e.lineTo(u,_))};for(c&&(p=i[v(0)],e.moveTo(p.x,p.y)),f=0;f<=s;++f){if(p=i[v(f)],p.skip)continue;let t=p.x,n=p.y,r=t|0;r===m?(ng&&(g=n),u=(d*u+t)/++d):(y(),e.lineTo(t,n),m=r,d=0,h=g=n),_=n}y()}function _h(e){let t=e.options,n=t.borderDash&&t.borderDash.length;return!e._decimated&&!e._loop&&!t.tension&&t.cubicInterpolationMode!==`monotone`&&!t.stepped&&!n?gh:hh}function vh(e){return e.stepped?Od:e.tension||e.cubicInterpolationMode===`monotone`?kd:Dd}function yh(e,t,n,r){let i=t._path;i||(i=t._path=new Path2D,t.path(i,n,r)&&i.closePath()),dh(e,t.options),e.stroke(i)}function bh(e,t,n,r){let{segments:i,options:a}=t,o=_h(t);for(let s of i)dh(e,a,s.style),e.beginPath(),o(e,t,s,{start:n,end:n+r-1})&&e.closePath(),e.stroke()}var xh=typeof Path2D==`function`;function Sh(e,t,n,r){xh&&!t.options.segment?yh(e,t,n,r):bh(e,t,n,r)}var Ch=class extends Gp{constructor(e){super(),this.animated=!0,this.options=void 0,this._chart=void 0,this._loop=void 0,this._fullLoop=void 0,this._path=void 0,this._points=void 0,this._segments=void 0,this._decimated=!1,this._pointsUpdated=!1,this._datasetIndex=void 0,e&&Object.assign(this,e)}updateControlPoints(e,t){let n=this.options;if((n.tension||n.cubicInterpolationMode===`monotone`)&&!n.stepped&&!this._pointsUpdated){let r=n.spanGaps?this._loop:this._fullLoop;ud(this._points,n,e,r,t),this._pointsUpdated=!0}}set points(e){this._points=e,delete this._segments,delete this._path,this._pointsUpdated=!1}get points(){return this._points}get segments(){return this._segments||(this._segments=Hd(this,this.options.segment))}first(){let e=this.segments,t=this.points;return e.length&&t[e[0].start]}last(){let e=this.segments,t=this.points,n=e.length;return n&&t[e[n-1].end]}interpolate(e,t){let n=this.options,r=e[t],i=this.points,a=zd(this,{property:t,start:r,end:r});if(!a.length)return;let o=[],s=vh(n),c,l;for(c=0,l=a.length;ce!==`borderDash`&&e!==`fill`});function wh(e,t,n,r){let i=e.options,{[n]:a}=e.getProps([n],r);return Math.abs(t-a)e+t)/r.size,y:i/a}},nearest(e,t){if(!e.length)return!1;let n=t.x,r=t.y,i=1/0,a,o,s;for(a=0,o=e.length;a-1?e.split(` -`):e}function zh(e,t){let{element:n,datasetIndex:r,index:i}=t,a=e.getDatasetMeta(r).controller,{label:o,value:s}=a.getLabelAndValue(i);return{chart:e,label:o,parsed:a.getParsed(i),raw:e.data.datasets[r].data[i],formattedValue:s,dataset:a.getDataset(),dataIndex:i,datasetIndex:r,element:n}}function Bh(e,t){let n=e.chart.ctx,{body:r,footer:i,title:a}=e,{boxWidth:o,boxHeight:s}=t,c=ju(t.bodyFont),l=ju(t.titleFont),u=ju(t.footerFont),d=a.length,f=i.length,p=r.length,m=Au(t.padding),h=m.height,g=0,_=r.reduce((e,t)=>e+t.before.length+t.lines.length+t.after.length,0);if(_+=e.beforeBody.length+e.afterBody.length,d&&(h+=d*l.lineHeight+(d-1)*t.titleSpacing+t.titleMarginBottom),_){let e=t.displayColors?Math.max(s,c.lineHeight):c.lineHeight;h+=p*e+(_-p)*c.lineHeight+(_-1)*t.bodySpacing}f&&(h+=t.footerMarginTop+f*u.lineHeight+(f-1)*t.footerSpacing);let v=0,y=function(e){g=Math.max(g,n.measureText(e).width+v)};return n.save(),n.font=l.string,Mc(e.title,y),n.font=c.string,Mc(e.beforeBody.concat(e.afterBody),y),v=t.displayColors?o+2+t.boxPadding:0,Mc(r,e=>{Mc(e.before,y),Mc(e.lines,y),Mc(e.after,y)}),v=0,n.font=u.string,Mc(e.footer,y),n.restore(),g+=m.width,{width:g,height:h}}function Vh(e,t){let{y:n,height:r}=t;return ne.height-r/2?`bottom`:`center`}function Hh(e,t,n,r){let{x:i,width:a}=r,o=n.caretSize+n.caretPadding;if(e===`left`&&i+a+o>t.width||e===`right`&&i-a-o<0)return!0}function Uh(e,t,n,r){let{x:i,width:a}=n,{width:o,chartArea:{left:s,right:c}}=e,l=`center`;return r===`center`?l=i<=(s+c)/2?`left`:`right`:i<=a/2?l=`left`:i>=o-a/2&&(l=`right`),Hh(l,e,t,n)&&(l=`center`),l}function Wh(e,t,n){let r=n.yAlign||t.yAlign||Vh(e,n);return{xAlign:n.xAlign||t.xAlign||Uh(e,t,n,r),yAlign:r}}function Gh(e,t){let{x:n,width:r}=e;return t===`right`?n-=r:t===`center`&&(n-=r/2),n}function Kh(e,t,n){let{y:r,height:i}=e;return t===`top`?r+=n:r-=t===`bottom`?i+n:i/2,r}function qh(e,t,n,r){let{caretSize:i,caretPadding:a,cornerRadius:o}=e,{xAlign:s,yAlign:c}=n,l=i+a,{topLeft:u,topRight:d,bottomLeft:f,bottomRight:p}=ku(o),m=Gh(t,s),h=Kh(t,c,l);return c===`center`?s===`left`?m+=l:s===`right`&&(m-=l):s===`left`?m-=Math.max(u,f)+i:s===`right`&&(m+=Math.max(d,p)+i),{x:yl(m,0,r.width-t.width),y:yl(h,0,r.height-t.height)}}function Jh(e,t,n){let r=Au(n.padding);return t===`center`?e.x+e.width/2:t===`right`?e.x+e.width-r.right:e.x+r.left}function Yh(e){return Lh([],Rh(e))}function Xh(e,t,n){return Pu(e,{tooltip:t,tooltipItems:n,type:`tooltip`})}function Zh(e,t){let n=t&&t.dataset&&t.dataset.tooltip&&t.dataset.tooltip.callbacks;return n?e.override(n):e}var Qh={beforeTitle:wc,title(e){if(e.length>0){let t=e[0],n=t.chart.data.labels,r=n?n.length:0;if(this&&this.options&&this.options.mode===`dataset`)return t.dataset.label||``;if(t.label)return t.label;if(r>0&&t.dataIndex{let t={before:[],lines:[],after:[]},i=Zh(n,e);Lh(t.before,Rh($h(i,`beforeLabel`,this,e))),Lh(t.lines,$h(i,`label`,this,e)),Lh(t.after,Rh($h(i,`afterLabel`,this,e))),r.push(t)}),r}getAfterBody(e,t){return Yh($h(t.callbacks,`afterBody`,this,e))}getFooter(e,t){let{callbacks:n}=t,r=$h(n,`beforeFooter`,this,e),i=$h(n,`footer`,this,e),a=$h(n,`afterFooter`,this,e),o=[];return o=Lh(o,Rh(r)),o=Lh(o,Rh(i)),o=Lh(o,Rh(a)),o}_createItems(e){let t=this._active,n=this.chart.data,r=[],i=[],a=[],o=[],s,c;for(s=0,c=t.length;se.filter(t,r,i,n))),e.itemSort&&(o=o.sort((t,r)=>e.itemSort(t,r,n))),Mc(o,t=>{let n=Zh(e.callbacks,t);r.push($h(n,`labelColor`,this,t)),i.push($h(n,`labelPointStyle`,this,t)),a.push($h(n,`labelTextColor`,this,t))}),this.labelColors=r,this.labelPointStyles=i,this.labelTextColors=a,this.dataPoints=o,o}update(e,t){let n=this.options.setContext(this.getContext()),r=this._active,i,a=[];if(!r.length)this.opacity!==0&&(i={opacity:0});else{let e=Ih[n.position].call(this,r,this._eventPosition);a=this._createItems(n),this.title=this.getTitle(a,n),this.beforeBody=this.getBeforeBody(a,n),this.body=this.getBody(a,n),this.afterBody=this.getAfterBody(a,n),this.footer=this.getFooter(a,n);let t=this._size=Bh(this,n),o=Object.assign({},e,t),s=Wh(this.chart,n,o),c=qh(n,o,s,this.chart);this.xAlign=s.xAlign,this.yAlign=s.yAlign,i={opacity:1,x:c.x,y:c.y,width:t.width,height:t.height,caretX:e.x,caretY:e.y}}this._tooltipItems=a,this.$context=void 0,i&&this._resolveAnimations().update(this,i),e&&n.external&&n.external.call(this,{chart:this.chart,tooltip:this,replay:t})}drawCaret(e,t,n,r){let i=this.getCaretPosition(e,n,r);t.lineTo(i.x1,i.y1),t.lineTo(i.x2,i.y2),t.lineTo(i.x3,i.y3)}getCaretPosition(e,t,n){let{xAlign:r,yAlign:i}=this,{caretSize:a,cornerRadius:o}=n,{topLeft:s,topRight:c,bottomLeft:l,bottomRight:u}=ku(o),{x:d,y:f}=e,{width:p,height:m}=t,h,g,_,v,y,b;return i===`center`?(y=f+m/2,r===`left`?(h=d,g=h-a,v=y+a,b=y-a):(h=d+p,g=h+a,v=y-a,b=y+a),_=h):(g=r===`left`?d+Math.max(s,l)+a:r===`right`?d+p-Math.max(c,u)-a:this.caretX,i===`top`?(v=f,y=v-a,h=g-a,_=g+a):(v=f+m,y=v+a,h=g+a,_=g-a),b=v),{x1:h,x2:g,x3:_,y1:v,y2:y,y3:b}}drawTitle(e,t,n){let r=this.title,i=r.length,a,o,s;if(i){let c=Md(n.rtl,this.x,this.width);for(e.x=Jh(this,n.titleAlign,n),t.textAlign=c.textAlign(n.titleAlign),t.textBaseline=`middle`,a=ju(n.titleFont),o=n.titleSpacing,t.fillStyle=n.titleColor,t.font=a.string,s=0;se!==0)?(e.beginPath(),e.fillStyle=i.multiKeyBackground,Su(e,{x:t,y:p,w:c,h:s,radius:o}),e.fill(),e.stroke(),e.fillStyle=a.backgroundColor,e.beginPath(),Su(e,{x:n,y:p+1,w:c-2,h:s-2,radius:o}),e.fill()):(e.fillStyle=i.multiKeyBackground,e.fillRect(t,p,c,s),e.strokeRect(t,p,c,s),e.fillStyle=a.backgroundColor,e.fillRect(n,p+1,c-2,s-2))}e.fillStyle=this.labelTextColors[n]}drawBody(e,t,n){let{body:r}=this,{bodySpacing:i,bodyAlign:a,displayColors:o,boxHeight:s,boxWidth:c,boxPadding:l}=n,u=ju(n.bodyFont),d=u.lineHeight,f=0,p=Md(n.rtl,this.x,this.width),m=function(n){t.fillText(n,p.x(e.x+f),e.y+d/2),e.y+=d+i},h=p.textAlign(a),g,_,v,y,b,x,S;for(t.textAlign=a,t.textBaseline=`middle`,t.font=u.string,e.x=Jh(this,h,n),t.fillStyle=n.bodyColor,Mc(this.beforeBody,m),f=o&&h!==`right`?a===`center`?c/2+l:c+2+l:0,y=0,x=r.length;y0&&t.stroke()}_updateAnimationTarget(e){let t=this.chart,n=this.$animations,r=n&&n.x,i=n&&n.y;if(r||i){let n=Ih[e.position].call(this,this._active,this._eventPosition);if(!n)return;let a=this._size=Bh(this,e),o=Object.assign({},n,this._size),s=Wh(t,e,o),c=qh(e,o,s,t);(r._to!==c.x||i._to!==c.y)&&(this.xAlign=s.xAlign,this.yAlign=s.yAlign,this.width=a.width,this.height=a.height,this.caretX=n.x,this.caretY=n.y,this._resolveAnimations().update(this,c))}}_willRender(){return!!this.opacity}draw(e){let t=this.options.setContext(this.getContext()),n=this.opacity;if(!n)return;this._updateAnimationTarget(t);let r={width:this.width,height:this.height},i={x:this.x,y:this.y};n=Math.abs(n)<.001?0:n;let a=Au(t.padding),o=this.title.length||this.beforeBody.length||this.body.length||this.afterBody.length||this.footer.length;t.enabled&&o&&(e.save(),e.globalAlpha=n,this.drawBackground(i,e,r,t),Nd(e,t.textDirection),i.y+=a.top,this.drawTitle(i,e,t),this.drawBody(i,e,t),this.drawFooter(i,e,t),Pd(e,t.textDirection),e.restore())}getActiveElements(){return this._active||[]}setActiveElements(e,t){let n=this._active,r=e.map(({datasetIndex:e,index:t})=>{let n=this.chart.getDatasetMeta(e);if(!n)throw Error(`Cannot find a dataset at index `+e);return{datasetIndex:e,element:n.data[t],index:t}}),i=!Nc(n,r),a=this._positionChanged(r,t);(i||a)&&(this._active=r,this._eventPosition=t,this._ignoreReplayEvents=!0,this.update(!0))}handleEvent(e,t,n=!0){if(t&&this._ignoreReplayEvents)return!1;this._ignoreReplayEvents=!1;let r=this.options,i=this._active||[],a=this._getActiveElements(e,i,t,n),o=this._positionChanged(a,e),s=t||!Nc(a,i)||o;return s&&(this._active=a,(r.enabled||r.external)&&(this._eventPosition={x:e.x,y:e.y},this.update(!0,t))),s}_getActiveElements(e,t,n,r){let i=this.options;if(e.type===`mouseout`)return[];if(!r)return t.filter(e=>this.chart.data.datasets[e.datasetIndex]&&this.chart.getDatasetMeta(e.datasetIndex).controller.getParsed(e.index)!==void 0);let a=this.chart.getElementsAtEventForMode(e,i.mode,i,n);return i.reverse&&a.reverse(),a}_positionChanged(e,t){let{caretX:n,caretY:r,options:i}=this,a=Ih[i.position].call(this,e,t);return a!==!1&&(n!==a.x||r!==a.y)}};V(eg,`positioners`,Ih);var tg={id:`tooltip`,_element:eg,positioners:Ih,afterInit(e,t,n){n&&(e.tooltip=new eg({chart:e,options:n}))},beforeUpdate(e,t,n){e.tooltip&&e.tooltip.initialize(n)},reset(e,t,n){e.tooltip&&e.tooltip.initialize(n)},afterDraw(e){let t=e.tooltip;if(t&&t._willRender()){let n={tooltip:t};if(e.notifyPlugins(`beforeTooltipDraw`,{...n,cancelable:!0})===!1)return;t.draw(e.ctx),e.notifyPlugins(`afterTooltipDraw`,n)}},afterEvent(e,t){if(e.tooltip){let n=t.replay;e.tooltip.handleEvent(t.event,n,t.inChartArea)&&(t.changed=!0)}},defaults:{enabled:!0,external:null,position:`average`,backgroundColor:`rgba(0,0,0,0.8)`,titleColor:`#fff`,titleFont:{weight:`bold`},titleSpacing:2,titleMarginBottom:6,titleAlign:`left`,bodyColor:`#fff`,bodySpacing:2,bodyFont:{},bodyAlign:`left`,footerColor:`#fff`,footerSpacing:2,footerMarginTop:6,footerFont:{weight:`bold`},footerAlign:`left`,padding:6,caretPadding:2,caretSize:5,cornerRadius:6,boxHeight:(e,t)=>t.bodyFont.size,boxWidth:(e,t)=>t.bodyFont.size,multiKeyBackground:`#fff`,displayColors:!0,boxPadding:0,borderColor:`rgba(0,0,0,0)`,borderWidth:0,animation:{duration:400,easing:`easeOutQuart`},animations:{numbers:{type:`number`,properties:[`x`,`y`,`width`,`height`,`caretX`,`caretY`]},opacity:{easing:`linear`,duration:200}},callbacks:Qh},defaultRoutes:{bodyFont:`font`,footerFont:`font`,titleFont:`font`},descriptors:{_scriptable:e=>e!==`filter`&&e!==`itemSort`&&e!==`external`,_indexable:!1,callbacks:{_scriptable:!1,_indexable:!1},animation:{_fallback:!1},animations:{_fallback:`animation`}},additionalOptionScopes:[`interaction`]},ng=(e,t,n,r)=>(typeof t==`string`?(n=e.push(t)-1,r.unshift({index:n,label:t})):isNaN(t)&&(n=null),n);function rg(e,t,n,r){let i=e.indexOf(t);return i===-1?ng(e,t,n,r):i===e.lastIndexOf(t)?i:n}var ig=(e,t)=>e===null?null:yl(Math.round(e),0,t);function ag(e){let t=this.getLabels();return e>=0&&et.length-1?null:this.getPixelForValue(t[e].value)}getValueForPixel(e){return Math.round(this._startValue+this.getDecimalForPixel(e)*this._valueRange)}getBasePixel(){return this.bottom}};V(og,`id`,`category`),V(og,`defaults`,{ticks:{callback:ag}});function sg(e,t){let n=[],{bounds:r,step:i,min:a,max:o,precision:s,count:c,maxTicks:l,maxDigits:u,includeBounds:d}=e,f=i||1,p=l-1,{min:m,max:h}=t,g=!X(a),_=!X(o),v=!X(c),y=(h-m)/(u+1),b=al((h-m)/p/f)*f,x,S,C,w;if(b<1e-14&&!g&&!_)return[{value:m},{value:h}];w=Math.ceil(h/b)-Math.floor(m/b),w>p&&(b=al(w*b/p/f)*f),X(s)||(x=10**s,b=Math.ceil(b*x)/x),r===`ticks`?(S=Math.floor(m/b)*b,C=Math.ceil(h/b)*b):(S=m,C=h),g&&_&&i&&ll((o-a)/i,b/1e3)?(w=Math.round(Math.min((o-a)/b,l)),b=(o-a)/w,S=a,C=o):v?(S=g?a:S,C=_?o:C,w=c-1,b=(C-S)/w):(w=(C-S)/b,w=il(w,Math.round(w),b/1e3)?Math.round(w):Math.ceil(w));let T=Math.max(pl(b),pl(S));x=10**(X(s)?T:s),S=Math.round(S*x)/x,C=Math.round(C*x)/x;let E=0;for(g&&(d&&S!==a?(n.push({value:a}),So)break;n.push({value:e})}return _&&d&&C!==o?n.length&&il(n[n.length-1].value,o,cg(o,y,e))?n[n.length-1].value=o:n.push({value:o}):_&&C!==o||n.push({value:C}),n}function cg(e,t,{horizontal:n,minRotation:r}){let i=dl(r),a=(n?Math.sin(i):Math.cos(i))||.001,o=.75*t*(``+e).length;return Math.min(t/a,o)}var lg=class extends dm{constructor(e){super(e),this.start=void 0,this.end=void 0,this._startValue=void 0,this._endValue=void 0,this._valueRange=0}parse(e,t){return X(e)||(typeof e==`number`||e instanceof Number)&&!isFinite(+e)?null:+e}handleTickRangeOptions(){let{beginAtZero:e}=this.options,{minDefined:t,maxDefined:n}=this.getUserBounds(),{min:r,max:i}=this,a=e=>r=t?r:e,o=e=>i=n?i:e;if(e){let e=rl(r),t=rl(i);e<0&&t<0?o(0):e>0&&t>0&&a(0)}if(r===i){let t=i===0?1:Math.abs(i*.05);o(i+t),e||a(r-t)}this.min=r,this.max=i}getTickLimit(){let{maxTicksLimit:e,stepSize:t}=this.options.ticks,n;return t?(n=Math.ceil(this.max/t)-Math.floor(this.min/t)+1,n>1e3&&(console.warn(`scales.${this.id}.ticks.stepSize: ${t} would result generating up to ${n} ticks. Limiting to 1000.`),n=1e3)):(n=this.computeTickLimit(),e=e||11),e&&(n=Math.min(e,n)),n}computeTickLimit(){return 1/0}buildTicks(){let e=this.options,t=e.ticks,n=this.getTickLimit();n=Math.max(2,n);let r=sg({maxTicks:n,bounds:e.bounds,min:e.min,max:e.max,precision:t.precision,step:t.stepSize,count:t.count,maxDigits:this._maxDigits(),horizontal:this.isHorizontal(),minRotation:t.minRotation||0,includeBounds:t.includeBounds!==!1},this._range||this);return e.bounds===`ticks`&&ul(r,this,`value`),e.reverse?(r.reverse(),this.start=this.max,this.end=this.min):(this.start=this.min,this.end=this.max),r}configure(){let e=this.ticks,t=this.min,n=this.max;if(super.configure(),this.options.offset&&e.length){let r=(n-t)/Math.max(e.length-1,1)/2;t-=r,n+=r}this._startValue=t,this._endValue=n,this._valueRange=n-t}getLabelForValue(e){return Xl(e,this.chart.options.locale,this.options.ticks.format)}},ug=class extends lg{determineDataLimits(){let{min:e,max:t}=this.getMinMax(!0);this.min=Dc(e)?e:0,this.max=Dc(t)?t:1,this.handleTickRangeOptions()}computeTickLimit(){let e=this.isHorizontal(),t=e?this.width:this.height,n=dl(this.options.ticks.minRotation),r=(e?Math.sin(n):Math.cos(n))||.001,i=this._resolveTickFontOptions(0);return Math.ceil(t/Math.min(40,i.lineHeight/r))}getPixelForValue(e){return e===null?NaN:this.getPixelForDecimal((e-this._startValue)/this._valueRange)}getValueForPixel(e){return this._startValue+this.getDecimalForPixel(e)*this._valueRange}};V(ug,`id`,`linear`),V(ug,`defaults`,{ticks:{callback:$l.formatters.numeric}});var dg=e=>Math.floor(nl(e)),fg=(e,t)=>10**(dg(e)+t);function pg(e){return e/10**dg(e)==1}function mg(e,t,n){let r=10**n,i=Math.floor(e/r);return Math.ceil(t/r)-i}function hg(e,t){let n=dg(t-e);for(;mg(e,t,n)>10;)n++;for(;mg(e,t,n)<10;)n--;return Math.min(n,dg(e))}function gg(e,{min:t,max:n}){t=Oc(e.min,t);let r=[],i=dg(t),a=hg(t,n),o=a<0?10**Math.abs(a):1,s=10**a,c=i>a?10**i:0,l=Math.round((t-c)*o)/o,u=Math.floor((t-c)/s/10)*s*10,d=Math.floor((l-u)/10**a),f=Oc(e.min,Math.round((c+u+d*10**a)*o)/o);for(;f=10?d=d<15?15:20:d++,d>=20&&(a++,d=2,o=a>=0?1:o),f=Math.round((c+u+d*10**a)*o)/o;let p=Oc(e.max,f);return r.push({value:p,major:pg(p),significand:d}),r}var _g=class extends dm{constructor(e){super(e),this.start=void 0,this.end=void 0,this._startValue=void 0,this._valueRange=0}parse(e,t){let n=lg.prototype.parse.apply(this,[e,t]);if(n===0)this._zero=!0;else return Dc(n)&&n>0?n:null}determineDataLimits(){let{min:e,max:t}=this.getMinMax(!0);this.min=Dc(e)?Math.max(0,e):null,this.max=Dc(t)?Math.max(0,t):null,this.options.beginAtZero&&(this._zero=!0),this._zero&&this.min!==this._suggestedMin&&!Dc(this._userMin)&&(this.min=e===fg(this.min,0)?fg(this.min,-1):fg(this.min,0)),this.handleTickRangeOptions()}handleTickRangeOptions(){let{minDefined:e,maxDefined:t}=this.getUserBounds(),n=this.min,r=this.max,i=t=>n=e?n:t,a=e=>r=t?r:e;n===r&&(n<=0?(i(1),a(10)):(i(fg(n,-1)),a(fg(r,1)))),n<=0&&i(fg(r,-1)),r<=0&&a(fg(n,1)),this.min=n,this.max=r}buildTicks(){let e=this.options,t=gg({min:this._userMin,max:this._userMax},this);return e.bounds===`ticks`&&ul(t,this,`value`),e.reverse?(t.reverse(),this.start=this.max,this.end=this.min):(this.start=this.min,this.end=this.max),t}getLabelForValue(e){return e===void 0?`0`:Xl(e,this.chart.options.locale,this.options.ticks.format)}configure(){let e=this.min;super.configure(),this._startValue=nl(e),this._valueRange=nl(this.max)-nl(e)}getPixelForValue(e){return(e===void 0||e===0)&&(e=this.min),e===null||isNaN(e)?NaN:this.getPixelForDecimal(e===this.min?0:(nl(e)-this._startValue)/this._valueRange)}getValueForPixel(e){let t=this.getDecimalForPixel(e);return 10**(this._startValue+t*this._valueRange)}};V(_g,`id`,`logarithmic`),V(_g,`defaults`,{ticks:{callback:$l.formatters.logarithmic,major:{enabled:!0}}});function vg(e){let t=e.ticks;if(t.display&&e.display){let e=Au(t.backdropPadding);return Q(t.font&&t.font.size,au.font.size)+e.height}return 0}function yg(e,t,n){return n=Ec(n)?n:[n],{w:cu(e,t.string,n),h:n.length*t.lineHeight}}function bg(e,t,n,r,i){return e===r||e===i?{start:t-n/2,end:t+n/2}:ei?{start:t-n,end:t}:{start:t,end:t+n}}function xg(e){let t={l:e.left+e._padding.left,r:e.right-e._padding.right,t:e.top+e._padding.top,b:e.bottom-e._padding.bottom},n=Object.assign({},t),r=[],i=[],a=e._pointLabels.length,o=e.options.pointLabels,s=o.centerPointLabels?$/a:0;for(let c=0;ct.r&&(s=(r.end-t.r)/a,e.r=Math.max(e.r,t.r+s)),i.startt.b&&(c=(i.end-t.b)/o,e.b=Math.max(e.b,t.b+c))}function Cg(e,t,n){let r=e.drawingArea,{extra:i,additionalAngle:a,padding:o,size:s}=n,c=e.getPointPosition(t,r+i+o,a),l=Math.round(fl(_l(c.angle+$c))),u=Og(c.y,s.h,l),d=Eg(l),f=Dg(c.x,s.w,d);return{visible:!0,x:c.x,y:u,textAlign:d,left:f,top:u,right:f+s.w,bottom:u+s.h}}function wg(e,t){if(!t)return!0;let{left:n,top:r,right:i,bottom:a}=e;return!(pu({x:n,y:r},t)||pu({x:n,y:a},t)||pu({x:i,y:r},t)||pu({x:i,y:a},t))}function Tg(e,t,n){let r=[],i=e._pointLabels.length,a=e.options,{centerPointLabels:o,display:s}=a.pointLabels,c={extra:vg(a)/2,additionalAngle:o?$/i:0},l;for(let a=0;a270||n<90)&&(e-=t),e}function kg(e,t,n){let{left:r,top:i,right:a,bottom:o}=n,{backdropColor:s}=t;if(!X(s)){let n=ku(t.borderRadius),c=Au(t.backdropPadding);e.fillStyle=s;let l=r-c.left,u=i-c.top,d=a-r+c.width,f=o-i+c.height;Object.values(n).some(e=>e!==0)?(e.beginPath(),Su(e,{x:l,y:u,w:d,h:f,radius:n}),e.fill()):e.fillRect(l,u,d,f)}}function Ag(e,t){let{ctx:n,options:{pointLabels:r}}=e;for(let i=t-1;i>=0;i--){let t=e._pointLabelItems[i];if(!t.visible)continue;let a=r.setContext(e.getPointLabelContext(i));kg(n,a,t);let o=ju(a.font),{x:s,y:c,textAlign:l}=t;xu(n,e._pointLabels[i],s,c+o.lineHeight/2,o,{color:a.color,textAlign:l,textBaseline:`middle`})}}function jg(e,t,n,r){let{ctx:i}=e;if(n)i.arc(e.xCenter,e.yCenter,t,0,Yc);else{let n=e.getPointPosition(0,t);i.moveTo(n.x,n.y);for(let a=1;a{let n=jc(this.options.pointLabels.callback,[e,t],this);return n||n===0?n:``}).filter((e,t)=>this.chart.getDataVisibility(t))}fit(){let e=this.options;e.display&&e.pointLabels.display?xg(this):this.setCenterPoint(0,0,0,0)}setCenterPoint(e,t,n,r){this.xCenter+=Math.floor((e-t)/2),this.yCenter+=Math.floor((n-r)/2),this.drawingArea-=Math.min(this.drawingArea/2,Math.max(e,t,n,r))}getIndexAngle(e){let t=Yc/(this._pointLabels.length||1),n=this.options.startAngle||0;return _l(e*t+dl(n))}getDistanceFromCenterForValue(e){if(X(e))return NaN;let t=this.drawingArea/(this.max-this.min);return this.options.reverse?(this.max-e)*t:(e-this.min)*t}getValueForDistanceFromCenter(e){if(X(e))return NaN;let t=e/(this.drawingArea/(this.max-this.min));return this.options.reverse?this.max-t:this.min+t}getPointLabelContext(e){let t=this._pointLabels||[];if(e>=0&&e{if(t!==0||t===0&&this.min<0){s=this.getDistanceFromCenterForValue(e.value);let n=this.getContext(t),o=r.setContext(n),c=i.setContext(n);Mg(this,o,s,a,c)}}),n.display){for(e.save(),o=a-1;o>=0;o--){let r=n.setContext(this.getPointLabelContext(o)),{color:i,lineWidth:a}=r;a&&i&&(e.lineWidth=a,e.strokeStyle=i,e.setLineDash(r.borderDash),e.lineDashOffset=r.borderDashOffset,s=this.getDistanceFromCenterForValue(t.reverse?this.min:this.max),c=this.getPointPosition(o,s),e.beginPath(),e.moveTo(this.xCenter,this.yCenter),e.lineTo(c.x,c.y),e.stroke())}e.restore()}}drawBorder(){}drawLabels(){let e=this.ctx,t=this.options,n=t.ticks;if(!n.display)return;let r=this.getIndexAngle(0),i,a;e.save(),e.translate(this.xCenter,this.yCenter),e.rotate(r),e.textAlign=`center`,e.textBaseline=`middle`,this.ticks.forEach((r,o)=>{if(o===0&&this.min>=0&&!t.reverse)return;let s=n.setContext(this.getContext(o)),c=ju(s.font);if(i=this.getDistanceFromCenterForValue(this.ticks[o].value),s.showLabelBackdrop){e.font=c.string,a=e.measureText(r.label).width,e.fillStyle=s.backdropColor;let t=Au(s.backdropPadding);e.fillRect(-a/2-t.left,-i-c.size/2-t.top,a+t.width,c.size+t.height)}xu(e,r.label,0,-i,c,{color:s.color,strokeColor:s.textStrokeColor,strokeWidth:s.textStrokeWidth})}),e.restore()}drawTitle(){}};V(Pg,`id`,`radialLinear`),V(Pg,`defaults`,{display:!0,animate:!0,position:`chartArea`,angleLines:{display:!0,lineWidth:1,borderDash:[],borderDashOffset:0},grid:{circular:!1},startAngle:0,ticks:{showLabelBackdrop:!0,callback:$l.formatters.numeric},pointLabels:{backdropColor:void 0,backdropPadding:2,display:!0,font:{size:10},callback(e){return e},padding:5,centerPointLabels:!1}}),V(Pg,`defaultRoutes`,{"angleLines.color":`borderColor`,"pointLabels.color":`color`,"ticks.color":`color`}),V(Pg,`descriptors`,{angleLines:{_fallback:`grid`}});var Fg={millisecond:{common:!0,size:1,steps:1e3},second:{common:!0,size:1e3,steps:60},minute:{common:!0,size:6e4,steps:60},hour:{common:!0,size:36e5,steps:24},day:{common:!0,size:864e5,steps:30},week:{common:!1,size:6048e5,steps:4},month:{common:!0,size:2628e6,steps:12},quarter:{common:!1,size:7884e6,steps:4},year:{common:!0,size:3154e7}},Ig=Object.keys(Fg);function Lg(e,t){return e-t}function Rg(e,t){if(X(t))return null;let n=e._adapter,{parser:r,round:i,isoWeekday:a}=e._parseOpts,o=t;return typeof r==`function`&&(o=r(o)),Dc(o)||(o=typeof r==`string`?n.parse(o,r):n.parse(o)),o===null?null:(i&&(o=i===`week`&&(cl(a)||a===!0)?n.startOf(o,`isoWeek`,a):n.startOf(o,i)),+o)}function zg(e,t,n,r){let i=Ig.length;for(let a=Ig.indexOf(e);a=Ig.indexOf(n);a--){let n=Ig[a];if(Fg[n].common&&e._adapter.diff(i,r,n)>=t-1)return n}return Ig[n?Ig.indexOf(n):0]}function Vg(e){for(let t=Ig.indexOf(e)+1,n=Ig.length;t=t?n[r]:n[i];e[a]=!0}}function Ug(e,t,n,r){let i=e._adapter,a=+i.startOf(t[0].value,r),o=t[t.length-1].value,s,c;for(s=a;s<=o;s=+i.add(s,1,r))c=n[s],c>=0&&(t[c].major=!0);return t}function Wg(e,t,n){let r=[],i={},a=t.length,o,s;for(o=0;o+e.value))}initOffsets(e=[]){let t=0,n=0,r,i;this.options.offset&&e.length&&(r=this.getDecimalForValue(e[0]),t=e.length===1?1-r:(this.getDecimalForValue(e[1])-r)/2,i=this.getDecimalForValue(e[e.length-1]),n=e.length===1?i:(i-this.getDecimalForValue(e[e.length-2]))/2);let a=e.length<3?.5:.25;t=yl(t,0,a),n=yl(n,0,a),this._offsets={start:t,end:n,factor:1/(t+1+n)}}_generate(){let e=this._adapter,t=this.min,n=this.max,r=this.options,i=r.time,a=i.unit||zg(i.minUnit,t,n,this._getLabelCapacity(t)),o=Q(r.ticks.stepSize,1),s=a===`week`&&i.isoWeekday,c=cl(s)||s===!0,l={},u=t,d,f;if(c&&(u=+e.startOf(u,`isoWeek`,s)),u=+e.startOf(u,c?`day`:a),e.diff(n,t,a)>1e5*o)throw Error(t+` and `+n+` are too far apart with stepSize of `+o+` `+a);let p=r.ticks.source===`data`&&this.getDataTimestamps();for(d=u,f=0;d+e)}getLabelForValue(e){let t=this._adapter,n=this.options.time;return n.tooltipFormat?t.format(e,n.tooltipFormat):t.format(e,n.displayFormats.datetime)}format(e,t){let n=this.options.time.displayFormats,r=this._unit,i=t||n[r];return this._adapter.format(e,i)}_tickFormatFunction(e,t,n,r){let i=this.options,a=i.ticks.callback;if(a)return jc(a,[e,t,n],this);let o=i.time.displayFormats,s=this._unit,c=this._majorUnit,l=s&&o[s],u=c&&o[c],d=n[t],f=c&&u&&d&&d.major;return this._adapter.format(e,r||(f?u:l))}generateTickLabels(e){let t,n,r;for(t=0,n=e.length;t0?o:1}getDataTimestamps(){let e=this._cache.data||[],t,n;if(e.length)return e;let r=this.getMatchingVisibleMetas();if(this._normalized&&r.length)return this._cache.data=r[0].controller.getAllParsedValues(this);for(t=0,n=r.length;t=e[r].pos&&t<=e[i].pos&&({lo:r,hi:i}=Cl(e,`pos`,t)),{pos:a,time:s}=e[r],{pos:o,time:c}=e[i]):(t>=e[r].time&&t<=e[i].time&&({lo:r,hi:i}=Cl(e,`time`,t)),{time:a,pos:s}=e[r],{time:o,pos:c}=e[i]);let l=o-a;return l?s+(c-s)*(t-a)/l:s}var qg=class extends Gg{constructor(e){super(e),this._table=[],this._minPos=void 0,this._tableRange=void 0}initOffsets(){let e=this._getTimestampsForTable(),t=this._table=this.buildLookupTable(e);this._minPos=Kg(t,this.min),this._tableRange=Kg(t,this.max)-this._minPos,super.initOffsets(e)}buildLookupTable(e){let{min:t,max:n}=this,r=[],i=[],a,o,s,c,l;for(a=0,o=e.length;a=t&&c<=n&&r.push(c);if(r.length<2)return[{time:t,pos:0},{time:n,pos:1}];for(a=0,o=r.length;ae-t)}_getTimestampsForTable(){let e=this._cache.all||[];if(e.length)return e;let t=this.getDataTimestamps(),n=this.getLabelTimestamps();return e=t.length&&n.length?this.normalize(t.concat(n)):t.length?t:n,e=this._cache.all=e,e}getDecimalForValue(e){return(Kg(this._table,e)-this._minPos)/this._tableRange}getValueForPixel(e){let t=this._offsets,n=this.getDecimalForPixel(e)/t.factor-t.end;return Kg(this._table,n*this._tableRange+this._minPos,!0)}};V(qg,`id`,`timeseries`),V(qg,`defaults`,Gg.defaults),$m.register(zf,Fh,og,ug,tg);var Jg=new Intl.DateTimeFormat(`ko-KR`,{hour:`2-digit`,hour12:!1}),Yg=`#94a3b8`,Xg=`rgba(148, 163, 184, 0.18)`,Zg=`#0d9488`,Qg=`#0f766e`;function $g(e){return Jg.format(new Date(e*1e3))}function e_(e){return e.map((t,n)=>n%3==0||n===e.length-1?$g(t.bucketStart):``)}function t_({buckets:e}){let t=z(null),n=z(null),r=z(e);return r.current=e,R(()=>{let e=t.current;if(!e)return;let i=window.matchMedia(`(prefers-reduced-motion: reduce)`).matches,a=window.getComputedStyle(e).fontFamily,o={animation:!i&&{duration:500,easing:`easeOutQuart`},maintainAspectRatio:!1,responsive:!0,interaction:{intersect:!1,mode:`index`},plugins:{legend:{display:!1},tooltip:{displayColors:!1,callbacks:{title:e=>{let t=e[0]?.dataIndex??-1,n=r.current[t];return n?$g(n.bucketStart):``},label:e=>`차단 ${Y(Number(e.raw??0))}`,afterLabel:e=>{let t=r.current[e.dataIndex];return t?`DNS 요청 ${Y(t.queries)}`:``}}}},scales:{x:{border:{display:!1},grid:{display:!1},ticks:{autoSkip:!1,color:Yg,font:{family:a,size:11,weight:700},maxRotation:0,minRotation:0}},y:{beginAtZero:!0,border:{display:!1},grid:{color:Xg},ticks:{color:Yg,font:{family:a,size:11,weight:700},maxTicksLimit:4,precision:0}}}};return n.current=new $m(e,{type:`bar`,data:{labels:e_(r.current),datasets:[{data:r.current.map(e=>e.blocked),backgroundColor:Zg,hoverBackgroundColor:Qg,borderRadius:4,borderSkipped:!1,barPercentage:.82,categoryPercentage:.9,maxBarThickness:24}]},options:o}),()=>{n.current?.destroy(),n.current=null}},[]),R(()=>{let t=n.current;t&&(t.data.labels=e_(e),t.data.datasets[0].data=e.map(e=>e.blocked),t.update())},[e]),W(`div`,{class:`mt-5 h-44 sm:h-52`,children:W(`canvas`,{"aria-label":`최근 24시간 시간대별 SafeShield 차단 요청`,ref:t,role:`img`,children:`최근 24시간 시간대별 SafeShield 차단 요청 차트`})})}var n_=3600,r_=24;function i_(e,t){return e<=0?`0.0%`:`${(t/e*100).toFixed(1)}%`}function a_(e){let t=e.hourly[e.hourly.length-1];if(t?.bucketStart)return t.bucketStart;let n=e.updatedAt>0?e.updatedAt:Math.floor(Date.now()/1e3);return Math.floor(n/n_)*n_}function o_(e){let t=new Map(e.hourly.map(e=>[e.bucketStart,e])),n=a_(e)-23*n_;return Array.from({length:r_},(e,r)=>{let i=n+r*n_;return t.get(i)??{bucketStart:i,queries:0,blocked:0}})}function s_({children:e}){return W(`div`,{class:`mt-5 flex min-h-52 items-center justify-center rounded-xl border border-slate-200 bg-slate-50 px-5 text-center`,children:W(`p`,{class:`m-0 max-w-lg text-sm font-semibold leading-6 text-slate-500`,children:e})})}function c_({data:e,error:t,loading:n,nowTimestamp:r,refreshing:i}){let a;if(n&&!e)a=W(s_,{children:`SafeShield 보호 활동을 불러오는 중입니다.`});else if(t&&!e)a=W(s_,{children:`SafeShield 보호 활동을 불러오지 못했습니다. SafeShield 페이지에서 상태를 확인해 주세요.`});else if(!e||!e.available)a=W(s_,{children:`현재 SafeShield에서 차단 통계를 제공하지 않습니다. 통계 기능을 확인해 주세요.`});else if(!e.enabled)a=W(s_,{children:`통계 수집이 꺼져 있습니다. SafeShield 페이지에서 통계를 켜면 최근 24시간 활동이 표시됩니다.`});else{let t=o_(e),n=t.reduce((e,t)=>({queries:e.queries+t.queries,blocked:e.blocked+t.blocked}),{queries:0,blocked:0});a=W(b,{children:[W(`dl`,{class:`mt-5 grid gap-3 sm:grid-cols-3`,children:[W(`div`,{class:`rounded-xl bg-slate-50 px-4 py-3`,children:[W(`dt`,{class:`text-[0.68rem] font-black uppercase tracking-[0.12em] text-slate-400`,children:`DNS 요청`}),W(`dd`,{class:`mt-1 mb-0 ml-0 text-lg font-black text-slate-950`,children:Y(n.queries)})]}),W(`div`,{class:`rounded-xl bg-slate-50 px-4 py-3`,children:[W(`dt`,{class:`text-[0.68rem] font-black uppercase tracking-[0.12em] text-slate-400`,children:`차단`}),W(`dd`,{class:`mt-1 mb-0 ml-0 text-lg font-black text-teal-700`,children:Y(n.blocked)})]}),W(`div`,{class:`rounded-xl bg-slate-50 px-4 py-3`,children:[W(`dt`,{class:`text-[0.68rem] font-black uppercase tracking-[0.12em] text-slate-400`,children:`차단율`}),W(`dd`,{class:`mt-1 mb-0 ml-0 text-lg font-black text-slate-950`,children:i_(n.queries,n.blocked)})]})]}),W(t_,{buckets:t})]})}return W(`article`,{class:`min-w-0 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5 sm:p-6`,children:[W(`div`,{class:`min-w-0`,children:[W(`p`,{class:`m-0 text-xs font-extrabold uppercase tracking-[0.16em] text-slate-500`,children:`SafeShield activity`}),W(`div`,{class:`mt-2 flex min-w-0 items-center justify-between gap-3`,children:[W(`h3`,{class:`m-0 min-w-0 text-xl font-black text-slate-950`,children:`최근 24시간 보호 활동`}),W(`a`,{class:`shrink-0 whitespace-nowrap text-xs font-extrabold text-teal-700 no-underline hover:text-teal-800`,href:`#safeshield`,children:`상세 통계 →`})]}),e?.updatedAt?W(`p`,{class:`mt-2 mb-0 text-[0.68rem] font-semibold text-slate-400`,title:Ao(e.updatedAt),children:i?`갱신 중…`:`집계: ${jo(e.updatedAt,r)}`}):null]}),a,t&&e?W(`p`,{class:`mt-3 mb-0 text-xs font-bold text-amber-700`,children:`최근 통계 갱신에 실패해 마지막 정상 데이터를 표시하고 있습니다.`}):null]})}var l_=7200;function u_(e,t=Math.floor(Date.now()/1e3)){return!e||!e.settings.checkEnabled||e.phase===`checking`||!e.lastCheckAt?!1:Math.max(0,t-e.lastCheckAt)>Math.max(e.settings.checkIntervalSeconds*2,l_)}var d_=2*Math.PI*39;function f_(e){return e===`healthy`?`bg-emerald-50 text-emerald-700 ring-emerald-200`:e===`warning`?`bg-amber-50 text-amber-800 ring-amber-200`:`bg-slate-100 text-slate-600 ring-slate-200`}function p_({detail:e,eyebrow:t,href:n,icon:r,linkLabel:i=`자세히 보기 →`,meta:a,metaState:o,metaWarning:s=!1,state:c=`neutral`,value:l}){let u=W(b,{children:[W(`div`,{class:`flex items-start justify-between gap-4`,children:[W(`p`,{class:`m-0 text-[0.68rem] font-black uppercase tracking-[0.16em] text-slate-500`,children:t}),W(`span`,{class:`grid size-10 shrink-0 place-items-center rounded-xl ring-1 ring-inset ${f_(c)}`,children:r})]}),W(`p`,{class:`mt-4 mb-0 break-words text-2xl font-black tracking-tight text-slate-950`,children:l}),W(`p`,{class:`mt-2 mb-0 min-h-10 text-sm font-medium leading-5 text-slate-500`,children:e}),a?W(`p`,{class:`mt-3 mb-0 text-xs font-bold ${o===`healthy`?`text-emerald-700`:o===`warning`||s?`text-amber-700`:`text-slate-400`}`,children:a}):null,n?W(`span`,{class:`mt-4 inline-flex text-xs font-extrabold text-teal-700`,children:i}):null]}),d=`min-w-0 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5 transition`;return n?W(`a`,{class:`${d} no-underline hover:border-slate-300 hover:shadow-md`,href:n,children:u}):W(`article`,{class:d,children:u})}var m_=6e4;function h_(e,t){return!e||!Number.isFinite(e)||e<=0?null:Math.max(0,t-e)}function g_(e,t,n,r){return!t||t<=0?`${e}: ${r}`:W(`span`,{title:Ao(t),children:[e,`: `,jo(t,n)]})}function __(e){if(!e)return null;switch(e.toLowerCase()){case`dhcp`:return`DHCP`;case`static`:return`Static`;case`pppoe`:return`PPPoE`;default:return e.toUpperCase()}}function v_(e){if(!e)return!1;let t=e.split(`.`).map(e=>Number(e));if(t.length!==4||t.some(e=>!Number.isInteger(e)||e<0||e>255))return!1;let[n,r]=t;return n===void 0||r===void 0?!1:n===10||n===172&&r>=16&&r<=31||n===192&&r===168}function y_({label:e,value:t}){return W(`div`,{class:`flex min-w-0 items-start justify-between gap-4 border-b border-slate-100 py-3 last:border-b-0`,children:[W(`dt`,{class:`text-sm font-semibold text-slate-500`,children:e}),W(`dd`,{class:`m-0 min-w-0 max-w-[68%] break-words text-right text-sm font-extrabold text-slate-950`,children:t})]})}function b_(e){return e===`initializing`?{badge:`bg-teal-100 text-teal-800`,icon:`text-teal-700`,label:`준비 중`,panel:`border-teal-200 bg-teal-50`}:e===`critical`?{badge:`bg-rose-100 text-rose-800`,icon:`text-rose-700`,label:`이상`,panel:`border-rose-200 bg-rose-50/70`}:e===`warning`?{badge:`bg-amber-100 text-amber-800`,icon:`text-amber-700`,label:`주의`,panel:`border-amber-200 bg-amber-50/70`}:e===`unknown`?{badge:`bg-slate-200 text-slate-700`,icon:`text-slate-500`,label:`확인 불가`,panel:`border-slate-200 bg-slate-50`}:{badge:`bg-emerald-100 text-emerald-800`,icon:`text-emerald-700`,label:`정상`,panel:`border-emerald-200 bg-emerald-50/70`}}function x_({data:e,error:t,loading:n,nowTimestamp:r}){let i=!!(e&&e.generatedAt>0),a=b_(i&&e?e.overall:`unknown`),o=i&&e?e.checks.filter(e=>e.status!==`ok`):[],s=o.slice(0,2),c=Math.max(0,o.length-s.length);return W(`div`,{class:`mt-5 border-t border-slate-200 pt-5`,children:[W(`div`,{class:`flex flex-wrap items-center justify-between gap-3`,children:[W(`div`,{class:`flex items-center gap-2`,children:[W(`p`,{class:`m-0 text-sm font-black text-slate-950`,children:`장치 진단`}),i?W(`span`,{class:`rounded-full px-2.5 py-1 text-[11px] font-extrabold ${a.badge}`,children:a.label}):null]}),W(`a`,{class:`text-xs font-extrabold text-teal-700 no-underline hover:text-teal-900`,href:`#settings`,children:`상세 보기 →`})]}),t&&!i?W(`div`,{class:`mt-3 rounded-xl border border-amber-200 bg-amber-50/70 px-4 py-3`,children:W(`div`,{class:`flex items-start gap-2`,children:[W(Cn,{class:`mt-0.5 size-4 shrink-0 text-amber-700`}),W(`div`,{class:`min-w-0`,children:[W(`p`,{class:`m-0 text-sm font-extrabold text-slate-900`,children:`진단 상태를 확인하지 못했습니다.`}),W(`p`,{class:`mt-1 mb-0 text-xs leading-5 text-slate-500`,children:`설정에서 장치 진단 상태를 다시 확인해 주세요.`})]})]})}):i&&e?W(`div`,{class:`mt-3 rounded-xl border px-4 py-3 ${a.panel}`,children:[W(`div`,{class:`flex min-w-0 items-start gap-3`,children:[e.overall===`ok`?W(Sn,{class:`mt-0.5 size-5 shrink-0 ${a.icon}`}):e.overall===`initializing`?W(dn,{class:`mt-0.5 size-5 shrink-0 ${a.icon}`}):W(Cn,{class:`mt-0.5 size-5 shrink-0 ${a.icon}`}),W(`div`,{class:`min-w-0 flex-1`,children:[W(`p`,{class:`m-0 text-sm font-black text-slate-950`,children:e.summary.message}),W(`p`,{class:`mt-1 mb-0 text-xs font-semibold leading-5 text-slate-500`,children:[`마지막 진단: `,jo(e.generatedAt,r),` · 검사 항목:`,` `,e.summary.total,`개`]})]})]}),s.length>0?W(`div`,{class:`mt-3 grid gap-2 sm:grid-cols-2`,children:s.map(e=>W(`div`,{class:`min-w-0 rounded-lg border border-white/80 bg-white/70 px-3 py-2`,children:[W(`p`,{class:`m-0 text-xs font-extrabold text-slate-900`,children:e.label}),W(`p`,{class:`mt-1 mb-0 line-clamp-2 text-xs leading-5 text-slate-500`,children:e.message})]},e.id))}):null,c>0?W(`p`,{class:`mt-2 mb-0 text-xs font-extrabold text-slate-600`,children:[`외 `,c,`건의 확인 항목이 있습니다.`]}):null]}):W(`div`,{class:`mt-3 rounded-xl border border-slate-200 bg-slate-50 px-4 py-3`,children:[W(`p`,{class:`m-0 text-sm font-extrabold text-slate-900`,children:n?`진단 상태를 확인하고 있습니다.`:`아직 생성된 진단 결과가 없습니다.`}),W(`p`,{class:`mt-1 mb-0 text-xs leading-5 text-slate-500`,children:`진단 결과가 생성되면 현재 장치 상태를 이곳에 요약해서 표시합니다.`})]})]})}function S_(e,t,n){if(t&&!e)return{detail:`보호 상태를 확인하고 있습니다.`,state:`neutral`,value:`확인 중`};if(n&&!e)return{detail:`SafeShield 상태를 불러오지 못했습니다.`,state:`warning`,value:`확인 필요`};if(!e||!e.available)return{detail:`SafeShield 서비스를 사용할 수 없습니다.`,state:`warning`,value:`사용 불가`};if(!e.enabled||e.status===`disabled`)return{detail:`DNS 보호가 비활성화되어 있습니다.`,state:`neutral`,value:`꺼짐`};if(e.status===`running`)return{detail:`차단 목록을 갱신하고 있습니다.`,state:`neutral`,value:`갱신 중`};if(!(e.active&&e.runtime.dnsmasqRunning&&e.runtime.dnsRuntimeOk&&e.health.overall!==`error`&&e.issueCounts.errors===0))return{detail:e.runtime.lastErrorCode?`최근 오류: ${e.runtime.lastErrorCode}`:`DNS 보호 상태를 확인해 주세요.`,state:`warning`,value:`확인 필요`};let r=e.entitlement.plan?.trim(),i=e.blocklist.validLineCount>0?`${Y(e.blocklist.validLineCount)}개 차단 규칙`:`차단 목록 적용됨`;return{detail:r?`${i} · ${r.toUpperCase()}`:i,state:`healthy`,value:`보호 중`}}function C_({accountRegistered:e,activity:t,activityError:n,activityLoading:r,data:i,devices:a,devicesError:o,devicesLoading:s,error:c,firmware:l,firmwareError:u,firmwareLoading:d,health:f,healthError:p,healthLoading:m,lan:h,lanError:g,lanLoading:_,loading:v,onRetry:y,safeshield:b,safeshieldError:x,safeshieldLoading:S,statistics:C,statisticsError:w,statisticsLoading:T,statisticsRefreshing:E,updates:D,updatesError:O,updatesLoading:k}){let A=Xe(()=>i?Io(i.runtime.memory):null,[i]),[j,M]=L(()=>Math.floor(Date.now()/1e3));if(R(()=>{let e=window.setInterval(()=>{M(Math.floor(Date.now()/1e3))},m_);return()=>window.clearInterval(e)},[]),v&&!i)return W(vs,{});if(c&&!i)return W(ys,{message:c,onRetry:y});if(!i)return null;let N=A?Math.round(A.percent):0,P=rs(i.runtime.storage),F=P?(P.availableRatio*d_).toFixed(2):`0`,I=S_(b,S,x),ee=D?.packages.find(e=>e.name===`luci-app-smartsafehub`)??D?.packages[0]??null,te=!!(D&&D.updateCount>0),ne=D?te?`${D.updateCount}개 업데이트`:D.lastCheckAt?`최신 상태`:`확인 전`:O?`확인 필요`:`확인 중`,re=ee?te&&ee.availableVersion?`${ee.installedVersion} → ${ee.availableVersion}`:`설치 버전 ${ee.installedVersion}`:O?`업데이트 상태를 불러오지 못했습니다.`:k?`업데이트 상태를 확인하고 있습니다.`:`업데이트 상태를 확인할 수 없습니다.`,ie=a?`${Y(a.totals.online)}대`:s?`확인 중`:`확인 필요`,ae=a?`Wi-Fi ${Y(a.totals.wireless)}대 · 유선/기타 ${Y(a.totals.ethernet)}대`:o||`연결 기기 요약을 불러오고 있습니다.`,oe=__(i.network.protocol),se=i.network.available?i.network.ipv4Address?`WAN ${i.network.ipv4Address}${oe?` · ${oe}`:``}`:oe?`${oe} · WAN 주소를 받지 못했습니다.`:`WAN 주소를 받지 못했습니다.`:`WAN 인터페이스를 찾을 수 없습니다.`,ce=!!h?.conflict.detected,le=!i.network.up||ce||g!==null&&!h?`warning`:h?`healthy`:`neutral`,ue=ce?`네트워크 충돌`:i.network.up?`정상 연결`:`연결 확인`,de=i.network.up?ce?`⚠ LAN 대역과 충돌합니다`:_&&!h?`네트워크 대역 확인 중`:g&&!h?`네트워크 대역을 확인하지 못했습니다.`:h?`✓ 네트워크 충돌 없음`:`네트워크 대역 확인 필요`:`WAN 연결 상태를 확인해 주세요.`,fe=!i.network.up||ce||g!==null&&!h?`warning`:h?`healthy`:`neutral`,pe=i.network.ipv4Address?v_(i.network.ipv4Address)?`${i.network.ipv4Address} · 사설 네트워크`:i.network.ipv4Address:`할당되지 않음`,me=h_(b?.timestamps.lastSuccess,j),he=b?Math.max(b.timestamps.refreshIntervalS*2,86400):null,ge=!!(b?.enabled&&b.status!==`running`&&me!==null&&he!==null&&me>he),_e=u_(D,j),ve=!!l?.current.metadataAvailable,ye=d&&!l?`확인 중`:u&&!l?`확인 필요`:ve?l?.current.releaseVersion?`SmartSafeHub ${l.current.releaseVersion}`:`SmartSafeHub 펌웨어`:`${i.software.distribution} ${i.software.version}`,be=d&&!l?`확인 중`:u&&!l?`확인 필요`:ve&&l?.current.buildId||i.software.revision,xe=ve?`빌드 ID`:`리비전`;return W(`div`,{class:`min-w-0 space-y-6`,children:[W(`section`,{"aria-labelledby":`dashboard-overview-title`,children:[W(J,{description:`인터넷, SafeShield, 연결 기기와 소프트웨어 상태를 한눈에 확인합니다.`,eyebrow:`Overview`,id:`dashboard-overview-title`,title:`시스템 개요`}),W(`div`,{class:`grid min-w-0 gap-4 sm:grid-cols-2 xl:grid-cols-4`,children:[W(p_,{detail:se,eyebrow:`Internet`,href:`#network`,icon:W(ln,{class:`size-5`}),linkLabel:ce?`해결하기 →`:`자세히 보기 →`,meta:de,metaState:fe,state:le,value:ue}),W(p_,{detail:I.detail,eyebrow:`SafeShield`,href:`#safeshield`,icon:W(qt,{class:`size-5`}),meta:g_(ge?`차단 목록 갱신 지연`:`차단 목록 갱신`,b?.timestamps.lastSuccess,j,S?`확인 중`:`기록 없음`),metaWarning:ge,state:I.state===`healthy`&&ge?`warning`:I.state,value:I.value}),W(p_,{detail:ae,eyebrow:`Connected devices`,href:`#devices`,icon:W(Yt,{class:`size-5`}),meta:g_(`목록 확인`,a?.generatedAt,j,s?`확인 중`:`기록 없음`),state:a?`healthy`:o?`warning`:`neutral`,value:ie}),W(p_,{detail:re,eyebrow:`Software update`,href:`#system`,icon:W(rn,{class:`size-5`}),meta:D&&!D.settings.checkEnabled?g_(`자동 확인 꺼짐 · 마지막 확인`,D.lastCheckAt,j,`확인 기록 없음`):g_(_e?`업데이트 확인 지연`:`마지막 확인`,D?.lastCheckAt,j,k?`확인 중`:`아직 없음`),metaWarning:_e,state:O||_e||te?`warning`:D?.lastCheckAt?`healthy`:`neutral`,value:ne})]}),e===!1?W(`div`,{class:`ssh-dashboard-account-banner mt-4 flex flex-col gap-4 rounded-2xl border p-4 sm:flex-row sm:items-center sm:justify-between sm:p-5`,role:`status`,children:[W(`div`,{class:`flex min-w-0 items-start gap-3`,children:[W(`span`,{class:`ssh-dashboard-account-banner-icon grid size-10 shrink-0 place-items-center rounded-xl`,"aria-hidden":`true`,children:W(Zt,{class:`size-5`})}),W(`div`,{class:`min-w-0`,children:[W(`div`,{class:`flex flex-wrap items-center gap-2`,children:[W(`p`,{class:`ssh-dashboard-account-banner-title m-0 font-bold`,children:`SmartSafeHub 계정을 연결해 주세요`}),W(`span`,{class:`ssh-dashboard-account-banner-badge rounded-full px-2 py-0.5 text-[0.7rem] font-bold`,children:`연결 필요`})]}),W(`p`,{class:`ssh-dashboard-account-banner-description mt-1 mb-0 text-sm leading-relaxed`,children:`계정을 연결하면 웹사이트에서 기기를 관리하고 구독 정보를 확인할 수 있습니다. DNS 보호는 연결하지 않아도 계속 작동합니다.`})]})]}),W(`a`,{href:`#account`,class:`ssh-dashboard-account-banner-action inline-flex min-h-10 shrink-0 items-center justify-center rounded-xl px-4 py-2 text-sm font-bold no-underline transition-colors focus-visible:outline-2 focus-visible:outline-offset-2`,children:`계정 연결하기 →`})]}):null]}),W(`section`,{"aria-labelledby":`dashboard-activity-title`,children:[W(J,{description:`최근 SafeShield 차단 활동과 현재 네트워크 연결 구성을 함께 확인합니다.`,eyebrow:`Activity`,id:`dashboard-activity-title`,title:`네트워크 보호 활동`}),W(`div`,{class:`grid min-w-0 gap-4 xl:grid-cols-[minmax(0,1.55fr)_minmax(18rem,0.65fr)]`,children:[W(c_,{data:C,error:w,loading:T,nowTimestamp:j,refreshing:E}),W(`article`,{class:`min-w-0 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5 sm:p-6`,children:[W(`div`,{class:`flex items-start justify-between gap-4`,children:[W(`div`,{children:[W(`p`,{class:`m-0 text-xs font-extrabold uppercase tracking-[0.16em] text-slate-500`,children:`Network`}),W(`h3`,{class:`mt-2 mb-0 text-xl font-black text-slate-950`,children:`연결 상태`})]}),W(`span`,{class:`grid size-10 shrink-0 place-items-center rounded-xl ${i.network.up&&!ce?`bg-emerald-50 text-emerald-700`:`bg-amber-50 text-amber-800`}`,children:W(ln,{class:`size-5`})})]}),W(`dl`,{class:`mt-4 mb-0`,children:[W(y_,{label:`인터넷`,value:ce?`네트워크 충돌`:i.network.up?`정상 연결`:`연결 확인`}),W(y_,{label:`WAN IP`,value:pe}),W(y_,{label:`프로토콜`,value:oe||`확인되지 않음`}),W(y_,{label:`상위 네트워크`,value:h?.wan.subnet||(_?`확인 중`:g?`확인 필요`:`확인되지 않음`)}),W(y_,{label:`LAN 네트워크`,value:h?.lan.subnet||(_?`확인 중`:g?`확인 필요`:`확인되지 않음`)}),W(y_,{label:`대역 충돌`,value:h?W(`span`,{class:h.conflict.detected?`text-amber-700`:`text-emerald-700`,children:h.conflict.detected?`충돌 감지`:`없음`}):_?`확인 중`:`확인 필요`})]}),W(`a`,{class:`mt-5 inline-flex min-h-10 w-full items-center justify-center rounded-xl border border-slate-200 bg-slate-50 px-4 py-2 text-sm font-extrabold text-slate-700 no-underline transition hover:border-teal-200 hover:bg-teal-50 hover:text-teal-800`,href:`#network`,children:`네트워크 보기`})]})]})]}),W(`section`,{"aria-labelledby":`dashboard-recent-activity-title`,children:[W(J,{action:W(`a`,{class:`inline-flex whitespace-nowrap text-xs font-extrabold text-teal-700 no-underline hover:text-teal-900`,href:`#activity`,children:`전체 보기 →`}),description:`인터넷, 보호, 업데이트와 진단에서 실제 상태가 바뀐 시점을 확인합니다.`,eyebrow:`History`,id:`dashboard-recent-activity-title`,title:`최근 활동`}),W(`article`,{class:`min-w-0 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5 sm:p-6`,children:r||n&&!t?W(Zo,{error:n,loading:r}):W(Xo,{compact:!0,events:(t?.events??[]).slice(0,3)})})]}),W(`section`,{"aria-labelledby":`dashboard-system-title`,children:[W(J,{description:`메모리, 디스크, 시스템 부하와 장치 정보를 빠르게 점검합니다.`,eyebrow:`System`,id:`dashboard-system-title`,title:`시스템 상태`}),W(`div`,{class:`grid min-w-0 gap-4 xl:grid-cols-[minmax(0,1.45fr)_minmax(20rem,0.75fr)]`,children:[W(`article`,{class:`min-w-0 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5 sm:p-6`,children:[W(`div`,{class:`flex flex-wrap items-start justify-between gap-4`,children:[W(`div`,{children:[W(`p`,{class:`m-0 text-xs font-extrabold uppercase tracking-[0.16em] text-slate-500`,children:`Runtime`}),W(`h3`,{class:`mt-2 mb-0 text-xl font-black text-slate-950`,children:`리소스 사용량`})]}),W(`span`,{class:`inline-flex items-center gap-2 rounded-full px-3 py-1.5 text-xs font-black ring-1 ring-inset ${N>=90?`bg-amber-50 text-amber-800 ring-amber-200`:`bg-emerald-50 text-emerald-700 ring-emerald-200`}`,children:[W(`span`,{class:`size-2 rounded-full ${N>=90?`bg-amber-500`:`bg-emerald-500`}`}),N>=90?`메모리 확인`:`정상 실행`]})]}),W(`div`,{class:`mt-5 grid min-w-0 grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-4`,children:[W(`div`,{class:`flex min-w-0 flex-col rounded-xl border border-slate-200 bg-slate-50 p-3 sm:p-4`,children:[W(`div`,{class:`flex items-center gap-2 text-slate-500`,children:[W(fn,{class:`size-4`}),W(`span`,{class:`text-xs font-extrabold`,children:`메모리`})]}),W(`div`,{class:`flex min-h-0 flex-1 flex-col justify-center py-1`,children:[W(`p`,{class:`mt-2 mb-0 text-xl font-black text-slate-950`,children:[N,`%`]}),W(`p`,{class:`mt-1 mb-0 text-xs font-semibold text-slate-500`,children:[A?Do(A.used):`-`,` /`,` `,Do(i.runtime.memory.total)]}),W(`div`,{"aria-label":`메모리 사용률 ${N}%`,class:`mt-3 h-1.5 overflow-hidden rounded-full bg-slate-200`,role:`progressbar`,"aria-valuemin":0,"aria-valuemax":100,"aria-valuenow":N,children:W(`div`,{class:`h-full rounded-full transition-[width] duration-300 ${N>=90?`bg-amber-500`:`bg-teal-600`}`,style:{width:`${Math.min(100,N)}%`}})})]})]}),W(`div`,{class:`ssh-dashboard-storage-card flex min-w-0 flex-col rounded-xl border border-slate-200 bg-slate-50 p-3 sm:p-4`,"data-level":P?.level??`unavailable`,children:[W(`div`,{class:`flex items-center gap-2 text-slate-500`,children:[W(gn,{class:`size-4`}),W(`span`,{class:`text-xs font-extrabold`,children:`디스크`})]}),W(`div`,{class:`mt-1 flex justify-center`,role:`img`,"aria-label":P?`디스크 여유 공간 ${P.availablePercent}%, ${P.availableText} 남음, 사용 ${P.usedText} / 전체 ${P.totalText}`:`디스크 저장 공간 확인 불가`,children:W(`div`,{class:`ssh-apps-storage-ring relative grid size-[76px] shrink-0 place-items-center`,"data-level":P?.level??`unavailable`,children:[W(`svg`,{"aria-hidden":`true`,class:`size-[76px]`,viewBox:`0 0 96 96`,children:[W(`circle`,{class:`ssh-apps-storage-track`,cx:`48`,cy:`48`,r:`39`}),P&&P.availableRatio>0?W(`circle`,{class:`ssh-apps-storage-free`,cx:`48`,cy:`48`,r:`39`,"stroke-dasharray":`${F} ${d_.toFixed(2)}`}):null]}),W(`div`,{class:`absolute inset-0 flex items-center justify-center text-center`,children:W(`strong`,{class:`text-base leading-none font-black text-slate-950`,children:P?`${P.availablePercent}%`:`—`})})]})}),W(`p`,{class:`ssh-apps-storage-available mt-1 mb-0 text-center text-xs font-extrabold text-teal-700`,children:P?`${P.availableText} 남음`:`저장 공간 확인 불가`}),P?W(`p`,{class:`mt-1 mb-0 text-center text-[11px] leading-4 font-semibold text-slate-500`,title:`앱 설치 가능 공간을 기준으로 계산하며 파일시스템 예약 공간이 사용량에 포함될 수 있습니다.`,children:[`사용 `,P.usedText,` / 전체 `,P.totalText]}):null]}),W(`div`,{class:`flex min-w-0 flex-col rounded-xl border border-slate-200 bg-slate-50 p-3 sm:p-4`,children:[W(`div`,{class:`flex items-center gap-2 text-slate-500`,children:[W(Jt,{class:`size-4`}),W(`span`,{class:`text-xs font-extrabold`,children:`시스템 부하`})]}),W(`div`,{class:`flex min-h-0 flex-1 flex-col justify-center py-1`,children:[W(`p`,{class:`mt-2 mb-0 text-xl font-black text-slate-950`,children:Oo(i.runtime.load[0])}),W(`p`,{class:`mt-1 mb-0 text-xs font-semibold text-slate-500`,children:[`5분 `,Oo(i.runtime.load[1]),` · 15분`,` `,Oo(i.runtime.load[2])]})]})]}),W(`div`,{class:`flex min-w-0 flex-col rounded-xl border border-slate-200 bg-slate-50 p-3 sm:p-4`,children:[W(`div`,{class:`flex items-center gap-2 text-slate-500`,children:[W(dn,{class:`size-4`}),W(`span`,{class:`text-xs font-extrabold`,children:`실행 시간`})]}),W(`div`,{class:`flex min-h-0 flex-1 flex-col justify-center py-1`,children:[W(`p`,{class:`mt-2 mb-0 text-xl font-black text-slate-950`,children:Eo(i.runtime.uptime)}),W(`p`,{class:`mt-1 mb-0 text-xs font-semibold text-slate-500`,children:[`부팅 `,ko(i.runtime.localtime,i.runtime.uptime)]})]})]})]}),W(x_,{data:f,error:p,loading:m,nowTimestamp:j})]}),W(`article`,{class:`min-w-0 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5 sm:p-6`,children:[W(`div`,{class:`flex items-start justify-between gap-4`,children:[W(`div`,{children:[W(`p`,{class:`m-0 text-xs font-extrabold uppercase tracking-[0.16em] text-slate-500`,children:`Device`}),W(`h3`,{class:`mt-2 mb-0 text-xl font-black text-slate-950`,children:`장치 정보`})]}),W(`span`,{class:`grid size-10 shrink-0 place-items-center rounded-xl bg-teal-50 text-teal-700`,children:W(Jt,{class:`size-5`})})]}),W(`dl`,{class:`mt-4 mb-0`,children:[W(y_,{label:`모델`,value:i.device.model}),W(y_,{label:`호스트 이름`,value:i.device.hostname}),W(y_,{label:`펌웨어`,value:W(`span`,{title:ve?l?.current.releaseVersion?`SmartSafeHub 펌웨어 ${l.current.releaseVersion}`:`SmartSafeHub 커스텀 펌웨어`:u||void 0,children:ye})}),W(y_,{label:xe,value:be}),W(y_,{label:`커널`,value:i.software.kernel}),W(y_,{label:`WAN IP`,value:i.network.ipv4Address||`할당되지 않음`})]})]})]})]})]})}var w_=Object.assign;function T_(e,t){for(var n in e)if(n!=`__source`&&e[n]!==t[n])return!0;for(var r in t)if(r!=`__source`&&!(r in e))return!0;return!1}var E_=/^(-|f[lo].*[^se]$|g.{5,}[^ps]$|z|o[pr]|(W.{5})?[lL]i.*(t|mp)$|an|(bo|s).{4}Im|sca|m.{6}[ds]|ta|c.*[st]$|wido|ini|asp|fill|st)/;function D_(e,t){function n(e){var n=this.props.ref;return n!=e.ref&&n&&(typeof n==`function`?n(null):n.current=null),t?!t(this.props,e)||n!=e.ref:T_(this.props,e)}function r(t){return this.shouldComponentUpdate=n,v(e,t)}return r.displayName=`Memo(`+(e.displayName||e.name)+`)`,r.prototype.isReactComponent=!0,r.type=e,r}function O_(e,t){var n=se(e,t);return n.containerInfo=t,n}var k_=Symbol.for(`react.element`),A_=/^(?:accent|alignment|arabic|baseline|cap|clip(?!PathU)|color|dominant|fill|flood|font|glyph(?!R)|horiz|image(?!S)|letter|lighting|marker(?!H|W|U)|overline|paint|pointer|shape|stop|strikethrough|stroke|text(?!L)|transform|underline|unicode|units|v|vector|vert|word|writing|x(?!C))[A-Z]/,j_=/[A-Z0-9]/g,M_=typeof document<`u`,N_=function(e){return/fil|che|rad/.test(e)};x.prototype.isReactComponent=!0,[`componentWillMount`,`componentWillReceiveProps`,`componentWillUpdate`].forEach(function(e){Object.defineProperty(x.prototype,e,{configurable:!0,get:function(){return this[`UNSAFE_`+e]},set:function(t){Object.defineProperty(this,e,{configurable:!0,writable:!0,value:t})}})});var P_=e.event;e.event=function(e){return P_&&(e=P_(e)),e.persist=function(){},e.isPropagationStopped=function(){return this.cancelBubble},e.isDefaultPrevented=function(){return this.defaultPrevented},e.nativeEvent=e};var F_={configurable:!0,get:function(){return this.class}},I_=e.vnode;e.vnode=function(e){if(typeof e.type==`string`)(function(e){var t=e.props,n=e.type,r={},i=n.indexOf(`-`)==-1;for(var a in t){var o=t[a];if(!(a==`value`&&`defaultValue`in t&&o==null||M_&&a==`children`&&n==`noscript`||a==`class`||a==`className`)){if(a==`style`&&o&&o.constructor==Object){var s=void 0;for(var c in o)typeof o[c]!=`number`||E_.test(c)||(s||(s=o=w_({},o)),o[c]+=`px`)}else if(a==`defaultValue`&&`value`in t&&t.value==null)a=`value`;else if(a==`download`&&!0===o)o=``;else if(a==`translate`&&o===`no`)o=!1;else if(a[0]==`o`&&a[1]==`n`){var l=a.toLowerCase();l==`ondoubleclick`?a=`ondblclick`:l!=`onchange`||n!=`input`&&n!=`textarea`||N_(t.type)?l==`onfocus`?a=`onfocusin`:l==`onblur`&&(a=`onfocusout`):l=a=`oninput`,l==`oninput`&&r[a=l]&&(a=`oninputCapture`)}else i&&A_.test(a)?a=a.replace(j_,`-$&`).toLowerCase():o===null&&(o=void 0);r[a]=o}}n==`select`&&(r.multiple&&Array.isArray(r.value)&&(r.value=k(t.children).forEach(function(e){e.props.selected=r.value.indexOf(e.props.value)!=-1})),r.defaultValue!=null&&(r.value=k(t.children).forEach(function(e){e.props.selected=r.multiple?r.defaultValue.indexOf(e.props.value)!=-1:r.defaultValue==e.props.value}))),t.class&&!t.className?(r.class=t.class,Object.defineProperty(r,"className",F_)):t.className&&(r.class=r.className=t.className),e.props=r})(e);else if(typeof e.type==`function`&&(`ref`in e.props&&`prototype`in e.type&&e.type.prototype.render&&(e.ref=e.props.ref,delete e.props.ref),e.type.defaultProps)){var t=w_({},e.props);for(var n in e.type.defaultProps)t[n]===void 0&&(t[n]=e.type.defaultProps[n]);e.props=t}e.ref&&!(`ref`in e.props)&&Object.defineProperty(e.props,"ref",{value:e.ref,configurable:!0,writable:!0}),e.$$typeof=k_,I_&&I_(e)};var L_=8,R_=256,z_=8;function B_(e){return e.findIndex(e=>!e.disabled)}function V_(e){for(let t=e.length-1;t>=0;--t)if(!e[t]?.disabled)return t;return-1}function H_(e,t,n){if(e.length===0)return-1;let r=t;for(let t=0;te.value===t&&!e.disabled);return n>=0?n:B_(e)}function W_({ariaLabel:e,className:t=``,disabled:n=!1,id:r,onChange:i,options:a,value:o,variant:s=`default`}){let c=Ze(),l=r??`ssh-custom-select-${c}`,u=`${l}-menu`,d=z(null),f=z(null),p=z(null),m=z([]),[h,g]=L(!1),[_,v]=L(()=>U_(a,o)),[y,b]=L(`bottom`),[x,S]=L(R_),[C,w]=L({}),[T,E]=L(null),D=Xe(()=>a.find(e=>e.value===o)??a[0]??null,[a,o]),O=()=>{let e=f.current;if(!e)return;let t=e.getBoundingClientRect(),n=Math.min(R_,Math.max(44,a.length*42+12)),r=window.innerHeight-t.bottom-z_,i=t.top-z_,o=rr?`top`:`bottom`,s=Math.min(t.width,Math.max(0,window.innerWidth-16)),c=Math.max(z_,Math.min(t.left,window.innerWidth-s-z_)),l=o===`bottom`?r:i;b(o),S(Math.max(44,Math.min(R_,Math.floor(l)))),w({left:`${c}px`,width:`${s}px`,...o===`bottom`?{top:`${t.bottom+L_}px`}:{bottom:`${window.innerHeight-t.top+L_}px`}})},k=(e=!1)=>{g(!1),e&&window.requestAnimationFrame(()=>f.current?.focus())},A=e=>{e<0||(v(e),window.requestAnimationFrame(()=>m.current[e]?.focus()))},j=e=>{let t=a[e];t&&!t.disabled&&(i(t.value),g(!1),window.requestAnimationFrame(()=>f.current?.focus()))},M=e=>{n||a.length===0||(O(),g(!0),A(e??U_(a,o)))};R(()=>{let e=d.current?.closest(`.ssh-app`);if(e){E(e);return}let t=d.current?.getRootNode();t instanceof ShadowRoot?E(t):typeof document<`u`&&E(document.body)},[]),R(()=>{v(U_(a,o)),m.current=m.current.slice(0,a.length)},[a,o]),R(()=>{if(!h)return;O();let e=e=>{let t=e.composedPath();d.current&&t.includes(d.current)||p.current&&t.includes(p.current)||k()},t=e=>{e.key===`Escape`&&(e.preventDefault(),k(!0))},n=()=>O();return document.addEventListener(`pointerdown`,e),document.addEventListener(`keydown`,t),window.addEventListener(`resize`,n),window.addEventListener(`scroll`,n,!0),()=>{document.removeEventListener(`pointerdown`,e),document.removeEventListener(`keydown`,t),window.removeEventListener(`resize`,n),window.removeEventListener(`scroll`,n,!0)}},[h,a.length]),R(()=>{n&&h&&k()},[n,h]);let N=s===`emphasized`?`min-h-11 border-2 border-slate-300 bg-slate-50 px-4 py-2.5 text-sm font-semibold text-slate-950 shadow-inner focus:border-teal-500 focus:bg-white focus:ring-4 focus:ring-teal-100`:`min-h-11 border border-slate-300 bg-slate-50 px-3 py-2.5 text-sm font-bold text-slate-900 focus:border-teal-500 focus:ring-2 focus:ring-teal-100`,P=h?W(`div`,{"aria-label":`${e} 목록`,class:`ssh-custom-select-menu fixed z-[1000] overflow-hidden rounded-xl border border-slate-200 bg-white p-1.5 shadow-xl shadow-slate-950/10`,id:u,ref:p,role:`listbox`,style:C,children:W(`div`,{class:`overflow-y-auto overscroll-contain`,style:{maxHeight:`${x}px`},children:a.map((e,t)=>{let n=e.value===o;return W(`button`,{"aria-selected":n,class:`flex min-h-10 w-full items-center justify-between gap-3 rounded-lg px-3 py-2.5 text-left text-sm font-bold transition ${n?`bg-teal-50 text-teal-800`:`text-slate-700 hover:bg-slate-50`} ${e.disabled?`cursor-not-allowed opacity-50`:``}`,disabled:e.disabled,onClick:()=>j(t),onFocus:()=>v(t),onKeyDown:e=>{switch(e.key){case`ArrowDown`:e.preventDefault(),A(H_(a,t,1));break;case`ArrowUp`:e.preventDefault(),A(H_(a,t,-1));break;case`Home`:e.preventDefault(),A(B_(a));break;case`End`:e.preventDefault(),A(V_(a));break;case`Enter`:case` `:e.preventDefault(),j(t);break;case`Escape`:e.preventDefault(),k(!0);break;case`Tab`:g(!1)}},ref:e=>{m.current[t]=e},role:`option`,tabIndex:_===t?0:-1,type:`button`,children:[W(`span`,{class:`min-w-0 truncate`,children:e.label}),n?W(`span`,{"aria-hidden":`true`,class:`size-2 shrink-0 rounded-full bg-teal-500`}):null]},e.value)})})}):null;return W(`div`,{class:`relative ${t}`,ref:d,children:[W(`button`,{"aria-controls":u,"aria-expanded":h,"aria-haspopup":`listbox`,"aria-label":e,class:`ssh-custom-select-trigger flex w-full cursor-pointer items-center justify-between gap-3 rounded-xl text-left outline-none transition disabled:cursor-not-allowed disabled:bg-slate-100 disabled:text-slate-400 disabled:opacity-60 ${N}`,disabled:n,id:l,onClick:()=>{h?k():(O(),g(!0))},onKeyDown:e=>{switch(e.key){case`ArrowDown`:e.preventDefault(),M(h?H_(a,_,1):U_(a,o));break;case`ArrowUp`:e.preventDefault(),M(h?H_(a,_,-1):U_(a,o));break;case`Home`:e.preventDefault(),M(B_(a));break;case`End`:e.preventDefault(),M(V_(a));break;case`Enter`:case` `:e.preventDefault(),h?k():M();break;case`Escape`:h&&(e.preventDefault(),k())}},ref:f,type:`button`,children:[W(`span`,{class:`min-w-0 truncate`,children:D?.label??`선택하세요`}),W(An,{"aria-hidden":`true`,class:`size-4 shrink-0 text-slate-400 transition-transform ${h?`rotate-180`:``}`})]}),P&&T?O_(P,T):null,W(`span`,{class:`sr-only`,"aria-live":`polite`,children:y===`top`?`선택 목록이 위쪽에 열렸습니다.`:``})]})}var G_=[{value:`skb`,label:`SK Broadband`},{value:`lgu`,label:`LG U+`}];function K_(e){return e===`lgu`?`LG U+`:`SK Broadband`}function q_({active:e,label:t,value:n}){return W(`div`,{class:`rounded-xl border border-slate-200 bg-white p-4`,children:[W(`div`,{class:`flex items-center justify-between gap-3`,children:[W(`p`,{class:`m-0 text-xs font-extrabold text-slate-500`,children:t}),W(`span`,{class:`size-2.5 shrink-0 rounded-full ${e?`bg-emerald-500`:`bg-slate-300`}`,"aria-hidden":`true`})]}),W(`p`,{class:`mt-2 mb-0 text-sm font-black text-slate-950`,children:n})]})}function J_({data:e,error:t,feedback:n,loading:r,onDismissFeedback:i,onRetry:a,onSave:o,saving:s}){let[c,l]=L(!1),[u,d]=L(`skb`);if(R(()=>{e&&(l(e.enabled),d(e.provider))},[e]),r&&!e)return W(vs,{});if(t&&!e)return W(ys,{message:t,onRetry:a});if(!e)return W(ys,{message:`IPTV 설정을 확인할 수 없습니다.`,onRetry:a});let f=c!==e.enabled||u!==e.provider,p=!e.enabled||e.running&&e.igmpSnooping;return W(`div`,{class:`space-y-5`,children:[W(`section`,{class:`rounded-2xl border border-amber-200 bg-amber-50 p-5 sm:p-6`,children:W(`div`,{class:`flex items-start gap-4`,children:[W(`span`,{class:`grid size-11 shrink-0 place-items-center rounded-xl bg-amber-100 text-amber-800`,children:W(Cn,{class:`size-5`})}),W(`div`,{class:`min-w-0 flex-1`,children:[W(`div`,{class:`flex flex-wrap items-center gap-2`,children:[W(`h2`,{class:`m-0 text-base font-black text-amber-950`,children:`IPTV 실험 기능`}),W(`span`,{class:`rounded-full border border-amber-300 bg-amber-100 px-2 py-0.5 text-[10px] font-black uppercase tracking-[0.12em] text-amber-800`,children:`Beta`})]}),W(`p`,{class:`mt-2 mb-0 text-sm leading-6 text-amber-900/80`,children:`현재 SK Broadband와 LG U+를 지원하며 두 통신사에는 동일한 멀티캐스트 IPTV 프로파일을 적용합니다. KT IPTV는 별도 네트워크 방식이 필요해 아직 지원하지 않습니다.`})]})]})}),n?W(`section`,{"aria-live":`polite`,class:`rounded-2xl border p-4 ${n.kind===`success`?`border-emerald-200 bg-emerald-50 text-emerald-900`:`border-red-200 bg-red-50 text-red-900`}`,children:W(`div`,{class:`flex items-start justify-between gap-3`,children:[W(`p`,{class:`m-0 text-sm font-bold leading-6`,children:n.message}),W(`button`,{class:`shrink-0 text-xs font-extrabold underline underline-offset-2`,onClick:i,type:`button`,children:`닫기`})]})}):null,W(J,{eyebrow:`IPTV`,title:`IPTV 설정`,description:`통신사 환경에 맞는 멀티캐스트 중계 기능을 관리합니다.`}),W(`section`,{class:`rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5 sm:p-6`,children:[W(`div`,{class:`flex min-w-0 items-start gap-3`,children:[W(`span`,{class:`grid size-11 shrink-0 place-items-center rounded-xl bg-teal-50 text-teal-700`,children:W(un,{class:`size-5`})}),W(`div`,{class:`min-w-0 flex-1`,children:[W(`div`,{class:`flex min-w-0 flex-wrap items-center justify-between gap-2`,children:[W(`h2`,{class:`m-0 text-lg font-black text-slate-950`,children:`IPTV`}),W(`div`,{class:`flex shrink-0 items-center gap-2`,children:[f?W(`span`,{"aria-live":`polite`,class:`inline-flex shrink-0 items-center gap-1.5 rounded-full border border-amber-200 bg-amber-50 px-2.5 py-1 text-xs font-extrabold text-amber-800`,role:`status`,children:[W(Cn,{"aria-hidden":`true`,class:`size-3.5 shrink-0`}),`저장되지 않음`]}):null,W(`button`,{"aria-checked":c,"aria-label":`IPTV 사용`,class:`ssh-switch-control relative inline-flex shrink-0 rounded-full border-0 transition focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-100 ${c?`bg-teal-700`:`bg-slate-300`} disabled:cursor-not-allowed disabled:opacity-60`,disabled:s||!e.available,onClick:()=>l(e=>!e),role:`switch`,type:`button`,children:W(`span`,{"aria-hidden":`true`,class:`ssh-switch-thumb absolute top-1 shadow-sm transition-[left] ${c?`left-6`:`left-1`}`})})]})]}),W(`p`,{class:`mt-1 mb-0 text-sm leading-6 text-slate-600`,children:`WAN의 멀티캐스트 방송을 LAN 셋톱박스로 전달합니다.`})]})]}),W(`div`,{class:`mt-6 grid gap-5 lg:grid-cols-[minmax(0,1fr)_minmax(0,1fr)]`,children:[W(`div`,{class:`rounded-xl border border-slate-200 bg-slate-50 p-4`,children:[W(`div`,{class:`flex flex-wrap items-start justify-between gap-2`,children:[W(`div`,{children:[W(`label`,{class:`block text-xs font-extrabold text-slate-600`,htmlFor:`iptv-provider`,children:`통신사`}),W(`p`,{class:`mt-1 mb-0 text-xs leading-5 text-slate-500`,children:`IPTV를 제공하는 회선 사업자를 선택하세요.`})]}),W(`span`,{class:`rounded-full border border-teal-200 bg-teal-50 px-2.5 py-1 text-[10px] font-black text-teal-700`,children:`멀티캐스트 방식`})]}),W(W_,{ariaLabel:`IPTV 통신사`,className:`mt-3`,disabled:s||!c,id:`iptv-provider`,onChange:e=>d(e),options:G_,value:u}),W(`div`,{class:`mt-3 flex items-start gap-2 rounded-lg border border-slate-200 bg-white p-3`,children:[W(Sn,{"aria-hidden":`true`,class:`mt-0.5 size-4 shrink-0 text-teal-700`}),W(`p`,{class:`m-0 text-xs leading-5 text-slate-600`,children:[K_(u),`에는 현재 SK Broadband/LG U+ 공통 IGMP Proxy 프로파일을 적용합니다. KT는 추후 별도 IPTV 프로파일로 지원할 예정입니다.`]})]})]}),W(`div`,{class:`rounded-xl border border-slate-200 bg-slate-50 p-4`,children:[W(`div`,{class:`flex items-center justify-between gap-3`,children:[W(`p`,{class:`m-0 text-xs font-extrabold text-slate-500`,children:`현재 지원 프로파일`}),W(`span`,{class:`text-xs font-extrabold text-slate-500`,children:`SKB · LG U+`})]}),W(`p`,{class:`mt-2 mb-0 text-sm font-black text-slate-950`,children:`WAN → IGMP Proxy → LAN`}),W(`p`,{class:`mt-1 mb-0 text-xs leading-5 text-slate-600`,children:`WAN을 upstream, LAN을 downstream으로 사용하고 LAN bridge에는 IGMP snooping을 함께 활성화해 필요하지 않은 포트와 Wi-Fi로 멀티캐스트가 불필요하게 퍼지는 것을 줄입니다.`})]})]}),e.available?null:W(`div`,{class:`mt-5 rounded-xl border border-red-200 bg-red-50 p-4 text-sm font-bold leading-6 text-red-900`,children:`IGMP Proxy 패키지를 찾지 못했습니다. 현재 펌웨어에 igmpproxy 패키지가 포함되어 있는지 확인해 주세요.`}),W(`div`,{class:`mt-6 flex flex-wrap items-center justify-between gap-3 border-t border-slate-200 pt-5`,children:[W(`p`,{class:`m-0 text-xs leading-5 text-slate-500`,children:`설정을 적용하면 네트워크 서비스가 잠시 다시 로드될 수 있습니다.`}),W(`button`,{class:`inline-flex min-h-11 items-center justify-center rounded-xl border-0 bg-teal-700 px-5 py-2.5 text-sm font-black text-white shadow-sm transition hover:bg-teal-800 focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-100 disabled:cursor-not-allowed disabled:opacity-50`,disabled:s||!e.available||!f&&!c,onClick:()=>void o({enabled:c,provider:u}),type:`button`,children:s?`적용 중…`:f?`변경사항 적용`:`설정 다시 적용`})]})]}),W(J,{eyebrow:`Runtime`,title:`IPTV 동작 상태`,description:`마지막으로 적용한 IPTV 설정과 중계 서비스의 실행 상태를 확인합니다.`}),W(`section`,{class:`rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5 sm:p-6`,children:[W(`div`,{class:`flex flex-wrap items-center justify-between gap-3`,children:[W(`p`,{class:`m-0 text-xs leading-5 text-slate-500`,children:`적용된 IPTV 중계 서비스와 멀티캐스트 전달 상태입니다.`}),W(`span`,{class:`inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-black ${p?`bg-emerald-100 text-emerald-800`:`bg-amber-100 text-amber-800`}`,children:[W(Sn,{class:`size-4`}),p?e.enabled?`정상`:`꺼짐`:`확인 필요`]})]}),f?W(`div`,{class:`mt-4 flex items-start gap-2 rounded-xl border border-amber-200 bg-amber-50 p-3 text-xs font-bold leading-5 text-amber-900`,children:[W(Cn,{"aria-hidden":`true`,class:`mt-0.5 size-4 shrink-0`}),`아래 동작 상태는 마지막으로 적용된 설정 기준입니다. 변경사항을 적용하면 새 설정으로 갱신됩니다.`]}):null,W(`div`,{class:`mt-5 grid gap-3 sm:grid-cols-2 lg:grid-cols-4`,children:[W(q_,{active:e.enabled&&e.running,label:`IGMP Proxy`,value:e.enabled?e.running?`동작 중`:`중지됨`:`꺼짐`}),W(q_,{active:e.enabled&&e.igmpSnooping,label:`IGMP Snooping`,value:e.igmpSnooping?`켜짐`:`꺼짐`}),W(q_,{active:e.enabled,label:`Upstream`,value:e.upstreamNetwork.toUpperCase()}),W(q_,{active:e.enabled,label:`Downstream`,value:e.lanBridge??e.downstreamNetwork.toUpperCase()})]})]})]})}function Y_(e){return e?`disabled:cursor-wait`:`disabled:cursor-not-allowed`}function X_(e){let t=e.split(`.`);return[t[0]??``,t[1]??``,t[2]??``,t[3]??``]}function Z_(e){return e.replace(/\D/g,``).slice(0,3)}function Q_(e){return e.every(e=>e===``)?``:e.join(`.`)}function $_({disabled:e=!1,label:t,value:n,onChange:r}){let i=X_(n),a=(e,t)=>{let n=[...i];n[e]=Z_(t),r(Q_(n))};return W(`div`,{class:`ssh-ipv4-segments mt-2 min-h-11`,"aria-label":`${t} 입력`,children:i.map((n,r)=>W(`div`,{class:`ssh-ipv4-segment`,children:[W(`input`,{"aria-label":`${t} ${r+1}번째 옥텟`,autocomplete:`off`,class:`min-h-11 w-full min-w-0 max-w-full rounded-xl border-2 border-slate-300 bg-slate-50 px-1.5 py-2.5 text-center text-sm font-semibold text-slate-950 shadow-inner outline-none transition focus:border-teal-500 focus:bg-white focus:ring-4 focus:ring-teal-100 disabled:cursor-not-allowed disabled:bg-slate-100 sm:px-2`,disabled:e,inputMode:`numeric`,maxLength:3,onInput:e=>a(r,e.currentTarget.value),pattern:`[0-9]*`,spellcheck:!1,value:n}),r{if(!/^(0|[1-9][0-9]{0,2})$/.test(e))return!1;let t=Number(e);return t>=0&&t<=255})}function rv(e){let t=X_(e);return t.slice(0,3).some(e=>e===``||Number(e)>255)?null:t.slice(0,3).join(`.`)}function iv(e){return X_(e)[3]}function av(e){let t=rv(e.lan.address),n=iv(e.dhcp.start??``),r=iv(e.dhcp.end??``);return{ipAddress:e.lan.address,prefixLength:e.lan.prefixLength,dhcpEnabled:e.dhcp.enabled,dhcpStart:t&&n?`${t}.${n}`:e.dhcp.start??``,dhcpEnd:t&&r?`${t}.${r}`:e.dhcp.end??``,leaseTime:e.dhcp.leaseTime}}function ov({disabled:e,label:t,lanAddress:n,value:r,onChange:i}){let a=rv(n),o=a?.split(`.`)??[``,``,``],s=iv(r),c=e=>{let t=Z_(e);i(a&&t!==``?`${a}.${t}`:t)};return W(`label`,{class:`block`,children:[W(`span`,{class:`text-sm font-extrabold text-slate-800`,children:t}),W(`div`,{class:`ssh-ipv4-segments mt-2 min-h-11`,"aria-label":`${t} 입력`,children:[o.map((e,t)=>W(`div`,{class:`ssh-ipv4-segment`,children:[W(`span`,{class:`grid min-h-11 w-full min-w-0 max-w-full place-items-center rounded-xl border border-slate-200 bg-slate-100 px-1.5 py-2.5 text-center text-sm font-bold text-slate-500 sm:px-2`,children:e||`—`}),W(`span`,{"aria-hidden":`true`,class:`ssh-ipv4-separator text-sm font-black text-slate-400`,children:`.`})]},t)),W(`div`,{class:`ssh-ipv4-segment`,children:W(`input`,{"aria-label":`${t} 마지막 옥텟`,autocomplete:`off`,class:`min-h-11 w-full min-w-0 max-w-full rounded-xl border-2 border-slate-300 bg-slate-50 px-1.5 py-2.5 text-center text-sm font-semibold text-slate-950 shadow-inner outline-none transition focus:border-teal-500 focus:bg-white focus:ring-4 focus:ring-teal-100 disabled:cursor-not-allowed disabled:bg-slate-100 sm:px-2`,disabled:e||!a,inputMode:`numeric`,maxLength:3,onInput:e=>c(e.currentTarget.value),pattern:`[0-9]*`,spellcheck:!1,value:s})})]}),W(`span`,{class:`mt-2 block text-xs text-slate-500`,children:`앞 3개 주소는 공유기 IP 주소와 동일하게 유지됩니다.`})]})}function sv(e){return`${window.location.protocol}//${e}${window.location.pathname}${window.location.search}${window.location.hash}`}function cv({feedback:e,onDismiss:t}){return W(`div`,{"aria-live":`polite`,class:`mb-5 rounded-2xl border px-4 py-4 text-sm ${e.kind===`success`?`border-emerald-200 bg-emerald-50 text-emerald-900`:e.kind===`warning`?`border-amber-200 bg-amber-50 text-amber-950`:`border-red-200 bg-red-50 text-red-900`}`,children:[W(`div`,{class:`flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between`,children:[W(`p`,{class:`m-0 font-bold leading-6`,children:e.message}),W(`button`,{class:`min-h-10 shrink-0 rounded-lg border border-current bg-transparent px-3 py-1.5 text-xs font-extrabold`,onClick:t,type:`button`,children:`닫기`})]}),e.newAddress?W(`a`,{class:`mt-3 inline-flex min-h-10 items-center rounded-lg border border-current px-3 py-2 font-extrabold underline underline-offset-2`,href:sv(e.newAddress),children:[`새 공유기 주소 `,e.newAddress,`로 연결`]}):null]})}function lv({data:e}){let t=e.conflict.detected;return W(`section`,{class:`grid gap-4 lg:grid-cols-3`,"aria-label":`내부 네트워크 상태`,children:[W(`article`,{class:`rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5`,children:[W(`span`,{class:`grid size-10 place-items-center rounded-xl bg-teal-50 text-teal-700`,children:W(Jt,{class:`size-5`})}),W(`p`,{class:`mt-4 mb-0 text-xs font-extrabold uppercase tracking-[0.14em] text-slate-500`,children:`SmartSafeHub LAN`}),W(`p`,{class:`mt-2 mb-0 text-xl font-black text-slate-950`,children:e.lan.subnet}),W(`p`,{class:`mt-1 mb-0 text-sm font-semibold text-slate-600`,children:[`공유기 주소 `,e.lan.address]})]}),W(`article`,{class:`rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5`,children:[W(`span`,{class:`grid size-10 place-items-center rounded-xl bg-slate-100 text-slate-700`,children:W(ln,{class:`size-5`})}),W(`p`,{class:`mt-4 mb-0 text-xs font-extrabold uppercase tracking-[0.14em] text-slate-500`,children:`상위 네트워크`}),W(`p`,{class:`mt-2 mb-0 text-xl font-black text-slate-950`,children:e.wan.subnet??`확인 전`}),W(`p`,{class:`mt-1 mb-0 text-sm font-semibold text-slate-600`,children:e.wan.connected?`WAN ${e.wan.address??`주소 확인 중`}`:`WAN 연결 후 자동으로 확인합니다.`})]}),W(`article`,{class:`rounded-2xl border p-5 shadow-sm shadow-slate-900/5 ${t?`border-red-200 bg-red-50 text-red-950`:`border-emerald-200 bg-emerald-50 text-emerald-950`}`,children:[W(`span`,{class:`grid size-10 place-items-center rounded-xl ${t?`bg-red-100 text-red-700`:`bg-emerald-100 text-emerald-700`}`,children:W(t?Cn:Sn,{class:`size-5`})}),W(`p`,{class:`mt-4 mb-0 text-xs font-extrabold uppercase tracking-[0.14em] opacity-70`,children:`대역 충돌`}),W(`p`,{class:`mt-2 mb-0 text-lg font-black`,children:t?`주소 대역이 겹칩니다`:`충돌이 감지되지 않았습니다`}),W(`p`,{class:`mt-1 mb-0 text-sm font-semibold leading-6 opacity-80`,children:t?`상위 네트워크 ${e.conflict.wanSubnet??``}와 다른 LAN 대역이 필요합니다.`:e.wan.connected?`현재 WAN과 LAN이 서로 다른 IPv4 대역을 사용합니다.`:`WAN이 연결되면 상위 네트워크와의 충돌 여부를 확인합니다.`})]})]})}function uv({action:e,data:t,error:n,feedback:r,loading:i,onApplyRecommendation:a,onDismissFeedback:o,onRetry:s,onSave:c}){let[l,u]=L(``),[d,f]=L(24),[p,m]=L(!0),[h,g]=L(``),[_,v]=L(``),[y,x]=L(`12h`),[S,C]=L(!1),[w,T]=L(null),E=(e={})=>{if(!t)return!1;let n=av(t);return(e.ipAddress??l).trim()!==n.ipAddress||(e.prefixLength??d)!==n.prefixLength||(e.dhcpEnabled??p)!==n.dhcpEnabled||(e.dhcpStart??h).trim()!==n.dhcpStart||(e.dhcpEnd??_).trim()!==n.dhcpEnd||(e.leaseTime??y)!==n.leaseTime},D=e=>{let t=rv(e);if(u(e),!t){C(E({ipAddress:e}));return}let n=iv(h),r=iv(_),i=n?`${t}.${n}`:``,a=r?`${t}.${r}`:``;g(i),v(a),C(E({ipAddress:e,dhcpStart:i,dhcpEnd:a}))},O=e=>{g(e),C(E({dhcpStart:e}))},k=e=>{v(e),C(E({dhcpEnd:e}))},A=e=>{f(e),C(E({prefixLength:e}))},j=e=>{x(e),C(E({leaseTime:e}))},M=e=>{m(e),C(E({dhcpEnabled:e}))};R(()=>{if(!t||S)return;let e=av(t);u(e.ipAddress),f(e.prefixLength),m(e.dhcpEnabled),g(e.dhcpStart),v(e.dhcpEnd),x(e.leaseTime),T(null)},[t,S]);let N=Xe(()=>{let e=[...ev];return e.includes(d)||(e.push(d),e.sort((e,t)=>e-t)),e},[d]),P=Xe(()=>{let e=[...tv];return e.some(([e])=>e===y)||e.push([y,`현재 설정 (${y})`]),e},[y]);if(i)return W(vs,{});if(n)return W(ys,{message:n,onRetry:s});if(!t)return null;let F=e!==null,I=async e=>{e.preventDefault(),nv(l)?!nv(h)||!nv(_)?T(`DHCP 시작/종료 주소를 올바른 IPv4 형식으로 입력해 주세요.`):window.confirm(`내부 네트워크 설정을 적용하면 연결된 기기가 새 DHCP 정보를 받는 동안 잠시 연결이 끊길 수 있습니다. 계속하시겠습니까?`)&&(T(null),await c({ipAddress:l.trim(),prefixLength:d,dhcpEnabled:p,dhcpStart:h.trim(),dhcpEnd:_.trim(),leaseTime:y})&&C(!1)):T(`공유기 주소를 올바른 IPv4 형식으로 입력해 주세요.`)},ee=async()=>{t.recommendation&&window.confirm(`상위 네트워크와 겹치지 않는 ${t.recommendation.subnet} 대역으로 자동 변경합니다. 현재 연결이 잠시 끊길 수 있습니다. 계속하시겠습니까?`)&&(T(null),await a()&&C(!1))};return W(b,{children:[r?W(cv,{feedback:r,onDismiss:o}):null,W(J,{eyebrow:`Overview`,title:`내부 네트워크 상태`,description:`LAN 주소와 DHCP 할당 상태를 확인합니다.`}),W(lv,{data:t}),t.conflict.detected?W(`section`,{class:`mt-5 rounded-2xl border border-amber-200 bg-amber-50 p-5 text-amber-950 sm:p-6`,children:W(`div`,{class:`flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between`,children:[W(`div`,{class:`min-w-0`,children:[W(`p`,{class:`m-0 text-xs font-extrabold uppercase tracking-[0.16em] text-amber-700`,children:`자동 충돌 해결`}),W(`h2`,{class:`mt-2 mb-0 text-lg font-black`,children:`안전한 LAN 대역으로 변경할 수 있습니다`}),W(`p`,{class:`mt-2 mb-0 text-sm font-semibold leading-6 text-amber-900/80`,children:t.recommendation?`${t.recommendation.subnet} · DHCP ${t.recommendation.dhcpStart} ~ ${t.recommendation.dhcpEnd}`:`현재 활성 네트워크와 겹치지 않는 추천 대역을 찾지 못했습니다. 아래에서 직접 설정해 주세요.`})]}),t.recommendation?W(`button`,{class:`inline-flex min-h-11 shrink-0 items-center justify-center gap-2 rounded-xl border-0 bg-amber-700 px-5 py-2.5 text-sm font-extrabold text-white transition hover:bg-amber-800 focus:outline-none focus-visible:ring-4 focus-visible:ring-amber-200 disabled:cursor-wait disabled:opacity-60`,disabled:F,onClick:()=>void ee(),type:`button`,children:[W(Sn,{class:`size-5 ${e===`auto`?`animate-pulse`:``}`}),e===`auto`?`변경 중`:`추천 대역으로 자동 변경`]}):null]})}):null,W(`div`,{class:`mt-7`,children:W(J,{eyebrow:`Local network`,title:`LAN 및 DHCP 설정`,description:`상위 공유기와 다른 주소 대역을 사용해야 안정적으로 라우팅할 수 있습니다.`})}),W(`form`,{class:`rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5 sm:p-6`,onSubmit:e=>void I(e),children:[S?W(`div`,{class:`mb-5 flex justify-end`,children:W(`span`,{"aria-live":`polite`,class:`inline-flex shrink-0 self-start items-center gap-1.5 rounded-full border border-amber-200 bg-amber-50 px-2.5 py-1 text-xs font-extrabold text-amber-800`,role:`status`,children:[W(Cn,{"aria-hidden":`true`,class:`size-3.5 shrink-0`}),`저장되지 않음`]})}):null,W(`div`,{class:`grid gap-5 lg:grid-cols-2`,children:[W(`div`,{class:`block`,children:[W(`span`,{class:`text-sm font-extrabold text-slate-800`,children:`공유기 IP 주소`}),W($_,{disabled:F,label:`공유기 IP 주소`,onChange:D,value:l})]}),W(`div`,{class:`block`,children:[W(`span`,{class:`text-sm font-extrabold text-slate-800`,children:`현재 LAN 대역`}),W(`div`,{class:`mt-2 min-h-11 rounded-xl border border-slate-200 bg-slate-50 px-4 py-3 text-sm font-bold text-slate-700`,children:t.lan.subnet}),W(`span`,{class:`mt-2 block text-xs text-slate-500`,children:`서브넷 마스크는 아래 고급 설정에서 변경할 수 있습니다.`})]}),W(ov,{disabled:F,label:`DHCP 시작 주소`,lanAddress:l,onChange:O,value:h}),W(ov,{disabled:F,label:`DHCP 종료 주소`,lanAddress:l,onChange:k,value:_})]}),W(`details`,{class:`mt-6 rounded-xl border border-slate-200 bg-slate-50 p-4`,children:[W(`summary`,{class:`cursor-pointer text-sm font-black text-slate-900`,children:`고급 DHCP 설정`}),W(`div`,{class:`mt-5 grid gap-5 lg:grid-cols-2`,children:[W(`div`,{class:`block`,children:[W(`span`,{class:`text-sm font-extrabold text-slate-800`,children:`서브넷 마스크`}),W(W_,{ariaLabel:`서브넷 마스크`,className:`mt-2`,disabled:F,id:`lan-prefix-length`,onChange:e=>A(Number(e)),options:N.map(e=>({value:String(e),label:`/${e} · ${e===t.lan.prefixLength?t.lan.netmask:`CIDR /${e}`}`})),value:String(d),variant:`emphasized`}),W(`span`,{class:`mt-2 block text-xs text-slate-500`,children:`일반 가정용 네트워크는 /24 사용을 권장합니다.`})]}),W(`div`,{class:`block`,children:[W(`span`,{class:`text-sm font-extrabold text-slate-800`,children:`DHCP 임대 시간`}),W(W_,{ariaLabel:`DHCP 임대 시간`,className:`mt-2`,disabled:F,id:`lan-dhcp-lease-time`,onChange:j,options:P.map(([e,t])=>({value:e,label:t})),value:y,variant:`emphasized`})]}),W(`label`,{class:`inline-flex min-h-11 items-center gap-3 text-sm font-extrabold text-slate-800 lg:col-span-2`,children:[W(`input`,{checked:p,class:`size-5 accent-teal-700`,disabled:F,onChange:e=>M(e.currentTarget.checked),type:`checkbox`}),`SmartSafeHub DHCP 서버 사용`]}),W(`p`,{class:`m-0 text-xs font-semibold leading-5 text-slate-500 lg:col-span-2`,children:`DHCP를 끄면 SmartSafeHub에 연결되는 장치에 IP 주소를 자동으로 할당하지 않습니다. 별도의 DHCP 서버를 사용하는 고급 구성에서만 비활성화하세요.`})]})]}),w?W(`p`,{class:`mt-5 mb-0 rounded-xl bg-red-50 px-4 py-3 text-sm font-bold text-red-800`,children:w}):null,W(`div`,{class:`mt-6 flex flex-col gap-3 border-t border-slate-200 pt-5 sm:flex-row sm:items-center sm:justify-between`,children:[W(`p`,{class:`m-0 text-xs font-semibold leading-5 text-slate-500`,children:`주소가 바뀌면 현재 관리 페이지 연결이 종료되며 새 IP를 받아 다시 접속해야 합니다.`}),W(`button`,{class:`inline-flex min-h-11 w-full shrink-0 items-center justify-center gap-2 rounded-xl border-0 bg-teal-700 px-5 py-2.5 text-sm font-extrabold text-white shadow-sm transition hover:bg-teal-800 focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-200 ${Y_(F)} disabled:opacity-60 sm:w-auto`,disabled:F||!S,type:`submit`,children:[W(Sn,{class:`size-5 ${e===`saving`?`animate-pulse`:``}`}),e===`saving`?`저장 중`:`설정 저장`]})]})]})]})}var dv=[`dhcp`,`pppoe`,`static`],fv=`1.1.1.1`,pv=Array.from({length:33},(e,t)=>t);function mv(e){return dv.includes(e)}function hv(e){switch(e){case`dhcp`:return`자동 IP (DHCP)`;case`pppoe`:return`PPPoE`;case`static`:return`고정 IPv4`;default:return e?e.toUpperCase():`확인 전`}}function gv(e){return{protocol:e.configuration.protocol,pppoeUsername:e.configuration.pppoe.username,pppoePassword:``,staticAddress:e.configuration.static.address??``,staticPrefixLength:e.configuration.static.prefixLength??24,staticGateway:e.configuration.static.gateway??``,dnsPrimary:e.configuration.static.dns[0]??``,dnsSecondary:e.configuration.static.dns[1]??``}}function _v(e){return e.trim()}function vv(e,t){let n=gv(t);return e.protocol===n.protocol?e.protocol===`dhcp`||!mv(e.protocol)?!1:e.protocol===`pppoe`?_v(e.pppoeUsername)!==n.pppoeUsername||e.pppoePassword.length>0:_v(e.staticAddress)!==n.staticAddress||e.staticPrefixLength!==n.staticPrefixLength||_v(e.staticGateway)!==n.staticGateway||_v(e.dnsPrimary)!==n.dnsPrimary||_v(e.dnsSecondary)!==n.dnsSecondary:!0}function yv(e){let t=e.trim().split(`.`);return t.length===4&&t.every(e=>{if(!/^(0|[1-9][0-9]{0,2})$/.test(e))return!1;let t=Number(e);return t>=0&&t<=255})}function bv(e){let t=e;return Array.from({length:4},()=>{if(t>=8)return t-=8,255;if(t<=0)return 0;let e=256-2**(8-t);return t=0,e}).join(`.`)}function xv(e){if(!Number.isFinite(e)||e<=0)return`연결 시간 확인 전`;let t=Math.floor(e/86400),n=Math.floor(e%86400/3600),r=Math.floor(e%3600/60);return t>0?`${t}일 ${n}시간 연결됨`:n>0?`${n}시간 ${r}분 연결됨`:`${Math.max(1,r)}분 연결됨`}function Sv({feedback:e,onDismiss:t}){return W(`div`,{"aria-live":`polite`,class:`mb-5 rounded-2xl border px-4 py-4 text-sm ${e.kind===`success`?`border-emerald-200 bg-emerald-50 text-emerald-900`:e.kind===`warning`?`border-amber-200 bg-amber-50 text-amber-950`:`border-red-200 bg-red-50 text-red-900`}`,children:W(`div`,{class:`flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between`,children:[W(`p`,{class:`m-0 font-bold leading-6`,children:e.message}),W(`button`,{class:`min-h-10 shrink-0 rounded-lg border border-current bg-transparent px-3 py-1.5 text-xs font-extrabold`,onClick:t,type:`button`,children:`닫기`})]})})}function Cv({data:e,busy:t,onReconnect:n}){let r=e.status,i=r.connected;return W(`section`,{class:`grid gap-4 lg:grid-cols-3`,"aria-label":`인터넷 연결 상태`,children:[W(`article`,{class:`rounded-2xl border p-5 shadow-sm shadow-slate-900/5 ${i?`border-emerald-200 bg-emerald-50 text-emerald-950`:`border-amber-200 bg-amber-50 text-amber-950`}`,children:[W(`div`,{class:`flex items-start justify-between gap-3`,children:[W(`span`,{class:`grid size-10 place-items-center rounded-xl ${i?`bg-emerald-100 text-emerald-700`:`bg-amber-100 text-amber-700`}`,children:W(i?Sn:Cn,{class:`size-5`})}),W(`button`,{class:`inline-flex min-h-10 items-center justify-center gap-2 rounded-xl border border-current bg-transparent px-3 py-2 text-xs font-extrabold transition disabled:cursor-wait disabled:opacity-60`,disabled:t,onClick:n,type:`button`,children:[W(pn,{class:`size-4 ${t?`animate-spin`:``}`}),`재연결`]})]}),W(`p`,{class:`mt-4 mb-0 text-xs font-extrabold uppercase tracking-[0.14em] opacity-70`,children:`인터넷 상태`}),W(`p`,{class:`mt-2 mb-0 text-xl font-black`,children:r.pending?`연결 중`:i?`연결됨`:`연결 안 됨`}),W(`p`,{class:`mt-1 mb-0 text-sm font-semibold leading-6 opacity-80`,children:[hv(r.protocol??e.configuration.protocol),` · `,xv(r.uptimeSeconds)]})]}),W(`article`,{class:`rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5`,children:[W(`span`,{class:`grid size-10 place-items-center rounded-xl bg-teal-50 text-teal-700`,children:W(ln,{class:`size-5`})}),W(`p`,{class:`mt-4 mb-0 text-xs font-extrabold uppercase tracking-[0.14em] text-slate-500`,children:`WAN IPv4`}),W(`p`,{class:`mt-2 mb-0 break-all text-xl font-black text-slate-950`,children:r.address?`${r.address}${r.prefixLength===null?``:`/${r.prefixLength}`}`:`주소 없음`}),W(`p`,{class:`mt-1 mb-0 text-sm font-semibold text-slate-600`,children:r.device?`인터페이스 ${r.device}`:`인터페이스 확인 전`})]}),W(`article`,{class:`rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5`,children:[W(`span`,{class:`grid size-10 place-items-center rounded-xl bg-slate-100 text-slate-700`,children:W(Jt,{class:`size-5`})}),W(`p`,{class:`mt-4 mb-0 text-xs font-extrabold uppercase tracking-[0.14em] text-slate-500`,children:`Gateway · DNS`}),W(`p`,{class:`mt-2 mb-0 break-all text-base font-black text-slate-950`,children:r.gateway??`Gateway 확인 전`}),W(`p`,{class:`mt-1 mb-0 break-all text-sm font-semibold leading-6 text-slate-600`,children:r.dns.length>0?r.dns.join(` · `):`DNS 확인 전`})]})]})}function wv({action:e,data:t,error:n,feedback:r,loading:i,onDismissFeedback:a,onReconnect:o,onRetry:s,onSave:c}){let[l,u]=L({protocol:`dhcp`,pppoeUsername:``,pppoePassword:``,staticAddress:``,staticPrefixLength:24,staticGateway:``,dnsPrimary:``,dnsSecondary:``}),[d,f]=L(!1),[p,m]=L(!1),[h,g]=L(null);R(()=>{t&&!d&&(u(gv(t)),g(null))},[t,d]);let _=Xe(()=>{let e=[{value:`dhcp`,label:`자동 IP (DHCP)`},{value:`pppoe`,label:`PPPoE`},{value:`static`,label:`고정 IPv4`}];return t&&!t.configuration.supported&&e.unshift({value:t.configuration.protocol,label:`현재 설정 (${t.configuration.protocol})`}),e},[t]);if(i)return W(vs,{});if(n)return W(ys,{message:n,onRetry:s});if(!t)return null;let v=e!==null,y=e=>{u(n=>{let r={...n,...e};return f(vv(r,t)),r}),g(null)},x=async e=>{if(e.preventDefault(),!mv(l.protocol))g(`DHCP, PPPoE 또는 고정 IPv4 중 하나를 선택해 주세요.`);else{if(l.protocol===`pppoe`){if(!_v(l.pppoeUsername)){g(`PPPoE 사용자명을 입력해 주세요.`);return}if((t.configuration.protocol!==`pppoe`||!t.configuration.pppoe.passwordConfigured)&&l.pppoePassword.length===0){g(`PPPoE 연결을 시작하려면 비밀번호를 입력해 주세요.`);return}}if(l.protocol===`static`){if(!yv(l.staticAddress)){g(`고정 IPv4 주소를 올바르게 입력해 주세요.`);return}if(!yv(l.staticGateway)){g(`기본 게이트웨이를 올바르게 입력해 주세요.`);return}if(!yv(l.dnsPrimary)){g(`기본 DNS 서버를 올바르게 입력해 주세요.`);return}if(_v(l.dnsSecondary)&&!yv(l.dnsSecondary)){g(`보조 DNS 서버를 올바르게 입력해 주세요.`);return}}window.confirm(`인터넷 연결 설정을 적용하면 WAN 연결이 다시 시작되어 인터넷이 잠시 끊길 수 있습니다. 계속하시겠습니까?`)&&await c({protocol:l.protocol,pppoeUsername:_v(l.pppoeUsername),pppoePassword:l.pppoePassword,pppoePasswordChanged:l.pppoePassword.length>0,staticAddress:_v(l.staticAddress),staticPrefixLength:l.staticPrefixLength,staticGateway:_v(l.staticGateway),dnsPrimary:_v(l.dnsPrimary),dnsSecondary:_v(l.dnsSecondary)})&&(u(e=>({...e,pppoePassword:``})),f(!1))}};return W(b,{children:[r?W(Sv,{feedback:r,onDismiss:a}):null,W(J,{eyebrow:`Overview`,title:`인터넷 연결 상태`,description:`WAN 연결 상태와 네트워크 정보를 확인합니다.`}),W(Cv,{data:t,busy:v,onReconnect:()=>{window.confirm(`WAN 연결을 다시 시작합니다. 인터넷이 잠시 끊길 수 있습니다. 계속하시겠습니까?`)&&o()}}),t.configuration.supported?null:W(`section`,{class:`mt-5 rounded-2xl border border-amber-200 bg-amber-50 p-5 text-amber-950 sm:p-6`,children:W(`div`,{class:`flex items-start gap-3`,children:[W(Cn,{class:`mt-0.5 size-5 shrink-0 text-amber-700`}),W(`div`,{children:[W(`h2`,{class:`m-0 text-base font-black`,children:`현재 WAN 방식은 직접 관리하지 않습니다`}),W(`p`,{class:`mt-2 mb-0 text-sm font-semibold leading-6 text-amber-900/80`,children:[`현재 설정은 `,t.configuration.protocol,`입니다. 아래에서 DHCP, PPPoE 또는 고정 IPv4를 선택해 저장하면 해당 방식으로 전환합니다.`]})]})]})}),W(`div`,{class:`mt-7`,children:W(J,{eyebrow:`Internet`,title:`인터넷 연결 설정`,description:`통신사 또는 상위 네트워크 환경에 맞는 WAN 연결 방식을 선택합니다.`})}),W(`form`,{class:`rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5 sm:p-6`,onSubmit:e=>void x(e),children:[d?W(`div`,{class:`mb-5 flex justify-end`,children:W(`span`,{"aria-live":`polite`,class:`inline-flex shrink-0 self-start items-center gap-1.5 rounded-full border border-amber-200 bg-amber-50 px-2.5 py-1 text-xs font-extrabold text-amber-800`,role:`status`,children:[W(Cn,{"aria-hidden":`true`,class:`size-3.5 shrink-0`}),`저장되지 않음`]})}):null,W(`div`,{children:[W(`span`,{class:`text-sm font-extrabold text-slate-800`,children:`연결 방식`}),W(W_,{ariaLabel:`WAN 연결 방식`,className:`mt-2 max-w-xl`,disabled:v,id:`wan-protocol`,onChange:e=>y(e===`static`&&!_v(l.dnsSecondary)?{protocol:e,dnsSecondary:fv}:{protocol:e}),options:_,value:l.protocol,variant:`emphasized`}),W(`span`,{class:`mt-2 block text-xs font-semibold leading-5 text-slate-500`,children:`대부분의 가정용 인터넷과 상위 공유기 연결은 자동 IP(DHCP)를 사용합니다.`})]}),l.protocol===`pppoe`?W(`div`,{class:`mt-6 grid gap-5 lg:grid-cols-2`,children:[W(`label`,{class:`block`,children:[W(`span`,{class:`text-sm font-extrabold text-slate-800`,children:`PPPoE 사용자명`}),W(`input`,{autocomplete:`username`,class:`mt-2 min-h-11 w-full rounded-xl border-2 border-slate-300 bg-slate-50 px-4 py-2.5 text-sm font-semibold text-slate-950 shadow-inner outline-none transition focus:border-teal-500 focus:bg-white focus:ring-4 focus:ring-teal-100 disabled:cursor-not-allowed disabled:bg-slate-100`,disabled:v,onInput:e=>y({pppoeUsername:e.currentTarget.value}),spellcheck:!1,value:l.pppoeUsername})]}),W(`label`,{class:`block`,children:[W(`span`,{class:`text-sm font-extrabold text-slate-800`,children:`PPPoE 비밀번호`}),W(`span`,{class:`relative mt-2 block`,children:[W(`input`,{autocomplete:`new-password`,class:`min-h-11 w-full rounded-xl border-2 border-slate-300 bg-slate-50 px-4 py-2.5 pr-12 text-sm font-semibold text-slate-950 shadow-inner outline-none transition focus:border-teal-500 focus:bg-white focus:ring-4 focus:ring-teal-100 disabled:cursor-not-allowed disabled:bg-slate-100`,disabled:v,onInput:e=>y({pppoePassword:e.currentTarget.value}),placeholder:t.configuration.protocol===`pppoe`&&t.configuration.pppoe.passwordConfigured?`변경할 때만 입력`:`PPPoE 비밀번호`,...p?{type:`text`}:{type:`password`},value:l.pppoePassword}),W(`button`,{"aria-label":p?`비밀번호 숨기기`:`비밀번호 표시`,class:`absolute inset-y-0 right-1 grid w-10 place-items-center rounded-lg border-0 bg-transparent text-slate-500`,disabled:v,onClick:()=>m(e=>!e),type:`button`,children:W(p?bn:yn,{class:`size-5`})})]}),W(`span`,{class:`mt-2 block text-xs text-slate-500`,children:t.configuration.protocol===`pppoe`&&t.configuration.pppoe.passwordConfigured?`비워 두면 현재 저장된 비밀번호를 그대로 사용합니다.`:`통신사에서 제공한 PPPoE 비밀번호를 입력해 주세요.`})]})]}):null,l.protocol===`static`?W(`div`,{class:`mt-6 grid gap-5 lg:grid-cols-2`,children:[W(`label`,{class:`block`,children:[W(`span`,{class:`text-sm font-extrabold text-slate-800`,children:`IPv4 주소`}),W($_,{disabled:v,label:`IPv4 주소`,onChange:e=>y({staticAddress:e}),value:l.staticAddress})]}),W(`div`,{class:`block`,children:[W(`span`,{class:`text-sm font-extrabold text-slate-800`,children:`서브넷 마스크`}),W(W_,{ariaLabel:`WAN 서브넷 마스크`,className:`mt-2`,disabled:v,id:`wan-static-prefix-length`,onChange:e=>y({staticPrefixLength:Number(e)}),options:pv.map(e=>({value:String(e),label:`/${e} · ${bv(e)}`})),value:String(l.staticPrefixLength),variant:`emphasized`})]}),W(`label`,{class:`block`,children:[W(`span`,{class:`text-sm font-extrabold text-slate-800`,children:`기본 게이트웨이`}),W($_,{disabled:v,label:`기본 게이트웨이`,onChange:e=>y({staticGateway:e}),value:l.staticGateway})]}),W(`label`,{class:`block`,children:[W(`span`,{class:`text-sm font-extrabold text-slate-800`,children:`기본 DNS`}),W($_,{disabled:v,label:`기본 DNS`,onChange:e=>y({dnsPrimary:e}),value:l.dnsPrimary})]}),W(`label`,{class:`block lg:col-start-2`,children:[W(`span`,{class:`text-sm font-extrabold text-slate-800`,children:`보조 DNS (선택)`}),W($_,{disabled:v,label:`보조 DNS`,onChange:e=>y({dnsSecondary:e}),value:l.dnsSecondary})]})]}):null,l.protocol===`dhcp`?W(`div`,{class:`mt-6 rounded-xl border border-slate-200 bg-slate-50 px-4 py-4 text-sm font-semibold leading-6 text-slate-600`,children:`상위 공유기 또는 통신사 장비에서 IP 주소, 기본 게이트웨이와 DNS 정보를 자동으로 받습니다. 별도의 계정이나 고정 주소가 없다면 이 방식을 사용하세요.`}):null,h?W(`p`,{class:`mt-5 mb-0 rounded-xl bg-red-50 px-4 py-3 text-sm font-bold text-red-800`,children:h}):null,W(`div`,{class:`mt-6 flex flex-col gap-3 border-t border-slate-200 pt-5 sm:flex-row sm:items-center sm:justify-between`,children:[W(`p`,{class:`m-0 text-xs font-semibold leading-5 text-slate-500`,children:`설정 저장 후 WAN만 다시 연결하며 LAN 관리 페이지와 내부 네트워크 설정은 유지됩니다.`}),W(`button`,{class:`inline-flex min-h-11 w-full shrink-0 items-center justify-center gap-2 rounded-xl border-0 bg-teal-700 px-5 py-2.5 text-sm font-extrabold text-white shadow-sm transition hover:bg-teal-800 focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-200 ${Y_(v)} disabled:opacity-60 sm:w-auto`,disabled:v||!d,type:`submit`,children:[W(Sn,{class:`size-5 ${e===`saving`?`animate-pulse`:``}`}),e===`saving`?`저장 중`:`설정 저장`]})]})]})]})}var Tv=3,Ev=10;function Dv(e,t){return e<=0?`0.0%`:`${(t/e*100).toFixed(1)}%`}function Ov(e){return e.id===`other`?`기타 기기`:e.hostname?e.hostname:e.identified?`이름 없는 기기`:`미식별 기기`}function kv(e){return[...e].sort((e,t)=>e.id===`other`?1:t.id===`other`?-1:t.blocked-e.blocked||t.queries-e.queries||Ov(e).localeCompare(Ov(t),`ko`))}function Av({devices:e,deviceLimit:t,truncated:n}){let r=kv(e),[i,a]=L(!1),[o,s]=L(1),c=Math.max(1,Math.ceil(r.length/Ev)),l=Math.min(o,c),u=(l-1)*Ev,d=Math.min(u+Ev,r.length),f=i?r.slice(u,d):r.slice(0,Tv),p=r.length>Tv;return R(()=>{s(e=>Math.min(e,c))},[c]),W(`div`,{class:`mt-6 rounded-2xl border border-slate-100 bg-slate-50/70 p-4 sm:p-5`,children:[W(`div`,{class:`flex flex-wrap items-start justify-between gap-3`,children:[W(`div`,{children:[W(`div`,{class:`flex flex-wrap items-center gap-2`,children:[W(`p`,{class:`m-0 text-xs font-bold text-slate-500`,children:`기기별 통계`}),p&&!i?W(`span`,{class:`inline-flex items-center rounded-full bg-teal-50 px-2 py-1 text-[10px] font-extrabold tracking-wide text-teal-700 ring-1 ring-teal-100`,children:`차단 TOP 3`}):null]}),W(`h3`,{class:`mt-1 mb-0 text-base font-extrabold text-slate-950`,children:`어떤 기기에서 차단되었는지 확인`}),W(`p`,{class:`mt-2 mb-0 text-xs leading-5 text-slate-500`,children:`DHCP 정보가 있는 기기는 MAC 주소를 기준으로 식별하며, 기기별 숫자만 현재 부팅 세션 동안 메모리에 집계합니다.`})]}),W(`div`,{class:`flex flex-wrap items-center justify-end gap-2`,children:[W(`span`,{class:`inline-flex items-center gap-2 rounded-full bg-white px-3 py-1.5 text-xs font-extrabold text-slate-600 ring-1 ring-slate-200`,children:[W(Yt,{class:`size-4 text-teal-700`}),Y(r.length),`개 항목`]}),p?W(`button`,{"aria-controls":`safeshield-device-statistics-list`,"aria-expanded":i,class:`rounded-full bg-white px-3 py-1.5 text-xs font-extrabold text-teal-700 ring-1 ring-slate-200 transition hover:bg-slate-50`,onClick:()=>{a(e=>!e),s(1)},type:`button`,children:i?`간단히 보기 ↑`:`전체 ${Y(r.length)}개 기기 보기 ↓`}):null]})]}),r.length===0?W(`p`,{class:`mt-4 mb-0 rounded-xl bg-white p-4 text-sm leading-6 text-slate-500 ring-1 ring-slate-100`,children:`아직 기기별 DNS 통계가 없습니다. 클라이언트에서 DNS 요청이 발생하면 다음 통계 갱신 시 표시됩니다.`}):W(`div`,{class:`mt-4 grid gap-3`,id:`safeshield-device-statistics-list`,children:f.map(e=>W(`article`,{class:`grid gap-4 rounded-xl bg-white p-4 ring-1 ring-slate-100 sm:grid-cols-[minmax(0,1fr)_auto] sm:items-center`,children:[W(`div`,{class:`min-w-0`,children:W(`div`,{class:`flex min-w-0 flex-wrap items-center gap-2`,children:[W(`span`,{class:`grid size-9 shrink-0 place-items-center rounded-lg bg-teal-50 text-teal-700`,children:W(Yt,{class:`size-4`})}),W(`div`,{class:`min-w-0`,children:[W(`h4`,{class:`m-0 truncate text-sm font-extrabold text-slate-950`,children:Ov(e)}),W(`p`,{class:`mt-1 mb-0 break-all text-xs text-slate-500`,children:[e.ip||`IP 미확인`,e.mac?` · ${e.mac}`:``]})]}),W(`span`,{class:`rounded-full px-2 py-1 text-[11px] font-extrabold ${e.identified?`bg-emerald-50 text-emerald-700`:`bg-slate-100 text-slate-600`}`,children:e.id===`other`?`합산`:e.identified?`DHCP 식별`:`IP 임시 식별`})]})}),W(`dl`,{class:`m-0 grid grid-cols-3 gap-2 text-right`,children:[W(`div`,{class:`min-w-20`,children:[W(`dt`,{class:`text-[11px] font-bold text-slate-400`,children:`DNS 요청`}),W(`dd`,{class:`mt-1 mb-0 ml-0 text-sm font-extrabold text-slate-900`,children:Y(e.queries)})]}),W(`div`,{class:`min-w-20`,children:[W(`dt`,{class:`text-[11px] font-bold text-slate-400`,children:`차단`}),W(`dd`,{class:`mt-1 mb-0 ml-0 text-sm font-extrabold text-teal-700`,children:Y(e.blocked)})]}),W(`div`,{class:`min-w-20`,children:[W(`dt`,{class:`text-[11px] font-bold text-slate-400`,children:`차단율`}),W(`dd`,{class:`mt-1 mb-0 ml-0 text-sm font-extrabold text-slate-900`,children:Dv(e.queries,e.blocked)})]})]})]},e.id))}),p&&!i?W(`p`,{class:`mt-3 mb-0 text-xs font-semibold leading-5 text-slate-500`,children:[`전체 `,Y(r.length),`개 기기 중 차단 수 기준 상위 `,Tv,`개를 표시하고 있습니다.`]}):null,i&&r.length>Ev?W(`nav`,{"aria-label":`기기별 통계 페이지`,class:`mt-4 flex flex-wrap items-center justify-between gap-3`,children:[W(`p`,{class:`m-0 text-xs font-semibold text-slate-500`,children:[Y(u+1),`–`,Y(d),` /`,` `,Y(r.length),`개`]}),W(`div`,{class:`flex items-center gap-2`,children:[W(`button`,{class:`rounded-lg bg-white px-3 py-2 text-xs font-extrabold text-slate-700 ring-1 ring-slate-200 transition hover:bg-slate-50 disabled:cursor-not-allowed disabled:opacity-50`,disabled:l<=1,onClick:()=>s(e=>Math.max(1,e-1)),type:`button`,children:`이전`}),W(`span`,{"aria-current":`page`,class:`min-w-20 text-center text-xs font-extrabold text-slate-600`,children:[Y(l),` / `,Y(c)]}),W(`button`,{class:`rounded-lg bg-white px-3 py-2 text-xs font-extrabold text-slate-700 ring-1 ring-slate-200 transition hover:bg-slate-50 disabled:cursor-not-allowed disabled:opacity-50`,disabled:l>=c,onClick:()=>s(e=>Math.min(c,e+1)),type:`button`,children:`다음`})]})]}):null,n?W(`p`,{class:`mt-4 mb-0 rounded-xl border border-amber-200 bg-amber-50 p-3 text-xs font-semibold leading-5 text-amber-800`,children:[`개별 통계는 최대 `,Y(t),`개 기기까지 추적합니다. 한도를 초과한 기기의 요청은 기타 기기로 합산됩니다.`]}):null]})}var jv=3600,Mv=24;function Nv(e,t){return e<=0?`0.0%`:`${(t/e*100).toFixed(1)}%`}function Pv(e){let t=e.hourly[e.hourly.length-1];if(t?.bucketStart)return t.bucketStart;let n=e.updatedAt>0?e.updatedAt:Math.floor(Date.now()/1e3);return Math.floor(n/jv)*jv}function Fv(e){let t=new Map(e.hourly.map(e=>[e.bucketStart,e])),n=Pv(e)-23*jv;return Array.from({length:Mv},(e,r)=>{let i=n+r*jv;return t.get(i)??{bucketStart:i,queries:0,blocked:0}})}function Iv({emphasized:e=!1,label:t,value:n}){return W(`div`,{class:`rounded-xl bg-slate-50 p-4 sm:p-5`,children:[W(`dt`,{class:`text-[0.68rem] font-black uppercase tracking-[0.12em] text-slate-400`,children:t}),W(`dd`,{class:`mt-2 mb-0 ml-0 text-2xl font-black tracking-tight ${e?`text-teal-700`:`text-slate-950`}`,children:n})]})}function Lv({action:e,data:t,error:n,loading:r,onRetry:i,onSetEnabled:a,refreshing:o}){if(r&&t===null)return W(b,{children:[W(J,{eyebrow:`Activity`,title:`최근 24시간 보호 활동`,description:`DNS 요청과 차단 현황을 확인합니다.`}),W(`section`,{class:`rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5 sm:p-6`,children:W(`p`,{class:`m-0 text-sm font-bold text-slate-500`,children:`차단 통계를 불러오는 중입니다…`})})]});if(n&&t===null)return W(b,{children:[W(J,{eyebrow:`Activity`,title:`최근 24시간 보호 활동`,description:`DNS 요청과 차단 현황을 확인합니다.`}),W(`section`,{class:`rounded-2xl border border-amber-200 bg-amber-50 p-5 sm:p-6`,children:[W(`p`,{class:`m-0 text-sm font-extrabold text-amber-950`,children:`차단 통계를 불러오지 못했습니다.`}),W(`p`,{class:`mt-2 mb-0 text-xs leading-5 text-amber-800`,children:n}),W(`button`,{class:`mt-4 inline-flex min-h-9 items-center justify-center rounded-xl border border-amber-300 bg-white px-3 py-2 text-xs font-extrabold text-amber-900 transition hover:bg-amber-100 focus:outline-none focus-visible:ring-4 focus-visible:ring-amber-100`,onClick:i,type:`button`,children:`다시 시도`})]})]});if(!t)return W(b,{children:[W(J,{eyebrow:`Activity`,title:`최근 24시간 보호 활동`,description:`DNS 요청과 차단 현황을 확인합니다.`}),W(`section`,{class:`rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5 sm:p-6`,children:W(`p`,{class:`m-0 text-sm leading-6 text-slate-600`,children:`현재 SafeShield에서 통계 데이터를 제공하지 않습니다. SafeShield 0.3.15 버전 이상이 설치되어 있고, 통계 수집이 활성화되어 있는지 확인해 주세요.`})})]});let s=Fv(t),c=s[s.length-1],l=s.reduce((e,t)=>({queries:e.queries+t.queries,blocked:e.blocked+t.blocked}),{queries:0,blocked:0}),u=e===`statistics-enable`||e===`statistics-disable`,d=e===`statistics-enable`||e!==`statistics-disable`&&t.enabled,f=u?d?`활성화하는 중…`:`비활성화하는 중…`:t.enabled?t.collectorRunning?`활성화 · 수집 중`:`활성화 · 시작 중`:`비활성화`;return W(b,{children:[W(J,{action:W(`div`,{class:`flex items-center gap-3 rounded-xl border border-slate-100 bg-slate-50 px-3 py-2.5`,children:[W(`span`,{class:`grid size-10 shrink-0 place-items-center rounded-xl bg-slate-100 text-slate-500`,children:W(gn,{class:`size-5`})}),W(`div`,{class:`text-right`,children:[W(`p`,{class:`m-0 text-[0.68rem] font-bold text-slate-400`,children:`통계 수집`}),W(`p`,{class:`mt-1 mb-0 text-xs font-extrabold ${u||d?`text-emerald-700`:`text-slate-500`}`,role:`status`,children:f})]}),W(`button`,{"aria-checked":d,"aria-label":d?`차단 통계 수집 끄기`:`차단 통계 수집 켜기`,class:`ssh-switch-control relative inline-flex shrink-0 items-center rounded-full border-0 p-0 transition focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-100 ${u?`cursor-wait opacity-80`:`disabled:cursor-not-allowed disabled:opacity-50`} ${d?`bg-teal-600`:`bg-slate-300`}`,disabled:e!==null,onClick:()=>a(!t.enabled),role:`switch`,type:`button`,children:W(`span`,{"aria-hidden":`true`,class:`ssh-switch-thumb absolute top-1 grid place-items-center shadow-sm transition-all ${d?`left-6`:`left-1`}`,children:u?W(pn,{"aria-hidden":`true`,class:`size-3 animate-spin text-teal-600`}):null})})]}),eyebrow:`Activity`,title:`최근 24시간 보호 활동`,description:`DNS 요청 원본은 저장하지 않고 숫자만 로컬 메모리에 집계합니다.`}),W(`section`,{"aria-busy":u,class:`rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5 sm:p-6 ${u?`cursor-wait`:``}`,children:[t.enabled?null:W(`div`,{class:`mb-4 rounded-xl border border-slate-200 bg-slate-50 px-4 py-3 text-sm leading-6 text-slate-600`,children:`통계 수집이 꺼져 있습니다. 활성화하면 DNS 요청 원본은 저장하지 않고 요청 수와 차단 수만 로컬 메모리에 집계합니다.`}),W(`dl`,{class:`mt-2 grid gap-3 sm:grid-cols-2 xl:grid-cols-4`,children:[W(Iv,{label:`최근 24시간 DNS 요청`,value:Y(l.queries)}),W(Iv,{emphasized:!0,label:`최근 24시간 차단`,value:Y(l.blocked)}),W(Iv,{label:`최근 24시간 차단율`,value:Nv(l.queries,l.blocked)}),W(Iv,{label:`현재 시간 차단`,value:Y(c?.blocked??0)})]}),W(`div`,{class:`mt-5 rounded-xl bg-slate-50/70 p-4 sm:p-5`,children:[W(`div`,{class:`flex flex-wrap items-end justify-between gap-3`,children:[W(`div`,{children:[W(`p`,{class:`m-0 text-xs font-bold text-slate-500`,children:`최근 24시간`}),W(`h3`,{class:`mt-1 mb-0 text-base font-extrabold text-slate-950`,children:`시간대별 차단 요청`})]}),W(`span`,{class:`text-xs font-bold text-slate-500`,children:o?`갱신 중…`:`마지막 집계 ${Ao(t.updatedAt)}`})]}),W(t_,{buckets:s})]}),W(Av,{deviceLimit:t.deviceLimit,devices:t.devices,truncated:t.devicesTruncated}),W(`div`,{class:`mt-5 flex flex-wrap gap-x-5 gap-y-2 border-t border-slate-100 pt-4 text-xs leading-5 text-slate-500`,children:[W(`span`,{children:[`수집 누적 DNS 요청 `,Y(t.totals.queries)]}),W(`span`,{children:[`수집 누적 차단 `,Y(t.totals.blocked)]}),W(`span`,{children:[`수집 시작 `,Ao(t.startedAt)]}),W(`span`,{children:[`보존 `,Y(t.retentionHours),`시간`]}),t.volatile?W(`span`,{children:`재부팅 시 초기화되는 메모리 통계`}):null]}),n?W(`p`,{class:`mt-3 mb-0 text-xs font-bold text-amber-700`,children:`최근 자동 갱신에 실패했습니다. 현재 표시된 마지막 정상 통계를 유지합니다.`}):null]})]})}function Rv({timestamp:e,fallback:t=`기록 없음`}){let[n,r]=L(()=>Date.now()/1e3),i=typeof e==`number`&&Number.isFinite(e)&&e>0&&e<=864e10;if(R(()=>{if(!i)return;r(Date.now()/1e3);let e=window.setInterval(()=>r(Date.now()/1e3),6e4);return()=>window.clearInterval(e)},[e,i]),!i||e==null)return W(`span`,{children:t});let a=Ao(e);return W(`span`,{class:`inline-flex max-w-full flex-wrap items-center gap-1`,children:[W(`time`,{class:`min-w-0`,dateTime:new Date(e*1e3).toISOString(),title:a,children:Mo(e,n)}),W(`details`,{class:`group inline-flex max-w-full flex-wrap items-center gap-1 align-middle`,children:[W(`summary`,{"aria-label":`정확한 날짜 및 시간 확인`,class:`inline-flex size-6 cursor-pointer list-none items-center justify-center rounded-full text-slate-400 transition hover:text-teal-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-teal-500 [&::-webkit-details-marker]:hidden`,title:`정확한 날짜 및 시간 확인`,children:W(`svg`,{"aria-hidden":`true`,class:`size-3.5`,fill:`none`,stroke:`currentColor`,strokeWidth:`1.8`,viewBox:`0 0 24 24`,children:[W(`circle`,{cx:`12`,cy:`12`,r:`9`}),W(`path`,{d:`M12 11v5m0-8h.01`,strokeLinecap:`round`})]})}),W(`span`,{class:`hidden max-w-full break-words rounded-md bg-slate-100 px-2 py-1 text-xs font-semibold text-slate-700 group-open:inline-block`,children:a})]})]})}var zv=`https://www.smartsafehub.com/pricing/`;function Bv({value:e,trueLabel:t=`정상`,falseLabel:n=`확인 필요`}){return W(`span`,{class:`inline-flex items-center gap-2 text-sm font-extrabold ${e?`text-emerald-700`:`text-slate-500`}`,children:[W(`span`,{class:`size-2 rounded-full ${e?`bg-emerald-500`:`bg-slate-300`}`}),e?t:n]})}function Vv(e){return!e.enabled||e.status===`disabled`?`disabled`:e.status===`running`?`refreshing`:e.status===`error`||e.health.overall===`error`||e.issueCounts.errors>0?`error`:e.status===`paused`?`paused`:e.active&&e.runtime.dnsmasqRunning&&e.runtime.dnsRuntimeOk?`protecting`:`attention`}function Hv(e){return!e.enabled||e.status===`disabled`?`비활성화`:e.active&&e.runtime.dnsmasqRunning&&e.runtime.dnsRuntimeOk?`보호 중`:e.status===`paused`?`일시 중지`:`확인 필요`}function Uv(e){let t=Vv(e);return t===`error`&&Hv(e)===`보호 중`&&e.runtime.lastErrorCode?`갱신 실패`:{disabled:`비활성화`,refreshing:`갱신 중`,protecting:`보호 중`,paused:`일시 중지`,error:`오류`,attention:`확인 필요`}[t]}function Wv(e){return e?`자동 최적화`:`확인되지 않음`}function Gv(e){let t=No(e);return t===null?`확인되지 않음`:W(Rv,{timestamp:t})}function Kv(e){let t=Vv(e);return t===`protecting`?e.blocklist.validLineCount>0?`${Y(e.blocklist.validLineCount)}개 도메인 차단 규칙으로 DNS를 보호하고 있습니다.`:e.blocklist.installed?`차단 목록이 설치되어 있으며 DNS 보호가 정상적으로 동작하고 있습니다.`:`SafeShield 서비스와 DNS 런타임이 정상적으로 동작하고 있습니다.`:t===`refreshing`?`차단 목록을 최신 상태로 갱신하고 있습니다.`:t===`disabled`?`SafeShield DNS 보호가 비활성화되어 있습니다.`:t===`paused`?`SafeShield 작업이 일시 중지되어 있습니다.`:t===`error`?Hv(e)===`보호 중`?`기존 DNS 보호는 유지되고 있지만 최근 차단 목록 갱신을 완료하지 못했습니다.`:`SafeShield 보호 상태에 문제가 발생했습니다. 아래 오류 내용을 확인해 주세요.`:e.active?e.runtime.dnsmasqRunning?e.runtime.dnsRuntimeOk?e.summary.message:`SafeShield DNS 런타임 상태를 확인해야 합니다.`:`dnsmasq가 실행 중이 아니어서 DNS 보호 상태를 확인해야 합니다.`:`SafeShield가 활성화되어 있지만 서비스가 실행 중이 아닙니다.`}function qv(e){return{ok:`정상`,warning:`경고`,error:`오류`,unavailable:`사용 불가`}[e]??e}function Jv({data:e}){let t=Vv(e);return W(`span`,{class:`inline-flex items-center rounded-full px-3 py-1.5 text-xs font-extrabold ring-1 ring-inset ${t===`error`?`bg-red-50 text-red-700 ring-red-200`:t===`disabled`?`bg-slate-100 text-slate-700 ring-slate-200`:t===`refreshing`?`bg-blue-50 text-blue-700 ring-blue-200`:t===`paused`||t===`attention`?`bg-amber-50 text-amber-800 ring-amber-200`:`bg-emerald-50 text-emerald-700 ring-emerald-200`}`,children:Uv(e)})}function Yv({failed:e,step:t,total:n}){let r=2*Math.PI*17,i=r*.22,a=r-i;return W(`span`,{"aria-label":e?`갱신 실패 · ${t}/${n} 단계`:`갱신 ${t}/${n} 단계`,class:`ssh-safeshield-refresh-donut`,"data-state":e?`error`:`active`,role:`progressbar`,"aria-valuemin":0,"aria-valuemax":n,"aria-valuenow":t,children:[W(`svg`,{"aria-hidden":`true`,class:e?`ssh-safeshield-refresh-loader-svg ssh-safeshield-refresh-loader-svg-error`:`ssh-safeshield-refresh-loader-svg`,viewBox:`0 0 48 48`,children:[W(`circle`,{class:`ssh-safeshield-refresh-loader-track`,cx:`24`,cy:`24`,fill:`none`,r:17,stroke:`currentColor`,strokeWidth:2.5}),e?null:W(`circle`,{class:`ssh-safeshield-refresh-loader-arc`,cx:`24`,cy:`24`,fill:`none`,r:17,stroke:`currentColor`,strokeDasharray:`${i} ${a}`,strokeLinecap:`round`,strokeWidth:3.5})]}),W(`span`,{class:`ssh-safeshield-refresh-donut-label`,children:e?`!`:`${t}/${n}`})]})}function Xv({data:e}){let t=Wa(e.status,e.stage),n=Vv(e)===`error`;if(!t&&!n)return null;let r=Ga(e.stage),i=n?qa(e.runtime.lastErrorCode,e.stage):null;return W(`div`,{"aria-live":n?`assertive`:`polite`,class:`ssh-safeshield-refresh`,"data-state":n?`error`:`active`,role:n?`alert`:`status`,children:[W(Yv,{failed:n,step:r.number,total:r.total}),W(`div`,{class:`ssh-safeshield-refresh-copy`,children:[W(`p`,{class:`ssh-safeshield-refresh-kicker`,children:n?`갱신 실패 · ${r.number}/${r.total}`:`갱신 ${r.number}/${r.total}`}),W(`p`,{class:`ssh-safeshield-refresh-title`,children:n?`${r.label} 단계에서 문제가 발생했습니다.`:r.label}),W(`p`,{class:`ssh-safeshield-refresh-description`,children:i??r.description}),n&&e.runtime.lastErrorCode?W(`p`,{class:`ssh-safeshield-refresh-error-code`,children:[`오류 코드: `,W(`code`,{children:e.runtime.lastErrorCode})]}):null]})]})}function Zv({label:e,value:t}){return W(`div`,{class:`bg-white px-5 py-4 sm:px-6`,children:[W(`dt`,{class:`text-[0.68rem] font-black uppercase tracking-[0.14em] text-slate-400`,children:e}),W(`dd`,{class:`mt-2 mb-0 ml-0 text-sm font-extrabold text-slate-950`,title:typeof t==`string`?t:void 0,children:t})]})}function Qv(e){return e?.trim().toUpperCase()||`FREE`}function $v(e){return e===`FREE`?`free`:e===`PRO`?`pro`:e===`ULTIMATE`?`ultimate`:`paid`}function ey({compact:e=!1,plan:t}){let n=$v(t),r=n!==`free`;return W(`span`,{class:`ssh-safeshield-plan-badge`,"data-compact":e?`true`:`false`,"data-tier":n,title:r?`${t} 멤버십`:`무료 플랜`,children:[W(`span`,{"aria-hidden":`true`,class:`ssh-safeshield-plan-badge-mark`,children:r?`✦`:`•`}),W(`span`,{children:t})]})}function ty({plan:e}){let t=$v(e)===`free`;return W(`div`,{class:`bg-white px-5 py-4 sm:px-6`,children:[W(`dt`,{class:`text-[0.68rem] font-black uppercase tracking-[0.14em] text-slate-400`,children:`Plan`}),W(`dd`,{class:`mt-2 mb-0 ml-0 flex min-w-0 flex-wrap items-center gap-2`,children:[W(ey,{plan:e}),t?W(`span`,{class:`ssh-safeshield-plan-caption`,children:`기본 플랜`}):null]})]})}function ny(){return W(`div`,{class:`ssh-safeshield-upgrade-card`,children:[W(`span`,{"aria-hidden":`true`,class:`ssh-safeshield-upgrade-mark`,children:`✦`}),W(`div`,{class:`ssh-safeshield-upgrade-copy`,children:[W(`div`,{class:`ssh-safeshield-upgrade-meta`,children:[W(`span`,{children:`SmartSafeHub Membership`}),W(`span`,{class:`ssh-safeshield-upgrade-soon`,children:`Coming soon`})]}),W(`strong`,{children:`더 강력한 보호 기능을 준비하고 있습니다.`}),W(`span`,{children:`프리미엄 보호와 확장된 관리 기능을 미리 확인해 보세요.`})]}),W(`a`,{class:`ssh-safeshield-upgrade-action`,href:zv,rel:`noopener noreferrer`,target:`_blank`,children:[`요금제 보기`,W(`span`,{"aria-hidden":`true`,children:`→`})]})]})}function ry({children:e,eyebrow:t,icon:n,title:r}){return W(`article`,{class:`min-w-0 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5 sm:p-6 xl:p-4 2xl:p-5`,children:[W(`div`,{class:`flex min-w-0 items-start justify-between gap-4 xl:gap-2`,children:[W(`div`,{children:[W(`p`,{class:`m-0 text-[0.68rem] font-black uppercase tracking-[0.16em] text-slate-400`,children:t}),W(`h3`,{class:`mt-2 mb-0 text-lg font-black tracking-tight text-slate-950 xl:text-base 2xl:text-lg`,children:r})]}),W(`span`,{class:`grid size-10 shrink-0 place-items-center rounded-xl bg-slate-100 text-slate-500 xl:size-9 2xl:size-10`,children:n})]}),W(`dl`,{class:`mt-5 mb-0 grid gap-3`,children:e})]})}function iy({label:e,value:t}){return W(`div`,{class:`flex min-w-0 items-center justify-between gap-2 border-b border-slate-100 pb-3 last:border-b-0 last:pb-0`,children:[W(`dt`,{class:`text-sm font-semibold text-slate-500`,children:e}),W(`dd`,{class:`m-0 min-w-0 break-words text-right text-sm font-extrabold text-slate-950`,children:t})]})}function ay({error:e,message:t,onDismiss:n}){if(!e&&!t)return null;let r=e!==null;return W(`div`,{"aria-live":r?`assertive`:`polite`,class:`mt-4 flex flex-col items-start gap-3 rounded-2xl border px-4 py-3 text-sm sm:flex-row sm:justify-between ${r?`border-red-200 bg-red-50 text-red-900`:`border-emerald-200 bg-emerald-50 text-emerald-900`}`,role:r?`alert`:`status`,children:[W(`div`,{class:`flex min-w-0 items-start gap-3`,children:[W(r?Cn:Sn,{class:`mt-0.5 size-5 shrink-0`}),W(`p`,{class:`m-0 font-semibold leading-6`,children:e??t})]}),W(`button`,{"aria-label":`알림 닫기`,class:`shrink-0 rounded-lg border-0 bg-transparent px-2 py-1 font-black text-current opacity-60 transition hover:opacity-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-current`,onClick:n,type:`button`,children:`×`})]})}function oy({action:e,actionError:t,actionMessage:n,data:r,error:i,loading:a,statistics:o,statisticsError:s,statisticsLoading:c,statisticsRefreshing:l,onDismissFeedback:u,onRefreshBlocklist:d,onRetry:f,onRetryStatistics:p,onSetEnabled:m,onSetStatisticsEnabled:h}){let[g,_]=L(null);if(R(()=>{r?.available&&r.blocklist.validLineCount>0&&!Wa(r.status,r.stage)&&_(r.blocklist.validLineCount)},[r?.available,r?.blocklist.validLineCount,r?.stage,r?.status]),a)return W(vs,{});if(i)return W(ys,{message:i,onRetry:f});if(!r)return W(vs,{});if(!r.available)return W(`section`,{class:`rounded-2xl border border-amber-200 bg-amber-50 p-5 sm:p-6`,children:[W(`span`,{class:`grid size-11 place-items-center rounded-xl bg-amber-100 text-amber-800`,children:W(Cn,{class:`size-6`})}),W(`h2`,{class:`mt-5 mb-0 text-xl font-extrabold text-amber-950`,children:`SafeShield 상태 API를 찾을 수 없습니다`}),W(`p`,{class:`mt-2 mb-0 text-sm leading-6 text-amber-900`,children:`safeshield 패키지와 rpcd ucode 플러그인이 설치되어 있는지 확인해 주세요.`})]});let v=r.enabled,y=Qv(r.entitlement.plan),x=r.status===`running`,S=e!==null,C=(Wa(r.status,r.stage)||Vv(r)===`error`)&&r.blocklist.validLineCount===0&&g!==null?g:r.blocklist.validLineCount,w=v?e===`disable`?`끄는 중…`:`보호 끄기`:e===`enable`?`켜는 중…`:`보호 켜기`;function T(){(!v||window.confirm(`보호를 끄면, 현재 차단 목록이 제거되어 위험에 노출될 수 있습니다. 그래도 계속하시겠습니까?`))&&m(!v)}return W(b,{children:[W(J,{eyebrow:`Protection`,title:`SafeShield 보호 상태`,description:`DNS 보호 상태와 적용 중인 차단 목록을 확인합니다.`}),W(`section`,{class:`mb-8 grid min-w-0 gap-4 xl:grid-cols-[minmax(0,1.08fr)_minmax(0,0.92fr)] xl:items-stretch`,children:[W(`article`,{"aria-label":`보호 상태 요약`,class:`min-w-0 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5 sm:p-6 xl:h-full`,children:[W(`div`,{class:`flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between xl:flex-col 2xl:flex-row`,children:[W(`div`,{class:`flex min-w-0 items-start gap-3`,children:[W(`span`,{class:`grid size-10 shrink-0 place-items-center rounded-xl bg-teal-50 text-teal-700`,children:W(qt,{class:`size-5`})}),W(`div`,{class:`min-w-0`,children:[W(`div`,{class:`flex flex-wrap items-center gap-2`,children:[W(`h3`,{class:`m-0 text-base font-black text-slate-950`,children:`현재 보호 상태`}),W(Jv,{data:r})]}),W(`p`,{class:`mt-2 mb-0 text-sm leading-6 text-slate-600`,children:Kv(r)})]})]}),W(`div`,{class:`flex w-full flex-col gap-2 sm:w-auto sm:flex-row sm:flex-wrap xl:justify-end`,children:[W(`button`,{class:`inline-flex min-h-11 items-center justify-center gap-2 rounded-xl border px-4 py-2.5 text-sm font-extrabold transition focus:outline-none focus-visible:ring-4 disabled:cursor-not-allowed disabled:opacity-50 ${r.enabled?`border-red-200 bg-white text-red-700 hover:border-red-300 hover:bg-red-50 focus-visible:ring-red-100`:`border-teal-700 bg-teal-700 text-white hover:border-teal-800 hover:bg-teal-800 focus-visible:ring-teal-100`}`,disabled:S||x,onClick:T,type:`button`,children:[W(mn,{class:`size-4 ${e===`enable`||e===`disable`?`animate-pulse`:``}`}),w]}),W(`button`,{class:`inline-flex min-h-11 items-center justify-center gap-2 rounded-xl border border-slate-200 bg-white px-4 py-2.5 text-sm font-extrabold text-slate-700 transition hover:border-teal-300 hover:bg-teal-50 hover:text-teal-800 focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-100 disabled:cursor-not-allowed disabled:opacity-50`,disabled:!r.enabled||S||x,onClick:d,type:`button`,children:[W(hn,{class:`size-4 ${e===`refresh`||x?`animate-bounce`:``}`}),e===`refresh`?`시작 중…`:x?`갱신 중…`:r.status===`error`?`다시 갱신`:`지금 갱신`]})]})]}),W(Xv,{data:r}),W(`dl`,{class:`mt-5 mb-0 grid min-w-0 grid-cols-2 overflow-hidden rounded-xl border border-slate-200 bg-slate-200`,children:[W(Zv,{label:`SafeShield`,value:r.version??`확인되지 않음`}),W(Zv,{label:`Blocklist`,value:`${Y(C)}개 도메인`}),W(Zv,{label:`Last refresh`,value:W(Rv,{timestamp:r.timestamps.lastSuccess})}),W(ty,{plan:y})]}),y===`FREE`?W(ny,{}):null]}),W(`section`,{"aria-label":`보호 데이터 요약`,class:`min-w-0 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5 sm:p-6 xl:h-full`,children:[W(`div`,{class:`flex flex-wrap items-center justify-between gap-3`,children:[W(`div`,{class:`flex min-w-0 items-center gap-3`,children:[W(`span`,{class:`grid size-10 shrink-0 place-items-center rounded-xl bg-slate-100 text-slate-500`,children:W(gn,{class:`size-5`})}),W(`div`,{children:[W(`p`,{class:`m-0 text-[0.65rem] font-black uppercase tracking-[0.16em] text-slate-400`,children:`Protection data`}),W(`h3`,{class:`mt-1 mb-0 text-sm font-black text-slate-950`,children:`보호 데이터`})]})]}),W(`span`,{"aria-label":`보호 데이터 상태: ${r.artifact.resolved&&r.blocklist.installed&&r.blocklist.verificationOk?`최신`:`확인 필요`}`,class:`inline-flex items-center gap-1.5 rounded-full px-2.5 py-1 text-xs font-extrabold ${r.artifact.resolved&&r.blocklist.installed&&r.blocklist.verificationOk?`bg-emerald-50 text-emerald-700`:`bg-amber-50 text-amber-700`}`,children:[r.artifact.resolved&&r.blocklist.installed&&r.blocklist.verificationOk?W(Sn,{class:`size-3.5 shrink-0`}):W(Cn,{class:`size-3.5 shrink-0`}),r.artifact.resolved&&r.blocklist.installed&&r.blocklist.verificationOk?`최신`:`확인 필요`]})]}),W(`p`,{class:`mt-2 mb-0 text-xs leading-5 text-slate-500`,children:`SafeShield가 기기 성능에 맞춰 사용하는 보호 목록의 구성과 최신 상태를 확인합니다.`}),W(`dl`,{class:`mt-3 mb-0 grid grid-cols-1 gap-2 sm:grid-cols-2`,children:[W(`div`,{class:`min-w-0 rounded-lg border border-slate-200 bg-white px-3 py-2.5`,children:[W(`dt`,{class:`text-xs font-bold text-slate-500`,children:`기기 최적화`}),W(`dd`,{class:`mt-1 mb-0 text-sm font-extrabold text-slate-950`,children:Wv(r.artifact.tier)})]}),W(`div`,{class:`min-w-0 rounded-lg border border-slate-200 bg-white px-3 py-2.5`,children:[W(`dt`,{class:`text-xs font-bold text-slate-500`,children:`마지막 업데이트`}),W(`dd`,{class:`mt-1 mb-0 text-sm font-extrabold leading-5 text-slate-950`,children:Gv(r.artifact.version)})]})]}),W(`section`,{"aria-label":`보호 데이터 세부 정보`,class:`mt-3 border-t border-slate-200 pt-3`,children:[W(`h4`,{class:`m-0 text-xs font-extrabold text-slate-600`,children:`세부 정보`}),W(`dl`,{class:`mt-3 mb-0 grid gap-2`,children:[W(iy,{label:`데이터 프로필`,value:r.artifact.tier||`확인되지 않음`}),W(iy,{label:`데이터 버전`,value:r.artifact.version||`확인되지 않음`})]})]})]})]}),W(ay,{error:t,message:n,onDismiss:u}),W(Lv,{action:e,data:o,error:s,loading:c,onRetry:p,onSetEnabled:h,refreshing:l}),W(`section`,{class:`mt-7`,children:[W(J,{description:`DNS 보호 런타임, 차단 목록, 갱신 일정과 상태 점검 결과를 한곳에서 확인합니다.`,eyebrow:`Protection details`,title:`보호 구성`}),W(`div`,{class:`grid min-w-0 grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4`,children:[W(ry,{eyebrow:`Runtime`,icon:W(Sn,{class:`size-5`}),title:`DNS 보호 런타임`,children:[W(iy,{label:`SafeShield 서비스`,value:W(Bv,{falseLabel:`중지됨`,trueLabel:`동작 중`,value:r.active})}),W(iy,{label:`DNS 서비스`,value:W(Bv,{falseLabel:`중지됨`,value:r.runtime.dnsmasqRunning})}),W(iy,{label:`SafeShield DNS 연동`,value:W(Bv,{value:r.runtime.dnsRuntimeOk})}),W(iy,{label:`차단 목록 갱신`,value:W(Bv,{falseLabel:`중지됨`,value:r.runtime.refreshdRunning})})]}),W(ry,{eyebrow:`Blocklist`,icon:W(gn,{class:`size-5`}),title:`차단 목록`,children:[W(iy,{label:`적용 규칙`,value:`${Y(r.blocklist.validLineCount)}개 도메인`}),W(iy,{label:`파일 크기`,value:Do(r.blocklist.fileSizeKb*1024)}),W(iy,{label:`설치 상태`,value:W(Bv,{falseLabel:`미설치`,trueLabel:`설치됨`,value:r.blocklist.installed})}),W(iy,{label:`검증 상태`,value:W(Bv,{falseLabel:`확인 필요`,value:r.blocklist.verificationOk})})]}),W(ry,{eyebrow:`Refresh schedule`,icon:W(xn,{class:`size-5`}),title:`차단 목록 갱신`,children:[W(iy,{label:`마지막 성공`,value:W(Rv,{timestamp:r.timestamps.lastSuccess})}),W(iy,{label:`다음 갱신`,value:Ao(r.timestamps.nextRefreshAt)}),W(iy,{label:`갱신 주기`,value:Fo(r.timestamps.refreshIntervalS)}),W(iy,{label:`최근 결과`,value:r.runtime.lastResult||(x?`갱신 중`:`확인되지 않음`)})]}),W(ry,{eyebrow:`Health`,icon:W(qt,{class:`size-5`}),title:`상태 점검`,children:[W(iy,{label:`전체 상태`,value:qv(r.health.overall)}),W(iy,{label:`경고`,value:W(`span`,{class:`text-amber-700`,children:Y(r.issueCounts.warnings)})}),W(iy,{label:`오류`,value:W(`span`,{class:`text-red-700`,children:Y(r.issueCounts.errors)})}),W(iy,{label:`최근 오류 코드`,value:r.runtime.lastErrorCode||`없음`})]})]})]})]})}function sy(e,t,n,r){let i=t?e.filter(e=>e.includes(t)):e;if(!n)return{filteredRules:i,manualRules:i,managedRules:[]};let a=[],o=[];for(let e of i)(r.has(e)?o:a).push(e);return{filteredRules:i,manualRules:a,managedRules:o}}function cy(e,t){return t?e.filter(e=>e.title.toLowerCase().includes(t)||e.domains.some(e=>e.includes(t))):e}function ly(e,t){return e===t||e.length===t.length&&e.every((e,n)=>e===t[n])}function uy(e,t){return e===t||e.length===t.length&&e.every((e,n)=>{let r=t[n];return r!==void 0&&e.id===r.id&&e.title===r.title&&ly(e.domains,r.domains)})}function dy(e,t){return{domains:ly(e.domains,t.domains)?e.domains:t.domains,rules:uy(e.rules,t.rules)?e.rules:t.rules}}var fy=[],py=[];function my(e){return e.trim().toLowerCase().replace(/\.+$/,``)}function hy(e){return e.length===0||e.length>253||!e.includes(`.`)||!/^[a-z0-9.-]+$/.test(e)?!1:e.split(`.`).every(e=>e.length>0&&e.length<=63&&/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(e))}var gy=D_(function({action:e,kind:t,onAddRule:n,onDeleteRule:r,rules:i,recommendedDomainSet:a,recommendedKnown:o,recommendedRules:s,onRequestRevoke:c,revokeBusy:l}){let[u,d]=L(``),[f,p]=L(``),[m,h]=L(null),[g,_]=L(!0),[v,y]=L(null),b=t===`allow`,x=e!==null,S=Xe(()=>f.trim().toLowerCase(),[f]),C=S.length>0,{filteredRules:w,manualRules:T,managedRules:E}=Xe(()=>sy(i,S,b&&o,a),[i,S,b,o,a]),D=Xe(()=>b?cy(s,S):[],[b,s,S]),O=C||(v??(s.length||E.length)<=3),k=C||g;async function A(e){e.preventDefault();let r=my(u);hy(r)?(h(null),await n(t,r,()=>{d(e=>my(e)===r?``:e)})):h(`example.com 형식의 도메인을 입력해 주세요. URL, IP 주소, 와일드카드는 사용할 수 없습니다.`)}let j=b?{badge:`bg-emerald-100 text-emerald-800`,button:`border-emerald-700 bg-emerald-700 text-white hover:border-emerald-800 hover:bg-emerald-800 focus-visible:ring-emerald-100`,icon:`bg-emerald-50 text-emerald-700`,label:`허용 목록`,description:`직접 허용하거나 웹사이트 추천으로 추가된 도메인을 관리합니다.`,empty:`등록된 허용 도메인이 없습니다.`}:{badge:`bg-red-100 text-red-800`,button:`border-red-700 bg-red-700 text-white hover:border-red-800 hover:bg-red-800 focus-visible:ring-red-100`,icon:`bg-red-50 text-red-700`,label:`차단 목록`,description:`SafeShield 기본 목록에 없더라도 직접 차단할 도메인을 등록합니다.`,empty:`등록된 차단 도메인이 없습니다.`};function M(n){let i=b&&o&&a.has(n),s=!b||o?i?`추천 규칙`:`직접 추가`:null,c=e?.operation===`delete`&&e.action===t&&e.domain===n;return W(`li`,{class:`flex min-w-0 items-center justify-between gap-3 px-3.5 py-3`,children:[W(`div`,{class:`flex min-w-0 flex-1 flex-wrap items-center gap-2`,children:W(`code`,{class:`min-w-0 break-all font-mono text-sm font-semibold text-slate-800`,children:n})}),i?W(`span`,{class:`shrink-0 rounded-full bg-teal-50 px-2.5 py-1 text-[11px] font-bold text-teal-800`,title:`추천 규칙의 적용과 해제는 웹사이트에서 관리합니다.`,children:`웹사이트 관리`}):W(`button`,{"aria-label":`${n} ${j.label}에서 삭제`,class:`inline-flex size-9 shrink-0 items-center justify-center rounded-lg border border-slate-200 bg-white text-slate-500 transition hover:border-red-200 hover:bg-red-50 hover:text-red-700 focus:outline-none focus-visible:ring-4 focus-visible:ring-red-100 disabled:cursor-not-allowed disabled:opacity-50`,disabled:x||i,onClick:()=>void r(t,n),title:s===`직접 추가`?`직접 추가한 규칙 삭제`:`삭제`,type:`button`,children:W(Tn,{class:`size-4 ${c?`animate-pulse`:``}`})})]},n)}return W(`article`,{class:`min-w-0 overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm shadow-slate-900/5`,children:[W(`div`,{class:`border-b border-slate-200 p-4 sm:p-6`,children:[W(`div`,{class:`flex min-w-0 items-start gap-3`,children:[W(`span`,{class:`grid size-11 shrink-0 place-items-center rounded-xl ${j.icon}`,children:W(b?Sn:qt,{class:`size-6`})}),W(`div`,{class:`min-w-0`,children:[W(`div`,{class:`flex flex-wrap items-center gap-2`,children:[W(`h2`,{class:`m-0 text-xl font-extrabold tracking-tight text-slate-950`,children:j.label}),W(`span`,{class:`rounded-full px-2.5 py-1 text-xs font-extrabold ${j.badge}`,children:i.length})]}),W(`p`,{class:`mt-2 mb-0 text-sm leading-6 text-slate-600`,children:j.description})]})]}),W(`form`,{class:`mt-5`,onSubmit:A,children:[W(`label`,{class:`block text-xs font-extrabold text-slate-600`,for:`${t}-domain`,children:`새 도메인`}),W(`div`,{class:`mt-2 flex flex-col gap-2 sm:flex-row`,children:[W(`input`,{"aria-describedby":m?`${t}-domain-error`:void 0,"aria-invalid":m?!0:void 0,autocomplete:`off`,class:`min-h-11 min-w-0 flex-1 rounded-xl border-2 border-slate-300 bg-slate-50 px-4 py-2.5 text-sm font-semibold text-slate-950 shadow-inner outline-none transition placeholder:text-slate-400 focus:border-teal-500 focus:bg-white focus:ring-4 focus:ring-teal-100 disabled:cursor-not-allowed disabled:bg-slate-100`,disabled:x,id:`${t}-domain`,onInput:e=>d(e.currentTarget.value),placeholder:`example.com`,spellcheck:!1,type:`text`,value:u}),W(`button`,{class:`inline-flex min-h-11 w-full shrink-0 items-center justify-center gap-2 rounded-xl border px-4 py-2.5 text-sm font-extrabold transition focus:outline-none focus-visible:ring-4 disabled:cursor-not-allowed disabled:opacity-50 sm:w-auto ${j.button}`,disabled:x||u.trim().length===0,type:`submit`,children:[W(wn,{class:`size-4`}),e?.operation===`add`&&e.action===t?`추가 중…`:`추가`]})]}),m?W(`p`,{class:`mt-2 mb-0 text-xs font-semibold leading-5 text-red-700`,id:`${t}-domain-error`,children:m}):null]})]}),W(`div`,{class:`p-4 sm:p-6`,children:[W(`label`,{class:`relative block`,for:`${t}-search`,children:[W(`span`,{class:`sr-only`,children:[j.label,` 검색`]}),W(`span`,{class:`pointer-events-none absolute inset-y-0 left-0 flex w-11 items-center justify-center text-slate-400`,children:W(En,{class:`size-4`})}),W(`input`,{class:`min-h-11 w-full rounded-xl border-2 border-slate-300 bg-slate-50 py-2.5 pr-4 pl-11 text-sm font-semibold text-slate-950 shadow-inner outline-none transition placeholder:text-slate-400 focus:border-teal-500 focus:bg-white focus:ring-4 focus:ring-teal-100`,id:`${t}-search`,onInput:e=>p(e.currentTarget.value),placeholder:`${j.label} 검색`,type:`search`,value:f})]}),b&&o?W(`div`,{class:`mt-4 overflow-hidden rounded-xl border border-slate-200`,children:[W(`section`,{"aria-label":`직접 추가한 허용 규칙`,children:[W(`button`,{"aria-controls":`allow-manual-domains`,"aria-expanded":k,class:`flex min-h-14 w-full items-center justify-between gap-3 ssh-rule-group-toggle bg-slate-50 px-4 py-3 text-left focus-visible:outline-2 focus-visible:outline-teal-500`,onClick:()=>_(!k),type:`button`,children:[W(`span`,{class:`min-w-0 text-sm font-extrabold text-slate-800`,children:[`직접 추가한 규칙 `,W(`span`,{class:`ml-1 rounded-full bg-white px-2 py-0.5 text-xs font-bold text-slate-600`,children:[T.length,C?`개 검색됨`:`개`]})]}),W(`span`,{"aria-hidden":`true`,class:`shrink-0 text-sm font-bold text-slate-500`,children:k?`접기 −`:`펼치기 +`})]}),W(`div`,{hidden:!k,id:`allow-manual-domains`,children:T.length?W(`ul`,{class:`m-0 max-h-[20rem] list-none divide-y divide-slate-200 overflow-y-auto p-0`,children:T.map(M)}):W(`p`,{class:`m-0 px-4 py-5 text-sm text-slate-500`,children:C?`검색 결과가 없습니다.`:`직접 추가한 허용 도메인이 없습니다.`})})]}),W(`section`,{"aria-label":`웹사이트 추천 규칙`,class:`border-t border-slate-200`,children:[W(`button`,{"aria-controls":`allow-recommended-domains`,"aria-expanded":O,class:`flex min-h-14 w-full items-center justify-between gap-3 ssh-rule-group-toggle bg-slate-50 px-4 py-3 text-left focus-visible:outline-2 focus-visible:outline-teal-500`,onClick:()=>y(!O),type:`button`,children:[W(`span`,{class:`min-w-0 text-sm font-extrabold text-slate-800`,children:[`추천 규칙 `,W(`span`,{class:`ml-1 rounded-full bg-white px-2 py-0.5 text-xs font-bold text-teal-800`,children:[!C&&s.length?s.length:E.length,C?`개 검색됨`:`개`]})]}),W(`span`,{"aria-hidden":`true`,class:`shrink-0 text-sm font-bold text-slate-500`,children:O?`접기 −`:`펼치기 +`})]}),W(`div`,{hidden:!O,id:`allow-recommended-domains`,children:[D.length?W(`ul`,{class:`m-0 max-h-[22rem] list-none divide-y divide-slate-200 overflow-y-auto p-0`,children:D.map(e=>W(`li`,{class:`flex min-w-0 flex-wrap items-center justify-between gap-3 px-3.5 py-3`,children:[W(`div`,{class:`min-w-0 flex-1`,children:[W(`strong`,{class:`block break-words text-sm text-slate-900`,children:e.title}),W(`div`,{class:`mt-1 flex flex-wrap gap-1.5`,children:e.domains.map(e=>W(`code`,{class:`break-all text-xs text-slate-600`,children:e},e))})]}),c?W(`button`,{class:`shrink-0 rounded-lg border border-red-200 px-3 py-2 text-xs font-bold text-red-700 hover:bg-red-50 disabled:opacity-50`,disabled:l||x,onClick:()=>c(e),type:`button`,children:`적용 해제`}):W(`span`,{class:`text-xs text-slate-500`,children:`웹사이트 관리`})]},e.id))}):E.length?W(`ul`,{class:`m-0 max-h-[20rem] list-none divide-y divide-slate-200 overflow-y-auto p-0`,children:E.map(M)}):W(`p`,{class:`m-0 px-4 py-5 text-sm text-slate-500`,children:C?`검색 결과가 없습니다.`:`추천으로 추가된 허용 도메인이 없습니다.`}),C?null:W(`p`,{class:`m-0 border-t border-slate-100 px-4 py-3 text-xs leading-5 text-slate-500`,children:`추천 규칙은 이 공유기에서 적용 해제할 수 있습니다. 새 규칙의 적용은 SmartSafeHub 웹사이트에서 관리해 주세요.`})]})]})]}):W(`div`,{class:`mt-4 max-h-[28rem] overflow-y-auto rounded-xl border border-slate-200`,children:w.length?W(`ul`,{class:`m-0 list-none divide-y divide-slate-200 p-0`,children:w.map(M)}):W(`div`,{class:`px-5 py-10 text-center`,children:[W(Dn,{class:`mx-auto size-8 text-slate-300`}),W(`p`,{class:`mt-3 mb-0 text-sm font-bold text-slate-600`,children:C?`검색 결과가 없습니다.`:j.empty})]})})]})]})});function _y({action:e,data:t,error:n,feedback:r,loading:i,onAddRule:a,onDeleteRule:o,onDismissFeedback:s,onRetry:c,onLoadRecommendedRulesStatus:l,onSyncRecommendedRules:u,onRevokeRecommendedRule:d}){let[f,p]=L(null),[m,h]=L(!1),[g,_]=L(!1),[v,y]=L(null),[x,S]=L(!1),[C,w]=L(!1),[T,E]=L(!1),D=z(null),O=z(null),k=z(null),A=z(c);A.current=c;let[j,M]=L(null),[N,P]=L(null),F=f?.domains??fy,I=f?.rules??py,ee=Xe(()=>new Set(F),[F]),te=B(e=>{p(t=>{if(!t)return e;let{domains:n,rules:r}=dy(t,e);return{...e,domains:n,rules:r}}),h(!0);let t=JSON.stringify([e.revision,e.domains]),n=!1;return e.syncStatus===`confirmed`&&k.current!==t&&(O.current!==null&&(A.current(),n=!0),k.current=t),O.current=t,D.current!==null&&e.revokeOperation?.phase===`failed`&&e.revokeOperation.argument===String(D.current)&&(D.current=null,w(!1),E(!1),P(`추천 규칙 적용 해제를 완료하지 못했습니다. 연결 상태를 확인하고 다시 시도해 주세요.`)),e.syncStatus===`confirmed`&&D.current===null&&e.revokeOperation?.phase!==`failed`&&P(null),D.current!==null&&e.syncStatus===`confirmed`&&!e.rules.some(e=>e.id===D.current)&&(D.current=null,w(!1),E(!1),P(null),M(`추천 규칙 적용 해제를 확인했습니다.`)),n},[]);R(()=>{if(!l)return;let e=l,t=!0,n=!1;function r(){n||g||x||(n=!0,e().then(e=>{t&&te(e)}).catch(()=>{}).finally(()=>{n=!1}))}r();let i=window.setInterval(r,15e3),a=()=>r(),o=()=>{document.hidden||r()};return window.addEventListener(`focus`,a),document.addEventListener(`visibilitychange`,o),()=>{t=!1,window.clearInterval(i),window.removeEventListener(`focus`,a),document.removeEventListener(`visibilitychange`,o)}},[l,g,x,te]),R(()=>{if(!C)return;let e=window.setTimeout(()=>E(!0),6e4);return()=>window.clearTimeout(e)},[C]);async function ne(){if(!(!u||g||x)){_(!0),P(null),M(null);try{let e=await u(),t=te(e);M(e.syncStatus===`confirmed`?`서버에서 규칙을 가져와 SafeShield 허용 목록을 확인했습니다.`:`추천 규칙 상태를 확인 중입니다. 진행 결과는 자동으로 갱신됩니다.`),e.syncStatus===`confirmed`&&!t&&c()}catch{P(`추천 규칙을 동기화하지 못했습니다. 서버 연결 및 기기 등록 상태를 확인해 주세요.`)}finally{_(!1)}}}async function re(){if(!v||x||!d)return;let e=v.id;D.current=e,S(!0),w(!0),E(!1),P(null),M(null);try{let t=await d(e);te(t),y(null),D.current!==null&&M(`적용 해제 요청을 처리했습니다. 공유기의 허용 목록을 확인하고 있습니다.`)}catch{y(null),P(`적용 해제 결과를 아직 확인하지 못했습니다. 공유기의 상태를 자동으로 다시 확인합니다.`);try{let e=await l?.();e&&te(e)}catch{}}finally{S(!1)}}let ie=f?.syncStage,ae=f?.syncStatus===`pending`,oe=f?.syncStatus===`delayed`,se=f?.syncStatus===`failed`,ce=ie===`server_sync`?`서버 동기화 중`:ie===`safeshield_refreshing`?`SafeShield 갱신 중`:ie===`applying`?`추천 규칙 적용 중`:ie===`verifying`?`허용 목록 확인 중`:oe?`재확인 지연`:se?`동기화 실패`:ae?`동기화 대기`:`허용 목록 확인`,le=ie===`safeshield_refreshing`&&ae?`SafeShield가 보호 목록을 갱신하고 있습니다. 갱신이 끝나면 추천 규칙을 자동으로 다시 확인합니다.`:ie===`applying`&&ae?`추천 규칙을 SafeShield 허용 목록에 반영하고 있습니다.`:ie===`verifying`&&ae?`SafeShield 허용 목록에서 추천 규칙 반영 결과를 확인하고 있습니다.`:ie===`server_sync`&&ae?`SmartSafeHub 서버에서 최신 추천 규칙을 확인하고 있습니다.`:oe?`서버 응답이 지연되고 있습니다. 이미 확인한 허용 규칙은 유지되며 자동으로 다시 시도합니다.`:se?f?.syncReason===`auth_rejected`?`SmartSafeHub 기기 인증이 거부되었습니다. 공유기 계정 연결 상태를 확인해 주세요.`:f?.syncReason===`api_rejected`?`서버가 추천 규칙 요청을 거부했습니다. Hub API 배포 상태를 확인해 주세요.`:f?.syncReason===`invalid_response`?`서버 응답을 확인할 수 없습니다. 규칙은 유지되며 다음 동기화에서 다시 확인합니다.`:`추천 규칙 반영을 완료하지 못했습니다. 다음 자동 동기화에서 다시 시도합니다.`:ae?`추천 규칙의 적용 결과를 기다리고 있습니다.`:null,ue=C&&D.current!==null?{tone:`amber`,label:T?`적용 해제 처리 지연`:`적용 해제 확인 중`,description:T?`적용 해제 확인이 지연되고 있습니다. SafeShield 갱신 중이라면 완료 후 자동으로 재확인합니다.`:le??`적용 해제 상태를 확인하고 있습니다. 확인되면 목록과 상태가 자동 갱신됩니다.`}:ae||oe||se?{tone:se?`red`:`amber`,label:ce,description:le??``}:N?{tone:`red`,label:`상태 확인 필요`,description:N}:f?.syncWarning?{tone:`amber`,label:`서버 재확인 지연`,description:`이미 확인한 허용 규칙은 유지되며 자동으로 다시 시도합니다.`}:j?{tone:`green`,label:`허용 목록 확인`,description:j}:null;return i&&!t?W(vs,{}):n&&!t?W(ys,{message:n,onRetry:c}):t?W(b,{children:[v?W(`div`,{class:`fixed inset-0 z-50 flex items-center justify-center bg-slate-950/70 p-4`,role:`presentation`,onClick:()=>{x||y(null)},children:W(`section`,{"aria-labelledby":`recommended-revoke-title`,"aria-modal":`true`,class:`w-full max-w-md rounded-2xl border border-slate-200 bg-white p-5 shadow-xl`,onClick:e=>e.stopPropagation(),role:`dialog`,children:[W(`h2`,{class:`m-0 text-lg font-extrabold text-slate-950`,id:`recommended-revoke-title`,children:`추천 규칙 적용 해제`}),W(`p`,{class:`mt-3 text-sm text-slate-700`,children:[W(`strong`,{children:v.title}),` 규칙을 이 공유기에서 해제할까요?`]}),W(`p`,{class:`mt-2 text-xs leading-5 text-slate-600`,children:[`포함된 도메인 `,v.domains.length,`개를 다시 확인합니다. 직접 추가한 허용 규칙과 다른 추천 규칙에 필요한 도메인은 유지됩니다.`]}),W(`div`,{class:`mt-5 flex justify-end gap-2`,children:[W(`button`,{class:`rounded-lg border border-slate-200 px-4 py-2 text-sm font-bold text-slate-700 disabled:cursor-wait disabled:opacity-50`,disabled:x,onClick:()=>y(null),type:`button`,children:`취소`}),W(`button`,{class:`inline-flex min-w-24 items-center justify-center gap-2 rounded-lg bg-red-700 px-4 py-2 text-sm font-bold text-white transition hover:bg-red-800 disabled:cursor-progress disabled:bg-red-700 disabled:opacity-80`,"aria-busy":x,disabled:x,onClick:()=>void re(),type:`button`,children:[x?W(`svg`,{class:`size-4 animate-spin`,"aria-hidden":`true`,viewBox:`0 0 24 24`,fill:`none`,children:[W(`circle`,{cx:`12`,cy:`12`,r:`9`,stroke:`currentColor`,"stroke-width":`3`,opacity:`0.25`}),W(`path`,{d:`M21 12a9 9 0 0 0-9-9`,stroke:`currentColor`,"stroke-width":`3`,"stroke-linecap":`round`})]}):null,W(`span`,{role:`status`,children:x?`해제 중…`:`적용 해제`})]})]})]})}):null,W(J,{eyebrow:`Local overrides`,title:`사용자 도메인 규칙`,description:`허용 목록은 오탐을 해제하고, 차단 목록은 원하는 도메인을 SafeShield 보호 목록에 추가합니다.`}),W(`section`,{"aria-label":`규칙 개수 요약`,class:`min-w-0`,children:W(`dl`,{class:`m-0 grid grid-cols-1 gap-3 sm:grid-cols-3`,children:[W(`div`,{class:`min-w-0 rounded-2xl border border-slate-200 bg-white px-5 py-4 shadow-sm shadow-slate-900/5`,children:[W(`dt`,{class:`text-sm font-extrabold text-emerald-700`,children:`허용 도메인`}),W(`dd`,{class:`mt-2 mb-0 ml-0 text-2xl font-black tracking-tight text-slate-950`,children:[t.counts.allow,W(`span`,{class:`ml-1 text-sm font-bold text-slate-500`,children:`개`})]}),W(`p`,{class:`mt-1 mb-0 text-xs text-slate-500`,children:`차단하지 않도록 등록한 도메인`})]}),W(`div`,{class:`min-w-0 rounded-2xl border border-slate-200 bg-white px-5 py-4 shadow-sm shadow-slate-900/5`,children:[W(`dt`,{class:`text-sm font-extrabold text-red-700`,children:`차단 도메인`}),W(`dd`,{class:`mt-2 mb-0 ml-0 text-2xl font-black tracking-tight text-slate-950`,children:[t.counts.block,W(`span`,{class:`ml-1 text-sm font-bold text-slate-500`,children:`개`})]}),W(`p`,{class:`mt-1 mb-0 text-xs text-slate-500`,children:`추가로 차단하도록 등록한 도메인`})]}),W(`div`,{class:`min-w-0 rounded-2xl border border-slate-200 bg-white px-5 py-4 shadow-sm shadow-slate-900/5`,children:[W(`dt`,{class:`text-sm font-extrabold text-slate-600`,children:`전체 등록 규칙`}),W(`dd`,{class:`mt-2 mb-0 ml-0 text-2xl font-black tracking-tight text-slate-950`,children:[t.counts.total,W(`span`,{class:`ml-1 text-sm font-bold text-slate-500`,children:`개`})]}),W(`p`,{class:`mt-1 mb-0 text-xs text-slate-500`,children:`허용 및 차단 규칙의 합계`})]})]})}),W(`div`,{class:`mt-7`,children:W(J,{eyebrow:`Recommendations`,title:`추천 사용자 규칙`,description:`웹사이트에서 선택한 권장 허용 규칙의 동기화 상태를 확인하고 관리합니다.`})}),W(`section`,{class:`rounded-2xl border border-slate-200 bg-white p-4 shadow-sm shadow-slate-900/5 sm:p-5`,"aria-label":`추천 사용자 규칙`,children:[W(`div`,{class:`flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between`,children:[W(`div`,{class:`min-w-0 flex-1`,children:[W(`div`,{class:`flex flex-wrap items-center gap-2`,children:[W(`span`,{class:`grid size-8 place-items-center rounded-lg bg-teal-50 text-teal-700`,children:W(Sn,{class:`size-4`})}),W(`h3`,{class:`m-0 text-base font-extrabold text-slate-950`,children:`동기화 상태`}),l?W(`span`,{"aria-live":`polite`,class:`rounded-full px-2.5 py-1 text-xs font-bold ${f?.syncStatus===`confirmed`?`bg-emerald-100 text-emerald-800`:f?.syncStatus===`failed`?`bg-red-100 text-red-800`:`bg-amber-100 text-amber-800`}`,children:f?.syncStatus===`confirmed`?`허용 목록 확인`:ce}):null]}),W(`p`,{class:`mt-2 mb-0 text-sm leading-6 text-slate-600`,children:`웹사이트에서 선택한 허용 규칙을 약 30초마다 확인합니다. 적용 해제는 이 공유기에서도 할 수 있습니다.`}),W(`div`,{class:`mt-2 flex flex-wrap items-center gap-x-4 gap-y-1 text-xs font-semibold text-slate-600`,children:[W(`span`,{children:[`추천으로 추가 `,W(`strong`,{class:`text-teal-800`,children:[m?F.length:`—`,`개 도메인`]})]}),f?.syncStatus===`confirmed`?W(`span`,{children:[`허용 확인 `,W(`strong`,{class:`text-emerald-800`,children:[f.confirmedDomains.length,`개`]})]}):null,f?.lastCheckedAt?W(`span`,{children:[f.syncWarning?`최근 재확인 시도`:`최근 확인`,`: `,W(Rv,{timestamp:f.lastCheckedAt})]}):null]})]}),W(`div`,{class:`flex w-full flex-wrap gap-2 sm:w-auto lg:shrink-0`,children:[u?W(`button`,{class:`inline-flex min-h-10 flex-1 items-center justify-center rounded-xl border border-slate-300 bg-white px-4 py-2.5 text-sm font-extrabold text-slate-800 transition hover:bg-slate-50 disabled:cursor-wait disabled:opacity-60 sm:flex-none`,disabled:g,onClick:()=>void ne(),type:`button`,children:g?`동기화 중…`:`지금 동기화`}):null,W(`a`,{class:`inline-flex min-h-10 flex-1 items-center justify-center rounded-xl border border-teal-700 bg-teal-700 px-4 py-2.5 text-sm font-extrabold text-white transition hover:bg-teal-800 sm:flex-none`,href:`https://www.smartsafehub.com/dashboard/recommended-rules/`,target:`_blank`,rel:`noopener noreferrer`,children:`추천 규칙 보기 ↗`})]})]}),ue?W(`div`,{"aria-live":`polite`,class:`mt-3 flex items-start gap-2 rounded-xl px-3 py-2.5 text-sm ${ue.tone===`red`?`bg-red-50 text-red-800`:ue.tone===`green`?`bg-emerald-50 text-emerald-800`:`bg-amber-50 text-amber-800`}`,role:`status`,children:[W(`span`,{"aria-hidden":`true`,class:`mt-1 size-2 shrink-0 rounded-full ${ue.tone===`red`?`bg-red-500`:ue.tone===`green`?`bg-emerald-500`:`bg-amber-500`}`}),W(`div`,{class:`min-w-0`,children:[W(`strong`,{children:ue.label}),W(`p`,{class:`mt-1 mb-0 leading-5`,children:ue.description})]})]}):null,W(`p`,{class:`mt-3 mb-0 text-xs leading-5 text-slate-500`,children:`추천 규칙 동기화는 SafeShield 차단 목록 갱신과 별개입니다. 허용 목록 확인은 DNS 처리나 서비스 재생 성공을 보장하지 않습니다.`})]}),t.applyLocalOverrides?null:W(`section`,{class:`mt-4 flex items-start gap-3 rounded-2xl border border-amber-200 bg-amber-50 p-4`,role:`alert`,children:[W(Cn,{class:`mt-0.5 size-5 shrink-0 text-amber-700`}),W(`div`,{children:[W(`p`,{class:`m-0 text-sm font-extrabold text-amber-950`,children:`로컬 규칙 적용이 꺼져 있습니다`}),W(`p`,{class:`mt-1 mb-0 text-sm leading-6 text-amber-800`,children:`규칙은 저장되지만 SafeShield의 apply_local_overrides 설정을 켜기 전까지 DNS에 반영되지 않습니다.`})]})]}),t.safeshieldEnabled?null:W(`section`,{class:`mt-4 flex items-start gap-3 rounded-2xl border border-slate-200 bg-slate-100 p-4`,role:`status`,children:[W(qt,{class:`mt-0.5 size-5 shrink-0 text-slate-600`}),W(`div`,{children:[W(`p`,{class:`m-0 text-sm font-extrabold text-slate-950`,children:`SafeShield가 비활성화되어 있습니다`}),W(`p`,{class:`mt-1 mb-0 text-sm leading-6 text-slate-600`,children:`규칙을 미리 저장할 수 있으며, SafeShield를 켜고 차단 목록을 갱신하면 적용됩니다.`})]})]}),r?W(`section`,{"aria-live":`polite`,class:`mt-4 flex flex-col items-start gap-3 rounded-2xl border border-teal-200 bg-teal-50 sm:flex-row sm:justify-between p-4`,children:[W(`div`,{class:`flex min-w-0 items-start gap-3`,children:[W(Sn,{class:`mt-0.5 size-5 shrink-0 text-teal-700`}),W(`p`,{class:`m-0 text-sm font-semibold leading-6 text-teal-950`,children:r})]}),W(`button`,{class:`shrink-0 rounded-lg border-0 bg-transparent px-2 py-1 text-xs font-extrabold text-teal-800 hover:bg-teal-100`,onClick:s,type:`button`,children:`닫기`})]}):null,n&&t?W(`section`,{"aria-live":`assertive`,class:`mt-4 flex items-start gap-3 rounded-2xl border border-red-200 bg-red-50 p-4`,children:[W(Cn,{class:`mt-0.5 size-5 shrink-0 text-red-700`}),W(`p`,{class:`m-0 text-sm font-semibold leading-6 text-red-900`,children:n})]}):null,W(`section`,{class:`mt-5 grid gap-5 xl:grid-cols-2`,children:[W(gy,{action:e,kind:`allow`,onAddRule:a,onDeleteRule:o,rules:t.allow,recommendedDomainSet:ee,recommendedKnown:m,recommendedRules:I,...d?{onRequestRevoke:y}:{},revokeBusy:x}),W(gy,{action:e,kind:`block`,onAddRule:a,onDeleteRule:o,rules:t.block,recommendedDomainSet:ee,recommendedKnown:m,recommendedRules:I,revokeBusy:x})]})]}):W(ys,{message:`사용자 규칙 응답이 비어 있습니다.`,onRetry:c})}function vy(e,t){return U(`smartsafehub`,`system_root_password_change`,{current_password:e,new_password:t})}var yy=Array.from({length:24},(e,t)=>{let n=String(t).padStart(2,`0`);return{value:n,label:`${n}시`}}),by=Array.from({length:60},(e,t)=>{let n=String(t).padStart(2,`0`);return{value:n,label:`${n}분`}});function xy(e){let t=/^([01]\d|2[0-3]):([0-5]\d)$/.exec(e);return{hour:t?.[1]??`03`,minute:t?.[2]??`00`}}function Sy({ariaLabel:e,className:t=``,disabled:n=!1,id:r,onChange:i,value:a,variant:o=`default`}){let{hour:s,minute:c}=xy(a),l=(e,t)=>{i(`${e}:${t}`)};return W(`div`,{"aria-label":e,class:`ssh-time-select ${t}`.trim(),"data-time-select":r,role:`group`,children:[W(W_,{ariaLabel:`${e} 시`,disabled:n,id:`${r}-hour`,onChange:e=>l(e,c),options:yy,value:s,variant:o}),W(W_,{ariaLabel:`${e} 분`,disabled:n,id:`${r}-minute`,onChange:e=>l(s,e),options:by,value:c,variant:o})]})}function Cy(){try{return Intl.DateTimeFormat().resolvedOptions().timeZone||null}catch{return null}}function wy(e){return W(`article`,{class:`h-full min-w-0 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5 sm:p-6 ${e.className??``}`,children:[W(`div`,{class:`flex min-w-0 items-start justify-between gap-3`,children:[W(`div`,{class:`flex min-w-0 items-start gap-4`,children:[W(`div`,{class:`flex size-11 shrink-0 items-center justify-center rounded-xl bg-slate-100 text-slate-600`,children:e.icon}),W(`div`,{class:`min-w-0`,children:[W(`div`,{class:`flex flex-wrap items-center gap-2`,children:[W(`h2`,{class:`m-0 text-lg font-black text-slate-950`,children:e.title}),e.titleActions]}),W(`p`,{class:`mt-1.5 mb-0 text-sm leading-6 text-slate-500`,children:e.description})]})]}),e.headerActions?W(`div`,{class:`shrink-0`,children:e.headerActions}):null]}),W(`div`,{class:`mt-5`,children:e.children})]})}function Ty(e){let[t,n]=L(``),[r,i]=L(e.data?.localtime??0),a=Cy();R(()=>{e.data?.zonename&&n(e.data.zonename)},[e.data?.zonename]),R(()=>{let t=e.data?.localtime??0,n=Date.now();if(i(t),!Number.isFinite(t)||t<=0)return;let r=window.setInterval(()=>{i(t+Math.floor((Date.now()-n)/1e3))},1e3);return()=>window.clearInterval(r)},[e.data?.localtime]);let o=e.data?Object.keys(e.data.timezones).sort((e,t)=>e.localeCompare(t)):[],s=Po(r,e.data?.zonename??`UTC`),c=a!==null&&e.data?.timezones[a]!==void 0,l=e.data!==null&&t.length>0&&t!==e.data.zonename;return W(b,{children:[W(wy,{description:`공유기의 기준 시간대를 설정합니다. 로그, 통계, 자동 설치와 예약 재부팅 시각도 이 시간대를 기준으로 동작합니다.`,icon:W(dn,{class:`size-5`}),title:`시간 및 시간대`,children:[(e.saveError||e.saveMessage)&&W(`div`,{class:`mb-5 flex min-w-0 items-start justify-between gap-3 rounded-xl border px-4 py-3 text-sm font-bold ${e.saveError?`border-rose-200 bg-rose-50 text-rose-800`:`border-emerald-200 bg-emerald-50 text-emerald-800`}`,children:[W(`span`,{class:`min-w-0`,children:e.saveError||e.saveMessage}),W(`button`,{class:`shrink-0 rounded-lg px-2 py-1 text-xs font-extrabold hover:bg-black/5`,onClick:e.onDismissFeedback,type:`button`,children:`닫기`})]}),e.error&&!e.data?W(`div`,{class:`rounded-xl border border-rose-200 bg-rose-50 p-4`,children:[W(`p`,{class:`m-0 text-sm font-bold text-rose-800`,children:e.error}),W(`button`,{class:`mt-3 inline-flex min-h-10 items-center rounded-xl border border-rose-300 bg-white px-3 py-2 text-xs font-extrabold text-rose-700 transition hover:bg-rose-100`,onClick:e.onRetry,type:`button`,children:`다시 불러오기`})]}):W(b,{children:[W(`div`,{class:`grid min-w-0 grid-cols-1 gap-3 sm:grid-cols-2`,children:[W(`div`,{class:`rounded-xl bg-slate-50 p-4`,children:[W(`p`,{class:`m-0 text-xs font-extrabold uppercase tracking-[0.12em] text-slate-500`,children:`현재 시간`}),W(`p`,{class:`mt-2 mb-0 text-sm font-extrabold leading-6 text-slate-900`,children:s}),W(`p`,{class:`mt-1 mb-0 break-words text-xs leading-5 text-slate-500`,children:e.data?.zonename??`시간대 확인 중`})]}),W(`div`,{class:`rounded-xl bg-slate-50 p-4`,children:[W(`p`,{class:`m-0 text-xs font-extrabold uppercase tracking-[0.12em] text-slate-500`,children:`시간 동기화`}),W(`p`,{class:`mt-2 mb-0 text-sm font-extrabold ${e.data?.ntpEnabled?`text-emerald-700`:`text-amber-700`}`,children:e.data?.ntpEnabled?`자동 동기화 설정됨`:`자동 동기화 꺼짐`}),W(`p`,{class:`mt-1 mb-0 text-xs leading-5 text-slate-500`,children:`장치의 기본 NTP 설정 상태입니다.`}),W(`button`,{class:`mt-3 inline-flex min-h-9 items-center justify-center gap-2 rounded-lg border border-slate-300 bg-white px-3 py-2 text-xs font-extrabold text-slate-700 transition hover:bg-slate-100 disabled:cursor-not-allowed disabled:opacity-50`,disabled:!e.data?.ntpEnabled||e.saving||e.syncing,onClick:()=>void e.onSync(),type:`button`,children:e.syncing?`동기화 요청 중`:`지금 동기화`})]})]}),W(`div`,{class:`mt-5`,children:[W(`label`,{class:`mb-2 block text-sm font-extrabold text-slate-800`,for:`smartsafehub-timezone`,children:`시간대`}),W(W_,{ariaLabel:`시간대`,disabled:e.loading||e.saving||e.syncing||!e.data,id:`smartsafehub-timezone`,onChange:n,options:e.data?o.map(e=>({value:e,label:e})):[{value:``,label:`시간대 불러오는 중`}],value:t}),W(`p`,{class:`mt-2 mb-0 text-xs leading-5 text-slate-500`,children:`시간대를 변경하면 시스템에 즉시 적용되며 자동 설치와 예약 재부팅 일정도 새 기준 시간으로 다시 계산합니다.`})]}),W(`div`,{class:`mt-4 flex flex-col gap-3 sm:flex-row sm:flex-wrap sm:justify-end`,children:[c&&a!==t&&W(`button`,{class:`inline-flex min-h-11 w-full items-center justify-center rounded-xl border border-slate-300 bg-white px-4 py-2.5 text-sm font-extrabold text-slate-700 transition hover:bg-slate-50 disabled:opacity-60 sm:w-auto`,disabled:e.saving||e.syncing,onClick:()=>n(a),type:`button`,children:`브라우저 시간대 사용`}),W(`button`,{class:`inline-flex min-h-11 w-full items-center justify-center rounded-xl bg-teal-700 px-4 py-2.5 text-sm font-extrabold text-white transition hover:bg-teal-800 disabled:cursor-not-allowed disabled:opacity-50 sm:w-auto`,disabled:!l||e.saving||e.syncing,onClick:()=>void e.onSave(t),type:`button`,children:e.saving?`시간대 저장 중`:`시간대 저장`})]})]})]}),W(`div`,{class:`min-w-0 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5 sm:p-6`,children:W(Ly,{data:e.scheduledRebootData,error:e.scheduledRebootError,loading:e.scheduledRebootLoading,onDismissFeedback:e.onDismissScheduledRebootFeedback,onRetry:e.onRetry,onSave:e.onSaveScheduledReboot,saveError:e.scheduledRebootSaveError,saveMessage:e.scheduledRebootSaveMessage,saving:e.scheduledRebootSaving})})]})}function Ey(e){return e===`initializing`?{badge:`bg-teal-100 text-teal-800`,panel:`border-teal-200 bg-teal-50`,label:`준비 중`}:e===`critical`?{badge:`bg-rose-100 text-rose-800`,panel:`border-rose-200 bg-rose-50`,label:`이상`}:e===`warning`?{badge:`bg-amber-100 text-amber-800`,panel:`border-amber-200 bg-amber-50`,label:`주의`}:e===`unknown`?{badge:`bg-slate-200 text-slate-700`,panel:`border-slate-200 bg-slate-50`,label:`확인 불가`}:{badge:`bg-emerald-100 text-emerald-800`,panel:`border-emerald-200 bg-emerald-50`,label:`정상`}}function Dy(e){if(!e.enabled)return`보고 중지됨`;switch(e.lastResult){case`reported`:return`정상 보고`;case`initializing`:return e.lastReportAt>0?`상태 갱신 중`:`첫 보고 진행 중`;case`failed`:return`최근 보고 실패`;case`idle`:return`다음 보고 대기`;case`ineligible`:return`멤버십 확인 필요`;case`disabled`:case`never`:return`첫 보고 준비 중`;default:return`보고 준비 중`}}function Oy(e){return e.lastReportAt>0?jo(e.lastReportAt):e.enabled?`첫 보고 대기 중`:`보고 기록 없음`}function ky(e){let t=e?.plan?.trim().toUpperCase(),n=e?.entitlementStatus?.trim().toLowerCase();return t&&(n===`trial`||n===`trialing`)?`${t} · 체험`:t||`유료 · Trial`}function Ay(e){let t=e.data,n=Ey(t?.overall??`unknown`),r=t?.checks.filter(e=>e.status!==`ok`)??[],i=r.length>0?r.slice(0,4):t?.checks.filter(e=>[`system.memory`,`network.wan`,`service.dnsmasq`,`safeshield.runtime`].includes(e.id))??[],a=t?.reporter,o=a?.enabled===!0,s=a?.eligible===!0||o,c=e.savingReporter?o?`원격 상태 보고를 켜고 있습니다.`:`원격 상태 보고를 끄고 있습니다.`:o?`원격 상태 보고가 켜져 있습니다.`:`원격 상태 보고가 꺼져 있습니다.`,l=o?`장치 상태가 바뀌면 즉시 보고하고, 정상 상태에서도 주기적으로 SmartSafeHub 서버에 보고합니다.`:`로컬 진단은 계속 동작하지만 장치 상태는 SmartSafeHub 서버에 자동으로 전송되지 않습니다.`;return W(b,{children:[W(wy,{description:`SmartSafeHub가 장치 상태를 직접 점검하고 이상 항목을 알려줍니다.`,icon:W(hn,{class:`size-5`}),title:`장치 진단`,children:[(e.actionError||e.actionMessage)&&W(`div`,{class:`mb-4 flex items-start justify-between gap-3 rounded-xl border px-4 py-3 text-sm font-bold ${e.actionError?`border-rose-200 bg-rose-50 text-rose-800`:`border-emerald-200 bg-emerald-50 text-emerald-800`}`,children:[W(`span`,{children:e.actionError||e.actionMessage}),W(`button`,{class:`shrink-0 rounded-lg px-2 py-1 text-xs font-extrabold hover:bg-black/5`,onClick:e.onDismissFeedback,type:`button`,children:`닫기`})]}),e.error&&!t?W(`div`,{class:`rounded-xl border border-rose-200 bg-rose-50 p-4`,children:[W(`p`,{class:`m-0 text-sm font-bold text-rose-800`,children:e.error}),W(`button`,{class:`mt-3 inline-flex min-h-10 items-center rounded-xl border border-rose-300 bg-white px-3 py-2 text-xs font-extrabold text-rose-700 transition hover:bg-rose-100`,onClick:e.onRetry,type:`button`,children:`다시 불러오기`})]}):W(b,{children:[W(`div`,{class:`rounded-xl border p-4 ${n.panel}`,children:W(`div`,{class:`flex flex-wrap items-start justify-between gap-3`,children:[W(`div`,{class:`flex min-w-0 items-start gap-3`,children:[W(`span`,{class:`mt-0.5 shrink-0`,children:t?.overall===`ok`?W(Sn,{class:`size-5 text-emerald-700`}):t?.overall===`initializing`?W(dn,{class:`size-5 text-teal-700`}):W(Cn,{class:`size-5 text-amber-700`})}),W(`div`,{class:`min-w-0`,children:[W(`p`,{class:`m-0 text-sm font-black text-slate-950`,children:t?.summary.message??`장치 상태를 확인하고 있습니다.`}),W(`p`,{class:`mt-1 mb-0 text-xs leading-5 text-slate-600`,children:[`마지막 진단 : `,t?jo(t.generatedAt):`확인 중`]})]})]}),W(`span`,{class:`rounded-full px-2.5 py-1 text-xs font-extrabold ${n.badge}`,children:n.label})]})}),t&&W(`div`,{class:`mt-4 grid grid-cols-3 gap-2`,children:[W(`div`,{class:`rounded-xl bg-slate-50 p-3`,children:[W(`p`,{class:`m-0 text-[11px] font-extrabold text-slate-500`,children:`진단 항목`}),W(`p`,{class:`mt-1 mb-0 text-lg font-black text-slate-950`,children:t.summary.total})]}),W(`div`,{class:`rounded-xl bg-slate-50 p-3`,children:[W(`p`,{class:`m-0 text-[11px] font-extrabold text-slate-500`,children:`주의`}),W(`p`,{class:`mt-1 mb-0 text-lg font-black text-amber-700`,children:t.summary.warning})]}),W(`div`,{class:`rounded-xl bg-slate-50 p-3`,children:[W(`p`,{class:`m-0 text-[11px] font-extrabold text-slate-500`,children:`이상`}),W(`p`,{class:`mt-1 mb-0 text-lg font-black text-rose-700`,children:t.summary.critical})]})]}),i.length>0&&W(`div`,{class:`mt-4 space-y-2`,children:i.map(e=>{let t=Ey(e.status);return W(`div`,{class:`flex min-w-0 items-start justify-between gap-3 rounded-xl border border-slate-200 px-3 py-2.5`,children:[W(`div`,{class:`min-w-0`,children:[W(`p`,{class:`m-0 text-xs font-extrabold text-slate-900`,children:e.label}),W(`p`,{class:`mt-1 mb-0 text-xs leading-5 text-slate-500`,children:e.message})]}),W(`span`,{class:`shrink-0 rounded-full px-2 py-1 text-[11px] font-extrabold ${t.badge}`,children:t.label})]},e.id)})}),W(`div`,{class:`mt-4 flex flex-col gap-2 sm:flex-row`,children:[W(`button`,{class:`inline-flex min-h-11 w-full items-center justify-center rounded-xl bg-teal-700 px-4 py-2.5 text-sm font-extrabold text-white transition hover:bg-teal-800 disabled:cursor-wait disabled:opacity-60 sm:w-auto`,disabled:e.running||e.loading,onClick:e.onRun,type:`button`,children:e.running?`진단 중`:`지금 진단`}),W(`button`,{class:`inline-flex min-h-11 w-full items-center justify-center rounded-xl border border-slate-300 bg-white px-4 py-2.5 text-sm font-extrabold text-slate-800 transition hover:bg-slate-50 disabled:cursor-wait disabled:opacity-60 sm:w-auto`,disabled:e.downloadBusy,onClick:e.onDownload,type:`button`,children:e.downloadBusy?`진단 정보 생성 중`:`진단 정보 다운로드`})]}),W(`p`,{class:`mt-3 mb-0 text-xs leading-5 text-slate-500`,children:`다운로드 파일에는 호스트명, WAN IP와 Wi-Fi SSID 같은 네트워크 식별 정보가 포함될 수 있으므로 외부 전달 전에 내용을 확인해 주세요.`})]})]}),W(`div`,{class:`grid min-w-0 content-start gap-4`,children:[W(wy,{description:`장치의 최소 상태 정보와 이상 코드만 서버로 전송합니다. 직접 켜고 언제든지 끌 수 있습니다.`,icon:W(qt,{class:`size-5`}),title:`원격 상태 보고`,titleActions:W(`span`,{class:`inline-flex rounded-full bg-slate-100 px-2.5 py-1 text-[11px] font-extrabold uppercase tracking-[0.08em] text-slate-600`,children:ky(a)}),headerActions:W(`button`,{"aria-checked":a?.enabled===!0,"aria-label":`원격 상태 보고 사용`,class:`ssh-switch-control relative inline-flex shrink-0 rounded-full border transition focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-100 disabled:cursor-not-allowed disabled:opacity-50 ${a?.enabled?`border-teal-600 bg-teal-600`:`border-slate-300 bg-slate-200`}`,disabled:!t||e.savingReporter||!s,onClick:()=>e.onSetReporter(!(a?.enabled??!1)),role:`switch`,type:`button`,children:W(`span`,{"aria-hidden":`true`,class:`ssh-switch-thumb absolute top-1 shadow-sm transition-[left] ${a?.enabled?`left-6`:`left-1`}`})}),children:W(`div`,{class:`min-w-0`,children:a?.eligible?W(`div`,{class:`rounded-xl bg-slate-50 p-4`,children:[W(`div`,{class:`mb-4 flex items-start gap-3 rounded-xl border p-3 ${o?`border-emerald-200 bg-emerald-50`:`border-slate-200 bg-white`}`,children:[W(`span`,{class:`mt-0.5 shrink-0`,children:o?W(Sn,{class:`size-5 text-emerald-700`}):W(mn,{class:`size-5 text-slate-500`})}),W(`div`,{class:`min-w-0 flex-1`,children:[W(`div`,{class:`flex flex-wrap items-center gap-2`,children:[W(`p`,{class:`m-0 text-sm font-black text-slate-950`,children:c}),W(`span`,{class:`rounded-full px-2 py-0.5 text-[10px] font-extrabold ${e.savingReporter?`bg-slate-200 text-slate-700`:o?`bg-emerald-100 text-emerald-800`:`bg-slate-200 text-slate-700`}`,children:e.savingReporter?`설정 중`:o?`켜짐`:`꺼짐`})]}),W(`p`,{class:`mt-1 mb-0 text-xs leading-5 text-slate-600`,children:l})]})]}),W(`div`,{class:`grid grid-cols-1 gap-3 sm:grid-cols-2`,children:[W(`div`,{children:[W(`p`,{class:`m-0 text-[11px] font-extrabold text-slate-500`,children:`최근 전송 상태`}),W(`p`,{class:`mt-1 mb-0 text-sm font-black text-slate-900`,children:e.savingReporter?`설정 반영 중`:Dy(a)})]}),W(`div`,{children:[W(`p`,{class:`m-0 text-[11px] font-extrabold text-slate-500`,children:`마지막 서버 보고`}),W(`p`,{class:`mt-1 mb-0 text-sm font-black text-slate-900`,children:Oy(a)})]})]}),a.enabled&&a.lastErrorCode&&W(`p`,{class:`mt-3 mb-0 text-xs font-bold text-rose-700`,children:[`최근 보고 오류 : `,a.lastErrorCode]})]}):W(`div`,{class:`rounded-xl border border-slate-200 bg-slate-50 p-4`,children:[W(`p`,{class:`m-0 text-sm font-extrabold text-slate-900`,children:`원격 상태 보고는 유료 멤버십 또는 체험 기간에 사용할 수 있습니다.`}),W(`a`,{class:`mt-3 inline-flex text-xs font-extrabold text-teal-700 hover:text-teal-800`,href:`https://www.smartsafehub.com/pricing/`,rel:`noopener noreferrer`,target:`_blank`,children:`멤버십 알아보기`})]})})}),W(wy,{description:`진단에 필요한 최소한의 상태 정보만 전송하며, 네트워크 식별 정보와 이용 내용은 보내지 않습니다.`,icon:W(qt,{class:`size-5`}),title:`개인정보 보호`,children:W(`div`,{class:`grid grid-cols-1 gap-3 sm:grid-cols-2`,children:[W(`div`,{class:`rounded-xl border border-slate-200 bg-slate-50 p-3.5`,children:[W(`div`,{class:`flex items-center gap-2`,children:[W(`span`,{class:`flex size-7 shrink-0 items-center justify-center rounded-lg bg-white text-slate-600`,children:W(gn,{class:`size-4`})}),W(`p`,{class:`m-0 text-sm font-black text-slate-900`,children:`전송되는 정보`})]}),W(`div`,{class:`mt-3 space-y-3`,children:[W(`div`,{children:[W(`p`,{class:`m-0 text-[11px] font-extrabold text-slate-500`,children:`기기 상태 수치`}),W(`p`,{class:`mt-1 mb-0 text-xs font-semibold leading-5 text-slate-700`,children:`메모리 · 부하 · 저장 공간`})]}),W(`div`,{children:[W(`p`,{class:`m-0 text-[11px] font-extrabold text-slate-500`,children:`진단 정보`}),W(`p`,{class:`mt-1 mb-0 text-xs font-semibold leading-5 text-slate-700`,children:`진단 상태 · 이상 코드 · 보고 시각`})]})]})]}),W(`div`,{class:`rounded-xl border border-teal-200 bg-teal-50 p-3.5`,children:[W(`div`,{class:`flex items-center gap-2`,children:[W(`span`,{class:`flex size-7 shrink-0 items-center justify-center rounded-lg bg-white text-teal-700`,children:W(qt,{class:`size-4`})}),W(`p`,{class:`m-0 text-sm font-black text-slate-900`,children:`전송하지 않는 정보`})]}),W(`div`,{class:`mt-3 space-y-3`,children:[W(`div`,{children:[W(`p`,{class:`m-0 text-[11px] font-extrabold text-teal-800`,children:`개인 식별 · 네트워크 정보`}),W(`p`,{class:`mt-1 mb-0 text-xs font-semibold leading-5 text-slate-700`,children:`호스트명 · WAN IP · Wi-Fi SSID/MAC`})]}),W(`div`,{children:[W(`p`,{class:`m-0 text-[11px] font-extrabold text-teal-800`,children:`이용 내용 및 원문 데이터`}),W(`p`,{class:`mt-1 mb-0 text-xs font-semibold leading-5 text-slate-700`,children:`DNS 요청 내용 · 시스템 로그 원문`})]})]})]})]})})]})]})}function jy(e){let[t,n]=L(!1);return W(`label`,{class:`block min-w-0`,for:e.id,children:[W(`span`,{class:`mb-2 block text-sm font-extrabold text-slate-800`,children:e.label}),W(`span`,{class:`relative block min-w-0`,children:[W(`input`,{autoComplete:e.autoComplete,class:`ssh-password-input min-h-11 w-full rounded-xl border border-slate-300 bg-white px-3 py-2.5 pr-12 text-sm font-bold text-slate-900 outline-none transition focus:border-teal-500 focus:ring-2 focus:ring-teal-100 disabled:cursor-wait disabled:opacity-60`,disabled:e.disabled,id:e.id,name:e.name,onInput:t=>e.onInput(t.currentTarget.value),placeholder:e.placeholder,...t?{type:`text`}:{type:`password`},value:e.value}),W(`button`,{"aria-label":t?`${e.label} 숨기기`:`${e.label} 표시`,"aria-pressed":t,class:`absolute inset-y-0 right-1 my-auto flex size-9 items-center justify-center rounded-lg text-slate-500 transition hover:bg-slate-100 hover:text-slate-800 disabled:opacity-50`,disabled:e.disabled,onClick:()=>n(e=>!e),title:t?`비밀번호 숨기기`:`비밀번호 표시`,type:`button`,children:W(t?bn:yn,{class:`size-4`})})]})]})}function My(e){return W(`li`,{class:`flex min-w-0 items-center gap-1.5 text-xs font-bold ${e.met?`text-emerald-700`:`text-slate-500`}`,children:[W(Sn,{class:`size-3.5 shrink-0`,"aria-hidden":`true`}),W(`span`,{children:e.label})]})}function Ny(e){let[t,n]=L(``),[r,i]=L(``),[a,o]=L(``),[s,c]=L(!1),[l,u]=L(null),d=Bn(r),f=Vn(d),p=a.length>0&&r===a,m=t.length>0&&r.length>0&&t!==r,h=!s&&m&&f&&p,g=async()=>{if(u(null),!t)u(`현재 관리자 비밀번호를 입력해 주세요.`);else if(!f)u(`새 비밀번호는 8자 이상이며 영문과 숫자를 각각 하나 이상 포함해야 합니다.`);else if(t===r)u(`새 관리자 비밀번호는 현재 비밀번호와 다르게 설정해 주세요.`);else if(r!==a)u(`새 비밀번호 확인 값이 일치하지 않습니다.`);else{c(!0);try{await vy(t,r),n(``),i(``),o(``);try{await Ee()}catch{}c(!1),e.onChanged()}catch(e){u(q(e,`관리자 비밀번호를 변경하지 못했습니다. 잠시 후 다시 시도해 주세요.`)),c(!1)}}};return W(wy,{description:`현재 비밀번호를 확인한 뒤 공유기 관리자 비밀번호를 변경합니다. 변경이 완료되면 새 비밀번호로 다시 로그인해야 합니다.`,icon:W(_n,{class:`size-5`}),title:`관리자 비밀번호`,children:W(`form`,{class:`space-y-4`,noValidate:!0,onSubmit:e=>{e.preventDefault(),g()},children:[l&&W(`div`,{"aria-live":`polite`,class:`rounded-xl border border-rose-200 bg-rose-50 px-4 py-3 text-sm font-bold text-rose-800`,role:`alert`,children:l}),W(jy,{autoComplete:`current-password`,disabled:s,id:`smartsafehub-current-admin-password`,label:`현재 비밀번호`,name:`current-password`,onInput:n,placeholder:`현재 관리자 비밀번호`,value:t}),W(jy,{autoComplete:`new-password`,disabled:s,id:`smartsafehub-new-admin-password`,label:`새 비밀번호`,name:`new-password`,onInput:i,placeholder:`새 관리자 비밀번호`,value:r}),W(`ul`,{"aria-label":`새 비밀번호 요구 사항`,class:`m-0 grid list-none grid-cols-1 gap-2 p-0 sm:grid-cols-3`,children:[W(My,{label:`8자 이상`,met:d.length}),W(My,{label:`영문자 포함`,met:d.letter}),W(My,{label:`숫자 포함`,met:d.number})]}),W(`div`,{class:`pt-2`,children:W(jy,{autoComplete:`new-password`,disabled:s,id:`smartsafehub-confirm-admin-password`,label:`새 비밀번호 확인`,name:`confirm-password`,onInput:o,placeholder:`새 관리자 비밀번호 다시 입력`,value:a})}),a.length>0&&W(`p`,{"aria-live":`polite`,class:`m-0 text-xs font-bold ${p?`text-emerald-700`:`text-rose-700`}`,role:`status`,children:p?`새 비밀번호가 일치합니다.`:`새 비밀번호 확인 값이 일치하지 않습니다.`}),r.length>0&&t===r&&W(`p`,{class:`m-0 text-xs font-bold text-amber-700`,children:`새 비밀번호는 현재 비밀번호와 다르게 설정해 주세요.`}),W(`div`,{class:`pt-2`,children:W(`div`,{class:`rounded-xl border border-slate-200 bg-slate-50 p-3 text-xs leading-5 text-slate-600`,children:`비밀번호는 현재 공유기에 직접 적용되며 외부 서버로 전송되지 않습니다. 변경 후 현재 로그인 세션은 종료됩니다.`})}),W(`div`,{class:`rounded-xl border border-amber-200 bg-amber-50 p-3 text-xs leading-5 text-amber-900`,children:[W(`strong`,{class:`block font-extrabold`,children:`비밀번호를 잊었을 때`}),`전원이 켜진 상태에서 Reset 버튼을 5~9초 누른 뒤 놓으면 관리자 비밀번호만 복구할 수 있습니다. 네트워크, Wi-Fi와 SafeShield 설정은 유지됩니다. 10초 이상 누르면 모든 사용자 설정을 초기화하는 기기 초기화가 실행됩니다.`]}),W(`div`,{class:`flex justify-end`,children:W(`button`,{class:`inline-flex min-h-11 w-full items-center justify-center rounded-xl bg-teal-700 px-4 py-2.5 text-sm font-extrabold text-white transition hover:bg-teal-800 disabled:cursor-not-allowed disabled:opacity-50 sm:w-auto`,disabled:!h,type:`submit`,children:s?`비밀번호 변경 중`:`비밀번호 변경`})})]})})}function Py(e){let t=z(null),[n,r]=L(null),[i,a]=L(!1),o=e.action!==null;R(()=>{e.validated||a(!1)},[e.validated]);let s=()=>{!o&&!e.restoreAccepted&&t.current?.click()},c=async()=>{n&&await e.onUpload(n)&&(r(null),t.current&&(t.current.value=``))};return W(wy,{description:`펌웨어 업데이트나 복구 전에 현재 OpenWrt 설정을 표준 백업 파일로 저장하고, 필요할 때 검증 후 복원합니다.`,icon:W(gn,{class:`size-5`}),title:`설정 백업 및 복원`,children:[(e.error||e.message)&&W(`div`,{class:`mb-5 flex min-w-0 items-start justify-between gap-3 rounded-xl border px-4 py-3 text-sm font-bold ${e.error?`border-rose-200 bg-rose-50 text-rose-800`:`border-emerald-200 bg-emerald-50 text-emerald-800`}`,children:[W(`span`,{class:`min-w-0`,children:e.error||e.message}),!e.restoreAccepted&&W(`button`,{class:`shrink-0 rounded-lg px-2 py-1 text-xs font-extrabold hover:bg-black/5`,onClick:e.onDismissFeedback,type:`button`,children:`닫기`})]}),W(`div`,{class:`grid min-w-0 grid-cols-1 gap-3`,children:[W(`div`,{class:`rounded-xl border border-slate-200 bg-slate-50 p-4 sm:p-5`,children:[W(`p`,{class:`m-0 text-sm font-black text-slate-900`,children:`현재 설정 백업`}),W(`p`,{class:`mt-2 mb-0 text-xs leading-5 text-slate-600`,children:`네트워크, Wi-Fi, SmartSafeHub와 SafeShield 등 OpenWrt가 관리하는 설정을 표준 백업 파일로 저장합니다.`}),W(`button`,{class:`mt-4 inline-flex min-h-11 w-full items-center justify-center gap-2 rounded-xl bg-teal-700 px-4 py-2.5 text-sm font-extrabold text-white transition hover:bg-teal-800 disabled:cursor-wait disabled:opacity-60 sm:w-auto`,disabled:o||e.restoreAccepted,onClick:e.onDownload,type:`button`,children:[W(hn,{class:`size-4`}),e.action===`download`?`백업 생성 중`:`설정 백업 다운로드`]}),W(`p`,{class:`mt-3 mb-0 text-xs font-bold leading-5 text-amber-700`,children:`백업에는 Wi-Fi 비밀번호, 관리자 설정, VPN 키나 기기 인증 정보 같은 민감한 값이 포함될 수 있으므로 안전한 위치에 보관해 주세요.`})]}),W(`div`,{class:`rounded-xl border border-slate-200 bg-white p-4 sm:p-5`,children:[W(`p`,{class:`m-0 text-sm font-black text-slate-900`,children:`설정 복원`}),W(`p`,{class:`mt-2 mb-0 text-xs leading-5 text-slate-600`,children:`SmartSafeHub 또는 OpenWrt 백업을 업로드하고 검증한 뒤 복원합니다. 최대 16MB까지 사용할 수 있습니다.`}),e.validated?W(`div`,{class:`mt-4 rounded-xl border border-emerald-200 bg-emerald-50 p-4`,children:[W(`div`,{class:`flex min-w-0 flex-col gap-1 sm:flex-row sm:items-center sm:justify-between`,children:W(`div`,{class:`min-w-0`,children:[W(`p`,{class:`m-0 break-words text-sm font-extrabold text-emerald-900`,children:e.validated.filename}),W(`p`,{class:`mt-1 mb-0 text-xs font-bold text-emerald-700`,children:[Do(e.validated.sizeBytes),` · 검증 완료`]})]})}),i?W(`div`,{class:`mt-4 rounded-xl border border-rose-200 bg-white p-4`,children:[W(`p`,{class:`m-0 text-sm font-black text-rose-900`,children:`현재 설정을 백업 파일의 내용으로 덮어쓰시겠습니까?`}),W(`p`,{class:`mt-2 mb-0 text-xs leading-5 text-rose-700`,children:`LAN 주소, Wi-Fi, 관리자 접속 정보가 바뀌어 현재 연결이 끊길 수 있습니다. 복원 직후 공유기가 자동으로 재부팅됩니다. 같은 장치와 호환되는 백업만 사용해 주세요.`}),W(`div`,{class:`mt-4 flex flex-col gap-3 sm:flex-row sm:flex-wrap`,children:[W(`button`,{class:`inline-flex min-h-10 w-full items-center justify-center rounded-xl border border-slate-300 bg-white px-4 py-2 text-sm font-extrabold text-slate-700 transition hover:bg-slate-50 disabled:opacity-60 sm:w-auto`,disabled:o,onClick:()=>a(!1),type:`button`,children:`취소`}),W(`button`,{class:`inline-flex min-h-10 w-full items-center justify-center rounded-xl bg-rose-700 px-4 py-2 text-sm font-extrabold text-white transition hover:bg-rose-800 disabled:cursor-wait disabled:opacity-60 sm:w-auto`,disabled:o,onClick:()=>void e.onRestore(),type:`button`,children:e.action===`restore`?`설정 복원 중`:`설정 복원 및 재부팅`})]})]}):W(`div`,{class:`mt-4 flex flex-col gap-3 sm:flex-row sm:flex-wrap`,children:[W(`button`,{class:`inline-flex min-h-10 w-full items-center justify-center rounded-xl border border-slate-300 bg-white px-4 py-2 text-sm font-extrabold text-slate-700 transition hover:bg-slate-50 disabled:opacity-60 sm:w-auto`,disabled:o||e.restoreAccepted,onClick:()=>void e.onDiscard(),type:`button`,children:e.action===`discard`?`파일 삭제 중`:`파일 삭제`}),W(`button`,{class:`inline-flex min-h-10 w-full items-center justify-center rounded-xl bg-amber-600 px-4 py-2 text-sm font-extrabold text-white transition hover:bg-amber-700 disabled:opacity-60 sm:w-auto`,disabled:o||e.restoreAccepted,onClick:()=>a(!0),type:`button`,children:`복원 준비`})]})]}):W(b,{children:[W(`input`,{accept:`.tar.gz,.tgz,application/gzip,application/x-gzip`,class:`sr-only`,disabled:o||e.restoreAccepted,onChange:e=>r(e.currentTarget.files?.[0]??null),ref:t,type:`file`}),W(`button`,{class:`mt-4 flex min-h-11 w-full items-center justify-between gap-3 rounded-xl border border-dashed border-slate-300 bg-slate-50 px-4 py-3 text-left text-sm font-bold text-slate-700 transition hover:border-teal-400 hover:bg-teal-50 disabled:cursor-wait disabled:opacity-60`,disabled:o||e.restoreAccepted,onClick:s,type:`button`,children:[W(`span`,{class:`min-w-0 truncate`,children:n?.name??`복원할 백업 파일 선택`}),W(`span`,{class:`shrink-0 text-xs font-extrabold text-teal-700`,children:`찾아보기`})]}),n&&W(`div`,{class:`mt-3 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between`,children:[W(`span`,{class:`text-xs font-bold text-slate-500`,children:Do(n.size)}),W(`button`,{class:`inline-flex min-h-10 w-full items-center justify-center rounded-xl border border-teal-300 bg-white px-4 py-2 text-sm font-extrabold text-teal-800 transition hover:bg-teal-50 disabled:cursor-wait disabled:opacity-60 sm:w-auto`,disabled:o||e.restoreAccepted,onClick:()=>void c(),type:`button`,children:e.action===`upload`?`업로드 중 ${Math.round(e.uploadProgress??0)}%`:e.action===`validate`?`백업 검증 중`:`업로드 및 검증`})]})]})]})]})]})}function Fy(e){return W(wy,{description:`공유기 재부팅과 SmartSafeHub에서 제공하지 않는 고급 관리 도구를 한곳에서 제공합니다.`,...e.className===void 0?{}:{className:e.className},icon:W(an,{class:`size-5`}),title:`시스템 도구`,children:W(`div`,{class:`space-y-3`,children:[W(`section`,{"aria-labelledby":`router-reboot-heading`,class:`rounded-xl border border-slate-200 bg-slate-50 p-4`,children:[W(`div`,{class:`flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between`,children:[W(`div`,{class:`flex min-w-0 items-start gap-3`,children:[W(`span`,{class:`flex size-9 shrink-0 items-center justify-center rounded-lg bg-rose-50 text-rose-700`,children:W(mn,{class:`size-4`})}),W(`div`,{class:`min-w-0`,children:[W(`p`,{class:`m-0 text-sm font-black text-rose-950`,id:`router-reboot-heading`,children:`공유기 재부팅`}),W(`p`,{class:`mt-1 mb-0 text-xs leading-5 text-slate-500`,children:`재부팅 중에는 인터넷과 Wi-Fi 연결이 잠시 중단됩니다.`})]})]}),!e.confirmingReboot&&W(`button`,{class:`inline-flex min-h-10 w-full shrink-0 items-center justify-center rounded-xl border border-rose-300 bg-white px-3 py-2 text-xs font-extrabold text-rose-700 transition hover:bg-rose-50 disabled:opacity-60 sm:w-auto`,disabled:e.action!==null||e.rebootAccepted,onClick:e.onConfirmReboot,type:`button`,children:`재부팅`})]}),e.confirmingReboot&&W(`div`,{class:`mt-3 rounded-xl border border-rose-200 bg-rose-50 p-3`,children:[W(`p`,{class:`m-0 text-xs font-extrabold leading-5 text-rose-900`,children:`지금 공유기를 재부팅하시겠습니까? 저장되지 않은 LuCI 설정이 있다면 먼저 저장해 주세요.`}),W(`div`,{class:`mt-3 flex flex-col gap-2 sm:flex-row sm:flex-wrap`,children:[W(`button`,{class:`inline-flex min-h-10 w-full items-center justify-center rounded-xl border border-slate-300 bg-white px-3 py-2 text-xs font-extrabold text-slate-700 transition hover:bg-slate-50 sm:w-auto`,disabled:e.action!==null,onClick:e.onCancelReboot,type:`button`,children:`취소`}),W(`button`,{class:`inline-flex min-h-10 w-full items-center justify-center rounded-xl bg-rose-700 px-3 py-2 text-xs font-extrabold text-white transition hover:bg-rose-800 disabled:cursor-wait disabled:opacity-60 sm:w-auto`,disabled:e.action!==null,onClick:e.onReboot,type:`button`,children:e.action===`reboot`?`재부팅 요청 중`:`지금 재부팅`})]})]})]}),W(`section`,{"aria-labelledby":`advanced-tools-heading`,class:`rounded-xl border border-slate-200 bg-slate-50 p-4`,children:[W(`div`,{class:`min-w-0`,children:[W(`p`,{class:`m-0 text-sm font-black text-slate-900`,id:`advanced-tools-heading`,children:`고급 도구`}),W(`p`,{class:`mt-1 mb-0 text-xs leading-5 text-slate-500`,children:`SmartSafeHub에서 아직 제공하지 않는 상세 시스템 설정이나 원본 로그가 필요할 때 사용합니다.`})]}),W(`div`,{class:`mt-3 flex flex-col gap-2 sm:flex-row sm:flex-wrap`,children:[W(`a`,{class:`inline-flex min-h-10 w-full items-center justify-center rounded-xl border border-slate-300 bg-white px-3 py-2 text-xs font-extrabold text-slate-800 no-underline transition hover:bg-slate-50 sm:w-auto`,href:e.advancedSystemUrl,children:`LuCI 고급 설정 열기`}),W(`a`,{class:`inline-flex min-h-10 w-full items-center justify-center rounded-xl border border-slate-300 bg-white px-3 py-2 text-xs font-extrabold text-slate-800 no-underline transition hover:bg-slate-50 sm:w-auto`,href:e.logsUrl,children:`시스템 로그 열기`})]})]})]})})}var Iy=[{value:`mon`,label:`월요일`},{value:`tue`,label:`화요일`},{value:`wed`,label:`수요일`},{value:`thu`,label:`목요일`},{value:`fri`,label:`금요일`},{value:`sat`,label:`토요일`},{value:`sun`,label:`일요일`}];function Ly(e){let[t,n]=L(!1),[r,i]=L(`weekly`),[a,o]=L(`sun`),[s,c]=L(`04:00`);R(()=>{e.data&&(n(e.data.enabled),i(e.data.frequency),o(e.data.dayOfWeek),c(e.data.time))},[e.data?.dayOfWeek,e.data?.enabled,e.data?.frequency,e.data?.time]);let l=e.data!==null&&(t!==e.data.enabled||r!==e.data.frequency||a!==e.data.dayOfWeek||s!==e.data.time),u=Iy.find(e=>e.value===a),d=t?r===`daily`?`매일 ${s}`:`매주 ${u?.label??`일요일`} ${s}`:`예약 재부팅 꺼짐`;return W(`section`,{"aria-labelledby":`scheduled-reboot-heading`,class:`min-w-0`,children:[W(`div`,{class:`flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between`,children:[W(`div`,{class:`flex min-w-0 items-start gap-3`,children:[W(`span`,{class:`flex size-11 shrink-0 items-center justify-center rounded-xl bg-slate-100 text-slate-600`,children:W(xn,{class:`size-5`})}),W(`div`,{class:`min-w-0`,children:[W(`h2`,{class:`m-0 text-lg font-black text-slate-950`,id:`scheduled-reboot-heading`,children:`예약 재부팅`}),W(`p`,{class:`mt-1.5 mb-0 text-sm leading-6 text-slate-500`,children:`공유기를 정해진 시간에 자동으로 재부팅합니다. 기본값은 꺼짐이며 필요한 장치에서만 사용하세요.`})]})]}),W(`div`,{class:`flex shrink-0 flex-wrap items-center justify-between gap-3 sm:justify-end`,children:[l&&W(`span`,{"aria-live":`polite`,class:`inline-flex items-center gap-1.5 rounded-full border border-amber-200 bg-amber-50 px-2.5 py-1 text-xs font-extrabold text-amber-800`,role:`status`,children:[W(Cn,{"aria-hidden":`true`,class:`size-3.5 shrink-0`}),`저장되지 않음`]}),W(`span`,{class:`text-xs font-extrabold text-slate-500`,children:t?`사용 중`:`꺼짐`}),W(`button`,{"aria-checked":t,"aria-label":`예약 재부팅 사용`,class:`ssh-switch-control relative inline-flex shrink-0 rounded-full border transition focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-100 disabled:cursor-not-allowed disabled:opacity-50 ${t?`border-teal-600 bg-teal-600`:`border-slate-300 bg-slate-200`}`,disabled:e.loading||e.saving||!e.data,onClick:()=>n(e=>!e),role:`switch`,type:`button`,children:W(`span`,{"aria-hidden":`true`,class:`ssh-switch-thumb absolute top-1 shadow-sm transition-[left] ${t?`left-6`:`left-1`}`})})]})]}),(e.saveError||e.saveMessage)&&W(`div`,{class:`mt-4 flex min-w-0 items-start justify-between gap-3 rounded-xl border px-4 py-3 text-sm font-bold ${e.saveError?`border-rose-200 bg-rose-50 text-rose-800`:`border-emerald-200 bg-emerald-50 text-emerald-800`}`,children:[W(`span`,{class:`min-w-0`,children:e.saveError||e.saveMessage}),W(`button`,{class:`shrink-0 rounded-lg px-2 py-1 text-xs font-extrabold hover:bg-black/5`,onClick:e.onDismissFeedback,type:`button`,children:`닫기`})]}),e.error&&!e.data?W(`div`,{class:`mt-4 rounded-xl border border-rose-200 bg-rose-50 p-4`,children:[W(`p`,{class:`m-0 text-sm font-bold text-rose-800`,children:e.error}),W(`button`,{class:`mt-3 inline-flex min-h-10 items-center rounded-xl border border-rose-300 bg-white px-3 py-2 text-xs font-extrabold text-rose-700 transition hover:bg-rose-100`,onClick:e.onRetry,type:`button`,children:`다시 불러오기`})]}):W(b,{children:[W(`div`,{class:`mt-4 grid min-w-0 grid-cols-1 gap-4 md:grid-cols-3`,children:[W(`div`,{children:[W(`label`,{class:`mb-2 block text-sm font-extrabold text-slate-800`,for:`scheduled-reboot-frequency`,children:`주기`}),W(W_,{ariaLabel:`예약 재부팅 주기`,disabled:!t||e.saving||!e.data,id:`scheduled-reboot-frequency`,onChange:e=>i(e),options:[{value:`weekly`,label:`매주`},{value:`daily`,label:`매일`}],value:r})]}),W(`div`,{children:[W(`label`,{class:`mb-2 block text-sm font-extrabold text-slate-800`,for:`scheduled-reboot-day`,children:`요일`}),W(W_,{ariaLabel:`예약 재부팅 요일`,disabled:!t||r!==`weekly`||e.saving||!e.data,id:`scheduled-reboot-day`,onChange:e=>o(e),options:Iy.map(e=>({value:e.value,label:e.label})),value:a})]}),W(`div`,{children:[W(`label`,{class:`mb-2 block text-sm font-extrabold text-slate-800`,for:`scheduled-reboot-time-hour`,children:`재부팅 시간`}),W(Sy,{ariaLabel:`예약 재부팅 시간`,disabled:!t||e.saving||!e.data,id:`scheduled-reboot-time`,onChange:c,value:s})]})]}),W(`div`,{class:`mt-4 flex flex-col gap-3 rounded-xl bg-slate-50 p-4 sm:flex-row sm:items-end sm:justify-between`,children:[W(`div`,{class:`min-w-0`,children:[W(`p`,{class:`m-0 text-xs font-extrabold uppercase tracking-[0.12em] text-slate-500`,children:`예약 일정`}),W(`p`,{class:`mt-1.5 mb-0 text-sm font-extrabold text-slate-900`,children:d}),W(`p`,{class:`mt-1 mb-0 text-xs leading-5 text-slate-500`,children:[e.data?.timezone??`장치 시간대`,` 기준 · 업데이트 작업 중이면 15분 단위로 최대 2시간 연기합니다.`]})]}),W(`button`,{class:`inline-flex min-h-11 w-full shrink-0 items-center justify-center rounded-xl bg-teal-700 px-4 py-2.5 text-sm font-extrabold text-white transition hover:bg-teal-800 disabled:cursor-not-allowed disabled:opacity-50 sm:w-auto`,disabled:!l||e.saving||!e.data,onClick:()=>void e.onSave({enabled:t,frequency:r,dayOfWeek:a,time:s}),type:`button`,children:e.saving?`예약 저장 중`:`예약 저장`})]})]})]})}function Ry({activeTab:e,action:t,backupAction:n,backupError:r,backupMessage:i,backupRestoreAccepted:a,backupUploadProgress:o,backupValidated:s,data:c,error:l,feedbackError:u,feedbackMessage:d,health:f,healthActionError:p,healthActionMessage:m,healthError:h,healthLoading:g,healthRunning:_,healthSavingReporter:v,loading:y,rebootAccepted:b,scheduledRebootData:x,scheduledRebootError:S,scheduledRebootLoading:C,scheduledRebootSaveError:w,scheduledRebootSaveMessage:T,scheduledRebootSaving:E,timeData:D,timeError:O,timeLoading:k,timeSaveError:A,timeSaveMessage:j,timeSaving:M,timeSyncing:N,onBackupDiscard:P,onBackupDownload:F,onBackupRestore:I,onBackupUpload:ee,onDismissBackupFeedback:te,onDismissFeedback:ne,onDismissHealthFeedback:re,onDismissTimeFeedback:ie,onDownloadDiagnostics:ae,onRunHealth:oe,onSetHealthReporter:se,onReboot:ce,onRetry:le,onDismissScheduledRebootFeedback:ue,onSaveScheduledReboot:de,onSaveTimezone:fe,onSyncTime:pe,onAdministratorPasswordChanged:me}){let[he,ge]=L(!1),_e=ve(`/admin/system`),ye=ve(`/admin/status/logs`);return W(`section`,{class:`min-w-0 space-y-7`,children:[(u||d)&&W(`div`,{class:`flex min-w-0 flex-col gap-3 rounded-xl border px-4 py-3 text-sm font-bold sm:flex-row sm:items-center sm:justify-between ${u?`border-rose-200 bg-rose-50 text-rose-800`:`border-emerald-200 bg-emerald-50 text-emerald-800`}`,children:[W(`span`,{class:`min-w-0`,children:u||d}),!b&&W(`button`,{class:`shrink-0 self-start rounded-lg px-3 py-1.5 text-xs font-extrabold hover:bg-black/5 sm:self-auto`,onClick:ne,type:`button`,children:`닫기`})]}),W(ci,{active:e,label:`설정 메뉴`,tabs:[{id:`diagnostics`,label:`진단 및 지원`,href:`#settings`},{id:`time`,label:`시간 및 예약`,href:`#settings/time`},{id:`management`,label:`관리 및 복구`,href:`#settings/management`}]}),W(`section`,{"aria-labelledby":`diagnostics-tab`,class:`min-w-0`,hidden:e!==`diagnostics`,id:`diagnostics-panel`,role:`tabpanel`,children:[l&&!c?W(`div`,{class:`mb-4 rounded-xl border border-amber-200 bg-amber-50 p-4 text-sm font-semibold text-amber-900`,role:`status`,children:[W(`p`,{class:`m-0`,children:[`시스템 정보를 불러오지 못해 진단 파일 다운로드가 제한될 수 있습니다. `,l]}),W(`button`,{class:`mt-2 rounded-lg border border-current px-3 py-1.5 text-xs font-extrabold`,onClick:le,type:`button`,children:`다시 시도`})]}):y&&!c?W(`p`,{class:`mb-4 text-sm font-semibold text-slate-500`,role:`status`,children:`진단 파일에 필요한 시스템 정보를 확인하고 있습니다.`}):null,W(J,{eyebrow:`Device health`,title:`진단 및 지원`,description:`장치 진단과 원격 상태 보고를 확인하고 개인정보 보호 정책을 관리합니다.`}),W(`div`,{class:`grid min-w-0 grid-cols-1 gap-4 lg:grid-cols-2`,children:W(Ay,{actionError:p,actionMessage:m,data:f,downloadBusy:t===`diagnostics`,error:h,loading:g,onDismissFeedback:re,onDownload:ae,onRetry:le,onRun:oe,onSetReporter:se,running:_,savingReporter:v})})]}),W(`section`,{"aria-labelledby":`time-tab`,class:`min-w-0`,hidden:e!==`time`,id:`time-panel`,role:`tabpanel`,children:[W(J,{eyebrow:`Device settings`,title:`시간 및 예약`,description:`시간 기준과 예약 재부팅을 SmartSafeHub에서 직접 관리합니다.`}),W(`div`,{class:`grid min-w-0 grid-cols-1 gap-4 lg:grid-cols-2`,children:W(Ty,{data:D,error:O,loading:k,onDismissFeedback:ie,onDismissScheduledRebootFeedback:ue,onRetry:le,onSave:fe,onSaveScheduledReboot:de,onSync:pe,saveError:A,saveMessage:j,saving:M,scheduledRebootData:x,scheduledRebootError:S,scheduledRebootLoading:C,scheduledRebootSaveError:w,scheduledRebootSaveMessage:T,scheduledRebootSaving:E,syncing:N})})]}),W(`section`,{"aria-labelledby":`management-tab`,class:`min-w-0`,hidden:e!==`management`,id:`management-panel`,role:`tabpanel`,children:[W(J,{eyebrow:`System management`,title:`시스템 관리`,description:`관리자 비밀번호, 설정 백업·복원과 공유기 시스템 도구를 한곳에서 관리합니다.`}),W(`div`,{class:`grid min-w-0 grid-cols-1 gap-4 lg:grid-cols-2`,children:[W(Ny,{onChanged:me}),W(Py,{action:n,error:r,message:i,onDiscard:P,onDismissFeedback:te,onDownload:F,onRestore:I,onUpload:ee,restoreAccepted:a,uploadProgress:o,validated:s}),W(Fy,{action:t,className:`lg:col-span-2`,advancedSystemUrl:_e,confirmingReboot:he,logsUrl:ye,onCancelReboot:()=>ge(!1),onConfirmReboot:()=>ge(!0),onReboot:ce,rebootAccepted:b})]})]})]})}var zy=`https://www.smartsafehub.com/dashboard/devices/`;async function By(e){if(navigator.clipboard?.writeText)try{await navigator.clipboard.writeText(e);return}catch{}let t=document.createElement(`textarea`);t.value=e,t.setAttribute(`readonly`,``),t.style.position=`fixed`,t.style.inset=`0 auto auto -9999px`,t.style.opacity=`0`,document.body.appendChild(t),t.select(),t.setSelectionRange(0,e.length);let n=document.execCommand(`copy`);if(t.remove(),!n)throw Error(`clipboard_copy_failed`)}function Vy(e){return(e||`free`).toUpperCase()}function Hy(e){return e===`FREE`?`free`:e===`PRO`?`pro`:e===`ULTIMATE`?`ultimate`:`paid`}function Uy({plan:e}){let t=Hy(e),n=t!==`free`;return W(`span`,{class:`ssh-safeshield-plan-badge`,"data-compact":`false`,"data-tier":t,title:n?`${e} 멤버십`:`무료 플랜`,children:[W(`span`,{"aria-hidden":`true`,class:`ssh-safeshield-plan-badge-mark`,children:n?`✦`:`•`}),W(`span`,{children:e})]})}function Wy(e){if(!e.pairingCode||!e.pairingExpiresAt)return!1;let t=Date.parse(e.pairingExpiresAt);return Number.isFinite(t)&&t<=Date.now()}function Gy({data:e,error:t,loading:n,pairingBusy:r,refreshing:i,syncError:a,onRequestPairing:o,onRetry:s,demo:c=!1,onDemoAction:l}){let[u,d]=L(null),[f,p]=L(!1);if(R(()=>{if(!f)return;let e=window.setTimeout(()=>p(!1),1800);return()=>window.clearTimeout(e)},[f]),n&&!e)return W(vs,{});if(t&&!e)return W(ys,{message:t,onRetry:s});let m=e,h=m?.accountRegistered===!0,g=Vy(m?.plan??null),_=m?Wy(m):!1,v=!h&&!_?m?.pairingCode:null,y=r||m?.pairingOperation?.phase===`starting`||m?.pairingOperation?.phase===`running`;async function b(){d(null),p(!1);try{await o()}catch{d(`계정 연결 코드를 발급하지 못했습니다. 잠시 후 다시 시도해 주세요.`)}}async function x(){if(v)try{await By(v),d(null),p(!0)}catch{p(!1),d(`연결 코드를 복사하지 못했습니다. 코드를 직접 선택해 복사해 주세요.`)}}return W(`div`,{class:`min-w-0`,children:[W(J,{eyebrow:`Account connection`,title:`기기 계정 연결`,description:`계정에 연결하면 웹사이트에서 기기를 관리하고 Cloud 기능과 구독 권한을 사용할 수 있습니다.`}),W(`div`,{class:`grid items-start gap-5 xl:items-stretch xl:grid-cols-[minmax(0,1.2fr)_minmax(18rem,0.8fr)]`,children:[W(`section`,{class:`rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5 sm:p-6 xl:h-full`,children:h?W(`div`,{children:[W(`div`,{class:`flex items-center gap-3`,children:[W(`span`,{class:`grid size-10 shrink-0 place-items-center rounded-full bg-emerald-100 text-emerald-700`,children:W(Sn,{class:`size-5`})}),W(`div`,{children:[W(`p`,{class:`m-0 text-base font-black text-slate-950`,children:`SmartSafeHub 계정에 연결됨`}),W(`p`,{class:`mt-1 mb-0 text-sm leading-6 text-slate-600`,children:`이 기기가 계정과 정상적으로 연결되어 있습니다. 연결 코드는 더 이상 필요하지 않습니다.`})]})]}),c?W(`button`,{class:`mt-5 inline-flex min-h-11 items-center justify-center rounded-xl border border-emerald-700 bg-emerald-700 px-4 py-2 text-sm font-extrabold text-white transition hover:bg-emerald-800`,onClick:l,type:`button`,children:`웹사이트에서 기기 관리`}):W(`a`,{class:`mt-5 inline-flex min-h-11 items-center justify-center rounded-xl border border-emerald-700 bg-emerald-700 px-4 py-2 text-sm font-extrabold text-white no-underline transition hover:bg-emerald-800 focus:outline-none focus-visible:ring-4 focus-visible:ring-emerald-100`,href:zy,rel:`noopener noreferrer`,target:`_blank`,children:`웹사이트에서 기기 관리`}),W(`p`,{class:`mt-4 mb-0 border-t border-slate-100 pt-4 text-xs font-semibold leading-5 text-slate-500`,children:`웹사이트에서 기기 등록을 해제하면 공유기도 다음 계정 상태 동기화에서 자동으로 연결 해제를 반영합니다.`})]}):W(`div`,{children:[W(`div`,{children:[W(`div`,{class:`flex flex-wrap items-center justify-between gap-2`,children:[W(`p`,{class:`m-0 text-sm font-black text-slate-900`,children:`계정 연결 코드`}),W(`span`,{class:`inline-flex items-center gap-1.5 rounded-full border border-amber-200 bg-amber-50 px-2.5 py-1 text-[0.7rem] font-black text-amber-800`,children:[W(dn,{class:`size-3.5`}),`10분 동안 유효`]})]}),W(`p`,{class:`mt-1 mb-0 text-sm leading-6 text-slate-500`,children:`코드를 발급한 뒤 smartsafehub.com에 로그인하여 기기를 연결하세요.`})]}),v?W(`div`,{class:`mt-4 flex flex-col gap-3 rounded-2xl border border-teal-200 bg-teal-50 p-4 sm:flex-row sm:items-center sm:justify-between`,role:`group`,"aria-label":`계정 연결 코드`,children:[W(`div`,{class:`min-w-0`,children:[W(`span`,{class:`block text-[0.7rem] font-black text-teal-700`,children:`연결 코드`}),W(`code`,{class:`mt-1 block select-all break-all font-mono text-2xl font-black tracking-[0.14em] text-slate-950`,children:v})]}),W(`button`,{"aria-label":`계정 연결 코드 복사`,class:`inline-flex min-h-11 shrink-0 items-center justify-center gap-2 rounded-xl border border-teal-300 bg-white px-4 text-sm font-black text-teal-800 transition hover:bg-teal-100 focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-100`,onClick:()=>void x(),type:`button`,children:[W(f?Sn:vn,{class:`size-4`}),W(`span`,{"aria-live":`polite`,children:f?`복사됨`:`복사`})]})]}):_?W(`p`,{class:`mt-4 mb-0 rounded-xl border border-amber-200 bg-amber-50 px-4 py-3 text-sm font-bold text-amber-800`,children:`이전 연결 코드가 만료되었습니다. 새 코드를 발급해 주세요.`}):null,W(`button`,{class:`mt-4 inline-flex min-h-11 w-full items-center justify-center gap-2 rounded-xl border border-teal-700 bg-teal-700 px-4 py-2 text-sm font-black text-white transition hover:bg-teal-800 focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-100 disabled:cursor-not-allowed disabled:opacity-50`,disabled:y,onClick:()=>void b(),type:`button`,children:[y?W(pn,{class:`size-4 animate-spin`}):null,y?`발급 중…`:v?`새 코드 발급`:`연결 코드 발급`]}),m?.pairingOperation?.phase===`failed`?W(`p`,{class:`mt-3 mb-0 text-sm font-semibold text-red-700`,children:`연결 코드 발급에 실패했습니다. 다시 시도해 주세요.`}):null,u?W(`p`,{class:`mt-3 mb-0 text-sm font-semibold text-red-700`,children:u}):null]})}),W(`aside`,{class:`rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5 sm:p-6 xl:h-full`,children:[W(`p`,{class:`m-0 text-[0.68rem] font-black uppercase tracking-[0.16em] text-slate-400`,children:`Device status`}),W(`h2`,{class:`mt-2 mb-0 text-lg font-black tracking-tight text-slate-950`,children:`연결 상태`}),W(`dl`,{class:`mt-5 mb-0 grid gap-4`,children:[W(`div`,{class:`flex items-center justify-between gap-4 border-b border-slate-100 pb-4`,children:[W(`dt`,{class:`text-sm font-bold text-slate-500`,children:`계정`}),W(`dd`,{class:`m-0 text-sm font-black ${h?`text-emerald-700`:`text-slate-700`}`,children:h?`연결됨`:`연결 필요`})]}),W(`div`,{class:`flex items-center justify-between gap-4 border-b border-slate-100 pb-4`,children:[W(`dt`,{class:`text-sm font-bold text-slate-500`,children:`요금제`}),W(`dd`,{class:`m-0 flex min-w-0 flex-wrap items-center justify-end gap-2`,children:[W(Uy,{plan:g}),g===`FREE`?W(`span`,{class:`ssh-safeshield-plan-caption`,children:`기본 플랜`}):null]})]}),W(`div`,{class:`flex items-center justify-between gap-4`,children:[W(`dt`,{class:`text-sm font-bold text-slate-500`,children:`마지막 확인`}),W(`dd`,{class:`m-0 text-sm font-black text-slate-700`,children:jo(m?.lastSuccessAt??0)})]})]}),i?W(`p`,{class:`mt-5 mb-0 text-xs font-bold text-teal-700`,children:`최신 상태를 확인하고 있습니다…`}):null,a?h?W(`p`,{class:`mt-5 mb-0 text-xs font-semibold leading-5 text-slate-500`,children:`최신 상태 확인이 지연되고 있습니다. 마지막으로 확인된 연결 상태를 표시합니다.`}):W(`p`,{class:`mt-5 mb-0 rounded-xl border border-amber-200 bg-amber-50 px-3 py-2 text-xs font-bold leading-5 text-amber-800`,children:[a,` 로컬에 저장된 마지막 상태를 표시합니다.`]}):null]})]})]})}function Ky(e,t){if(!e||!t)return`unknown`;if(e===t)return`same`;let n=/^(\d+)\.(\d+)\.(\d+)(?:-r(\d+))?$/,r=n.exec(e),i=n.exec(t);if(!r||!i)return`different`;for(let e=1;e<=4;e+=1){let t=BigInt(r[e]||`0`),n=BigInt(i[e]||`0`);if(t>n)return`newer`;if(t=1048576?`${(e/1048576).toFixed(1)} MB`:`${(e/1024).toFixed(1)} KB`}function Jy(e){switch(e.phase){case`checking`:return`확인 중`;case`downloading`:return`다운로드 중`;case`verifying`:return`검증 중`;case`ready`:return`설치 준비 완료`;case`flashing`:return`설치 중`;case`error`:return`확인 필요`;default:return e.updateAvailable?`업데이트 가능`:e.lastCheckAt?`최신 상태`:`확인 전`}}function Yy(e){return e.phase===`error`?`bg-rose-50 text-rose-700 ring-rose-200`:e.phase===`checking`||e.phase===`downloading`||e.phase===`verifying`||e.phase===`flashing`?`bg-sky-50 text-sky-700 ring-sky-200`:e.phase===`ready`||e.updateAvailable?`bg-amber-50 text-amber-800 ring-amber-200`:e.lastCheckAt?`bg-emerald-50 text-emerald-700 ring-emerald-200`:`bg-slate-100 text-slate-600 ring-slate-200`}function Xy(e){return e.phase===`checking`||e.phase===`downloading`||e.phase===`verifying`||e.phase===`flashing`?W(pn,{class:`size-3.5 animate-spin`}):e.phase===`error`?W(Cn,{class:`size-3.5`}):e.phase===`ready`||e.updateAvailable?W(hn,{class:`size-3.5`}):e.lastCheckAt?W(Sn,{class:`size-3.5`}):W(dn,{class:`size-3.5`})}function Zy({checked:e,disabled:t,onChange:n}){return W(`button`,{"aria-checked":e,"aria-label":`현재 설정 유지`,class:`ssh-switch-control relative inline-flex shrink-0 rounded-full border transition focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-100 disabled:cursor-not-allowed disabled:opacity-50 ${e?`border-teal-600 bg-teal-600`:`border-slate-300 bg-slate-200`}`,disabled:t,onClick:()=>n(!e),role:`switch`,type:`button`,children:W(`span`,{"aria-hidden":`true`,class:`ssh-switch-thumb absolute top-1 shadow-sm transition-[left] ${e?`left-6`:`left-1`}`})})}function Qy({action:e,actionError:t,data:n,software:r,error:i,loading:a,message:o,reconnecting:s,uploadProgress:c,onCheck:l,onDiscard:u,onDismissFeedback:d,onInstall:f,onPrepare:p,onRetry:m,onUpload:h}){let g=z(null),[_,v]=L(null),[y,x]=L(!0),[S,C]=L(!1);R(()=>{n?.prepared&&x(n.prepared.allowBackup)},[n?.prepared]);let w=e!==null||n?.phase===`checking`||n?.phase===`downloading`||n?.phase===`verifying`||n?.phase===`flashing`,T=n?.phase===`ready`?n.prepared:null,E=e===`check`||n?.phase===`checking`,D=e===`prepare`||n?.phase===`downloading`||n?.phase===`verifying`,O=e===`upload`||e===`validate-upload`,k=n?.release?.bundledSoftware?.smartsafehub??null,A=r?.packages.find(e=>e.name===`luci-app-smartsafehub`)?.installedVersion??null,j=Ky(k,A),M=async()=>{_&&await h(_)&&(v(null),g.current&&(g.current.value=``))};return W(`article`,{class:`min-w-0 overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm shadow-slate-900/5`,"data-component":`firmware-update-card`,children:[W(`div`,{class:`p-5 sm:p-6`,children:[W(`div`,{class:`flex flex-col gap-5 lg:flex-row lg:items-start lg:justify-between`,children:[W(`div`,{class:`flex min-w-0 gap-4`,children:[W(`span`,{class:`grid size-12 shrink-0 place-items-center rounded-2xl bg-sky-50 text-sky-700`,children:W(Jt,{class:`size-6`})}),W(`div`,{class:`min-w-0`,children:[W(`p`,{class:`m-0 text-xs font-extrabold uppercase tracking-[0.16em] text-sky-700`,children:`Firmware update`}),W(`h2`,{class:`mt-2 mb-0 text-xl font-black text-slate-950 sm:text-2xl`,children:`펌웨어 업데이트`}),W(`p`,{class:`mt-2 mb-0 text-sm leading-6 text-slate-500 lg:whitespace-nowrap`,children:`시스템 펌웨어를 확인하고 설치합니다. 온라인·수동 설치는 안전성 검증 후 진행됩니다.`})]})]}),n?W(`div`,{class:`flex shrink-0 flex-col gap-2 sm:flex-row lg:justify-end`,children:[W(`span`,{class:`inline-flex min-h-11 items-center justify-center gap-1.5 rounded-xl px-3 py-2 text-xs font-extrabold ring-1 ring-inset ${Yy(n)}`,children:[Xy(n),Jy(n)]}),W(`button`,{class:`inline-flex min-h-11 items-center justify-center gap-2 rounded-xl border border-slate-300 bg-white px-4 py-2.5 text-sm font-extrabold text-slate-800 transition hover:bg-slate-50 disabled:cursor-wait disabled:opacity-60`,disabled:w||T!==null,onClick:l,type:`button`,children:[W(pn,{class:`size-4 ${E?`animate-spin`:``}`}),E?`확인 중...`:`펌웨어 확인`]}),n.updateAvailable&&!T?W(`button`,{class:`inline-flex min-h-11 items-center justify-center gap-2 rounded-xl bg-sky-700 px-4 py-2.5 text-sm font-extrabold text-white transition hover:bg-sky-800 disabled:cursor-wait disabled:opacity-60`,disabled:w,onClick:p,type:`button`,children:[D?W(pn,{class:`size-4 animate-spin`}):W(hn,{class:`size-4`}),D?`준비 중...`:`다운로드 및 검증`]}):null]}):null]}),t||o?W(`div`,{class:`mt-5 flex flex-col gap-3 rounded-xl border px-4 py-3 text-sm font-bold sm:flex-row sm:items-center sm:justify-between ${t?`border-rose-200 bg-rose-50 text-rose-800`:`border-emerald-200 bg-emerald-50 text-emerald-800`}`,children:[W(`span`,{children:t||o}),W(`button`,{class:`shrink-0 self-start rounded-lg px-3 py-1.5 text-xs font-extrabold hover:bg-black/5 sm:self-auto`,onClick:d,type:`button`,children:`닫기`})]}):null,s||n?.phase===`flashing`?W(`div`,{class:`mt-5 rounded-xl border border-amber-200 bg-amber-50 p-4 sm:p-5`,role:`status`,children:W(`div`,{class:`flex gap-3`,children:[W(`span`,{class:`grid size-10 shrink-0 place-items-center rounded-xl bg-white text-amber-700 ring-1 ring-inset ring-amber-200`,children:W(pn,{class:`size-5 animate-spin`})}),W(`div`,{children:[W(`strong`,{class:`block text-sm font-black text-amber-950`,children:`펌웨어를 설치하고 재부팅하고 있습니다.`}),W(`p`,{class:`mt-1 mb-0 text-sm leading-6 text-amber-800`,children:`전원을 끄지 마세요. 현재 주소에서 공유기가 다시 응답하면 SmartSafeHub가 자동으로 새로고침됩니다.`})]})]})}):null,i&&!n?W(`div`,{class:`mt-5 rounded-xl border border-rose-200 bg-rose-50 p-4 text-sm text-rose-800`,children:[W(`p`,{class:`m-0 font-bold`,children:i}),W(`button`,{class:`mt-3 rounded-lg border border-rose-300 bg-white px-3 py-2 text-xs font-extrabold text-rose-700`,onClick:m,type:`button`,children:`다시 시도`})]}):a&&!n?W(`div`,{class:`mt-5 flex items-center gap-3 rounded-xl bg-slate-50 p-4 text-sm font-bold text-slate-500`,children:[W(pn,{class:`size-4 animate-spin`}),`펌웨어 상태를 확인하고 있습니다.`]}):n?W(b,{children:[n.lastError?W(`div`,{class:`mt-5 rounded-xl border border-rose-200 bg-rose-50 p-4 sm:p-5`,children:W(`div`,{class:`flex gap-3`,children:[W(`span`,{class:`grid size-10 shrink-0 place-items-center rounded-xl bg-white text-rose-700 ring-1 ring-inset ring-rose-200`,children:W(Cn,{class:`size-5`})}),W(`div`,{class:`min-w-0 flex-1`,children:[W(`strong`,{class:`block text-sm font-black text-rose-950`,children:`펌웨어 작업을 완료하지 못했습니다.`}),W(`p`,{class:`mt-1 mb-0 text-sm leading-6 text-rose-800`,children:n.lastError.message}),W(`details`,{class:`mt-3 rounded-lg border border-rose-200 bg-white p-3`,children:[W(`summary`,{class:`cursor-pointer text-xs font-extrabold text-rose-800`,children:`상세 정보 보기`}),W(`p`,{class:`mt-2 mb-0 break-all font-mono text-[11px] text-slate-600`,children:n.lastError.code})]})]})]})}):null,n.current.metadataAvailable?null:W(`div`,{class:`mt-5 rounded-xl border border-amber-200 bg-amber-50 px-4 py-3 text-xs leading-5 text-amber-900`,children:[W(`strong`,{class:`block font-extrabold`,children:`현재 펌웨어의 버전 정보를 완전히 확인할 수 없습니다.`}),W(`span`,{class:`mt-1 block text-amber-800`,children:`새 펌웨어를 설치하면 이후부터 정확한 설치 버전을 자동으로 확인할 수 있습니다.`}),W(`details`,{class:`mt-2`,children:[W(`summary`,{class:`cursor-pointer font-extrabold text-amber-800`,children:`기술 상세 보기`}),W(`p`,{class:`mt-2 mb-0 break-all font-mono text-[11px] leading-5 text-amber-800`,children:`SmartSafeHub build ID metadata is missing. Future images should include /usr/share/smartsafehub/firmware.json.`})]})]}),W(`dl`,{class:`mt-5 grid overflow-hidden rounded-xl border border-slate-200 bg-slate-200 sm:grid-cols-2 xl:grid-cols-4`,children:[W(`div`,{class:`bg-white p-4`,children:[W(`dt`,{class:`text-[11px] font-black uppercase tracking-[0.16em] text-slate-400`,children:`Device`}),W(`dd`,{class:`mt-2 mb-0 ml-0 break-all text-sm font-black text-slate-950`,children:n.current.deviceCode||n.current.boardName||`미확인`})]}),W(`div`,{class:`bg-white p-4`,children:[W(`dt`,{class:`text-[11px] font-black uppercase tracking-[0.16em] text-slate-400`,children:`Current version`}),W(`dd`,{class:`mt-2 mb-0 ml-0 break-all text-sm font-black text-slate-950`,children:n.current.releaseVersion||`미확인`}),n.current.buildId?W(`span`,{class:`mt-1 block break-all text-[11px] leading-5 text-slate-400`,children:[`빌드 ID `,n.current.buildId]}):n.current.openwrtVersion?W(`span`,{class:`mt-1 block text-[11px] leading-5 text-slate-400`,children:[`OpenWrt `,n.current.openwrtVersion]}):null]}),W(`div`,{class:`bg-white p-4`,children:[W(`dt`,{class:`text-[11px] font-black uppercase tracking-[0.16em] text-slate-400`,children:`Available version`}),W(`dd`,{class:`mt-2 mb-0 ml-0 break-all text-sm font-black ${n.updateAvailable?`text-amber-700`:`text-slate-950`}`,children:n.lastCheckAt?n.release?.version?`${n.release.version}${n.updateAvailable?``:` · 최신`}`:n.updateAvailable?`새 펌웨어`:`최신 버전`:`미확인`}),n.release?.buildId?W(`span`,{class:`mt-1 block break-all text-[11px] leading-5 text-slate-400`,children:[`빌드 ID `,n.release.buildId]}):null]}),W(`div`,{class:`bg-white p-4`,children:[W(`dt`,{class:`text-[11px] font-black uppercase tracking-[0.16em] text-slate-400`,children:`Last check`}),W(`dd`,{class:`mt-2 mb-0 ml-0 text-sm font-black text-slate-950`,children:W(Rv,{fallback:`아직 없음`,timestamp:n.lastCheckAt})})]})]})]}):null,n?.updateAvailable&&n.release?W(`section`,{class:`mt-5 rounded-xl border border-sky-200 bg-sky-50/60 p-4 sm:p-5`,"data-section":`available-firmware`,children:[W(`div`,{class:`flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between sm:gap-4`,children:[W(`div`,{children:[W(`p`,{class:`m-0 text-xs font-extrabold uppercase tracking-[0.16em] text-sky-700`,children:`Available firmware`}),W(`h3`,{class:`mt-2 mb-0 text-lg font-black text-slate-950`,children:`새 펌웨어가 있습니다.`}),W(`p`,{class:`mt-2 mb-0 text-sm leading-6 text-slate-600`,children:[`펌웨어 `,n.release.version||`미확인`,` · `,qy(n.release.sysupgrade.sizeBytes),` · `,(n.release.channel||n.settings.channel)===`beta`?`Beta`:`Stable`]})]}),W(`div`,{class:`flex shrink-0 flex-wrap items-center justify-start gap-2 sm:justify-end`,children:[W(`span`,{class:`rounded-full bg-white px-3 py-1.5 text-xs font-extrabold text-sky-700 ring-1 ring-inset ring-sky-200`,children:n.release.version||`Latest`}),k?W(`span`,{class:`inline-flex items-center gap-1.5 rounded-full bg-white px-3 py-1.5 text-xs font-extrabold text-slate-700 ring-1 ring-inset ring-sky-200`,"data-section":`firmware-bundled-software-chip`,children:[W(`span`,{class:`text-[11px] uppercase tracking-[0.12em] text-slate-500`,children:`관리 SW`}),W(`span`,{class:`text-slate-950`,children:k})]}):null]})]}),k?W(`div`,{class:`mt-4 rounded-xl border border-sky-100 bg-white p-4`,"data-section":`firmware-bundled-software`,children:[W(`div`,{class:`flex flex-wrap items-center justify-between gap-2`,children:[W(`span`,{class:`text-xs font-extrabold text-slate-800`,children:`포함된 관리 소프트웨어`}),W(`div`,{class:`flex flex-wrap items-center gap-2`,children:[W(`span`,{class:`text-sm font-black text-slate-950`,children:k}),j===`same`?W(`span`,{class:`rounded-full bg-emerald-50 px-2.5 py-1 text-[11px] font-extrabold text-emerald-700 ring-1 ring-inset ring-emerald-200`,children:`현재 버전과 동일`}):null]})]}),W(`p`,{class:`mt-2 mb-0 text-xs leading-5 text-slate-600`,children:j===`same`?`관리 소프트웨어는 이미 같은 버전입니다. 펌웨어 업데이트는 시스템 구성 요소를 갱신합니다.`:j===`older`?`현재 설치된 관리 소프트웨어(${A})보다 이전 버전이 포함되어 있습니다. 펌웨어 설치 전 버전을 확인해 주세요.`:j===`newer`?`현재 설치된 관리 소프트웨어(${A})보다 새 버전이 포함되어 있습니다.`:A?`현재 설치 버전 ${A} · 펌웨어 포함 버전 ${k}`:`이 펌웨어에 포함된 관리 소프트웨어 버전입니다.`})]}):null,n.release.releaseNotes.length?W(`ul`,{class:`mt-4 mb-0 space-y-2 rounded-xl bg-white p-4 pl-9 text-sm leading-6 text-slate-700 ring-1 ring-inset ring-sky-100`,children:n.release.releaseNotes.map((e,t)=>W(`li`,{children:e},`${t}-${e}`))}):null]}):null,T?W(`section`,{class:`mt-5 rounded-xl border border-amber-200 bg-amber-50 p-4 sm:p-5`,"data-section":`prepared-firmware`,children:W(`div`,{class:`flex gap-4`,children:[W(`span`,{class:`grid size-11 shrink-0 place-items-center rounded-xl bg-white text-amber-700 ring-1 ring-inset ring-amber-200`,children:W(Sn,{class:`size-5`})}),W(`div`,{class:`min-w-0 flex-1`,children:[W(`p`,{class:`m-0 text-xs font-extrabold uppercase tracking-[0.16em] text-amber-700`,children:`Verified image`}),W(`h3`,{class:`mt-2 mb-0 text-lg font-black text-amber-950`,children:`설치할 펌웨어가 준비되었습니다.`}),W(`p`,{class:`mt-2 mb-0 break-all text-sm leading-6 text-amber-900`,children:[T.filename||`firmware.bin`,` · `,qy(T.sizeBytes)]}),T.sha256?W(`p`,{class:`mt-2 mb-0 break-all font-mono text-[11px] leading-5 text-amber-800`,children:[`SHA-256 `,T.sha256]}):null,W(`div`,{class:`mt-5 flex items-start justify-between gap-4 rounded-xl border border-amber-200 bg-white p-4`,children:[W(`div`,{children:[W(`strong`,{class:`block text-sm font-black text-slate-950`,children:`현재 설정 유지`}),W(`span`,{class:`mt-1 block text-xs leading-5 text-slate-500`,children:`Wi-Fi, SafeShield, SmartSafeHub 설정을 유지합니다.`}),T.allowBackup?null:W(`span`,{class:`mt-2 block text-xs font-bold text-rose-700`,children:`이 이미지는 설정 유지 업그레이드를 지원하지 않습니다.`})]}),W(Zy,{checked:y,disabled:!T.allowBackup,onChange:x})]}),S?W(`div`,{class:`mt-4 rounded-xl border border-rose-200 bg-rose-50 p-4`,children:[W(`strong`,{class:`block text-sm font-black text-rose-950`,children:`펌웨어 설치를 시작할까요?`}),W(`p`,{class:`mt-1 mb-0 text-xs leading-5 text-rose-800`,children:`설치 중 전원을 끄면 장치가 복구 불가능한 상태가 될 수 있습니다. 이미지 검증은 설치 직전에 다시 수행됩니다.`}),W(`div`,{class:`mt-4 flex flex-wrap justify-end gap-2`,children:[W(`button`,{class:`min-h-10 rounded-lg border border-slate-300 bg-white px-3 py-2 text-xs font-extrabold text-slate-700`,onClick:()=>C(!1),type:`button`,children:`취소`}),W(`button`,{class:`min-h-10 rounded-lg bg-rose-700 px-3 py-2 text-xs font-extrabold text-white`,onClick:()=>{C(!1),f(y)},type:`button`,children:`설치 시작`})]})]}):W(`div`,{class:`mt-4 flex flex-wrap justify-end gap-2`,children:[W(`button`,{class:`min-h-10 rounded-lg border border-slate-300 bg-white px-3 py-2 text-xs font-extrabold text-slate-700`,disabled:e===`discard`,onClick:()=>void u(),type:`button`,children:`파일 삭제`}),W(`button`,{class:`inline-flex min-h-10 items-center gap-2 rounded-lg bg-rose-700 px-4 py-2 text-xs font-extrabold text-white`,onClick:()=>C(!0),type:`button`,children:[W(rn,{class:`size-4`}),`펌웨어 설치`]})]})]})]})}):null]}),W(`details`,{class:`group mx-5 mb-5 rounded-xl border border-slate-200 bg-slate-50/70 sm:mx-6 sm:mb-6`,"data-layout":`firmware-card-subsection`,"data-section":`manual-firmware`,children:[W(`summary`,{class:`flex cursor-pointer list-none items-center justify-between gap-4 px-4 py-4 sm:px-5 [&::-webkit-details-marker]:hidden`,children:[W(`div`,{class:`flex min-w-0 items-center gap-3`,children:[W(`span`,{class:`grid size-10 shrink-0 place-items-center rounded-xl bg-white text-slate-600 ring-1 ring-inset ring-slate-200`,children:W(hn,{class:`size-5 rotate-180`})}),W(`div`,{class:`min-w-0`,children:[W(`strong`,{class:`block text-sm font-black text-slate-950`,children:`수동 펌웨어 설치`}),W(`span`,{class:`mt-1 block text-xs leading-5 text-slate-500`,children:`펌웨어 파일(.bin)을 직접 업로드하여 검증한 뒤 설치합니다.`})]})]}),W(`span`,{class:`shrink-0 rounded-full bg-white px-3 py-1.5 text-[11px] font-extrabold text-slate-600 ring-1 ring-inset ring-slate-200 group-open:hidden`,children:`열기`}),W(`span`,{class:`hidden shrink-0 rounded-full bg-white px-3 py-1.5 text-[11px] font-extrabold text-slate-600 ring-1 ring-inset ring-slate-200 group-open:inline-flex`,children:`닫기`})]}),W(`div`,{class:`border-t border-slate-200 px-5 pb-5 pt-3 sm:px-6`,children:[W(`p`,{class:`mt-0 mb-4 text-xs leading-5 text-slate-500 lg:whitespace-nowrap`,children:"이 장치에 맞는 Sysupgrade 이미지(.bin)만 사용하세요. 업로드한 파일은 이미지 검증과 `sysupgrade --test`를 모두 통과해야 설치할 수 있으며 강제 설치는 제공하지 않습니다."}),W(`div`,{class:`rounded-xl border border-slate-200 bg-white p-4 sm:p-5`,"data-layout":`firmware-file-picker`,children:[W(`input`,{accept:`.bin,application/octet-stream`,"aria-label":`펌웨어 파일 선택`,class:`sr-only`,disabled:w||T!==null,onChange:e=>v(e.currentTarget.files?.[0]??null),ref:g,type:`file`}),W(`div`,{class:`flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between`,children:[W(`div`,{class:`flex min-w-0 items-center gap-3`,children:[W(`span`,{class:`grid size-10 shrink-0 place-items-center rounded-xl bg-slate-50 text-slate-500 ring-1 ring-inset ring-slate-200`,children:W(hn,{class:`size-5 rotate-180`})}),W(`div`,{class:`min-w-0`,children:[W(`strong`,{class:`block truncate text-sm font-black text-slate-900`,children:_?_.name:`펌웨어 파일을 선택하세요`}),W(`span`,{class:`mt-1 block text-xs leading-5 text-slate-500`,children:_?`${qy(_.size)} · Sysupgrade 이미지 (.bin)`:`이 장치에 맞는 Sysupgrade 이미지 (.bin)를 선택합니다.`})]})]}),W(`div`,{class:`flex shrink-0 flex-wrap gap-2 sm:justify-end`,children:[W(`button`,{class:`inline-flex min-h-10 items-center justify-center rounded-lg border border-slate-300 bg-white px-3.5 py-2 text-xs font-extrabold text-slate-700 transition hover:bg-slate-50 disabled:cursor-not-allowed disabled:opacity-50`,disabled:w||T!==null,onClick:()=>g.current?.click(),type:`button`,children:_?`다른 파일 선택`:`파일 선택`}),W(`button`,{class:`inline-flex min-h-10 items-center justify-center gap-2 rounded-lg bg-slate-900 px-3.5 py-2 text-xs font-extrabold text-white transition hover:bg-slate-800 disabled:cursor-not-allowed disabled:opacity-50`,disabled:!_||w||T!==null,onClick:()=>void M(),type:`button`,children:[O?W(pn,{class:`size-4 animate-spin`}):W(hn,{class:`size-4 rotate-180`}),O?`처리 중...`:`업로드 및 검증`]})]})]}),c===null?null:W(`div`,{class:`mt-4 border-t border-slate-100 pt-4`,children:[W(`div`,{class:`mb-2 flex items-center justify-between gap-3 text-xs font-bold text-slate-600`,children:[W(`span`,{children:`업로드 중`}),W(`span`,{children:[c,`%`]})]}),W(`div`,{class:`h-2 overflow-hidden rounded-full bg-slate-200`,children:W(`div`,{class:`h-full rounded-full bg-sky-600 transition-[width]`,style:{width:`${Math.max(0,Math.min(100,c))}%`}})})]})]})]})]})]})}var $y=[{value:3600,label:`1시간마다`},{value:21600,label:`6시간마다`},{value:43200,label:`12시간마다`},{value:86400,label:`24시간마다`}],eb=`luci-app-smartsafehub`;function tb(e){return e?new Date(e*1e3).toLocaleString(`ko-KR`,{dateStyle:`medium`,timeStyle:`short`}):`아직 없음`}function nb(e){return e===`stable`?`Stable`:e===`beta`?`Beta`:`미확인`}function rb(e){return e===`stable`?`bg-emerald-50 text-emerald-700 ring-emerald-200`:e===`beta`?`bg-amber-50 text-amber-700 ring-amber-200`:`bg-slate-100 text-slate-600 ring-slate-200`}function ib(e){return e.code===`UPDATES_INDEX_REFRESH_FAILED`?{title:`패키지 저장소를 확인하지 못했습니다.`,description:`OpenWrt 패키지 저장소 동기화 또는 네트워크 상태가 일시적으로 불안정할 수 있습니다. 잠시 후 업데이트 확인을 다시 실행해 주세요.`}:e.code===`UPDATES_INSTALL_FAILED`?{title:`관리 소프트웨어 업데이트 설치에 실패했습니다.`,description:`패키지 설치가 완료되지 않았습니다. 네트워크 연결과 저장 공간을 확인한 뒤 다시 시도해 주세요.`}:{title:`최근 업데이트 작업을 완료하지 못했습니다.`,description:`잠시 후 다시 시도해 주세요. 문제가 반복되면 상세 정보를 확인해 주세요.`}}function ab(e,t){return e.phase===`checking`?`업데이트 확인 중`:e.phase===`installing`?`업데이트 설치 중`:e.phase===`error`?`확인 필요`:e.updateCount>0?`업데이트 가능`:t?`업데이트 확인 지연`:e.lastCheckAt?`최신 상태`:`확인 전`}function ob(e,t){return e.phase===`error`?`bg-rose-50 text-rose-700 ring-rose-200`:e.phase===`checking`||e.phase===`installing`?`bg-sky-50 text-sky-700 ring-sky-200`:e.updateCount>0||t?`bg-amber-50 text-amber-800 ring-amber-200`:e.lastCheckAt?`bg-emerald-50 text-emerald-700 ring-emerald-200`:`bg-slate-100 text-slate-600 ring-slate-200`}function sb(e,t){return e.phase===`checking`||e.phase===`installing`?W(pn,{class:`size-3.5 shrink-0 animate-spin`}):e.phase===`error`?W(Cn,{class:`size-3.5 shrink-0`}):e.updateCount>0?W(hn,{class:`size-3.5 shrink-0`}):t?W(Cn,{class:`size-3.5 shrink-0`}):e.lastCheckAt?W(Sn,{class:`size-3.5 shrink-0`}):W(dn,{class:`size-3.5 shrink-0`})}function cb({checked:e,disabled:t=!1,label:n,onChange:r}){return W(`button`,{"aria-checked":e,"aria-label":n,class:`ssh-switch-control relative inline-flex shrink-0 rounded-full border transition focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-100 disabled:cursor-not-allowed disabled:opacity-50 ${e?`border-teal-600 bg-teal-600`:`border-slate-300 bg-slate-200`}`,disabled:t,onClick:()=>r(!e),role:`switch`,type:`button`,children:W(`span`,{"aria-hidden":`true`,class:`ssh-switch-thumb absolute top-1 shadow-sm transition-[left] ${e?`left-6`:`left-1`}`})})}function lb({action:e,actionError:t,data:n,firmware:r,error:i,loading:a,message:o,onCheck:s,onDismissFeedback:c,onInstall:l,onRetry:u,onSaveSettings:d}){let[f,p]=L(!0),[m,h]=L(21600),[g,_]=L(!1),[v,y]=L(`03:00`),[x,S]=L(!1),[C,w]=L(!1);R(()=>{n&&!x&&(p(n.settings.checkEnabled),h(n.settings.checkIntervalSeconds),_(n.settings.autoInstall),y(n.settings.autoInstallTime))},[n,x]);let T=e===`check`||n?.phase===`checking`,E=e===`install`||n?.phase===`installing`,D=T||E,O=u_(n),k=n?.phase===`error`&&n.lastError?.code===`UPDATES_INDEX_REFRESH_FAILED`,A=n?.lastError?ib(n.lastError):null,j=n?.packages.find(e=>e.name===eb)??n?.packages[0]??null,M=r?.updateAvailable?r.release?.bundledSoftware?.smartsafehub:null,N=!(!n||n.releaseNotesComplete||n.releaseNotes.length!==1||j?.name!==eb||n.releaseNotes[0]?.version!==j.availableVersion),P=!(!n?.lastCheckAt||n.updateCount!==0||O||j?.name!==eb)&&Ky(M,j?.installedVersion)===`same`,F=(e={})=>n?(e.checkEnabled??f)!==n.settings.checkEnabled||(e.checkIntervalSeconds??m)!==n.settings.checkIntervalSeconds||(e.autoInstall??g)!==n.settings.autoInstall||(e.autoInstallTime??v)!==n.settings.autoInstallTime:!1,I=async()=>{await d({checkEnabled:f,checkIntervalSeconds:m,autoInstall:g,autoInstallTime:v})&&S(!1)};return W(`article`,{class:`min-w-0 overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm shadow-slate-900/5 ${n?`grid xl:grid-cols-[minmax(0,0.95fr)_minmax(0,1.05fr)]`:``}`,"data-component":`management-software-update-card`,"data-layout":`management-software-sections`,children:[W(`section`,{class:`min-w-0 xl:col-start-1 xl:row-start-1`,"data-section":`software-update-status`,children:[W(`div`,{class:`p-5 sm:p-6`,children:[W(`div`,{class:`flex flex-col gap-5 lg:flex-row lg:items-start lg:justify-between`,children:[W(`div`,{class:`flex min-w-0 gap-4`,children:[W(`span`,{class:`grid size-12 shrink-0 place-items-center rounded-2xl bg-teal-50 text-teal-700`,children:W(rn,{class:`size-6`})}),W(`div`,{class:`min-w-0`,children:[W(`div`,{class:`flex flex-wrap items-center gap-2`,children:[W(`p`,{class:`m-0 text-xs font-extrabold uppercase tracking-[0.16em] text-teal-700`,children:`Management software`}),n?W(`span`,{class:`inline-flex items-center gap-1.5 rounded-full px-2.5 py-1 text-[11px] font-extrabold ring-1 ring-inset ${ob(n,O)}`,children:[W(`span`,{"aria-hidden":`true`,class:`flex items-center justify-center`,children:sb(n,O)}),ab(n,O)]}):null]}),W(`h2`,{class:`mt-2 mb-0 text-xl font-black text-slate-950 sm:text-2xl`,children:`관리 소프트웨어 업데이트`}),W(`p`,{class:`mt-2 mb-0 text-sm leading-6 text-slate-500 xl:whitespace-nowrap`,children:`새 버전을 확인하고 안전하게 설치합니다.`})]})]}),n?W(`div`,{class:`flex shrink-0 flex-col gap-2 sm:flex-row lg:justify-end`,children:[W(`button`,{class:`inline-flex min-h-11 items-center justify-center gap-2 rounded-xl border border-slate-300 bg-white px-4 py-2.5 text-sm font-extrabold text-slate-800 transition hover:bg-slate-50 disabled:cursor-wait disabled:opacity-60`,disabled:D,onClick:s,type:`button`,children:[W(pn,{class:`size-4 ${T?`animate-spin`:``}`}),T?`확인 중...`:`업데이트 확인`]}),n.updateCount>0&&!C?W(`button`,{class:`inline-flex min-h-11 items-center justify-center gap-2 rounded-xl bg-teal-700 px-4 py-2.5 text-sm font-extrabold text-white transition hover:bg-teal-800 disabled:cursor-wait disabled:opacity-60`,disabled:D,onClick:()=>w(!0),type:`button`,children:[E?W(pn,{class:`size-4 animate-spin`}):W(hn,{class:`size-4`}),E?`설치 중...`:`업데이트 설치`]}):null]}):null]}),(t||o)&&W(`div`,{class:`mt-5 flex flex-col gap-3 rounded-xl border px-4 py-3 text-sm font-bold sm:flex-row sm:items-center sm:justify-between ${t?`border-rose-200 bg-rose-50 text-rose-800`:`border-emerald-200 bg-emerald-50 text-emerald-800`}`,children:[W(`span`,{children:t||o}),W(`button`,{class:`shrink-0 self-start rounded-lg px-3 py-1.5 text-xs font-extrabold hover:bg-black/5 sm:self-auto`,onClick:c,type:`button`,children:`닫기`})]}),E?W(`div`,{"aria-busy":`true`,"aria-live":`polite`,class:`mt-5 rounded-xl border border-sky-200 bg-sky-50 p-4 sm:p-5`,children:[W(`div`,{class:`flex items-start gap-3`,children:[W(`span`,{class:`grid size-10 shrink-0 place-items-center rounded-xl bg-white text-sky-700 ring-1 ring-inset ring-sky-200`,children:W(pn,{"aria-hidden":`true`,class:`size-5 animate-spin`})}),W(`div`,{class:`min-w-0`,children:[W(`h3`,{class:`m-0 text-sm font-black text-sky-950`,children:`관리 소프트웨어를 업데이트하고 있습니다.`}),W(`p`,{class:`mt-1 mb-0 text-sm leading-6 text-sky-800`,children:`설치가 완료되면 이 화면이 자동으로 갱신됩니다. 작업 중에는 웹 연결이 잠시 끊길 수 있습니다.`})]})]}),W(`div`,{"aria-hidden":`true`,class:`ssh-update-progress-track mt-4`,children:W(`span`,{class:`ssh-update-progress-bar`})})]}):null,i&&!n?W(`div`,{class:`mt-5 rounded-xl border border-rose-200 bg-rose-50 p-4 text-sm text-rose-800`,children:[W(`p`,{class:`m-0 font-bold`,children:i}),W(`button`,{class:`mt-3 rounded-lg border border-rose-300 bg-white px-3 py-2 text-xs font-extrabold text-rose-700`,onClick:u,type:`button`,children:`다시 시도`})]}):a&&!n?W(`div`,{class:`mt-5 flex items-center gap-3 rounded-xl bg-slate-50 p-4 text-sm font-bold text-slate-500`,children:[W(`span`,{class:`grid size-9 shrink-0 place-items-center rounded-xl bg-white text-teal-700 ring-1 ring-inset ring-slate-200`,children:W(pn,{"aria-hidden":`true`,class:`size-4 animate-spin`})}),W(`span`,{children:`관리 소프트웨어 업데이트 상태를 확인하고 있습니다.`})]}):n?W(b,{children:[n.lastError&&A?W(`div`,{class:`mt-5 rounded-xl border border-rose-200 bg-rose-50 p-4 sm:p-5`,children:W(`div`,{class:`flex items-start gap-3`,children:[W(`span`,{class:`grid size-10 shrink-0 place-items-center rounded-xl bg-white text-rose-700 ring-1 ring-inset ring-rose-200`,children:W(Cn,{class:`size-5`})}),W(`div`,{class:`min-w-0 flex-1`,children:[W(`p`,{class:`m-0 text-sm font-extrabold text-rose-950`,children:A.title}),W(`p`,{class:`mt-1 mb-0 text-sm leading-6 text-rose-800`,children:A.description}),n.lastError.at?W(`p`,{class:`mt-2 mb-0 text-xs font-bold text-rose-700`,children:[`발생 시각 `,tb(n.lastError.at)]}):null,W(`details`,{class:`mt-3 rounded-lg border border-rose-200 bg-white p-3`,children:[W(`summary`,{class:`cursor-pointer text-xs font-extrabold text-rose-800`,children:`상세 정보 보기`}),W(`div`,{class:`mt-3 grid gap-2 text-xs leading-5 text-slate-600`,children:[W(`p`,{class:`m-0`,children:[`오류 코드 `,W(`strong`,{class:`font-black text-slate-900`,children:n.lastError.code})]}),W(`pre`,{class:`m-0 max-h-40 overflow-auto whitespace-pre-wrap break-words rounded-lg bg-slate-50 p-3 font-mono text-[11px] leading-5 text-slate-700`,children:n.lastError.message})]})]})]})]})}):null,C?W(`div`,{class:`mt-5 flex flex-col gap-3 rounded-xl border border-amber-200 bg-amber-50 p-4 sm:flex-row sm:items-center sm:justify-between`,children:[W(`div`,{class:`min-w-0`,children:[W(`strong`,{class:`block text-sm font-black text-amber-900`,children:`관리 소프트웨어를 지금 업데이트할까요?`}),W(`span`,{class:`mt-1 block text-xs leading-5 text-amber-800`,children:`설치 중 SmartSafeHub 웹 화면 연결이 잠시 끊길 수 있습니다.`})]}),W(`div`,{class:`flex shrink-0 gap-2`,children:[W(`button`,{class:`min-h-10 rounded-lg border border-slate-300 bg-white px-3 py-2 text-xs font-extrabold text-slate-700`,disabled:D,onClick:()=>w(!1),type:`button`,children:`취소`}),W(`button`,{class:`min-h-10 rounded-lg bg-amber-700 px-3 py-2 text-xs font-extrabold text-white disabled:opacity-60`,disabled:D,onClick:()=>{w(!1),l()},type:`button`,children:`설치 시작`})]})]}):null]}):null]}),n?W(`div`,{class:`px-5 pb-5 sm:px-6 sm:pb-6`,children:[W(`h3`,{class:`mb-3 text-sm font-black text-slate-950`,children:`현재 상태`}),W(`dl`,{class:`grid overflow-hidden rounded-xl border border-slate-200 bg-slate-200 sm:grid-cols-2`,children:[W(`div`,{class:`bg-white p-4`,children:[W(`dt`,{class:`text-[11px] font-black uppercase tracking-[0.16em] text-slate-400`,children:`Installed`}),W(`dd`,{class:`mt-2 mb-0 ml-0 break-all text-sm font-black text-slate-950`,children:j?.installedVersion||`미확인`})]}),W(`div`,{class:`bg-white p-4`,children:[W(`dt`,{class:`text-[11px] font-black uppercase tracking-[0.16em] text-slate-400`,children:`Available`}),W(`dd`,{class:`mt-2 mb-0 ml-0 break-all text-sm font-black ${j?.updateAvailable||O?`text-amber-700`:`text-slate-950`}`,children:!n.lastCheckAt||k&&!j?.updateAvailable?`미확인`:j?.updateAvailable&&j.availableVersion?j.availableVersion:O?`확인 필요`:`최신 버전`})]}),W(`div`,{class:`bg-white p-4`,children:[W(`dt`,{class:`text-[11px] font-black uppercase tracking-[0.16em] text-slate-400`,children:`Last check`}),W(`dd`,{class:`mt-2 mb-0 ml-0 text-sm font-black text-slate-950`,children:W(Rv,{fallback:`아직 없음`,timestamp:n.lastCheckAt})})]}),W(`div`,{class:`bg-white p-4`,children:[W(`dt`,{class:`text-[11px] font-black uppercase tracking-[0.16em] text-slate-400`,children:`Auto install`}),W(`dd`,{class:`mt-2 mb-0 ml-0 text-sm font-black text-slate-950`,children:n.settings.autoInstall?`켜짐 · ${n.settings.autoInstallTime}`:`꺼짐`})]})]})]}):null,n&&n.updateCount===0&&n.lastCheckAt?O?W(`section`,{class:`mx-5 mb-5 rounded-xl border border-amber-200 bg-amber-50 p-4 sm:mx-6 sm:mb-6 sm:p-5`,"data-section":`software-update-result`,children:W(`div`,{class:`flex gap-3`,children:[W(`span`,{class:`grid size-10 shrink-0 place-items-center rounded-xl bg-white text-amber-700 ring-1 ring-inset ring-amber-200`,children:W(Cn,{class:`size-5`})}),W(`div`,{children:[W(`h3`,{class:`m-0 text-base font-black text-amber-950`,children:`업데이트 확인이 지연되고 있습니다.`}),W(`p`,{class:`mt-1 mb-0 text-sm leading-6 text-amber-800`,children:[`마지막 업데이트 확인: `,W(Rv,{timestamp:n.lastCheckAt}),`. 자동 확인 상태를 점검하거나 업데이트 확인을 실행해 주세요.`]})]})]})}):W(`section`,{class:`mx-5 mb-5 rounded-xl border border-emerald-200 bg-emerald-50 p-4 sm:mx-6 sm:mb-6 sm:p-5`,"data-section":`software-update-result`,children:W(`div`,{class:`flex gap-3`,children:[W(`span`,{class:`grid size-10 shrink-0 place-items-center rounded-xl bg-white text-emerald-700`,children:W(Sn,{class:`size-5`})}),W(`div`,{children:[W(`h3`,{class:`m-0 text-base font-black text-emerald-950`,children:`최신 버전을 사용 중입니다.`}),W(`p`,{class:`mt-1 mb-0 text-sm leading-6 text-emerald-800`,children:[`마지막 업데이트 확인: `,W(Rv,{timestamp:n.lastCheckAt}),`.`]}),P?W(`p`,{class:`mt-2 mb-0 text-xs leading-5 text-emerald-800`,"data-section":`software-firmware-match`,children:[`새 펌웨어에도 동일한 관리 소프트웨어(`,M,`)가 포함되어 있어 별도로 업데이트할 필요가 없습니다.`]}):null]})]})}):null,n&&n.updateCount===0&&!n.lastCheckAt?W(`section`,{class:`mx-5 mb-5 rounded-xl border border-slate-200 bg-slate-50 p-4 sm:mx-6 sm:mb-6 sm:p-5`,"data-section":`software-update-result`,children:W(`div`,{class:`flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between`,children:[W(`div`,{class:`flex min-w-0 gap-3`,children:[W(`span`,{class:`grid size-10 shrink-0 place-items-center rounded-xl bg-white text-slate-500 ring-1 ring-inset ring-slate-200`,children:W(pn,{class:`size-5`})}),W(`div`,{class:`min-w-0`,children:[W(`h3`,{class:`m-0 text-base font-black text-slate-900`,children:`업데이트 상태를 아직 확인하지 않았습니다.`}),W(`p`,{class:`mt-1 mb-0 text-sm leading-6 text-slate-600`,children:`업데이트 확인을 실행하면 현재 설치 버전과 사용 가능한 최신 버전을 확인합니다.`})]})]}),W(`button`,{class:`inline-flex min-h-10 shrink-0 items-center justify-center gap-2 rounded-xl border border-slate-300 bg-white px-4 py-2 text-sm font-extrabold text-slate-800 transition hover:border-teal-300 hover:text-teal-700 disabled:cursor-wait disabled:opacity-60`,disabled:D,onClick:s,type:`button`,children:[W(pn,{class:`size-4`}),`지금 확인`]})]})}):null]}),n?W(b,{children:[W(`section`,{class:`min-w-0 border-t border-slate-200 p-5 sm:p-6 xl:col-start-2 xl:row-start-1 xl:row-span-2 xl:self-stretch xl:border-t-0 xl:border-l`,"data-section":`software-update-settings`,children:[W(`div`,{class:`flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between`,children:[W(`div`,{class:`flex min-w-0 gap-4`,children:[W(`span`,{class:`grid size-11 shrink-0 place-items-center rounded-xl bg-slate-100 text-slate-600`,children:W(an,{class:`size-5`})}),W(`div`,{class:`min-w-0`,children:[W(`p`,{class:`m-0 text-xs font-extrabold uppercase tracking-[0.16em] text-slate-500`,children:`Settings`}),W(`h3`,{class:`mt-2 mb-0 text-lg font-black text-slate-950`,children:`자동 업데이트`}),W(`p`,{class:`mt-2 mb-0 max-w-3xl text-sm leading-6 text-slate-500`,children:`관리 소프트웨어의 자동 확인과 자동 설치를 설정합니다.`})]})]}),x?W(`span`,{"aria-live":`polite`,class:`inline-flex shrink-0 self-start items-center gap-1.5 rounded-full border border-amber-200 bg-amber-50 px-2.5 py-1 text-xs font-extrabold text-amber-800`,role:`status`,children:[W(Cn,{"aria-hidden":`true`,class:`size-3.5 shrink-0`}),`저장되지 않음`]}):null]}),W(`div`,{class:`ssh-software-settings-grid mt-4`,children:[W(`div`,{class:`ssh-software-settings-channel rounded-xl border border-slate-200 p-4 sm:p-5`,children:W(`div`,{class:`flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between`,children:[W(`div`,{class:`min-w-0`,children:[W(`strong`,{class:`block text-sm font-black text-slate-950`,children:`업데이트 채널`}),W(`span`,{class:`mt-1 block text-xs leading-5 text-slate-500`,children:`현재 관리 소프트웨어 업데이트를 확인하는 배포 채널입니다.`})]}),W(`span`,{class:`inline-flex min-h-9 shrink-0 items-center justify-center rounded-full px-3 py-1.5 text-xs font-extrabold ring-1 ring-inset ${rb(n.settings.channel)}`,title:`현재 업데이트 확인 채널`,children:nb(n.settings.channel)})]})}),W(`div`,{class:`min-w-0 overflow-hidden rounded-xl border border-slate-200 p-4 sm:p-5`,children:[W(`div`,{class:`flex min-w-0 items-start justify-between gap-4`,children:[W(`div`,{class:`min-w-0`,children:W(`strong`,{class:`block text-sm font-black text-slate-950`,children:`자동으로 업데이트 확인`})}),W(cb,{checked:f,label:`자동 업데이트 확인`,onChange:e=>{p(e),S(F({checkEnabled:e}))}})]}),W(`div`,{class:`mt-4 block`,children:[W(`span`,{class:`text-xs font-extrabold text-slate-700`,children:`확인 주기`}),W(W_,{ariaLabel:`업데이트 확인 주기`,className:`mt-2`,disabled:!f,id:`software-update-check-interval`,onChange:e=>{let t=Number(e);h(t),S(F({checkIntervalSeconds:t}))},options:$y.map(e=>({value:String(e.value),label:e.label})),value:String(m),variant:`emphasized`})]})]}),W(`div`,{class:`min-w-0 overflow-hidden rounded-xl border border-slate-200 p-4 sm:p-5`,children:[W(`div`,{class:`flex min-w-0 items-start justify-between gap-4`,children:[W(`div`,{class:`min-w-0`,children:W(`strong`,{class:`block text-sm font-black text-slate-950`,children:`자동으로 업데이트 설치`})}),W(cb,{checked:g,label:`자동 업데이트 설치`,onChange:e=>{_(e),S(F({autoInstall:e}))}})]}),W(`div`,{class:`mt-4 min-w-0`,children:[W(`span`,{class:`text-xs font-extrabold text-slate-700`,children:`설치 시각`}),W(Sy,{ariaLabel:`자동 업데이트 설치 시각`,className:`mt-2`,disabled:!g,id:`software-update-auto-install-time`,onChange:e=>{y(e),S(F({autoInstallTime:e}))},value:v,variant:`emphasized`})]})]})]}),g?W(`p`,{class:`mt-4 mb-0 rounded-xl bg-amber-50 px-4 py-3 text-xs font-bold leading-5 text-amber-800`,children:`자동 설치 중에는 SmartSafeHub 웹 화면 연결이 잠시 끊길 수 있습니다.`}):null,W(`div`,{class:`mt-5 flex flex-col gap-3 border-t border-slate-100 pt-5 sm:flex-row sm:items-center sm:justify-between`,children:[W(`p`,{class:`m-0 text-xs leading-5 text-slate-500`,children:[`마지막 설치: `,tb(n.lastInstallAt)]}),W(`button`,{class:`inline-flex min-h-11 items-center justify-center rounded-xl bg-slate-900 px-4 py-2.5 text-sm font-extrabold text-white transition hover:bg-slate-800 disabled:cursor-not-allowed disabled:opacity-50`,disabled:!x||e===`settings`||D,onClick:()=>void I(),type:`button`,children:e===`settings`?`저장 중`:`자동 업데이트 설정 저장`})]})]}),n.updateCount>0?W(`section`,{class:`border-t border-slate-200 p-5 sm:p-6 xl:col-start-1 xl:row-start-2`,"data-section":`software-update-release-notes`,children:[W(`div`,{class:`flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between sm:gap-4`,children:[W(`div`,{children:[W(`p`,{class:`m-0 text-xs font-extrabold uppercase tracking-[0.16em] text-teal-700`,children:`Release notes`}),W(`h3`,{class:`mt-2 mb-0 text-lg font-black text-slate-950`,children:`업데이트 내용`}),W(`p`,{class:`mt-2 mb-0 text-sm leading-6 text-slate-500`,children:N?`릴리즈 내역이 많거나 일부 내역을 가져올 수 없어 최신 버전의 변경 사항만 표시합니다.`:`현재 설치된 버전 이후 포함되는 주요 변경 사항입니다.`})]}),n.releaseNotes.length?W(`span`,{class:`shrink-0 rounded-full bg-slate-100 px-3 py-1.5 text-xs font-extrabold text-slate-600`,children:N?`최신 릴리즈 노트`:n.releaseNotes.length===1?`1개 릴리즈`:`${n.releaseNotes.length}개 릴리즈`}):null]}),n.releaseNotes.length?W(`div`,{class:`mt-5 grid gap-3`,children:[n.releaseNotes.map(e=>W(`article`,{class:`rounded-xl border border-slate-200 bg-slate-50 p-4 sm:p-5`,children:[W(`div`,{class:`flex flex-col gap-1 sm:flex-row sm:items-center sm:justify-between sm:gap-4`,children:[W(`strong`,{class:`break-all text-sm font-black text-slate-950`,children:e.version}),e.date?W(`span`,{class:`text-xs font-bold text-slate-500`,children:e.date}):null]}),e.summary?W(`p`,{class:`mt-3 mb-0 text-sm leading-6 text-slate-700`,children:e.summary}):null,e.sections.length?W(`details`,{class:`mt-4 rounded-lg border border-slate-200 bg-white p-3`,open:n.releaseNotes.length===1,children:[W(`summary`,{class:`cursor-pointer text-xs font-extrabold text-slate-800`,children:`자세한 변경 사항`}),W(`div`,{class:`mt-3 grid gap-4`,children:e.sections.map(t=>W(`section`,{children:[W(`h4`,{class:`m-0 text-xs font-extrabold text-slate-700`,children:t.title}),W(`ul`,{class:`mt-2 mb-0 space-y-1.5 pl-5 text-xs leading-5 text-slate-600`,children:t.items.map((n,r)=>W(`li`,{children:n},`${e.version}-${t.title}-${r}`))})]},`${e.version}-${t.title}`))})]}):null]},e.version)),n.releaseNotesComplete?null:W(`p`,{class:`m-0 rounded-xl bg-amber-50 px-4 py-3 text-xs font-bold leading-5 text-amber-800`,children:N?`최신 버전의 릴리즈 노트만 표시합니다. 이전 버전의 변경 사항은 생략되었을 수 있습니다.`:`일부 릴리즈 노트를 불러오지 못했습니다. 업데이트 확인과 설치는 계속 사용할 수 있습니다.`})]}):W(`p`,{class:`mt-4 mb-0 rounded-xl bg-slate-50 px-4 py-3 text-sm text-slate-500`,children:`릴리즈 노트를 불러오지 못했습니다. 업데이트 확인과 설치는 계속 사용할 수 있습니다.`})]}):null]}):null]})}function ub({action:e,actionError:t,data:n,error:r,firmware:i,loading:a,message:o,onCheck:s,onDismissFeedback:c,onInstall:l,onRetry:u,onSaveSettings:d}){return W(`section`,{class:`min-w-0 space-y-5`,children:[W(`div`,{children:W(J,{eyebrow:`Firmware`,title:`시스템 펌웨어`,description:`공유기 운영체제의 새 버전을 확인하고 설치합니다.`})}),W(Qy,{action:i.action,actionError:i.actionError,data:i.data,software:n,error:i.error,loading:i.loading,message:i.message,reconnecting:i.reconnecting,uploadProgress:i.uploadProgress,onCheck:()=>void i.check(),onDiscard:i.discard,onDismissFeedback:i.dismissFeedback,onInstall:i.install,onPrepare:()=>void i.prepare(),onRetry:()=>void i.refresh(),onUpload:i.upload}),W(`div`,{class:`pt-2`,children:W(J,{eyebrow:`Software`,title:`관리 소프트웨어`,description:`SmartSafeHub 관리 화면 및 서비스의 업데이트를 확인합니다.`})}),W(lb,{firmware:i.data,action:e,actionError:t,data:n,error:r,loading:a,message:o,onCheck:s,onDismissFeedback:c,onInstall:l,onRetry:u,onSaveSettings:d})]})}var db={};db.QR8bitByte=(e,t)=>{var n=t("QRMode");function r(e){this.mode=n.MODE_8BIT_BYTE,this.data=e}r.prototype={getLength:function(){return this.data.length},write:function(e){for(var t=0;t{function n(){this.buffer=[],this.length=0}n.prototype={get:function(e){var t=Math.floor(e/8);return(this.buffer[t]>>>7-e%8&1)==1},put:function(e,t){for(var n=0;n>>t-n-1&1)==1)},getLengthInBits:function(){return this.length},putBit:function(e){var t=Math.floor(this.length/8);this.buffer.length<=t&&this.buffer.push(0),e&&(this.buffer[t]|=128>>>this.length%8),this.length++}},e.exports=n},db.QRErrorCorrectLevel=(e,t)=>{e.exports={L:1,M:0,Q:3,H:2}},db.QRMaskPattern=(e,t)=>{e.exports={PATTERN000:0,PATTERN001:1,PATTERN010:2,PATTERN011:3,PATTERN100:4,PATTERN101:5,PATTERN110:6,PATTERN111:7}},db.QRMath=(e,t)=>{for(var n={glog:function(e){if(e<1)throw Error(`glog(`+e+`)`);return n.LOG_TABLE[e]},gexp:function(e){for(;e<0;)e+=255;for(;e>=256;)e-=255;return n.EXP_TABLE[e]},EXP_TABLE:Array(256),LOG_TABLE:Array(256)},r=0;r<8;r++)n.EXP_TABLE[r]=1<{e.exports={MODE_NUMBER:1,MODE_ALPHA_NUM:2,MODE_8BIT_BYTE:4,MODE_KANJI:8}},db.QRPolynomial=(e,t)=>{var n=t("QRMath");function r(e,t){if(e.length===void 0)throw Error(e.length+`/`+t);for(var n=0;n{var n=t("QRErrorCorrectLevel");function r(e,t){this.totalCount=e,this.dataCount=t}r.RS_BLOCK_TABLE=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]],r.getRSBlocks=function(e,t){var n=r.getRsBlockTable(e,t);if(n===void 0)throw Error(`bad rs block @ typeNumber:`+e+`/errorCorrectLevel:`+t);for(var i=n.length/3,a=[],o=0;o{var n=t("QRMode"),r=t("QRPolynomial"),i=t("QRMath"),a=t("QRMaskPattern"),o={PATTERN_POSITION_TABLE:[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],G15:1335,G18:7973,G15_MASK:21522,getBCHTypeInfo:function(e){for(var t=e<<10;o.getBCHDigit(t)-o.getBCHDigit(o.G15)>=0;)t^=o.G15<=0;)t^=o.G18<>>=1;return t},getPatternPosition:function(e){return o.PATTERN_POSITION_TABLE[e-1]},getMask:function(e,t,n){switch(e){case a.PATTERN000:return(t+n)%2==0;case a.PATTERN001:return t%2==0;case a.PATTERN010:return n%3==0;case a.PATTERN011:return(t+n)%3==0;case a.PATTERN100:return(Math.floor(t/2)+Math.floor(n/3))%2==0;case a.PATTERN101:return t*n%2+t*n%3==0;case a.PATTERN110:return(t*n%2+t*n%3)%2==0;case a.PATTERN111:return(t*n%3+(t+n)%2)%2==0;default:throw Error(`bad maskPattern:`+e)}},getErrorCorrectPolynomial:function(e){for(var t=new r([1],0),n=0;n5&&(n+=3+a-5)}for(r=0;r{var n=t("QR8bitByte"),r=t("QRUtil"),i=t("QRPolynomial"),a=t("QRRSBlock"),o=t("QRBitBuffer");function s(e,t){this.typeNumber=e,this.errorCorrectLevel=t,this.modules=null,this.moduleCount=0,this.dataCache=null,this.dataList=[]}s.prototype={addData:function(e){var t=new n(e);this.dataList.push(t),this.dataCache=null},isDark:function(e,t){if(e<0||this.moduleCount<=e||t<0||this.moduleCount<=t)throw Error(e+`,`+t);return this.modules[e][t]},getModuleCount:function(){return this.moduleCount},make:function(){if(this.typeNumber<1){var e=1;for(e=1;e<40;e++){for(var t=a.getRSBlocks(e,this.errorCorrectLevel),n=new o,i=0,s=0;s=7&&this.setupTypeNumber(e),this.dataCache===null&&(this.dataCache=s.createData(this.typeNumber,this.errorCorrectLevel,this.dataList)),this.mapData(this.dataCache,t)},setupPositionProbePattern:function(e,t){for(var n=-1;n<=7;n++)if(!(e+n<=-1||this.moduleCount<=e+n))for(var r=-1;r<=7;r++)t+r<=-1||this.moduleCount<=t+r||(0<=n&&n<=6&&(r===0||r===6)||0<=r&&r<=6&&(n===0||n===6)||2<=n&&n<=4&&2<=r&&r<=4?this.modules[e+n][t+r]=!0:this.modules[e+n][t+r]=!1)},getBestMaskPattern:function(){for(var e=0,t=0,n=0;n<8;n++){this.makeImpl(!0,n);var i=r.getLostPoint(this);(n===0||e>i)&&(e=i,t=n)}return t},createMovieClip:function(e,t,n){var r=e.createEmptyMovieClip(t,n),i=1;this.make();for(var a=0;a>i&1)==1,this.modules[Math.floor(i/3)][i%3+this.moduleCount-8-3]=n;for(var a=0;a<18;a++)n=!e&&(t>>a&1)==1,this.modules[a%3+this.moduleCount-8-3][Math.floor(a/3)]=n},setupTypeInfo:function(e,t){for(var n=this.errorCorrectLevel<<3|t,i=r.getBCHTypeInfo(n),a,o=0;o<15;o++)a=!e&&(i>>o&1)==1,o<6?this.modules[o][8]=a:o<8?this.modules[o+1][8]=a:this.modules[this.moduleCount-15+o][8]=a;for(var s=0;s<15;s++)a=!e&&(i>>s&1)==1,s<8?this.modules[8][this.moduleCount-s-1]=a:s<9?this.modules[8][15-s-1+1]=a:this.modules[8][15-s-1]=a;this.modules[this.moduleCount-8][8]=!e},mapData:function(e,t){for(var n=-1,i=this.moduleCount-1,a=7,o=0,s=this.moduleCount-1;s>0;s-=2)for(s===6&&s--;;){for(var c=0;c<2;c++)if(this.modules[i][s-c]===null){var l=!1;o>>a&1)==1),r.getMask(t,i,s-c)&&(l=!l),this.modules[i][s-c]=l,a--,a===-1&&(o++,a=7)}if(i+=n,i<0||this.moduleCount<=i){i-=n,n=-n;break}}}},s.PAD0=236,s.PAD1=17,s.createData=function(e,t,n){for(var i=a.getRSBlocks(e,t),c=new o,l=0;ld*8)throw Error(`code length overflow. (`+c.getLengthInBits()+`>`+d*8+`)`);for(c.getLengthInBits()+4<=d*8&&c.put(0,4);c.getLengthInBits()%8!=0;)c.putBit(!1);for(;!(c.getLengthInBits()>=d*8||(c.put(s.PAD0,8),c.getLengthInBits()>=d*8));)c.put(s.PAD1,8);return s.createBytes(c,i)},s.createBytes=function(e,t){for(var n=0,a=0,o=0,s=Array(t.length),c=Array(t.length),l=0;l=0?m.get(g):0}}for(var _=0,v=0;vString.fromCharCode(e)).join(``),r=new(pb(`index`))(0,pb(`QRErrorCorrectLevel`).M);r.addData(n),r.make();let i=r.getModuleCount();return Array.from({length:i},(e,t)=>Array.from({length:i},(e,n)=>!!r.isDark(t,n)))}function hb(e,t,n){let r=e=>e.replace(/([\\;,:"])/g,`\\$1`);return`WIFI:T:${t===`none`?`nopass`:t===`sae`?`WPA3`:`WPA`};S:${r(e)};P:${r(n)};;`}function gb(e){return`${(e.replace(/[\\/:*?"<>|\x00-\x1f\x7f]/g,`_`).replace(/[. ]+$/g,``).trim()||`Wi-Fi`).slice(0,64)}-QR.png`}function _b(e,t,n=8){let r=(e.length+8)*n;t.fillStyle=`#ffffff`,t.fillRect(0,0,r,r),t.fillStyle=`#000000`,e.forEach((e,r)=>e.forEach((e,i)=>{e&&t.fillRect((i+4)*n,(r+4)*n,n,n)}))}function vb({load:e,onClose:t}){let[n,r]=L(null),[i,a]=L(null),[o,s]=L(null);R(()=>{let t=!0;return e().then(e=>{if(t)try{r({ssid:e.ssid,matrix:mb(hb(e.ssid,e.security,e.password))})}catch{a(`QR 코드를 생성할 수 없습니다.`)}}).catch(()=>{t&&a(`Wi-Fi 정보를 불러오지 못했습니다.`)}),()=>{t=!1,r(null)}},[e]);let c=n?.matrix.length??0;return W(`div`,{class:`fixed inset-0 z-50 flex items-center justify-center bg-slate-950/70 p-4`,role:`presentation`,onClick:e=>{e.target===e.currentTarget&&t()},children:W(`section`,{role:`dialog`,"aria-modal":`true`,"aria-label":`Wi-Fi QR 코드`,class:`w-full max-w-sm rounded-2xl bg-white p-5 text-center text-slate-950 shadow-2xl sm:p-7`,children:[W(`h2`,{class:`m-0 text-xl font-extrabold`,children:`Wi-Fi QR 코드`}),W(`p`,{class:`mt-2 break-all text-sm font-semibold text-slate-600`,children:n?.ssid??`Wi-Fi 정보를 확인하는 중`}),i?W(`p`,{role:`alert`,class:`my-6 text-sm font-bold text-red-700`,children:i}):null,!n&&!i?W(`p`,{class:`my-6 text-sm`,children:`QR 코드를 만드는 중...`}):null,n?W(`div`,{class:`mx-auto my-5 w-full max-w-[288px] rounded-xl border border-slate-200 bg-white p-3`,children:W(`svg`,{class:`block h-auto w-full`,viewBox:`0 0 ${c+8} ${c+8}`,role:`img`,"aria-label":`${n.ssid} Wi-Fi 연결 QR 코드`,"shape-rendering":`crispEdges`,children:[W(`rect`,{width:c+8,height:c+8,fill:`white`}),n.matrix.map((e,t)=>e.map((e,n)=>e?W(`rect`,{x:n+4,y:t+4,width:`1`,height:`1`,fill:`black`},`${t}-${n}`):null))]})}):null,W(`p`,{class:`text-xs leading-5 text-slate-500`,children:[W(`span`,{class:`block`,children:`카메라로 스캔해 Wi-Fi에 연결하세요.`}),W(`span`,{class:`block`,children:`QR 코드에는 비밀번호가 포함됩니다.`})]}),o?W(`p`,{role:`alert`,class:`mt-3 text-sm font-bold text-red-700`,children:o}):null,n?W(`button`,{class:`mt-5 inline-flex min-h-11 w-full items-center justify-center gap-2 rounded-xl bg-teal-700 px-4 font-extrabold text-white transition hover:bg-teal-800 focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-200`,onClick:()=>{if(n){s(null);try{let e=(n.matrix.length+8)*8,t=document.createElement(`canvas`);t.width=e,t.height=e;let r=t.getContext(`2d`);if(!r)throw Error(`Canvas is not supported`);_b(n.matrix,r,8);let i=document.createElement(`a`);i.href=t.toDataURL(`image/png`),i.download=gb(n.ssid),i.click()}catch{s(`QR 코드 이미지를 저장하지 못했습니다. 다시 시도해 주세요.`)}}},type:`button`,children:[W(hn,{class:`size-5`}),`QR 코드 다운로드`]}):null,W(`button`,{class:`mt-2 min-h-11 w-full rounded-xl border border-slate-300 bg-white px-4 font-extrabold text-slate-700 transition hover:bg-slate-50`,onClick:t,type:`button`,children:`닫기`})]})})}function yb({guest:e,busy:t,saving:n,onUpdate:r,onLoadQr:i}){let[a,o]=L(!1),[s,c]=L(!1),[l,u]=L(e.ssid),[d,f]=L(``),[p,m]=L(e.enabled),[h,g]=L(null);if(R(()=>{u(e.ssid),m(e.enabled),f(``),g(null)},[e]),!e.supported)return W(`section`,{class:`mt-5 rounded-2xl border border-slate-200 bg-white p-5 sm:p-6`,children:[W(`h2`,{class:`m-0 text-lg font-extrabold text-slate-900`,children:`게스트 Wi-Fi`}),W(`p`,{class:`mt-2 mb-0 text-sm text-slate-600`,children:`2.4GHz 기본 Wi-Fi 장치를 찾지 못해 게스트 Wi-Fi를 사용할 수 없습니다.`})]});let _=l.trim()!==e.ssid||p!==e.enabled||d.length>0,v=t||_||!e.enabled||!e.passwordConfigured,y=async t=>{t.preventDefault();let n=l.trim(),i=new TextEncoder().encode(n).length;i<1||i>32?g(`Wi-Fi 이름은 UTF-8 기준 1~32바이트로 입력해 주세요.`):d&&!(d.length>=8&&d.length<=63||/^[0-9a-fA-F]{64}$/.test(d))?g(`비밀번호는 8~63자 또는 64자리 16진수여야 합니다.`):p&&!e.passwordConfigured&&!d?g(`게스트 전용 비밀번호를 입력해 주세요.`):window.confirm(`게스트 Wi-Fi 설정을 적용하면 무선 연결이 잠시 끊어질 수 있습니다. 계속하시겠습니까?`)&&(g(null),await r({ssid:n,enabled:p,password:d})&&f(``))};return W(b,{children:[W(`form`,{"aria-label":`게스트 Wi-Fi 설정`,class:`mt-5 rounded-2xl border border-slate-200 bg-white p-4 shadow-sm shadow-slate-900/5 sm:p-6`,onSubmit:e=>void y(e),children:[W(`div`,{class:`flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between`,children:[W(`button`,{"aria-controls":`ssh-guest-settings`,"aria-expanded":a,"aria-label":`게스트 Wi-Fi 설정 ${a?`접기`:`펼치기`}`,class:`flex min-w-0 flex-1 items-start gap-3 rounded-xl text-left focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-100`,onClick:()=>o(e=>!e),type:`button`,children:[W(`span`,{class:`grid size-11 shrink-0 place-items-center rounded-xl bg-teal-50 text-teal-700`,children:W(Jt,{class:`size-6`})}),W(`span`,{class:`min-w-0 flex-1`,children:[W(`span`,{class:`block text-xs font-extrabold uppercase tracking-[0.16em] text-slate-500`,children:`2.4 GHz · 분리 네트워크`}),W(`span`,{class:`mt-2 block text-xl font-extrabold tracking-tight text-slate-950`,children:`게스트 Wi-Fi`}),W(`span`,{class:`mt-2 block break-all text-sm text-slate-600`,children:e.ssid})]}),W(An,{class:`mt-3 size-5 shrink-0 text-slate-500 transition-transform ${a?`rotate-180`:``}`})]}),W(`div`,{class:`flex flex-wrap items-center gap-2 sm:justify-end`,children:[_&&W(`span`,{"aria-live":`polite`,class:`inline-flex items-center gap-1 rounded-full border border-amber-200 bg-amber-50 px-2.5 py-1 text-xs font-extrabold text-amber-800`,children:[W(Cn,{class:`size-3.5`}),`저장되지 않음`]}),W(`button`,{"aria-label":`게스트 Wi-Fi QR 코드 보기`,class:`inline-flex min-h-10 items-center gap-2 rounded-xl border border-slate-300 bg-white px-3.5 py-2 text-sm font-extrabold text-slate-800 transition hover:border-teal-300 hover:text-teal-700 focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-100 disabled:cursor-not-allowed disabled:opacity-50`,disabled:v,onClick:()=>c(!0),title:e.enabled?void 0:`게스트 Wi-Fi를 켜고 저장하면 QR 코드를 사용할 수 있습니다.`,type:`button`,children:[W(sn,{class:`size-4 shrink-0`}),`QR 코드 보기`]}),W(`span`,{class:`rounded-full px-3 py-1.5 text-xs font-extrabold ${e.enabled?`bg-emerald-50 text-emerald-700`:`bg-slate-100 text-slate-600`}`,children:e.enabled?`사용 중`:`꺼짐`})]})]}),W(`div`,{class:a?``:`hidden`,id:`ssh-guest-settings`,children:[W(`p`,{class:`mt-5 mb-0 text-sm leading-6 text-slate-600`,children:`방문자는 인터넷만 이용하며 내부 네트워크와 공유기 관리 화면에는 접근할 수 없습니다.`}),W(`div`,{class:`mt-5 grid gap-5 lg:grid-cols-2`,children:[W(`label`,{class:`block`,children:[W(`span`,{class:`text-sm font-extrabold text-slate-800`,children:`게스트 Wi-Fi 이름 (SSID)`}),W(`input`,{autocomplete:`off`,class:`mt-2 min-h-11 w-full rounded-xl border-2 border-slate-300 bg-slate-50 px-4 py-2.5 text-sm font-semibold text-slate-950 outline-none focus:border-teal-500 focus:bg-white focus:ring-4 focus:ring-teal-100 disabled:opacity-60`,disabled:t,maxLength:32,onInput:e=>u(e.currentTarget.value),value:l})]}),W(`label`,{class:`block`,children:[W(`span`,{class:`text-sm font-extrabold text-slate-800`,children:`게스트 Wi-Fi 비밀번호`}),W(`input`,{autocomplete:`new-password`,class:`mt-2 min-h-11 w-full rounded-xl border-2 border-slate-300 bg-slate-50 px-4 py-2.5 text-sm font-semibold text-slate-950 outline-none focus:border-teal-500 focus:bg-white focus:ring-4 focus:ring-teal-100 disabled:opacity-60`,disabled:t,onInput:e=>f(e.currentTarget.value),placeholder:e.passwordConfigured?`비워 두면 기존 비밀번호 유지`:`8자 이상의 별도 비밀번호 입력`,type:`password`,value:d}),W(`span`,{class:`mt-2 block text-xs text-slate-500`,children:`WPA2/WPA3 혼합 · 기본 Wi-Fi와 다른 비밀번호를 권장합니다.`})]})]}),W(`div`,{class:`mt-5 rounded-xl bg-slate-50 px-4 py-3 text-sm leading-6 text-slate-700`,children:[`내부망 접근 차단 · 게스트 기기 간 통신 차단 · SafeShield DNS 사용`,e.subnet&&W(`span`,{class:`block text-xs text-slate-500`,children:[`게스트 게이트웨이: `,e.subnet]})]}),W(`div`,{class:`mt-5 flex flex-col gap-4 border-t border-slate-200 pt-5 sm:flex-row sm:items-center sm:justify-between`,children:[W(`label`,{class:`inline-flex min-h-11 items-center gap-3 text-sm font-extrabold text-slate-800`,children:[W(`input`,{checked:p,class:`size-5 accent-teal-700`,disabled:t,onChange:e=>m(e.currentTarget.checked),type:`checkbox`}),`게스트 Wi-Fi 사용`]}),W(`button`,{class:`inline-flex min-h-11 w-full items-center justify-center gap-2 rounded-xl bg-teal-700 px-5 py-2.5 text-sm font-extrabold text-white transition hover:bg-teal-800 disabled:opacity-60 sm:w-auto ${Y_(t)}`,disabled:t||!_,type:`submit`,children:[W(Sn,{class:`size-5 ${n?`animate-pulse`:``}`}),n?`적용 중`:`설정 저장`]})]}),h&&W(`p`,{class:`mt-4 mb-0 rounded-xl bg-red-50 px-4 py-3 text-sm font-bold text-red-800`,children:h})]})]}),s&&W(vb,{load:i,onClose:()=>c(!1)})]})}var bb=[{value:`sae-mixed`,label:`WPA2/WPA3 혼합 (권장)`},{value:`sae`,label:`WPA3-Personal`},{value:`psk2`,label:`WPA2-Personal`},{value:`none`,label:`암호화 없음`}];function xb(e){switch(e.security){case`sae-mixed`:return`WPA2/WPA3 혼합`;case`sae`:return`WPA3-Personal`;case`psk2`:return`WPA2-Personal`;case`none`:return`암호화 없음`;default:return`고급 설정 (${e.securityRaw})`}}function Sb(e){return e.length>=8&&e.length<=63||/^[0-9a-fA-F]{64}$/.test(e)}function Cb({network:e,busy:t,saving:n,onUpdate:r,onLoadQr:i}){let[a,o]=L(e.ssid),[s,c]=L(e.enabled),[l,u]=L(e.security===`custom`?`keep`:e.security),[d,f]=L(``),[p,m]=L(!1),h=B(()=>i(e.section),[i,e.section]),[g,_]=L(null);R(()=>{o(e.ssid),c(e.enabled),u(e.security===`custom`?`keep`:e.security),f(``),_(null)},[e]);let v=e.security===`custom`&&l===`keep`,y=l===`psk2`||l===`sae-mixed`||l===`sae`,b=e.security===`custom`?`keep`:e.security,x=a.trim()!==e.ssid||s!==e.enabled||l!==b||d.length>0,S=t||x||e.security===`custom`||!e.enabled||e.security!==`none`&&!e.passwordConfigured,C=[...e.security===`custom`?[{value:`keep`,label:`현재 고급 설정 유지 (${e.securityRaw})`}]:[],...bb],w=async t=>{t.preventDefault();let n=a.trim(),i=new TextEncoder().encode(n).length;i<1||i>32?_(`SSID는 UTF-8 기준 1~32바이트로 입력해 주세요.`):d&&!Sb(d)?_(`비밀번호는 8~63자 또는 64자리 16진수여야 합니다.`):y&&!e.passwordConfigured&&d.length===0?_(`보안 Wi-Fi를 사용하려면 비밀번호를 입력해 주세요.`):window.confirm(`Wi-Fi 설정을 적용하면 현재 무선 연결이 잠시 끊어질 수 있습니다. 계속하시겠습니까?`)&&(_(null),await r({section:e.section,ssid:n,security:l,password:d,enabled:s})&&f(``))};return W(`form`,{class:`rounded-2xl border border-slate-200 bg-white p-4 shadow-sm shadow-slate-900/5 sm:p-6`,onSubmit:e=>void w(e),children:[W(`div`,{class:`flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between`,children:[W(`div`,{class:`flex min-w-0 items-start gap-3`,children:[W(`span`,{class:`grid size-11 shrink-0 place-items-center rounded-xl bg-teal-50 text-teal-700`,children:W(Jt,{class:`size-6`})}),W(`div`,{class:`min-w-0`,children:[W(`p`,{class:`m-0 text-xs font-extrabold uppercase tracking-[0.16em] text-slate-500`,children:e.bandLabel}),W(`h2`,{class:`mt-2 mb-0 break-words text-xl font-extrabold tracking-tight text-slate-950`,children:e.ssid||`이름 없는 Wi-Fi`}),W(`p`,{class:`mt-2 mb-0 text-sm text-slate-600`,children:[`채널 `,e.channel??`자동`,` · 연결 기기 `,e.clientCount,`대`]})]})]}),W(`div`,{class:`flex flex-wrap items-center gap-2 sm:justify-end`,children:[x?W(`span`,{"aria-live":`polite`,class:`inline-flex shrink-0 items-center gap-1.5 rounded-full border border-amber-200 bg-amber-50 px-2.5 py-1 text-xs font-extrabold text-amber-800`,role:`status`,children:[W(Cn,{"aria-hidden":`true`,class:`size-3.5 shrink-0`}),`저장되지 않음`]}):null,W(`button`,{"aria-label":`${e.bandLabel} Wi-Fi QR 코드 보기`,class:`inline-flex min-h-10 items-center gap-2 rounded-xl border border-slate-300 bg-white px-3.5 py-2 text-sm font-extrabold text-slate-800 shadow-sm transition hover:border-teal-300 hover:text-teal-700 focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-100 disabled:cursor-not-allowed disabled:opacity-50`,disabled:S,onClick:()=>m(!0),type:`button`,children:[W(sn,{class:`size-4 shrink-0`}),`QR 코드 보기`]}),W(`span`,{class:`inline-flex w-fit items-center gap-2 rounded-full px-3 py-1.5 text-xs font-extrabold ring-1 ring-inset ${e.runtimeUp?`bg-emerald-50 text-emerald-700 ring-emerald-200`:`bg-slate-100 text-slate-600 ring-slate-200`}`,children:[W(`span`,{class:`size-2 rounded-full ${e.runtimeUp?`bg-emerald-500`:`bg-slate-400`}`}),e.runtimeUp?`동작 중`:e.enabled?`시작 대기`:`꺼짐`]})]})]}),W(`div`,{class:`mt-6 grid gap-5 lg:grid-cols-2`,children:[W(`label`,{class:`block`,children:[W(`span`,{class:`text-sm font-extrabold text-slate-800`,children:`Wi-Fi 이름 (SSID)`}),W(`input`,{autocomplete:`off`,class:`mt-2 min-h-11 w-full rounded-xl border-2 border-slate-300 bg-slate-50 px-4 py-2.5 text-sm font-semibold text-slate-950 shadow-inner outline-none transition placeholder:text-slate-400 focus:border-teal-500 focus:bg-white focus:ring-4 focus:ring-teal-100 disabled:cursor-not-allowed disabled:bg-slate-100`,disabled:t,maxLength:32,onInput:e=>o(e.currentTarget.value),value:a})]}),W(`div`,{class:`block`,children:[W(`span`,{class:`text-sm font-extrabold text-slate-800`,children:`보안 방식`}),W(W_,{ariaLabel:`${e.bandLabel} Wi-Fi 보안 방식`,className:`mt-2`,disabled:t,id:`wifi-security-${e.section}`,onChange:e=>{let t=e;u(t),(t===`none`||t===`keep`)&&f(``)},options:C,value:l,variant:`emphasized`}),W(`span`,{class:`mt-2 block text-xs text-slate-500`,children:[`현재: `,xb(e)]})]}),W(`label`,{class:`block lg:col-span-2`,children:[W(`span`,{class:`text-sm font-extrabold text-slate-800`,children:`새 비밀번호`}),W(`input`,{autocomplete:`new-password`,class:`mt-2 min-h-11 w-full rounded-xl border-2 border-slate-300 bg-slate-50 px-4 py-2.5 text-sm font-semibold text-slate-950 shadow-inner outline-none transition placeholder:text-slate-400 focus:border-teal-500 focus:bg-white focus:ring-4 focus:ring-teal-100 disabled:cursor-not-allowed disabled:bg-slate-100`,disabled:t||l===`none`||v,onInput:e=>f(e.currentTarget.value),placeholder:v?`고급 보안 설정은 기존 LuCI에서 변경합니다`:e.passwordConfigured?`비워 두면 기존 비밀번호를 유지합니다`:`8자 이상의 비밀번호를 입력하세요`,type:`password`,value:d}),W(`span`,{class:`mt-2 block text-xs text-slate-500`,children:`저장된 비밀번호는 일반 설정 조회에 노출되지 않습니다. QR 코드에는 비밀번호가 포함됩니다.`})]})]}),W(`div`,{class:`mt-6 flex flex-col gap-4 border-t border-slate-200 pt-5 sm:flex-row sm:items-center sm:justify-between`,children:[W(`label`,{class:`inline-flex min-h-11 items-center gap-3 text-sm font-extrabold text-slate-800`,children:[W(`input`,{checked:s,class:`size-5 accent-teal-700`,disabled:t,onChange:e=>c(e.currentTarget.checked),type:`checkbox`}),`이 Wi-Fi 사용`]}),W(`button`,{class:`inline-flex min-h-11 w-full items-center justify-center gap-2 rounded-xl border-0 bg-teal-700 px-5 py-2.5 text-sm font-extrabold text-white shadow-sm transition hover:bg-teal-800 focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-200 ${Y_(t)} disabled:opacity-60 sm:w-auto`,disabled:t||!x,type:`submit`,children:[W(Sn,{class:`size-5 ${n?`animate-pulse`:``}`}),n?`적용 중`:`설정 저장`]})]}),p?W(vb,{load:h,onClose:()=>m(!1)}):null,g?W(`p`,{class:`mt-4 mb-0 rounded-xl bg-red-50 px-4 py-3 text-sm font-bold text-red-800`,children:g}):null]})}function wb({feedback:e,onDismiss:t}){return W(`div`,{"aria-live":`polite`,class:`mb-5 flex flex-col gap-3 rounded-2xl border px-4 py-3 text-sm sm:flex-row sm:items-center sm:justify-between ${e.kind===`success`?`border-emerald-200 bg-emerald-50 text-emerald-900`:e.kind===`warning`?`border-amber-200 bg-amber-50 text-amber-900`:`border-red-200 bg-red-50 text-red-900`}`,children:[W(`p`,{class:`m-0 font-bold leading-6`,children:e.message}),W(`button`,{class:`min-h-11 w-full shrink-0 rounded-lg border border-current bg-transparent sm:min-h-9 sm:w-auto px-3 py-1.5 text-xs font-extrabold`,onClick:t,type:`button`,children:`닫기`})]})}function Tb({data:e,error:t,feedback:n,loading:r,updatingSection:i,onDismissFeedback:a,onRetry:o,onUpdate:s,onUpdateGuest:c,onLoadQr:l,onLoadGuestQr:u}){return r?W(vs,{}):t?W(ys,{message:t,onRetry:o}):e?W(b,{children:[n?W(wb,{feedback:n,onDismiss:a}):null,W(`section`,{class:`mb-5 rounded-2xl border border-amber-200 bg-amber-50 p-5 text-amber-950 sm:p-6`,children:[W(`p`,{class:`m-0 text-xs font-extrabold uppercase tracking-[0.16em] text-amber-700`,children:`연결 주의`}),W(`h2`,{class:`mt-2 mb-0 text-lg font-extrabold`,children:`Wi-Fi 이름이나 비밀번호를 바꾸면 현재 연결이 끊어집니다`}),W(`p`,{class:`mt-2 mb-0 text-sm leading-6 text-amber-900/80`,children:`변경한 Wi-Fi 이름과 비밀번호로 다시 연결하면 SmartSafeHub를 계속 사용할 수 있습니다. VLAN 등 고급 무선 옵션은 기존 LuCI에서 관리합니다.`})]}),W(J,{eyebrow:`Wireless`,title:`기본 Wi-Fi`,description:`무선 네트워크의 이름, 비밀번호, 보안 방식과 QR 코드를 관리합니다.`}),e.networks.length?W(`section`,{"aria-label":`기본 Wi-Fi 설정`,class:`grid gap-5 xl:grid-cols-2`,children:e.networks.map(e=>W(Cb,{busy:i!==null,network:e,onUpdate:s,onLoadQr:l,saving:i===e.section},e.section))}):W(`section`,{class:`rounded-2xl border border-slate-200 bg-white p-5 text-center shadow-sm shadow-slate-900/5 sm:p-8`,children:[W(Jt,{class:`mx-auto size-10 text-slate-400`}),W(`h2`,{class:`mt-4 mb-0 text-lg font-extrabold text-slate-950`,children:`관리할 기본 Wi-Fi를 찾지 못했습니다`}),W(`p`,{class:`mt-2 mb-0 text-sm leading-6 text-slate-600`,children:`기존 LuCI에서 AP 모드의 무선 네트워크를 먼저 구성해 주세요.`})]}),e.guest&&W(b,{children:[W(`div`,{role:`separator`,"aria-label":`기본 Wi-Fi와 게스트 Wi-Fi 구분`,class:`mt-5 w-full border-t border-slate-200`}),W(`div`,{class:`mt-5`,children:W(J,{eyebrow:`Guest network`,title:`방문자용 네트워크`,description:`기본 네트워크와 분리된 게스트 Wi-Fi를 관리합니다.`})}),W(yb,{guest:e.guest,busy:i!==null,saving:i===`ssh_guest`,onUpdate:c,onLoadQr:u})]}),e.guestError&&W(`section`,{class:`mt-5 rounded-2xl border border-amber-200 bg-amber-50 p-5 text-sm text-amber-900`,role:`status`,children:[`게스트 Wi-Fi 정보를 불러오지 못했습니다. 패키지를 설치한 직후라면 로그아웃 후 다시 로그인해 주세요.`,W(`button`,{class:`ml-3 underline font-bold`,onClick:o,type:`button`,children:`다시 시도`})]})]}):null}function Eb(){return U(`smartsafehub`,`netwatch_status`,{},{priority:`low`,timeoutMs:5e3})}function Db(e){return U(`smartsafehub`,`netwatch_enabled_update`,{enabled:e},{priority:`high`,timeoutMs:6e3})}var Ob=120,kb=40;function Ab(e,t){return t.limited?{label:`판단 보류`,tone:`bg-slate-100 text-slate-600`}:t.alert?.ip===e.ip?{label:`주의 기록`,tone:`bg-amber-100 text-amber-800`}:e.connections>=Ob||e.destinations>=kb?{label:`연결 많음`,tone:`bg-amber-50 text-amber-800`}:{label:`관찰 중`,tone:`bg-slate-100 text-slate-600`}}function jb(e){return(e?.events??[]).filter(e=>e.eventType===`network_watch.anomaly.warning`&&e.source===`network_watch`&&Number.isFinite(e.occurredAt)&&e.occurredAt>0).sort((e,t)=>t.occurredAt-e.occurredAt).slice(0,3)}function Mb(e){let t=e.metadata?.source_ip;return typeof t==`string`&&/^\d{1,3}(?:\.\d{1,3}){3}$/.test(t)?t:null}function Nb({data:e,error:t,loading:n,onRetry:r,onSetEnabled:i,devices:a,activity:o,demo:s=!1,onDemoAction:c}){let[l,u]=L(!1),[d,f]=L(null),[p,m]=L(null),h=!!e?.enabled,g=h&&e?.phase===`ready`,_=l?`저장 중…`:e?h?e.phase===`ready`?`사용 중 · 관찰 중`:e.phase===`unavailable`?`사용 중 · 수집 불가`:`사용 중 · 준비 중`:`사용 안 함`:n?`확인 중`:`상태 확인 불가`,v=e&&h&&e.phase===`ready`?`text-emerald-700`:e&&h&&e.phase===`unavailable`?`text-amber-700`:`text-slate-500`;R(()=>{if(!p)return;let e=window.setTimeout(()=>m(null),3500);return()=>window.clearTimeout(e)},[p]);let y=async()=>{if(s)c?.();else if(!l&&e){u(!0),f(null),m(null);try{await i(!h),m(h?`Network Watch를 껐습니다.`:`Network Watch를 켰습니다. 다음 관찰 주기부터 반영됩니다.`)}catch(e){f(e instanceof Error?e.message:`설정을 저장하지 못했습니다.`)}finally{u(!1)}}},x=new Map;for(let e of a?.devices??[])e.online&&e.ipv4Address&&e.hostname?.trim()&&x.set(e.ipv4Address,e.hostname.trim());let S=jb(o);return W(`div`,{class:`space-y-5`,children:[W(J,{eyebrow:`Security`,title:`Network Watch`,description:`연결된 기기의 외부 통신 변화를 ${e?.intervalSeconds??60}초 간격으로 관찰합니다. 의심 징후는 참고 정보이며 악성 앱을 확정하거나 연결을 자동 차단하지 않습니다.`,action:W(`div`,{class:`flex items-center gap-3 rounded-xl border border-slate-100 bg-slate-50 px-3 py-2.5`,"aria-busy":l,children:[W(`span`,{class:`grid size-10 shrink-0 place-items-center rounded-xl bg-slate-100 text-slate-500`,children:W($t,{class:`size-5`})}),W(`div`,{class:`text-right`,children:[W(`p`,{class:`m-0 text-[0.68rem] font-bold text-slate-400`,children:`관찰 수집`}),W(`p`,{class:`mt-1 mb-0 whitespace-nowrap text-xs font-extrabold ${v}`,role:`status`,children:_})]}),W(`button`,{type:`button`,role:`switch`,"aria-checked":h,"aria-label":`네트워크 이상 감지 사용`,disabled:l||!e,onClick:()=>void y(),class:`ssh-switch-control relative inline-flex shrink-0 items-center rounded-full border-0 p-0 transition focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-100 disabled:cursor-not-allowed disabled:opacity-50 ${h?`bg-teal-600`:`bg-slate-300`}`,children:W(`span`,{"aria-hidden":`true`,class:`ssh-switch-thumb absolute top-1 shadow-sm transition-[left] ${h?`left-6`:`left-1`}`})})]})}),p?W(`p`,{role:`status`,class:`text-sm text-teal-700`,children:p}):null,d?W(`p`,{role:`alert`,class:`text-sm text-rose-700`,children:d}):null,n&&!e?W(vs,{}):null,t&&!e?W(ys,{message:t,onRetry:r}):null,e&&!h?W(`p`,{role:`status`,class:`rounded-xl border border-slate-200 bg-slate-50 p-4 text-sm text-slate-600`,children:`네트워크 관찰이 꺼져 있습니다. 기존 방화벽과 SafeShield 보호에는 영향이 없습니다.`}):null,h&&e?.phase===`preparing`?W(`p`,{role:`status`,class:`rounded-xl border border-slate-200 bg-slate-50 p-4 text-sm text-slate-600`,children:`첫 연결 관찰을 준비하고 있습니다. 잠시 후 자동으로 갱신됩니다.`}):null,h&&e?.phase===`unavailable`?W(`div`,{role:`alert`,class:`rounded-xl border border-amber-200 bg-amber-50 p-4 text-sm text-amber-900`,children:`연결 정보를 읽을 수 없습니다. OpenWrt의 conntrack 지원 여부를 확인해 주세요. 실제 감시가 수행 중인 것으로 표시하지 않습니다.`}):null,g&&e?W(b,{children:[W(`div`,{class:`grid gap-3 sm:grid-cols-3`,children:[W(`div`,{class:`rounded-2xl border border-slate-200 bg-white p-5 shadow-sm`,children:[W(`p`,{class:`text-sm font-semibold text-slate-500`,children:`현재 연결 수`}),W(`p`,{class:`mt-2 text-xl font-extrabold text-slate-900`,children:Y(e.totalConnections)})]}),W(`div`,{class:`rounded-2xl border border-slate-200 bg-white p-5 shadow-sm`,children:[W(`p`,{class:`text-sm font-semibold text-slate-500`,children:`관찰된 기기 수`}),W(`p`,{class:`mt-2 text-xl font-extrabold text-slate-900`,children:Y(e.totalDevices)})]}),W(`div`,{class:`rounded-2xl border border-slate-200 bg-white p-5 shadow-sm`,children:[W(`p`,{class:`text-sm font-semibold text-slate-500`,children:`마지막 갱신`}),W(`p`,{class:`mt-2 text-xl font-extrabold text-slate-900`,children:e.sampledAt>0?jo(e.sampledAt):`—`}),e.sampledAt>0?W(`p`,{class:`mt-1 text-xs text-slate-500`,title:`브라우저 시간대 기준`,children:Ao(e.sampledAt)}):null]})]}),e.limited?W(`p`,{role:`status`,class:`rounded-xl border border-amber-200 bg-amber-50 p-4 text-sm text-amber-800`,children:`연결 수가 관찰 한도를 초과하여 일부 연결만 집계했습니다. 이번 결과로 이상 여부를 판단하지 않습니다.`}):null,e.alert?W(`p`,{role:`status`,class:`rounded-xl border border-amber-200 bg-amber-50 p-4 text-sm text-amber-900`,children:[e.alert.ip,` 기기에서 연결 또는 목적지 수가 연속해 높게 관찰되었습니다. 정상적인 앱 사용일 수도 있습니다.`]}):null,W(`section`,{class:`rounded-2xl border border-slate-200 bg-white p-5 shadow-sm`,children:[W(`h2`,{class:`text-base font-extrabold text-slate-900`,children:`기기별 외부 연결 현황`}),W(`p`,{class:`mt-1 text-xs text-slate-500`,children:`IPv4 기준 현재 연결 스냅샷 · 최대 12대 표시 · 콘텐츠 분석 없음`}),e.devices.length?W(`div`,{class:`mt-4 overflow-x-auto`,children:W(`table`,{class:`w-full text-left text-sm`,children:[W(`thead`,{class:`text-slate-500`,children:W(`tr`,{children:[W(`th`,{class:`pb-3`,children:`기기`}),W(`th`,{class:`pb-3 text-right`,children:`연결 수`}),W(`th`,{class:`pb-3 text-right`,children:`외부 목적지 수`}),W(`th`,{class:`pb-3 text-right`,children:`관찰 상태`})]})}),W(`tbody`,{children:e.devices.map(t=>{let n=Ab(t,e),r=x.get(t.ip);return W(`tr`,{class:`border-t border-slate-100 text-slate-700`,children:[W(`td`,{class:`py-3`,children:[W(`div`,{class:`font-semibold`,children:r||t.ip}),r?W(`div`,{class:`mt-0.5 text-xs text-slate-500`,children:t.ip}):null]}),W(`td`,{class:`py-3 text-right`,children:Y(t.connections)}),W(`td`,{class:`py-3 text-right`,children:Y(t.destinations)}),W(`td`,{class:`py-3 text-right`,children:W(`span`,{class:`inline-block whitespace-nowrap rounded-full px-2.5 py-1 text-xs font-bold ${n.tone}`,children:n.label})})]},t.ip)})})]})}):W(`p`,{class:`mt-4 text-sm text-slate-500`,children:`현재 확인된 외부 연결이 없습니다.`}),W(`p`,{class:`mt-3 text-xs text-slate-500`,children:`‘연결 많음’은 관찰량 기준이며 악성 통신 판정이 아닙니다. 기기 이름을 확인할 수 없으면 IP만 표시합니다.`})]}),W(`section`,{class:`rounded-2xl border border-slate-200 bg-white p-5 shadow-sm`,children:[W(`div`,{class:`flex flex-wrap items-center justify-between gap-3`,children:[W(`h2`,{class:`text-base font-extrabold text-slate-900`,children:`최근 감지 이벤트`}),W(`a`,{href:`#activity`,class:`text-xs font-bold text-teal-700 hover:underline`,children:`최근 활동 전체 보기`})]}),W(`p`,{class:`mt-1 text-xs text-slate-500`,children:`현재 부팅 후 기록된 Network Watch 주의 이벤트 중 최신 3건`}),S.length?W(`ul`,{class:`mt-4 divide-y divide-slate-100`,children:S.map(e=>W(`li`,{class:`flex flex-wrap items-start justify-between gap-2 py-3 first:pt-0 last:pb-0`,children:[W(`div`,{class:`min-w-0`,children:[W(`p`,{class:`text-sm font-bold text-slate-800`,children:`외부 통신 증가 감지`}),W(`p`,{class:`mt-1 text-xs text-slate-600`,children:[Mb(e)??`연결된 기기`,` · 비정상 통신 확정 아님`]})]}),W(`time`,{class:`text-xs text-slate-500`,dateTime:new Date(e.occurredAt*1e3).toISOString(),title:Ao(e.occurredAt),children:jo(e.occurredAt)})]},e.eventId))}):W(`p`,{class:`mt-4 text-sm text-slate-500`,children:`최근 감지된 주의 이벤트가 없습니다.`})]})]}):null,W(`section`,{"aria-label":`Network Watch 관찰 범위`,class:`rounded-xl border border-slate-200 bg-slate-50 p-4`,children:[W(`h2`,{class:`text-sm font-bold text-slate-700`,children:`관찰 범위 안내`}),W(`ul`,{class:`mt-2 list-disc space-y-1 pl-5 text-xs leading-relaxed text-slate-600`,children:[W(`li`,{children:`개인정보 보호를 위해 통신 내용은 읽지 않고 연결 요약만 임시 메모리에 저장합니다.`}),W(`li`,{children:`IPv6, 짧게 종료된 연결 및 일부 하드웨어 가속 연결은 관찰에서 제외될 수 있습니다.`}),W(`li`,{children:`주의 이벤트는 최근 활동에 기록되며, Cloud 활동 전송을 직접 켠 경우 내부 IP·연결 수 등의 요약 정보가 전송될 수 있습니다.`})]})]})]})}function Pb({onAdministratorPasswordChanged:e}){let t=zi(),n=Ui(),r=xi(),i=di(t===`activity`),a=li({active:t===`apps`,fallbackError:`검증된 앱 목록을 불러오지 못했습니다.`,loader:or,pollInterval:e=>e?.refreshing?2e3:e?.ready?6e4:e?.refreshFailed?15e3:2e3,staleTimeMs:2e3}),o=li({active:t===`apps`,fallbackError:`앱 설치 상태를 불러오지 못했습니다.`,loader:cr,pollInterval:e=>!e?.ready||e?.refreshing?2e3:e?.job.phase===`running`||e?.job.phase===`starting`||e?.restore?.phase===`running`||e?.restore?.phase===`starting`?3e3:12e3,staleTimeMs:3e3}),s=li({active:t===`apps`,fallbackError:`저장 공간 정보를 확인하지 못했습니다.`,loader:ur,pollInterval:e=>e?.refreshing||!e?.ready?3e3:6e4,staleTimeMs:5e3}),c=ro(t===`home`||t===`settings`),l=to(!0),u=Ii(!0),d=ea(t===`network`),f=Qi(t===`home`||t===`network`),p=Xi(t===`iptv`),m=Co(t===`wifi`),h=li({active:t===`netwatch`,fallbackError:`Network Watch 상태를 읽을 수 없습니다.`,loader:Eb,pollInterval:6e4,staleTimeMs:5e3}),g=Ci(t===`home`,!1),_=Ci(t===`devices`||t===`netwatch`,t===`devices`),v=li({active:t===`netwatch`,fallbackError:`Network Watch 최근 이벤트를 확인할 수 없습니다.`,loader:Jn,pollInterval:6e4,staleTimeMs:5e3}),y=Xa(t===`home`||t===`safeshield`),b=Va(t===`home`||t===`safeshield`,t===`safeshield`),x=y,S=b,C=za(t===`rules`),w=vo(t===`account`),T=bo(t===`account`,w.data?.accountRegistered??null),E=po(c.data),D=Yi(t===`home`||t===`settings`),O=io(t===`settings`),k=mo(t===`settings`);R(()=>{let e=Cy();e&&Er(e).then(e=>{e.applied&&t===`settings`&&k.refresh()}).catch(()=>{})},[]);let A=o.data?.job.operationId,j=o.data?.job.phase,M=o.data?.restore?.phase,N=z(``);R(()=>{if(t!==`apps`||j!==`success`&&M!==`success`)return;let e=`${A??``}:${j}:${M??``}`;e!==N.current&&(N.current=e,Promise.allSettled([lr(),dr()]).then(()=>{o.refresh(),s.refresh()}))},[t,A,j,M]);let P=Aa(y.refresh,b.refresh),F=t===`activity`?i:t===`netwatch`?h:t===`network`?d:t===`wifi`?m:t===`iptv`?p:t===`devices`?_:t===`safeshield`?y:t===`rules`?C:t===`account`?w:t===`apps`?a:t===`system`?l:c,I;switch(t){case`netwatch`:I=W(Nb,{data:h.data,devices:_.data,activity:v.data,error:h.error,loading:h.loading,onRetry:()=>void h.refresh(),onSetEnabled:async e=>{await Db(e),await h.refresh()}});break;case`apps`:I=W(_s,{data:a.data?.ready===!1?null:a.data,error:a.error??(a.data?.refreshFailed&&!a.data?.ready?`앱 목록 갱신에 실패했습니다. 서버 연결을 확인해 주세요.`:null),loading:a.loading||!!a.data&&!a.data.ready&&!a.data.refreshFailed,refreshing:a.refreshing||!!a.data?.refreshing,onRetry:()=>void sr().then(()=>a.refresh()).then(()=>Promise.all([o.refresh(),s.refresh()])).catch(()=>a.refresh()),installStatus:o.data?.ready===!1?null:o.data,metrics:s.data,statusError:o.error??(o.data?.ready===!1?`설치 상태를 확인하고 있습니다.`:null),onRefreshStatus:()=>void o.refresh(),onInstall:async e=>{await fr(e),await o.refresh()},onUpdate:async e=>{await pr(e),await o.refresh()},onRemove:async e=>{await mr(e),await o.refresh()}});break;case`network`:I=W(`div`,{class:`min-w-0`,children:[W(ci,{active:n.network===`internet`?`internet`:`lan`,label:`네트워크 설정 메뉴`,tabs:[{id:`internet`,label:`인터넷 연결`,href:`#network`},{id:`lan`,label:`LAN 및 DHCP`,href:`#network/lan`}]}),W(`div`,{"aria-label":`인터넷 연결 설정`,"aria-labelledby":`internet-tab`,class:`min-w-0`,hidden:n.network!==`internet`,id:`internet-panel`,role:`tabpanel`,children:W(wv,{action:d.action,data:d.data,error:d.error,feedback:d.feedback,loading:d.loading,onDismissFeedback:d.dismissFeedback,onReconnect:()=>void d.reconnect(),onRetry:()=>void d.refresh(),onSave:d.save})}),W(`div`,{"aria-label":`내부 네트워크 설정`,"aria-labelledby":`lan-tab`,class:`min-w-0`,hidden:n.network!==`lan`,id:`lan-panel`,role:`tabpanel`,children:W(uv,{action:f.action,data:f.data,error:f.error,feedback:f.feedback,loading:f.loading,onApplyRecommendation:f.applyRecommendation,onDismissFeedback:f.dismissFeedback,onRetry:()=>void f.refresh(),onSave:f.save})})]});break;case`activity`:I=W(ts,{actionError:i.actionError,actionMessage:i.actionMessage,data:i.data,error:i.error,loading:i.loading,onDismissActionFeedback:i.dismissActionFeedback,onRetry:()=>void i.refresh(),onSetCloudSync:e=>void i.setCloudSyncEnabled(e),savingCloudSync:i.savingCloudSync});break;case`wifi`:I=W(Tb,{data:m.data,error:m.error,feedback:m.feedback,loading:m.loading,onDismissFeedback:m.dismissFeedback,onRetry:()=>void m.refresh(),onUpdate:m.update,onUpdateGuest:m.updateGuest,onLoadQr:Ur,onLoadGuestQr:Wr,updatingSection:m.updatingSection});break;case`iptv`:I=W(J_,{data:p.data,error:p.error,feedback:p.feedback,loading:p.loading,onDismissFeedback:p.dismissFeedback,onRetry:()=>void p.refresh(),onSave:p.save,saving:p.saving});break;case`devices`:I=W(js,{data:_.data,error:_.error,loading:_.loading,onRetry:()=>void _.refresh()});break;case`account`:I=W(Gy,{data:w.data,error:w.error,loading:w.loading,pairingBusy:w.pairingBusy,refreshing:w.refreshing,syncError:w.syncError,onRequestPairing:w.requestPairing,onRetry:()=>void w.refresh()});break;case`system`:I=W(ub,{action:l.action,actionError:l.actionError,data:l.data,error:l.error,firmware:u,loading:l.loading,message:l.message,onCheck:()=>void l.check(),onDismissFeedback:l.dismissFeedback,onInstall:()=>void l.install(),onRetry:()=>void l.refresh(),onSaveSettings:l.saveSettings});break;case`settings`:I=W(Ry,{activeTab:n.settings,action:E.action,backupAction:r.action,backupError:r.error,backupMessage:r.message,backupRestoreAccepted:r.restoreAccepted,backupUploadProgress:r.uploadProgress,backupValidated:r.validated,data:c.data,error:c.error,feedbackError:E.error,feedbackMessage:E.message,health:D.data,healthActionError:D.actionError,healthActionMessage:D.actionMessage,healthError:D.error,healthLoading:D.loading,healthRunning:D.running,healthSavingReporter:D.savingReporter,loading:c.loading,onBackupDiscard:r.discard,onBackupDownload:()=>void r.download(),onBackupRestore:r.restore,onBackupUpload:r.upload,onDismissBackupFeedback:r.dismissFeedback,onDismissFeedback:E.dismissFeedback,onDismissHealthFeedback:D.dismissActionFeedback,onDismissTimeFeedback:k.dismissSaveFeedback,onDownloadDiagnostics:()=>void E.downloadDiagnostics(),onRunHealth:()=>void D.runDiagnostic(),onSetHealthReporter:e=>void D.setReporterEnabled(e),onReboot:()=>void E.reboot(),onRetry:()=>void Promise.all([c.refresh(),u.refresh(),k.refresh(),O.refresh(),D.refresh()]),onDismissScheduledRebootFeedback:O.dismissSaveFeedback,onSaveScheduledReboot:O.saveSettings,onSaveTimezone:k.saveTimezone,onSyncTime:k.syncTime,onAdministratorPasswordChanged:e,rebootAccepted:E.rebootAccepted,scheduledRebootData:O.data,scheduledRebootError:O.error,scheduledRebootLoading:O.loading,scheduledRebootSaveError:O.saveError,scheduledRebootSaveMessage:O.saveMessage,scheduledRebootSaving:O.saving,timeData:k.data,timeError:k.error,timeLoading:k.loading,timeSaveError:k.saveError,timeSaveMessage:k.saveMessage,timeSaving:k.saving,timeSyncing:k.syncing});break;case`safeshield`:I=W(oy,{action:P.action,actionError:P.error,actionMessage:P.message,data:y.data,error:y.error,loading:y.loading,statistics:b.data,statisticsError:b.error,statisticsLoading:b.loading,statisticsRefreshing:b.refreshing,onDismissFeedback:P.dismissFeedback,onRefreshBlocklist:()=>void P.refreshBlocklist(),onRetry:()=>void y.refresh(),onRetryStatistics:()=>void b.refresh(),onSetEnabled:e=>void P.setEnabled(e),onSetStatisticsEnabled:e=>void P.setStatisticsEnabled(e)});break;case`rules`:I=W(_y,{action:C.action,data:C.data,error:C.error,feedback:C.feedback,loading:C.loading,onAddRule:C.addRule,onDeleteRule:C.deleteRule,onLoadRecommendedRulesStatus:Zn,onSyncRecommendedRules:Qn,onRevokeRecommendedRule:$n,onDismissFeedback:C.dismissFeedback,onRetry:()=>void C.refresh()});break;default:I=W(C_,{accountRegistered:T,activity:c.data?.activityHistory??null,activityError:c.error,activityLoading:c.loading,data:c.data,devices:g.data,devicesError:g.error,devicesLoading:g.loading,error:c.error,firmware:u.data,firmwareError:u.error,firmwareLoading:u.loading,health:D.data,healthError:D.error,healthLoading:D.loading,lan:f.data,lanError:f.error,lanLoading:f.loading,loading:c.loading,onRetry:()=>void Promise.all([c.refresh(),g.refresh(),y.refresh(),b.refresh(),l.refresh(),u.refresh(),D.refresh(),f.refresh()]),safeshield:x.data,safeshieldError:x.error,safeshieldLoading:x.loading,statistics:S.data,statisticsError:S.error,statisticsLoading:S.loading,statisticsRefreshing:S.refreshing,updates:l.data,updatesError:l.error,updatesLoading:l.loading})}let ee=()=>{t===`home`?Promise.all([c.refresh(),g.refresh(),x.refresh(),S.refresh(),l.refresh(),u.refresh(),D.refresh(),f.refresh()]):t===`network`?Promise.all([d.refresh(),f.refresh()]):t===`system`?Promise.all([l.refresh(),u.refresh()]):t===`settings`?Promise.all([c.refresh(),u.refresh(),k.refresh(),O.refresh(),D.refresh()]):t===`safeshield`?Promise.all([y.refresh(),b.refresh()]):F.refresh()},te=+((l.data?.updateCount??0)>0)+ +!!u.data?.updateAvailable;return W(si,{accountRegistered:T,loading:t===`network`?d.loading||f.loading:F.loading,onRefresh:ee,refreshing:F.refreshing||t===`network`&&f.refreshing||t===`home`&&(i.refreshing||g.refreshing||x.refreshing||S.refreshing||l.refreshing||u.refreshing||D.refreshing||f.refreshing)||t===`system`&&u.refreshing||t===`settings`&&(u.refreshing||k.refreshing||O.refreshing||D.refreshing)||t===`safeshield`&&b.refreshing,route:t,updateCount:te,children:I})}function Fb({onAdministratorPasswordChanged:e,onPasswordConfigured:t}){let[n,r]=L(`checking`),[i,a]=L(!1),o=B(async()=>{r(`checking`);try{let e=await Ut();a(e.recovery),r(e.configured?`ready`:`required`)}catch{r(`error`)}},[]);return R(()=>{o()},[o]),n===`ready`?W(Pb,{onAdministratorPasswordChanged:e}):n===`required`?W(Wn,{onCompleted:()=>t(i),recovery:i}):W(`main`,{class:`ssh-initial-setup-probe`,children:W(`div`,{class:`ssh-initial-setup-probe-card`,role:n===`error`?`alert`:`status`,children:[n===`checking`?W(pn,{class:`ssh-login-probe-spinner`,"aria-hidden":`true`}):W(qt,{"aria-hidden":`true`}),W(`strong`,{children:n===`checking`?`관리자 보안 상태 확인 중`:`관리자 보안 상태를 확인할 수 없습니다`}),W(`span`,{children:n===`checking`?`root 비밀번호 설정 여부를 확인하고 있습니다.`:`공유기 연결을 확인한 뒤 다시 시도해 주세요.`}),n===`error`?W(`button`,{onClick:()=>void o(),type:`button`,children:`다시 시도`}):null]})})}function Ib({icon:e,title:t,description:n}){return W(`div`,{class:`ssh-login-feature`,children:[W(`span`,{class:`ssh-login-feature-icon`,"aria-hidden":`true`,children:e}),W(`span`,{class:`ssh-login-feature-copy`,children:[W(`strong`,{children:t}),W(`span`,{children:n})]})]})}function Lb({theme:e}){return W(e===`dark`?Fn:Pn,{})}function Rb({notice:e,onAuthenticated:t,probing:n=!1}){let r=Xe(xe,[]),i=z(null),a=z(null),[o,s]=L(`root`),[c,l]=L(``),[u,d]=L(`ready`),[f,p]=L(null),[m,h]=L(e??null),[g,_]=L(!1),[v,y]=L(!1),[b,x]=L(!1),[S,C]=L(Ln),w=u===`submitting`,T=S===`dark`?`라이트 모드로 전환`:`다크 모드로 전환`;R(()=>{Rn(S),zn(S)},[S]),R(()=>{if(n)return;let e=window.requestAnimationFrame(()=>a.current?.focus());return()=>window.cancelAnimationFrame(e)},[n]),R(()=>{if(h(e??null),!e)return;let t=window.setTimeout(()=>h(null),7e3);return()=>window.clearTimeout(t)},[e]);let E=e=>{x(e.getModifierState?.(`CapsLock`)??!1)};return W(`main`,{class:`ssh-login-page`,"data-theme":S,children:[m?W(`div`,{"aria-atomic":`true`,"aria-live":`assertive`,class:`ssh-login-toast`,role:`alert`,children:[W(qt,{"aria-hidden":`true`}),W(`span`,{children:m})]}):null,W(`section`,{class:`ssh-login-brand`,"aria-labelledby":`ssh-login-brand-title`,children:[W(`div`,{class:`ssh-login-brand-orb ssh-login-brand-orb-one`,"aria-hidden":`true`}),W(`div`,{class:`ssh-login-brand-orb ssh-login-brand-orb-two`,"aria-hidden":`true`}),W(`div`,{class:`ssh-login-brand-content`,children:[W(`span`,{class:`ssh-login-brand-mark`,"aria-hidden":`true`,children:W(qt,{})}),W(`p`,{class:`ssh-login-eyebrow`,children:`SMART NETWORK PROTECTION`}),W(`h1`,{id:`ssh-login-brand-title`,children:`SmartSafeHub`}),W(`p`,{class:`ssh-login-brand-description`,children:`네트워크와 보안을 하나의 허브에서 편리하게 관리하세요.`}),W(`div`,{class:`ssh-login-features`,"aria-label":`SmartSafeHub 주요 기능`,children:[W(Ib,{description:`DNS 기반 광고·피싱·추적 차단과 사용자 보호 정책을 관리합니다.`,icon:W(qt,{}),title:`SafeShield 보호`}),W(Ib,{description:`Wi-Fi 설정과 현재 연결된 기기의 상태를 한눈에 확인합니다.`,icon:W(on,{}),title:`네트워크 관리`}),W(Ib,{description:`관리 기능과 인증은 현재 SmartSafeHub 장치에서 직접 처리됩니다.`,icon:W(_n,{}),title:`로컬 우선 관리`})]})]})]}),W(`section`,{class:`ssh-login-panel`,"aria-labelledby":`ssh-login-title`,children:[W(`button`,{"aria-label":T,class:`ssh-login-theme-toggle`,onClick:()=>C(e=>e===`dark`?`light`:`dark`),title:T,type:`button`,children:W(Lb,{theme:S})}),W(`div`,{class:`ssh-login-panel-inner`,children:[n?W(`div`,{class:`ssh-login-probe`,role:`status`,"aria-live":`polite`,children:[W(pn,{class:`ssh-login-probe-spinner`,"aria-hidden":`true`}),W(`strong`,{children:`SmartSafeHub 연결 확인 중`}),W(`span`,{children:`로그인 세션을 확인하고 있습니다.`})]}):W(`div`,{class:`ssh-login-card`,children:[W(`div`,{class:`ssh-login-card-heading`,children:[W(`span`,{class:`ssh-login-mobile-mark`,"aria-hidden":`true`,children:W(qt,{})}),W(`div`,{children:[W(`p`,{class:`ssh-login-kicker`,children:`SMARTSAFEHUB ACCESS`}),W(`h2`,{id:`ssh-login-title`,children:`로그인`}),W(`p`,{class:`ssh-login-subtitle`,children:`공유기 관리자 계정의 사용자 이름과 비밀번호를 입력해 주세요.`})]})]}),f?W(`div`,{class:`ssh-login-alert`,id:`ssh-login-error`,role:`alert`,"aria-live":`polite`,children:f}):null,W(`form`,{class:`ssh-login-form`,onSubmit:async e=>{e.preventDefault();let n=o.trim(),r=c;if(p(null),_(!1),!n||!r&&n!==`root`)p(n?`비밀번호를 입력해 주세요.`:`사용자 이름을 입력해 주세요.`),window.requestAnimationFrame(()=>{n?a.current?.focus():i.current?.focus()});else{d(`submitting`);try{let e=await De(n,r);if(e){l(``),t(e);return}l(``),_(!0),p(`로그인하지 못했습니다. 계정 정보를 확인해 주세요. 추가 인증이 설정된 장치는 기본 LuCI 로그인에서 계속할 수 있습니다.`),window.requestAnimationFrame(()=>a.current?.focus())}catch{p(`공유기와 통신할 수 없습니다. 네트워크 연결을 확인한 뒤 다시 시도해 주세요.`)}finally{d(`ready`)}}},noValidate:!0,children:[W(`label`,{class:`ssh-login-field`,for:`smartsafehub-username`,children:[W(`span`,{class:`ssh-login-label`,children:`사용자 이름`}),W(`span`,{class:`ssh-login-input-wrap`,children:[W(`span`,{class:`ssh-login-field-icon`,"aria-hidden":`true`,children:W(Zt,{})}),W(`input`,{autoCapitalize:`none`,autoComplete:`username`,disabled:w,id:`smartsafehub-username`,name:`username`,onInput:e=>s(e.currentTarget.value),placeholder:`관리자 계정`,ref:i,required:!0,spellcheck:!1,type:`text`,value:o})]})]}),W(`label`,{class:`ssh-login-field`,for:`smartsafehub-password`,children:[W(`span`,{class:`ssh-login-label`,children:`비밀번호`}),W(`span`,{class:`ssh-login-input-wrap`,children:[W(`span`,{class:`ssh-login-field-icon`,"aria-hidden":`true`,children:W(_n,{})}),W(`input`,{autoComplete:`current-password`,disabled:w,id:`smartsafehub-password`,name:`password`,onBlur:()=>x(!1),onInput:e=>l(e.currentTarget.value),onKeyDown:E,onKeyUp:E,placeholder:`관리자 비밀번호`,ref:a,...v?{type:`text`}:{type:`password`},value:c}),W(`button`,{"aria-label":v?`비밀번호 숨기기`:`비밀번호 표시`,"aria-pressed":v,class:`ssh-login-password-toggle`,disabled:w,onClick:()=>{y(e=>!e),window.requestAnimationFrame(()=>a.current?.focus())},title:v?`비밀번호 숨기기`:`비밀번호 표시`,type:`button`,children:W(v?bn:yn,{})})]})]}),o.trim()===`root`&&c.length===0?W(`p`,{class:`ssh-login-password-hint`,children:`초기 설정 전 root 계정은 비밀번호가 비어 있을 수 있습니다.`}):null,b?W(`p`,{class:`ssh-login-caps`,role:`status`,children:`Caps Lock이 켜져 있습니다.`}):null,W(`button`,{class:`ssh-login-submit`,disabled:w,type:`submit`,children:[w?W(pn,{class:`ssh-login-submit-spinner`,"aria-hidden":`true`}):null,W(`span`,{children:w?`로그인 중…`:`로그인`})]})]}),W(`div`,{class:`ssh-login-security-note`,children:[W(_n,{"aria-hidden":`true`}),W(`span`,{children:`관리자 비밀번호를 잊었다면 전원이 켜진 상태에서 Reset 버튼을 5~9초 누른 뒤 놓아 비밀번호만 복구할 수 있습니다. 10초 이상 누르면 모든 사용자 설정을 초기화하는 기기 초기화가 실행됩니다.`})]}),g?W(`a`,{class:`ssh-login-fallback`,href:r,children:`기본 LuCI 로그인으로 계속`}):null]}),W(`footer`,{class:`ssh-login-footer`,children:[W(`span`,{children:`SmartSafeHub`}),W(`span`,{"aria-hidden":`true`,children:`·`}),W(`span`,{children:window.location.host})]})]})]})]})}var zb=`smartsafehub-entry-root`,Bb=`data-smartsafehub-mount`,Vb=`data-smartsafehub-shadow-style`;function Hb(){let e=_e(`/`);new Set([e,e.replace(/\/$/,``),_e(`/smartsafehub`),_e(`/admin/smartsafehub`)]).has(window.location.pathname)&&window.history.replaceState(null,``,`${ye()}${window.location.search}${window.location.hash}`)}function Ub(e,t){je(),window.__SMARTHUB_BOOTSTRAP__=Object.freeze({sessionId:e,rpcUrl:_e(`/admin/ubus`),assetBase:t.dataset.assetBase??`/luci-static/smartsafehub/`,assetVersion:t.dataset.assetVersion??`0.2.32-r1`,locale:document.documentElement.lang||`ko`})}function Wb(e,t){let n=window.__SMARTHUB_BOOTSTRAP__,r=n?.assetBase??t.dataset.assetBase??`/luci-static/smartsafehub/`,i=n?.assetVersion??t.dataset.assetVersion,a=`${r}${r.endsWith(`/`)?``:`/`}${e}`;return i?`${a}?v=${encodeURIComponent(i)}`:a}function Gb(e,t){let n=e.shadowRoot??e.attachShadow({mode:`open`}),r=n.querySelector(`link[${Vb}]`),i=Wb(`app.css`,e);r?r.getAttribute(`href`)!==i&&(r.href=i):(r=document.createElement(`link`),r.rel=`stylesheet`,r.href=i,r.setAttribute(Vb,``),n.appendChild(r));let a=n.querySelector(`[${Bb}]`);return a?a.className!==t&&(a.className=t):(a=document.createElement(`div`),a.className=t,a.setAttribute(Bb,``),n.appendChild(a)),a}function Kb(e,t,n){Ub(n,e),t.className=`smartsafehub-shadow-root`,ae(W(Fb,{onAdministratorPasswordChanged:()=>{qb(e,t,`관리자 비밀번호가 변경되었습니다. 새 비밀번호로 다시 로그인해 주세요.`)},onPasswordConfigured:n=>{qb(e,t,n?`관리자 비밀번호 복구가 완료되었습니다. 새 비밀번호로 다시 로그인해 주세요.`:`관리자 비밀번호가 설정되었습니다. 새 비밀번호로 다시 로그인해 주세요.`)}}),t)}function qb(e,t,n){delete window.__SMARTHUB_BOOTSTRAP__,t.className=`smartsafehub-login-shadow-root`,ae(W(Rb,{notice:n,onAuthenticated:n=>{Kb(e,t,n)}}),t)}async function Jb(){zn(Ln());let e=document.getElementById(zb);if(!e)return;Hb(),e.style.display=`block`,e.style.width=`100%`,e.style.minWidth=`0`,e.style.minHeight=`100dvh`,e.style.maxWidth=`100%`,e.style.isolation=`isolate`;let t=Gb(e,`smartsafehub-login-shadow-root`);window.addEventListener(Oe,()=>{qb(e,t,ke)}),ae(W(Rb,{onAuthenticated:n=>{Kb(e,t,n)},probing:!0}),t);try{let n=await de();if(n){Kb(e,t,n);return}}catch{}try{let n=await Te();if(n){Kb(e,t,n);return}}catch{}qb(e,t)}Jb(); \ No newline at end of file +`):e}function zh(e,t){let{element:n,datasetIndex:r,index:i}=t,a=e.getDatasetMeta(r).controller,{label:o,value:s}=a.getLabelAndValue(i);return{chart:e,label:o,parsed:a.getParsed(i),raw:e.data.datasets[r].data[i],formattedValue:s,dataset:a.getDataset(),dataIndex:i,datasetIndex:r,element:n}}function Bh(e,t){let n=e.chart.ctx,{body:r,footer:i,title:a}=e,{boxWidth:o,boxHeight:s}=t,c=ju(t.bodyFont),l=ju(t.titleFont),u=ju(t.footerFont),d=a.length,f=i.length,p=r.length,m=Au(t.padding),h=m.height,g=0,_=r.reduce((e,t)=>e+t.before.length+t.lines.length+t.after.length,0);if(_+=e.beforeBody.length+e.afterBody.length,d&&(h+=d*l.lineHeight+(d-1)*t.titleSpacing+t.titleMarginBottom),_){let e=t.displayColors?Math.max(s,c.lineHeight):c.lineHeight;h+=p*e+(_-p)*c.lineHeight+(_-1)*t.bodySpacing}f&&(h+=t.footerMarginTop+f*u.lineHeight+(f-1)*t.footerSpacing);let v=0,y=function(e){g=Math.max(g,n.measureText(e).width+v)};return n.save(),n.font=l.string,Mc(e.title,y),n.font=c.string,Mc(e.beforeBody.concat(e.afterBody),y),v=t.displayColors?o+2+t.boxPadding:0,Mc(r,e=>{Mc(e.before,y),Mc(e.lines,y),Mc(e.after,y)}),v=0,n.font=u.string,Mc(e.footer,y),n.restore(),g+=m.width,{width:g,height:h}}function Vh(e,t){let{y:n,height:r}=t;return ne.height-r/2?`bottom`:`center`}function Hh(e,t,n,r){let{x:i,width:a}=r,o=n.caretSize+n.caretPadding;if(e===`left`&&i+a+o>t.width||e===`right`&&i-a-o<0)return!0}function Uh(e,t,n,r){let{x:i,width:a}=n,{width:o,chartArea:{left:s,right:c}}=e,l=`center`;return r===`center`?l=i<=(s+c)/2?`left`:`right`:i<=a/2?l=`left`:i>=o-a/2&&(l=`right`),Hh(l,e,t,n)&&(l=`center`),l}function Wh(e,t,n){let r=n.yAlign||t.yAlign||Vh(e,n);return{xAlign:n.xAlign||t.xAlign||Uh(e,t,n,r),yAlign:r}}function Gh(e,t){let{x:n,width:r}=e;return t===`right`?n-=r:t===`center`&&(n-=r/2),n}function Kh(e,t,n){let{y:r,height:i}=e;return t===`top`?r+=n:r-=t===`bottom`?i+n:i/2,r}function qh(e,t,n,r){let{caretSize:i,caretPadding:a,cornerRadius:o}=e,{xAlign:s,yAlign:c}=n,l=i+a,{topLeft:u,topRight:d,bottomLeft:f,bottomRight:p}=ku(o),m=Gh(t,s),h=Kh(t,c,l);return c===`center`?s===`left`?m+=l:s===`right`&&(m-=l):s===`left`?m-=Math.max(u,f)+i:s===`right`&&(m+=Math.max(d,p)+i),{x:yl(m,0,r.width-t.width),y:yl(h,0,r.height-t.height)}}function Jh(e,t,n){let r=Au(n.padding);return t===`center`?e.x+e.width/2:t===`right`?e.x+e.width-r.right:e.x+r.left}function Yh(e){return Lh([],Rh(e))}function Xh(e,t,n){return Pu(e,{tooltip:t,tooltipItems:n,type:`tooltip`})}function Zh(e,t){let n=t&&t.dataset&&t.dataset.tooltip&&t.dataset.tooltip.callbacks;return n?e.override(n):e}var Qh={beforeTitle:wc,title(e){if(e.length>0){let t=e[0],n=t.chart.data.labels,r=n?n.length:0;if(this&&this.options&&this.options.mode===`dataset`)return t.dataset.label||``;if(t.label)return t.label;if(r>0&&t.dataIndex{let t={before:[],lines:[],after:[]},i=Zh(n,e);Lh(t.before,Rh($h(i,`beforeLabel`,this,e))),Lh(t.lines,$h(i,`label`,this,e)),Lh(t.after,Rh($h(i,`afterLabel`,this,e))),r.push(t)}),r}getAfterBody(e,t){return Yh($h(t.callbacks,`afterBody`,this,e))}getFooter(e,t){let{callbacks:n}=t,r=$h(n,`beforeFooter`,this,e),i=$h(n,`footer`,this,e),a=$h(n,`afterFooter`,this,e),o=[];return o=Lh(o,Rh(r)),o=Lh(o,Rh(i)),o=Lh(o,Rh(a)),o}_createItems(e){let t=this._active,n=this.chart.data,r=[],i=[],a=[],o=[],s,c;for(s=0,c=t.length;se.filter(t,r,i,n))),e.itemSort&&(o=o.sort((t,r)=>e.itemSort(t,r,n))),Mc(o,t=>{let n=Zh(e.callbacks,t);r.push($h(n,`labelColor`,this,t)),i.push($h(n,`labelPointStyle`,this,t)),a.push($h(n,`labelTextColor`,this,t))}),this.labelColors=r,this.labelPointStyles=i,this.labelTextColors=a,this.dataPoints=o,o}update(e,t){let n=this.options.setContext(this.getContext()),r=this._active,i,a=[];if(!r.length)this.opacity!==0&&(i={opacity:0});else{let e=Ih[n.position].call(this,r,this._eventPosition);a=this._createItems(n),this.title=this.getTitle(a,n),this.beforeBody=this.getBeforeBody(a,n),this.body=this.getBody(a,n),this.afterBody=this.getAfterBody(a,n),this.footer=this.getFooter(a,n);let t=this._size=Bh(this,n),o=Object.assign({},e,t),s=Wh(this.chart,n,o),c=qh(n,o,s,this.chart);this.xAlign=s.xAlign,this.yAlign=s.yAlign,i={opacity:1,x:c.x,y:c.y,width:t.width,height:t.height,caretX:e.x,caretY:e.y}}this._tooltipItems=a,this.$context=void 0,i&&this._resolveAnimations().update(this,i),e&&n.external&&n.external.call(this,{chart:this.chart,tooltip:this,replay:t})}drawCaret(e,t,n,r){let i=this.getCaretPosition(e,n,r);t.lineTo(i.x1,i.y1),t.lineTo(i.x2,i.y2),t.lineTo(i.x3,i.y3)}getCaretPosition(e,t,n){let{xAlign:r,yAlign:i}=this,{caretSize:a,cornerRadius:o}=n,{topLeft:s,topRight:c,bottomLeft:l,bottomRight:u}=ku(o),{x:d,y:f}=e,{width:p,height:m}=t,h,g,_,v,y,b;return i===`center`?(y=f+m/2,r===`left`?(h=d,g=h-a,v=y+a,b=y-a):(h=d+p,g=h+a,v=y-a,b=y+a),_=h):(g=r===`left`?d+Math.max(s,l)+a:r===`right`?d+p-Math.max(c,u)-a:this.caretX,i===`top`?(v=f,y=v-a,h=g-a,_=g+a):(v=f+m,y=v+a,h=g+a,_=g-a),b=v),{x1:h,x2:g,x3:_,y1:v,y2:y,y3:b}}drawTitle(e,t,n){let r=this.title,i=r.length,a,o,s;if(i){let c=Md(n.rtl,this.x,this.width);for(e.x=Jh(this,n.titleAlign,n),t.textAlign=c.textAlign(n.titleAlign),t.textBaseline=`middle`,a=ju(n.titleFont),o=n.titleSpacing,t.fillStyle=n.titleColor,t.font=a.string,s=0;se!==0)?(e.beginPath(),e.fillStyle=i.multiKeyBackground,Su(e,{x:t,y:p,w:c,h:s,radius:o}),e.fill(),e.stroke(),e.fillStyle=a.backgroundColor,e.beginPath(),Su(e,{x:n,y:p+1,w:c-2,h:s-2,radius:o}),e.fill()):(e.fillStyle=i.multiKeyBackground,e.fillRect(t,p,c,s),e.strokeRect(t,p,c,s),e.fillStyle=a.backgroundColor,e.fillRect(n,p+1,c-2,s-2))}e.fillStyle=this.labelTextColors[n]}drawBody(e,t,n){let{body:r}=this,{bodySpacing:i,bodyAlign:a,displayColors:o,boxHeight:s,boxWidth:c,boxPadding:l}=n,u=ju(n.bodyFont),d=u.lineHeight,f=0,p=Md(n.rtl,this.x,this.width),m=function(n){t.fillText(n,p.x(e.x+f),e.y+d/2),e.y+=d+i},h=p.textAlign(a),g,_,v,y,b,x,S;for(t.textAlign=a,t.textBaseline=`middle`,t.font=u.string,e.x=Jh(this,h,n),t.fillStyle=n.bodyColor,Mc(this.beforeBody,m),f=o&&h!==`right`?a===`center`?c/2+l:c+2+l:0,y=0,x=r.length;y0&&t.stroke()}_updateAnimationTarget(e){let t=this.chart,n=this.$animations,r=n&&n.x,i=n&&n.y;if(r||i){let n=Ih[e.position].call(this,this._active,this._eventPosition);if(!n)return;let a=this._size=Bh(this,e),o=Object.assign({},n,this._size),s=Wh(t,e,o),c=qh(e,o,s,t);(r._to!==c.x||i._to!==c.y)&&(this.xAlign=s.xAlign,this.yAlign=s.yAlign,this.width=a.width,this.height=a.height,this.caretX=n.x,this.caretY=n.y,this._resolveAnimations().update(this,c))}}_willRender(){return!!this.opacity}draw(e){let t=this.options.setContext(this.getContext()),n=this.opacity;if(!n)return;this._updateAnimationTarget(t);let r={width:this.width,height:this.height},i={x:this.x,y:this.y};n=Math.abs(n)<.001?0:n;let a=Au(t.padding),o=this.title.length||this.beforeBody.length||this.body.length||this.afterBody.length||this.footer.length;t.enabled&&o&&(e.save(),e.globalAlpha=n,this.drawBackground(i,e,r,t),Nd(e,t.textDirection),i.y+=a.top,this.drawTitle(i,e,t),this.drawBody(i,e,t),this.drawFooter(i,e,t),Pd(e,t.textDirection),e.restore())}getActiveElements(){return this._active||[]}setActiveElements(e,t){let n=this._active,r=e.map(({datasetIndex:e,index:t})=>{let n=this.chart.getDatasetMeta(e);if(!n)throw Error(`Cannot find a dataset at index `+e);return{datasetIndex:e,element:n.data[t],index:t}}),i=!Nc(n,r),a=this._positionChanged(r,t);(i||a)&&(this._active=r,this._eventPosition=t,this._ignoreReplayEvents=!0,this.update(!0))}handleEvent(e,t,n=!0){if(t&&this._ignoreReplayEvents)return!1;this._ignoreReplayEvents=!1;let r=this.options,i=this._active||[],a=this._getActiveElements(e,i,t,n),o=this._positionChanged(a,e),s=t||!Nc(a,i)||o;return s&&(this._active=a,(r.enabled||r.external)&&(this._eventPosition={x:e.x,y:e.y},this.update(!0,t))),s}_getActiveElements(e,t,n,r){let i=this.options;if(e.type===`mouseout`)return[];if(!r)return t.filter(e=>this.chart.data.datasets[e.datasetIndex]&&this.chart.getDatasetMeta(e.datasetIndex).controller.getParsed(e.index)!==void 0);let a=this.chart.getElementsAtEventForMode(e,i.mode,i,n);return i.reverse&&a.reverse(),a}_positionChanged(e,t){let{caretX:n,caretY:r,options:i}=this,a=Ih[i.position].call(this,e,t);return a!==!1&&(n!==a.x||r!==a.y)}};V(eg,`positioners`,Ih);var tg={id:`tooltip`,_element:eg,positioners:Ih,afterInit(e,t,n){n&&(e.tooltip=new eg({chart:e,options:n}))},beforeUpdate(e,t,n){e.tooltip&&e.tooltip.initialize(n)},reset(e,t,n){e.tooltip&&e.tooltip.initialize(n)},afterDraw(e){let t=e.tooltip;if(t&&t._willRender()){let n={tooltip:t};if(e.notifyPlugins(`beforeTooltipDraw`,{...n,cancelable:!0})===!1)return;t.draw(e.ctx),e.notifyPlugins(`afterTooltipDraw`,n)}},afterEvent(e,t){if(e.tooltip){let n=t.replay;e.tooltip.handleEvent(t.event,n,t.inChartArea)&&(t.changed=!0)}},defaults:{enabled:!0,external:null,position:`average`,backgroundColor:`rgba(0,0,0,0.8)`,titleColor:`#fff`,titleFont:{weight:`bold`},titleSpacing:2,titleMarginBottom:6,titleAlign:`left`,bodyColor:`#fff`,bodySpacing:2,bodyFont:{},bodyAlign:`left`,footerColor:`#fff`,footerSpacing:2,footerMarginTop:6,footerFont:{weight:`bold`},footerAlign:`left`,padding:6,caretPadding:2,caretSize:5,cornerRadius:6,boxHeight:(e,t)=>t.bodyFont.size,boxWidth:(e,t)=>t.bodyFont.size,multiKeyBackground:`#fff`,displayColors:!0,boxPadding:0,borderColor:`rgba(0,0,0,0)`,borderWidth:0,animation:{duration:400,easing:`easeOutQuart`},animations:{numbers:{type:`number`,properties:[`x`,`y`,`width`,`height`,`caretX`,`caretY`]},opacity:{easing:`linear`,duration:200}},callbacks:Qh},defaultRoutes:{bodyFont:`font`,footerFont:`font`,titleFont:`font`},descriptors:{_scriptable:e=>e!==`filter`&&e!==`itemSort`&&e!==`external`,_indexable:!1,callbacks:{_scriptable:!1,_indexable:!1},animation:{_fallback:!1},animations:{_fallback:`animation`}},additionalOptionScopes:[`interaction`]},ng=(e,t,n,r)=>(typeof t==`string`?(n=e.push(t)-1,r.unshift({index:n,label:t})):isNaN(t)&&(n=null),n);function rg(e,t,n,r){let i=e.indexOf(t);return i===-1?ng(e,t,n,r):i===e.lastIndexOf(t)?i:n}var ig=(e,t)=>e===null?null:yl(Math.round(e),0,t);function ag(e){let t=this.getLabels();return e>=0&&et.length-1?null:this.getPixelForValue(t[e].value)}getValueForPixel(e){return Math.round(this._startValue+this.getDecimalForPixel(e)*this._valueRange)}getBasePixel(){return this.bottom}};V(og,`id`,`category`),V(og,`defaults`,{ticks:{callback:ag}});function sg(e,t){let n=[],{bounds:r,step:i,min:a,max:o,precision:s,count:c,maxTicks:l,maxDigits:u,includeBounds:d}=e,f=i||1,p=l-1,{min:m,max:h}=t,g=!X(a),_=!X(o),v=!X(c),y=(h-m)/(u+1),b=al((h-m)/p/f)*f,x,S,C,w;if(b<1e-14&&!g&&!_)return[{value:m},{value:h}];w=Math.ceil(h/b)-Math.floor(m/b),w>p&&(b=al(w*b/p/f)*f),X(s)||(x=10**s,b=Math.ceil(b*x)/x),r===`ticks`?(S=Math.floor(m/b)*b,C=Math.ceil(h/b)*b):(S=m,C=h),g&&_&&i&&ll((o-a)/i,b/1e3)?(w=Math.round(Math.min((o-a)/b,l)),b=(o-a)/w,S=a,C=o):v?(S=g?a:S,C=_?o:C,w=c-1,b=(C-S)/w):(w=(C-S)/b,w=il(w,Math.round(w),b/1e3)?Math.round(w):Math.ceil(w));let T=Math.max(pl(b),pl(S));x=10**(X(s)?T:s),S=Math.round(S*x)/x,C=Math.round(C*x)/x;let E=0;for(g&&(d&&S!==a?(n.push({value:a}),So)break;n.push({value:e})}return _&&d&&C!==o?n.length&&il(n[n.length-1].value,o,cg(o,y,e))?n[n.length-1].value=o:n.push({value:o}):_&&C!==o||n.push({value:C}),n}function cg(e,t,{horizontal:n,minRotation:r}){let i=dl(r),a=(n?Math.sin(i):Math.cos(i))||.001,o=.75*t*(``+e).length;return Math.min(t/a,o)}var lg=class extends dm{constructor(e){super(e),this.start=void 0,this.end=void 0,this._startValue=void 0,this._endValue=void 0,this._valueRange=0}parse(e,t){return X(e)||(typeof e==`number`||e instanceof Number)&&!isFinite(+e)?null:+e}handleTickRangeOptions(){let{beginAtZero:e}=this.options,{minDefined:t,maxDefined:n}=this.getUserBounds(),{min:r,max:i}=this,a=e=>r=t?r:e,o=e=>i=n?i:e;if(e){let e=rl(r),t=rl(i);e<0&&t<0?o(0):e>0&&t>0&&a(0)}if(r===i){let t=i===0?1:Math.abs(i*.05);o(i+t),e||a(r-t)}this.min=r,this.max=i}getTickLimit(){let{maxTicksLimit:e,stepSize:t}=this.options.ticks,n;return t?(n=Math.ceil(this.max/t)-Math.floor(this.min/t)+1,n>1e3&&(console.warn(`scales.${this.id}.ticks.stepSize: ${t} would result generating up to ${n} ticks. Limiting to 1000.`),n=1e3)):(n=this.computeTickLimit(),e=e||11),e&&(n=Math.min(e,n)),n}computeTickLimit(){return 1/0}buildTicks(){let e=this.options,t=e.ticks,n=this.getTickLimit();n=Math.max(2,n);let r=sg({maxTicks:n,bounds:e.bounds,min:e.min,max:e.max,precision:t.precision,step:t.stepSize,count:t.count,maxDigits:this._maxDigits(),horizontal:this.isHorizontal(),minRotation:t.minRotation||0,includeBounds:t.includeBounds!==!1},this._range||this);return e.bounds===`ticks`&&ul(r,this,`value`),e.reverse?(r.reverse(),this.start=this.max,this.end=this.min):(this.start=this.min,this.end=this.max),r}configure(){let e=this.ticks,t=this.min,n=this.max;if(super.configure(),this.options.offset&&e.length){let r=(n-t)/Math.max(e.length-1,1)/2;t-=r,n+=r}this._startValue=t,this._endValue=n,this._valueRange=n-t}getLabelForValue(e){return Xl(e,this.chart.options.locale,this.options.ticks.format)}},ug=class extends lg{determineDataLimits(){let{min:e,max:t}=this.getMinMax(!0);this.min=Dc(e)?e:0,this.max=Dc(t)?t:1,this.handleTickRangeOptions()}computeTickLimit(){let e=this.isHorizontal(),t=e?this.width:this.height,n=dl(this.options.ticks.minRotation),r=(e?Math.sin(n):Math.cos(n))||.001,i=this._resolveTickFontOptions(0);return Math.ceil(t/Math.min(40,i.lineHeight/r))}getPixelForValue(e){return e===null?NaN:this.getPixelForDecimal((e-this._startValue)/this._valueRange)}getValueForPixel(e){return this._startValue+this.getDecimalForPixel(e)*this._valueRange}};V(ug,`id`,`linear`),V(ug,`defaults`,{ticks:{callback:$l.formatters.numeric}});var dg=e=>Math.floor(nl(e)),fg=(e,t)=>10**(dg(e)+t);function pg(e){return e/10**dg(e)==1}function mg(e,t,n){let r=10**n,i=Math.floor(e/r);return Math.ceil(t/r)-i}function hg(e,t){let n=dg(t-e);for(;mg(e,t,n)>10;)n++;for(;mg(e,t,n)<10;)n--;return Math.min(n,dg(e))}function gg(e,{min:t,max:n}){t=Oc(e.min,t);let r=[],i=dg(t),a=hg(t,n),o=a<0?10**Math.abs(a):1,s=10**a,c=i>a?10**i:0,l=Math.round((t-c)*o)/o,u=Math.floor((t-c)/s/10)*s*10,d=Math.floor((l-u)/10**a),f=Oc(e.min,Math.round((c+u+d*10**a)*o)/o);for(;f=10?d=d<15?15:20:d++,d>=20&&(a++,d=2,o=a>=0?1:o),f=Math.round((c+u+d*10**a)*o)/o;let p=Oc(e.max,f);return r.push({value:p,major:pg(p),significand:d}),r}var _g=class extends dm{constructor(e){super(e),this.start=void 0,this.end=void 0,this._startValue=void 0,this._valueRange=0}parse(e,t){let n=lg.prototype.parse.apply(this,[e,t]);if(n===0)this._zero=!0;else return Dc(n)&&n>0?n:null}determineDataLimits(){let{min:e,max:t}=this.getMinMax(!0);this.min=Dc(e)?Math.max(0,e):null,this.max=Dc(t)?Math.max(0,t):null,this.options.beginAtZero&&(this._zero=!0),this._zero&&this.min!==this._suggestedMin&&!Dc(this._userMin)&&(this.min=e===fg(this.min,0)?fg(this.min,-1):fg(this.min,0)),this.handleTickRangeOptions()}handleTickRangeOptions(){let{minDefined:e,maxDefined:t}=this.getUserBounds(),n=this.min,r=this.max,i=t=>n=e?n:t,a=e=>r=t?r:e;n===r&&(n<=0?(i(1),a(10)):(i(fg(n,-1)),a(fg(r,1)))),n<=0&&i(fg(r,-1)),r<=0&&a(fg(n,1)),this.min=n,this.max=r}buildTicks(){let e=this.options,t=gg({min:this._userMin,max:this._userMax},this);return e.bounds===`ticks`&&ul(t,this,`value`),e.reverse?(t.reverse(),this.start=this.max,this.end=this.min):(this.start=this.min,this.end=this.max),t}getLabelForValue(e){return e===void 0?`0`:Xl(e,this.chart.options.locale,this.options.ticks.format)}configure(){let e=this.min;super.configure(),this._startValue=nl(e),this._valueRange=nl(this.max)-nl(e)}getPixelForValue(e){return(e===void 0||e===0)&&(e=this.min),e===null||isNaN(e)?NaN:this.getPixelForDecimal(e===this.min?0:(nl(e)-this._startValue)/this._valueRange)}getValueForPixel(e){let t=this.getDecimalForPixel(e);return 10**(this._startValue+t*this._valueRange)}};V(_g,`id`,`logarithmic`),V(_g,`defaults`,{ticks:{callback:$l.formatters.logarithmic,major:{enabled:!0}}});function vg(e){let t=e.ticks;if(t.display&&e.display){let e=Au(t.backdropPadding);return Q(t.font&&t.font.size,au.font.size)+e.height}return 0}function yg(e,t,n){return n=Ec(n)?n:[n],{w:cu(e,t.string,n),h:n.length*t.lineHeight}}function bg(e,t,n,r,i){return e===r||e===i?{start:t-n/2,end:t+n/2}:ei?{start:t-n,end:t}:{start:t,end:t+n}}function xg(e){let t={l:e.left+e._padding.left,r:e.right-e._padding.right,t:e.top+e._padding.top,b:e.bottom-e._padding.bottom},n=Object.assign({},t),r=[],i=[],a=e._pointLabels.length,o=e.options.pointLabels,s=o.centerPointLabels?$/a:0;for(let c=0;ct.r&&(s=(r.end-t.r)/a,e.r=Math.max(e.r,t.r+s)),i.startt.b&&(c=(i.end-t.b)/o,e.b=Math.max(e.b,t.b+c))}function Cg(e,t,n){let r=e.drawingArea,{extra:i,additionalAngle:a,padding:o,size:s}=n,c=e.getPointPosition(t,r+i+o,a),l=Math.round(fl(_l(c.angle+$c))),u=Og(c.y,s.h,l),d=Eg(l),f=Dg(c.x,s.w,d);return{visible:!0,x:c.x,y:u,textAlign:d,left:f,top:u,right:f+s.w,bottom:u+s.h}}function wg(e,t){if(!t)return!0;let{left:n,top:r,right:i,bottom:a}=e;return!(pu({x:n,y:r},t)||pu({x:n,y:a},t)||pu({x:i,y:r},t)||pu({x:i,y:a},t))}function Tg(e,t,n){let r=[],i=e._pointLabels.length,a=e.options,{centerPointLabels:o,display:s}=a.pointLabels,c={extra:vg(a)/2,additionalAngle:o?$/i:0},l;for(let a=0;a270||n<90)&&(e-=t),e}function kg(e,t,n){let{left:r,top:i,right:a,bottom:o}=n,{backdropColor:s}=t;if(!X(s)){let n=ku(t.borderRadius),c=Au(t.backdropPadding);e.fillStyle=s;let l=r-c.left,u=i-c.top,d=a-r+c.width,f=o-i+c.height;Object.values(n).some(e=>e!==0)?(e.beginPath(),Su(e,{x:l,y:u,w:d,h:f,radius:n}),e.fill()):e.fillRect(l,u,d,f)}}function Ag(e,t){let{ctx:n,options:{pointLabels:r}}=e;for(let i=t-1;i>=0;i--){let t=e._pointLabelItems[i];if(!t.visible)continue;let a=r.setContext(e.getPointLabelContext(i));kg(n,a,t);let o=ju(a.font),{x:s,y:c,textAlign:l}=t;xu(n,e._pointLabels[i],s,c+o.lineHeight/2,o,{color:a.color,textAlign:l,textBaseline:`middle`})}}function jg(e,t,n,r){let{ctx:i}=e;if(n)i.arc(e.xCenter,e.yCenter,t,0,Yc);else{let n=e.getPointPosition(0,t);i.moveTo(n.x,n.y);for(let a=1;a{let n=jc(this.options.pointLabels.callback,[e,t],this);return n||n===0?n:``}).filter((e,t)=>this.chart.getDataVisibility(t))}fit(){let e=this.options;e.display&&e.pointLabels.display?xg(this):this.setCenterPoint(0,0,0,0)}setCenterPoint(e,t,n,r){this.xCenter+=Math.floor((e-t)/2),this.yCenter+=Math.floor((n-r)/2),this.drawingArea-=Math.min(this.drawingArea/2,Math.max(e,t,n,r))}getIndexAngle(e){let t=Yc/(this._pointLabels.length||1),n=this.options.startAngle||0;return _l(e*t+dl(n))}getDistanceFromCenterForValue(e){if(X(e))return NaN;let t=this.drawingArea/(this.max-this.min);return this.options.reverse?(this.max-e)*t:(e-this.min)*t}getValueForDistanceFromCenter(e){if(X(e))return NaN;let t=e/(this.drawingArea/(this.max-this.min));return this.options.reverse?this.max-t:this.min+t}getPointLabelContext(e){let t=this._pointLabels||[];if(e>=0&&e{if(t!==0||t===0&&this.min<0){s=this.getDistanceFromCenterForValue(e.value);let n=this.getContext(t),o=r.setContext(n),c=i.setContext(n);Mg(this,o,s,a,c)}}),n.display){for(e.save(),o=a-1;o>=0;o--){let r=n.setContext(this.getPointLabelContext(o)),{color:i,lineWidth:a}=r;a&&i&&(e.lineWidth=a,e.strokeStyle=i,e.setLineDash(r.borderDash),e.lineDashOffset=r.borderDashOffset,s=this.getDistanceFromCenterForValue(t.reverse?this.min:this.max),c=this.getPointPosition(o,s),e.beginPath(),e.moveTo(this.xCenter,this.yCenter),e.lineTo(c.x,c.y),e.stroke())}e.restore()}}drawBorder(){}drawLabels(){let e=this.ctx,t=this.options,n=t.ticks;if(!n.display)return;let r=this.getIndexAngle(0),i,a;e.save(),e.translate(this.xCenter,this.yCenter),e.rotate(r),e.textAlign=`center`,e.textBaseline=`middle`,this.ticks.forEach((r,o)=>{if(o===0&&this.min>=0&&!t.reverse)return;let s=n.setContext(this.getContext(o)),c=ju(s.font);if(i=this.getDistanceFromCenterForValue(this.ticks[o].value),s.showLabelBackdrop){e.font=c.string,a=e.measureText(r.label).width,e.fillStyle=s.backdropColor;let t=Au(s.backdropPadding);e.fillRect(-a/2-t.left,-i-c.size/2-t.top,a+t.width,c.size+t.height)}xu(e,r.label,0,-i,c,{color:s.color,strokeColor:s.textStrokeColor,strokeWidth:s.textStrokeWidth})}),e.restore()}drawTitle(){}};V(Pg,`id`,`radialLinear`),V(Pg,`defaults`,{display:!0,animate:!0,position:`chartArea`,angleLines:{display:!0,lineWidth:1,borderDash:[],borderDashOffset:0},grid:{circular:!1},startAngle:0,ticks:{showLabelBackdrop:!0,callback:$l.formatters.numeric},pointLabels:{backdropColor:void 0,backdropPadding:2,display:!0,font:{size:10},callback(e){return e},padding:5,centerPointLabels:!1}}),V(Pg,`defaultRoutes`,{"angleLines.color":`borderColor`,"pointLabels.color":`color`,"ticks.color":`color`}),V(Pg,`descriptors`,{angleLines:{_fallback:`grid`}});var Fg={millisecond:{common:!0,size:1,steps:1e3},second:{common:!0,size:1e3,steps:60},minute:{common:!0,size:6e4,steps:60},hour:{common:!0,size:36e5,steps:24},day:{common:!0,size:864e5,steps:30},week:{common:!1,size:6048e5,steps:4},month:{common:!0,size:2628e6,steps:12},quarter:{common:!1,size:7884e6,steps:4},year:{common:!0,size:3154e7}},Ig=Object.keys(Fg);function Lg(e,t){return e-t}function Rg(e,t){if(X(t))return null;let n=e._adapter,{parser:r,round:i,isoWeekday:a}=e._parseOpts,o=t;return typeof r==`function`&&(o=r(o)),Dc(o)||(o=typeof r==`string`?n.parse(o,r):n.parse(o)),o===null?null:(i&&(o=i===`week`&&(cl(a)||a===!0)?n.startOf(o,`isoWeek`,a):n.startOf(o,i)),+o)}function zg(e,t,n,r){let i=Ig.length;for(let a=Ig.indexOf(e);a=Ig.indexOf(n);a--){let n=Ig[a];if(Fg[n].common&&e._adapter.diff(i,r,n)>=t-1)return n}return Ig[n?Ig.indexOf(n):0]}function Vg(e){for(let t=Ig.indexOf(e)+1,n=Ig.length;t=t?n[r]:n[i];e[a]=!0}}function Ug(e,t,n,r){let i=e._adapter,a=+i.startOf(t[0].value,r),o=t[t.length-1].value,s,c;for(s=a;s<=o;s=+i.add(s,1,r))c=n[s],c>=0&&(t[c].major=!0);return t}function Wg(e,t,n){let r=[],i={},a=t.length,o,s;for(o=0;o+e.value))}initOffsets(e=[]){let t=0,n=0,r,i;this.options.offset&&e.length&&(r=this.getDecimalForValue(e[0]),t=e.length===1?1-r:(this.getDecimalForValue(e[1])-r)/2,i=this.getDecimalForValue(e[e.length-1]),n=e.length===1?i:(i-this.getDecimalForValue(e[e.length-2]))/2);let a=e.length<3?.5:.25;t=yl(t,0,a),n=yl(n,0,a),this._offsets={start:t,end:n,factor:1/(t+1+n)}}_generate(){let e=this._adapter,t=this.min,n=this.max,r=this.options,i=r.time,a=i.unit||zg(i.minUnit,t,n,this._getLabelCapacity(t)),o=Q(r.ticks.stepSize,1),s=a===`week`&&i.isoWeekday,c=cl(s)||s===!0,l={},u=t,d,f;if(c&&(u=+e.startOf(u,`isoWeek`,s)),u=+e.startOf(u,c?`day`:a),e.diff(n,t,a)>1e5*o)throw Error(t+` and `+n+` are too far apart with stepSize of `+o+` `+a);let p=r.ticks.source===`data`&&this.getDataTimestamps();for(d=u,f=0;d+e)}getLabelForValue(e){let t=this._adapter,n=this.options.time;return n.tooltipFormat?t.format(e,n.tooltipFormat):t.format(e,n.displayFormats.datetime)}format(e,t){let n=this.options.time.displayFormats,r=this._unit,i=t||n[r];return this._adapter.format(e,i)}_tickFormatFunction(e,t,n,r){let i=this.options,a=i.ticks.callback;if(a)return jc(a,[e,t,n],this);let o=i.time.displayFormats,s=this._unit,c=this._majorUnit,l=s&&o[s],u=c&&o[c],d=n[t],f=c&&u&&d&&d.major;return this._adapter.format(e,r||(f?u:l))}generateTickLabels(e){let t,n,r;for(t=0,n=e.length;t0?o:1}getDataTimestamps(){let e=this._cache.data||[],t,n;if(e.length)return e;let r=this.getMatchingVisibleMetas();if(this._normalized&&r.length)return this._cache.data=r[0].controller.getAllParsedValues(this);for(t=0,n=r.length;t=e[r].pos&&t<=e[i].pos&&({lo:r,hi:i}=Cl(e,`pos`,t)),{pos:a,time:s}=e[r],{pos:o,time:c}=e[i]):(t>=e[r].time&&t<=e[i].time&&({lo:r,hi:i}=Cl(e,`time`,t)),{time:a,pos:s}=e[r],{time:o,pos:c}=e[i]);let l=o-a;return l?s+(c-s)*(t-a)/l:s}var qg=class extends Gg{constructor(e){super(e),this._table=[],this._minPos=void 0,this._tableRange=void 0}initOffsets(){let e=this._getTimestampsForTable(),t=this._table=this.buildLookupTable(e);this._minPos=Kg(t,this.min),this._tableRange=Kg(t,this.max)-this._minPos,super.initOffsets(e)}buildLookupTable(e){let{min:t,max:n}=this,r=[],i=[],a,o,s,c,l;for(a=0,o=e.length;a=t&&c<=n&&r.push(c);if(r.length<2)return[{time:t,pos:0},{time:n,pos:1}];for(a=0,o=r.length;ae-t)}_getTimestampsForTable(){let e=this._cache.all||[];if(e.length)return e;let t=this.getDataTimestamps(),n=this.getLabelTimestamps();return e=t.length&&n.length?this.normalize(t.concat(n)):t.length?t:n,e=this._cache.all=e,e}getDecimalForValue(e){return(Kg(this._table,e)-this._minPos)/this._tableRange}getValueForPixel(e){let t=this._offsets,n=this.getDecimalForPixel(e)/t.factor-t.end;return Kg(this._table,n*this._tableRange+this._minPos,!0)}};V(qg,`id`,`timeseries`),V(qg,`defaults`,Gg.defaults),$m.register(zf,Fh,og,ug,tg);var Jg=new Intl.DateTimeFormat(`ko-KR`,{hour:`2-digit`,hour12:!1}),Yg=`#94a3b8`,Xg=`rgba(148, 163, 184, 0.18)`,Zg=`#0d9488`,Qg=`#0f766e`;function $g(e){return Jg.format(new Date(e*1e3))}function e_(e){return e.map((t,n)=>n%3==0||n===e.length-1?$g(t.bucketStart):``)}function t_({buckets:e}){let t=z(null),n=z(null),r=z(e);return r.current=e,R(()=>{let e=t.current;if(!e)return;let i=window.matchMedia(`(prefers-reduced-motion: reduce)`).matches,a=window.getComputedStyle(e).fontFamily,o={animation:!i&&{duration:500,easing:`easeOutQuart`},maintainAspectRatio:!1,responsive:!0,interaction:{intersect:!1,mode:`index`},plugins:{legend:{display:!1},tooltip:{displayColors:!1,callbacks:{title:e=>{let t=e[0]?.dataIndex??-1,n=r.current[t];return n?$g(n.bucketStart):``},label:e=>`차단 ${Y(Number(e.raw??0))}`,afterLabel:e=>{let t=r.current[e.dataIndex];return t?`DNS 요청 ${Y(t.queries)}`:``}}}},scales:{x:{border:{display:!1},grid:{display:!1},ticks:{autoSkip:!1,color:Yg,font:{family:a,size:11,weight:700},maxRotation:0,minRotation:0}},y:{beginAtZero:!0,border:{display:!1},grid:{color:Xg},ticks:{color:Yg,font:{family:a,size:11,weight:700},maxTicksLimit:4,precision:0}}}};return n.current=new $m(e,{type:`bar`,data:{labels:e_(r.current),datasets:[{data:r.current.map(e=>e.blocked),backgroundColor:Zg,hoverBackgroundColor:Qg,borderRadius:4,borderSkipped:!1,barPercentage:.82,categoryPercentage:.9,maxBarThickness:24}]},options:o}),()=>{n.current?.destroy(),n.current=null}},[]),R(()=>{let t=n.current;t&&(t.data.labels=e_(e),t.data.datasets[0].data=e.map(e=>e.blocked),t.update())},[e]),W(`div`,{class:`mt-5 h-44 sm:h-52`,children:W(`canvas`,{"aria-label":`최근 24시간 시간대별 SafeShield 차단 요청`,ref:t,role:`img`,children:`최근 24시간 시간대별 SafeShield 차단 요청 차트`})})}var n_=3600,r_=24;function i_(e,t){return e<=0?`0.0%`:`${(t/e*100).toFixed(1)}%`}function a_(e){let t=e.hourly[e.hourly.length-1];if(t?.bucketStart)return t.bucketStart;let n=e.updatedAt>0?e.updatedAt:Math.floor(Date.now()/1e3);return Math.floor(n/n_)*n_}function o_(e){let t=new Map(e.hourly.map(e=>[e.bucketStart,e])),n=a_(e)-23*n_;return Array.from({length:r_},(e,r)=>{let i=n+r*n_;return t.get(i)??{bucketStart:i,queries:0,blocked:0}})}function s_({children:e}){return W(`div`,{class:`mt-5 flex min-h-52 items-center justify-center rounded-xl border border-slate-200 bg-slate-50 px-5 text-center`,children:W(`p`,{class:`m-0 max-w-lg text-sm font-semibold leading-6 text-slate-500`,children:e})})}function c_({data:e,error:t,loading:n,nowTimestamp:r,refreshing:i}){let a;if(n&&!e)a=W(s_,{children:`SafeShield 보호 활동을 불러오는 중입니다.`});else if(t&&!e)a=W(s_,{children:`SafeShield 보호 활동을 불러오지 못했습니다. SafeShield 페이지에서 상태를 확인해 주세요.`});else if(!e||!e.available)a=W(s_,{children:`현재 SafeShield에서 차단 통계를 제공하지 않습니다. 통계 기능을 확인해 주세요.`});else if(!e.enabled)a=W(s_,{children:`통계 수집이 꺼져 있습니다. SafeShield 페이지에서 통계를 켜면 최근 24시간 활동이 표시됩니다.`});else{let t=o_(e),n=t.reduce((e,t)=>({queries:e.queries+t.queries,blocked:e.blocked+t.blocked}),{queries:0,blocked:0});a=W(b,{children:[W(`dl`,{class:`mt-5 grid gap-3 sm:grid-cols-3`,children:[W(`div`,{class:`rounded-xl bg-slate-50 px-4 py-3`,children:[W(`dt`,{class:`text-[0.68rem] font-black uppercase tracking-[0.12em] text-slate-400`,children:`DNS 요청`}),W(`dd`,{class:`mt-1 mb-0 ml-0 text-lg font-black text-slate-950`,children:Y(n.queries)})]}),W(`div`,{class:`rounded-xl bg-slate-50 px-4 py-3`,children:[W(`dt`,{class:`text-[0.68rem] font-black uppercase tracking-[0.12em] text-slate-400`,children:`차단`}),W(`dd`,{class:`mt-1 mb-0 ml-0 text-lg font-black text-teal-700`,children:Y(n.blocked)})]}),W(`div`,{class:`rounded-xl bg-slate-50 px-4 py-3`,children:[W(`dt`,{class:`text-[0.68rem] font-black uppercase tracking-[0.12em] text-slate-400`,children:`차단율`}),W(`dd`,{class:`mt-1 mb-0 ml-0 text-lg font-black text-slate-950`,children:i_(n.queries,n.blocked)})]})]}),W(t_,{buckets:t})]})}return W(`article`,{class:`min-w-0 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5 sm:p-6`,children:[W(`div`,{class:`min-w-0`,children:[W(`p`,{class:`m-0 text-xs font-extrabold uppercase tracking-[0.16em] text-slate-500`,children:`SafeShield activity`}),W(`div`,{class:`mt-2 flex min-w-0 items-center justify-between gap-3`,children:[W(`h3`,{class:`m-0 min-w-0 text-xl font-black text-slate-950`,children:`최근 24시간 보호 활동`}),W(`a`,{class:`shrink-0 whitespace-nowrap text-xs font-extrabold text-teal-700 no-underline hover:text-teal-800`,href:`#safeshield`,children:`상세 통계 →`})]}),e?.updatedAt?W(`p`,{class:`mt-2 mb-0 text-[0.68rem] font-semibold text-slate-400`,title:Ao(e.updatedAt),children:i?`갱신 중…`:`집계: ${jo(e.updatedAt,r)}`}):null]}),a,t&&e?W(`p`,{class:`mt-3 mb-0 text-xs font-bold text-amber-700`,children:`최근 통계 갱신에 실패해 마지막 정상 데이터를 표시하고 있습니다.`}):null]})}var l_=7200;function u_(e,t=Math.floor(Date.now()/1e3)){return!e||!e.settings.checkEnabled||e.phase===`checking`||!e.lastCheckAt?!1:Math.max(0,t-e.lastCheckAt)>Math.max(e.settings.checkIntervalSeconds*2,l_)}var d_=2*Math.PI*39;function f_(e){return e===`healthy`?`bg-emerald-50 text-emerald-700 ring-emerald-200`:e===`warning`?`bg-amber-50 text-amber-800 ring-amber-200`:`bg-slate-100 text-slate-600 ring-slate-200`}function p_({detail:e,eyebrow:t,href:n,icon:r,linkLabel:i=`자세히 보기 →`,meta:a,metaState:o,metaWarning:s=!1,state:c=`neutral`,value:l}){let u=W(b,{children:[W(`div`,{class:`flex items-start justify-between gap-4`,children:[W(`p`,{class:`m-0 text-[0.68rem] font-black uppercase tracking-[0.16em] text-slate-500`,children:t}),W(`span`,{class:`grid size-10 shrink-0 place-items-center rounded-xl ring-1 ring-inset ${f_(c)}`,children:r})]}),W(`p`,{class:`mt-4 mb-0 break-words text-2xl font-black tracking-tight text-slate-950`,children:l}),W(`p`,{class:`mt-2 mb-0 min-h-10 text-sm font-medium leading-5 text-slate-500`,children:e}),a?W(`p`,{class:`mt-3 mb-0 text-xs font-bold ${o===`healthy`?`text-emerald-700`:o===`warning`||s?`text-amber-700`:`text-slate-400`}`,children:a}):null,n?W(`span`,{class:`mt-4 inline-flex text-xs font-extrabold text-teal-700`,children:i}):null]}),d=`min-w-0 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5 transition`;return n?W(`a`,{class:`${d} no-underline hover:border-slate-300 hover:shadow-md`,href:n,children:u}):W(`article`,{class:d,children:u})}var m_=6e4;function h_(e,t){return!e||!Number.isFinite(e)||e<=0?null:Math.max(0,t-e)}function g_(e,t,n,r){return!t||t<=0?`${e}: ${r}`:W(`span`,{title:Ao(t),children:[e,`: `,jo(t,n)]})}function __(e){if(!e)return null;switch(e.toLowerCase()){case`dhcp`:return`DHCP`;case`static`:return`Static`;case`pppoe`:return`PPPoE`;default:return e.toUpperCase()}}function v_(e){if(!e)return!1;let t=e.split(`.`).map(e=>Number(e));if(t.length!==4||t.some(e=>!Number.isInteger(e)||e<0||e>255))return!1;let[n,r]=t;return n===void 0||r===void 0?!1:n===10||n===172&&r>=16&&r<=31||n===192&&r===168}function y_({label:e,value:t}){return W(`div`,{class:`flex min-w-0 items-start justify-between gap-4 border-b border-slate-100 py-3 last:border-b-0`,children:[W(`dt`,{class:`text-sm font-semibold text-slate-500`,children:e}),W(`dd`,{class:`m-0 min-w-0 max-w-[68%] break-words text-right text-sm font-extrabold text-slate-950`,children:t})]})}function b_(e){return e===`initializing`?{badge:`bg-teal-100 text-teal-800`,icon:`text-teal-700`,label:`준비 중`,panel:`border-teal-200 bg-teal-50`}:e===`critical`?{badge:`bg-rose-100 text-rose-800`,icon:`text-rose-700`,label:`이상`,panel:`border-rose-200 bg-rose-50/70`}:e===`warning`?{badge:`bg-amber-100 text-amber-800`,icon:`text-amber-700`,label:`주의`,panel:`border-amber-200 bg-amber-50/70`}:e===`unknown`?{badge:`bg-slate-200 text-slate-700`,icon:`text-slate-500`,label:`확인 불가`,panel:`border-slate-200 bg-slate-50`}:{badge:`bg-emerald-100 text-emerald-800`,icon:`text-emerald-700`,label:`정상`,panel:`border-emerald-200 bg-emerald-50/70`}}function x_({data:e,error:t,loading:n,nowTimestamp:r}){let i=!!(e&&e.generatedAt>0),a=b_(i&&e?e.overall:`unknown`),o=i&&e?e.checks.filter(e=>e.status!==`ok`):[],s=o.slice(0,2),c=Math.max(0,o.length-s.length);return W(`div`,{class:`mt-5 border-t border-slate-200 pt-5`,children:[W(`div`,{class:`flex flex-wrap items-center justify-between gap-3`,children:[W(`div`,{class:`flex items-center gap-2`,children:[W(`p`,{class:`m-0 text-sm font-black text-slate-950`,children:`장치 진단`}),i?W(`span`,{class:`rounded-full px-2.5 py-1 text-[11px] font-extrabold ${a.badge}`,children:a.label}):null]}),W(`a`,{class:`text-xs font-extrabold text-teal-700 no-underline hover:text-teal-900`,href:`#settings`,children:`상세 보기 →`})]}),t&&!i?W(`div`,{class:`mt-3 rounded-xl border border-amber-200 bg-amber-50/70 px-4 py-3`,children:W(`div`,{class:`flex items-start gap-2`,children:[W(Cn,{class:`mt-0.5 size-4 shrink-0 text-amber-700`}),W(`div`,{class:`min-w-0`,children:[W(`p`,{class:`m-0 text-sm font-extrabold text-slate-900`,children:`진단 상태를 확인하지 못했습니다.`}),W(`p`,{class:`mt-1 mb-0 text-xs leading-5 text-slate-500`,children:`설정에서 장치 진단 상태를 다시 확인해 주세요.`})]})]})}):i&&e?W(`div`,{class:`mt-3 rounded-xl border px-4 py-3 ${a.panel}`,children:[W(`div`,{class:`flex min-w-0 items-start gap-3`,children:[e.overall===`ok`?W(Sn,{class:`mt-0.5 size-5 shrink-0 ${a.icon}`}):e.overall===`initializing`?W(dn,{class:`mt-0.5 size-5 shrink-0 ${a.icon}`}):W(Cn,{class:`mt-0.5 size-5 shrink-0 ${a.icon}`}),W(`div`,{class:`min-w-0 flex-1`,children:[W(`p`,{class:`m-0 text-sm font-black text-slate-950`,children:e.summary.message}),W(`p`,{class:`mt-1 mb-0 text-xs font-semibold leading-5 text-slate-500`,children:[`마지막 진단: `,jo(e.generatedAt,r),` · 검사 항목:`,` `,e.summary.total,`개`]})]})]}),s.length>0?W(`div`,{class:`mt-3 grid gap-2 sm:grid-cols-2`,children:s.map(e=>W(`div`,{class:`min-w-0 rounded-lg border border-white/80 bg-white/70 px-3 py-2`,children:[W(`p`,{class:`m-0 text-xs font-extrabold text-slate-900`,children:e.label}),W(`p`,{class:`mt-1 mb-0 line-clamp-2 text-xs leading-5 text-slate-500`,children:e.message})]},e.id))}):null,c>0?W(`p`,{class:`mt-2 mb-0 text-xs font-extrabold text-slate-600`,children:[`외 `,c,`건의 확인 항목이 있습니다.`]}):null]}):W(`div`,{class:`mt-3 rounded-xl border border-slate-200 bg-slate-50 px-4 py-3`,children:[W(`p`,{class:`m-0 text-sm font-extrabold text-slate-900`,children:n?`진단 상태를 확인하고 있습니다.`:`아직 생성된 진단 결과가 없습니다.`}),W(`p`,{class:`mt-1 mb-0 text-xs leading-5 text-slate-500`,children:`진단 결과가 생성되면 현재 장치 상태를 이곳에 요약해서 표시합니다.`})]})]})}function S_(e,t,n){if(t&&!e)return{detail:`보호 상태를 확인하고 있습니다.`,state:`neutral`,value:`확인 중`};if(n&&!e)return{detail:`SafeShield 상태를 불러오지 못했습니다.`,state:`warning`,value:`확인 필요`};if(!e||!e.available)return{detail:`SafeShield 서비스를 사용할 수 없습니다.`,state:`warning`,value:`사용 불가`};if(!e.enabled||e.status===`disabled`)return{detail:`DNS 보호가 비활성화되어 있습니다.`,state:`neutral`,value:`꺼짐`};if(e.status===`running`)return{detail:`차단 목록을 갱신하고 있습니다.`,state:`neutral`,value:`갱신 중`};if(!(e.active&&e.runtime.dnsmasqRunning&&e.runtime.dnsRuntimeOk&&e.health.overall!==`error`&&e.issueCounts.errors===0))return{detail:e.runtime.lastErrorCode?`최근 오류: ${e.runtime.lastErrorCode}`:`DNS 보호 상태를 확인해 주세요.`,state:`warning`,value:`확인 필요`};let r=e.entitlement.plan?.trim(),i=e.blocklist.validLineCount>0?`${Y(e.blocklist.validLineCount)}개 차단 규칙`:`차단 목록 적용됨`;return{detail:r?`${i} · ${r.toUpperCase()}`:i,state:`healthy`,value:`보호 중`}}function C_({accountRegistered:e,activity:t,activityError:n,activityLoading:r,data:i,devices:a,devicesError:o,devicesLoading:s,error:c,firmware:l,firmwareError:u,firmwareLoading:d,health:f,healthError:p,healthLoading:m,lan:h,lanError:g,lanLoading:_,loading:v,onRetry:y,safeshield:b,safeshieldError:x,safeshieldLoading:S,statistics:C,statisticsError:w,statisticsLoading:T,statisticsRefreshing:E,updates:D,updatesError:O,updatesLoading:k}){let A=Xe(()=>i?Io(i.runtime.memory):null,[i]),[j,M]=L(()=>Math.floor(Date.now()/1e3));if(R(()=>{let e=window.setInterval(()=>{M(Math.floor(Date.now()/1e3))},m_);return()=>window.clearInterval(e)},[]),v&&!i)return W(vs,{});if(c&&!i)return W(ys,{message:c,onRetry:y});if(!i)return null;let N=A?Math.round(A.percent):0,P=rs(i.runtime.storage),F=P?(P.availableRatio*d_).toFixed(2):`0`,I=S_(b,S,x),ee=D?.packages.find(e=>e.name===`luci-app-smartsafehub`)??D?.packages[0]??null,te=!!(D&&D.updateCount>0),ne=D?te?`${D.updateCount}개 업데이트`:D.lastCheckAt?`최신 상태`:`확인 전`:O?`확인 필요`:`확인 중`,re=ee?te&&ee.availableVersion?`${ee.installedVersion} → ${ee.availableVersion}`:`설치 버전 ${ee.installedVersion}`:O?`업데이트 상태를 불러오지 못했습니다.`:k?`업데이트 상태를 확인하고 있습니다.`:`업데이트 상태를 확인할 수 없습니다.`,ie=a?`${Y(a.totals.online)}대`:s?`확인 중`:`확인 필요`,ae=a?`Wi-Fi ${Y(a.totals.wireless)}대 · 유선/기타 ${Y(a.totals.ethernet)}대`:o||`연결 기기 요약을 불러오고 있습니다.`,oe=__(i.network.protocol),se=i.network.available?i.network.ipv4Address?`WAN ${i.network.ipv4Address}${oe?` · ${oe}`:``}`:oe?`${oe} · WAN 주소를 받지 못했습니다.`:`WAN 주소를 받지 못했습니다.`:`WAN 인터페이스를 찾을 수 없습니다.`,ce=!!h?.conflict.detected,le=!i.network.up||ce||g!==null&&!h?`warning`:h?`healthy`:`neutral`,ue=ce?`네트워크 충돌`:i.network.up?`정상 연결`:`연결 확인`,de=i.network.up?ce?`⚠ LAN 대역과 충돌합니다`:_&&!h?`네트워크 대역 확인 중`:g&&!h?`네트워크 대역을 확인하지 못했습니다.`:h?`✓ 네트워크 충돌 없음`:`네트워크 대역 확인 필요`:`WAN 연결 상태를 확인해 주세요.`,fe=!i.network.up||ce||g!==null&&!h?`warning`:h?`healthy`:`neutral`,pe=i.network.ipv4Address?v_(i.network.ipv4Address)?`${i.network.ipv4Address} · 사설 네트워크`:i.network.ipv4Address:`할당되지 않음`,me=h_(b?.timestamps.lastSuccess,j),he=b?Math.max(b.timestamps.refreshIntervalS*2,86400):null,ge=!!(b?.enabled&&b.status!==`running`&&me!==null&&he!==null&&me>he),_e=u_(D,j),ve=!!l?.current.metadataAvailable,ye=d&&!l?`확인 중`:u&&!l?`확인 필요`:ve?l?.current.releaseVersion?`SmartSafeHub ${l.current.releaseVersion}`:`SmartSafeHub 펌웨어`:`${i.software.distribution} ${i.software.version}`,be=d&&!l?`확인 중`:u&&!l?`확인 필요`:ve&&l?.current.buildId||i.software.revision,xe=ve?`빌드 ID`:`리비전`;return W(`div`,{class:`min-w-0 space-y-6`,children:[W(`section`,{"aria-labelledby":`dashboard-overview-title`,children:[W(J,{description:`인터넷, SafeShield, 연결 기기와 소프트웨어 상태를 한눈에 확인합니다.`,eyebrow:`Overview`,id:`dashboard-overview-title`,title:`시스템 개요`}),W(`div`,{class:`grid min-w-0 gap-4 sm:grid-cols-2 xl:grid-cols-4`,children:[W(p_,{detail:se,eyebrow:`Internet`,href:`#network`,icon:W(ln,{class:`size-5`}),linkLabel:ce?`해결하기 →`:`자세히 보기 →`,meta:de,metaState:fe,state:le,value:ue}),W(p_,{detail:I.detail,eyebrow:`SafeShield`,href:`#safeshield`,icon:W(qt,{class:`size-5`}),meta:g_(ge?`차단 목록 갱신 지연`:`차단 목록 갱신`,b?.timestamps.lastSuccess,j,S?`확인 중`:`기록 없음`),metaWarning:ge,state:I.state===`healthy`&&ge?`warning`:I.state,value:I.value}),W(p_,{detail:ae,eyebrow:`Connected devices`,href:`#devices`,icon:W(Yt,{class:`size-5`}),meta:g_(`목록 확인`,a?.generatedAt,j,s?`확인 중`:`기록 없음`),state:a?`healthy`:o?`warning`:`neutral`,value:ie}),W(p_,{detail:re,eyebrow:`Software update`,href:`#system`,icon:W(rn,{class:`size-5`}),meta:D&&!D.settings.checkEnabled?g_(`자동 확인 꺼짐 · 마지막 확인`,D.lastCheckAt,j,`확인 기록 없음`):g_(_e?`업데이트 확인 지연`:`마지막 확인`,D?.lastCheckAt,j,k?`확인 중`:`아직 없음`),metaWarning:_e,state:O||_e||te?`warning`:D?.lastCheckAt?`healthy`:`neutral`,value:ne})]}),e===!1?W(`div`,{class:`ssh-dashboard-account-banner mt-4 flex flex-col gap-4 rounded-2xl border p-4 sm:flex-row sm:items-center sm:justify-between sm:p-5`,role:`status`,children:[W(`div`,{class:`flex min-w-0 items-start gap-3`,children:[W(`span`,{class:`ssh-dashboard-account-banner-icon grid size-10 shrink-0 place-items-center rounded-xl`,"aria-hidden":`true`,children:W(Zt,{class:`size-5`})}),W(`div`,{class:`min-w-0`,children:[W(`div`,{class:`flex flex-wrap items-center gap-2`,children:[W(`p`,{class:`ssh-dashboard-account-banner-title m-0 font-bold`,children:`SmartSafeHub 계정을 연결해 주세요`}),W(`span`,{class:`ssh-dashboard-account-banner-badge rounded-full px-2 py-0.5 text-[0.7rem] font-bold`,children:`연결 필요`})]}),W(`p`,{class:`ssh-dashboard-account-banner-description mt-1 mb-0 text-sm leading-relaxed`,children:`계정을 연결하면 웹사이트에서 기기를 관리하고 구독 정보를 확인할 수 있습니다. DNS 보호는 연결하지 않아도 계속 작동합니다.`})]})]}),W(`a`,{href:`#account`,class:`ssh-dashboard-account-banner-action inline-flex min-h-10 shrink-0 items-center justify-center rounded-xl px-4 py-2 text-sm font-bold no-underline transition-colors focus-visible:outline-2 focus-visible:outline-offset-2`,children:`계정 연결하기 →`})]}):null]}),W(`section`,{"aria-labelledby":`dashboard-activity-title`,children:[W(J,{description:`최근 SafeShield 차단 활동과 현재 네트워크 연결 구성을 함께 확인합니다.`,eyebrow:`Activity`,id:`dashboard-activity-title`,title:`네트워크 보호 활동`}),W(`div`,{class:`grid min-w-0 gap-4 xl:grid-cols-[minmax(0,1.55fr)_minmax(18rem,0.65fr)]`,children:[W(c_,{data:C,error:w,loading:T,nowTimestamp:j,refreshing:E}),W(`article`,{class:`min-w-0 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5 sm:p-6`,children:[W(`div`,{class:`flex items-start justify-between gap-4`,children:[W(`div`,{children:[W(`p`,{class:`m-0 text-xs font-extrabold uppercase tracking-[0.16em] text-slate-500`,children:`Network`}),W(`h3`,{class:`mt-2 mb-0 text-xl font-black text-slate-950`,children:`연결 상태`})]}),W(`span`,{class:`grid size-10 shrink-0 place-items-center rounded-xl ${i.network.up&&!ce?`bg-emerald-50 text-emerald-700`:`bg-amber-50 text-amber-800`}`,children:W(ln,{class:`size-5`})})]}),W(`dl`,{class:`mt-4 mb-0`,children:[W(y_,{label:`인터넷`,value:ce?`네트워크 충돌`:i.network.up?`정상 연결`:`연결 확인`}),W(y_,{label:`WAN IP`,value:pe}),W(y_,{label:`프로토콜`,value:oe||`확인되지 않음`}),W(y_,{label:`상위 네트워크`,value:h?.wan.subnet||(_?`확인 중`:g?`확인 필요`:`확인되지 않음`)}),W(y_,{label:`LAN 네트워크`,value:h?.lan.subnet||(_?`확인 중`:g?`확인 필요`:`확인되지 않음`)}),W(y_,{label:`대역 충돌`,value:h?W(`span`,{class:h.conflict.detected?`text-amber-700`:`text-emerald-700`,children:h.conflict.detected?`충돌 감지`:`없음`}):_?`확인 중`:`확인 필요`})]}),W(`a`,{class:`mt-5 inline-flex min-h-10 w-full items-center justify-center rounded-xl border border-slate-200 bg-slate-50 px-4 py-2 text-sm font-extrabold text-slate-700 no-underline transition hover:border-teal-200 hover:bg-teal-50 hover:text-teal-800`,href:`#network`,children:`네트워크 보기`})]})]})]}),W(`section`,{"aria-labelledby":`dashboard-recent-activity-title`,children:[W(J,{action:W(`a`,{class:`inline-flex whitespace-nowrap text-xs font-extrabold text-teal-700 no-underline hover:text-teal-900`,href:`#activity`,children:`전체 보기 →`}),description:`인터넷, 보호, 업데이트와 진단에서 실제 상태가 바뀐 시점을 확인합니다.`,eyebrow:`History`,id:`dashboard-recent-activity-title`,title:`최근 활동`}),W(`article`,{class:`min-w-0 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5 sm:p-6`,children:r||n&&!t?W(Zo,{error:n,loading:r}):W(Xo,{compact:!0,events:(t?.events??[]).slice(0,3)})})]}),W(`section`,{"aria-labelledby":`dashboard-system-title`,children:[W(J,{description:`메모리, 디스크, 시스템 부하와 장치 정보를 빠르게 점검합니다.`,eyebrow:`System`,id:`dashboard-system-title`,title:`시스템 상태`}),W(`div`,{class:`grid min-w-0 gap-4 xl:grid-cols-[minmax(0,1.45fr)_minmax(20rem,0.75fr)]`,children:[W(`article`,{class:`min-w-0 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5 sm:p-6`,children:[W(`div`,{class:`flex flex-wrap items-start justify-between gap-4`,children:[W(`div`,{children:[W(`p`,{class:`m-0 text-xs font-extrabold uppercase tracking-[0.16em] text-slate-500`,children:`Runtime`}),W(`h3`,{class:`mt-2 mb-0 text-xl font-black text-slate-950`,children:`리소스 사용량`})]}),W(`span`,{class:`inline-flex items-center gap-2 rounded-full px-3 py-1.5 text-xs font-black ring-1 ring-inset ${N>=90?`bg-amber-50 text-amber-800 ring-amber-200`:`bg-emerald-50 text-emerald-700 ring-emerald-200`}`,children:[W(`span`,{class:`size-2 rounded-full ${N>=90?`bg-amber-500`:`bg-emerald-500`}`}),N>=90?`메모리 확인`:`정상 실행`]})]}),W(`div`,{class:`mt-5 grid min-w-0 grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-4`,children:[W(`div`,{class:`flex min-w-0 flex-col rounded-xl border border-slate-200 bg-slate-50 p-3 sm:p-4`,children:[W(`div`,{class:`flex items-center gap-2 text-slate-500`,children:[W(fn,{class:`size-4`}),W(`span`,{class:`text-xs font-extrabold`,children:`메모리`})]}),W(`div`,{class:`flex min-h-0 flex-1 flex-col justify-center py-1`,children:[W(`p`,{class:`mt-2 mb-0 text-xl font-black text-slate-950`,children:[N,`%`]}),W(`p`,{class:`mt-1 mb-0 text-xs font-semibold text-slate-500`,children:[A?Do(A.used):`-`,` /`,` `,Do(i.runtime.memory.total)]}),W(`div`,{"aria-label":`메모리 사용률 ${N}%`,class:`mt-3 h-1.5 overflow-hidden rounded-full bg-slate-200`,role:`progressbar`,"aria-valuemin":0,"aria-valuemax":100,"aria-valuenow":N,children:W(`div`,{class:`h-full rounded-full transition-[width] duration-300 ${N>=90?`bg-amber-500`:`bg-teal-600`}`,style:{width:`${Math.min(100,N)}%`}})})]})]}),W(`div`,{class:`ssh-dashboard-storage-card flex min-w-0 flex-col rounded-xl border border-slate-200 bg-slate-50 p-3 sm:p-4`,"data-level":P?.level??`unavailable`,children:[W(`div`,{class:`flex items-center gap-2 text-slate-500`,children:[W(gn,{class:`size-4`}),W(`span`,{class:`text-xs font-extrabold`,children:`디스크`})]}),W(`div`,{class:`mt-1 flex justify-center`,role:`img`,"aria-label":P?`디스크 여유 공간 ${P.availablePercent}%, ${P.availableText} 남음, 사용 ${P.usedText} / 전체 ${P.totalText}`:`디스크 저장 공간 확인 불가`,children:W(`div`,{class:`ssh-apps-storage-ring relative grid size-[76px] shrink-0 place-items-center`,"data-level":P?.level??`unavailable`,children:[W(`svg`,{"aria-hidden":`true`,class:`size-[76px]`,viewBox:`0 0 96 96`,children:[W(`circle`,{class:`ssh-apps-storage-track`,cx:`48`,cy:`48`,r:`39`}),P&&P.availableRatio>0?W(`circle`,{class:`ssh-apps-storage-free`,cx:`48`,cy:`48`,r:`39`,"stroke-dasharray":`${F} ${d_.toFixed(2)}`}):null]}),W(`div`,{class:`absolute inset-0 flex items-center justify-center text-center`,children:W(`strong`,{class:`text-base leading-none font-black text-slate-950`,children:P?`${P.availablePercent}%`:`—`})})]})}),W(`p`,{class:`ssh-apps-storage-available mt-1 mb-0 text-center text-xs font-extrabold text-teal-700`,children:P?`${P.availableText} 남음`:`저장 공간 확인 불가`}),P?W(`p`,{class:`mt-1 mb-0 text-center text-[11px] leading-4 font-semibold text-slate-500`,title:`앱 설치 가능 공간을 기준으로 계산하며 파일시스템 예약 공간이 사용량에 포함될 수 있습니다.`,children:[`사용 `,P.usedText,` / 전체 `,P.totalText]}):null]}),W(`div`,{class:`flex min-w-0 flex-col rounded-xl border border-slate-200 bg-slate-50 p-3 sm:p-4`,children:[W(`div`,{class:`flex items-center gap-2 text-slate-500`,children:[W(Jt,{class:`size-4`}),W(`span`,{class:`text-xs font-extrabold`,children:`시스템 부하`})]}),W(`div`,{class:`flex min-h-0 flex-1 flex-col justify-center py-1`,children:[W(`p`,{class:`mt-2 mb-0 text-xl font-black text-slate-950`,children:Oo(i.runtime.load[0])}),W(`p`,{class:`mt-1 mb-0 text-xs font-semibold text-slate-500`,children:[`5분 `,Oo(i.runtime.load[1]),` · 15분`,` `,Oo(i.runtime.load[2])]})]})]}),W(`div`,{class:`flex min-w-0 flex-col rounded-xl border border-slate-200 bg-slate-50 p-3 sm:p-4`,children:[W(`div`,{class:`flex items-center gap-2 text-slate-500`,children:[W(dn,{class:`size-4`}),W(`span`,{class:`text-xs font-extrabold`,children:`실행 시간`})]}),W(`div`,{class:`flex min-h-0 flex-1 flex-col justify-center py-1`,children:[W(`p`,{class:`mt-2 mb-0 text-xl font-black text-slate-950`,children:Eo(i.runtime.uptime)}),W(`p`,{class:`mt-1 mb-0 text-xs font-semibold text-slate-500`,children:[`부팅 `,ko(i.runtime.localtime,i.runtime.uptime)]})]})]})]}),W(x_,{data:f,error:p,loading:m,nowTimestamp:j})]}),W(`article`,{class:`min-w-0 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5 sm:p-6`,children:[W(`div`,{class:`flex items-start justify-between gap-4`,children:[W(`div`,{children:[W(`p`,{class:`m-0 text-xs font-extrabold uppercase tracking-[0.16em] text-slate-500`,children:`Device`}),W(`h3`,{class:`mt-2 mb-0 text-xl font-black text-slate-950`,children:`장치 정보`})]}),W(`span`,{class:`grid size-10 shrink-0 place-items-center rounded-xl bg-teal-50 text-teal-700`,children:W(Jt,{class:`size-5`})})]}),W(`dl`,{class:`mt-4 mb-0`,children:[W(y_,{label:`모델`,value:i.device.model}),W(y_,{label:`호스트 이름`,value:i.device.hostname}),W(y_,{label:`펌웨어`,value:W(`span`,{title:ve?l?.current.releaseVersion?`SmartSafeHub 펌웨어 ${l.current.releaseVersion}`:`SmartSafeHub 커스텀 펌웨어`:u||void 0,children:ye})}),W(y_,{label:xe,value:be}),W(y_,{label:`커널`,value:i.software.kernel}),W(y_,{label:`WAN IP`,value:i.network.ipv4Address||`할당되지 않음`})]})]})]})]})]})}var w_=Object.assign;function T_(e,t){for(var n in e)if(n!=`__source`&&e[n]!==t[n])return!0;for(var r in t)if(r!=`__source`&&!(r in e))return!0;return!1}var E_=/^(-|f[lo].*[^se]$|g.{5,}[^ps]$|z|o[pr]|(W.{5})?[lL]i.*(t|mp)$|an|(bo|s).{4}Im|sca|m.{6}[ds]|ta|c.*[st]$|wido|ini|asp|fill|st)/;function D_(e,t){function n(e){var n=this.props.ref;return n!=e.ref&&n&&(typeof n==`function`?n(null):n.current=null),t?!t(this.props,e)||n!=e.ref:T_(this.props,e)}function r(t){return this.shouldComponentUpdate=n,v(e,t)}return r.displayName=`Memo(`+(e.displayName||e.name)+`)`,r.prototype.isReactComponent=!0,r.type=e,r}function O_(e,t){var n=se(e,t);return n.containerInfo=t,n}var k_=Symbol.for(`react.element`),A_=/^(?:accent|alignment|arabic|baseline|cap|clip(?!PathU)|color|dominant|fill|flood|font|glyph(?!R)|horiz|image(?!S)|letter|lighting|marker(?!H|W|U)|overline|paint|pointer|shape|stop|strikethrough|stroke|text(?!L)|transform|underline|unicode|units|v|vector|vert|word|writing|x(?!C))[A-Z]/,j_=/[A-Z0-9]/g,M_=typeof document<`u`,N_=function(e){return/fil|che|rad/.test(e)};x.prototype.isReactComponent=!0,[`componentWillMount`,`componentWillReceiveProps`,`componentWillUpdate`].forEach(function(e){Object.defineProperty(x.prototype,e,{configurable:!0,get:function(){return this[`UNSAFE_`+e]},set:function(t){Object.defineProperty(this,e,{configurable:!0,writable:!0,value:t})}})});var P_=e.event;e.event=function(e){return P_&&(e=P_(e)),e.persist=function(){},e.isPropagationStopped=function(){return this.cancelBubble},e.isDefaultPrevented=function(){return this.defaultPrevented},e.nativeEvent=e};var F_={configurable:!0,get:function(){return this.class}},I_=e.vnode;e.vnode=function(e){if(typeof e.type==`string`)(function(e){var t=e.props,n=e.type,r={},i=n.indexOf(`-`)==-1;for(var a in t){var o=t[a];if(!(a==`value`&&`defaultValue`in t&&o==null||M_&&a==`children`&&n==`noscript`||a==`class`||a==`className`)){if(a==`style`&&o&&o.constructor==Object){var s=void 0;for(var c in o)typeof o[c]!=`number`||E_.test(c)||(s||(s=o=w_({},o)),o[c]+=`px`)}else if(a==`defaultValue`&&`value`in t&&t.value==null)a=`value`;else if(a==`download`&&!0===o)o=``;else if(a==`translate`&&o===`no`)o=!1;else if(a[0]==`o`&&a[1]==`n`){var l=a.toLowerCase();l==`ondoubleclick`?a=`ondblclick`:l!=`onchange`||n!=`input`&&n!=`textarea`||N_(t.type)?l==`onfocus`?a=`onfocusin`:l==`onblur`&&(a=`onfocusout`):l=a=`oninput`,l==`oninput`&&r[a=l]&&(a=`oninputCapture`)}else i&&A_.test(a)?a=a.replace(j_,`-$&`).toLowerCase():o===null&&(o=void 0);r[a]=o}}n==`select`&&(r.multiple&&Array.isArray(r.value)&&(r.value=k(t.children).forEach(function(e){e.props.selected=r.value.indexOf(e.props.value)!=-1})),r.defaultValue!=null&&(r.value=k(t.children).forEach(function(e){e.props.selected=r.multiple?r.defaultValue.indexOf(e.props.value)!=-1:r.defaultValue==e.props.value}))),t.class&&!t.className?(r.class=t.class,Object.defineProperty(r,"className",F_)):t.className&&(r.class=r.className=t.className),e.props=r})(e);else if(typeof e.type==`function`&&(`ref`in e.props&&`prototype`in e.type&&e.type.prototype.render&&(e.ref=e.props.ref,delete e.props.ref),e.type.defaultProps)){var t=w_({},e.props);for(var n in e.type.defaultProps)t[n]===void 0&&(t[n]=e.type.defaultProps[n]);e.props=t}e.ref&&!(`ref`in e.props)&&Object.defineProperty(e.props,"ref",{value:e.ref,configurable:!0,writable:!0}),e.$$typeof=k_,I_&&I_(e)};var L_=8,R_=256,z_=8;function B_(e){return e.findIndex(e=>!e.disabled)}function V_(e){for(let t=e.length-1;t>=0;--t)if(!e[t]?.disabled)return t;return-1}function H_(e,t,n){if(e.length===0)return-1;let r=t;for(let t=0;te.value===t&&!e.disabled);return n>=0?n:B_(e)}function W_({ariaLabel:e,className:t=``,disabled:n=!1,id:r,onChange:i,options:a,value:o,variant:s=`default`}){let c=Ze(),l=r??`ssh-custom-select-${c}`,u=`${l}-menu`,d=z(null),f=z(null),p=z(null),m=z([]),[h,g]=L(!1),[_,v]=L(()=>U_(a,o)),[y,b]=L(`bottom`),[x,S]=L(R_),[C,w]=L({}),[T,E]=L(null),D=Xe(()=>a.find(e=>e.value===o)??a[0]??null,[a,o]),O=()=>{let e=f.current;if(!e)return;let t=e.getBoundingClientRect(),n=Math.min(R_,Math.max(44,a.length*42+12)),r=window.innerHeight-t.bottom-z_,i=t.top-z_,o=rr?`top`:`bottom`,s=Math.min(t.width,Math.max(0,window.innerWidth-16)),c=Math.max(z_,Math.min(t.left,window.innerWidth-s-z_)),l=o===`bottom`?r:i;b(o),S(Math.max(44,Math.min(R_,Math.floor(l)))),w({left:`${c}px`,width:`${s}px`,...o===`bottom`?{top:`${t.bottom+L_}px`}:{bottom:`${window.innerHeight-t.top+L_}px`}})},k=(e=!1)=>{g(!1),e&&window.requestAnimationFrame(()=>f.current?.focus())},A=e=>{e<0||(v(e),window.requestAnimationFrame(()=>m.current[e]?.focus()))},j=e=>{let t=a[e];t&&!t.disabled&&(i(t.value),g(!1),window.requestAnimationFrame(()=>f.current?.focus()))},M=e=>{n||a.length===0||(O(),g(!0),A(e??U_(a,o)))};R(()=>{let e=d.current?.closest(`.ssh-app`);if(e){E(e);return}let t=d.current?.getRootNode();t instanceof ShadowRoot?E(t):typeof document<`u`&&E(document.body)},[]),R(()=>{v(U_(a,o)),m.current=m.current.slice(0,a.length)},[a,o]),R(()=>{if(!h)return;O();let e=e=>{let t=e.composedPath();d.current&&t.includes(d.current)||p.current&&t.includes(p.current)||k()},t=e=>{e.key===`Escape`&&(e.preventDefault(),k(!0))},n=()=>O();return document.addEventListener(`pointerdown`,e),document.addEventListener(`keydown`,t),window.addEventListener(`resize`,n),window.addEventListener(`scroll`,n,!0),()=>{document.removeEventListener(`pointerdown`,e),document.removeEventListener(`keydown`,t),window.removeEventListener(`resize`,n),window.removeEventListener(`scroll`,n,!0)}},[h,a.length]),R(()=>{n&&h&&k()},[n,h]);let N=s===`emphasized`?`min-h-11 border-2 border-slate-300 bg-slate-50 px-4 py-2.5 text-sm font-semibold text-slate-950 shadow-inner focus:border-teal-500 focus:bg-white focus:ring-4 focus:ring-teal-100`:`min-h-11 border border-slate-300 bg-slate-50 px-3 py-2.5 text-sm font-bold text-slate-900 focus:border-teal-500 focus:ring-2 focus:ring-teal-100`,P=h?W(`div`,{"aria-label":`${e} 목록`,class:`ssh-custom-select-menu fixed z-[1000] overflow-hidden rounded-xl border border-slate-200 bg-white p-1.5 shadow-xl shadow-slate-950/10`,id:u,ref:p,role:`listbox`,style:C,children:W(`div`,{class:`overflow-y-auto overscroll-contain`,style:{maxHeight:`${x}px`},children:a.map((e,t)=>{let n=e.value===o;return W(`button`,{"aria-selected":n,class:`flex min-h-10 w-full items-center justify-between gap-3 rounded-lg px-3 py-2.5 text-left text-sm font-bold transition ${n?`bg-teal-50 text-teal-800`:`text-slate-700 hover:bg-slate-50`} ${e.disabled?`cursor-not-allowed opacity-50`:``}`,disabled:e.disabled,onClick:()=>j(t),onFocus:()=>v(t),onKeyDown:e=>{switch(e.key){case`ArrowDown`:e.preventDefault(),A(H_(a,t,1));break;case`ArrowUp`:e.preventDefault(),A(H_(a,t,-1));break;case`Home`:e.preventDefault(),A(B_(a));break;case`End`:e.preventDefault(),A(V_(a));break;case`Enter`:case` `:e.preventDefault(),j(t);break;case`Escape`:e.preventDefault(),k(!0);break;case`Tab`:g(!1)}},ref:e=>{m.current[t]=e},role:`option`,tabIndex:_===t?0:-1,type:`button`,children:[W(`span`,{class:`min-w-0 truncate`,children:e.label}),n?W(`span`,{"aria-hidden":`true`,class:`size-2 shrink-0 rounded-full bg-teal-500`}):null]},e.value)})})}):null;return W(`div`,{class:`relative ${t}`,ref:d,children:[W(`button`,{"aria-controls":u,"aria-expanded":h,"aria-haspopup":`listbox`,"aria-label":e,class:`ssh-custom-select-trigger flex w-full cursor-pointer items-center justify-between gap-3 rounded-xl text-left outline-none transition disabled:cursor-not-allowed disabled:bg-slate-100 disabled:text-slate-400 disabled:opacity-60 ${N}`,disabled:n,id:l,onClick:()=>{h?k():(O(),g(!0))},onKeyDown:e=>{switch(e.key){case`ArrowDown`:e.preventDefault(),M(h?H_(a,_,1):U_(a,o));break;case`ArrowUp`:e.preventDefault(),M(h?H_(a,_,-1):U_(a,o));break;case`Home`:e.preventDefault(),M(B_(a));break;case`End`:e.preventDefault(),M(V_(a));break;case`Enter`:case` `:e.preventDefault(),h?k():M();break;case`Escape`:h&&(e.preventDefault(),k())}},ref:f,type:`button`,children:[W(`span`,{class:`min-w-0 truncate`,children:D?.label??`선택하세요`}),W(An,{"aria-hidden":`true`,class:`size-4 shrink-0 text-slate-400 transition-transform ${h?`rotate-180`:``}`})]}),P&&T?O_(P,T):null,W(`span`,{class:`sr-only`,"aria-live":`polite`,children:y===`top`?`선택 목록이 위쪽에 열렸습니다.`:``})]})}var G_=[{value:`skb`,label:`SK Broadband`},{value:`lgu`,label:`LG U+`}];function K_(e){return e===`lgu`?`LG U+`:`SK Broadband`}function q_({active:e,label:t,value:n}){return W(`div`,{class:`rounded-xl border border-slate-200 bg-white p-4`,children:[W(`div`,{class:`flex items-center justify-between gap-3`,children:[W(`p`,{class:`m-0 text-xs font-extrabold text-slate-500`,children:t}),W(`span`,{class:`size-2.5 shrink-0 rounded-full ${e?`bg-emerald-500`:`bg-slate-300`}`,"aria-hidden":`true`})]}),W(`p`,{class:`mt-2 mb-0 text-sm font-black text-slate-950`,children:n})]})}function J_({data:e,error:t,feedback:n,loading:r,onDismissFeedback:i,onRetry:a,onSave:o,saving:s}){let[c,l]=L(!1),[u,d]=L(`skb`);if(R(()=>{e&&(l(e.enabled),d(e.provider))},[e]),r&&!e)return W(vs,{});if(t&&!e)return W(ys,{message:t,onRetry:a});if(!e)return W(ys,{message:`IPTV 설정을 확인할 수 없습니다.`,onRetry:a});let f=c!==e.enabled||u!==e.provider,p=!e.enabled||e.running&&e.igmpSnooping;return W(`div`,{class:`space-y-5`,children:[W(`section`,{class:`rounded-2xl border border-amber-200 bg-amber-50 p-5 sm:p-6`,children:W(`div`,{class:`flex items-start gap-4`,children:[W(`span`,{class:`grid size-11 shrink-0 place-items-center rounded-xl bg-amber-100 text-amber-800`,children:W(Cn,{class:`size-5`})}),W(`div`,{class:`min-w-0 flex-1`,children:[W(`div`,{class:`flex flex-wrap items-center gap-2`,children:[W(`h2`,{class:`m-0 text-base font-black text-amber-950`,children:`IPTV 실험 기능`}),W(`span`,{class:`rounded-full border border-amber-300 bg-amber-100 px-2 py-0.5 text-[10px] font-black uppercase tracking-[0.12em] text-amber-800`,children:`Beta`})]}),W(`p`,{class:`mt-2 mb-0 text-sm leading-6 text-amber-900/80`,children:`현재 SK Broadband와 LG U+를 지원하며 두 통신사에는 동일한 멀티캐스트 IPTV 프로파일을 적용합니다. KT IPTV는 별도 네트워크 방식이 필요해 아직 지원하지 않습니다.`})]})]})}),n?W(`section`,{"aria-live":`polite`,class:`rounded-2xl border p-4 ${n.kind===`success`?`border-emerald-200 bg-emerald-50 text-emerald-900`:`border-red-200 bg-red-50 text-red-900`}`,children:W(`div`,{class:`flex items-start justify-between gap-3`,children:[W(`p`,{class:`m-0 text-sm font-bold leading-6`,children:n.message}),W(`button`,{class:`shrink-0 text-xs font-extrabold underline underline-offset-2`,onClick:i,type:`button`,children:`닫기`})]})}):null,W(J,{eyebrow:`IPTV`,title:`IPTV 설정`,description:`통신사 환경에 맞는 멀티캐스트 중계 기능을 관리합니다.`}),W(`section`,{class:`rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5 sm:p-6`,children:[W(`div`,{class:`flex min-w-0 items-start gap-3`,children:[W(`span`,{class:`grid size-11 shrink-0 place-items-center rounded-xl bg-teal-50 text-teal-700`,children:W(un,{class:`size-5`})}),W(`div`,{class:`min-w-0 flex-1`,children:[W(`div`,{class:`flex min-w-0 flex-wrap items-center justify-between gap-2`,children:[W(`h2`,{class:`m-0 text-lg font-black text-slate-950`,children:`IPTV`}),W(`div`,{class:`flex shrink-0 items-center gap-2`,children:[f?W(`span`,{"aria-live":`polite`,class:`inline-flex shrink-0 items-center gap-1.5 rounded-full border border-amber-200 bg-amber-50 px-2.5 py-1 text-xs font-extrabold text-amber-800`,role:`status`,children:[W(Cn,{"aria-hidden":`true`,class:`size-3.5 shrink-0`}),`저장되지 않음`]}):null,W(`button`,{"aria-checked":c,"aria-label":`IPTV 사용`,class:`ssh-switch-control relative inline-flex shrink-0 rounded-full border-0 transition focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-100 ${c?`bg-teal-700`:`bg-slate-300`} disabled:cursor-not-allowed disabled:opacity-60`,disabled:s||!e.available,onClick:()=>l(e=>!e),role:`switch`,type:`button`,children:W(`span`,{"aria-hidden":`true`,class:`ssh-switch-thumb absolute top-1 shadow-sm transition-[left] ${c?`left-6`:`left-1`}`})})]})]}),W(`p`,{class:`mt-1 mb-0 text-sm leading-6 text-slate-600`,children:`WAN의 멀티캐스트 방송을 LAN 셋톱박스로 전달합니다.`})]})]}),W(`div`,{class:`mt-6 grid gap-5 lg:grid-cols-[minmax(0,1fr)_minmax(0,1fr)]`,children:[W(`div`,{class:`rounded-xl border border-slate-200 bg-slate-50 p-4`,children:[W(`div`,{class:`flex flex-wrap items-start justify-between gap-2`,children:[W(`div`,{children:[W(`label`,{class:`block text-xs font-extrabold text-slate-600`,htmlFor:`iptv-provider`,children:`통신사`}),W(`p`,{class:`mt-1 mb-0 text-xs leading-5 text-slate-500`,children:`IPTV를 제공하는 회선 사업자를 선택하세요.`})]}),W(`span`,{class:`rounded-full border border-teal-200 bg-teal-50 px-2.5 py-1 text-[10px] font-black text-teal-700`,children:`멀티캐스트 방식`})]}),W(W_,{ariaLabel:`IPTV 통신사`,className:`mt-3`,disabled:s||!c,id:`iptv-provider`,onChange:e=>d(e),options:G_,value:u}),W(`div`,{class:`mt-3 flex items-start gap-2 rounded-lg border border-slate-200 bg-white p-3`,children:[W(Sn,{"aria-hidden":`true`,class:`mt-0.5 size-4 shrink-0 text-teal-700`}),W(`p`,{class:`m-0 text-xs leading-5 text-slate-600`,children:[K_(u),`에는 현재 SK Broadband/LG U+ 공통 IGMP Proxy 프로파일을 적용합니다. KT는 추후 별도 IPTV 프로파일로 지원할 예정입니다.`]})]})]}),W(`div`,{class:`rounded-xl border border-slate-200 bg-slate-50 p-4`,children:[W(`div`,{class:`flex items-center justify-between gap-3`,children:[W(`p`,{class:`m-0 text-xs font-extrabold text-slate-500`,children:`현재 지원 프로파일`}),W(`span`,{class:`text-xs font-extrabold text-slate-500`,children:`SKB · LG U+`})]}),W(`p`,{class:`mt-2 mb-0 text-sm font-black text-slate-950`,children:`WAN → IGMP Proxy → LAN`}),W(`p`,{class:`mt-1 mb-0 text-xs leading-5 text-slate-600`,children:`WAN을 upstream, LAN을 downstream으로 사용하고 LAN bridge에는 IGMP snooping을 함께 활성화해 필요하지 않은 포트와 Wi-Fi로 멀티캐스트가 불필요하게 퍼지는 것을 줄입니다.`})]})]}),e.available?null:W(`div`,{class:`mt-5 rounded-xl border border-red-200 bg-red-50 p-4 text-sm font-bold leading-6 text-red-900`,children:`IGMP Proxy 패키지를 찾지 못했습니다. 현재 펌웨어에 igmpproxy 패키지가 포함되어 있는지 확인해 주세요.`}),W(`div`,{class:`mt-6 flex flex-wrap items-center justify-between gap-3 border-t border-slate-200 pt-5`,children:[W(`p`,{class:`m-0 text-xs leading-5 text-slate-500`,children:`설정을 적용하면 네트워크 서비스가 잠시 다시 로드될 수 있습니다.`}),W(`button`,{class:`inline-flex min-h-11 items-center justify-center rounded-xl border-0 bg-teal-700 px-5 py-2.5 text-sm font-black text-white shadow-sm transition hover:bg-teal-800 focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-100 disabled:cursor-not-allowed disabled:opacity-50`,disabled:s||!e.available||!f&&!c,onClick:()=>void o({enabled:c,provider:u}),type:`button`,children:s?`적용 중…`:f?`변경사항 적용`:`설정 다시 적용`})]})]}),W(J,{eyebrow:`Runtime`,title:`IPTV 동작 상태`,description:`마지막으로 적용한 IPTV 설정과 중계 서비스의 실행 상태를 확인합니다.`}),W(`section`,{class:`rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5 sm:p-6`,children:[W(`div`,{class:`flex flex-wrap items-center justify-between gap-3`,children:[W(`p`,{class:`m-0 text-xs leading-5 text-slate-500`,children:`적용된 IPTV 중계 서비스와 멀티캐스트 전달 상태입니다.`}),W(`span`,{class:`inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-black ${p?`bg-emerald-100 text-emerald-800`:`bg-amber-100 text-amber-800`}`,children:[W(Sn,{class:`size-4`}),p?e.enabled?`정상`:`꺼짐`:`확인 필요`]})]}),f?W(`div`,{class:`mt-4 flex items-start gap-2 rounded-xl border border-amber-200 bg-amber-50 p-3 text-xs font-bold leading-5 text-amber-900`,children:[W(Cn,{"aria-hidden":`true`,class:`mt-0.5 size-4 shrink-0`}),`아래 동작 상태는 마지막으로 적용된 설정 기준입니다. 변경사항을 적용하면 새 설정으로 갱신됩니다.`]}):null,W(`div`,{class:`mt-5 grid gap-3 sm:grid-cols-2 lg:grid-cols-4`,children:[W(q_,{active:e.enabled&&e.running,label:`IGMP Proxy`,value:e.enabled?e.running?`동작 중`:`중지됨`:`꺼짐`}),W(q_,{active:e.enabled&&e.igmpSnooping,label:`IGMP Snooping`,value:e.igmpSnooping?`켜짐`:`꺼짐`}),W(q_,{active:e.enabled,label:`Upstream`,value:e.upstreamNetwork.toUpperCase()}),W(q_,{active:e.enabled,label:`Downstream`,value:e.lanBridge??e.downstreamNetwork.toUpperCase()})]})]})]})}function Y_(e){return e?`disabled:cursor-wait`:`disabled:cursor-not-allowed`}function X_(e){let t=e.split(`.`);return[t[0]??``,t[1]??``,t[2]??``,t[3]??``]}function Z_(e){return e.replace(/\D/g,``).slice(0,3)}function Q_(e){return e.every(e=>e===``)?``:e.join(`.`)}function $_({disabled:e=!1,label:t,value:n,onChange:r}){let i=X_(n),a=(e,t)=>{let n=[...i];n[e]=Z_(t),r(Q_(n))};return W(`div`,{class:`ssh-ipv4-segments mt-2 min-h-11`,"aria-label":`${t} 입력`,children:i.map((n,r)=>W(`div`,{class:`ssh-ipv4-segment`,children:[W(`input`,{"aria-label":`${t} ${r+1}번째 옥텟`,autocomplete:`off`,class:`min-h-11 w-full min-w-0 max-w-full rounded-xl border-2 border-slate-300 bg-slate-50 px-1.5 py-2.5 text-center text-sm font-semibold text-slate-950 shadow-inner outline-none transition focus:border-teal-500 focus:bg-white focus:ring-4 focus:ring-teal-100 disabled:cursor-not-allowed disabled:bg-slate-100 sm:px-2`,disabled:e,inputMode:`numeric`,maxLength:3,onInput:e=>a(r,e.currentTarget.value),pattern:`[0-9]*`,spellcheck:!1,value:n}),r{if(!/^(0|[1-9][0-9]{0,2})$/.test(e))return!1;let t=Number(e);return t>=0&&t<=255})}function rv(e){let t=X_(e);return t.slice(0,3).some(e=>e===``||Number(e)>255)?null:t.slice(0,3).join(`.`)}function iv(e){return X_(e)[3]}function av(e){let t=rv(e.lan.address),n=iv(e.dhcp.start??``),r=iv(e.dhcp.end??``);return{ipAddress:e.lan.address,prefixLength:e.lan.prefixLength,dhcpEnabled:e.dhcp.enabled,dhcpStart:t&&n?`${t}.${n}`:e.dhcp.start??``,dhcpEnd:t&&r?`${t}.${r}`:e.dhcp.end??``,leaseTime:e.dhcp.leaseTime}}function ov({disabled:e,label:t,lanAddress:n,value:r,onChange:i}){let a=rv(n),o=a?.split(`.`)??[``,``,``],s=iv(r),c=e=>{let t=Z_(e);i(a&&t!==``?`${a}.${t}`:t)};return W(`label`,{class:`block`,children:[W(`span`,{class:`text-sm font-extrabold text-slate-800`,children:t}),W(`div`,{class:`ssh-ipv4-segments mt-2 min-h-11`,"aria-label":`${t} 입력`,children:[o.map((e,t)=>W(`div`,{class:`ssh-ipv4-segment`,children:[W(`span`,{class:`grid min-h-11 w-full min-w-0 max-w-full place-items-center rounded-xl border border-slate-200 bg-slate-100 px-1.5 py-2.5 text-center text-sm font-bold text-slate-500 sm:px-2`,children:e||`—`}),W(`span`,{"aria-hidden":`true`,class:`ssh-ipv4-separator text-sm font-black text-slate-400`,children:`.`})]},t)),W(`div`,{class:`ssh-ipv4-segment`,children:W(`input`,{"aria-label":`${t} 마지막 옥텟`,autocomplete:`off`,class:`min-h-11 w-full min-w-0 max-w-full rounded-xl border-2 border-slate-300 bg-slate-50 px-1.5 py-2.5 text-center text-sm font-semibold text-slate-950 shadow-inner outline-none transition focus:border-teal-500 focus:bg-white focus:ring-4 focus:ring-teal-100 disabled:cursor-not-allowed disabled:bg-slate-100 sm:px-2`,disabled:e||!a,inputMode:`numeric`,maxLength:3,onInput:e=>c(e.currentTarget.value),pattern:`[0-9]*`,spellcheck:!1,value:s})})]}),W(`span`,{class:`mt-2 block text-xs text-slate-500`,children:`앞 3개 주소는 공유기 IP 주소와 동일하게 유지됩니다.`})]})}function sv(e){return`${window.location.protocol}//${e}${window.location.pathname}${window.location.search}${window.location.hash}`}function cv({feedback:e,onDismiss:t}){return W(`div`,{"aria-live":`polite`,class:`mb-5 rounded-2xl border px-4 py-4 text-sm ${e.kind===`success`?`border-emerald-200 bg-emerald-50 text-emerald-900`:e.kind===`warning`?`border-amber-200 bg-amber-50 text-amber-950`:`border-red-200 bg-red-50 text-red-900`}`,children:[W(`div`,{class:`flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between`,children:[W(`p`,{class:`m-0 font-bold leading-6`,children:e.message}),W(`button`,{class:`min-h-10 shrink-0 rounded-lg border border-current bg-transparent px-3 py-1.5 text-xs font-extrabold`,onClick:t,type:`button`,children:`닫기`})]}),e.newAddress?W(`a`,{class:`mt-3 inline-flex min-h-10 items-center rounded-lg border border-current px-3 py-2 font-extrabold underline underline-offset-2`,href:sv(e.newAddress),children:[`새 공유기 주소 `,e.newAddress,`로 연결`]}):null]})}function lv({data:e}){let t=e.conflict.detected;return W(`section`,{class:`grid gap-4 lg:grid-cols-3`,"aria-label":`내부 네트워크 상태`,children:[W(`article`,{class:`rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5`,children:[W(`span`,{class:`grid size-10 place-items-center rounded-xl bg-teal-50 text-teal-700`,children:W(Jt,{class:`size-5`})}),W(`p`,{class:`mt-4 mb-0 text-xs font-extrabold uppercase tracking-[0.14em] text-slate-500`,children:`SmartSafeHub LAN`}),W(`p`,{class:`mt-2 mb-0 text-xl font-black text-slate-950`,children:e.lan.subnet}),W(`p`,{class:`mt-1 mb-0 text-sm font-semibold text-slate-600`,children:[`공유기 주소 `,e.lan.address]})]}),W(`article`,{class:`rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5`,children:[W(`span`,{class:`grid size-10 place-items-center rounded-xl bg-slate-100 text-slate-700`,children:W(ln,{class:`size-5`})}),W(`p`,{class:`mt-4 mb-0 text-xs font-extrabold uppercase tracking-[0.14em] text-slate-500`,children:`상위 네트워크`}),W(`p`,{class:`mt-2 mb-0 text-xl font-black text-slate-950`,children:e.wan.subnet??`확인 전`}),W(`p`,{class:`mt-1 mb-0 text-sm font-semibold text-slate-600`,children:e.wan.connected?`WAN ${e.wan.address??`주소 확인 중`}`:`WAN 연결 후 자동으로 확인합니다.`})]}),W(`article`,{class:`rounded-2xl border p-5 shadow-sm shadow-slate-900/5 ${t?`border-red-200 bg-red-50 text-red-950`:`border-emerald-200 bg-emerald-50 text-emerald-950`}`,children:[W(`span`,{class:`grid size-10 place-items-center rounded-xl ${t?`bg-red-100 text-red-700`:`bg-emerald-100 text-emerald-700`}`,children:W(t?Cn:Sn,{class:`size-5`})}),W(`p`,{class:`mt-4 mb-0 text-xs font-extrabold uppercase tracking-[0.14em] opacity-70`,children:`대역 충돌`}),W(`p`,{class:`mt-2 mb-0 text-lg font-black`,children:t?`주소 대역이 겹칩니다`:`충돌이 감지되지 않았습니다`}),W(`p`,{class:`mt-1 mb-0 text-sm font-semibold leading-6 opacity-80`,children:t?`상위 네트워크 ${e.conflict.wanSubnet??``}와 다른 LAN 대역이 필요합니다.`:e.wan.connected?`현재 WAN과 LAN이 서로 다른 IPv4 대역을 사용합니다.`:`WAN이 연결되면 상위 네트워크와의 충돌 여부를 확인합니다.`})]})]})}function uv({action:e,data:t,error:n,feedback:r,loading:i,onApplyRecommendation:a,onDismissFeedback:o,onRetry:s,onSave:c}){let[l,u]=L(``),[d,f]=L(24),[p,m]=L(!0),[h,g]=L(``),[_,v]=L(``),[y,x]=L(`12h`),[S,C]=L(!1),[w,T]=L(null),E=(e={})=>{if(!t)return!1;let n=av(t);return(e.ipAddress??l).trim()!==n.ipAddress||(e.prefixLength??d)!==n.prefixLength||(e.dhcpEnabled??p)!==n.dhcpEnabled||(e.dhcpStart??h).trim()!==n.dhcpStart||(e.dhcpEnd??_).trim()!==n.dhcpEnd||(e.leaseTime??y)!==n.leaseTime},D=e=>{let t=rv(e);if(u(e),!t){C(E({ipAddress:e}));return}let n=iv(h),r=iv(_),i=n?`${t}.${n}`:``,a=r?`${t}.${r}`:``;g(i),v(a),C(E({ipAddress:e,dhcpStart:i,dhcpEnd:a}))},O=e=>{g(e),C(E({dhcpStart:e}))},k=e=>{v(e),C(E({dhcpEnd:e}))},A=e=>{f(e),C(E({prefixLength:e}))},j=e=>{x(e),C(E({leaseTime:e}))},M=e=>{m(e),C(E({dhcpEnabled:e}))};R(()=>{if(!t||S)return;let e=av(t);u(e.ipAddress),f(e.prefixLength),m(e.dhcpEnabled),g(e.dhcpStart),v(e.dhcpEnd),x(e.leaseTime),T(null)},[t,S]);let N=Xe(()=>{let e=[...ev];return e.includes(d)||(e.push(d),e.sort((e,t)=>e-t)),e},[d]),P=Xe(()=>{let e=[...tv];return e.some(([e])=>e===y)||e.push([y,`현재 설정 (${y})`]),e},[y]);if(i)return W(vs,{});if(n)return W(ys,{message:n,onRetry:s});if(!t)return null;let F=e!==null,I=async e=>{e.preventDefault(),nv(l)?!nv(h)||!nv(_)?T(`DHCP 시작/종료 주소를 올바른 IPv4 형식으로 입력해 주세요.`):window.confirm(`내부 네트워크 설정을 적용하면 연결된 기기가 새 DHCP 정보를 받는 동안 잠시 연결이 끊길 수 있습니다. 계속하시겠습니까?`)&&(T(null),await c({ipAddress:l.trim(),prefixLength:d,dhcpEnabled:p,dhcpStart:h.trim(),dhcpEnd:_.trim(),leaseTime:y})&&C(!1)):T(`공유기 주소를 올바른 IPv4 형식으로 입력해 주세요.`)},ee=async()=>{t.recommendation&&window.confirm(`상위 네트워크와 겹치지 않는 ${t.recommendation.subnet} 대역으로 자동 변경합니다. 현재 연결이 잠시 끊길 수 있습니다. 계속하시겠습니까?`)&&(T(null),await a()&&C(!1))};return W(b,{children:[r?W(cv,{feedback:r,onDismiss:o}):null,W(J,{eyebrow:`Overview`,title:`내부 네트워크 상태`,description:`LAN 주소와 DHCP 할당 상태를 확인합니다.`}),W(lv,{data:t}),t.conflict.detected?W(`section`,{class:`mt-5 rounded-2xl border border-amber-200 bg-amber-50 p-5 text-amber-950 sm:p-6`,children:W(`div`,{class:`flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between`,children:[W(`div`,{class:`min-w-0`,children:[W(`p`,{class:`m-0 text-xs font-extrabold uppercase tracking-[0.16em] text-amber-700`,children:`자동 충돌 해결`}),W(`h2`,{class:`mt-2 mb-0 text-lg font-black`,children:`안전한 LAN 대역으로 변경할 수 있습니다`}),W(`p`,{class:`mt-2 mb-0 text-sm font-semibold leading-6 text-amber-900/80`,children:t.recommendation?`${t.recommendation.subnet} · DHCP ${t.recommendation.dhcpStart} ~ ${t.recommendation.dhcpEnd}`:`현재 활성 네트워크와 겹치지 않는 추천 대역을 찾지 못했습니다. 아래에서 직접 설정해 주세요.`})]}),t.recommendation?W(`button`,{class:`inline-flex min-h-11 shrink-0 items-center justify-center gap-2 rounded-xl border-0 bg-amber-700 px-5 py-2.5 text-sm font-extrabold text-white transition hover:bg-amber-800 focus:outline-none focus-visible:ring-4 focus-visible:ring-amber-200 disabled:cursor-wait disabled:opacity-60`,disabled:F,onClick:()=>void ee(),type:`button`,children:[W(Sn,{class:`size-5 ${e===`auto`?`animate-pulse`:``}`}),e===`auto`?`변경 중`:`추천 대역으로 자동 변경`]}):null]})}):null,W(`div`,{class:`mt-7`,children:W(J,{eyebrow:`Local network`,title:`LAN 및 DHCP 설정`,description:`상위 공유기와 다른 주소 대역을 사용해야 안정적으로 라우팅할 수 있습니다.`})}),W(`form`,{class:`rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5 sm:p-6`,onSubmit:e=>void I(e),children:[S?W(`div`,{class:`mb-5 flex justify-end`,children:W(`span`,{"aria-live":`polite`,class:`inline-flex shrink-0 self-start items-center gap-1.5 rounded-full border border-amber-200 bg-amber-50 px-2.5 py-1 text-xs font-extrabold text-amber-800`,role:`status`,children:[W(Cn,{"aria-hidden":`true`,class:`size-3.5 shrink-0`}),`저장되지 않음`]})}):null,W(`div`,{class:`grid gap-5 lg:grid-cols-2`,children:[W(`div`,{class:`block`,children:[W(`span`,{class:`text-sm font-extrabold text-slate-800`,children:`공유기 IP 주소`}),W($_,{disabled:F,label:`공유기 IP 주소`,onChange:D,value:l})]}),W(`div`,{class:`block`,children:[W(`span`,{class:`text-sm font-extrabold text-slate-800`,children:`현재 LAN 대역`}),W(`div`,{class:`mt-2 min-h-11 rounded-xl border border-slate-200 bg-slate-50 px-4 py-3 text-sm font-bold text-slate-700`,children:t.lan.subnet}),W(`span`,{class:`mt-2 block text-xs text-slate-500`,children:`서브넷 마스크는 아래 고급 설정에서 변경할 수 있습니다.`})]}),W(ov,{disabled:F,label:`DHCP 시작 주소`,lanAddress:l,onChange:O,value:h}),W(ov,{disabled:F,label:`DHCP 종료 주소`,lanAddress:l,onChange:k,value:_})]}),W(`details`,{class:`mt-6 rounded-xl border border-slate-200 bg-slate-50 p-4`,children:[W(`summary`,{class:`cursor-pointer text-sm font-black text-slate-900`,children:`고급 DHCP 설정`}),W(`div`,{class:`mt-5 grid gap-5 lg:grid-cols-2`,children:[W(`div`,{class:`block`,children:[W(`span`,{class:`text-sm font-extrabold text-slate-800`,children:`서브넷 마스크`}),W(W_,{ariaLabel:`서브넷 마스크`,className:`mt-2`,disabled:F,id:`lan-prefix-length`,onChange:e=>A(Number(e)),options:N.map(e=>({value:String(e),label:`/${e} · ${e===t.lan.prefixLength?t.lan.netmask:`CIDR /${e}`}`})),value:String(d),variant:`emphasized`}),W(`span`,{class:`mt-2 block text-xs text-slate-500`,children:`일반 가정용 네트워크는 /24 사용을 권장합니다.`})]}),W(`div`,{class:`block`,children:[W(`span`,{class:`text-sm font-extrabold text-slate-800`,children:`DHCP 임대 시간`}),W(W_,{ariaLabel:`DHCP 임대 시간`,className:`mt-2`,disabled:F,id:`lan-dhcp-lease-time`,onChange:j,options:P.map(([e,t])=>({value:e,label:t})),value:y,variant:`emphasized`})]}),W(`label`,{class:`inline-flex min-h-11 items-center gap-3 text-sm font-extrabold text-slate-800 lg:col-span-2`,children:[W(`input`,{checked:p,class:`size-5 accent-teal-700`,disabled:F,onChange:e=>M(e.currentTarget.checked),type:`checkbox`}),`SmartSafeHub DHCP 서버 사용`]}),W(`p`,{class:`m-0 text-xs font-semibold leading-5 text-slate-500 lg:col-span-2`,children:`DHCP를 끄면 SmartSafeHub에 연결되는 장치에 IP 주소를 자동으로 할당하지 않습니다. 별도의 DHCP 서버를 사용하는 고급 구성에서만 비활성화하세요.`})]})]}),w?W(`p`,{class:`mt-5 mb-0 rounded-xl bg-red-50 px-4 py-3 text-sm font-bold text-red-800`,children:w}):null,W(`div`,{class:`mt-6 flex flex-col gap-3 border-t border-slate-200 pt-5 sm:flex-row sm:items-center sm:justify-between`,children:[W(`p`,{class:`m-0 text-xs font-semibold leading-5 text-slate-500`,children:`주소가 바뀌면 현재 관리 페이지 연결이 종료되며 새 IP를 받아 다시 접속해야 합니다.`}),W(`button`,{class:`inline-flex min-h-11 w-full shrink-0 items-center justify-center gap-2 rounded-xl border-0 bg-teal-700 px-5 py-2.5 text-sm font-extrabold text-white shadow-sm transition hover:bg-teal-800 focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-200 ${Y_(F)} disabled:opacity-60 sm:w-auto`,disabled:F||!S,type:`submit`,children:[W(Sn,{class:`size-5 ${e===`saving`?`animate-pulse`:``}`}),e===`saving`?`저장 중`:`설정 저장`]})]})]})]})}var dv=[`dhcp`,`pppoe`,`static`],fv=`1.1.1.1`,pv=Array.from({length:33},(e,t)=>t);function mv(e){return dv.includes(e)}function hv(e){switch(e){case`dhcp`:return`자동 IP (DHCP)`;case`pppoe`:return`PPPoE`;case`static`:return`고정 IPv4`;default:return e?e.toUpperCase():`확인 전`}}function gv(e){return{protocol:e.configuration.protocol,pppoeUsername:e.configuration.pppoe.username,pppoePassword:``,staticAddress:e.configuration.static.address??``,staticPrefixLength:e.configuration.static.prefixLength??24,staticGateway:e.configuration.static.gateway??``,dnsPrimary:e.configuration.static.dns[0]??``,dnsSecondary:e.configuration.static.dns[1]??``}}function _v(e){return e.trim()}function vv(e,t){let n=gv(t);return e.protocol===n.protocol?e.protocol===`dhcp`||!mv(e.protocol)?!1:e.protocol===`pppoe`?_v(e.pppoeUsername)!==n.pppoeUsername||e.pppoePassword.length>0:_v(e.staticAddress)!==n.staticAddress||e.staticPrefixLength!==n.staticPrefixLength||_v(e.staticGateway)!==n.staticGateway||_v(e.dnsPrimary)!==n.dnsPrimary||_v(e.dnsSecondary)!==n.dnsSecondary:!0}function yv(e){let t=e.trim().split(`.`);return t.length===4&&t.every(e=>{if(!/^(0|[1-9][0-9]{0,2})$/.test(e))return!1;let t=Number(e);return t>=0&&t<=255})}function bv(e){let t=e;return Array.from({length:4},()=>{if(t>=8)return t-=8,255;if(t<=0)return 0;let e=256-2**(8-t);return t=0,e}).join(`.`)}function xv(e){if(!Number.isFinite(e)||e<=0)return`연결 시간 확인 전`;let t=Math.floor(e/86400),n=Math.floor(e%86400/3600),r=Math.floor(e%3600/60);return t>0?`${t}일 ${n}시간 연결됨`:n>0?`${n}시간 ${r}분 연결됨`:`${Math.max(1,r)}분 연결됨`}function Sv({feedback:e,onDismiss:t}){return W(`div`,{"aria-live":`polite`,class:`mb-5 rounded-2xl border px-4 py-4 text-sm ${e.kind===`success`?`border-emerald-200 bg-emerald-50 text-emerald-900`:e.kind===`warning`?`border-amber-200 bg-amber-50 text-amber-950`:`border-red-200 bg-red-50 text-red-900`}`,children:W(`div`,{class:`flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between`,children:[W(`p`,{class:`m-0 font-bold leading-6`,children:e.message}),W(`button`,{class:`min-h-10 shrink-0 rounded-lg border border-current bg-transparent px-3 py-1.5 text-xs font-extrabold`,onClick:t,type:`button`,children:`닫기`})]})})}function Cv({data:e,busy:t,onReconnect:n}){let r=e.status,i=r.connected;return W(`section`,{class:`grid gap-4 lg:grid-cols-3`,"aria-label":`인터넷 연결 상태`,children:[W(`article`,{class:`rounded-2xl border p-5 shadow-sm shadow-slate-900/5 ${i?`border-emerald-200 bg-emerald-50 text-emerald-950`:`border-amber-200 bg-amber-50 text-amber-950`}`,children:[W(`div`,{class:`flex items-start justify-between gap-3`,children:[W(`span`,{class:`grid size-10 place-items-center rounded-xl ${i?`bg-emerald-100 text-emerald-700`:`bg-amber-100 text-amber-700`}`,children:W(i?Sn:Cn,{class:`size-5`})}),W(`button`,{class:`inline-flex min-h-10 items-center justify-center gap-2 rounded-xl border border-current bg-transparent px-3 py-2 text-xs font-extrabold transition disabled:cursor-wait disabled:opacity-60`,disabled:t,onClick:n,type:`button`,children:[W(pn,{class:`size-4 ${t?`animate-spin`:``}`}),`재연결`]})]}),W(`p`,{class:`mt-4 mb-0 text-xs font-extrabold uppercase tracking-[0.14em] opacity-70`,children:`인터넷 상태`}),W(`p`,{class:`mt-2 mb-0 text-xl font-black`,children:r.pending?`연결 중`:i?`연결됨`:`연결 안 됨`}),W(`p`,{class:`mt-1 mb-0 text-sm font-semibold leading-6 opacity-80`,children:[hv(r.protocol??e.configuration.protocol),` · `,xv(r.uptimeSeconds)]})]}),W(`article`,{class:`rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5`,children:[W(`span`,{class:`grid size-10 place-items-center rounded-xl bg-teal-50 text-teal-700`,children:W(ln,{class:`size-5`})}),W(`p`,{class:`mt-4 mb-0 text-xs font-extrabold uppercase tracking-[0.14em] text-slate-500`,children:`WAN IPv4`}),W(`p`,{class:`mt-2 mb-0 break-all text-xl font-black text-slate-950`,children:r.address?`${r.address}${r.prefixLength===null?``:`/${r.prefixLength}`}`:`주소 없음`}),W(`p`,{class:`mt-1 mb-0 text-sm font-semibold text-slate-600`,children:r.device?`인터페이스 ${r.device}`:`인터페이스 확인 전`})]}),W(`article`,{class:`rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5`,children:[W(`span`,{class:`grid size-10 place-items-center rounded-xl bg-slate-100 text-slate-700`,children:W(Jt,{class:`size-5`})}),W(`p`,{class:`mt-4 mb-0 text-xs font-extrabold uppercase tracking-[0.14em] text-slate-500`,children:`Gateway · DNS`}),W(`p`,{class:`mt-2 mb-0 break-all text-base font-black text-slate-950`,children:r.gateway??`Gateway 확인 전`}),W(`p`,{class:`mt-1 mb-0 break-all text-sm font-semibold leading-6 text-slate-600`,children:r.dns.length>0?r.dns.join(` · `):`DNS 확인 전`})]})]})}function wv({action:e,data:t,error:n,feedback:r,loading:i,onDismissFeedback:a,onReconnect:o,onRetry:s,onSave:c}){let[l,u]=L({protocol:`dhcp`,pppoeUsername:``,pppoePassword:``,staticAddress:``,staticPrefixLength:24,staticGateway:``,dnsPrimary:``,dnsSecondary:``}),[d,f]=L(!1),[p,m]=L(!1),[h,g]=L(null);R(()=>{t&&!d&&(u(gv(t)),g(null))},[t,d]);let _=Xe(()=>{let e=[{value:`dhcp`,label:`자동 IP (DHCP)`},{value:`pppoe`,label:`PPPoE`},{value:`static`,label:`고정 IPv4`}];return t&&!t.configuration.supported&&e.unshift({value:t.configuration.protocol,label:`현재 설정 (${t.configuration.protocol})`}),e},[t]);if(i)return W(vs,{});if(n)return W(ys,{message:n,onRetry:s});if(!t)return null;let v=e!==null,y=e=>{u(n=>{let r={...n,...e};return f(vv(r,t)),r}),g(null)},x=async e=>{if(e.preventDefault(),!mv(l.protocol))g(`DHCP, PPPoE 또는 고정 IPv4 중 하나를 선택해 주세요.`);else{if(l.protocol===`pppoe`){if(!_v(l.pppoeUsername)){g(`PPPoE 사용자명을 입력해 주세요.`);return}if((t.configuration.protocol!==`pppoe`||!t.configuration.pppoe.passwordConfigured)&&l.pppoePassword.length===0){g(`PPPoE 연결을 시작하려면 비밀번호를 입력해 주세요.`);return}}if(l.protocol===`static`){if(!yv(l.staticAddress)){g(`고정 IPv4 주소를 올바르게 입력해 주세요.`);return}if(!yv(l.staticGateway)){g(`기본 게이트웨이를 올바르게 입력해 주세요.`);return}if(!yv(l.dnsPrimary)){g(`기본 DNS 서버를 올바르게 입력해 주세요.`);return}if(_v(l.dnsSecondary)&&!yv(l.dnsSecondary)){g(`보조 DNS 서버를 올바르게 입력해 주세요.`);return}}window.confirm(`인터넷 연결 설정을 적용하면 WAN 연결이 다시 시작되어 인터넷이 잠시 끊길 수 있습니다. 계속하시겠습니까?`)&&await c({protocol:l.protocol,pppoeUsername:_v(l.pppoeUsername),pppoePassword:l.pppoePassword,pppoePasswordChanged:l.pppoePassword.length>0,staticAddress:_v(l.staticAddress),staticPrefixLength:l.staticPrefixLength,staticGateway:_v(l.staticGateway),dnsPrimary:_v(l.dnsPrimary),dnsSecondary:_v(l.dnsSecondary)})&&(u(e=>({...e,pppoePassword:``})),f(!1))}};return W(b,{children:[r?W(Sv,{feedback:r,onDismiss:a}):null,W(J,{eyebrow:`Overview`,title:`인터넷 연결 상태`,description:`WAN 연결 상태와 네트워크 정보를 확인합니다.`}),W(Cv,{data:t,busy:v,onReconnect:()=>{window.confirm(`WAN 연결을 다시 시작합니다. 인터넷이 잠시 끊길 수 있습니다. 계속하시겠습니까?`)&&o()}}),t.configuration.supported?null:W(`section`,{class:`mt-5 rounded-2xl border border-amber-200 bg-amber-50 p-5 text-amber-950 sm:p-6`,children:W(`div`,{class:`flex items-start gap-3`,children:[W(Cn,{class:`mt-0.5 size-5 shrink-0 text-amber-700`}),W(`div`,{children:[W(`h2`,{class:`m-0 text-base font-black`,children:`현재 WAN 방식은 직접 관리하지 않습니다`}),W(`p`,{class:`mt-2 mb-0 text-sm font-semibold leading-6 text-amber-900/80`,children:[`현재 설정은 `,t.configuration.protocol,`입니다. 아래에서 DHCP, PPPoE 또는 고정 IPv4를 선택해 저장하면 해당 방식으로 전환합니다.`]})]})]})}),W(`div`,{class:`mt-7`,children:W(J,{eyebrow:`Internet`,title:`인터넷 연결 설정`,description:`통신사 또는 상위 네트워크 환경에 맞는 WAN 연결 방식을 선택합니다.`})}),W(`form`,{class:`rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5 sm:p-6`,onSubmit:e=>void x(e),children:[d?W(`div`,{class:`mb-5 flex justify-end`,children:W(`span`,{"aria-live":`polite`,class:`inline-flex shrink-0 self-start items-center gap-1.5 rounded-full border border-amber-200 bg-amber-50 px-2.5 py-1 text-xs font-extrabold text-amber-800`,role:`status`,children:[W(Cn,{"aria-hidden":`true`,class:`size-3.5 shrink-0`}),`저장되지 않음`]})}):null,W(`div`,{children:[W(`span`,{class:`text-sm font-extrabold text-slate-800`,children:`연결 방식`}),W(W_,{ariaLabel:`WAN 연결 방식`,className:`mt-2 max-w-xl`,disabled:v,id:`wan-protocol`,onChange:e=>y(e===`static`&&!_v(l.dnsSecondary)?{protocol:e,dnsSecondary:fv}:{protocol:e}),options:_,value:l.protocol,variant:`emphasized`}),W(`span`,{class:`mt-2 block text-xs font-semibold leading-5 text-slate-500`,children:`대부분의 가정용 인터넷과 상위 공유기 연결은 자동 IP(DHCP)를 사용합니다.`})]}),l.protocol===`pppoe`?W(`div`,{class:`mt-6 grid gap-5 lg:grid-cols-2`,children:[W(`label`,{class:`block`,children:[W(`span`,{class:`text-sm font-extrabold text-slate-800`,children:`PPPoE 사용자명`}),W(`input`,{autocomplete:`username`,class:`mt-2 min-h-11 w-full rounded-xl border-2 border-slate-300 bg-slate-50 px-4 py-2.5 text-sm font-semibold text-slate-950 shadow-inner outline-none transition focus:border-teal-500 focus:bg-white focus:ring-4 focus:ring-teal-100 disabled:cursor-not-allowed disabled:bg-slate-100`,disabled:v,onInput:e=>y({pppoeUsername:e.currentTarget.value}),spellcheck:!1,value:l.pppoeUsername})]}),W(`label`,{class:`block`,children:[W(`span`,{class:`text-sm font-extrabold text-slate-800`,children:`PPPoE 비밀번호`}),W(`span`,{class:`relative mt-2 block`,children:[W(`input`,{autocomplete:`new-password`,class:`min-h-11 w-full rounded-xl border-2 border-slate-300 bg-slate-50 px-4 py-2.5 pr-12 text-sm font-semibold text-slate-950 shadow-inner outline-none transition focus:border-teal-500 focus:bg-white focus:ring-4 focus:ring-teal-100 disabled:cursor-not-allowed disabled:bg-slate-100`,disabled:v,onInput:e=>y({pppoePassword:e.currentTarget.value}),placeholder:t.configuration.protocol===`pppoe`&&t.configuration.pppoe.passwordConfigured?`변경할 때만 입력`:`PPPoE 비밀번호`,...p?{type:`text`}:{type:`password`},value:l.pppoePassword}),W(`button`,{"aria-label":p?`비밀번호 숨기기`:`비밀번호 표시`,class:`absolute inset-y-0 right-1 grid w-10 place-items-center rounded-lg border-0 bg-transparent text-slate-500`,disabled:v,onClick:()=>m(e=>!e),type:`button`,children:W(p?bn:yn,{class:`size-5`})})]}),W(`span`,{class:`mt-2 block text-xs text-slate-500`,children:t.configuration.protocol===`pppoe`&&t.configuration.pppoe.passwordConfigured?`비워 두면 현재 저장된 비밀번호를 그대로 사용합니다.`:`통신사에서 제공한 PPPoE 비밀번호를 입력해 주세요.`})]})]}):null,l.protocol===`static`?W(`div`,{class:`mt-6 grid gap-5 lg:grid-cols-2`,children:[W(`label`,{class:`block`,children:[W(`span`,{class:`text-sm font-extrabold text-slate-800`,children:`IPv4 주소`}),W($_,{disabled:v,label:`IPv4 주소`,onChange:e=>y({staticAddress:e}),value:l.staticAddress})]}),W(`div`,{class:`block`,children:[W(`span`,{class:`text-sm font-extrabold text-slate-800`,children:`서브넷 마스크`}),W(W_,{ariaLabel:`WAN 서브넷 마스크`,className:`mt-2`,disabled:v,id:`wan-static-prefix-length`,onChange:e=>y({staticPrefixLength:Number(e)}),options:pv.map(e=>({value:String(e),label:`/${e} · ${bv(e)}`})),value:String(l.staticPrefixLength),variant:`emphasized`})]}),W(`label`,{class:`block`,children:[W(`span`,{class:`text-sm font-extrabold text-slate-800`,children:`기본 게이트웨이`}),W($_,{disabled:v,label:`기본 게이트웨이`,onChange:e=>y({staticGateway:e}),value:l.staticGateway})]}),W(`label`,{class:`block`,children:[W(`span`,{class:`text-sm font-extrabold text-slate-800`,children:`기본 DNS`}),W($_,{disabled:v,label:`기본 DNS`,onChange:e=>y({dnsPrimary:e}),value:l.dnsPrimary})]}),W(`label`,{class:`block lg:col-start-2`,children:[W(`span`,{class:`text-sm font-extrabold text-slate-800`,children:`보조 DNS (선택)`}),W($_,{disabled:v,label:`보조 DNS`,onChange:e=>y({dnsSecondary:e}),value:l.dnsSecondary})]})]}):null,l.protocol===`dhcp`?W(`div`,{class:`mt-6 rounded-xl border border-slate-200 bg-slate-50 px-4 py-4 text-sm font-semibold leading-6 text-slate-600`,children:`상위 공유기 또는 통신사 장비에서 IP 주소, 기본 게이트웨이와 DNS 정보를 자동으로 받습니다. 별도의 계정이나 고정 주소가 없다면 이 방식을 사용하세요.`}):null,h?W(`p`,{class:`mt-5 mb-0 rounded-xl bg-red-50 px-4 py-3 text-sm font-bold text-red-800`,children:h}):null,W(`div`,{class:`mt-6 flex flex-col gap-3 border-t border-slate-200 pt-5 sm:flex-row sm:items-center sm:justify-between`,children:[W(`p`,{class:`m-0 text-xs font-semibold leading-5 text-slate-500`,children:`설정 저장 후 WAN만 다시 연결하며 LAN 관리 페이지와 내부 네트워크 설정은 유지됩니다.`}),W(`button`,{class:`inline-flex min-h-11 w-full shrink-0 items-center justify-center gap-2 rounded-xl border-0 bg-teal-700 px-5 py-2.5 text-sm font-extrabold text-white shadow-sm transition hover:bg-teal-800 focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-200 ${Y_(v)} disabled:opacity-60 sm:w-auto`,disabled:v||!d,type:`submit`,children:[W(Sn,{class:`size-5 ${e===`saving`?`animate-pulse`:``}`}),e===`saving`?`저장 중`:`설정 저장`]})]})]})]})}var Tv=3,Ev=10;function Dv(e,t){return e<=0?`0.0%`:`${(t/e*100).toFixed(1)}%`}function Ov(e){return e.id===`other`?`기타 기기`:e.hostname?e.hostname:e.identified?`이름 없는 기기`:`미식별 기기`}function kv(e){return[...e].sort((e,t)=>e.id===`other`?1:t.id===`other`?-1:t.blocked-e.blocked||t.queries-e.queries||Ov(e).localeCompare(Ov(t),`ko`))}function Av({devices:e,deviceLimit:t,truncated:n}){let r=kv(e),[i,a]=L(!1),[o,s]=L(1),c=Math.max(1,Math.ceil(r.length/Ev)),l=Math.min(o,c),u=(l-1)*Ev,d=Math.min(u+Ev,r.length),f=i?r.slice(u,d):r.slice(0,Tv),p=r.length>Tv;return R(()=>{s(e=>Math.min(e,c))},[c]),W(`div`,{class:`mt-6 rounded-2xl border border-slate-100 bg-slate-50/70 p-4 sm:p-5`,children:[W(`div`,{class:`flex flex-wrap items-start justify-between gap-3`,children:[W(`div`,{children:[W(`div`,{class:`flex flex-wrap items-center gap-2`,children:[W(`p`,{class:`m-0 text-xs font-bold text-slate-500`,children:`기기별 통계`}),p&&!i?W(`span`,{class:`inline-flex items-center rounded-full bg-teal-50 px-2 py-1 text-[10px] font-extrabold tracking-wide text-teal-700 ring-1 ring-teal-100`,children:`차단 TOP 3`}):null]}),W(`h3`,{class:`mt-1 mb-0 text-base font-extrabold text-slate-950`,children:`어떤 기기에서 차단되었는지 확인`}),W(`p`,{class:`mt-2 mb-0 text-xs leading-5 text-slate-500`,children:`DHCP 정보가 있는 기기는 MAC 주소를 기준으로 식별하며, 기기별 숫자만 현재 부팅 세션 동안 메모리에 집계합니다.`})]}),W(`div`,{class:`flex flex-wrap items-center justify-end gap-2`,children:[W(`span`,{class:`inline-flex items-center gap-2 rounded-full bg-white px-3 py-1.5 text-xs font-extrabold text-slate-600 ring-1 ring-slate-200`,children:[W(Yt,{class:`size-4 text-teal-700`}),Y(r.length),`개 항목`]}),p?W(`button`,{"aria-controls":`safeshield-device-statistics-list`,"aria-expanded":i,class:`rounded-full bg-white px-3 py-1.5 text-xs font-extrabold text-teal-700 ring-1 ring-slate-200 transition hover:bg-slate-50`,onClick:()=>{a(e=>!e),s(1)},type:`button`,children:i?`간단히 보기 ↑`:`전체 ${Y(r.length)}개 기기 보기 ↓`}):null]})]}),r.length===0?W(`p`,{class:`mt-4 mb-0 rounded-xl bg-white p-4 text-sm leading-6 text-slate-500 ring-1 ring-slate-100`,children:`아직 기기별 DNS 통계가 없습니다. 클라이언트에서 DNS 요청이 발생하면 다음 통계 갱신 시 표시됩니다.`}):W(`div`,{class:`mt-4 grid gap-3`,id:`safeshield-device-statistics-list`,children:f.map(e=>W(`article`,{class:`grid gap-4 rounded-xl bg-white p-4 ring-1 ring-slate-100 sm:grid-cols-[minmax(0,1fr)_auto] sm:items-center`,children:[W(`div`,{class:`min-w-0`,children:W(`div`,{class:`flex min-w-0 flex-wrap items-center gap-2`,children:[W(`span`,{class:`grid size-9 shrink-0 place-items-center rounded-lg bg-teal-50 text-teal-700`,children:W(Yt,{class:`size-4`})}),W(`div`,{class:`min-w-0`,children:[W(`h4`,{class:`m-0 truncate text-sm font-extrabold text-slate-950`,children:Ov(e)}),W(`p`,{class:`mt-1 mb-0 break-all text-xs text-slate-500`,children:[e.ip||`IP 미확인`,e.mac?` · ${e.mac}`:``]})]}),W(`span`,{class:`rounded-full px-2 py-1 text-[11px] font-extrabold ${e.identified?`bg-emerald-50 text-emerald-700`:`bg-slate-100 text-slate-600`}`,children:e.id===`other`?`합산`:e.identified?`DHCP 식별`:`IP 임시 식별`})]})}),W(`dl`,{class:`m-0 grid grid-cols-3 gap-2 text-right`,children:[W(`div`,{class:`min-w-20`,children:[W(`dt`,{class:`text-[11px] font-bold text-slate-400`,children:`DNS 요청`}),W(`dd`,{class:`mt-1 mb-0 ml-0 text-sm font-extrabold text-slate-900`,children:Y(e.queries)})]}),W(`div`,{class:`min-w-20`,children:[W(`dt`,{class:`text-[11px] font-bold text-slate-400`,children:`차단`}),W(`dd`,{class:`mt-1 mb-0 ml-0 text-sm font-extrabold text-teal-700`,children:Y(e.blocked)})]}),W(`div`,{class:`min-w-20`,children:[W(`dt`,{class:`text-[11px] font-bold text-slate-400`,children:`차단율`}),W(`dd`,{class:`mt-1 mb-0 ml-0 text-sm font-extrabold text-slate-900`,children:Dv(e.queries,e.blocked)})]})]})]},e.id))}),p&&!i?W(`p`,{class:`mt-3 mb-0 text-xs font-semibold leading-5 text-slate-500`,children:[`전체 `,Y(r.length),`개 기기 중 차단 수 기준 상위 `,Tv,`개를 표시하고 있습니다.`]}):null,i&&r.length>Ev?W(`nav`,{"aria-label":`기기별 통계 페이지`,class:`mt-4 flex flex-wrap items-center justify-between gap-3`,children:[W(`p`,{class:`m-0 text-xs font-semibold text-slate-500`,children:[Y(u+1),`–`,Y(d),` /`,` `,Y(r.length),`개`]}),W(`div`,{class:`flex items-center gap-2`,children:[W(`button`,{class:`rounded-lg bg-white px-3 py-2 text-xs font-extrabold text-slate-700 ring-1 ring-slate-200 transition hover:bg-slate-50 disabled:cursor-not-allowed disabled:opacity-50`,disabled:l<=1,onClick:()=>s(e=>Math.max(1,e-1)),type:`button`,children:`이전`}),W(`span`,{"aria-current":`page`,class:`min-w-20 text-center text-xs font-extrabold text-slate-600`,children:[Y(l),` / `,Y(c)]}),W(`button`,{class:`rounded-lg bg-white px-3 py-2 text-xs font-extrabold text-slate-700 ring-1 ring-slate-200 transition hover:bg-slate-50 disabled:cursor-not-allowed disabled:opacity-50`,disabled:l>=c,onClick:()=>s(e=>Math.min(c,e+1)),type:`button`,children:`다음`})]})]}):null,n?W(`p`,{class:`mt-4 mb-0 rounded-xl border border-amber-200 bg-amber-50 p-3 text-xs font-semibold leading-5 text-amber-800`,children:[`개별 통계는 최대 `,Y(t),`개 기기까지 추적합니다. 한도를 초과한 기기의 요청은 기타 기기로 합산됩니다.`]}):null]})}var jv=3600,Mv=24;function Nv(e,t){return e<=0?`0.0%`:`${(t/e*100).toFixed(1)}%`}function Pv(e){let t=e.hourly[e.hourly.length-1];if(t?.bucketStart)return t.bucketStart;let n=e.updatedAt>0?e.updatedAt:Math.floor(Date.now()/1e3);return Math.floor(n/jv)*jv}function Fv(e){let t=new Map(e.hourly.map(e=>[e.bucketStart,e])),n=Pv(e)-23*jv;return Array.from({length:Mv},(e,r)=>{let i=n+r*jv;return t.get(i)??{bucketStart:i,queries:0,blocked:0}})}function Iv({emphasized:e=!1,label:t,value:n}){return W(`div`,{class:`rounded-xl bg-slate-50 p-4 sm:p-5`,children:[W(`dt`,{class:`text-[0.68rem] font-black uppercase tracking-[0.12em] text-slate-400`,children:t}),W(`dd`,{class:`mt-2 mb-0 ml-0 text-2xl font-black tracking-tight ${e?`text-teal-700`:`text-slate-950`}`,children:n})]})}function Lv({action:e,data:t,error:n,loading:r,onRetry:i,onSetEnabled:a,refreshing:o}){if(r&&t===null)return W(b,{children:[W(J,{eyebrow:`Activity`,title:`최근 24시간 보호 활동`,description:`DNS 요청과 차단 현황을 확인합니다.`}),W(`section`,{class:`rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5 sm:p-6`,children:W(`p`,{class:`m-0 text-sm font-bold text-slate-500`,children:`차단 통계를 불러오는 중입니다…`})})]});if(n&&t===null)return W(b,{children:[W(J,{eyebrow:`Activity`,title:`최근 24시간 보호 활동`,description:`DNS 요청과 차단 현황을 확인합니다.`}),W(`section`,{class:`rounded-2xl border border-amber-200 bg-amber-50 p-5 sm:p-6`,children:[W(`p`,{class:`m-0 text-sm font-extrabold text-amber-950`,children:`차단 통계를 불러오지 못했습니다.`}),W(`p`,{class:`mt-2 mb-0 text-xs leading-5 text-amber-800`,children:n}),W(`button`,{class:`mt-4 inline-flex min-h-9 items-center justify-center rounded-xl border border-amber-300 bg-white px-3 py-2 text-xs font-extrabold text-amber-900 transition hover:bg-amber-100 focus:outline-none focus-visible:ring-4 focus-visible:ring-amber-100`,onClick:i,type:`button`,children:`다시 시도`})]})]});if(!t)return W(b,{children:[W(J,{eyebrow:`Activity`,title:`최근 24시간 보호 활동`,description:`DNS 요청과 차단 현황을 확인합니다.`}),W(`section`,{class:`rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5 sm:p-6`,children:W(`p`,{class:`m-0 text-sm leading-6 text-slate-600`,children:`현재 SafeShield에서 통계 데이터를 제공하지 않습니다. SafeShield 0.3.15 버전 이상이 설치되어 있고, 통계 수집이 활성화되어 있는지 확인해 주세요.`})})]});let s=Fv(t),c=s[s.length-1],l=s.reduce((e,t)=>({queries:e.queries+t.queries,blocked:e.blocked+t.blocked}),{queries:0,blocked:0}),u=e===`statistics-enable`||e===`statistics-disable`,d=e===`statistics-enable`||e!==`statistics-disable`&&t.enabled,f=u?d?`활성화하는 중…`:`비활성화하는 중…`:t.enabled?t.collectorRunning?`활성화 · 수집 중`:`활성화 · 시작 중`:`비활성화`;return W(b,{children:[W(J,{action:W(`div`,{class:`flex items-center gap-3 rounded-xl border border-slate-100 bg-slate-50 px-3 py-2.5`,children:[W(`span`,{class:`grid size-10 shrink-0 place-items-center rounded-xl bg-slate-100 text-slate-500`,children:W(gn,{class:`size-5`})}),W(`div`,{class:`text-right`,children:[W(`p`,{class:`m-0 text-[0.68rem] font-bold text-slate-400`,children:`통계 수집`}),W(`p`,{class:`mt-1 mb-0 text-xs font-extrabold ${u||d?`text-emerald-700`:`text-slate-500`}`,role:`status`,children:f})]}),W(`button`,{"aria-checked":d,"aria-label":d?`차단 통계 수집 끄기`:`차단 통계 수집 켜기`,class:`ssh-switch-control relative inline-flex shrink-0 items-center rounded-full border-0 p-0 transition focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-100 ${u?`cursor-wait opacity-80`:`disabled:cursor-not-allowed disabled:opacity-50`} ${d?`bg-teal-600`:`bg-slate-300`}`,disabled:e!==null,onClick:()=>a(!t.enabled),role:`switch`,type:`button`,children:W(`span`,{"aria-hidden":`true`,class:`ssh-switch-thumb absolute top-1 grid place-items-center shadow-sm transition-all ${d?`left-6`:`left-1`}`,children:u?W(pn,{"aria-hidden":`true`,class:`size-3 animate-spin text-teal-600`}):null})})]}),eyebrow:`Activity`,title:`최근 24시간 보호 활동`,description:`DNS 요청 원본은 저장하지 않고 숫자만 로컬 메모리에 집계합니다.`}),W(`section`,{"aria-busy":u,class:`rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5 sm:p-6 ${u?`cursor-wait`:``}`,children:[t.enabled?null:W(`div`,{class:`mb-4 rounded-xl border border-slate-200 bg-slate-50 px-4 py-3 text-sm leading-6 text-slate-600`,children:`통계 수집이 꺼져 있습니다. 활성화하면 DNS 요청 원본은 저장하지 않고 요청 수와 차단 수만 로컬 메모리에 집계합니다.`}),W(`dl`,{class:`mt-2 grid gap-3 sm:grid-cols-2 xl:grid-cols-4`,children:[W(Iv,{label:`최근 24시간 DNS 요청`,value:Y(l.queries)}),W(Iv,{emphasized:!0,label:`최근 24시간 차단`,value:Y(l.blocked)}),W(Iv,{label:`최근 24시간 차단율`,value:Nv(l.queries,l.blocked)}),W(Iv,{label:`현재 시간 차단`,value:Y(c?.blocked??0)})]}),W(`div`,{class:`mt-5 rounded-xl bg-slate-50/70 p-4 sm:p-5`,children:[W(`div`,{class:`flex flex-wrap items-end justify-between gap-3`,children:[W(`div`,{children:[W(`p`,{class:`m-0 text-xs font-bold text-slate-500`,children:`최근 24시간`}),W(`h3`,{class:`mt-1 mb-0 text-base font-extrabold text-slate-950`,children:`시간대별 차단 요청`})]}),W(`span`,{class:`text-xs font-bold text-slate-500`,children:o?`갱신 중…`:`마지막 집계 ${Ao(t.updatedAt)}`})]}),W(t_,{buckets:s})]}),W(Av,{deviceLimit:t.deviceLimit,devices:t.devices,truncated:t.devicesTruncated}),W(`div`,{class:`mt-5 flex flex-wrap gap-x-5 gap-y-2 border-t border-slate-100 pt-4 text-xs leading-5 text-slate-500`,children:[W(`span`,{children:[`수집 누적 DNS 요청 `,Y(t.totals.queries)]}),W(`span`,{children:[`수집 누적 차단 `,Y(t.totals.blocked)]}),W(`span`,{children:[`수집 시작 `,Ao(t.startedAt)]}),W(`span`,{children:[`보존 `,Y(t.retentionHours),`시간`]}),t.volatile?W(`span`,{children:`재부팅 시 초기화되는 메모리 통계`}):null]}),n?W(`p`,{class:`mt-3 mb-0 text-xs font-bold text-amber-700`,children:`최근 자동 갱신에 실패했습니다. 현재 표시된 마지막 정상 통계를 유지합니다.`}):null]})]})}function Rv({timestamp:e,fallback:t=`기록 없음`}){let[n,r]=L(()=>Date.now()/1e3),i=typeof e==`number`&&Number.isFinite(e)&&e>0&&e<=864e10;if(R(()=>{if(!i)return;r(Date.now()/1e3);let e=window.setInterval(()=>r(Date.now()/1e3),6e4);return()=>window.clearInterval(e)},[e,i]),!i||e==null)return W(`span`,{children:t});let a=Ao(e);return W(`span`,{class:`inline-flex max-w-full flex-wrap items-center gap-1`,children:[W(`time`,{class:`min-w-0`,dateTime:new Date(e*1e3).toISOString(),title:a,children:Mo(e,n)}),W(`details`,{class:`group inline-flex max-w-full flex-wrap items-center gap-1 align-middle`,children:[W(`summary`,{"aria-label":`정확한 날짜 및 시간 확인`,class:`inline-flex size-6 cursor-pointer list-none items-center justify-center rounded-full text-slate-400 transition hover:text-teal-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-teal-500 [&::-webkit-details-marker]:hidden`,title:`정확한 날짜 및 시간 확인`,children:W(`svg`,{"aria-hidden":`true`,class:`size-3.5`,fill:`none`,stroke:`currentColor`,strokeWidth:`1.8`,viewBox:`0 0 24 24`,children:[W(`circle`,{cx:`12`,cy:`12`,r:`9`}),W(`path`,{d:`M12 11v5m0-8h.01`,strokeLinecap:`round`})]})}),W(`span`,{class:`hidden max-w-full break-words rounded-md bg-slate-100 px-2 py-1 text-xs font-semibold text-slate-700 group-open:inline-block`,children:a})]})]})}var zv=`https://www.smartsafehub.com/pricing/`;function Bv({value:e,trueLabel:t=`정상`,falseLabel:n=`확인 필요`}){return W(`span`,{class:`inline-flex items-center gap-2 text-sm font-extrabold ${e?`text-emerald-700`:`text-slate-500`}`,children:[W(`span`,{class:`size-2 rounded-full ${e?`bg-emerald-500`:`bg-slate-300`}`}),e?t:n]})}function Vv(e){return!e.enabled||e.status===`disabled`?`disabled`:e.status===`running`?`refreshing`:e.status===`error`||e.health.overall===`error`||e.issueCounts.errors>0?`error`:e.status===`paused`?`paused`:e.active&&e.runtime.dnsmasqRunning&&e.runtime.dnsRuntimeOk?`protecting`:`attention`}function Hv(e){return!e.enabled||e.status===`disabled`?`비활성화`:e.active&&e.runtime.dnsmasqRunning&&e.runtime.dnsRuntimeOk?`보호 중`:e.status===`paused`?`일시 중지`:`확인 필요`}function Uv(e){let t=Vv(e);return t===`error`&&Hv(e)===`보호 중`&&e.runtime.lastErrorCode?`갱신 실패`:{disabled:`비활성화`,refreshing:`갱신 중`,protecting:`보호 중`,paused:`일시 중지`,error:`오류`,attention:`확인 필요`}[t]}function Wv(e){return e?`자동 최적화`:`확인되지 않음`}function Gv(e){let t=No(e);return t===null?`확인되지 않음`:W(Rv,{timestamp:t})}function Kv(e){let t=Vv(e);return t===`protecting`?e.blocklist.validLineCount>0?`${Y(e.blocklist.validLineCount)}개 도메인 차단 규칙으로 DNS를 보호하고 있습니다.`:e.blocklist.installed?`차단 목록이 설치되어 있으며 DNS 보호가 정상적으로 동작하고 있습니다.`:`SafeShield 서비스와 DNS 런타임이 정상적으로 동작하고 있습니다.`:t===`refreshing`?`차단 목록을 최신 상태로 갱신하고 있습니다.`:t===`disabled`?`SafeShield DNS 보호가 비활성화되어 있습니다.`:t===`paused`?`SafeShield 작업이 일시 중지되어 있습니다.`:t===`error`?Hv(e)===`보호 중`?`기존 DNS 보호는 유지되고 있지만 최근 차단 목록 갱신을 완료하지 못했습니다.`:`SafeShield 보호 상태에 문제가 발생했습니다. 아래 오류 내용을 확인해 주세요.`:e.active?e.runtime.dnsmasqRunning?e.runtime.dnsRuntimeOk?e.summary.message:`SafeShield DNS 런타임 상태를 확인해야 합니다.`:`dnsmasq가 실행 중이 아니어서 DNS 보호 상태를 확인해야 합니다.`:`SafeShield가 활성화되어 있지만 서비스가 실행 중이 아닙니다.`}function qv(e){return{ok:`정상`,warning:`경고`,error:`오류`,unavailable:`사용 불가`}[e]??e}function Jv({data:e}){let t=Vv(e);return W(`span`,{class:`inline-flex items-center rounded-full px-3 py-1.5 text-xs font-extrabold ring-1 ring-inset ${t===`error`?`bg-red-50 text-red-700 ring-red-200`:t===`disabled`?`bg-slate-100 text-slate-700 ring-slate-200`:t===`refreshing`?`bg-blue-50 text-blue-700 ring-blue-200`:t===`paused`||t===`attention`?`bg-amber-50 text-amber-800 ring-amber-200`:`bg-emerald-50 text-emerald-700 ring-emerald-200`}`,children:Uv(e)})}function Yv({failed:e,step:t,total:n}){let r=2*Math.PI*17,i=r*.22,a=r-i;return W(`span`,{"aria-label":e?`갱신 실패 · ${t}/${n} 단계`:`갱신 ${t}/${n} 단계`,class:`ssh-safeshield-refresh-donut`,"data-state":e?`error`:`active`,role:`progressbar`,"aria-valuemin":0,"aria-valuemax":n,"aria-valuenow":t,children:[W(`svg`,{"aria-hidden":`true`,class:e?`ssh-safeshield-refresh-loader-svg ssh-safeshield-refresh-loader-svg-error`:`ssh-safeshield-refresh-loader-svg`,viewBox:`0 0 48 48`,children:[W(`circle`,{class:`ssh-safeshield-refresh-loader-track`,cx:`24`,cy:`24`,fill:`none`,r:17,stroke:`currentColor`,strokeWidth:2.5}),e?null:W(`circle`,{class:`ssh-safeshield-refresh-loader-arc`,cx:`24`,cy:`24`,fill:`none`,r:17,stroke:`currentColor`,strokeDasharray:`${i} ${a}`,strokeLinecap:`round`,strokeWidth:3.5})]}),W(`span`,{class:`ssh-safeshield-refresh-donut-label`,children:e?`!`:`${t}/${n}`})]})}function Xv({data:e}){let t=Wa(e.status,e.stage),n=Vv(e)===`error`;if(!t&&!n)return null;let r=Ga(e.stage),i=n?qa(e.runtime.lastErrorCode,e.stage):null;return W(`div`,{"aria-live":n?`assertive`:`polite`,class:`ssh-safeshield-refresh`,"data-state":n?`error`:`active`,role:n?`alert`:`status`,children:[W(Yv,{failed:n,step:r.number,total:r.total}),W(`div`,{class:`ssh-safeshield-refresh-copy`,children:[W(`p`,{class:`ssh-safeshield-refresh-kicker`,children:n?`갱신 실패 · ${r.number}/${r.total}`:`갱신 ${r.number}/${r.total}`}),W(`p`,{class:`ssh-safeshield-refresh-title`,children:n?`${r.label} 단계에서 문제가 발생했습니다.`:r.label}),W(`p`,{class:`ssh-safeshield-refresh-description`,children:i??r.description}),n&&e.runtime.lastErrorCode?W(`p`,{class:`ssh-safeshield-refresh-error-code`,children:[`오류 코드: `,W(`code`,{children:e.runtime.lastErrorCode})]}):null]})]})}function Zv({label:e,value:t}){return W(`div`,{class:`bg-white px-5 py-4 sm:px-6`,children:[W(`dt`,{class:`text-[0.68rem] font-black uppercase tracking-[0.14em] text-slate-400`,children:e}),W(`dd`,{class:`mt-2 mb-0 ml-0 text-sm font-extrabold text-slate-950`,title:typeof t==`string`?t:void 0,children:t})]})}function Qv(e){return e?.trim().toUpperCase()||`FREE`}function $v(e){return e===`FREE`?`free`:e===`PRO`?`pro`:e===`ULTIMATE`?`ultimate`:`paid`}function ey({compact:e=!1,plan:t}){let n=$v(t),r=n!==`free`;return W(`span`,{class:`ssh-safeshield-plan-badge`,"data-compact":e?`true`:`false`,"data-tier":n,title:r?`${t} 멤버십`:`무료 플랜`,children:[W(`span`,{"aria-hidden":`true`,class:`ssh-safeshield-plan-badge-mark`,children:r?`✦`:`•`}),W(`span`,{children:t})]})}function ty({plan:e}){let t=$v(e)===`free`;return W(`div`,{class:`bg-white px-5 py-4 sm:px-6`,children:[W(`dt`,{class:`text-[0.68rem] font-black uppercase tracking-[0.14em] text-slate-400`,children:`Plan`}),W(`dd`,{class:`mt-2 mb-0 ml-0 flex min-w-0 flex-wrap items-center gap-2`,children:[W(ey,{plan:e}),t?W(`span`,{class:`ssh-safeshield-plan-caption`,children:`기본 플랜`}):null]})]})}function ny(){return W(`div`,{class:`ssh-safeshield-upgrade-card`,children:[W(`span`,{"aria-hidden":`true`,class:`ssh-safeshield-upgrade-mark`,children:`✦`}),W(`div`,{class:`ssh-safeshield-upgrade-copy`,children:[W(`div`,{class:`ssh-safeshield-upgrade-meta`,children:[W(`span`,{children:`SmartSafeHub Membership`}),W(`span`,{class:`ssh-safeshield-upgrade-soon`,children:`Coming soon`})]}),W(`strong`,{children:`더 강력한 보호 기능을 준비하고 있습니다.`}),W(`span`,{children:`프리미엄 보호와 확장된 관리 기능을 미리 확인해 보세요.`})]}),W(`a`,{class:`ssh-safeshield-upgrade-action`,href:zv,rel:`noopener noreferrer`,target:`_blank`,children:[`요금제 보기`,W(`span`,{"aria-hidden":`true`,children:`→`})]})]})}function ry({children:e,eyebrow:t,icon:n,title:r}){return W(`article`,{class:`min-w-0 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5 sm:p-6 xl:p-4 2xl:p-5`,children:[W(`div`,{class:`flex min-w-0 items-start justify-between gap-4 xl:gap-2`,children:[W(`div`,{children:[W(`p`,{class:`m-0 text-[0.68rem] font-black uppercase tracking-[0.16em] text-slate-400`,children:t}),W(`h3`,{class:`mt-2 mb-0 text-lg font-black tracking-tight text-slate-950 xl:text-base 2xl:text-lg`,children:r})]}),W(`span`,{class:`grid size-10 shrink-0 place-items-center rounded-xl bg-slate-100 text-slate-500 xl:size-9 2xl:size-10`,children:n})]}),W(`dl`,{class:`mt-5 mb-0 grid gap-3`,children:e})]})}function iy({label:e,value:t}){return W(`div`,{class:`flex min-w-0 items-center justify-between gap-2 border-b border-slate-100 pb-3 last:border-b-0 last:pb-0`,children:[W(`dt`,{class:`text-sm font-semibold text-slate-500`,children:e}),W(`dd`,{class:`m-0 min-w-0 break-words text-right text-sm font-extrabold text-slate-950`,children:t})]})}function ay({error:e,message:t,onDismiss:n}){if(!e&&!t)return null;let r=e!==null;return W(`div`,{"aria-live":r?`assertive`:`polite`,class:`mt-4 flex flex-col items-start gap-3 rounded-2xl border px-4 py-3 text-sm sm:flex-row sm:justify-between ${r?`border-red-200 bg-red-50 text-red-900`:`border-emerald-200 bg-emerald-50 text-emerald-900`}`,role:r?`alert`:`status`,children:[W(`div`,{class:`flex min-w-0 items-start gap-3`,children:[W(r?Cn:Sn,{class:`mt-0.5 size-5 shrink-0`}),W(`p`,{class:`m-0 font-semibold leading-6`,children:e??t})]}),W(`button`,{"aria-label":`알림 닫기`,class:`shrink-0 rounded-lg border-0 bg-transparent px-2 py-1 font-black text-current opacity-60 transition hover:opacity-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-current`,onClick:n,type:`button`,children:`×`})]})}function oy({action:e,actionError:t,actionMessage:n,data:r,error:i,loading:a,statistics:o,statisticsError:s,statisticsLoading:c,statisticsRefreshing:l,onDismissFeedback:u,onRefreshBlocklist:d,onRetry:f,onRetryStatistics:p,onSetEnabled:m,onSetStatisticsEnabled:h}){let[g,_]=L(null);if(R(()=>{r?.available&&r.blocklist.validLineCount>0&&!Wa(r.status,r.stage)&&_(r.blocklist.validLineCount)},[r?.available,r?.blocklist.validLineCount,r?.stage,r?.status]),a)return W(vs,{});if(i)return W(ys,{message:i,onRetry:f});if(!r)return W(vs,{});if(!r.available)return W(`section`,{class:`rounded-2xl border border-amber-200 bg-amber-50 p-5 sm:p-6`,children:[W(`span`,{class:`grid size-11 place-items-center rounded-xl bg-amber-100 text-amber-800`,children:W(Cn,{class:`size-6`})}),W(`h2`,{class:`mt-5 mb-0 text-xl font-extrabold text-amber-950`,children:`SafeShield 상태 API를 찾을 수 없습니다`}),W(`p`,{class:`mt-2 mb-0 text-sm leading-6 text-amber-900`,children:`safeshield 패키지와 rpcd ucode 플러그인이 설치되어 있는지 확인해 주세요.`})]});let v=r.enabled,y=Qv(r.entitlement.plan),x=r.status===`running`,S=e!==null,C=(Wa(r.status,r.stage)||Vv(r)===`error`)&&r.blocklist.validLineCount===0&&g!==null?g:r.blocklist.validLineCount,w=v?e===`disable`?`끄는 중…`:`보호 끄기`:e===`enable`?`켜는 중…`:`보호 켜기`;function T(){(!v||window.confirm(`보호를 끄면, 현재 차단 목록이 제거되어 위험에 노출될 수 있습니다. 그래도 계속하시겠습니까?`))&&m(!v)}return W(b,{children:[W(J,{eyebrow:`Protection`,title:`SafeShield 보호 상태`,description:`DNS 보호 상태와 적용 중인 차단 목록을 확인합니다.`}),W(`section`,{class:`mb-8 grid min-w-0 gap-4 xl:grid-cols-[minmax(0,1.08fr)_minmax(0,0.92fr)] xl:items-stretch`,children:[W(`article`,{"aria-label":`보호 상태 요약`,class:`min-w-0 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5 sm:p-6 xl:h-full`,children:[W(`div`,{class:`flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between xl:flex-col 2xl:flex-row`,children:[W(`div`,{class:`flex min-w-0 items-start gap-3`,children:[W(`span`,{class:`grid size-10 shrink-0 place-items-center rounded-xl bg-teal-50 text-teal-700`,children:W(qt,{class:`size-5`})}),W(`div`,{class:`min-w-0`,children:[W(`div`,{class:`flex flex-wrap items-center gap-2`,children:[W(`h3`,{class:`m-0 text-base font-black text-slate-950`,children:`현재 보호 상태`}),W(Jv,{data:r})]}),W(`p`,{class:`mt-2 mb-0 text-sm leading-6 text-slate-600`,children:Kv(r)})]})]}),W(`div`,{class:`flex w-full flex-col gap-2 sm:w-auto sm:flex-row sm:flex-wrap xl:justify-end`,children:[W(`button`,{class:`inline-flex min-h-11 items-center justify-center gap-2 rounded-xl border px-4 py-2.5 text-sm font-extrabold transition focus:outline-none focus-visible:ring-4 disabled:cursor-not-allowed disabled:opacity-50 ${r.enabled?`border-red-200 bg-white text-red-700 hover:border-red-300 hover:bg-red-50 focus-visible:ring-red-100`:`border-teal-700 bg-teal-700 text-white hover:border-teal-800 hover:bg-teal-800 focus-visible:ring-teal-100`}`,disabled:S||x,onClick:T,type:`button`,children:[W(mn,{class:`size-4 ${e===`enable`||e===`disable`?`animate-pulse`:``}`}),w]}),W(`button`,{class:`inline-flex min-h-11 items-center justify-center gap-2 rounded-xl border border-slate-200 bg-white px-4 py-2.5 text-sm font-extrabold text-slate-700 transition hover:border-teal-300 hover:bg-teal-50 hover:text-teal-800 focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-100 disabled:cursor-not-allowed disabled:opacity-50`,disabled:!r.enabled||S||x,onClick:d,type:`button`,children:[W(hn,{class:`size-4 ${e===`refresh`||x?`animate-bounce`:``}`}),e===`refresh`?`시작 중…`:x?`갱신 중…`:r.status===`error`?`다시 갱신`:`지금 갱신`]})]})]}),W(Xv,{data:r}),W(`dl`,{class:`mt-5 mb-0 grid min-w-0 grid-cols-2 overflow-hidden rounded-xl border border-slate-200 bg-slate-200`,children:[W(Zv,{label:`SafeShield`,value:r.version??`확인되지 않음`}),W(Zv,{label:`Blocklist`,value:`${Y(C)}개 도메인`}),W(Zv,{label:`Last refresh`,value:W(Rv,{timestamp:r.timestamps.lastSuccess})}),W(ty,{plan:y})]}),y===`FREE`?W(ny,{}):null]}),W(`section`,{"aria-label":`보호 데이터 요약`,class:`min-w-0 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5 sm:p-6 xl:h-full`,children:[W(`div`,{class:`flex flex-wrap items-center justify-between gap-3`,children:[W(`div`,{class:`flex min-w-0 items-center gap-3`,children:[W(`span`,{class:`grid size-10 shrink-0 place-items-center rounded-xl bg-slate-100 text-slate-500`,children:W(gn,{class:`size-5`})}),W(`div`,{children:[W(`p`,{class:`m-0 text-[0.65rem] font-black uppercase tracking-[0.16em] text-slate-400`,children:`Protection data`}),W(`h3`,{class:`mt-1 mb-0 text-sm font-black text-slate-950`,children:`보호 데이터`})]})]}),W(`span`,{"aria-label":`보호 데이터 상태: ${r.artifact.resolved&&r.blocklist.installed&&r.blocklist.verificationOk?`최신`:`확인 필요`}`,class:`inline-flex items-center gap-1.5 rounded-full px-2.5 py-1 text-xs font-extrabold ${r.artifact.resolved&&r.blocklist.installed&&r.blocklist.verificationOk?`bg-emerald-50 text-emerald-700`:`bg-amber-50 text-amber-700`}`,children:[r.artifact.resolved&&r.blocklist.installed&&r.blocklist.verificationOk?W(Sn,{class:`size-3.5 shrink-0`}):W(Cn,{class:`size-3.5 shrink-0`}),r.artifact.resolved&&r.blocklist.installed&&r.blocklist.verificationOk?`최신`:`확인 필요`]})]}),W(`p`,{class:`mt-2 mb-0 text-xs leading-5 text-slate-500`,children:`SafeShield가 기기 성능에 맞춰 사용하는 보호 목록의 구성과 최신 상태를 확인합니다.`}),W(`dl`,{class:`mt-3 mb-0 grid grid-cols-1 gap-2 sm:grid-cols-2`,children:[W(`div`,{class:`min-w-0 rounded-lg border border-slate-200 bg-white px-3 py-2.5`,children:[W(`dt`,{class:`text-xs font-bold text-slate-500`,children:`기기 최적화`}),W(`dd`,{class:`mt-1 mb-0 text-sm font-extrabold text-slate-950`,children:Wv(r.artifact.tier)})]}),W(`div`,{class:`min-w-0 rounded-lg border border-slate-200 bg-white px-3 py-2.5`,children:[W(`dt`,{class:`text-xs font-bold text-slate-500`,children:`마지막 업데이트`}),W(`dd`,{class:`mt-1 mb-0 text-sm font-extrabold leading-5 text-slate-950`,children:Gv(r.artifact.version)})]})]}),W(`section`,{"aria-label":`보호 데이터 세부 정보`,class:`mt-3 border-t border-slate-200 pt-3`,children:[W(`h4`,{class:`m-0 text-xs font-extrabold text-slate-600`,children:`세부 정보`}),W(`dl`,{class:`mt-3 mb-0 grid gap-2`,children:[W(iy,{label:`데이터 프로필`,value:r.artifact.tier||`확인되지 않음`}),W(iy,{label:`데이터 버전`,value:r.artifact.version||`확인되지 않음`})]})]})]})]}),W(ay,{error:t,message:n,onDismiss:u}),W(Lv,{action:e,data:o,error:s,loading:c,onRetry:p,onSetEnabled:h,refreshing:l}),W(`section`,{class:`mt-7`,children:[W(J,{description:`DNS 보호 런타임, 차단 목록, 갱신 일정과 상태 점검 결과를 한곳에서 확인합니다.`,eyebrow:`Protection details`,title:`보호 구성`}),W(`div`,{class:`grid min-w-0 grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4`,children:[W(ry,{eyebrow:`Runtime`,icon:W(Sn,{class:`size-5`}),title:`DNS 보호 런타임`,children:[W(iy,{label:`SafeShield 서비스`,value:W(Bv,{falseLabel:`중지됨`,trueLabel:`동작 중`,value:r.active})}),W(iy,{label:`DNS 서비스`,value:W(Bv,{falseLabel:`중지됨`,value:r.runtime.dnsmasqRunning})}),W(iy,{label:`SafeShield DNS 연동`,value:W(Bv,{value:r.runtime.dnsRuntimeOk})}),W(iy,{label:`차단 목록 갱신`,value:W(Bv,{falseLabel:`중지됨`,value:r.runtime.refreshdRunning})})]}),W(ry,{eyebrow:`Blocklist`,icon:W(gn,{class:`size-5`}),title:`차단 목록`,children:[W(iy,{label:`적용 규칙`,value:`${Y(r.blocklist.validLineCount)}개 도메인`}),W(iy,{label:`파일 크기`,value:Do(r.blocklist.fileSizeKb*1024)}),W(iy,{label:`설치 상태`,value:W(Bv,{falseLabel:`미설치`,trueLabel:`설치됨`,value:r.blocklist.installed})}),W(iy,{label:`검증 상태`,value:W(Bv,{falseLabel:`확인 필요`,value:r.blocklist.verificationOk})})]}),W(ry,{eyebrow:`Refresh schedule`,icon:W(xn,{class:`size-5`}),title:`차단 목록 갱신`,children:[W(iy,{label:`마지막 성공`,value:W(Rv,{timestamp:r.timestamps.lastSuccess})}),W(iy,{label:`다음 갱신`,value:Ao(r.timestamps.nextRefreshAt)}),W(iy,{label:`갱신 주기`,value:Fo(r.timestamps.refreshIntervalS)}),W(iy,{label:`최근 결과`,value:r.runtime.lastResult||(x?`갱신 중`:`확인되지 않음`)})]}),W(ry,{eyebrow:`Health`,icon:W(qt,{class:`size-5`}),title:`상태 점검`,children:[W(iy,{label:`전체 상태`,value:qv(r.health.overall)}),W(iy,{label:`경고`,value:W(`span`,{class:`text-amber-700`,children:Y(r.issueCounts.warnings)})}),W(iy,{label:`오류`,value:W(`span`,{class:`text-red-700`,children:Y(r.issueCounts.errors)})}),W(iy,{label:`최근 오류 코드`,value:r.runtime.lastErrorCode||`없음`})]})]})]})]})}function sy(e,t,n,r){let i=t?e.filter(e=>e.includes(t)):e;if(!n)return{filteredRules:i,manualRules:i,managedRules:[]};let a=[],o=[];for(let e of i)(r.has(e)?o:a).push(e);return{filteredRules:i,manualRules:a,managedRules:o}}function cy(e,t){return t?e.filter(e=>e.title.toLowerCase().includes(t)||e.domains.some(e=>e.includes(t))):e}function ly(e,t){return e===t||e.length===t.length&&e.every((e,n)=>e===t[n])}function uy(e,t){return e===t||e.length===t.length&&e.every((e,n)=>{let r=t[n];return r!==void 0&&e.id===r.id&&e.title===r.title&&ly(e.domains,r.domains)})}function dy(e,t){return{domains:ly(e.domains,t.domains)?e.domains:t.domains,rules:uy(e.rules,t.rules)?e.rules:t.rules}}var fy=[],py=[];function my(e){return e.trim().toLowerCase().replace(/\.+$/,``)}function hy(e){return e.length===0||e.length>253||!e.includes(`.`)||!/^[a-z0-9.-]+$/.test(e)?!1:e.split(`.`).every(e=>e.length>0&&e.length<=63&&/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(e))}var gy=D_(function({action:e,kind:t,onAddRule:n,onDeleteRule:r,rules:i,recommendedDomainSet:a,recommendedKnown:o,recommendedRules:s,onRequestRevoke:c,revokeBusy:l}){let[u,d]=L(``),[f,p]=L(``),[m,h]=L(null),[g,_]=L(!0),[v,y]=L(null),b=t===`allow`,x=e!==null,S=Xe(()=>f.trim().toLowerCase(),[f]),C=S.length>0,{filteredRules:w,manualRules:T,managedRules:E}=Xe(()=>sy(i,S,b&&o,a),[i,S,b,o,a]),D=Xe(()=>b?cy(s,S):[],[b,s,S]),O=C||(v??(s.length||E.length)<=3),k=C||g;async function A(e){e.preventDefault();let r=my(u);hy(r)?(h(null),await n(t,r,()=>{d(e=>my(e)===r?``:e)})):h(`example.com 형식의 도메인을 입력해 주세요. URL, IP 주소, 와일드카드는 사용할 수 없습니다.`)}let j=b?{badge:`bg-emerald-100 text-emerald-800`,button:`border-emerald-700 bg-emerald-700 text-white hover:border-emerald-800 hover:bg-emerald-800 focus-visible:ring-emerald-100`,icon:`bg-emerald-50 text-emerald-700`,label:`허용 목록`,description:`직접 허용하거나 웹사이트 추천으로 추가된 도메인을 관리합니다.`,empty:`등록된 허용 도메인이 없습니다.`}:{badge:`bg-red-100 text-red-800`,button:`border-red-700 bg-red-700 text-white hover:border-red-800 hover:bg-red-800 focus-visible:ring-red-100`,icon:`bg-red-50 text-red-700`,label:`차단 목록`,description:`SafeShield 기본 목록에 없더라도 직접 차단할 도메인을 등록합니다.`,empty:`등록된 차단 도메인이 없습니다.`};function M(n){let i=b&&o&&a.has(n),s=!b||o?i?`추천 규칙`:`직접 추가`:null,c=e?.operation===`delete`&&e.action===t&&e.domain===n;return W(`li`,{class:`flex min-w-0 items-center justify-between gap-3 px-3.5 py-3`,children:[W(`div`,{class:`flex min-w-0 flex-1 flex-wrap items-center gap-2`,children:W(`code`,{class:`min-w-0 break-all font-mono text-sm font-semibold text-slate-800`,children:n})}),i?W(`span`,{class:`shrink-0 rounded-full bg-teal-50 px-2.5 py-1 text-[11px] font-bold text-teal-800`,title:`추천 규칙의 적용과 해제는 웹사이트에서 관리합니다.`,children:`웹사이트 관리`}):W(`button`,{"aria-label":`${n} ${j.label}에서 삭제`,class:`inline-flex size-9 shrink-0 items-center justify-center rounded-lg border border-slate-200 bg-white text-slate-500 transition hover:border-red-200 hover:bg-red-50 hover:text-red-700 focus:outline-none focus-visible:ring-4 focus-visible:ring-red-100 disabled:cursor-not-allowed disabled:opacity-50`,disabled:x||i,onClick:()=>void r(t,n),title:s===`직접 추가`?`직접 추가한 규칙 삭제`:`삭제`,type:`button`,children:W(Tn,{class:`size-4 ${c?`animate-pulse`:``}`})})]},n)}return W(`article`,{class:`min-w-0 overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm shadow-slate-900/5`,children:[W(`div`,{class:`border-b border-slate-200 p-4 sm:p-6`,children:[W(`div`,{class:`flex min-w-0 items-start gap-3`,children:[W(`span`,{class:`grid size-11 shrink-0 place-items-center rounded-xl ${j.icon}`,children:W(b?Sn:qt,{class:`size-6`})}),W(`div`,{class:`min-w-0`,children:[W(`div`,{class:`flex flex-wrap items-center gap-2`,children:[W(`h2`,{class:`m-0 text-xl font-extrabold tracking-tight text-slate-950`,children:j.label}),W(`span`,{class:`rounded-full px-2.5 py-1 text-xs font-extrabold ${j.badge}`,children:i.length})]}),W(`p`,{class:`mt-2 mb-0 text-sm leading-6 text-slate-600`,children:j.description})]})]}),W(`form`,{class:`mt-5`,onSubmit:A,children:[W(`label`,{class:`block text-xs font-extrabold text-slate-600`,for:`${t}-domain`,children:`새 도메인`}),W(`div`,{class:`mt-2 flex flex-col gap-2 sm:flex-row`,children:[W(`input`,{"aria-describedby":m?`${t}-domain-error`:void 0,"aria-invalid":m?!0:void 0,autocomplete:`off`,class:`min-h-11 min-w-0 flex-1 rounded-xl border-2 border-slate-300 bg-slate-50 px-4 py-2.5 text-sm font-semibold text-slate-950 shadow-inner outline-none transition placeholder:text-slate-400 focus:border-teal-500 focus:bg-white focus:ring-4 focus:ring-teal-100 disabled:cursor-not-allowed disabled:bg-slate-100`,disabled:x,id:`${t}-domain`,onInput:e=>d(e.currentTarget.value),placeholder:`example.com`,spellcheck:!1,type:`text`,value:u}),W(`button`,{class:`inline-flex min-h-11 w-full shrink-0 items-center justify-center gap-2 rounded-xl border px-4 py-2.5 text-sm font-extrabold transition focus:outline-none focus-visible:ring-4 disabled:cursor-not-allowed disabled:opacity-50 sm:w-auto ${j.button}`,disabled:x||u.trim().length===0,type:`submit`,children:[W(wn,{class:`size-4`}),e?.operation===`add`&&e.action===t?`추가 중…`:`추가`]})]}),m?W(`p`,{class:`mt-2 mb-0 text-xs font-semibold leading-5 text-red-700`,id:`${t}-domain-error`,children:m}):null]})]}),W(`div`,{class:`p-4 sm:p-6`,children:[W(`label`,{class:`relative block`,for:`${t}-search`,children:[W(`span`,{class:`sr-only`,children:[j.label,` 검색`]}),W(`span`,{class:`pointer-events-none absolute inset-y-0 left-0 flex w-11 items-center justify-center text-slate-400`,children:W(En,{class:`size-4`})}),W(`input`,{class:`min-h-11 w-full rounded-xl border-2 border-slate-300 bg-slate-50 py-2.5 pr-4 pl-11 text-sm font-semibold text-slate-950 shadow-inner outline-none transition placeholder:text-slate-400 focus:border-teal-500 focus:bg-white focus:ring-4 focus:ring-teal-100`,id:`${t}-search`,onInput:e=>p(e.currentTarget.value),placeholder:`${j.label} 검색`,type:`search`,value:f})]}),b&&o?W(`div`,{class:`mt-4 overflow-hidden rounded-xl border border-slate-200`,children:[W(`section`,{"aria-label":`직접 추가한 허용 규칙`,children:[W(`button`,{"aria-controls":`allow-manual-domains`,"aria-expanded":k,class:`flex min-h-14 w-full items-center justify-between gap-3 ssh-rule-group-toggle bg-slate-50 px-4 py-3 text-left focus-visible:outline-2 focus-visible:outline-teal-500`,onClick:()=>_(!k),type:`button`,children:[W(`span`,{class:`min-w-0 text-sm font-extrabold text-slate-800`,children:[`직접 추가한 규칙 `,W(`span`,{class:`ml-1 rounded-full bg-white px-2 py-0.5 text-xs font-bold text-slate-600`,children:[T.length,C?`개 검색됨`:`개`]})]}),W(`span`,{"aria-hidden":`true`,class:`shrink-0 text-sm font-bold text-slate-500`,children:k?`접기 −`:`펼치기 +`})]}),W(`div`,{hidden:!k,id:`allow-manual-domains`,children:T.length?W(`ul`,{class:`m-0 max-h-[20rem] list-none divide-y divide-slate-200 overflow-y-auto p-0`,children:T.map(M)}):W(`p`,{class:`m-0 px-4 py-5 text-sm text-slate-500`,children:C?`검색 결과가 없습니다.`:`직접 추가한 허용 도메인이 없습니다.`})})]}),W(`section`,{"aria-label":`웹사이트 추천 규칙`,class:`border-t border-slate-200`,children:[W(`button`,{"aria-controls":`allow-recommended-domains`,"aria-expanded":O,class:`flex min-h-14 w-full items-center justify-between gap-3 ssh-rule-group-toggle bg-slate-50 px-4 py-3 text-left focus-visible:outline-2 focus-visible:outline-teal-500`,onClick:()=>y(!O),type:`button`,children:[W(`span`,{class:`min-w-0 text-sm font-extrabold text-slate-800`,children:[`추천 규칙 `,W(`span`,{class:`ml-1 rounded-full bg-white px-2 py-0.5 text-xs font-bold text-teal-800`,children:[!C&&s.length?s.length:E.length,C?`개 검색됨`:`개`]})]}),W(`span`,{"aria-hidden":`true`,class:`shrink-0 text-sm font-bold text-slate-500`,children:O?`접기 −`:`펼치기 +`})]}),W(`div`,{hidden:!O,id:`allow-recommended-domains`,children:[D.length?W(`ul`,{class:`m-0 max-h-[22rem] list-none divide-y divide-slate-200 overflow-y-auto p-0`,children:D.map(e=>W(`li`,{class:`flex min-w-0 flex-wrap items-center justify-between gap-3 px-3.5 py-3`,children:[W(`div`,{class:`min-w-0 flex-1`,children:[W(`strong`,{class:`block break-words text-sm text-slate-900`,children:e.title}),W(`div`,{class:`mt-1 flex flex-wrap gap-1.5`,children:e.domains.map(e=>W(`code`,{class:`break-all text-xs text-slate-600`,children:e},e))})]}),c?W(`button`,{class:`shrink-0 rounded-lg border border-red-200 px-3 py-2 text-xs font-bold text-red-700 hover:bg-red-50 disabled:opacity-50`,disabled:l||x,onClick:()=>c(e),type:`button`,children:`적용 해제`}):W(`span`,{class:`text-xs text-slate-500`,children:`웹사이트 관리`})]},e.id))}):E.length?W(`ul`,{class:`m-0 max-h-[20rem] list-none divide-y divide-slate-200 overflow-y-auto p-0`,children:E.map(M)}):W(`p`,{class:`m-0 px-4 py-5 text-sm text-slate-500`,children:C?`검색 결과가 없습니다.`:`추천으로 추가된 허용 도메인이 없습니다.`}),C?null:W(`p`,{class:`m-0 border-t border-slate-100 px-4 py-3 text-xs leading-5 text-slate-500`,children:`추천 규칙은 이 공유기에서 적용 해제할 수 있습니다. 새 규칙의 적용은 SmartSafeHub 웹사이트에서 관리해 주세요.`})]})]})]}):W(`div`,{class:`mt-4 max-h-[28rem] overflow-y-auto rounded-xl border border-slate-200`,children:w.length?W(`ul`,{class:`m-0 list-none divide-y divide-slate-200 p-0`,children:w.map(M)}):W(`div`,{class:`px-5 py-10 text-center`,children:[W(Dn,{class:`mx-auto size-8 text-slate-300`}),W(`p`,{class:`mt-3 mb-0 text-sm font-bold text-slate-600`,children:C?`검색 결과가 없습니다.`:j.empty})]})})]})]})});function _y({action:e,data:t,error:n,feedback:r,loading:i,onAddRule:a,onDeleteRule:o,onDismissFeedback:s,onRetry:c,onLoadRecommendedRulesStatus:l,onSyncRecommendedRules:u,onRevokeRecommendedRule:d}){let[f,p]=L(null),[m,h]=L(!1),[g,_]=L(!1),[v,y]=L(null),[x,S]=L(!1),[C,w]=L(!1),[T,E]=L(!1),D=z(null),O=z(null),k=z(null),A=z(c);A.current=c;let[j,M]=L(null),[N,P]=L(null),F=f?.domains??fy,I=f?.rules??py,ee=Xe(()=>new Set(F),[F]),te=B(e=>{p(t=>{if(!t)return e;let{domains:n,rules:r}=dy(t,e);return{...e,domains:n,rules:r}}),h(!0);let t=JSON.stringify([e.revision,e.domains]),n=!1;return e.syncStatus===`confirmed`&&k.current!==t&&(O.current!==null&&(A.current(),n=!0),k.current=t),O.current=t,D.current!==null&&e.revokeOperation?.phase===`failed`&&e.revokeOperation.argument===String(D.current)&&(D.current=null,w(!1),E(!1),P(`추천 규칙 적용 해제를 완료하지 못했습니다. 연결 상태를 확인하고 다시 시도해 주세요.`)),e.syncStatus===`confirmed`&&D.current===null&&e.revokeOperation?.phase!==`failed`&&P(null),D.current!==null&&e.syncStatus===`confirmed`&&!e.rules.some(e=>e.id===D.current)&&(D.current=null,w(!1),E(!1),P(null),M(`추천 규칙 적용 해제를 확인했습니다.`)),n},[]);R(()=>{if(!l)return;let e=l,t=!0,n=!1;function r(){n||g||x||(n=!0,e().then(e=>{t&&te(e)}).catch(()=>{}).finally(()=>{n=!1}))}r();let i=window.setInterval(r,15e3),a=()=>r(),o=()=>{document.hidden||r()};return window.addEventListener(`focus`,a),document.addEventListener(`visibilitychange`,o),()=>{t=!1,window.clearInterval(i),window.removeEventListener(`focus`,a),document.removeEventListener(`visibilitychange`,o)}},[l,g,x,te]),R(()=>{if(!C)return;let e=window.setTimeout(()=>E(!0),6e4);return()=>window.clearTimeout(e)},[C]);async function ne(){if(!(!u||g||x)){_(!0),P(null),M(null);try{let e=await u(),t=te(e);M(e.syncStatus===`confirmed`?`서버에서 규칙을 가져와 SafeShield 허용 목록을 확인했습니다.`:`추천 규칙 상태를 확인 중입니다. 진행 결과는 자동으로 갱신됩니다.`),e.syncStatus===`confirmed`&&!t&&c()}catch{P(`추천 규칙을 동기화하지 못했습니다. 서버 연결 및 기기 등록 상태를 확인해 주세요.`)}finally{_(!1)}}}async function re(){if(!v||x||!d)return;let e=v.id;D.current=e,S(!0),w(!0),E(!1),P(null),M(null);try{let t=await d(e);te(t),y(null),D.current!==null&&M(`적용 해제 요청을 처리했습니다. 공유기의 허용 목록을 확인하고 있습니다.`)}catch{y(null),P(`적용 해제 결과를 아직 확인하지 못했습니다. 공유기의 상태를 자동으로 다시 확인합니다.`);try{let e=await l?.();e&&te(e)}catch{}}finally{S(!1)}}let ie=f?.syncStage,ae=f?.syncStatus===`pending`,oe=f?.syncStatus===`delayed`,se=f?.syncStatus===`failed`,ce=ie===`server_sync`?`서버 동기화 중`:ie===`safeshield_refreshing`?`SafeShield 갱신 중`:ie===`applying`?`추천 규칙 적용 중`:ie===`verifying`?`허용 목록 확인 중`:oe?`재확인 지연`:se?`동기화 실패`:ae?`동기화 대기`:`허용 목록 확인`,le=ie===`safeshield_refreshing`&&ae?`SafeShield가 보호 목록을 갱신하고 있습니다. 갱신이 끝나면 추천 규칙을 자동으로 다시 확인합니다.`:ie===`applying`&&ae?`추천 규칙을 SafeShield 허용 목록에 반영하고 있습니다.`:ie===`verifying`&&ae?`SafeShield 허용 목록에서 추천 규칙 반영 결과를 확인하고 있습니다.`:ie===`server_sync`&&ae?`SmartSafeHub 서버에서 최신 추천 규칙을 확인하고 있습니다.`:oe?`서버 응답이 지연되고 있습니다. 이미 확인한 허용 규칙은 유지되며 자동으로 다시 시도합니다.`:se?f?.syncReason===`auth_rejected`?`SmartSafeHub 기기 인증이 거부되었습니다. 공유기 계정 연결 상태를 확인해 주세요.`:f?.syncReason===`api_rejected`?`서버가 추천 규칙 요청을 거부했습니다. Hub API 배포 상태를 확인해 주세요.`:f?.syncReason===`invalid_response`?`서버 응답을 확인할 수 없습니다. 규칙은 유지되며 다음 동기화에서 다시 확인합니다.`:`추천 규칙 반영을 완료하지 못했습니다. 다음 자동 동기화에서 다시 시도합니다.`:ae?`추천 규칙의 적용 결과를 기다리고 있습니다.`:null,ue=C&&D.current!==null?{tone:`amber`,label:T?`적용 해제 처리 지연`:`적용 해제 확인 중`,description:T?`적용 해제 확인이 지연되고 있습니다. SafeShield 갱신 중이라면 완료 후 자동으로 재확인합니다.`:le??`적용 해제 상태를 확인하고 있습니다. 확인되면 목록과 상태가 자동 갱신됩니다.`}:ae||oe||se?{tone:se?`red`:`amber`,label:ce,description:le??``}:N?{tone:`red`,label:`상태 확인 필요`,description:N}:f?.syncWarning?{tone:`amber`,label:`서버 재확인 지연`,description:`이미 확인한 허용 규칙은 유지되며 자동으로 다시 시도합니다.`}:j?{tone:`green`,label:`허용 목록 확인`,description:j}:null;return i&&!t?W(vs,{}):n&&!t?W(ys,{message:n,onRetry:c}):t?W(b,{children:[v?W(`div`,{class:`fixed inset-0 z-50 flex items-center justify-center bg-slate-950/70 p-4`,role:`presentation`,onClick:()=>{x||y(null)},children:W(`section`,{"aria-labelledby":`recommended-revoke-title`,"aria-modal":`true`,class:`w-full max-w-md rounded-2xl border border-slate-200 bg-white p-5 shadow-xl`,onClick:e=>e.stopPropagation(),role:`dialog`,children:[W(`h2`,{class:`m-0 text-lg font-extrabold text-slate-950`,id:`recommended-revoke-title`,children:`추천 규칙 적용 해제`}),W(`p`,{class:`mt-3 text-sm text-slate-700`,children:[W(`strong`,{children:v.title}),` 규칙을 이 공유기에서 해제할까요?`]}),W(`p`,{class:`mt-2 text-xs leading-5 text-slate-600`,children:[`포함된 도메인 `,v.domains.length,`개를 다시 확인합니다. 직접 추가한 허용 규칙과 다른 추천 규칙에 필요한 도메인은 유지됩니다.`]}),W(`div`,{class:`mt-5 flex justify-end gap-2`,children:[W(`button`,{class:`rounded-lg border border-slate-200 px-4 py-2 text-sm font-bold text-slate-700 disabled:cursor-wait disabled:opacity-50`,disabled:x,onClick:()=>y(null),type:`button`,children:`취소`}),W(`button`,{class:`inline-flex min-w-24 items-center justify-center gap-2 rounded-lg bg-red-700 px-4 py-2 text-sm font-bold text-white transition hover:bg-red-800 disabled:cursor-progress disabled:bg-red-700 disabled:opacity-80`,"aria-busy":x,disabled:x,onClick:()=>void re(),type:`button`,children:[x?W(`svg`,{class:`size-4 animate-spin`,"aria-hidden":`true`,viewBox:`0 0 24 24`,fill:`none`,children:[W(`circle`,{cx:`12`,cy:`12`,r:`9`,stroke:`currentColor`,"stroke-width":`3`,opacity:`0.25`}),W(`path`,{d:`M21 12a9 9 0 0 0-9-9`,stroke:`currentColor`,"stroke-width":`3`,"stroke-linecap":`round`})]}):null,W(`span`,{role:`status`,children:x?`해제 중…`:`적용 해제`})]})]})]})}):null,W(J,{eyebrow:`Local overrides`,title:`사용자 도메인 규칙`,description:`허용 목록은 오탐을 해제하고, 차단 목록은 원하는 도메인을 SafeShield 보호 목록에 추가합니다.`}),W(`section`,{"aria-label":`규칙 개수 요약`,class:`min-w-0`,children:W(`dl`,{class:`m-0 grid grid-cols-1 gap-3 sm:grid-cols-3`,children:[W(`div`,{class:`min-w-0 rounded-2xl border border-slate-200 bg-white px-5 py-4 shadow-sm shadow-slate-900/5`,children:[W(`dt`,{class:`text-sm font-extrabold text-emerald-700`,children:`허용 도메인`}),W(`dd`,{class:`mt-2 mb-0 ml-0 text-2xl font-black tracking-tight text-slate-950`,children:[t.counts.allow,W(`span`,{class:`ml-1 text-sm font-bold text-slate-500`,children:`개`})]}),W(`p`,{class:`mt-1 mb-0 text-xs text-slate-500`,children:`차단하지 않도록 등록한 도메인`})]}),W(`div`,{class:`min-w-0 rounded-2xl border border-slate-200 bg-white px-5 py-4 shadow-sm shadow-slate-900/5`,children:[W(`dt`,{class:`text-sm font-extrabold text-red-700`,children:`차단 도메인`}),W(`dd`,{class:`mt-2 mb-0 ml-0 text-2xl font-black tracking-tight text-slate-950`,children:[t.counts.block,W(`span`,{class:`ml-1 text-sm font-bold text-slate-500`,children:`개`})]}),W(`p`,{class:`mt-1 mb-0 text-xs text-slate-500`,children:`추가로 차단하도록 등록한 도메인`})]}),W(`div`,{class:`min-w-0 rounded-2xl border border-slate-200 bg-white px-5 py-4 shadow-sm shadow-slate-900/5`,children:[W(`dt`,{class:`text-sm font-extrabold text-slate-600`,children:`전체 등록 규칙`}),W(`dd`,{class:`mt-2 mb-0 ml-0 text-2xl font-black tracking-tight text-slate-950`,children:[t.counts.total,W(`span`,{class:`ml-1 text-sm font-bold text-slate-500`,children:`개`})]}),W(`p`,{class:`mt-1 mb-0 text-xs text-slate-500`,children:`허용 및 차단 규칙의 합계`})]})]})}),W(`div`,{class:`mt-7`,children:W(J,{eyebrow:`Recommendations`,title:`추천 사용자 규칙`,description:`웹사이트에서 선택한 권장 허용 규칙의 동기화 상태를 확인하고 관리합니다.`})}),W(`section`,{class:`rounded-2xl border border-slate-200 bg-white p-4 shadow-sm shadow-slate-900/5 sm:p-5`,"aria-label":`추천 사용자 규칙`,children:[W(`div`,{class:`flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between`,children:[W(`div`,{class:`min-w-0 flex-1`,children:[W(`div`,{class:`flex flex-wrap items-center gap-2`,children:[W(`span`,{class:`grid size-8 place-items-center rounded-lg bg-teal-50 text-teal-700`,children:W(Sn,{class:`size-4`})}),W(`h3`,{class:`m-0 text-base font-extrabold text-slate-950`,children:`동기화 상태`}),l?W(`span`,{"aria-live":`polite`,class:`rounded-full px-2.5 py-1 text-xs font-bold ${f?.syncStatus===`confirmed`?`bg-emerald-100 text-emerald-800`:f?.syncStatus===`failed`?`bg-red-100 text-red-800`:`bg-amber-100 text-amber-800`}`,children:f?.syncStatus===`confirmed`?`허용 목록 확인`:ce}):null]}),W(`p`,{class:`mt-2 mb-0 text-sm leading-6 text-slate-600`,children:`웹사이트에서 선택한 허용 규칙을 약 30초마다 확인합니다. 적용 해제는 이 공유기에서도 할 수 있습니다.`}),W(`div`,{class:`mt-2 flex flex-wrap items-center gap-x-4 gap-y-1 text-xs font-semibold text-slate-600`,children:[W(`span`,{children:[`추천으로 추가 `,W(`strong`,{class:`text-teal-800`,children:[m?F.length:`—`,`개 도메인`]})]}),f?.syncStatus===`confirmed`?W(`span`,{children:[`허용 확인 `,W(`strong`,{class:`text-emerald-800`,children:[f.confirmedDomains.length,`개`]})]}):null,f?.lastCheckedAt?W(`span`,{children:[f.syncWarning?`최근 재확인 시도`:`최근 확인`,`: `,W(Rv,{timestamp:f.lastCheckedAt})]}):null]})]}),W(`div`,{class:`flex w-full flex-wrap gap-2 sm:w-auto lg:shrink-0`,children:[u?W(`button`,{class:`inline-flex min-h-10 flex-1 items-center justify-center rounded-xl border border-slate-300 bg-white px-4 py-2.5 text-sm font-extrabold text-slate-800 transition hover:bg-slate-50 disabled:cursor-wait disabled:opacity-60 sm:flex-none`,disabled:g,onClick:()=>void ne(),type:`button`,children:g?`동기화 중…`:`지금 동기화`}):null,W(`a`,{class:`inline-flex min-h-10 flex-1 items-center justify-center rounded-xl border border-teal-700 bg-teal-700 px-4 py-2.5 text-sm font-extrabold text-white transition hover:bg-teal-800 sm:flex-none`,href:`https://www.smartsafehub.com/dashboard/recommended-rules/`,target:`_blank`,rel:`noopener noreferrer`,children:`추천 규칙 보기 ↗`})]})]}),ue?W(`div`,{"aria-live":`polite`,class:`mt-3 flex items-start gap-2 rounded-xl px-3 py-2.5 text-sm ${ue.tone===`red`?`bg-red-50 text-red-800`:ue.tone===`green`?`bg-emerald-50 text-emerald-800`:`bg-amber-50 text-amber-800`}`,role:`status`,children:[W(`span`,{"aria-hidden":`true`,class:`mt-1 size-2 shrink-0 rounded-full ${ue.tone===`red`?`bg-red-500`:ue.tone===`green`?`bg-emerald-500`:`bg-amber-500`}`}),W(`div`,{class:`min-w-0`,children:[W(`strong`,{children:ue.label}),W(`p`,{class:`mt-1 mb-0 leading-5`,children:ue.description})]})]}):null,W(`p`,{class:`mt-3 mb-0 text-xs leading-5 text-slate-500`,children:`추천 규칙 동기화는 SafeShield 차단 목록 갱신과 별개입니다. 허용 목록 확인은 DNS 처리나 서비스 재생 성공을 보장하지 않습니다.`})]}),t.applyLocalOverrides?null:W(`section`,{class:`mt-4 flex items-start gap-3 rounded-2xl border border-amber-200 bg-amber-50 p-4`,role:`alert`,children:[W(Cn,{class:`mt-0.5 size-5 shrink-0 text-amber-700`}),W(`div`,{children:[W(`p`,{class:`m-0 text-sm font-extrabold text-amber-950`,children:`로컬 규칙 적용이 꺼져 있습니다`}),W(`p`,{class:`mt-1 mb-0 text-sm leading-6 text-amber-800`,children:`규칙은 저장되지만 SafeShield의 apply_local_overrides 설정을 켜기 전까지 DNS에 반영되지 않습니다.`})]})]}),t.safeshieldEnabled?null:W(`section`,{class:`mt-4 flex items-start gap-3 rounded-2xl border border-slate-200 bg-slate-100 p-4`,role:`status`,children:[W(qt,{class:`mt-0.5 size-5 shrink-0 text-slate-600`}),W(`div`,{children:[W(`p`,{class:`m-0 text-sm font-extrabold text-slate-950`,children:`SafeShield가 비활성화되어 있습니다`}),W(`p`,{class:`mt-1 mb-0 text-sm leading-6 text-slate-600`,children:`규칙을 미리 저장할 수 있으며, SafeShield를 켜고 차단 목록을 갱신하면 적용됩니다.`})]})]}),r?W(`section`,{"aria-live":`polite`,class:`mt-4 flex flex-col items-start gap-3 rounded-2xl border border-teal-200 bg-teal-50 sm:flex-row sm:justify-between p-4`,children:[W(`div`,{class:`flex min-w-0 items-start gap-3`,children:[W(Sn,{class:`mt-0.5 size-5 shrink-0 text-teal-700`}),W(`p`,{class:`m-0 text-sm font-semibold leading-6 text-teal-950`,children:r})]}),W(`button`,{class:`shrink-0 rounded-lg border-0 bg-transparent px-2 py-1 text-xs font-extrabold text-teal-800 hover:bg-teal-100`,onClick:s,type:`button`,children:`닫기`})]}):null,n&&t?W(`section`,{"aria-live":`assertive`,class:`mt-4 flex items-start gap-3 rounded-2xl border border-red-200 bg-red-50 p-4`,children:[W(Cn,{class:`mt-0.5 size-5 shrink-0 text-red-700`}),W(`p`,{class:`m-0 text-sm font-semibold leading-6 text-red-900`,children:n})]}):null,W(`section`,{class:`mt-5 grid gap-5 xl:grid-cols-2`,children:[W(gy,{action:e,kind:`allow`,onAddRule:a,onDeleteRule:o,rules:t.allow,recommendedDomainSet:ee,recommendedKnown:m,recommendedRules:I,...d?{onRequestRevoke:y}:{},revokeBusy:x}),W(gy,{action:e,kind:`block`,onAddRule:a,onDeleteRule:o,rules:t.block,recommendedDomainSet:ee,recommendedKnown:m,recommendedRules:I,revokeBusy:x})]})]}):W(ys,{message:`사용자 규칙 응답이 비어 있습니다.`,onRetry:c})}function vy(e,t){return U(`smartsafehub`,`system_root_password_change`,{current_password:e,new_password:t})}var yy=Array.from({length:24},(e,t)=>{let n=String(t).padStart(2,`0`);return{value:n,label:`${n}시`}}),by=Array.from({length:60},(e,t)=>{let n=String(t).padStart(2,`0`);return{value:n,label:`${n}분`}});function xy(e){let t=/^([01]\d|2[0-3]):([0-5]\d)$/.exec(e);return{hour:t?.[1]??`03`,minute:t?.[2]??`00`}}function Sy({ariaLabel:e,className:t=``,disabled:n=!1,id:r,onChange:i,value:a,variant:o=`default`}){let{hour:s,minute:c}=xy(a),l=(e,t)=>{i(`${e}:${t}`)};return W(`div`,{"aria-label":e,class:`ssh-time-select ${t}`.trim(),"data-time-select":r,role:`group`,children:[W(W_,{ariaLabel:`${e} 시`,disabled:n,id:`${r}-hour`,onChange:e=>l(e,c),options:yy,value:s,variant:o}),W(W_,{ariaLabel:`${e} 분`,disabled:n,id:`${r}-minute`,onChange:e=>l(s,e),options:by,value:c,variant:o})]})}function Cy(){try{return Intl.DateTimeFormat().resolvedOptions().timeZone||null}catch{return null}}function wy(e){return W(`article`,{class:`h-full min-w-0 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5 sm:p-6 ${e.className??``}`,children:[W(`div`,{class:`flex min-w-0 items-start justify-between gap-3`,children:[W(`div`,{class:`flex min-w-0 items-start gap-4`,children:[W(`div`,{class:`flex size-11 shrink-0 items-center justify-center rounded-xl bg-slate-100 text-slate-600`,children:e.icon}),W(`div`,{class:`min-w-0`,children:[W(`div`,{class:`flex flex-wrap items-center gap-2`,children:[W(`h2`,{class:`m-0 text-lg font-black text-slate-950`,children:e.title}),e.titleActions]}),W(`p`,{class:`mt-1.5 mb-0 text-sm leading-6 text-slate-500`,children:e.description})]})]}),e.headerActions?W(`div`,{class:`shrink-0`,children:e.headerActions}):null]}),W(`div`,{class:`mt-5`,children:e.children})]})}function Ty(e){let[t,n]=L(``),[r,i]=L(e.data?.localtime??0),a=Cy();R(()=>{e.data?.zonename&&n(e.data.zonename)},[e.data?.zonename]),R(()=>{let t=e.data?.localtime??0,n=Date.now();if(i(t),!Number.isFinite(t)||t<=0)return;let r=window.setInterval(()=>{i(t+Math.floor((Date.now()-n)/1e3))},1e3);return()=>window.clearInterval(r)},[e.data?.localtime]);let o=e.data?Object.keys(e.data.timezones).sort((e,t)=>e.localeCompare(t)):[],s=Po(r,e.data?.zonename??`UTC`),c=a!==null&&e.data?.timezones[a]!==void 0,l=e.data!==null&&t.length>0&&t!==e.data.zonename;return W(b,{children:[W(wy,{description:`공유기의 기준 시간대를 설정합니다. 로그, 통계, 자동 설치와 예약 재부팅 시각도 이 시간대를 기준으로 동작합니다.`,icon:W(dn,{class:`size-5`}),title:`시간 및 시간대`,children:[(e.saveError||e.saveMessage)&&W(`div`,{class:`mb-5 flex min-w-0 items-start justify-between gap-3 rounded-xl border px-4 py-3 text-sm font-bold ${e.saveError?`border-rose-200 bg-rose-50 text-rose-800`:`border-emerald-200 bg-emerald-50 text-emerald-800`}`,children:[W(`span`,{class:`min-w-0`,children:e.saveError||e.saveMessage}),W(`button`,{class:`shrink-0 rounded-lg px-2 py-1 text-xs font-extrabold hover:bg-black/5`,onClick:e.onDismissFeedback,type:`button`,children:`닫기`})]}),e.error&&!e.data?W(`div`,{class:`rounded-xl border border-rose-200 bg-rose-50 p-4`,children:[W(`p`,{class:`m-0 text-sm font-bold text-rose-800`,children:e.error}),W(`button`,{class:`mt-3 inline-flex min-h-10 items-center rounded-xl border border-rose-300 bg-white px-3 py-2 text-xs font-extrabold text-rose-700 transition hover:bg-rose-100`,onClick:e.onRetry,type:`button`,children:`다시 불러오기`})]}):W(b,{children:[W(`div`,{class:`grid min-w-0 grid-cols-1 gap-3 sm:grid-cols-2`,children:[W(`div`,{class:`rounded-xl bg-slate-50 p-4`,children:[W(`p`,{class:`m-0 text-xs font-extrabold uppercase tracking-[0.12em] text-slate-500`,children:`현재 시간`}),W(`p`,{class:`mt-2 mb-0 text-sm font-extrabold leading-6 text-slate-900`,children:s}),W(`p`,{class:`mt-1 mb-0 break-words text-xs leading-5 text-slate-500`,children:e.data?.zonename??`시간대 확인 중`})]}),W(`div`,{class:`rounded-xl bg-slate-50 p-4`,children:[W(`p`,{class:`m-0 text-xs font-extrabold uppercase tracking-[0.12em] text-slate-500`,children:`시간 동기화`}),W(`p`,{class:`mt-2 mb-0 text-sm font-extrabold ${e.data?.ntpEnabled?`text-emerald-700`:`text-amber-700`}`,children:e.data?.ntpEnabled?`자동 동기화 설정됨`:`자동 동기화 꺼짐`}),W(`p`,{class:`mt-1 mb-0 text-xs leading-5 text-slate-500`,children:`장치의 기본 NTP 설정 상태입니다.`}),W(`button`,{class:`mt-3 inline-flex min-h-9 items-center justify-center gap-2 rounded-lg border border-slate-300 bg-white px-3 py-2 text-xs font-extrabold text-slate-700 transition hover:bg-slate-100 disabled:cursor-not-allowed disabled:opacity-50`,disabled:!e.data?.ntpEnabled||e.saving||e.syncing,onClick:()=>void e.onSync(),type:`button`,children:e.syncing?`동기화 요청 중`:`지금 동기화`})]})]}),W(`div`,{class:`mt-5`,children:[W(`label`,{class:`mb-2 block text-sm font-extrabold text-slate-800`,for:`smartsafehub-timezone`,children:`시간대`}),W(W_,{ariaLabel:`시간대`,disabled:e.loading||e.saving||e.syncing||!e.data,id:`smartsafehub-timezone`,onChange:n,options:e.data?o.map(e=>({value:e,label:e})):[{value:``,label:`시간대 불러오는 중`}],value:t}),W(`p`,{class:`mt-2 mb-0 text-xs leading-5 text-slate-500`,children:`시간대를 변경하면 시스템에 즉시 적용되며 자동 설치와 예약 재부팅 일정도 새 기준 시간으로 다시 계산합니다.`})]}),W(`div`,{class:`mt-4 flex flex-col gap-3 sm:flex-row sm:flex-wrap sm:justify-end`,children:[c&&a!==t&&W(`button`,{class:`inline-flex min-h-11 w-full items-center justify-center rounded-xl border border-slate-300 bg-white px-4 py-2.5 text-sm font-extrabold text-slate-700 transition hover:bg-slate-50 disabled:opacity-60 sm:w-auto`,disabled:e.saving||e.syncing,onClick:()=>n(a),type:`button`,children:`브라우저 시간대 사용`}),W(`button`,{class:`inline-flex min-h-11 w-full items-center justify-center rounded-xl bg-teal-700 px-4 py-2.5 text-sm font-extrabold text-white transition hover:bg-teal-800 disabled:cursor-not-allowed disabled:opacity-50 sm:w-auto`,disabled:!l||e.saving||e.syncing,onClick:()=>void e.onSave(t),type:`button`,children:e.saving?`시간대 저장 중`:`시간대 저장`})]})]})]}),W(`div`,{class:`min-w-0 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5 sm:p-6`,children:W(Ly,{data:e.scheduledRebootData,error:e.scheduledRebootError,loading:e.scheduledRebootLoading,onDismissFeedback:e.onDismissScheduledRebootFeedback,onRetry:e.onRetry,onSave:e.onSaveScheduledReboot,saveError:e.scheduledRebootSaveError,saveMessage:e.scheduledRebootSaveMessage,saving:e.scheduledRebootSaving})})]})}function Ey(e){return e===`initializing`?{badge:`bg-teal-100 text-teal-800`,panel:`border-teal-200 bg-teal-50`,label:`준비 중`}:e===`critical`?{badge:`bg-rose-100 text-rose-800`,panel:`border-rose-200 bg-rose-50`,label:`이상`}:e===`warning`?{badge:`bg-amber-100 text-amber-800`,panel:`border-amber-200 bg-amber-50`,label:`주의`}:e===`unknown`?{badge:`bg-slate-200 text-slate-700`,panel:`border-slate-200 bg-slate-50`,label:`확인 불가`}:{badge:`bg-emerald-100 text-emerald-800`,panel:`border-emerald-200 bg-emerald-50`,label:`정상`}}function Dy(e){if(!e.enabled)return`보고 중지됨`;switch(e.lastResult){case`reported`:return`정상 보고`;case`initializing`:return e.lastReportAt>0?`상태 갱신 중`:`첫 보고 진행 중`;case`failed`:return`최근 보고 실패`;case`idle`:return`다음 보고 대기`;case`ineligible`:return`멤버십 확인 필요`;case`disabled`:case`never`:return`첫 보고 준비 중`;default:return`보고 준비 중`}}function Oy(e){return e.lastReportAt>0?jo(e.lastReportAt):e.enabled?`첫 보고 대기 중`:`보고 기록 없음`}function ky(e){let t=e?.plan?.trim().toUpperCase(),n=e?.entitlementStatus?.trim().toLowerCase();return t&&(n===`trial`||n===`trialing`)?`${t} · 체험`:t||`유료 · Trial`}function Ay(e){let t=e.data,n=Ey(t?.overall??`unknown`),r=t?.checks.filter(e=>e.status!==`ok`)??[],i=r.length>0?r.slice(0,4):t?.checks.filter(e=>[`system.memory`,`network.wan`,`service.dnsmasq`,`safeshield.runtime`].includes(e.id))??[],a=t?.reporter,o=a?.enabled===!0,s=a?.eligible===!0||o,c=e.savingReporter?o?`원격 상태 보고를 켜고 있습니다.`:`원격 상태 보고를 끄고 있습니다.`:o?`원격 상태 보고가 켜져 있습니다.`:`원격 상태 보고가 꺼져 있습니다.`,l=o?`장치 상태가 바뀌면 즉시 보고하고, 정상 상태에서도 주기적으로 SmartSafeHub 서버에 보고합니다.`:`로컬 진단은 계속 동작하지만 장치 상태는 SmartSafeHub 서버에 자동으로 전송되지 않습니다.`;return W(b,{children:[W(wy,{description:`SmartSafeHub가 장치 상태를 직접 점검하고 이상 항목을 알려줍니다.`,icon:W(hn,{class:`size-5`}),title:`장치 진단`,children:[(e.actionError||e.actionMessage)&&W(`div`,{class:`mb-4 flex items-start justify-between gap-3 rounded-xl border px-4 py-3 text-sm font-bold ${e.actionError?`border-rose-200 bg-rose-50 text-rose-800`:`border-emerald-200 bg-emerald-50 text-emerald-800`}`,children:[W(`span`,{children:e.actionError||e.actionMessage}),W(`button`,{class:`shrink-0 rounded-lg px-2 py-1 text-xs font-extrabold hover:bg-black/5`,onClick:e.onDismissFeedback,type:`button`,children:`닫기`})]}),e.error&&!t?W(`div`,{class:`rounded-xl border border-rose-200 bg-rose-50 p-4`,children:[W(`p`,{class:`m-0 text-sm font-bold text-rose-800`,children:e.error}),W(`button`,{class:`mt-3 inline-flex min-h-10 items-center rounded-xl border border-rose-300 bg-white px-3 py-2 text-xs font-extrabold text-rose-700 transition hover:bg-rose-100`,onClick:e.onRetry,type:`button`,children:`다시 불러오기`})]}):W(b,{children:[W(`div`,{class:`rounded-xl border p-4 ${n.panel}`,children:W(`div`,{class:`flex flex-wrap items-start justify-between gap-3`,children:[W(`div`,{class:`flex min-w-0 items-start gap-3`,children:[W(`span`,{class:`mt-0.5 shrink-0`,children:t?.overall===`ok`?W(Sn,{class:`size-5 text-emerald-700`}):t?.overall===`initializing`?W(dn,{class:`size-5 text-teal-700`}):W(Cn,{class:`size-5 text-amber-700`})}),W(`div`,{class:`min-w-0`,children:[W(`p`,{class:`m-0 text-sm font-black text-slate-950`,children:t?.summary.message??`장치 상태를 확인하고 있습니다.`}),W(`p`,{class:`mt-1 mb-0 text-xs leading-5 text-slate-600`,children:[`마지막 진단 : `,t?jo(t.generatedAt):`확인 중`]})]})]}),W(`span`,{class:`rounded-full px-2.5 py-1 text-xs font-extrabold ${n.badge}`,children:n.label})]})}),t&&W(`div`,{class:`mt-4 grid grid-cols-3 gap-2`,children:[W(`div`,{class:`rounded-xl bg-slate-50 p-3`,children:[W(`p`,{class:`m-0 text-[11px] font-extrabold text-slate-500`,children:`진단 항목`}),W(`p`,{class:`mt-1 mb-0 text-lg font-black text-slate-950`,children:t.summary.total})]}),W(`div`,{class:`rounded-xl bg-slate-50 p-3`,children:[W(`p`,{class:`m-0 text-[11px] font-extrabold text-slate-500`,children:`주의`}),W(`p`,{class:`mt-1 mb-0 text-lg font-black text-amber-700`,children:t.summary.warning})]}),W(`div`,{class:`rounded-xl bg-slate-50 p-3`,children:[W(`p`,{class:`m-0 text-[11px] font-extrabold text-slate-500`,children:`이상`}),W(`p`,{class:`mt-1 mb-0 text-lg font-black text-rose-700`,children:t.summary.critical})]})]}),i.length>0&&W(`div`,{class:`mt-4 space-y-2`,children:i.map(e=>{let t=Ey(e.status);return W(`div`,{class:`flex min-w-0 items-start justify-between gap-3 rounded-xl border border-slate-200 px-3 py-2.5`,children:[W(`div`,{class:`min-w-0`,children:[W(`p`,{class:`m-0 text-xs font-extrabold text-slate-900`,children:e.label}),W(`p`,{class:`mt-1 mb-0 text-xs leading-5 text-slate-500`,children:e.message})]}),W(`span`,{class:`shrink-0 rounded-full px-2 py-1 text-[11px] font-extrabold ${t.badge}`,children:t.label})]},e.id)})}),W(`div`,{class:`mt-4 flex flex-col gap-2 sm:flex-row`,children:[W(`button`,{class:`inline-flex min-h-11 w-full items-center justify-center rounded-xl bg-teal-700 px-4 py-2.5 text-sm font-extrabold text-white transition hover:bg-teal-800 disabled:cursor-wait disabled:opacity-60 sm:w-auto`,disabled:e.running||e.loading,onClick:e.onRun,type:`button`,children:e.running?`진단 중`:`지금 진단`}),W(`button`,{class:`inline-flex min-h-11 w-full items-center justify-center rounded-xl border border-slate-300 bg-white px-4 py-2.5 text-sm font-extrabold text-slate-800 transition hover:bg-slate-50 disabled:cursor-wait disabled:opacity-60 sm:w-auto`,disabled:e.downloadBusy,onClick:e.onDownload,type:`button`,children:e.downloadBusy?`진단 정보 생성 중`:`진단 정보 다운로드`})]}),W(`p`,{class:`mt-3 mb-0 text-xs leading-5 text-slate-500`,children:`다운로드 파일에는 호스트명, WAN IP와 Wi-Fi SSID 같은 네트워크 식별 정보가 포함될 수 있으므로 외부 전달 전에 내용을 확인해 주세요.`})]})]}),W(`div`,{class:`grid min-w-0 content-start gap-4`,children:[W(wy,{description:`장치의 최소 상태 정보와 이상 코드만 서버로 전송합니다. 직접 켜고 언제든지 끌 수 있습니다.`,icon:W(qt,{class:`size-5`}),title:`원격 상태 보고`,titleActions:W(`span`,{class:`inline-flex rounded-full bg-slate-100 px-2.5 py-1 text-[11px] font-extrabold uppercase tracking-[0.08em] text-slate-600`,children:ky(a)}),headerActions:W(`button`,{"aria-checked":a?.enabled===!0,"aria-label":`원격 상태 보고 사용`,class:`ssh-switch-control relative inline-flex shrink-0 rounded-full border transition focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-100 disabled:cursor-not-allowed disabled:opacity-50 ${a?.enabled?`border-teal-600 bg-teal-600`:`border-slate-300 bg-slate-200`}`,disabled:!t||e.savingReporter||!s,onClick:()=>e.onSetReporter(!(a?.enabled??!1)),role:`switch`,type:`button`,children:W(`span`,{"aria-hidden":`true`,class:`ssh-switch-thumb absolute top-1 shadow-sm transition-[left] ${a?.enabled?`left-6`:`left-1`}`})}),children:W(`div`,{class:`min-w-0`,children:a?.eligible?W(`div`,{class:`rounded-xl bg-slate-50 p-4`,children:[W(`div`,{class:`mb-4 flex items-start gap-3 rounded-xl border p-3 ${o?`border-emerald-200 bg-emerald-50`:`border-slate-200 bg-white`}`,children:[W(`span`,{class:`mt-0.5 shrink-0`,children:o?W(Sn,{class:`size-5 text-emerald-700`}):W(mn,{class:`size-5 text-slate-500`})}),W(`div`,{class:`min-w-0 flex-1`,children:[W(`div`,{class:`flex flex-wrap items-center gap-2`,children:[W(`p`,{class:`m-0 text-sm font-black text-slate-950`,children:c}),W(`span`,{class:`rounded-full px-2 py-0.5 text-[10px] font-extrabold ${e.savingReporter?`bg-slate-200 text-slate-700`:o?`bg-emerald-100 text-emerald-800`:`bg-slate-200 text-slate-700`}`,children:e.savingReporter?`설정 중`:o?`켜짐`:`꺼짐`})]}),W(`p`,{class:`mt-1 mb-0 text-xs leading-5 text-slate-600`,children:l})]})]}),W(`div`,{class:`grid grid-cols-1 gap-3 sm:grid-cols-2`,children:[W(`div`,{children:[W(`p`,{class:`m-0 text-[11px] font-extrabold text-slate-500`,children:`최근 전송 상태`}),W(`p`,{class:`mt-1 mb-0 text-sm font-black text-slate-900`,children:e.savingReporter?`설정 반영 중`:Dy(a)})]}),W(`div`,{children:[W(`p`,{class:`m-0 text-[11px] font-extrabold text-slate-500`,children:`마지막 서버 보고`}),W(`p`,{class:`mt-1 mb-0 text-sm font-black text-slate-900`,children:Oy(a)})]})]}),a.enabled&&a.lastErrorCode&&W(`p`,{class:`mt-3 mb-0 text-xs font-bold text-rose-700`,children:[`최근 보고 오류 : `,a.lastErrorCode]})]}):W(`div`,{class:`rounded-xl border border-slate-200 bg-slate-50 p-4`,children:[W(`p`,{class:`m-0 text-sm font-extrabold text-slate-900`,children:`원격 상태 보고는 유료 멤버십 또는 체험 기간에 사용할 수 있습니다.`}),W(`a`,{class:`mt-3 inline-flex text-xs font-extrabold text-teal-700 hover:text-teal-800`,href:`https://www.smartsafehub.com/pricing/`,rel:`noopener noreferrer`,target:`_blank`,children:`멤버십 알아보기`})]})})}),W(wy,{description:`진단에 필요한 최소한의 상태 정보만 전송하며, 네트워크 식별 정보와 이용 내용은 보내지 않습니다.`,icon:W(qt,{class:`size-5`}),title:`개인정보 보호`,children:W(`div`,{class:`grid grid-cols-1 gap-3 sm:grid-cols-2`,children:[W(`div`,{class:`rounded-xl border border-slate-200 bg-slate-50 p-3.5`,children:[W(`div`,{class:`flex items-center gap-2`,children:[W(`span`,{class:`flex size-7 shrink-0 items-center justify-center rounded-lg bg-white text-slate-600`,children:W(gn,{class:`size-4`})}),W(`p`,{class:`m-0 text-sm font-black text-slate-900`,children:`전송되는 정보`})]}),W(`div`,{class:`mt-3 space-y-3`,children:[W(`div`,{children:[W(`p`,{class:`m-0 text-[11px] font-extrabold text-slate-500`,children:`기기 상태 수치`}),W(`p`,{class:`mt-1 mb-0 text-xs font-semibold leading-5 text-slate-700`,children:`메모리 · 부하 · 저장 공간`})]}),W(`div`,{children:[W(`p`,{class:`m-0 text-[11px] font-extrabold text-slate-500`,children:`진단 정보`}),W(`p`,{class:`mt-1 mb-0 text-xs font-semibold leading-5 text-slate-700`,children:`진단 상태 · 이상 코드 · 보고 시각`})]})]})]}),W(`div`,{class:`rounded-xl border border-teal-200 bg-teal-50 p-3.5`,children:[W(`div`,{class:`flex items-center gap-2`,children:[W(`span`,{class:`flex size-7 shrink-0 items-center justify-center rounded-lg bg-white text-teal-700`,children:W(qt,{class:`size-4`})}),W(`p`,{class:`m-0 text-sm font-black text-slate-900`,children:`전송하지 않는 정보`})]}),W(`div`,{class:`mt-3 space-y-3`,children:[W(`div`,{children:[W(`p`,{class:`m-0 text-[11px] font-extrabold text-teal-800`,children:`개인 식별 · 네트워크 정보`}),W(`p`,{class:`mt-1 mb-0 text-xs font-semibold leading-5 text-slate-700`,children:`호스트명 · WAN IP · Wi-Fi SSID/MAC`})]}),W(`div`,{children:[W(`p`,{class:`m-0 text-[11px] font-extrabold text-teal-800`,children:`이용 내용 및 원문 데이터`}),W(`p`,{class:`mt-1 mb-0 text-xs font-semibold leading-5 text-slate-700`,children:`DNS 요청 내용 · 시스템 로그 원문`})]})]})]})]})})]})]})}function jy(e){let[t,n]=L(!1);return W(`label`,{class:`block min-w-0`,for:e.id,children:[W(`span`,{class:`mb-2 block text-sm font-extrabold text-slate-800`,children:e.label}),W(`span`,{class:`relative block min-w-0`,children:[W(`input`,{autoComplete:e.autoComplete,class:`ssh-password-input min-h-11 w-full rounded-xl border border-slate-300 bg-white px-3 py-2.5 pr-12 text-sm font-bold text-slate-900 outline-none transition focus:border-teal-500 focus:ring-2 focus:ring-teal-100 disabled:cursor-wait disabled:opacity-60`,disabled:e.disabled,id:e.id,name:e.name,onInput:t=>e.onInput(t.currentTarget.value),placeholder:e.placeholder,...t?{type:`text`}:{type:`password`},value:e.value}),W(`button`,{"aria-label":t?`${e.label} 숨기기`:`${e.label} 표시`,"aria-pressed":t,class:`absolute inset-y-0 right-1 my-auto flex size-9 items-center justify-center rounded-lg text-slate-500 transition hover:bg-slate-100 hover:text-slate-800 disabled:opacity-50`,disabled:e.disabled,onClick:()=>n(e=>!e),title:t?`비밀번호 숨기기`:`비밀번호 표시`,type:`button`,children:W(t?bn:yn,{class:`size-4`})})]})]})}function My(e){return W(`li`,{class:`flex min-w-0 items-center gap-1.5 text-xs font-bold ${e.met?`text-emerald-700`:`text-slate-500`}`,children:[W(Sn,{class:`size-3.5 shrink-0`,"aria-hidden":`true`}),W(`span`,{children:e.label})]})}function Ny(e){let[t,n]=L(``),[r,i]=L(``),[a,o]=L(``),[s,c]=L(!1),[l,u]=L(null),d=Bn(r),f=Vn(d),p=a.length>0&&r===a,m=t.length>0&&r.length>0&&t!==r,h=!s&&m&&f&&p,g=async()=>{if(u(null),!t)u(`현재 관리자 비밀번호를 입력해 주세요.`);else if(!f)u(`새 비밀번호는 8자 이상이며 영문과 숫자를 각각 하나 이상 포함해야 합니다.`);else if(t===r)u(`새 관리자 비밀번호는 현재 비밀번호와 다르게 설정해 주세요.`);else if(r!==a)u(`새 비밀번호 확인 값이 일치하지 않습니다.`);else{c(!0);try{await vy(t,r),n(``),i(``),o(``);try{await Ee()}catch{}c(!1),e.onChanged()}catch(e){u(q(e,`관리자 비밀번호를 변경하지 못했습니다. 잠시 후 다시 시도해 주세요.`)),c(!1)}}};return W(wy,{description:`현재 비밀번호를 확인한 뒤 공유기 관리자 비밀번호를 변경합니다. 변경이 완료되면 새 비밀번호로 다시 로그인해야 합니다.`,icon:W(_n,{class:`size-5`}),title:`관리자 비밀번호`,children:W(`form`,{class:`space-y-4`,noValidate:!0,onSubmit:e=>{e.preventDefault(),g()},children:[l&&W(`div`,{"aria-live":`polite`,class:`rounded-xl border border-rose-200 bg-rose-50 px-4 py-3 text-sm font-bold text-rose-800`,role:`alert`,children:l}),W(jy,{autoComplete:`current-password`,disabled:s,id:`smartsafehub-current-admin-password`,label:`현재 비밀번호`,name:`current-password`,onInput:n,placeholder:`현재 관리자 비밀번호`,value:t}),W(jy,{autoComplete:`new-password`,disabled:s,id:`smartsafehub-new-admin-password`,label:`새 비밀번호`,name:`new-password`,onInput:i,placeholder:`새 관리자 비밀번호`,value:r}),W(`ul`,{"aria-label":`새 비밀번호 요구 사항`,class:`m-0 grid list-none grid-cols-1 gap-2 p-0 sm:grid-cols-3`,children:[W(My,{label:`8자 이상`,met:d.length}),W(My,{label:`영문자 포함`,met:d.letter}),W(My,{label:`숫자 포함`,met:d.number})]}),W(`div`,{class:`pt-2`,children:W(jy,{autoComplete:`new-password`,disabled:s,id:`smartsafehub-confirm-admin-password`,label:`새 비밀번호 확인`,name:`confirm-password`,onInput:o,placeholder:`새 관리자 비밀번호 다시 입력`,value:a})}),a.length>0&&W(`p`,{"aria-live":`polite`,class:`m-0 text-xs font-bold ${p?`text-emerald-700`:`text-rose-700`}`,role:`status`,children:p?`새 비밀번호가 일치합니다.`:`새 비밀번호 확인 값이 일치하지 않습니다.`}),r.length>0&&t===r&&W(`p`,{class:`m-0 text-xs font-bold text-amber-700`,children:`새 비밀번호는 현재 비밀번호와 다르게 설정해 주세요.`}),W(`div`,{class:`pt-2`,children:W(`div`,{class:`rounded-xl border border-slate-200 bg-slate-50 p-3 text-xs leading-5 text-slate-600`,children:`비밀번호는 현재 공유기에 직접 적용되며 외부 서버로 전송되지 않습니다. 변경 후 현재 로그인 세션은 종료됩니다.`})}),W(`div`,{class:`rounded-xl border border-amber-200 bg-amber-50 p-3 text-xs leading-5 text-amber-900`,children:[W(`strong`,{class:`block font-extrabold`,children:`비밀번호를 잊었을 때`}),`전원이 켜진 상태에서 Reset 버튼을 5~9초 누른 뒤 놓으면 관리자 비밀번호만 복구할 수 있습니다. 네트워크, Wi-Fi와 SafeShield 설정은 유지됩니다. 10초 이상 누르면 모든 사용자 설정을 초기화하는 기기 초기화가 실행됩니다.`]}),W(`div`,{class:`flex justify-end`,children:W(`button`,{class:`inline-flex min-h-11 w-full items-center justify-center rounded-xl bg-teal-700 px-4 py-2.5 text-sm font-extrabold text-white transition hover:bg-teal-800 disabled:cursor-not-allowed disabled:opacity-50 sm:w-auto`,disabled:!h,type:`submit`,children:s?`비밀번호 변경 중`:`비밀번호 변경`})})]})})}function Py(e){let t=z(null),[n,r]=L(null),[i,a]=L(!1),o=e.action!==null;R(()=>{e.validated||a(!1)},[e.validated]);let s=()=>{!o&&!e.restoreAccepted&&t.current?.click()},c=async()=>{n&&await e.onUpload(n)&&(r(null),t.current&&(t.current.value=``))};return W(wy,{description:`펌웨어 업데이트나 복구 전에 현재 OpenWrt 설정을 표준 백업 파일로 저장하고, 필요할 때 검증 후 복원합니다.`,icon:W(gn,{class:`size-5`}),title:`설정 백업 및 복원`,children:[(e.error||e.message)&&W(`div`,{class:`mb-5 flex min-w-0 items-start justify-between gap-3 rounded-xl border px-4 py-3 text-sm font-bold ${e.error?`border-rose-200 bg-rose-50 text-rose-800`:`border-emerald-200 bg-emerald-50 text-emerald-800`}`,children:[W(`span`,{class:`min-w-0`,children:e.error||e.message}),!e.restoreAccepted&&W(`button`,{class:`shrink-0 rounded-lg px-2 py-1 text-xs font-extrabold hover:bg-black/5`,onClick:e.onDismissFeedback,type:`button`,children:`닫기`})]}),W(`div`,{class:`grid min-w-0 grid-cols-1 gap-3`,children:[W(`div`,{class:`rounded-xl border border-slate-200 bg-slate-50 p-4 sm:p-5`,children:[W(`p`,{class:`m-0 text-sm font-black text-slate-900`,children:`현재 설정 백업`}),W(`p`,{class:`mt-2 mb-0 text-xs leading-5 text-slate-600`,children:`네트워크, Wi-Fi, SmartSafeHub와 SafeShield 등 OpenWrt가 관리하는 설정을 표준 백업 파일로 저장합니다.`}),W(`button`,{class:`mt-4 inline-flex min-h-11 w-full items-center justify-center gap-2 rounded-xl bg-teal-700 px-4 py-2.5 text-sm font-extrabold text-white transition hover:bg-teal-800 disabled:cursor-wait disabled:opacity-60 sm:w-auto`,disabled:o||e.restoreAccepted,onClick:e.onDownload,type:`button`,children:[W(hn,{class:`size-4`}),e.action===`download`?`백업 생성 중`:`설정 백업 다운로드`]}),W(`p`,{class:`mt-3 mb-0 text-xs font-bold leading-5 text-amber-700`,children:`백업에는 Wi-Fi 비밀번호, 관리자 설정, VPN 키나 기기 인증 정보 같은 민감한 값이 포함될 수 있으므로 안전한 위치에 보관해 주세요.`})]}),W(`div`,{class:`rounded-xl border border-slate-200 bg-white p-4 sm:p-5`,children:[W(`p`,{class:`m-0 text-sm font-black text-slate-900`,children:`설정 복원`}),W(`p`,{class:`mt-2 mb-0 text-xs leading-5 text-slate-600`,children:`SmartSafeHub 또는 OpenWrt 백업을 업로드하고 검증한 뒤 복원합니다. 최대 16MB까지 사용할 수 있습니다.`}),e.validated?W(`div`,{class:`mt-4 rounded-xl border border-emerald-200 bg-emerald-50 p-4`,children:[W(`div`,{class:`flex min-w-0 flex-col gap-1 sm:flex-row sm:items-center sm:justify-between`,children:W(`div`,{class:`min-w-0`,children:[W(`p`,{class:`m-0 break-words text-sm font-extrabold text-emerald-900`,children:e.validated.filename}),W(`p`,{class:`mt-1 mb-0 text-xs font-bold text-emerald-700`,children:[Do(e.validated.sizeBytes),` · 검증 완료`]})]})}),i?W(`div`,{class:`mt-4 rounded-xl border border-rose-200 bg-white p-4`,children:[W(`p`,{class:`m-0 text-sm font-black text-rose-900`,children:`현재 설정을 백업 파일의 내용으로 덮어쓰시겠습니까?`}),W(`p`,{class:`mt-2 mb-0 text-xs leading-5 text-rose-700`,children:`LAN 주소, Wi-Fi, 관리자 접속 정보가 바뀌어 현재 연결이 끊길 수 있습니다. 복원 직후 공유기가 자동으로 재부팅됩니다. 같은 장치와 호환되는 백업만 사용해 주세요.`}),W(`div`,{class:`mt-4 flex flex-col gap-3 sm:flex-row sm:flex-wrap`,children:[W(`button`,{class:`inline-flex min-h-10 w-full items-center justify-center rounded-xl border border-slate-300 bg-white px-4 py-2 text-sm font-extrabold text-slate-700 transition hover:bg-slate-50 disabled:opacity-60 sm:w-auto`,disabled:o,onClick:()=>a(!1),type:`button`,children:`취소`}),W(`button`,{class:`inline-flex min-h-10 w-full items-center justify-center rounded-xl bg-rose-700 px-4 py-2 text-sm font-extrabold text-white transition hover:bg-rose-800 disabled:cursor-wait disabled:opacity-60 sm:w-auto`,disabled:o,onClick:()=>void e.onRestore(),type:`button`,children:e.action===`restore`?`설정 복원 중`:`설정 복원 및 재부팅`})]})]}):W(`div`,{class:`mt-4 flex flex-col gap-3 sm:flex-row sm:flex-wrap`,children:[W(`button`,{class:`inline-flex min-h-10 w-full items-center justify-center rounded-xl border border-slate-300 bg-white px-4 py-2 text-sm font-extrabold text-slate-700 transition hover:bg-slate-50 disabled:opacity-60 sm:w-auto`,disabled:o||e.restoreAccepted,onClick:()=>void e.onDiscard(),type:`button`,children:e.action===`discard`?`파일 삭제 중`:`파일 삭제`}),W(`button`,{class:`inline-flex min-h-10 w-full items-center justify-center rounded-xl bg-amber-600 px-4 py-2 text-sm font-extrabold text-white transition hover:bg-amber-700 disabled:opacity-60 sm:w-auto`,disabled:o||e.restoreAccepted,onClick:()=>a(!0),type:`button`,children:`복원 준비`})]})]}):W(b,{children:[W(`input`,{accept:`.tar.gz,.tgz,application/gzip,application/x-gzip`,class:`sr-only`,disabled:o||e.restoreAccepted,onChange:e=>r(e.currentTarget.files?.[0]??null),ref:t,type:`file`}),W(`button`,{class:`mt-4 flex min-h-11 w-full items-center justify-between gap-3 rounded-xl border border-dashed border-slate-300 bg-slate-50 px-4 py-3 text-left text-sm font-bold text-slate-700 transition hover:border-teal-400 hover:bg-teal-50 disabled:cursor-wait disabled:opacity-60`,disabled:o||e.restoreAccepted,onClick:s,type:`button`,children:[W(`span`,{class:`min-w-0 truncate`,children:n?.name??`복원할 백업 파일 선택`}),W(`span`,{class:`shrink-0 text-xs font-extrabold text-teal-700`,children:`찾아보기`})]}),n&&W(`div`,{class:`mt-3 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between`,children:[W(`span`,{class:`text-xs font-bold text-slate-500`,children:Do(n.size)}),W(`button`,{class:`inline-flex min-h-10 w-full items-center justify-center rounded-xl border border-teal-300 bg-white px-4 py-2 text-sm font-extrabold text-teal-800 transition hover:bg-teal-50 disabled:cursor-wait disabled:opacity-60 sm:w-auto`,disabled:o||e.restoreAccepted,onClick:()=>void c(),type:`button`,children:e.action===`upload`?`업로드 중 ${Math.round(e.uploadProgress??0)}%`:e.action===`validate`?`백업 검증 중`:`업로드 및 검증`})]})]})]})]})]})}function Fy(e){return W(wy,{description:`공유기 재부팅과 SmartSafeHub에서 제공하지 않는 고급 관리 도구를 한곳에서 제공합니다.`,...e.className===void 0?{}:{className:e.className},icon:W(an,{class:`size-5`}),title:`시스템 도구`,children:W(`div`,{class:`space-y-3`,children:[W(`section`,{"aria-labelledby":`router-reboot-heading`,class:`rounded-xl border border-slate-200 bg-slate-50 p-4`,children:[W(`div`,{class:`flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between`,children:[W(`div`,{class:`flex min-w-0 items-start gap-3`,children:[W(`span`,{class:`flex size-9 shrink-0 items-center justify-center rounded-lg bg-rose-50 text-rose-700`,children:W(mn,{class:`size-4`})}),W(`div`,{class:`min-w-0`,children:[W(`p`,{class:`m-0 text-sm font-black text-rose-950`,id:`router-reboot-heading`,children:`공유기 재부팅`}),W(`p`,{class:`mt-1 mb-0 text-xs leading-5 text-slate-500`,children:`재부팅 중에는 인터넷과 Wi-Fi 연결이 잠시 중단됩니다.`})]})]}),!e.confirmingReboot&&W(`button`,{class:`inline-flex min-h-10 w-full shrink-0 items-center justify-center rounded-xl border border-rose-300 bg-white px-3 py-2 text-xs font-extrabold text-rose-700 transition hover:bg-rose-50 disabled:opacity-60 sm:w-auto`,disabled:e.action!==null||e.rebootAccepted,onClick:e.onConfirmReboot,type:`button`,children:`재부팅`})]}),e.confirmingReboot&&W(`div`,{class:`mt-3 rounded-xl border border-rose-200 bg-rose-50 p-3`,children:[W(`p`,{class:`m-0 text-xs font-extrabold leading-5 text-rose-900`,children:`지금 공유기를 재부팅하시겠습니까? 저장되지 않은 LuCI 설정이 있다면 먼저 저장해 주세요.`}),W(`div`,{class:`mt-3 flex flex-col gap-2 sm:flex-row sm:flex-wrap`,children:[W(`button`,{class:`inline-flex min-h-10 w-full items-center justify-center rounded-xl border border-slate-300 bg-white px-3 py-2 text-xs font-extrabold text-slate-700 transition hover:bg-slate-50 sm:w-auto`,disabled:e.action!==null,onClick:e.onCancelReboot,type:`button`,children:`취소`}),W(`button`,{class:`inline-flex min-h-10 w-full items-center justify-center rounded-xl bg-rose-700 px-3 py-2 text-xs font-extrabold text-white transition hover:bg-rose-800 disabled:cursor-wait disabled:opacity-60 sm:w-auto`,disabled:e.action!==null,onClick:e.onReboot,type:`button`,children:e.action===`reboot`?`재부팅 요청 중`:`지금 재부팅`})]})]})]}),W(`section`,{"aria-labelledby":`advanced-tools-heading`,class:`rounded-xl border border-slate-200 bg-slate-50 p-4`,children:[W(`div`,{class:`min-w-0`,children:[W(`p`,{class:`m-0 text-sm font-black text-slate-900`,id:`advanced-tools-heading`,children:`고급 도구`}),W(`p`,{class:`mt-1 mb-0 text-xs leading-5 text-slate-500`,children:`SmartSafeHub에서 아직 제공하지 않는 상세 시스템 설정이나 원본 로그가 필요할 때 사용합니다.`})]}),W(`div`,{class:`mt-3 flex flex-col gap-2 sm:flex-row sm:flex-wrap`,children:[W(`a`,{class:`inline-flex min-h-10 w-full items-center justify-center rounded-xl border border-slate-300 bg-white px-3 py-2 text-xs font-extrabold text-slate-800 no-underline transition hover:bg-slate-50 sm:w-auto`,href:e.advancedSystemUrl,children:`LuCI 고급 설정 열기`}),W(`a`,{class:`inline-flex min-h-10 w-full items-center justify-center rounded-xl border border-slate-300 bg-white px-3 py-2 text-xs font-extrabold text-slate-800 no-underline transition hover:bg-slate-50 sm:w-auto`,href:e.logsUrl,children:`시스템 로그 열기`})]})]})]})})}var Iy=[{value:`mon`,label:`월요일`},{value:`tue`,label:`화요일`},{value:`wed`,label:`수요일`},{value:`thu`,label:`목요일`},{value:`fri`,label:`금요일`},{value:`sat`,label:`토요일`},{value:`sun`,label:`일요일`}];function Ly(e){let[t,n]=L(!1),[r,i]=L(`weekly`),[a,o]=L(`sun`),[s,c]=L(`04:00`);R(()=>{e.data&&(n(e.data.enabled),i(e.data.frequency),o(e.data.dayOfWeek),c(e.data.time))},[e.data?.dayOfWeek,e.data?.enabled,e.data?.frequency,e.data?.time]);let l=e.data!==null&&(t!==e.data.enabled||r!==e.data.frequency||a!==e.data.dayOfWeek||s!==e.data.time),u=Iy.find(e=>e.value===a),d=t?r===`daily`?`매일 ${s}`:`매주 ${u?.label??`일요일`} ${s}`:`예약 재부팅 꺼짐`;return W(`section`,{"aria-labelledby":`scheduled-reboot-heading`,class:`min-w-0`,children:[W(`div`,{class:`flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between`,children:[W(`div`,{class:`flex min-w-0 items-start gap-3`,children:[W(`span`,{class:`flex size-11 shrink-0 items-center justify-center rounded-xl bg-slate-100 text-slate-600`,children:W(xn,{class:`size-5`})}),W(`div`,{class:`min-w-0`,children:[W(`h2`,{class:`m-0 text-lg font-black text-slate-950`,id:`scheduled-reboot-heading`,children:`예약 재부팅`}),W(`p`,{class:`mt-1.5 mb-0 text-sm leading-6 text-slate-500`,children:`공유기를 정해진 시간에 자동으로 재부팅합니다. 기본값은 꺼짐이며 필요한 장치에서만 사용하세요.`})]})]}),W(`div`,{class:`flex shrink-0 flex-wrap items-center justify-between gap-3 sm:justify-end`,children:[l&&W(`span`,{"aria-live":`polite`,class:`inline-flex items-center gap-1.5 rounded-full border border-amber-200 bg-amber-50 px-2.5 py-1 text-xs font-extrabold text-amber-800`,role:`status`,children:[W(Cn,{"aria-hidden":`true`,class:`size-3.5 shrink-0`}),`저장되지 않음`]}),W(`span`,{class:`text-xs font-extrabold text-slate-500`,children:t?`사용 중`:`꺼짐`}),W(`button`,{"aria-checked":t,"aria-label":`예약 재부팅 사용`,class:`ssh-switch-control relative inline-flex shrink-0 rounded-full border transition focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-100 disabled:cursor-not-allowed disabled:opacity-50 ${t?`border-teal-600 bg-teal-600`:`border-slate-300 bg-slate-200`}`,disabled:e.loading||e.saving||!e.data,onClick:()=>n(e=>!e),role:`switch`,type:`button`,children:W(`span`,{"aria-hidden":`true`,class:`ssh-switch-thumb absolute top-1 shadow-sm transition-[left] ${t?`left-6`:`left-1`}`})})]})]}),(e.saveError||e.saveMessage)&&W(`div`,{class:`mt-4 flex min-w-0 items-start justify-between gap-3 rounded-xl border px-4 py-3 text-sm font-bold ${e.saveError?`border-rose-200 bg-rose-50 text-rose-800`:`border-emerald-200 bg-emerald-50 text-emerald-800`}`,children:[W(`span`,{class:`min-w-0`,children:e.saveError||e.saveMessage}),W(`button`,{class:`shrink-0 rounded-lg px-2 py-1 text-xs font-extrabold hover:bg-black/5`,onClick:e.onDismissFeedback,type:`button`,children:`닫기`})]}),e.error&&!e.data?W(`div`,{class:`mt-4 rounded-xl border border-rose-200 bg-rose-50 p-4`,children:[W(`p`,{class:`m-0 text-sm font-bold text-rose-800`,children:e.error}),W(`button`,{class:`mt-3 inline-flex min-h-10 items-center rounded-xl border border-rose-300 bg-white px-3 py-2 text-xs font-extrabold text-rose-700 transition hover:bg-rose-100`,onClick:e.onRetry,type:`button`,children:`다시 불러오기`})]}):W(b,{children:[W(`div`,{class:`mt-4 grid min-w-0 grid-cols-1 gap-4 md:grid-cols-3`,children:[W(`div`,{children:[W(`label`,{class:`mb-2 block text-sm font-extrabold text-slate-800`,for:`scheduled-reboot-frequency`,children:`주기`}),W(W_,{ariaLabel:`예약 재부팅 주기`,disabled:!t||e.saving||!e.data,id:`scheduled-reboot-frequency`,onChange:e=>i(e),options:[{value:`weekly`,label:`매주`},{value:`daily`,label:`매일`}],value:r})]}),W(`div`,{children:[W(`label`,{class:`mb-2 block text-sm font-extrabold text-slate-800`,for:`scheduled-reboot-day`,children:`요일`}),W(W_,{ariaLabel:`예약 재부팅 요일`,disabled:!t||r!==`weekly`||e.saving||!e.data,id:`scheduled-reboot-day`,onChange:e=>o(e),options:Iy.map(e=>({value:e.value,label:e.label})),value:a})]}),W(`div`,{children:[W(`label`,{class:`mb-2 block text-sm font-extrabold text-slate-800`,for:`scheduled-reboot-time-hour`,children:`재부팅 시간`}),W(Sy,{ariaLabel:`예약 재부팅 시간`,disabled:!t||e.saving||!e.data,id:`scheduled-reboot-time`,onChange:c,value:s})]})]}),W(`div`,{class:`mt-4 flex flex-col gap-3 rounded-xl bg-slate-50 p-4 sm:flex-row sm:items-end sm:justify-between`,children:[W(`div`,{class:`min-w-0`,children:[W(`p`,{class:`m-0 text-xs font-extrabold uppercase tracking-[0.12em] text-slate-500`,children:`예약 일정`}),W(`p`,{class:`mt-1.5 mb-0 text-sm font-extrabold text-slate-900`,children:d}),W(`p`,{class:`mt-1 mb-0 text-xs leading-5 text-slate-500`,children:[e.data?.timezone??`장치 시간대`,` 기준 · 업데이트 작업 중이면 15분 단위로 최대 2시간 연기합니다.`]})]}),W(`button`,{class:`inline-flex min-h-11 w-full shrink-0 items-center justify-center rounded-xl bg-teal-700 px-4 py-2.5 text-sm font-extrabold text-white transition hover:bg-teal-800 disabled:cursor-not-allowed disabled:opacity-50 sm:w-auto`,disabled:!l||e.saving||!e.data,onClick:()=>void e.onSave({enabled:t,frequency:r,dayOfWeek:a,time:s}),type:`button`,children:e.saving?`예약 저장 중`:`예약 저장`})]})]})]})}function Ry({activeTab:e,action:t,backupAction:n,backupError:r,backupMessage:i,backupRestoreAccepted:a,backupUploadProgress:o,backupValidated:s,data:c,error:l,feedbackError:u,feedbackMessage:d,health:f,healthActionError:p,healthActionMessage:m,healthError:h,healthLoading:g,healthRunning:_,healthSavingReporter:v,loading:y,rebootAccepted:b,scheduledRebootData:x,scheduledRebootError:S,scheduledRebootLoading:C,scheduledRebootSaveError:w,scheduledRebootSaveMessage:T,scheduledRebootSaving:E,timeData:D,timeError:O,timeLoading:k,timeSaveError:A,timeSaveMessage:j,timeSaving:M,timeSyncing:N,onBackupDiscard:P,onBackupDownload:F,onBackupRestore:I,onBackupUpload:ee,onDismissBackupFeedback:te,onDismissFeedback:ne,onDismissHealthFeedback:re,onDismissTimeFeedback:ie,onDownloadDiagnostics:ae,onRunHealth:oe,onSetHealthReporter:se,onReboot:ce,onRetry:le,onDismissScheduledRebootFeedback:ue,onSaveScheduledReboot:de,onSaveTimezone:fe,onSyncTime:pe,onAdministratorPasswordChanged:me}){let[he,ge]=L(!1),_e=ve(`/admin/system`),ye=ve(`/admin/status/logs`);return W(`section`,{class:`min-w-0 space-y-7`,children:[(u||d)&&W(`div`,{class:`flex min-w-0 flex-col gap-3 rounded-xl border px-4 py-3 text-sm font-bold sm:flex-row sm:items-center sm:justify-between ${u?`border-rose-200 bg-rose-50 text-rose-800`:`border-emerald-200 bg-emerald-50 text-emerald-800`}`,children:[W(`span`,{class:`min-w-0`,children:u||d}),!b&&W(`button`,{class:`shrink-0 self-start rounded-lg px-3 py-1.5 text-xs font-extrabold hover:bg-black/5 sm:self-auto`,onClick:ne,type:`button`,children:`닫기`})]}),W(ci,{active:e,label:`설정 메뉴`,tabs:[{id:`diagnostics`,label:`진단 및 지원`,href:`#settings`},{id:`time`,label:`시간 및 예약`,href:`#settings/time`},{id:`management`,label:`관리 및 복구`,href:`#settings/management`}]}),W(`section`,{"aria-labelledby":`diagnostics-tab`,class:`min-w-0`,hidden:e!==`diagnostics`,id:`diagnostics-panel`,role:`tabpanel`,children:[l&&!c?W(`div`,{class:`mb-4 rounded-xl border border-amber-200 bg-amber-50 p-4 text-sm font-semibold text-amber-900`,role:`status`,children:[W(`p`,{class:`m-0`,children:[`시스템 정보를 불러오지 못해 진단 파일 다운로드가 제한될 수 있습니다. `,l]}),W(`button`,{class:`mt-2 rounded-lg border border-current px-3 py-1.5 text-xs font-extrabold`,onClick:le,type:`button`,children:`다시 시도`})]}):y&&!c?W(`p`,{class:`mb-4 text-sm font-semibold text-slate-500`,role:`status`,children:`진단 파일에 필요한 시스템 정보를 확인하고 있습니다.`}):null,W(J,{eyebrow:`Device health`,title:`진단 및 지원`,description:`장치 진단과 원격 상태 보고를 확인하고 개인정보 보호 정책을 관리합니다.`}),W(`div`,{class:`grid min-w-0 grid-cols-1 gap-4 lg:grid-cols-2`,children:W(Ay,{actionError:p,actionMessage:m,data:f,downloadBusy:t===`diagnostics`,error:h,loading:g,onDismissFeedback:re,onDownload:ae,onRetry:le,onRun:oe,onSetReporter:se,running:_,savingReporter:v})})]}),W(`section`,{"aria-labelledby":`time-tab`,class:`min-w-0`,hidden:e!==`time`,id:`time-panel`,role:`tabpanel`,children:[W(J,{eyebrow:`Device settings`,title:`시간 및 예약`,description:`시간 기준과 예약 재부팅을 SmartSafeHub에서 직접 관리합니다.`}),W(`div`,{class:`grid min-w-0 grid-cols-1 gap-4 lg:grid-cols-2`,children:W(Ty,{data:D,error:O,loading:k,onDismissFeedback:ie,onDismissScheduledRebootFeedback:ue,onRetry:le,onSave:fe,onSaveScheduledReboot:de,onSync:pe,saveError:A,saveMessage:j,saving:M,scheduledRebootData:x,scheduledRebootError:S,scheduledRebootLoading:C,scheduledRebootSaveError:w,scheduledRebootSaveMessage:T,scheduledRebootSaving:E,syncing:N})})]}),W(`section`,{"aria-labelledby":`management-tab`,class:`min-w-0`,hidden:e!==`management`,id:`management-panel`,role:`tabpanel`,children:[W(J,{eyebrow:`System management`,title:`시스템 관리`,description:`관리자 비밀번호, 설정 백업·복원과 공유기 시스템 도구를 한곳에서 관리합니다.`}),W(`div`,{class:`grid min-w-0 grid-cols-1 gap-4 lg:grid-cols-2`,children:[W(Ny,{onChanged:me}),W(Py,{action:n,error:r,message:i,onDiscard:P,onDismissFeedback:te,onDownload:F,onRestore:I,onUpload:ee,restoreAccepted:a,uploadProgress:o,validated:s}),W(Fy,{action:t,className:`lg:col-span-2`,advancedSystemUrl:_e,confirmingReboot:he,logsUrl:ye,onCancelReboot:()=>ge(!1),onConfirmReboot:()=>ge(!0),onReboot:ce,rebootAccepted:b})]})]})]})}var zy=`https://www.smartsafehub.com/dashboard/devices/`;async function By(e){if(navigator.clipboard?.writeText)try{await navigator.clipboard.writeText(e);return}catch{}let t=document.createElement(`textarea`);t.value=e,t.setAttribute(`readonly`,``),t.style.position=`fixed`,t.style.inset=`0 auto auto -9999px`,t.style.opacity=`0`,document.body.appendChild(t),t.select(),t.setSelectionRange(0,e.length);let n=document.execCommand(`copy`);if(t.remove(),!n)throw Error(`clipboard_copy_failed`)}function Vy(e){return(e||`free`).toUpperCase()}function Hy(e){return e===`FREE`?`free`:e===`PRO`?`pro`:e===`ULTIMATE`?`ultimate`:`paid`}function Uy({plan:e}){let t=Hy(e),n=t!==`free`;return W(`span`,{class:`ssh-safeshield-plan-badge`,"data-compact":`false`,"data-tier":t,title:n?`${e} 멤버십`:`무료 플랜`,children:[W(`span`,{"aria-hidden":`true`,class:`ssh-safeshield-plan-badge-mark`,children:n?`✦`:`•`}),W(`span`,{children:e})]})}function Wy(e){if(!e.pairingCode||!e.pairingExpiresAt)return!1;let t=Date.parse(e.pairingExpiresAt);return Number.isFinite(t)&&t<=Date.now()}function Gy({data:e,error:t,loading:n,pairingBusy:r,refreshing:i,syncError:a,onRequestPairing:o,onRetry:s,demo:c=!1,onDemoAction:l}){let[u,d]=L(null),[f,p]=L(!1);if(R(()=>{if(!f)return;let e=window.setTimeout(()=>p(!1),1800);return()=>window.clearTimeout(e)},[f]),n&&!e)return W(vs,{});if(t&&!e)return W(ys,{message:t,onRetry:s});let m=e,h=m?.accountRegistered===!0,g=Vy(m?.plan??null),_=m?Wy(m):!1,v=!h&&!_?m?.pairingCode:null,y=r||m?.pairingOperation?.phase===`starting`||m?.pairingOperation?.phase===`running`;async function b(){d(null),p(!1);try{await o()}catch{d(`계정 연결 코드를 발급하지 못했습니다. 잠시 후 다시 시도해 주세요.`)}}async function x(){if(v)try{await By(v),d(null),p(!0)}catch{p(!1),d(`연결 코드를 복사하지 못했습니다. 코드를 직접 선택해 복사해 주세요.`)}}return W(`div`,{class:`min-w-0`,children:[W(J,{eyebrow:`Account connection`,title:`기기 계정 연결`,description:`계정에 연결하면 웹사이트에서 기기를 관리하고 Cloud 기능과 구독 권한을 사용할 수 있습니다.`}),W(`div`,{class:`grid items-start gap-5 xl:items-stretch xl:grid-cols-[minmax(0,1.2fr)_minmax(18rem,0.8fr)]`,children:[W(`section`,{class:`rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5 sm:p-6 xl:h-full`,children:h?W(`div`,{children:[W(`div`,{class:`flex items-center gap-3`,children:[W(`span`,{class:`grid size-10 shrink-0 place-items-center rounded-full bg-emerald-100 text-emerald-700`,children:W(Sn,{class:`size-5`})}),W(`div`,{children:[W(`p`,{class:`m-0 text-base font-black text-slate-950`,children:`SmartSafeHub 계정에 연결됨`}),W(`p`,{class:`mt-1 mb-0 text-sm leading-6 text-slate-600`,children:`이 기기가 계정과 정상적으로 연결되어 있습니다. 연결 코드는 더 이상 필요하지 않습니다.`})]})]}),c?W(`button`,{class:`mt-5 inline-flex min-h-11 items-center justify-center rounded-xl border border-emerald-700 bg-emerald-700 px-4 py-2 text-sm font-extrabold text-white transition hover:bg-emerald-800`,onClick:l,type:`button`,children:`웹사이트에서 기기 관리`}):W(`a`,{class:`mt-5 inline-flex min-h-11 items-center justify-center rounded-xl border border-emerald-700 bg-emerald-700 px-4 py-2 text-sm font-extrabold text-white no-underline transition hover:bg-emerald-800 focus:outline-none focus-visible:ring-4 focus-visible:ring-emerald-100`,href:zy,rel:`noopener noreferrer`,target:`_blank`,children:`웹사이트에서 기기 관리`}),W(`p`,{class:`mt-4 mb-0 border-t border-slate-100 pt-4 text-xs font-semibold leading-5 text-slate-500`,children:`웹사이트에서 기기 등록을 해제하면 공유기도 다음 계정 상태 동기화에서 자동으로 연결 해제를 반영합니다.`})]}):W(`div`,{children:[W(`div`,{children:[W(`div`,{class:`flex flex-wrap items-center justify-between gap-2`,children:[W(`p`,{class:`m-0 text-sm font-black text-slate-900`,children:`계정 연결 코드`}),W(`span`,{class:`inline-flex items-center gap-1.5 rounded-full border border-amber-200 bg-amber-50 px-2.5 py-1 text-[0.7rem] font-black text-amber-800`,children:[W(dn,{class:`size-3.5`}),`10분 동안 유효`]})]}),W(`p`,{class:`mt-1 mb-0 text-sm leading-6 text-slate-500`,children:`코드를 발급한 뒤 smartsafehub.com에 로그인하여 기기를 연결하세요.`})]}),v?W(`div`,{class:`mt-4 flex flex-col gap-3 rounded-2xl border border-teal-200 bg-teal-50 p-4 sm:flex-row sm:items-center sm:justify-between`,role:`group`,"aria-label":`계정 연결 코드`,children:[W(`div`,{class:`min-w-0`,children:[W(`span`,{class:`block text-[0.7rem] font-black text-teal-700`,children:`연결 코드`}),W(`code`,{class:`mt-1 block select-all break-all font-mono text-2xl font-black tracking-[0.14em] text-slate-950`,children:v})]}),W(`button`,{"aria-label":`계정 연결 코드 복사`,class:`inline-flex min-h-11 shrink-0 items-center justify-center gap-2 rounded-xl border border-teal-300 bg-white px-4 text-sm font-black text-teal-800 transition hover:bg-teal-100 focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-100`,onClick:()=>void x(),type:`button`,children:[W(f?Sn:vn,{class:`size-4`}),W(`span`,{"aria-live":`polite`,children:f?`복사됨`:`복사`})]})]}):_?W(`p`,{class:`mt-4 mb-0 rounded-xl border border-amber-200 bg-amber-50 px-4 py-3 text-sm font-bold text-amber-800`,children:`이전 연결 코드가 만료되었습니다. 새 코드를 발급해 주세요.`}):null,W(`button`,{class:`mt-4 inline-flex min-h-11 w-full items-center justify-center gap-2 rounded-xl border border-teal-700 bg-teal-700 px-4 py-2 text-sm font-black text-white transition hover:bg-teal-800 focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-100 disabled:cursor-not-allowed disabled:opacity-50`,disabled:y,onClick:()=>void b(),type:`button`,children:[y?W(pn,{class:`size-4 animate-spin`}):null,y?`발급 중…`:v?`새 코드 발급`:`연결 코드 발급`]}),m?.pairingOperation?.phase===`failed`?W(`p`,{class:`mt-3 mb-0 text-sm font-semibold text-red-700`,children:`연결 코드 발급에 실패했습니다. 다시 시도해 주세요.`}):null,u?W(`p`,{class:`mt-3 mb-0 text-sm font-semibold text-red-700`,children:u}):null]})}),W(`aside`,{class:`rounded-2xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-900/5 sm:p-6 xl:h-full`,children:[W(`p`,{class:`m-0 text-[0.68rem] font-black uppercase tracking-[0.16em] text-slate-400`,children:`Device status`}),W(`h2`,{class:`mt-2 mb-0 text-lg font-black tracking-tight text-slate-950`,children:`연결 상태`}),W(`dl`,{class:`mt-5 mb-0 grid gap-4`,children:[W(`div`,{class:`flex items-center justify-between gap-4 border-b border-slate-100 pb-4`,children:[W(`dt`,{class:`text-sm font-bold text-slate-500`,children:`계정`}),W(`dd`,{class:`m-0 text-sm font-black ${h?`text-emerald-700`:`text-slate-700`}`,children:h?`연결됨`:`연결 필요`})]}),W(`div`,{class:`flex items-center justify-between gap-4 border-b border-slate-100 pb-4`,children:[W(`dt`,{class:`text-sm font-bold text-slate-500`,children:`요금제`}),W(`dd`,{class:`m-0 flex min-w-0 flex-wrap items-center justify-end gap-2`,children:[W(Uy,{plan:g}),g===`FREE`?W(`span`,{class:`ssh-safeshield-plan-caption`,children:`기본 플랜`}):null]})]}),W(`div`,{class:`flex items-center justify-between gap-4`,children:[W(`dt`,{class:`text-sm font-bold text-slate-500`,children:`마지막 확인`}),W(`dd`,{class:`m-0 text-sm font-black text-slate-700`,children:jo(m?.lastSuccessAt??0)})]})]}),i?W(`p`,{class:`mt-5 mb-0 text-xs font-bold text-teal-700`,children:`최신 상태를 확인하고 있습니다…`}):null,a?h?W(`p`,{class:`mt-5 mb-0 text-xs font-semibold leading-5 text-slate-500`,children:`최신 상태 확인이 지연되고 있습니다. 마지막으로 확인된 연결 상태를 표시합니다.`}):W(`p`,{class:`mt-5 mb-0 rounded-xl border border-amber-200 bg-amber-50 px-3 py-2 text-xs font-bold leading-5 text-amber-800`,children:[a,` 로컬에 저장된 마지막 상태를 표시합니다.`]}):null]})]})]})}function Ky(e,t){if(!e||!t)return`unknown`;if(e===t)return`same`;let n=/^(\d+)\.(\d+)\.(\d+)(?:-r(\d+))?$/,r=n.exec(e),i=n.exec(t);if(!r||!i)return`different`;for(let e=1;e<=4;e+=1){let t=BigInt(r[e]||`0`),n=BigInt(i[e]||`0`);if(t>n)return`newer`;if(t=1048576?`${(e/1048576).toFixed(1)} MB`:`${(e/1024).toFixed(1)} KB`}function Jy(e){switch(e.phase){case`checking`:return`확인 중`;case`downloading`:return`다운로드 중`;case`verifying`:return`검증 중`;case`ready`:return`설치 준비 완료`;case`flashing`:return`설치 중`;case`error`:return`확인 필요`;default:return e.updateAvailable?`업데이트 가능`:e.lastCheckAt?`최신 상태`:`확인 전`}}function Yy(e){return e.phase===`error`?`bg-rose-50 text-rose-700 ring-rose-200`:e.phase===`checking`||e.phase===`downloading`||e.phase===`verifying`||e.phase===`flashing`?`bg-sky-50 text-sky-700 ring-sky-200`:e.phase===`ready`||e.updateAvailable?`bg-amber-50 text-amber-800 ring-amber-200`:e.lastCheckAt?`bg-emerald-50 text-emerald-700 ring-emerald-200`:`bg-slate-100 text-slate-600 ring-slate-200`}function Xy(e){return e.phase===`checking`||e.phase===`downloading`||e.phase===`verifying`||e.phase===`flashing`?W(pn,{class:`size-3.5 animate-spin`}):e.phase===`error`?W(Cn,{class:`size-3.5`}):e.phase===`ready`||e.updateAvailable?W(hn,{class:`size-3.5`}):e.lastCheckAt?W(Sn,{class:`size-3.5`}):W(dn,{class:`size-3.5`})}function Zy({checked:e,disabled:t,onChange:n}){return W(`button`,{"aria-checked":e,"aria-label":`현재 설정 유지`,class:`ssh-switch-control relative inline-flex shrink-0 rounded-full border transition focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-100 disabled:cursor-not-allowed disabled:opacity-50 ${e?`border-teal-600 bg-teal-600`:`border-slate-300 bg-slate-200`}`,disabled:t,onClick:()=>n(!e),role:`switch`,type:`button`,children:W(`span`,{"aria-hidden":`true`,class:`ssh-switch-thumb absolute top-1 shadow-sm transition-[left] ${e?`left-6`:`left-1`}`})})}function Qy({action:e,actionError:t,data:n,software:r,error:i,loading:a,message:o,reconnecting:s,uploadProgress:c,onCheck:l,onDiscard:u,onDismissFeedback:d,onInstall:f,onPrepare:p,onRetry:m,onUpload:h}){let g=z(null),[_,v]=L(null),[y,x]=L(!0),[S,C]=L(!1);R(()=>{n?.prepared&&x(n.prepared.allowBackup)},[n?.prepared]);let w=e!==null||n?.phase===`checking`||n?.phase===`downloading`||n?.phase===`verifying`||n?.phase===`flashing`,T=n?.phase===`ready`?n.prepared:null,E=e===`check`||n?.phase===`checking`,D=e===`prepare`||n?.phase===`downloading`||n?.phase===`verifying`,O=e===`upload`||e===`validate-upload`,k=n?.release?.bundledSoftware?.smartsafehub??null,A=r?.packages.find(e=>e.name===`luci-app-smartsafehub`)?.installedVersion??null,j=Ky(k,A),M=async()=>{_&&await h(_)&&(v(null),g.current&&(g.current.value=``))};return W(`article`,{class:`min-w-0 overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm shadow-slate-900/5`,"data-component":`firmware-update-card`,children:[W(`div`,{class:`p-5 sm:p-6`,children:[W(`div`,{class:`flex flex-col gap-5 lg:flex-row lg:items-start lg:justify-between`,children:[W(`div`,{class:`flex min-w-0 gap-4`,children:[W(`span`,{class:`grid size-12 shrink-0 place-items-center rounded-2xl bg-sky-50 text-sky-700`,children:W(Jt,{class:`size-6`})}),W(`div`,{class:`min-w-0`,children:[W(`p`,{class:`m-0 text-xs font-extrabold uppercase tracking-[0.16em] text-sky-700`,children:`Firmware update`}),W(`h2`,{class:`mt-2 mb-0 text-xl font-black text-slate-950 sm:text-2xl`,children:`펌웨어 업데이트`}),W(`p`,{class:`mt-2 mb-0 text-sm leading-6 text-slate-500 lg:whitespace-nowrap`,children:`시스템 펌웨어를 확인하고 설치합니다. 온라인·수동 설치는 안전성 검증 후 진행됩니다.`})]})]}),n?W(`div`,{class:`flex shrink-0 flex-col gap-2 sm:flex-row lg:justify-end`,children:[W(`span`,{class:`inline-flex min-h-11 items-center justify-center gap-1.5 rounded-xl px-3 py-2 text-xs font-extrabold ring-1 ring-inset ${Yy(n)}`,children:[Xy(n),Jy(n)]}),W(`button`,{class:`inline-flex min-h-11 items-center justify-center gap-2 rounded-xl border border-slate-300 bg-white px-4 py-2.5 text-sm font-extrabold text-slate-800 transition hover:bg-slate-50 disabled:cursor-wait disabled:opacity-60`,disabled:w||T!==null,onClick:l,type:`button`,children:[W(pn,{class:`size-4 ${E?`animate-spin`:``}`}),E?`확인 중...`:`펌웨어 확인`]}),n.updateAvailable&&!T?W(`button`,{class:`inline-flex min-h-11 items-center justify-center gap-2 rounded-xl bg-sky-700 px-4 py-2.5 text-sm font-extrabold text-white transition hover:bg-sky-800 disabled:cursor-wait disabled:opacity-60`,disabled:w,onClick:p,type:`button`,children:[D?W(pn,{class:`size-4 animate-spin`}):W(hn,{class:`size-4`}),D?`준비 중...`:`다운로드 및 검증`]}):null]}):null]}),t||o?W(`div`,{class:`mt-5 flex flex-col gap-3 rounded-xl border px-4 py-3 text-sm font-bold sm:flex-row sm:items-center sm:justify-between ${t?`border-rose-200 bg-rose-50 text-rose-800`:`border-emerald-200 bg-emerald-50 text-emerald-800`}`,children:[W(`span`,{children:t||o}),W(`button`,{class:`shrink-0 self-start rounded-lg px-3 py-1.5 text-xs font-extrabold hover:bg-black/5 sm:self-auto`,onClick:d,type:`button`,children:`닫기`})]}):null,s||n?.phase===`flashing`?W(`div`,{class:`mt-5 rounded-xl border border-amber-200 bg-amber-50 p-4 sm:p-5`,role:`status`,children:W(`div`,{class:`flex gap-3`,children:[W(`span`,{class:`grid size-10 shrink-0 place-items-center rounded-xl bg-white text-amber-700 ring-1 ring-inset ring-amber-200`,children:W(pn,{class:`size-5 animate-spin`})}),W(`div`,{children:[W(`strong`,{class:`block text-sm font-black text-amber-950`,children:`펌웨어를 설치하고 재부팅하고 있습니다.`}),W(`p`,{class:`mt-1 mb-0 text-sm leading-6 text-amber-800`,children:`전원을 끄지 마세요. 현재 주소에서 공유기가 다시 응답하면 SmartSafeHub가 자동으로 새로고침됩니다.`})]})]})}):null,i&&!n?W(`div`,{class:`mt-5 rounded-xl border border-rose-200 bg-rose-50 p-4 text-sm text-rose-800`,children:[W(`p`,{class:`m-0 font-bold`,children:i}),W(`button`,{class:`mt-3 rounded-lg border border-rose-300 bg-white px-3 py-2 text-xs font-extrabold text-rose-700`,onClick:m,type:`button`,children:`다시 시도`})]}):a&&!n?W(`div`,{class:`mt-5 flex items-center gap-3 rounded-xl bg-slate-50 p-4 text-sm font-bold text-slate-500`,children:[W(pn,{class:`size-4 animate-spin`}),`펌웨어 상태를 확인하고 있습니다.`]}):n?W(b,{children:[n.lastError?W(`div`,{class:`mt-5 rounded-xl border border-rose-200 bg-rose-50 p-4 sm:p-5`,children:W(`div`,{class:`flex gap-3`,children:[W(`span`,{class:`grid size-10 shrink-0 place-items-center rounded-xl bg-white text-rose-700 ring-1 ring-inset ring-rose-200`,children:W(Cn,{class:`size-5`})}),W(`div`,{class:`min-w-0 flex-1`,children:[W(`strong`,{class:`block text-sm font-black text-rose-950`,children:`펌웨어 작업을 완료하지 못했습니다.`}),W(`p`,{class:`mt-1 mb-0 text-sm leading-6 text-rose-800`,children:n.lastError.message}),W(`details`,{class:`mt-3 rounded-lg border border-rose-200 bg-white p-3`,children:[W(`summary`,{class:`cursor-pointer text-xs font-extrabold text-rose-800`,children:`상세 정보 보기`}),W(`p`,{class:`mt-2 mb-0 break-all font-mono text-[11px] text-slate-600`,children:n.lastError.code})]})]})]})}):null,n.current.metadataAvailable?null:W(`div`,{class:`mt-5 rounded-xl border border-amber-200 bg-amber-50 px-4 py-3 text-xs leading-5 text-amber-900`,children:[W(`strong`,{class:`block font-extrabold`,children:`현재 펌웨어의 버전 정보를 완전히 확인할 수 없습니다.`}),W(`span`,{class:`mt-1 block text-amber-800`,children:`새 펌웨어를 설치하면 이후부터 정확한 설치 버전을 자동으로 확인할 수 있습니다.`}),W(`details`,{class:`mt-2`,children:[W(`summary`,{class:`cursor-pointer font-extrabold text-amber-800`,children:`기술 상세 보기`}),W(`p`,{class:`mt-2 mb-0 break-all font-mono text-[11px] leading-5 text-amber-800`,children:`SmartSafeHub build ID metadata is missing. Future images should include /usr/share/smartsafehub/firmware.json.`})]})]}),W(`dl`,{class:`mt-5 grid overflow-hidden rounded-xl border border-slate-200 bg-slate-200 sm:grid-cols-2 xl:grid-cols-4`,children:[W(`div`,{class:`bg-white p-4`,children:[W(`dt`,{class:`text-[11px] font-black uppercase tracking-[0.16em] text-slate-400`,children:`Device`}),W(`dd`,{class:`mt-2 mb-0 ml-0 break-all text-sm font-black text-slate-950`,children:n.current.deviceCode||n.current.boardName||`미확인`})]}),W(`div`,{class:`bg-white p-4`,children:[W(`dt`,{class:`text-[11px] font-black uppercase tracking-[0.16em] text-slate-400`,children:`Current version`}),W(`dd`,{class:`mt-2 mb-0 ml-0 break-all text-sm font-black text-slate-950`,children:n.current.releaseVersion||`미확인`}),n.current.buildId?W(`span`,{class:`mt-1 block break-all text-[11px] leading-5 text-slate-400`,children:[`빌드 ID `,n.current.buildId]}):n.current.openwrtVersion?W(`span`,{class:`mt-1 block text-[11px] leading-5 text-slate-400`,children:[`OpenWrt `,n.current.openwrtVersion]}):null]}),W(`div`,{class:`bg-white p-4`,children:[W(`dt`,{class:`text-[11px] font-black uppercase tracking-[0.16em] text-slate-400`,children:`Available version`}),W(`dd`,{class:`mt-2 mb-0 ml-0 break-all text-sm font-black ${n.updateAvailable?`text-amber-700`:`text-slate-950`}`,children:n.lastCheckAt?n.release?.version?`${n.release.version}${n.updateAvailable?``:` · 최신`}`:n.updateAvailable?`새 펌웨어`:`최신 버전`:`미확인`}),n.release?.buildId?W(`span`,{class:`mt-1 block break-all text-[11px] leading-5 text-slate-400`,children:[`빌드 ID `,n.release.buildId]}):null]}),W(`div`,{class:`bg-white p-4`,children:[W(`dt`,{class:`text-[11px] font-black uppercase tracking-[0.16em] text-slate-400`,children:`Last check`}),W(`dd`,{class:`mt-2 mb-0 ml-0 text-sm font-black text-slate-950`,children:W(Rv,{fallback:`아직 없음`,timestamp:n.lastCheckAt})})]})]})]}):null,n?.updateAvailable&&n.release?W(`section`,{class:`mt-5 rounded-xl border border-sky-200 bg-sky-50/60 p-4 sm:p-5`,"data-section":`available-firmware`,children:[W(`div`,{class:`flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between sm:gap-4`,children:[W(`div`,{children:[W(`p`,{class:`m-0 text-xs font-extrabold uppercase tracking-[0.16em] text-sky-700`,children:`Available firmware`}),W(`h3`,{class:`mt-2 mb-0 text-lg font-black text-slate-950`,children:`새 펌웨어가 있습니다.`}),W(`p`,{class:`mt-2 mb-0 text-sm leading-6 text-slate-600`,children:[`펌웨어 `,n.release.version||`미확인`,` · `,qy(n.release.sysupgrade.sizeBytes),` · `,(n.release.channel||n.settings.channel)===`beta`?`Beta`:`Stable`]})]}),W(`div`,{class:`flex shrink-0 flex-wrap items-center justify-start gap-2 sm:justify-end`,children:[W(`span`,{class:`rounded-full bg-white px-3 py-1.5 text-xs font-extrabold text-sky-700 ring-1 ring-inset ring-sky-200`,children:n.release.version||`Latest`}),k?W(`span`,{class:`inline-flex items-center gap-1.5 rounded-full bg-white px-3 py-1.5 text-xs font-extrabold text-slate-700 ring-1 ring-inset ring-sky-200`,"data-section":`firmware-bundled-software-chip`,children:[W(`span`,{class:`text-[11px] uppercase tracking-[0.12em] text-slate-500`,children:`관리 SW`}),W(`span`,{class:`text-slate-950`,children:k})]}):null]})]}),k?W(`div`,{class:`mt-4 rounded-xl border border-sky-100 bg-white p-4`,"data-section":`firmware-bundled-software`,children:[W(`div`,{class:`flex flex-wrap items-center justify-between gap-2`,children:[W(`span`,{class:`text-xs font-extrabold text-slate-800`,children:`포함된 관리 소프트웨어`}),W(`div`,{class:`flex flex-wrap items-center gap-2`,children:[W(`span`,{class:`text-sm font-black text-slate-950`,children:k}),j===`same`?W(`span`,{class:`rounded-full bg-emerald-50 px-2.5 py-1 text-[11px] font-extrabold text-emerald-700 ring-1 ring-inset ring-emerald-200`,children:`현재 버전과 동일`}):null]})]}),W(`p`,{class:`mt-2 mb-0 text-xs leading-5 text-slate-600`,children:j===`same`?`관리 소프트웨어는 이미 같은 버전입니다. 펌웨어 업데이트는 시스템 구성 요소를 갱신합니다.`:j===`older`?`현재 설치된 관리 소프트웨어(${A})보다 이전 버전이 포함되어 있습니다. 펌웨어 설치 전 버전을 확인해 주세요.`:j===`newer`?`현재 설치된 관리 소프트웨어(${A})보다 새 버전이 포함되어 있습니다.`:A?`현재 설치 버전 ${A} · 펌웨어 포함 버전 ${k}`:`이 펌웨어에 포함된 관리 소프트웨어 버전입니다.`})]}):null,n.release.releaseNotes.length?W(`ul`,{class:`mt-4 mb-0 space-y-2 rounded-xl bg-white p-4 pl-9 text-sm leading-6 text-slate-700 ring-1 ring-inset ring-sky-100`,children:n.release.releaseNotes.map((e,t)=>W(`li`,{children:e},`${t}-${e}`))}):null]}):null,T?W(`section`,{class:`mt-5 rounded-xl border border-amber-200 bg-amber-50 p-4 sm:p-5`,"data-section":`prepared-firmware`,children:W(`div`,{class:`flex gap-4`,children:[W(`span`,{class:`grid size-11 shrink-0 place-items-center rounded-xl bg-white text-amber-700 ring-1 ring-inset ring-amber-200`,children:W(Sn,{class:`size-5`})}),W(`div`,{class:`min-w-0 flex-1`,children:[W(`p`,{class:`m-0 text-xs font-extrabold uppercase tracking-[0.16em] text-amber-700`,children:`Verified image`}),W(`h3`,{class:`mt-2 mb-0 text-lg font-black text-amber-950`,children:`설치할 펌웨어가 준비되었습니다.`}),W(`p`,{class:`mt-2 mb-0 break-all text-sm leading-6 text-amber-900`,children:[T.filename||`firmware.bin`,` · `,qy(T.sizeBytes)]}),T.sha256?W(`p`,{class:`mt-2 mb-0 break-all font-mono text-[11px] leading-5 text-amber-800`,children:[`SHA-256 `,T.sha256]}):null,W(`div`,{class:`mt-5 flex items-start justify-between gap-4 rounded-xl border border-amber-200 bg-white p-4`,children:[W(`div`,{children:[W(`strong`,{class:`block text-sm font-black text-slate-950`,children:`현재 설정 유지`}),W(`span`,{class:`mt-1 block text-xs leading-5 text-slate-500`,children:`Wi-Fi, SafeShield, SmartSafeHub 설정을 유지합니다.`}),T.allowBackup?null:W(`span`,{class:`mt-2 block text-xs font-bold text-rose-700`,children:`이 이미지는 설정 유지 업그레이드를 지원하지 않습니다.`})]}),W(Zy,{checked:y,disabled:!T.allowBackup,onChange:x})]}),S?W(`div`,{class:`mt-4 rounded-xl border border-rose-200 bg-rose-50 p-4`,children:[W(`strong`,{class:`block text-sm font-black text-rose-950`,children:`펌웨어 설치를 시작할까요?`}),W(`p`,{class:`mt-1 mb-0 text-xs leading-5 text-rose-800`,children:`설치 중 전원을 끄면 장치가 복구 불가능한 상태가 될 수 있습니다. 이미지 검증은 설치 직전에 다시 수행됩니다.`}),W(`div`,{class:`mt-4 flex flex-wrap justify-end gap-2`,children:[W(`button`,{class:`min-h-10 rounded-lg border border-slate-300 bg-white px-3 py-2 text-xs font-extrabold text-slate-700`,onClick:()=>C(!1),type:`button`,children:`취소`}),W(`button`,{class:`min-h-10 rounded-lg bg-rose-700 px-3 py-2 text-xs font-extrabold text-white`,onClick:()=>{C(!1),f(y)},type:`button`,children:`설치 시작`})]})]}):W(`div`,{class:`mt-4 flex flex-wrap justify-end gap-2`,children:[W(`button`,{class:`min-h-10 rounded-lg border border-slate-300 bg-white px-3 py-2 text-xs font-extrabold text-slate-700`,disabled:e===`discard`,onClick:()=>void u(),type:`button`,children:`파일 삭제`}),W(`button`,{class:`inline-flex min-h-10 items-center gap-2 rounded-lg bg-rose-700 px-4 py-2 text-xs font-extrabold text-white`,onClick:()=>C(!0),type:`button`,children:[W(rn,{class:`size-4`}),`펌웨어 설치`]})]})]})]})}):null]}),W(`details`,{class:`group mx-5 mb-5 rounded-xl border border-slate-200 bg-slate-50/70 sm:mx-6 sm:mb-6`,"data-layout":`firmware-card-subsection`,"data-section":`manual-firmware`,children:[W(`summary`,{class:`flex cursor-pointer list-none items-center justify-between gap-4 px-4 py-4 sm:px-5 [&::-webkit-details-marker]:hidden`,children:[W(`div`,{class:`flex min-w-0 items-center gap-3`,children:[W(`span`,{class:`grid size-10 shrink-0 place-items-center rounded-xl bg-white text-slate-600 ring-1 ring-inset ring-slate-200`,children:W(hn,{class:`size-5 rotate-180`})}),W(`div`,{class:`min-w-0`,children:[W(`strong`,{class:`block text-sm font-black text-slate-950`,children:`수동 펌웨어 설치`}),W(`span`,{class:`mt-1 block text-xs leading-5 text-slate-500`,children:`펌웨어 파일(.bin)을 직접 업로드하여 검증한 뒤 설치합니다.`})]})]}),W(`span`,{class:`shrink-0 rounded-full bg-white px-3 py-1.5 text-[11px] font-extrabold text-slate-600 ring-1 ring-inset ring-slate-200 group-open:hidden`,children:`열기`}),W(`span`,{class:`hidden shrink-0 rounded-full bg-white px-3 py-1.5 text-[11px] font-extrabold text-slate-600 ring-1 ring-inset ring-slate-200 group-open:inline-flex`,children:`닫기`})]}),W(`div`,{class:`border-t border-slate-200 px-5 pb-5 pt-3 sm:px-6`,children:[W(`p`,{class:`mt-0 mb-4 text-xs leading-5 text-slate-500 lg:whitespace-nowrap`,children:"이 장치에 맞는 Sysupgrade 이미지(.bin)만 사용하세요. 업로드한 파일은 이미지 검증과 `sysupgrade --test`를 모두 통과해야 설치할 수 있으며 강제 설치는 제공하지 않습니다."}),W(`div`,{class:`rounded-xl border border-slate-200 bg-white p-4 sm:p-5`,"data-layout":`firmware-file-picker`,children:[W(`input`,{accept:`.bin,application/octet-stream`,"aria-label":`펌웨어 파일 선택`,class:`sr-only`,disabled:w||T!==null,onChange:e=>v(e.currentTarget.files?.[0]??null),ref:g,type:`file`}),W(`div`,{class:`flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between`,children:[W(`div`,{class:`flex min-w-0 items-center gap-3`,children:[W(`span`,{class:`grid size-10 shrink-0 place-items-center rounded-xl bg-slate-50 text-slate-500 ring-1 ring-inset ring-slate-200`,children:W(hn,{class:`size-5 rotate-180`})}),W(`div`,{class:`min-w-0`,children:[W(`strong`,{class:`block truncate text-sm font-black text-slate-900`,children:_?_.name:`펌웨어 파일을 선택하세요`}),W(`span`,{class:`mt-1 block text-xs leading-5 text-slate-500`,children:_?`${qy(_.size)} · Sysupgrade 이미지 (.bin)`:`이 장치에 맞는 Sysupgrade 이미지 (.bin)를 선택합니다.`})]})]}),W(`div`,{class:`flex shrink-0 flex-wrap gap-2 sm:justify-end`,children:[W(`button`,{class:`inline-flex min-h-10 items-center justify-center rounded-lg border border-slate-300 bg-white px-3.5 py-2 text-xs font-extrabold text-slate-700 transition hover:bg-slate-50 disabled:cursor-not-allowed disabled:opacity-50`,disabled:w||T!==null,onClick:()=>g.current?.click(),type:`button`,children:_?`다른 파일 선택`:`파일 선택`}),W(`button`,{class:`inline-flex min-h-10 items-center justify-center gap-2 rounded-lg bg-slate-900 px-3.5 py-2 text-xs font-extrabold text-white transition hover:bg-slate-800 disabled:cursor-not-allowed disabled:opacity-50`,disabled:!_||w||T!==null,onClick:()=>void M(),type:`button`,children:[O?W(pn,{class:`size-4 animate-spin`}):W(hn,{class:`size-4 rotate-180`}),O?`처리 중...`:`업로드 및 검증`]})]})]}),c===null?null:W(`div`,{class:`mt-4 border-t border-slate-100 pt-4`,children:[W(`div`,{class:`mb-2 flex items-center justify-between gap-3 text-xs font-bold text-slate-600`,children:[W(`span`,{children:`업로드 중`}),W(`span`,{children:[c,`%`]})]}),W(`div`,{class:`h-2 overflow-hidden rounded-full bg-slate-200`,children:W(`div`,{class:`h-full rounded-full bg-sky-600 transition-[width]`,style:{width:`${Math.max(0,Math.min(100,c))}%`}})})]})]})]})]})]})}var $y=[{value:3600,label:`1시간마다`},{value:21600,label:`6시간마다`},{value:43200,label:`12시간마다`},{value:86400,label:`24시간마다`}],eb=`luci-app-smartsafehub`;function tb(e){return e?new Date(e*1e3).toLocaleString(`ko-KR`,{dateStyle:`medium`,timeStyle:`short`}):`아직 없음`}function nb(e){return e===`stable`?`Stable`:e===`beta`?`Beta`:`미확인`}function rb(e){return e===`stable`?`bg-emerald-50 text-emerald-700 ring-emerald-200`:e===`beta`?`bg-amber-50 text-amber-700 ring-amber-200`:`bg-slate-100 text-slate-600 ring-slate-200`}function ib(e){return e.code===`UPDATES_INDEX_REFRESH_FAILED`?{title:`패키지 저장소를 확인하지 못했습니다.`,description:`OpenWrt 패키지 저장소 동기화 또는 네트워크 상태가 일시적으로 불안정할 수 있습니다. 잠시 후 업데이트 확인을 다시 실행해 주세요.`}:e.code===`UPDATES_INSTALL_FAILED`?{title:`관리 소프트웨어 업데이트 설치에 실패했습니다.`,description:`패키지 설치가 완료되지 않았습니다. 네트워크 연결과 저장 공간을 확인한 뒤 다시 시도해 주세요.`}:{title:`최근 업데이트 작업을 완료하지 못했습니다.`,description:`잠시 후 다시 시도해 주세요. 문제가 반복되면 상세 정보를 확인해 주세요.`}}function ab(e,t){return e.phase===`checking`?`업데이트 확인 중`:e.phase===`installing`?`업데이트 설치 중`:e.phase===`error`?`확인 필요`:e.updateCount>0?`업데이트 가능`:t?`업데이트 확인 지연`:e.lastCheckAt?`최신 상태`:`확인 전`}function ob(e,t){return e.phase===`error`?`bg-rose-50 text-rose-700 ring-rose-200`:e.phase===`checking`||e.phase===`installing`?`bg-sky-50 text-sky-700 ring-sky-200`:e.updateCount>0||t?`bg-amber-50 text-amber-800 ring-amber-200`:e.lastCheckAt?`bg-emerald-50 text-emerald-700 ring-emerald-200`:`bg-slate-100 text-slate-600 ring-slate-200`}function sb(e,t){return e.phase===`checking`||e.phase===`installing`?W(pn,{class:`size-3.5 shrink-0 animate-spin`}):e.phase===`error`?W(Cn,{class:`size-3.5 shrink-0`}):e.updateCount>0?W(hn,{class:`size-3.5 shrink-0`}):t?W(Cn,{class:`size-3.5 shrink-0`}):e.lastCheckAt?W(Sn,{class:`size-3.5 shrink-0`}):W(dn,{class:`size-3.5 shrink-0`})}function cb({checked:e,disabled:t=!1,label:n,onChange:r}){return W(`button`,{"aria-checked":e,"aria-label":n,class:`ssh-switch-control relative inline-flex shrink-0 rounded-full border transition focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-100 disabled:cursor-not-allowed disabled:opacity-50 ${e?`border-teal-600 bg-teal-600`:`border-slate-300 bg-slate-200`}`,disabled:t,onClick:()=>r(!e),role:`switch`,type:`button`,children:W(`span`,{"aria-hidden":`true`,class:`ssh-switch-thumb absolute top-1 shadow-sm transition-[left] ${e?`left-6`:`left-1`}`})})}function lb({action:e,actionError:t,data:n,firmware:r,error:i,loading:a,message:o,onCheck:s,onDismissFeedback:c,onInstall:l,onRetry:u,onSaveSettings:d}){let[f,p]=L(!0),[m,h]=L(21600),[g,_]=L(!1),[v,y]=L(`03:00`),[x,S]=L(!1),[C,w]=L(!1);R(()=>{n&&!x&&(p(n.settings.checkEnabled),h(n.settings.checkIntervalSeconds),_(n.settings.autoInstall),y(n.settings.autoInstallTime))},[n,x]);let T=e===`check`||n?.phase===`checking`,E=e===`install`||n?.phase===`installing`,D=T||E,O=u_(n),k=n?.phase===`error`&&n.lastError?.code===`UPDATES_INDEX_REFRESH_FAILED`,A=n?.lastError?ib(n.lastError):null,j=n?.packages.find(e=>e.name===eb)??n?.packages[0]??null,M=r?.updateAvailable?r.release?.bundledSoftware?.smartsafehub:null,N=!(!n||n.releaseNotesComplete||n.releaseNotes.length!==1||j?.name!==eb||n.releaseNotes[0]?.version!==j.availableVersion),P=!(!n?.lastCheckAt||n.updateCount!==0||O||j?.name!==eb)&&Ky(M,j?.installedVersion)===`same`,F=(e={})=>n?(e.checkEnabled??f)!==n.settings.checkEnabled||(e.checkIntervalSeconds??m)!==n.settings.checkIntervalSeconds||(e.autoInstall??g)!==n.settings.autoInstall||(e.autoInstallTime??v)!==n.settings.autoInstallTime:!1,I=async()=>{await d({checkEnabled:f,checkIntervalSeconds:m,autoInstall:g,autoInstallTime:v})&&S(!1)};return W(`article`,{class:`min-w-0 overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm shadow-slate-900/5 ${n?`grid xl:grid-cols-[minmax(0,0.95fr)_minmax(0,1.05fr)]`:``}`,"data-component":`management-software-update-card`,"data-layout":`management-software-sections`,children:[W(`section`,{class:`min-w-0 xl:col-start-1 xl:row-start-1`,"data-section":`software-update-status`,children:[W(`div`,{class:`p-5 sm:p-6`,children:[W(`div`,{class:`flex flex-col gap-5 lg:flex-row lg:items-start lg:justify-between`,children:[W(`div`,{class:`flex min-w-0 gap-4`,children:[W(`span`,{class:`grid size-12 shrink-0 place-items-center rounded-2xl bg-teal-50 text-teal-700`,children:W(rn,{class:`size-6`})}),W(`div`,{class:`min-w-0`,children:[W(`div`,{class:`flex flex-wrap items-center gap-2`,children:[W(`p`,{class:`m-0 text-xs font-extrabold uppercase tracking-[0.16em] text-teal-700`,children:`Management software`}),n?W(`span`,{class:`inline-flex items-center gap-1.5 rounded-full px-2.5 py-1 text-[11px] font-extrabold ring-1 ring-inset ${ob(n,O)}`,children:[W(`span`,{"aria-hidden":`true`,class:`flex items-center justify-center`,children:sb(n,O)}),ab(n,O)]}):null]}),W(`h2`,{class:`mt-2 mb-0 text-xl font-black text-slate-950 sm:text-2xl`,children:`관리 소프트웨어 업데이트`}),W(`p`,{class:`mt-2 mb-0 text-sm leading-6 text-slate-500 xl:whitespace-nowrap`,children:`새 버전을 확인하고 안전하게 설치합니다.`})]})]}),n?W(`div`,{class:`flex shrink-0 flex-col gap-2 sm:flex-row lg:justify-end`,children:[W(`button`,{class:`inline-flex min-h-11 items-center justify-center gap-2 rounded-xl border border-slate-300 bg-white px-4 py-2.5 text-sm font-extrabold text-slate-800 transition hover:bg-slate-50 disabled:cursor-wait disabled:opacity-60`,disabled:D,onClick:s,type:`button`,children:[W(pn,{class:`size-4 ${T?`animate-spin`:``}`}),T?`확인 중...`:`업데이트 확인`]}),n.updateCount>0&&!C?W(`button`,{class:`inline-flex min-h-11 items-center justify-center gap-2 rounded-xl bg-teal-700 px-4 py-2.5 text-sm font-extrabold text-white transition hover:bg-teal-800 disabled:cursor-wait disabled:opacity-60`,disabled:D,onClick:()=>w(!0),type:`button`,children:[E?W(pn,{class:`size-4 animate-spin`}):W(hn,{class:`size-4`}),E?`설치 중...`:`업데이트 설치`]}):null]}):null]}),(t||o)&&W(`div`,{class:`mt-5 flex flex-col gap-3 rounded-xl border px-4 py-3 text-sm font-bold sm:flex-row sm:items-center sm:justify-between ${t?`border-rose-200 bg-rose-50 text-rose-800`:`border-emerald-200 bg-emerald-50 text-emerald-800`}`,children:[W(`span`,{children:t||o}),W(`button`,{class:`shrink-0 self-start rounded-lg px-3 py-1.5 text-xs font-extrabold hover:bg-black/5 sm:self-auto`,onClick:c,type:`button`,children:`닫기`})]}),E?W(`div`,{"aria-busy":`true`,"aria-live":`polite`,class:`mt-5 rounded-xl border border-sky-200 bg-sky-50 p-4 sm:p-5`,children:[W(`div`,{class:`flex items-start gap-3`,children:[W(`span`,{class:`grid size-10 shrink-0 place-items-center rounded-xl bg-white text-sky-700 ring-1 ring-inset ring-sky-200`,children:W(pn,{"aria-hidden":`true`,class:`size-5 animate-spin`})}),W(`div`,{class:`min-w-0`,children:[W(`h3`,{class:`m-0 text-sm font-black text-sky-950`,children:`관리 소프트웨어를 업데이트하고 있습니다.`}),W(`p`,{class:`mt-1 mb-0 text-sm leading-6 text-sky-800`,children:`설치가 완료되면 이 화면이 자동으로 갱신됩니다. 작업 중에는 웹 연결이 잠시 끊길 수 있습니다.`})]})]}),W(`div`,{"aria-hidden":`true`,class:`ssh-update-progress-track mt-4`,children:W(`span`,{class:`ssh-update-progress-bar`})})]}):null,i&&!n?W(`div`,{class:`mt-5 rounded-xl border border-rose-200 bg-rose-50 p-4 text-sm text-rose-800`,children:[W(`p`,{class:`m-0 font-bold`,children:i}),W(`button`,{class:`mt-3 rounded-lg border border-rose-300 bg-white px-3 py-2 text-xs font-extrabold text-rose-700`,onClick:u,type:`button`,children:`다시 시도`})]}):a&&!n?W(`div`,{class:`mt-5 flex items-center gap-3 rounded-xl bg-slate-50 p-4 text-sm font-bold text-slate-500`,children:[W(`span`,{class:`grid size-9 shrink-0 place-items-center rounded-xl bg-white text-teal-700 ring-1 ring-inset ring-slate-200`,children:W(pn,{"aria-hidden":`true`,class:`size-4 animate-spin`})}),W(`span`,{children:`관리 소프트웨어 업데이트 상태를 확인하고 있습니다.`})]}):n?W(b,{children:[n.lastError&&A?W(`div`,{class:`mt-5 rounded-xl border border-rose-200 bg-rose-50 p-4 sm:p-5`,children:W(`div`,{class:`flex items-start gap-3`,children:[W(`span`,{class:`grid size-10 shrink-0 place-items-center rounded-xl bg-white text-rose-700 ring-1 ring-inset ring-rose-200`,children:W(Cn,{class:`size-5`})}),W(`div`,{class:`min-w-0 flex-1`,children:[W(`p`,{class:`m-0 text-sm font-extrabold text-rose-950`,children:A.title}),W(`p`,{class:`mt-1 mb-0 text-sm leading-6 text-rose-800`,children:A.description}),n.lastError.at?W(`p`,{class:`mt-2 mb-0 text-xs font-bold text-rose-700`,children:[`발생 시각 `,tb(n.lastError.at)]}):null,W(`details`,{class:`mt-3 rounded-lg border border-rose-200 bg-white p-3`,children:[W(`summary`,{class:`cursor-pointer text-xs font-extrabold text-rose-800`,children:`상세 정보 보기`}),W(`div`,{class:`mt-3 grid gap-2 text-xs leading-5 text-slate-600`,children:[W(`p`,{class:`m-0`,children:[`오류 코드 `,W(`strong`,{class:`font-black text-slate-900`,children:n.lastError.code})]}),W(`pre`,{class:`m-0 max-h-40 overflow-auto whitespace-pre-wrap break-words rounded-lg bg-slate-50 p-3 font-mono text-[11px] leading-5 text-slate-700`,children:n.lastError.message})]})]})]})]})}):null,C?W(`div`,{class:`mt-5 flex flex-col gap-3 rounded-xl border border-amber-200 bg-amber-50 p-4 sm:flex-row sm:items-center sm:justify-between`,children:[W(`div`,{class:`min-w-0`,children:[W(`strong`,{class:`block text-sm font-black text-amber-900`,children:`관리 소프트웨어를 지금 업데이트할까요?`}),W(`span`,{class:`mt-1 block text-xs leading-5 text-amber-800`,children:`설치 중 SmartSafeHub 웹 화면 연결이 잠시 끊길 수 있습니다.`})]}),W(`div`,{class:`flex shrink-0 gap-2`,children:[W(`button`,{class:`min-h-10 rounded-lg border border-slate-300 bg-white px-3 py-2 text-xs font-extrabold text-slate-700`,disabled:D,onClick:()=>w(!1),type:`button`,children:`취소`}),W(`button`,{class:`min-h-10 rounded-lg bg-amber-700 px-3 py-2 text-xs font-extrabold text-white disabled:opacity-60`,disabled:D,onClick:()=>{w(!1),l()},type:`button`,children:`설치 시작`})]})]}):null]}):null]}),n?W(`div`,{class:`px-5 pb-5 sm:px-6 sm:pb-6`,children:[W(`h3`,{class:`mb-3 text-sm font-black text-slate-950`,children:`현재 상태`}),W(`dl`,{class:`grid overflow-hidden rounded-xl border border-slate-200 bg-slate-200 sm:grid-cols-2`,children:[W(`div`,{class:`bg-white p-4`,children:[W(`dt`,{class:`text-[11px] font-black uppercase tracking-[0.16em] text-slate-400`,children:`Installed`}),W(`dd`,{class:`mt-2 mb-0 ml-0 break-all text-sm font-black text-slate-950`,children:j?.installedVersion||`미확인`})]}),W(`div`,{class:`bg-white p-4`,children:[W(`dt`,{class:`text-[11px] font-black uppercase tracking-[0.16em] text-slate-400`,children:`Available`}),W(`dd`,{class:`mt-2 mb-0 ml-0 break-all text-sm font-black ${j?.updateAvailable||O?`text-amber-700`:`text-slate-950`}`,children:!n.lastCheckAt||k&&!j?.updateAvailable?`미확인`:j?.updateAvailable&&j.availableVersion?j.availableVersion:O?`확인 필요`:`최신 버전`})]}),W(`div`,{class:`bg-white p-4`,children:[W(`dt`,{class:`text-[11px] font-black uppercase tracking-[0.16em] text-slate-400`,children:`Last check`}),W(`dd`,{class:`mt-2 mb-0 ml-0 text-sm font-black text-slate-950`,children:W(Rv,{fallback:`아직 없음`,timestamp:n.lastCheckAt})})]}),W(`div`,{class:`bg-white p-4`,children:[W(`dt`,{class:`text-[11px] font-black uppercase tracking-[0.16em] text-slate-400`,children:`Auto install`}),W(`dd`,{class:`mt-2 mb-0 ml-0 text-sm font-black text-slate-950`,children:n.settings.autoInstall?`켜짐 · ${n.settings.autoInstallTime}`:`꺼짐`})]})]})]}):null,n&&n.updateCount===0&&n.lastCheckAt?O?W(`section`,{class:`mx-5 mb-5 rounded-xl border border-amber-200 bg-amber-50 p-4 sm:mx-6 sm:mb-6 sm:p-5`,"data-section":`software-update-result`,children:W(`div`,{class:`flex gap-3`,children:[W(`span`,{class:`grid size-10 shrink-0 place-items-center rounded-xl bg-white text-amber-700 ring-1 ring-inset ring-amber-200`,children:W(Cn,{class:`size-5`})}),W(`div`,{children:[W(`h3`,{class:`m-0 text-base font-black text-amber-950`,children:`업데이트 확인이 지연되고 있습니다.`}),W(`p`,{class:`mt-1 mb-0 text-sm leading-6 text-amber-800`,children:[`마지막 업데이트 확인: `,W(Rv,{timestamp:n.lastCheckAt}),`. 자동 확인 상태를 점검하거나 업데이트 확인을 실행해 주세요.`]})]})]})}):W(`section`,{class:`mx-5 mb-5 rounded-xl border border-emerald-200 bg-emerald-50 p-4 sm:mx-6 sm:mb-6 sm:p-5`,"data-section":`software-update-result`,children:W(`div`,{class:`flex gap-3`,children:[W(`span`,{class:`grid size-10 shrink-0 place-items-center rounded-xl bg-white text-emerald-700`,children:W(Sn,{class:`size-5`})}),W(`div`,{children:[W(`h3`,{class:`m-0 text-base font-black text-emerald-950`,children:`최신 버전을 사용 중입니다.`}),W(`p`,{class:`mt-1 mb-0 text-sm leading-6 text-emerald-800`,children:[`마지막 업데이트 확인: `,W(Rv,{timestamp:n.lastCheckAt}),`.`]}),P?W(`p`,{class:`mt-2 mb-0 text-xs leading-5 text-emerald-800`,"data-section":`software-firmware-match`,children:[`새 펌웨어에도 동일한 관리 소프트웨어(`,M,`)가 포함되어 있어 별도로 업데이트할 필요가 없습니다.`]}):null]})]})}):null,n&&n.updateCount===0&&!n.lastCheckAt?W(`section`,{class:`mx-5 mb-5 rounded-xl border border-slate-200 bg-slate-50 p-4 sm:mx-6 sm:mb-6 sm:p-5`,"data-section":`software-update-result`,children:W(`div`,{class:`flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between`,children:[W(`div`,{class:`flex min-w-0 gap-3`,children:[W(`span`,{class:`grid size-10 shrink-0 place-items-center rounded-xl bg-white text-slate-500 ring-1 ring-inset ring-slate-200`,children:W(pn,{class:`size-5`})}),W(`div`,{class:`min-w-0`,children:[W(`h3`,{class:`m-0 text-base font-black text-slate-900`,children:`업데이트 상태를 아직 확인하지 않았습니다.`}),W(`p`,{class:`mt-1 mb-0 text-sm leading-6 text-slate-600`,children:`업데이트 확인을 실행하면 현재 설치 버전과 사용 가능한 최신 버전을 확인합니다.`})]})]}),W(`button`,{class:`inline-flex min-h-10 shrink-0 items-center justify-center gap-2 rounded-xl border border-slate-300 bg-white px-4 py-2 text-sm font-extrabold text-slate-800 transition hover:border-teal-300 hover:text-teal-700 disabled:cursor-wait disabled:opacity-60`,disabled:D,onClick:s,type:`button`,children:[W(pn,{class:`size-4`}),`지금 확인`]})]})}):null]}),n?W(b,{children:[W(`section`,{class:`min-w-0 border-t border-slate-200 p-5 sm:p-6 xl:col-start-2 xl:row-start-1 xl:row-span-2 xl:self-stretch xl:border-t-0 xl:border-l`,"data-section":`software-update-settings`,children:[W(`div`,{class:`flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between`,children:[W(`div`,{class:`flex min-w-0 gap-4`,children:[W(`span`,{class:`grid size-11 shrink-0 place-items-center rounded-xl bg-slate-100 text-slate-600`,children:W(an,{class:`size-5`})}),W(`div`,{class:`min-w-0`,children:[W(`p`,{class:`m-0 text-xs font-extrabold uppercase tracking-[0.16em] text-slate-500`,children:`Settings`}),W(`h3`,{class:`mt-2 mb-0 text-lg font-black text-slate-950`,children:`자동 업데이트`}),W(`p`,{class:`mt-2 mb-0 max-w-3xl text-sm leading-6 text-slate-500`,children:`관리 소프트웨어의 자동 확인과 자동 설치를 설정합니다.`})]})]}),x?W(`span`,{"aria-live":`polite`,class:`inline-flex shrink-0 self-start items-center gap-1.5 rounded-full border border-amber-200 bg-amber-50 px-2.5 py-1 text-xs font-extrabold text-amber-800`,role:`status`,children:[W(Cn,{"aria-hidden":`true`,class:`size-3.5 shrink-0`}),`저장되지 않음`]}):null]}),W(`div`,{class:`ssh-software-settings-grid mt-4`,children:[W(`div`,{class:`ssh-software-settings-channel rounded-xl border border-slate-200 p-4 sm:p-5`,children:W(`div`,{class:`flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between`,children:[W(`div`,{class:`min-w-0`,children:[W(`strong`,{class:`block text-sm font-black text-slate-950`,children:`업데이트 채널`}),W(`span`,{class:`mt-1 block text-xs leading-5 text-slate-500`,children:`현재 관리 소프트웨어 업데이트를 확인하는 배포 채널입니다.`})]}),W(`span`,{class:`inline-flex min-h-9 shrink-0 items-center justify-center rounded-full px-3 py-1.5 text-xs font-extrabold ring-1 ring-inset ${rb(n.settings.channel)}`,title:`현재 업데이트 확인 채널`,children:nb(n.settings.channel)})]})}),W(`div`,{class:`min-w-0 overflow-hidden rounded-xl border border-slate-200 p-4 sm:p-5`,children:[W(`div`,{class:`flex min-w-0 items-start justify-between gap-4`,children:[W(`div`,{class:`min-w-0`,children:W(`strong`,{class:`block text-sm font-black text-slate-950`,children:`자동으로 업데이트 확인`})}),W(cb,{checked:f,label:`자동 업데이트 확인`,onChange:e=>{p(e),S(F({checkEnabled:e}))}})]}),W(`div`,{class:`mt-4 block`,children:[W(`span`,{class:`text-xs font-extrabold text-slate-700`,children:`확인 주기`}),W(W_,{ariaLabel:`업데이트 확인 주기`,className:`mt-2`,disabled:!f,id:`software-update-check-interval`,onChange:e=>{let t=Number(e);h(t),S(F({checkIntervalSeconds:t}))},options:$y.map(e=>({value:String(e.value),label:e.label})),value:String(m),variant:`emphasized`})]})]}),W(`div`,{class:`min-w-0 overflow-hidden rounded-xl border border-slate-200 p-4 sm:p-5`,children:[W(`div`,{class:`flex min-w-0 items-start justify-between gap-4`,children:[W(`div`,{class:`min-w-0`,children:W(`strong`,{class:`block text-sm font-black text-slate-950`,children:`자동으로 업데이트 설치`})}),W(cb,{checked:g,label:`자동 업데이트 설치`,onChange:e=>{_(e),S(F({autoInstall:e}))}})]}),W(`div`,{class:`mt-4 min-w-0`,children:[W(`span`,{class:`text-xs font-extrabold text-slate-700`,children:`설치 시각`}),W(Sy,{ariaLabel:`자동 업데이트 설치 시각`,className:`mt-2`,disabled:!g,id:`software-update-auto-install-time`,onChange:e=>{y(e),S(F({autoInstallTime:e}))},value:v,variant:`emphasized`})]})]})]}),g?W(`p`,{class:`mt-4 mb-0 rounded-xl bg-amber-50 px-4 py-3 text-xs font-bold leading-5 text-amber-800`,children:`자동 설치 중에는 SmartSafeHub 웹 화면 연결이 잠시 끊길 수 있습니다.`}):null,W(`div`,{class:`mt-5 flex flex-col gap-3 border-t border-slate-100 pt-5 sm:flex-row sm:items-center sm:justify-between`,children:[W(`p`,{class:`m-0 text-xs leading-5 text-slate-500`,children:[`마지막 설치: `,tb(n.lastInstallAt)]}),W(`button`,{class:`inline-flex min-h-11 items-center justify-center rounded-xl bg-slate-900 px-4 py-2.5 text-sm font-extrabold text-white transition hover:bg-slate-800 disabled:cursor-not-allowed disabled:opacity-50`,disabled:!x||e===`settings`||D,onClick:()=>void I(),type:`button`,children:e===`settings`?`저장 중`:`자동 업데이트 설정 저장`})]})]}),n.updateCount>0?W(`section`,{class:`border-t border-slate-200 p-5 sm:p-6 xl:col-start-1 xl:row-start-2`,"data-section":`software-update-release-notes`,children:[W(`div`,{class:`flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between sm:gap-4`,children:[W(`div`,{children:[W(`p`,{class:`m-0 text-xs font-extrabold uppercase tracking-[0.16em] text-teal-700`,children:`Release notes`}),W(`h3`,{class:`mt-2 mb-0 text-lg font-black text-slate-950`,children:`업데이트 내용`}),W(`p`,{class:`mt-2 mb-0 text-sm leading-6 text-slate-500`,children:N?`릴리즈 내역이 많거나 일부 내역을 가져올 수 없어 최신 버전의 변경 사항만 표시합니다.`:`현재 설치된 버전 이후 포함되는 주요 변경 사항입니다.`})]}),n.releaseNotes.length?W(`span`,{class:`shrink-0 rounded-full bg-slate-100 px-3 py-1.5 text-xs font-extrabold text-slate-600`,children:N?`최신 릴리즈 노트`:n.releaseNotes.length===1?`1개 릴리즈`:`${n.releaseNotes.length}개 릴리즈`}):null]}),n.releaseNotes.length?W(`div`,{class:`mt-5 grid gap-3`,children:[n.releaseNotes.map(e=>W(`article`,{class:`rounded-xl border border-slate-200 bg-slate-50 p-4 sm:p-5`,children:[W(`div`,{class:`flex flex-col gap-1 sm:flex-row sm:items-center sm:justify-between sm:gap-4`,children:[W(`strong`,{class:`break-all text-sm font-black text-slate-950`,children:e.version}),e.date?W(`span`,{class:`text-xs font-bold text-slate-500`,children:e.date}):null]}),e.summary?W(`p`,{class:`mt-3 mb-0 text-sm leading-6 text-slate-700`,children:e.summary}):null,e.sections.length?W(`details`,{class:`mt-4 rounded-lg border border-slate-200 bg-white p-3`,open:n.releaseNotes.length===1,children:[W(`summary`,{class:`cursor-pointer text-xs font-extrabold text-slate-800`,children:`자세한 변경 사항`}),W(`div`,{class:`mt-3 grid gap-4`,children:e.sections.map(t=>W(`section`,{children:[W(`h4`,{class:`m-0 text-xs font-extrabold text-slate-700`,children:t.title}),W(`ul`,{class:`mt-2 mb-0 space-y-1.5 pl-5 text-xs leading-5 text-slate-600`,children:t.items.map((n,r)=>W(`li`,{children:n},`${e.version}-${t.title}-${r}`))})]},`${e.version}-${t.title}`))})]}):null]},e.version)),n.releaseNotesComplete?null:W(`p`,{class:`m-0 rounded-xl bg-amber-50 px-4 py-3 text-xs font-bold leading-5 text-amber-800`,children:N?`최신 버전의 릴리즈 노트만 표시합니다. 이전 버전의 변경 사항은 생략되었을 수 있습니다.`:`일부 릴리즈 노트를 불러오지 못했습니다. 업데이트 확인과 설치는 계속 사용할 수 있습니다.`})]}):W(`p`,{class:`mt-4 mb-0 rounded-xl bg-slate-50 px-4 py-3 text-sm text-slate-500`,children:`릴리즈 노트를 불러오지 못했습니다. 업데이트 확인과 설치는 계속 사용할 수 있습니다.`})]}):null]}):null]})}function ub({action:e,actionError:t,data:n,error:r,firmware:i,loading:a,message:o,onCheck:s,onDismissFeedback:c,onInstall:l,onRetry:u,onSaveSettings:d}){return W(`section`,{class:`min-w-0 space-y-5`,children:[W(`div`,{children:W(J,{eyebrow:`Firmware`,title:`시스템 펌웨어`,description:`공유기 운영체제의 새 버전을 확인하고 설치합니다.`})}),W(Qy,{action:i.action,actionError:i.actionError,data:i.data,software:n,error:i.error,loading:i.loading,message:i.message,reconnecting:i.reconnecting,uploadProgress:i.uploadProgress,onCheck:()=>void i.check(),onDiscard:i.discard,onDismissFeedback:i.dismissFeedback,onInstall:i.install,onPrepare:()=>void i.prepare(),onRetry:()=>void i.refresh(),onUpload:i.upload}),W(`div`,{class:`pt-2`,children:W(J,{eyebrow:`Software`,title:`관리 소프트웨어`,description:`SmartSafeHub 관리 화면 및 서비스의 업데이트를 확인합니다.`})}),W(lb,{firmware:i.data,action:e,actionError:t,data:n,error:r,loading:a,message:o,onCheck:s,onDismissFeedback:c,onInstall:l,onRetry:u,onSaveSettings:d})]})}var db={};db.QR8bitByte=(e,t)=>{var n=t("QRMode");function r(e){this.mode=n.MODE_8BIT_BYTE,this.data=e}r.prototype={getLength:function(){return this.data.length},write:function(e){for(var t=0;t{function n(){this.buffer=[],this.length=0}n.prototype={get:function(e){var t=Math.floor(e/8);return(this.buffer[t]>>>7-e%8&1)==1},put:function(e,t){for(var n=0;n>>t-n-1&1)==1)},getLengthInBits:function(){return this.length},putBit:function(e){var t=Math.floor(this.length/8);this.buffer.length<=t&&this.buffer.push(0),e&&(this.buffer[t]|=128>>>this.length%8),this.length++}},e.exports=n},db.QRErrorCorrectLevel=(e,t)=>{e.exports={L:1,M:0,Q:3,H:2}},db.QRMaskPattern=(e,t)=>{e.exports={PATTERN000:0,PATTERN001:1,PATTERN010:2,PATTERN011:3,PATTERN100:4,PATTERN101:5,PATTERN110:6,PATTERN111:7}},db.QRMath=(e,t)=>{for(var n={glog:function(e){if(e<1)throw Error(`glog(`+e+`)`);return n.LOG_TABLE[e]},gexp:function(e){for(;e<0;)e+=255;for(;e>=256;)e-=255;return n.EXP_TABLE[e]},EXP_TABLE:Array(256),LOG_TABLE:Array(256)},r=0;r<8;r++)n.EXP_TABLE[r]=1<{e.exports={MODE_NUMBER:1,MODE_ALPHA_NUM:2,MODE_8BIT_BYTE:4,MODE_KANJI:8}},db.QRPolynomial=(e,t)=>{var n=t("QRMath");function r(e,t){if(e.length===void 0)throw Error(e.length+`/`+t);for(var n=0;n{var n=t("QRErrorCorrectLevel");function r(e,t){this.totalCount=e,this.dataCount=t}r.RS_BLOCK_TABLE=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]],r.getRSBlocks=function(e,t){var n=r.getRsBlockTable(e,t);if(n===void 0)throw Error(`bad rs block @ typeNumber:`+e+`/errorCorrectLevel:`+t);for(var i=n.length/3,a=[],o=0;o{var n=t("QRMode"),r=t("QRPolynomial"),i=t("QRMath"),a=t("QRMaskPattern"),o={PATTERN_POSITION_TABLE:[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],G15:1335,G18:7973,G15_MASK:21522,getBCHTypeInfo:function(e){for(var t=e<<10;o.getBCHDigit(t)-o.getBCHDigit(o.G15)>=0;)t^=o.G15<=0;)t^=o.G18<>>=1;return t},getPatternPosition:function(e){return o.PATTERN_POSITION_TABLE[e-1]},getMask:function(e,t,n){switch(e){case a.PATTERN000:return(t+n)%2==0;case a.PATTERN001:return t%2==0;case a.PATTERN010:return n%3==0;case a.PATTERN011:return(t+n)%3==0;case a.PATTERN100:return(Math.floor(t/2)+Math.floor(n/3))%2==0;case a.PATTERN101:return t*n%2+t*n%3==0;case a.PATTERN110:return(t*n%2+t*n%3)%2==0;case a.PATTERN111:return(t*n%3+(t+n)%2)%2==0;default:throw Error(`bad maskPattern:`+e)}},getErrorCorrectPolynomial:function(e){for(var t=new r([1],0),n=0;n5&&(n+=3+a-5)}for(r=0;r{var n=t("QR8bitByte"),r=t("QRUtil"),i=t("QRPolynomial"),a=t("QRRSBlock"),o=t("QRBitBuffer");function s(e,t){this.typeNumber=e,this.errorCorrectLevel=t,this.modules=null,this.moduleCount=0,this.dataCache=null,this.dataList=[]}s.prototype={addData:function(e){var t=new n(e);this.dataList.push(t),this.dataCache=null},isDark:function(e,t){if(e<0||this.moduleCount<=e||t<0||this.moduleCount<=t)throw Error(e+`,`+t);return this.modules[e][t]},getModuleCount:function(){return this.moduleCount},make:function(){if(this.typeNumber<1){var e=1;for(e=1;e<40;e++){for(var t=a.getRSBlocks(e,this.errorCorrectLevel),n=new o,i=0,s=0;s=7&&this.setupTypeNumber(e),this.dataCache===null&&(this.dataCache=s.createData(this.typeNumber,this.errorCorrectLevel,this.dataList)),this.mapData(this.dataCache,t)},setupPositionProbePattern:function(e,t){for(var n=-1;n<=7;n++)if(!(e+n<=-1||this.moduleCount<=e+n))for(var r=-1;r<=7;r++)t+r<=-1||this.moduleCount<=t+r||(0<=n&&n<=6&&(r===0||r===6)||0<=r&&r<=6&&(n===0||n===6)||2<=n&&n<=4&&2<=r&&r<=4?this.modules[e+n][t+r]=!0:this.modules[e+n][t+r]=!1)},getBestMaskPattern:function(){for(var e=0,t=0,n=0;n<8;n++){this.makeImpl(!0,n);var i=r.getLostPoint(this);(n===0||e>i)&&(e=i,t=n)}return t},createMovieClip:function(e,t,n){var r=e.createEmptyMovieClip(t,n),i=1;this.make();for(var a=0;a>i&1)==1,this.modules[Math.floor(i/3)][i%3+this.moduleCount-8-3]=n;for(var a=0;a<18;a++)n=!e&&(t>>a&1)==1,this.modules[a%3+this.moduleCount-8-3][Math.floor(a/3)]=n},setupTypeInfo:function(e,t){for(var n=this.errorCorrectLevel<<3|t,i=r.getBCHTypeInfo(n),a,o=0;o<15;o++)a=!e&&(i>>o&1)==1,o<6?this.modules[o][8]=a:o<8?this.modules[o+1][8]=a:this.modules[this.moduleCount-15+o][8]=a;for(var s=0;s<15;s++)a=!e&&(i>>s&1)==1,s<8?this.modules[8][this.moduleCount-s-1]=a:s<9?this.modules[8][15-s-1+1]=a:this.modules[8][15-s-1]=a;this.modules[this.moduleCount-8][8]=!e},mapData:function(e,t){for(var n=-1,i=this.moduleCount-1,a=7,o=0,s=this.moduleCount-1;s>0;s-=2)for(s===6&&s--;;){for(var c=0;c<2;c++)if(this.modules[i][s-c]===null){var l=!1;o>>a&1)==1),r.getMask(t,i,s-c)&&(l=!l),this.modules[i][s-c]=l,a--,a===-1&&(o++,a=7)}if(i+=n,i<0||this.moduleCount<=i){i-=n,n=-n;break}}}},s.PAD0=236,s.PAD1=17,s.createData=function(e,t,n){for(var i=a.getRSBlocks(e,t),c=new o,l=0;ld*8)throw Error(`code length overflow. (`+c.getLengthInBits()+`>`+d*8+`)`);for(c.getLengthInBits()+4<=d*8&&c.put(0,4);c.getLengthInBits()%8!=0;)c.putBit(!1);for(;!(c.getLengthInBits()>=d*8||(c.put(s.PAD0,8),c.getLengthInBits()>=d*8));)c.put(s.PAD1,8);return s.createBytes(c,i)},s.createBytes=function(e,t){for(var n=0,a=0,o=0,s=Array(t.length),c=Array(t.length),l=0;l=0?m.get(g):0}}for(var _=0,v=0;vString.fromCharCode(e)).join(``),r=new(pb(`index`))(0,pb(`QRErrorCorrectLevel`).M);r.addData(n),r.make();let i=r.getModuleCount();return Array.from({length:i},(e,t)=>Array.from({length:i},(e,n)=>!!r.isDark(t,n)))}function hb(e,t,n){let r=e=>e.replace(/([\\;,:"])/g,`\\$1`);return`WIFI:T:${t===`none`?`nopass`:t===`sae`?`WPA3`:`WPA`};S:${r(e)};P:${r(n)};;`}function gb(e){return`${(e.replace(/[\\/:*?"<>|\x00-\x1f\x7f]/g,`_`).replace(/[. ]+$/g,``).trim()||`Wi-Fi`).slice(0,64)}-QR.png`}function _b(e,t,n=8){let r=(e.length+8)*n;t.fillStyle=`#ffffff`,t.fillRect(0,0,r,r),t.fillStyle=`#000000`,e.forEach((e,r)=>e.forEach((e,i)=>{e&&t.fillRect((i+4)*n,(r+4)*n,n,n)}))}function vb({load:e,onClose:t}){let[n,r]=L(null),[i,a]=L(null),[o,s]=L(null);R(()=>{let t=!0;return e().then(e=>{if(t)try{r({ssid:e.ssid,matrix:mb(hb(e.ssid,e.security,e.password))})}catch{a(`QR 코드를 생성할 수 없습니다.`)}}).catch(()=>{t&&a(`Wi-Fi 정보를 불러오지 못했습니다.`)}),()=>{t=!1,r(null)}},[e]);let c=n?.matrix.length??0;return W(`div`,{class:`fixed inset-0 z-50 flex items-center justify-center bg-slate-950/70 p-4`,role:`presentation`,onClick:e=>{e.target===e.currentTarget&&t()},children:W(`section`,{role:`dialog`,"aria-modal":`true`,"aria-label":`Wi-Fi QR 코드`,class:`w-full max-w-sm rounded-2xl bg-white p-5 text-center text-slate-950 shadow-2xl sm:p-7`,children:[W(`h2`,{class:`m-0 text-xl font-extrabold`,children:`Wi-Fi QR 코드`}),W(`p`,{class:`mt-2 break-all text-sm font-semibold text-slate-600`,children:n?.ssid??`Wi-Fi 정보를 확인하는 중`}),i?W(`p`,{role:`alert`,class:`my-6 text-sm font-bold text-red-700`,children:i}):null,!n&&!i?W(`p`,{class:`my-6 text-sm`,children:`QR 코드를 만드는 중...`}):null,n?W(`div`,{class:`mx-auto my-5 w-full max-w-[288px] rounded-xl border border-slate-200 bg-white p-3`,children:W(`svg`,{class:`block h-auto w-full`,viewBox:`0 0 ${c+8} ${c+8}`,role:`img`,"aria-label":`${n.ssid} Wi-Fi 연결 QR 코드`,"shape-rendering":`crispEdges`,children:[W(`rect`,{width:c+8,height:c+8,fill:`white`}),n.matrix.map((e,t)=>e.map((e,n)=>e?W(`rect`,{x:n+4,y:t+4,width:`1`,height:`1`,fill:`black`},`${t}-${n}`):null))]})}):null,W(`p`,{class:`text-xs leading-5 text-slate-500`,children:[W(`span`,{class:`block`,children:`카메라로 스캔해 Wi-Fi에 연결하세요.`}),W(`span`,{class:`block`,children:`QR 코드에는 비밀번호가 포함됩니다.`})]}),o?W(`p`,{role:`alert`,class:`mt-3 text-sm font-bold text-red-700`,children:o}):null,n?W(`button`,{class:`mt-5 inline-flex min-h-11 w-full items-center justify-center gap-2 rounded-xl bg-teal-700 px-4 font-extrabold text-white transition hover:bg-teal-800 focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-200`,onClick:()=>{if(n){s(null);try{let e=(n.matrix.length+8)*8,t=document.createElement(`canvas`);t.width=e,t.height=e;let r=t.getContext(`2d`);if(!r)throw Error(`Canvas is not supported`);_b(n.matrix,r,8);let i=document.createElement(`a`);i.href=t.toDataURL(`image/png`),i.download=gb(n.ssid),i.click()}catch{s(`QR 코드 이미지를 저장하지 못했습니다. 다시 시도해 주세요.`)}}},type:`button`,children:[W(hn,{class:`size-5`}),`QR 코드 다운로드`]}):null,W(`button`,{class:`mt-2 min-h-11 w-full rounded-xl border border-slate-300 bg-white px-4 font-extrabold text-slate-700 transition hover:bg-slate-50`,onClick:t,type:`button`,children:`닫기`})]})})}function yb({guest:e,busy:t,saving:n,onUpdate:r,onLoadQr:i}){let[a,o]=L(!1),[s,c]=L(!1),[l,u]=L(e.ssid),[d,f]=L(``),[p,m]=L(e.enabled),[h,g]=L(null);if(R(()=>{u(e.ssid),m(e.enabled),f(``),g(null)},[e]),!e.supported)return W(`section`,{class:`mt-5 rounded-2xl border border-slate-200 bg-white p-5 sm:p-6`,children:[W(`h2`,{class:`m-0 text-lg font-extrabold text-slate-900`,children:`게스트 Wi-Fi`}),W(`p`,{class:`mt-2 mb-0 text-sm text-slate-600`,children:`2.4GHz 기본 Wi-Fi 장치를 찾지 못해 게스트 Wi-Fi를 사용할 수 없습니다.`})]});let _=l.trim()!==e.ssid||p!==e.enabled||d.length>0,v=t||_||!e.enabled||!e.passwordConfigured,y=async t=>{t.preventDefault();let n=l.trim(),i=new TextEncoder().encode(n).length;i<1||i>32?g(`Wi-Fi 이름은 UTF-8 기준 1~32바이트로 입력해 주세요.`):d&&!(d.length>=8&&d.length<=63||/^[0-9a-fA-F]{64}$/.test(d))?g(`비밀번호는 8~63자 또는 64자리 16진수여야 합니다.`):p&&!e.passwordConfigured&&!d?g(`게스트 전용 비밀번호를 입력해 주세요.`):window.confirm(`게스트 Wi-Fi 설정을 적용하면 무선 연결이 잠시 끊어질 수 있습니다. 계속하시겠습니까?`)&&(g(null),await r({ssid:n,enabled:p,password:d})&&f(``))};return W(b,{children:[W(`form`,{"aria-label":`게스트 Wi-Fi 설정`,class:`mt-5 rounded-2xl border border-slate-200 bg-white p-4 shadow-sm shadow-slate-900/5 sm:p-6`,onSubmit:e=>void y(e),children:[W(`div`,{class:`flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between`,children:[W(`button`,{"aria-controls":`ssh-guest-settings`,"aria-expanded":a,"aria-label":`게스트 Wi-Fi 설정 ${a?`접기`:`펼치기`}`,class:`flex min-w-0 flex-1 items-start gap-3 rounded-xl text-left focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-100`,onClick:()=>o(e=>!e),type:`button`,children:[W(`span`,{class:`grid size-11 shrink-0 place-items-center rounded-xl bg-teal-50 text-teal-700`,children:W(Jt,{class:`size-6`})}),W(`span`,{class:`min-w-0 flex-1`,children:[W(`span`,{class:`block text-xs font-extrabold uppercase tracking-[0.16em] text-slate-500`,children:`2.4 GHz · 분리 네트워크`}),W(`span`,{class:`mt-2 block text-xl font-extrabold tracking-tight text-slate-950`,children:`게스트 Wi-Fi`}),W(`span`,{class:`mt-2 block break-all text-sm text-slate-600`,children:e.ssid})]}),W(An,{class:`mt-3 size-5 shrink-0 text-slate-500 transition-transform ${a?`rotate-180`:``}`})]}),W(`div`,{class:`flex flex-wrap items-center gap-2 sm:justify-end`,children:[_&&W(`span`,{"aria-live":`polite`,class:`inline-flex items-center gap-1 rounded-full border border-amber-200 bg-amber-50 px-2.5 py-1 text-xs font-extrabold text-amber-800`,children:[W(Cn,{class:`size-3.5`}),`저장되지 않음`]}),W(`button`,{"aria-label":`게스트 Wi-Fi QR 코드 보기`,class:`inline-flex min-h-10 items-center gap-2 rounded-xl border border-slate-300 bg-white px-3.5 py-2 text-sm font-extrabold text-slate-800 transition hover:border-teal-300 hover:text-teal-700 focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-100 disabled:cursor-not-allowed disabled:opacity-50`,disabled:v,onClick:()=>c(!0),title:e.enabled?void 0:`게스트 Wi-Fi를 켜고 저장하면 QR 코드를 사용할 수 있습니다.`,type:`button`,children:[W(sn,{class:`size-4 shrink-0`}),`QR 코드 보기`]}),W(`span`,{class:`rounded-full px-3 py-1.5 text-xs font-extrabold ${e.enabled?`bg-emerald-50 text-emerald-700`:`bg-slate-100 text-slate-600`}`,children:e.enabled?`사용 중`:`꺼짐`})]})]}),W(`div`,{class:a?``:`hidden`,id:`ssh-guest-settings`,children:[W(`p`,{class:`mt-5 mb-0 text-sm leading-6 text-slate-600`,children:`방문자는 인터넷만 이용하며 내부 네트워크와 공유기 관리 화면에는 접근할 수 없습니다.`}),W(`div`,{class:`mt-5 grid gap-5 lg:grid-cols-2`,children:[W(`label`,{class:`block`,children:[W(`span`,{class:`text-sm font-extrabold text-slate-800`,children:`게스트 Wi-Fi 이름 (SSID)`}),W(`input`,{autocomplete:`off`,class:`mt-2 min-h-11 w-full rounded-xl border-2 border-slate-300 bg-slate-50 px-4 py-2.5 text-sm font-semibold text-slate-950 outline-none focus:border-teal-500 focus:bg-white focus:ring-4 focus:ring-teal-100 disabled:opacity-60`,disabled:t,maxLength:32,onInput:e=>u(e.currentTarget.value),value:l})]}),W(`label`,{class:`block`,children:[W(`span`,{class:`text-sm font-extrabold text-slate-800`,children:`게스트 Wi-Fi 비밀번호`}),W(`input`,{autocomplete:`new-password`,class:`mt-2 min-h-11 w-full rounded-xl border-2 border-slate-300 bg-slate-50 px-4 py-2.5 text-sm font-semibold text-slate-950 outline-none focus:border-teal-500 focus:bg-white focus:ring-4 focus:ring-teal-100 disabled:opacity-60`,disabled:t,onInput:e=>f(e.currentTarget.value),placeholder:e.passwordConfigured?`비워 두면 기존 비밀번호 유지`:`8자 이상의 별도 비밀번호 입력`,type:`password`,value:d}),W(`span`,{class:`mt-2 block text-xs text-slate-500`,children:`WPA2/WPA3 혼합 · 기본 Wi-Fi와 다른 비밀번호를 권장합니다.`})]})]}),W(`div`,{class:`mt-5 rounded-xl bg-slate-50 px-4 py-3 text-sm leading-6 text-slate-700`,children:[`내부망 접근 차단 · 게스트 기기 간 통신 차단 · SafeShield DNS 사용`,e.subnet&&W(`span`,{class:`block text-xs text-slate-500`,children:[`게스트 게이트웨이: `,e.subnet]})]}),W(`div`,{class:`mt-5 flex flex-col gap-4 border-t border-slate-200 pt-5 sm:flex-row sm:items-center sm:justify-between`,children:[W(`label`,{class:`inline-flex min-h-11 items-center gap-3 text-sm font-extrabold text-slate-800`,children:[W(`input`,{checked:p,class:`size-5 accent-teal-700`,disabled:t,onChange:e=>m(e.currentTarget.checked),type:`checkbox`}),`게스트 Wi-Fi 사용`]}),W(`button`,{class:`inline-flex min-h-11 w-full items-center justify-center gap-2 rounded-xl bg-teal-700 px-5 py-2.5 text-sm font-extrabold text-white transition hover:bg-teal-800 disabled:opacity-60 sm:w-auto ${Y_(t)}`,disabled:t||!_,type:`submit`,children:[W(Sn,{class:`size-5 ${n?`animate-pulse`:``}`}),n?`적용 중`:`설정 저장`]})]}),h&&W(`p`,{class:`mt-4 mb-0 rounded-xl bg-red-50 px-4 py-3 text-sm font-bold text-red-800`,children:h})]})]}),s&&W(vb,{load:i,onClose:()=>c(!1)})]})}var bb=[{value:`sae-mixed`,label:`WPA2/WPA3 혼합 (권장)`},{value:`sae`,label:`WPA3-Personal`},{value:`psk2`,label:`WPA2-Personal`},{value:`none`,label:`암호화 없음`}];function xb(e){switch(e.security){case`sae-mixed`:return`WPA2/WPA3 혼합`;case`sae`:return`WPA3-Personal`;case`psk2`:return`WPA2-Personal`;case`none`:return`암호화 없음`;default:return`고급 설정 (${e.securityRaw})`}}function Sb(e){return e.length>=8&&e.length<=63||/^[0-9a-fA-F]{64}$/.test(e)}function Cb({network:e,busy:t,saving:n,onUpdate:r,onLoadQr:i}){let[a,o]=L(e.ssid),[s,c]=L(e.enabled),[l,u]=L(e.security===`custom`?`keep`:e.security),[d,f]=L(``),[p,m]=L(!1),h=B(()=>i(e.section),[i,e.section]),[g,_]=L(null);R(()=>{o(e.ssid),c(e.enabled),u(e.security===`custom`?`keep`:e.security),f(``),_(null)},[e]);let v=e.security===`custom`&&l===`keep`,y=l===`psk2`||l===`sae-mixed`||l===`sae`,b=e.security===`custom`?`keep`:e.security,x=a.trim()!==e.ssid||s!==e.enabled||l!==b||d.length>0,S=t||x||e.security===`custom`||!e.enabled||e.security!==`none`&&!e.passwordConfigured,C=[...e.security===`custom`?[{value:`keep`,label:`현재 고급 설정 유지 (${e.securityRaw})`}]:[],...bb],w=async t=>{t.preventDefault();let n=a.trim(),i=new TextEncoder().encode(n).length;i<1||i>32?_(`SSID는 UTF-8 기준 1~32바이트로 입력해 주세요.`):d&&!Sb(d)?_(`비밀번호는 8~63자 또는 64자리 16진수여야 합니다.`):y&&!e.passwordConfigured&&d.length===0?_(`보안 Wi-Fi를 사용하려면 비밀번호를 입력해 주세요.`):window.confirm(`Wi-Fi 설정을 적용하면 현재 무선 연결이 잠시 끊어질 수 있습니다. 계속하시겠습니까?`)&&(_(null),await r({section:e.section,ssid:n,security:l,password:d,enabled:s})&&f(``))};return W(`form`,{class:`rounded-2xl border border-slate-200 bg-white p-4 shadow-sm shadow-slate-900/5 sm:p-6`,onSubmit:e=>void w(e),children:[W(`div`,{class:`flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between`,children:[W(`div`,{class:`flex min-w-0 items-start gap-3`,children:[W(`span`,{class:`grid size-11 shrink-0 place-items-center rounded-xl bg-teal-50 text-teal-700`,children:W(Jt,{class:`size-6`})}),W(`div`,{class:`min-w-0`,children:[W(`p`,{class:`m-0 text-xs font-extrabold uppercase tracking-[0.16em] text-slate-500`,children:e.bandLabel}),W(`h2`,{class:`mt-2 mb-0 break-words text-xl font-extrabold tracking-tight text-slate-950`,children:e.ssid||`이름 없는 Wi-Fi`}),W(`p`,{class:`mt-2 mb-0 text-sm text-slate-600`,children:[`채널 `,e.channel??`자동`,` · 연결 기기 `,e.clientCount,`대`]})]})]}),W(`div`,{class:`flex flex-wrap items-center gap-2 sm:justify-end`,children:[x?W(`span`,{"aria-live":`polite`,class:`inline-flex shrink-0 items-center gap-1.5 rounded-full border border-amber-200 bg-amber-50 px-2.5 py-1 text-xs font-extrabold text-amber-800`,role:`status`,children:[W(Cn,{"aria-hidden":`true`,class:`size-3.5 shrink-0`}),`저장되지 않음`]}):null,W(`button`,{"aria-label":`${e.bandLabel} Wi-Fi QR 코드 보기`,class:`inline-flex min-h-10 items-center gap-2 rounded-xl border border-slate-300 bg-white px-3.5 py-2 text-sm font-extrabold text-slate-800 shadow-sm transition hover:border-teal-300 hover:text-teal-700 focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-100 disabled:cursor-not-allowed disabled:opacity-50`,disabled:S,onClick:()=>m(!0),type:`button`,children:[W(sn,{class:`size-4 shrink-0`}),`QR 코드 보기`]}),W(`span`,{class:`inline-flex w-fit items-center gap-2 rounded-full px-3 py-1.5 text-xs font-extrabold ring-1 ring-inset ${e.runtimeUp?`bg-emerald-50 text-emerald-700 ring-emerald-200`:`bg-slate-100 text-slate-600 ring-slate-200`}`,children:[W(`span`,{class:`size-2 rounded-full ${e.runtimeUp?`bg-emerald-500`:`bg-slate-400`}`}),e.runtimeUp?`동작 중`:e.enabled?`시작 대기`:`꺼짐`]})]})]}),W(`div`,{class:`mt-6 grid gap-5 lg:grid-cols-2`,children:[W(`label`,{class:`block`,children:[W(`span`,{class:`text-sm font-extrabold text-slate-800`,children:`Wi-Fi 이름 (SSID)`}),W(`input`,{autocomplete:`off`,class:`mt-2 min-h-11 w-full rounded-xl border-2 border-slate-300 bg-slate-50 px-4 py-2.5 text-sm font-semibold text-slate-950 shadow-inner outline-none transition placeholder:text-slate-400 focus:border-teal-500 focus:bg-white focus:ring-4 focus:ring-teal-100 disabled:cursor-not-allowed disabled:bg-slate-100`,disabled:t,maxLength:32,onInput:e=>o(e.currentTarget.value),value:a})]}),W(`div`,{class:`block`,children:[W(`span`,{class:`text-sm font-extrabold text-slate-800`,children:`보안 방식`}),W(W_,{ariaLabel:`${e.bandLabel} Wi-Fi 보안 방식`,className:`mt-2`,disabled:t,id:`wifi-security-${e.section}`,onChange:e=>{let t=e;u(t),(t===`none`||t===`keep`)&&f(``)},options:C,value:l,variant:`emphasized`}),W(`span`,{class:`mt-2 block text-xs text-slate-500`,children:[`현재: `,xb(e)]})]}),W(`label`,{class:`block lg:col-span-2`,children:[W(`span`,{class:`text-sm font-extrabold text-slate-800`,children:`새 비밀번호`}),W(`input`,{autocomplete:`new-password`,class:`mt-2 min-h-11 w-full rounded-xl border-2 border-slate-300 bg-slate-50 px-4 py-2.5 text-sm font-semibold text-slate-950 shadow-inner outline-none transition placeholder:text-slate-400 focus:border-teal-500 focus:bg-white focus:ring-4 focus:ring-teal-100 disabled:cursor-not-allowed disabled:bg-slate-100`,disabled:t||l===`none`||v,onInput:e=>f(e.currentTarget.value),placeholder:v?`고급 보안 설정은 기존 LuCI에서 변경합니다`:e.passwordConfigured?`비워 두면 기존 비밀번호를 유지합니다`:`8자 이상의 비밀번호를 입력하세요`,type:`password`,value:d}),W(`span`,{class:`mt-2 block text-xs text-slate-500`,children:`저장된 비밀번호는 일반 설정 조회에 노출되지 않습니다. QR 코드에는 비밀번호가 포함됩니다.`})]})]}),W(`div`,{class:`mt-6 flex flex-col gap-4 border-t border-slate-200 pt-5 sm:flex-row sm:items-center sm:justify-between`,children:[W(`label`,{class:`inline-flex min-h-11 items-center gap-3 text-sm font-extrabold text-slate-800`,children:[W(`input`,{checked:s,class:`size-5 accent-teal-700`,disabled:t,onChange:e=>c(e.currentTarget.checked),type:`checkbox`}),`이 Wi-Fi 사용`]}),W(`button`,{class:`inline-flex min-h-11 w-full items-center justify-center gap-2 rounded-xl border-0 bg-teal-700 px-5 py-2.5 text-sm font-extrabold text-white shadow-sm transition hover:bg-teal-800 focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-200 ${Y_(t)} disabled:opacity-60 sm:w-auto`,disabled:t||!x,type:`submit`,children:[W(Sn,{class:`size-5 ${n?`animate-pulse`:``}`}),n?`적용 중`:`설정 저장`]})]}),p?W(vb,{load:h,onClose:()=>m(!1)}):null,g?W(`p`,{class:`mt-4 mb-0 rounded-xl bg-red-50 px-4 py-3 text-sm font-bold text-red-800`,children:g}):null]})}function wb({feedback:e,onDismiss:t}){return W(`div`,{"aria-live":`polite`,class:`mb-5 flex flex-col gap-3 rounded-2xl border px-4 py-3 text-sm sm:flex-row sm:items-center sm:justify-between ${e.kind===`success`?`border-emerald-200 bg-emerald-50 text-emerald-900`:e.kind===`warning`?`border-amber-200 bg-amber-50 text-amber-900`:`border-red-200 bg-red-50 text-red-900`}`,children:[W(`p`,{class:`m-0 font-bold leading-6`,children:e.message}),W(`button`,{class:`min-h-11 w-full shrink-0 rounded-lg border border-current bg-transparent sm:min-h-9 sm:w-auto px-3 py-1.5 text-xs font-extrabold`,onClick:t,type:`button`,children:`닫기`})]})}function Tb({data:e,error:t,feedback:n,loading:r,updatingSection:i,onDismissFeedback:a,onRetry:o,onUpdate:s,onUpdateGuest:c,onLoadQr:l,onLoadGuestQr:u}){return r?W(vs,{}):t?W(ys,{message:t,onRetry:o}):e?W(b,{children:[n?W(wb,{feedback:n,onDismiss:a}):null,W(`section`,{class:`mb-5 rounded-2xl border border-amber-200 bg-amber-50 p-5 text-amber-950 sm:p-6`,children:[W(`p`,{class:`m-0 text-xs font-extrabold uppercase tracking-[0.16em] text-amber-700`,children:`연결 주의`}),W(`h2`,{class:`mt-2 mb-0 text-lg font-extrabold`,children:`Wi-Fi 이름이나 비밀번호를 바꾸면 현재 연결이 끊어집니다`}),W(`p`,{class:`mt-2 mb-0 text-sm leading-6 text-amber-900/80`,children:`변경한 Wi-Fi 이름과 비밀번호로 다시 연결하면 SmartSafeHub를 계속 사용할 수 있습니다. VLAN 등 고급 무선 옵션은 기존 LuCI에서 관리합니다.`})]}),W(J,{eyebrow:`Wireless`,title:`기본 Wi-Fi`,description:`무선 네트워크의 이름, 비밀번호, 보안 방식과 QR 코드를 관리합니다.`}),e.networks.length?W(`section`,{"aria-label":`기본 Wi-Fi 설정`,class:`grid gap-5 xl:grid-cols-2`,children:e.networks.map(e=>W(Cb,{busy:i!==null,network:e,onUpdate:s,onLoadQr:l,saving:i===e.section},e.section))}):W(`section`,{class:`rounded-2xl border border-slate-200 bg-white p-5 text-center shadow-sm shadow-slate-900/5 sm:p-8`,children:[W(Jt,{class:`mx-auto size-10 text-slate-400`}),W(`h2`,{class:`mt-4 mb-0 text-lg font-extrabold text-slate-950`,children:`관리할 기본 Wi-Fi를 찾지 못했습니다`}),W(`p`,{class:`mt-2 mb-0 text-sm leading-6 text-slate-600`,children:`기존 LuCI에서 AP 모드의 무선 네트워크를 먼저 구성해 주세요.`})]}),e.guest&&W(b,{children:[W(`div`,{role:`separator`,"aria-label":`기본 Wi-Fi와 게스트 Wi-Fi 구분`,class:`mt-5 w-full border-t border-slate-200`}),W(`div`,{class:`mt-5`,children:W(J,{eyebrow:`Guest network`,title:`방문자용 네트워크`,description:`기본 네트워크와 분리된 게스트 Wi-Fi를 관리합니다.`})}),W(yb,{guest:e.guest,busy:i!==null,saving:i===`ssh_guest`,onUpdate:c,onLoadQr:u})]}),e.guestError&&W(`section`,{class:`mt-5 rounded-2xl border border-amber-200 bg-amber-50 p-5 text-sm text-amber-900`,role:`status`,children:[`게스트 Wi-Fi 정보를 불러오지 못했습니다. 패키지를 설치한 직후라면 로그아웃 후 다시 로그인해 주세요.`,W(`button`,{class:`ml-3 underline font-bold`,onClick:o,type:`button`,children:`다시 시도`})]})]}):null}function Eb(){return U(`smartsafehub`,`netwatch_status`,{},{priority:`low`,timeoutMs:5e3})}function Db(e){return U(`smartsafehub`,`netwatch_enabled_update`,{enabled:e},{priority:`high`,timeoutMs:6e3})}var Ob=120,kb=40;function Ab(e,t){return t.limited?{label:`판단 보류`,tone:`bg-slate-100 text-slate-600`}:t.alert?.ip===e.ip?{label:`주의 기록`,tone:`bg-amber-100 text-amber-800`}:e.connections>=Ob||e.destinations>=kb?{label:`연결 많음`,tone:`bg-amber-50 text-amber-800`}:{label:`관찰 중`,tone:`bg-slate-100 text-slate-600`}}function jb(e){return(e?.events??[]).filter(e=>e.eventType===`network_watch.anomaly.warning`&&e.source===`network_watch`&&Number.isFinite(e.occurredAt)&&e.occurredAt>0).sort((e,t)=>t.occurredAt-e.occurredAt).slice(0,3)}function Mb(e){let t=e.metadata?.source_ip;return typeof t==`string`&&/^\d{1,3}(?:\.\d{1,3}){3}$/.test(t)?t:null}function Nb({data:e,error:t,loading:n,onRetry:r,onSetEnabled:i,devices:a,activity:o,demo:s=!1,onDemoAction:c}){let[l,u]=L(!1),[d,f]=L(null),[p,m]=L(null),h=!!e?.enabled,g=h&&e?.phase===`ready`,_=l?`저장 중…`:e?h?e.phase===`ready`?`사용 중 · 관찰 중`:e.phase===`unavailable`?`사용 중 · 수집 불가`:`사용 중 · 준비 중`:`사용 안 함`:n?`확인 중`:`상태 확인 불가`,v=e&&h&&e.phase===`ready`?`text-emerald-700`:e&&h&&e.phase===`unavailable`?`text-amber-700`:`text-slate-500`;R(()=>{if(!p)return;let e=window.setTimeout(()=>m(null),3500);return()=>window.clearTimeout(e)},[p]);let y=async()=>{if(s)c?.();else if(!l&&e){u(!0),f(null),m(null);try{await i(!h),m(h?`Network Watch를 껐습니다.`:`Network Watch를 켰습니다. 다음 관찰 주기부터 반영됩니다.`)}catch(e){f(e instanceof Error?e.message:`설정을 저장하지 못했습니다.`)}finally{u(!1)}}},x=new Map;for(let e of a?.devices??[])e.online&&e.ipv4Address&&e.hostname?.trim()&&x.set(e.ipv4Address,e.hostname.trim());let S=jb(o);return W(`div`,{class:`space-y-5`,children:[W(J,{eyebrow:`Security`,title:`Network Watch`,description:`연결된 기기의 외부 통신 변화를 ${e?.intervalSeconds??60}초 간격으로 관찰합니다. 의심 징후는 참고 정보이며 악성 앱을 확정하거나 연결을 자동 차단하지 않습니다.`,action:W(`div`,{class:`flex items-center gap-3 rounded-xl border border-slate-100 bg-slate-50 px-3 py-2.5`,"aria-busy":l,children:[W(`span`,{class:`grid size-10 shrink-0 place-items-center rounded-xl bg-slate-100 text-slate-500`,children:W($t,{class:`size-5`})}),W(`div`,{class:`text-right`,children:[W(`p`,{class:`m-0 text-[0.68rem] font-bold text-slate-400`,children:`관찰 수집`}),W(`p`,{class:`mt-1 mb-0 whitespace-nowrap text-xs font-extrabold ${v}`,role:`status`,children:_})]}),W(`button`,{type:`button`,role:`switch`,"aria-checked":h,"aria-label":`네트워크 이상 감지 사용`,disabled:l||!e,onClick:()=>void y(),class:`ssh-switch-control relative inline-flex shrink-0 items-center rounded-full border-0 p-0 transition focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-100 disabled:cursor-not-allowed disabled:opacity-50 ${h?`bg-teal-600`:`bg-slate-300`}`,children:W(`span`,{"aria-hidden":`true`,class:`ssh-switch-thumb absolute top-1 shadow-sm transition-[left] ${h?`left-6`:`left-1`}`})})]})}),p?W(`p`,{role:`status`,class:`text-sm text-teal-700`,children:p}):null,d?W(`p`,{role:`alert`,class:`text-sm text-rose-700`,children:d}):null,n&&!e?W(vs,{}):null,t&&!e?W(ys,{message:t,onRetry:r}):null,e&&!h?W(`p`,{role:`status`,class:`rounded-xl border border-slate-200 bg-slate-50 p-4 text-sm text-slate-600`,children:`네트워크 관찰이 꺼져 있습니다. 기존 방화벽과 SafeShield 보호에는 영향이 없습니다.`}):null,h&&e?.phase===`preparing`?W(`p`,{role:`status`,class:`rounded-xl border border-slate-200 bg-slate-50 p-4 text-sm text-slate-600`,children:`첫 연결 관찰을 준비하고 있습니다. 잠시 후 자동으로 갱신됩니다.`}):null,h&&e?.phase===`unavailable`?W(`div`,{role:`alert`,class:`rounded-xl border border-amber-200 bg-amber-50 p-4 text-sm text-amber-900`,children:`연결 정보를 읽을 수 없습니다. OpenWrt의 conntrack 지원 여부를 확인해 주세요. 실제 감시가 수행 중인 것으로 표시하지 않습니다.`}):null,g&&e?W(b,{children:[W(`div`,{class:`grid gap-3 sm:grid-cols-3`,children:[W(`div`,{class:`rounded-2xl border border-slate-200 bg-white p-5 shadow-sm`,children:[W(`p`,{class:`text-sm font-semibold text-slate-500`,children:`현재 연결 수`}),W(`p`,{class:`mt-2 text-xl font-extrabold text-slate-900`,children:Y(e.totalConnections)})]}),W(`div`,{class:`rounded-2xl border border-slate-200 bg-white p-5 shadow-sm`,children:[W(`p`,{class:`text-sm font-semibold text-slate-500`,children:`관찰된 기기 수`}),W(`p`,{class:`mt-2 text-xl font-extrabold text-slate-900`,children:Y(e.totalDevices)})]}),W(`div`,{class:`rounded-2xl border border-slate-200 bg-white p-5 shadow-sm`,children:[W(`p`,{class:`text-sm font-semibold text-slate-500`,children:`마지막 갱신`}),W(`p`,{class:`mt-2 text-xl font-extrabold text-slate-900`,children:e.sampledAt>0?jo(e.sampledAt):`—`}),e.sampledAt>0?W(`p`,{class:`mt-1 text-xs text-slate-500`,title:`브라우저 시간대 기준`,children:Ao(e.sampledAt)}):null]})]}),e.limited?W(`p`,{role:`status`,class:`rounded-xl border border-amber-200 bg-amber-50 p-4 text-sm text-amber-800`,children:`연결 수가 관찰 한도를 초과하여 일부 연결만 집계했습니다. 이번 결과로 이상 여부를 판단하지 않습니다.`}):null,e.alert?W(`p`,{role:`status`,class:`rounded-xl border border-amber-200 bg-amber-50 p-4 text-sm text-amber-900`,children:[e.alert.ip,` 기기에서 연결 또는 목적지 수가 연속해 높게 관찰되었습니다. 정상적인 앱 사용일 수도 있습니다.`]}):null,W(`section`,{class:`rounded-2xl border border-slate-200 bg-white p-5 shadow-sm`,children:[W(`h2`,{class:`text-base font-extrabold text-slate-900`,children:`기기별 외부 연결 현황`}),W(`p`,{class:`mt-1 text-xs text-slate-500`,children:`IPv4 기준 현재 연결 스냅샷 · 최대 12대 표시 · 콘텐츠 분석 없음`}),e.devices.length?W(`div`,{class:`mt-4 overflow-x-auto`,children:W(`table`,{class:`w-full text-left text-sm`,children:[W(`thead`,{class:`text-slate-500`,children:W(`tr`,{children:[W(`th`,{class:`pb-3`,children:`기기`}),W(`th`,{class:`pb-3 text-right`,children:`연결 수`}),W(`th`,{class:`pb-3 text-right`,children:`외부 목적지 수`}),W(`th`,{class:`pb-3 text-right`,children:`관찰 상태`})]})}),W(`tbody`,{children:e.devices.map(t=>{let n=Ab(t,e),r=x.get(t.ip);return W(`tr`,{class:`border-t border-slate-100 text-slate-700`,children:[W(`td`,{class:`py-3`,children:[W(`div`,{class:`font-semibold`,children:r||t.ip}),r?W(`div`,{class:`mt-0.5 text-xs text-slate-500`,children:t.ip}):null]}),W(`td`,{class:`py-3 text-right`,children:Y(t.connections)}),W(`td`,{class:`py-3 text-right`,children:Y(t.destinations)}),W(`td`,{class:`py-3 text-right`,children:W(`span`,{class:`inline-block whitespace-nowrap rounded-full px-2.5 py-1 text-xs font-bold ${n.tone}`,children:n.label})})]},t.ip)})})]})}):W(`p`,{class:`mt-4 text-sm text-slate-500`,children:`현재 확인된 외부 연결이 없습니다.`}),W(`p`,{class:`mt-3 text-xs text-slate-500`,children:`‘연결 많음’은 관찰량 기준이며 악성 통신 판정이 아닙니다. 기기 이름을 확인할 수 없으면 IP만 표시합니다.`})]}),W(`section`,{class:`rounded-2xl border border-slate-200 bg-white p-5 shadow-sm`,children:[W(`div`,{class:`flex flex-wrap items-center justify-between gap-3`,children:[W(`h2`,{class:`text-base font-extrabold text-slate-900`,children:`최근 감지 이벤트`}),W(`a`,{href:`#activity`,class:`text-xs font-bold text-teal-700 hover:underline`,children:`최근 활동 전체 보기`})]}),W(`p`,{class:`mt-1 text-xs text-slate-500`,children:`현재 부팅 후 기록된 Network Watch 주의 이벤트 중 최신 3건`}),S.length?W(`ul`,{class:`mt-4 divide-y divide-slate-100`,children:S.map(e=>W(`li`,{class:`flex flex-wrap items-start justify-between gap-2 py-3 first:pt-0 last:pb-0`,children:[W(`div`,{class:`min-w-0`,children:[W(`p`,{class:`text-sm font-bold text-slate-800`,children:`외부 통신 증가 감지`}),W(`p`,{class:`mt-1 text-xs text-slate-600`,children:[Mb(e)??`연결된 기기`,` · 비정상 통신 확정 아님`]})]}),W(`time`,{class:`text-xs text-slate-500`,dateTime:new Date(e.occurredAt*1e3).toISOString(),title:Ao(e.occurredAt),children:jo(e.occurredAt)})]},e.eventId))}):W(`p`,{class:`mt-4 text-sm text-slate-500`,children:`최근 감지된 주의 이벤트가 없습니다.`})]})]}):null,W(`section`,{"aria-label":`Network Watch 관찰 범위`,class:`rounded-xl border border-slate-200 bg-slate-50 p-4`,children:[W(`h2`,{class:`text-sm font-bold text-slate-700`,children:`관찰 범위 안내`}),W(`ul`,{class:`mt-2 list-disc space-y-1 pl-5 text-xs leading-relaxed text-slate-600`,children:[W(`li`,{children:`개인정보 보호를 위해 통신 내용은 읽지 않고 연결 요약만 임시 메모리에 저장합니다.`}),W(`li`,{children:`IPv6, 짧게 종료된 연결 및 일부 하드웨어 가속 연결은 관찰에서 제외될 수 있습니다.`}),W(`li`,{children:`주의 이벤트는 최근 활동에 기록되며, Cloud 활동 전송을 직접 켠 경우 내부 IP·연결 수 등의 요약 정보가 전송될 수 있습니다.`})]})]})]})}function Pb({onAdministratorPasswordChanged:e}){let t=zi(),n=Ui(),r=xi(),i=di(t===`activity`),a=li({active:t===`apps`,fallbackError:`검증된 앱 목록을 불러오지 못했습니다.`,loader:or,pollInterval:e=>e?.refreshing?2e3:e?.ready?6e4:e?.refreshFailed?15e3:2e3,staleTimeMs:2e3}),o=li({active:t===`apps`,fallbackError:`앱 설치 상태를 불러오지 못했습니다.`,loader:cr,pollInterval:e=>!e?.ready||e?.refreshing?2e3:e?.job.phase===`running`||e?.job.phase===`starting`||e?.restore?.phase===`running`||e?.restore?.phase===`starting`?3e3:12e3,staleTimeMs:3e3}),s=li({active:t===`apps`,fallbackError:`저장 공간 정보를 확인하지 못했습니다.`,loader:ur,pollInterval:e=>e?.refreshing||!e?.ready?3e3:6e4,staleTimeMs:5e3}),c=ro(t===`home`||t===`settings`),l=to(!0),u=Ii(!0),d=ea(t===`network`),f=Qi(t===`home`||t===`network`),p=Xi(t===`iptv`),m=Co(t===`wifi`),h=li({active:t===`netwatch`,fallbackError:`Network Watch 상태를 읽을 수 없습니다.`,loader:Eb,pollInterval:6e4,staleTimeMs:5e3}),g=Ci(t===`home`,!1),_=Ci(t===`devices`||t===`netwatch`,t===`devices`),v=li({active:t===`netwatch`,fallbackError:`Network Watch 최근 이벤트를 확인할 수 없습니다.`,loader:Jn,pollInterval:6e4,staleTimeMs:5e3}),y=Xa(t===`home`||t===`safeshield`),b=Va(t===`home`||t===`safeshield`,t===`safeshield`),x=y,S=b,C=za(t===`rules`),w=vo(t===`account`),T=bo(t===`account`,w.data?.accountRegistered??null),E=po(c.data),D=Yi(t===`home`||t===`settings`),O=io(t===`settings`),k=mo(t===`settings`);R(()=>{let e=Cy();e&&Er(e).then(e=>{e.applied&&t===`settings`&&k.refresh()}).catch(()=>{})},[]);let A=o.data?.job.operationId,j=o.data?.job.phase,M=o.data?.restore?.phase,N=z(``);R(()=>{if(t!==`apps`||j!==`success`&&M!==`success`)return;let e=`${A??``}:${j}:${M??``}`;e!==N.current&&(N.current=e,Promise.allSettled([lr(),dr()]).then(()=>{o.refresh(),s.refresh()}))},[t,A,j,M]);let P=Aa(y.refresh,b.refresh),F=t===`activity`?i:t===`netwatch`?h:t===`network`?d:t===`wifi`?m:t===`iptv`?p:t===`devices`?_:t===`safeshield`?y:t===`rules`?C:t===`account`?w:t===`apps`?a:t===`system`?l:c,I;switch(t){case`netwatch`:I=W(Nb,{data:h.data,devices:_.data,activity:v.data,error:h.error,loading:h.loading,onRetry:()=>void h.refresh(),onSetEnabled:async e=>{await Db(e),await h.refresh()}});break;case`apps`:I=W(_s,{data:a.data?.ready===!1?null:a.data,error:a.error??(a.data?.refreshFailed&&!a.data?.ready?`앱 목록 갱신에 실패했습니다. 서버 연결을 확인해 주세요.`:null),loading:a.loading||!!a.data&&!a.data.ready&&!a.data.refreshFailed,refreshing:a.refreshing||!!a.data?.refreshing,onRetry:()=>void sr().then(()=>a.refresh()).then(()=>Promise.all([o.refresh(),s.refresh()])).catch(()=>a.refresh()),installStatus:o.data?.ready===!1?null:o.data,metrics:s.data,statusError:o.error??(o.data?.ready===!1?`설치 상태를 확인하고 있습니다.`:null),onRefreshStatus:()=>void o.refresh(),onInstall:async e=>{await fr(e),await o.refresh()},onUpdate:async e=>{await pr(e),await o.refresh()},onRemove:async e=>{await mr(e),await o.refresh()}});break;case`network`:I=W(`div`,{class:`min-w-0`,children:[W(ci,{active:n.network===`internet`?`internet`:`lan`,label:`네트워크 설정 메뉴`,tabs:[{id:`internet`,label:`인터넷 연결`,href:`#network`},{id:`lan`,label:`LAN 및 DHCP`,href:`#network/lan`}]}),W(`div`,{"aria-label":`인터넷 연결 설정`,"aria-labelledby":`internet-tab`,class:`min-w-0`,hidden:n.network!==`internet`,id:`internet-panel`,role:`tabpanel`,children:W(wv,{action:d.action,data:d.data,error:d.error,feedback:d.feedback,loading:d.loading,onDismissFeedback:d.dismissFeedback,onReconnect:()=>void d.reconnect(),onRetry:()=>void d.refresh(),onSave:d.save})}),W(`div`,{"aria-label":`내부 네트워크 설정`,"aria-labelledby":`lan-tab`,class:`min-w-0`,hidden:n.network!==`lan`,id:`lan-panel`,role:`tabpanel`,children:W(uv,{action:f.action,data:f.data,error:f.error,feedback:f.feedback,loading:f.loading,onApplyRecommendation:f.applyRecommendation,onDismissFeedback:f.dismissFeedback,onRetry:()=>void f.refresh(),onSave:f.save})})]});break;case`activity`:I=W(ts,{actionError:i.actionError,actionMessage:i.actionMessage,data:i.data,error:i.error,loading:i.loading,onDismissActionFeedback:i.dismissActionFeedback,onRetry:()=>void i.refresh(),onSetCloudSync:e=>void i.setCloudSyncEnabled(e),savingCloudSync:i.savingCloudSync});break;case`wifi`:I=W(Tb,{data:m.data,error:m.error,feedback:m.feedback,loading:m.loading,onDismissFeedback:m.dismissFeedback,onRetry:()=>void m.refresh(),onUpdate:m.update,onUpdateGuest:m.updateGuest,onLoadQr:Ur,onLoadGuestQr:Wr,updatingSection:m.updatingSection});break;case`iptv`:I=W(J_,{data:p.data,error:p.error,feedback:p.feedback,loading:p.loading,onDismissFeedback:p.dismissFeedback,onRetry:()=>void p.refresh(),onSave:p.save,saving:p.saving});break;case`devices`:I=W(js,{data:_.data,error:_.error,loading:_.loading,onRetry:()=>void _.refresh()});break;case`account`:I=W(Gy,{data:w.data,error:w.error,loading:w.loading,pairingBusy:w.pairingBusy,refreshing:w.refreshing,syncError:w.syncError,onRequestPairing:w.requestPairing,onRetry:()=>void w.refresh()});break;case`system`:I=W(ub,{action:l.action,actionError:l.actionError,data:l.data,error:l.error,firmware:u,loading:l.loading,message:l.message,onCheck:()=>void l.check(),onDismissFeedback:l.dismissFeedback,onInstall:()=>void l.install(),onRetry:()=>void l.refresh(),onSaveSettings:l.saveSettings});break;case`settings`:I=W(Ry,{activeTab:n.settings,action:E.action,backupAction:r.action,backupError:r.error,backupMessage:r.message,backupRestoreAccepted:r.restoreAccepted,backupUploadProgress:r.uploadProgress,backupValidated:r.validated,data:c.data,error:c.error,feedbackError:E.error,feedbackMessage:E.message,health:D.data,healthActionError:D.actionError,healthActionMessage:D.actionMessage,healthError:D.error,healthLoading:D.loading,healthRunning:D.running,healthSavingReporter:D.savingReporter,loading:c.loading,onBackupDiscard:r.discard,onBackupDownload:()=>void r.download(),onBackupRestore:r.restore,onBackupUpload:r.upload,onDismissBackupFeedback:r.dismissFeedback,onDismissFeedback:E.dismissFeedback,onDismissHealthFeedback:D.dismissActionFeedback,onDismissTimeFeedback:k.dismissSaveFeedback,onDownloadDiagnostics:()=>void E.downloadDiagnostics(),onRunHealth:()=>void D.runDiagnostic(),onSetHealthReporter:e=>void D.setReporterEnabled(e),onReboot:()=>void E.reboot(),onRetry:()=>void Promise.all([c.refresh(),u.refresh(),k.refresh(),O.refresh(),D.refresh()]),onDismissScheduledRebootFeedback:O.dismissSaveFeedback,onSaveScheduledReboot:O.saveSettings,onSaveTimezone:k.saveTimezone,onSyncTime:k.syncTime,onAdministratorPasswordChanged:e,rebootAccepted:E.rebootAccepted,scheduledRebootData:O.data,scheduledRebootError:O.error,scheduledRebootLoading:O.loading,scheduledRebootSaveError:O.saveError,scheduledRebootSaveMessage:O.saveMessage,scheduledRebootSaving:O.saving,timeData:k.data,timeError:k.error,timeLoading:k.loading,timeSaveError:k.saveError,timeSaveMessage:k.saveMessage,timeSaving:k.saving,timeSyncing:k.syncing});break;case`safeshield`:I=W(oy,{action:P.action,actionError:P.error,actionMessage:P.message,data:y.data,error:y.error,loading:y.loading,statistics:b.data,statisticsError:b.error,statisticsLoading:b.loading,statisticsRefreshing:b.refreshing,onDismissFeedback:P.dismissFeedback,onRefreshBlocklist:()=>void P.refreshBlocklist(),onRetry:()=>void y.refresh(),onRetryStatistics:()=>void b.refresh(),onSetEnabled:e=>void P.setEnabled(e),onSetStatisticsEnabled:e=>void P.setStatisticsEnabled(e)});break;case`rules`:I=W(_y,{action:C.action,data:C.data,error:C.error,feedback:C.feedback,loading:C.loading,onAddRule:C.addRule,onDeleteRule:C.deleteRule,onLoadRecommendedRulesStatus:Zn,onSyncRecommendedRules:Qn,onRevokeRecommendedRule:$n,onDismissFeedback:C.dismissFeedback,onRetry:()=>void C.refresh()});break;default:I=W(C_,{accountRegistered:T,activity:c.data?.activityHistory??null,activityError:c.error,activityLoading:c.loading,data:c.data,devices:g.data,devicesError:g.error,devicesLoading:g.loading,error:c.error,firmware:u.data,firmwareError:u.error,firmwareLoading:u.loading,health:D.data,healthError:D.error,healthLoading:D.loading,lan:f.data,lanError:f.error,lanLoading:f.loading,loading:c.loading,onRetry:()=>void Promise.all([c.refresh(),g.refresh(),y.refresh(),b.refresh(),l.refresh(),u.refresh(),D.refresh(),f.refresh()]),safeshield:x.data,safeshieldError:x.error,safeshieldLoading:x.loading,statistics:S.data,statisticsError:S.error,statisticsLoading:S.loading,statisticsRefreshing:S.refreshing,updates:l.data,updatesError:l.error,updatesLoading:l.loading})}let ee=()=>{t===`home`?Promise.all([c.refresh(),g.refresh(),x.refresh(),S.refresh(),l.refresh(),u.refresh(),D.refresh(),f.refresh()]):t===`network`?Promise.all([d.refresh(),f.refresh()]):t===`system`?Promise.all([l.refresh(),u.refresh()]):t===`settings`?Promise.all([c.refresh(),u.refresh(),k.refresh(),O.refresh(),D.refresh()]):t===`safeshield`?Promise.all([y.refresh(),b.refresh()]):F.refresh()},te=+((l.data?.updateCount??0)>0)+ +!!u.data?.updateAvailable;return W(si,{accountRegistered:T,loading:t===`network`?d.loading||f.loading:F.loading,onRefresh:ee,refreshing:F.refreshing||t===`network`&&f.refreshing||t===`home`&&(i.refreshing||g.refreshing||x.refreshing||S.refreshing||l.refreshing||u.refreshing||D.refreshing||f.refreshing)||t===`system`&&u.refreshing||t===`settings`&&(u.refreshing||k.refreshing||O.refreshing||D.refreshing)||t===`safeshield`&&b.refreshing,route:t,updateCount:te,children:I})}function Fb({onAdministratorPasswordChanged:e,onPasswordConfigured:t}){let[n,r]=L(`checking`),[i,a]=L(!1),o=B(async()=>{r(`checking`);try{let e=await Ut();a(e.recovery),r(e.configured?`ready`:`required`)}catch{r(`error`)}},[]);return R(()=>{o()},[o]),n===`ready`?W(Pb,{onAdministratorPasswordChanged:e}):n===`required`?W(Wn,{onCompleted:()=>t(i),recovery:i}):W(`main`,{class:`ssh-initial-setup-probe`,children:W(`div`,{class:`ssh-initial-setup-probe-card`,role:n===`error`?`alert`:`status`,children:[n===`checking`?W(pn,{class:`ssh-login-probe-spinner`,"aria-hidden":`true`}):W(qt,{"aria-hidden":`true`}),W(`strong`,{children:n===`checking`?`관리자 보안 상태 확인 중`:`관리자 보안 상태를 확인할 수 없습니다`}),W(`span`,{children:n===`checking`?`root 비밀번호 설정 여부를 확인하고 있습니다.`:`공유기 연결을 확인한 뒤 다시 시도해 주세요.`}),n===`error`?W(`button`,{onClick:()=>void o(),type:`button`,children:`다시 시도`}):null]})})}function Ib({icon:e,title:t,description:n}){return W(`div`,{class:`ssh-login-feature`,children:[W(`span`,{class:`ssh-login-feature-icon`,"aria-hidden":`true`,children:e}),W(`span`,{class:`ssh-login-feature-copy`,children:[W(`strong`,{children:t}),W(`span`,{children:n})]})]})}function Lb({theme:e}){return W(e===`dark`?Fn:Pn,{})}function Rb({notice:e,onAuthenticated:t,probing:n=!1}){let r=Xe(xe,[]),i=z(null),a=z(null),[o,s]=L(`root`),[c,l]=L(``),[u,d]=L(`ready`),[f,p]=L(null),[m,h]=L(e??null),[g,_]=L(!1),[v,y]=L(!1),[b,x]=L(!1),[S,C]=L(Ln),w=u===`submitting`,T=S===`dark`?`라이트 모드로 전환`:`다크 모드로 전환`;R(()=>{Rn(S),zn(S)},[S]),R(()=>{if(n)return;let e=window.requestAnimationFrame(()=>a.current?.focus());return()=>window.cancelAnimationFrame(e)},[n]),R(()=>{if(h(e??null),!e)return;let t=window.setTimeout(()=>h(null),7e3);return()=>window.clearTimeout(t)},[e]);let E=e=>{x(e.getModifierState?.(`CapsLock`)??!1)};return W(`main`,{class:`ssh-login-page`,"data-theme":S,children:[m?W(`div`,{"aria-atomic":`true`,"aria-live":`assertive`,class:`ssh-login-toast`,role:`alert`,children:[W(qt,{"aria-hidden":`true`}),W(`span`,{children:m})]}):null,W(`section`,{class:`ssh-login-brand`,"aria-labelledby":`ssh-login-brand-title`,children:[W(`div`,{class:`ssh-login-brand-orb ssh-login-brand-orb-one`,"aria-hidden":`true`}),W(`div`,{class:`ssh-login-brand-orb ssh-login-brand-orb-two`,"aria-hidden":`true`}),W(`div`,{class:`ssh-login-brand-content`,children:[W(`span`,{class:`ssh-login-brand-mark`,"aria-hidden":`true`,children:W(qt,{})}),W(`p`,{class:`ssh-login-eyebrow`,children:`SMART NETWORK PROTECTION`}),W(`h1`,{id:`ssh-login-brand-title`,children:`SmartSafeHub`}),W(`p`,{class:`ssh-login-brand-description`,children:`네트워크와 보안을 하나의 허브에서 편리하게 관리하세요.`}),W(`div`,{class:`ssh-login-features`,"aria-label":`SmartSafeHub 주요 기능`,children:[W(Ib,{description:`DNS 기반 광고·피싱·추적 차단과 사용자 보호 정책을 관리합니다.`,icon:W(qt,{}),title:`SafeShield 보호`}),W(Ib,{description:`Wi-Fi 설정과 현재 연결된 기기의 상태를 한눈에 확인합니다.`,icon:W(on,{}),title:`네트워크 관리`}),W(Ib,{description:`관리 기능과 인증은 현재 SmartSafeHub 장치에서 직접 처리됩니다.`,icon:W(_n,{}),title:`로컬 우선 관리`})]})]})]}),W(`section`,{class:`ssh-login-panel`,"aria-labelledby":`ssh-login-title`,children:[W(`button`,{"aria-label":T,class:`ssh-login-theme-toggle`,onClick:()=>C(e=>e===`dark`?`light`:`dark`),title:T,type:`button`,children:W(Lb,{theme:S})}),W(`div`,{class:`ssh-login-panel-inner`,children:[n?W(`div`,{class:`ssh-login-probe`,role:`status`,"aria-live":`polite`,children:[W(pn,{class:`ssh-login-probe-spinner`,"aria-hidden":`true`}),W(`strong`,{children:`SmartSafeHub 연결 확인 중`}),W(`span`,{children:`로그인 세션을 확인하고 있습니다.`})]}):W(`div`,{class:`ssh-login-card`,children:[W(`div`,{class:`ssh-login-card-heading`,children:[W(`span`,{class:`ssh-login-mobile-mark`,"aria-hidden":`true`,children:W(qt,{})}),W(`div`,{children:[W(`p`,{class:`ssh-login-kicker`,children:`SMARTSAFEHUB ACCESS`}),W(`h2`,{id:`ssh-login-title`,children:`로그인`}),W(`p`,{class:`ssh-login-subtitle`,children:`공유기 관리자 계정의 사용자 이름과 비밀번호를 입력해 주세요.`})]})]}),f?W(`div`,{class:`ssh-login-alert`,id:`ssh-login-error`,role:`alert`,"aria-live":`polite`,children:f}):null,W(`form`,{class:`ssh-login-form`,onSubmit:async e=>{e.preventDefault();let n=o.trim(),r=c;if(p(null),_(!1),!n||!r&&n!==`root`)p(n?`비밀번호를 입력해 주세요.`:`사용자 이름을 입력해 주세요.`),window.requestAnimationFrame(()=>{n?a.current?.focus():i.current?.focus()});else{d(`submitting`);try{let e=await De(n,r);if(e){l(``),t(e);return}l(``),_(!0),p(`로그인하지 못했습니다. 계정 정보를 확인해 주세요. 추가 인증이 설정된 장치는 기본 LuCI 로그인에서 계속할 수 있습니다.`),window.requestAnimationFrame(()=>a.current?.focus())}catch{p(`공유기와 통신할 수 없습니다. 네트워크 연결을 확인한 뒤 다시 시도해 주세요.`)}finally{d(`ready`)}}},noValidate:!0,children:[W(`label`,{class:`ssh-login-field`,for:`smartsafehub-username`,children:[W(`span`,{class:`ssh-login-label`,children:`사용자 이름`}),W(`span`,{class:`ssh-login-input-wrap`,children:[W(`span`,{class:`ssh-login-field-icon`,"aria-hidden":`true`,children:W(Zt,{})}),W(`input`,{autoCapitalize:`none`,autoComplete:`username`,disabled:w,id:`smartsafehub-username`,name:`username`,onInput:e=>s(e.currentTarget.value),placeholder:`관리자 계정`,ref:i,required:!0,spellcheck:!1,type:`text`,value:o})]})]}),W(`label`,{class:`ssh-login-field`,for:`smartsafehub-password`,children:[W(`span`,{class:`ssh-login-label`,children:`비밀번호`}),W(`span`,{class:`ssh-login-input-wrap`,children:[W(`span`,{class:`ssh-login-field-icon`,"aria-hidden":`true`,children:W(_n,{})}),W(`input`,{autoComplete:`current-password`,disabled:w,id:`smartsafehub-password`,name:`password`,onBlur:()=>x(!1),onInput:e=>l(e.currentTarget.value),onKeyDown:E,onKeyUp:E,placeholder:`관리자 비밀번호`,ref:a,...v?{type:`text`}:{type:`password`},value:c}),W(`button`,{"aria-label":v?`비밀번호 숨기기`:`비밀번호 표시`,"aria-pressed":v,class:`ssh-login-password-toggle`,disabled:w,onClick:()=>{y(e=>!e),window.requestAnimationFrame(()=>a.current?.focus())},title:v?`비밀번호 숨기기`:`비밀번호 표시`,type:`button`,children:W(v?bn:yn,{})})]})]}),o.trim()===`root`&&c.length===0?W(`p`,{class:`ssh-login-password-hint`,children:`초기 설정 전 root 계정은 비밀번호가 비어 있을 수 있습니다.`}):null,b?W(`p`,{class:`ssh-login-caps`,role:`status`,children:`Caps Lock이 켜져 있습니다.`}):null,W(`button`,{class:`ssh-login-submit`,disabled:w,type:`submit`,children:[w?W(pn,{class:`ssh-login-submit-spinner`,"aria-hidden":`true`}):null,W(`span`,{children:w?`로그인 중…`:`로그인`})]})]}),W(`div`,{class:`ssh-login-security-note`,children:[W(_n,{"aria-hidden":`true`}),W(`span`,{children:`관리자 비밀번호를 잊었다면 전원이 켜진 상태에서 Reset 버튼을 5~9초 누른 뒤 놓아 비밀번호만 복구할 수 있습니다. 10초 이상 누르면 모든 사용자 설정을 초기화하는 기기 초기화가 실행됩니다.`})]}),g?W(`a`,{class:`ssh-login-fallback`,href:r,children:`기본 LuCI 로그인으로 계속`}):null]}),W(`footer`,{class:`ssh-login-footer`,children:[W(`span`,{children:`SmartSafeHub`}),W(`span`,{"aria-hidden":`true`,children:`·`}),W(`span`,{children:window.location.host})]})]})]})]})}var zb=`smartsafehub-entry-root`,Bb=`data-smartsafehub-mount`,Vb=`data-smartsafehub-shadow-style`;function Hb(){let e=_e(`/`);new Set([e,e.replace(/\/$/,``),_e(`/smartsafehub`),_e(`/admin/smartsafehub`)]).has(window.location.pathname)&&window.history.replaceState(null,``,`${ye()}${window.location.search}${window.location.hash}`)}function Ub(e,t){je(),window.__SMARTHUB_BOOTSTRAP__=Object.freeze({sessionId:e,rpcUrl:_e(`/admin/ubus`),assetBase:t.dataset.assetBase??`/luci-static/smartsafehub/`,assetVersion:t.dataset.assetVersion??`0.2.33-r1`,locale:document.documentElement.lang||`ko`})}function Wb(e,t){let n=window.__SMARTHUB_BOOTSTRAP__,r=n?.assetBase??t.dataset.assetBase??`/luci-static/smartsafehub/`,i=n?.assetVersion??t.dataset.assetVersion,a=`${r}${r.endsWith(`/`)?``:`/`}${e}`;return i?`${a}?v=${encodeURIComponent(i)}`:a}function Gb(e,t){let n=e.shadowRoot??e.attachShadow({mode:`open`}),r=n.querySelector(`link[${Vb}]`),i=Wb(`app.css`,e);r?r.getAttribute(`href`)!==i&&(r.href=i):(r=document.createElement(`link`),r.rel=`stylesheet`,r.href=i,r.setAttribute(Vb,``),n.appendChild(r));let a=n.querySelector(`[${Bb}]`);return a?a.className!==t&&(a.className=t):(a=document.createElement(`div`),a.className=t,a.setAttribute(Bb,``),n.appendChild(a)),a}function Kb(e,t,n){Ub(n,e),t.className=`smartsafehub-shadow-root`,ae(W(Fb,{onAdministratorPasswordChanged:()=>{qb(e,t,`관리자 비밀번호가 변경되었습니다. 새 비밀번호로 다시 로그인해 주세요.`)},onPasswordConfigured:n=>{qb(e,t,n?`관리자 비밀번호 복구가 완료되었습니다. 새 비밀번호로 다시 로그인해 주세요.`:`관리자 비밀번호가 설정되었습니다. 새 비밀번호로 다시 로그인해 주세요.`)}}),t)}function qb(e,t,n){delete window.__SMARTHUB_BOOTSTRAP__,t.className=`smartsafehub-login-shadow-root`,ae(W(Rb,{notice:n,onAuthenticated:n=>{Kb(e,t,n)}}),t)}async function Jb(){zn(Ln());let e=document.getElementById(zb);if(!e)return;Hb(),e.style.display=`block`,e.style.width=`100%`,e.style.minWidth=`0`,e.style.minHeight=`100dvh`,e.style.maxWidth=`100%`,e.style.isolation=`isolate`;let t=Gb(e,`smartsafehub-login-shadow-root`);window.addEventListener(Oe,()=>{qb(e,t,ke)}),ae(W(Rb,{onAuthenticated:n=>{Kb(e,t,n)},probing:!0}),t);try{let n=await de();if(n){Kb(e,t,n);return}}catch{}try{let n=await Te();if(n){Kb(e,t,n);return}}catch{}qb(e,t)}Jb(); \ No newline at end of file diff --git a/luci-app-smartsafehub/tests/test-apps-manager.sh b/luci-app-smartsafehub/tests/test-apps-manager.sh index 7f2397953..259b8a74e 100755 --- a/luci-app-smartsafehub/tests/test-apps-manager.sh +++ b/luci-app-smartsafehub/tests/test-apps-manager.sh @@ -556,6 +556,93 @@ grep -Fq "$(printf 'retro-arcade\t1\t1\t0\t0\t')" "$TMP/runtime/apps-status.tsv" grep -Fqx "$(printf 'code\tE29')" "$TMP/runtime/apps-operation.state" || fail 'second app did not reach manual ownership check' printf 'PASS: multi-app lookup, E22 regression and manual package protection\n' +# Firmware sysupgrade drops optional APKs but keeps Apps ownership. A newer +# firmware may approve a different APK version/hash for the same owned name. +# Only the privileged restore path may reconcile missing owned packages. +: > "$APPS_TEST_INSTALLED" +: > "$APPS_TEST_VERSION_DB" +rm -f "$TMP/owned/retro-arcade" "$TMP/owned/network-usage-monitor" +printf 'new signed Retro payload\n' > "$TMP/repo/smartsafehub-app-retro-arcade-1.1.0-r1.apk" +RESTORE_NEW_SHA="$(sha256sum "$TMP/repo/smartsafehub-app-retro-arcade-1.1.0-r1.apk" | cut -d ' ' -f 1)" +cat > "$TMP/catalog.json" < "$TMP/owned/retro-arcade" +RESTORE_OLD_MANIFEST="$(cat "$TMP/owned/retro-arcade")" +RESTORE_RECEIPTS="$(wc -l < "$APPS_TEST_METRICS_LOG" | tr -d ' ')" +"$MANAGER" start install retro-arcade || fail 'manual install with old ownership did not queue' +grep -Fqx "$(printf 'code\tE28')" "$TMP/runtime/apps-operation.state" || fail 'ordinary install bypassed version ownership check' +[ "$(cat "$TMP/owned/retro-arcade")" = "$RESTORE_OLD_MANIFEST" ] || fail 'failed manual install rewrote ownership' +# A different release that is still installed is not safe to replace. +printf 'smartsafehub-app-retro-arcade\n' > "$APPS_TEST_INSTALLED" +"$MANAGER" start install retro-arcade restore || fail 'installed old release restore did not queue' +grep -Fqx "$(printf 'code\tE28')" "$TMP/runtime/apps-operation.state" || fail 'restore silently replaced a present release' +[ "$(cat "$TMP/owned/retro-arcade")" = "$RESTORE_OLD_MANIFEST" ] || fail 'present package changed ownership' +: > "$APPS_TEST_INSTALLED" +# Missing dependencies and a tampered APK must fail before re-pinning records. +export APPS_TEST_MISSING_DEP=1 +"$MANAGER" start install retro-arcade restore || fail 'dependency simulation failure did not queue' +grep -Fqx "$(printf 'code\tE34')" "$TMP/runtime/apps-operation.state" || fail 'restore bypassed solver guard' +[ "$(cat "$TMP/owned/retro-arcade")" = "$RESTORE_OLD_MANIFEST" ] || fail 'failed solver changed ownership' +unset APPS_TEST_MISSING_DEP +printf 'tampered new Retro payload\n' > "$TMP/repo/smartsafehub-app-retro-arcade-1.1.0-r1.apk" +"$MANAGER" start install retro-arcade restore || fail 'tampered restore did not queue' +grep -Fqx "$(printf 'code\tE33')" "$TMP/runtime/apps-operation.state" || fail 'tampered new APK passed restore' +[ "$(cat "$TMP/owned/retro-arcade")" = "$RESTORE_OLD_MANIFEST" ] || fail 'tampered APK re-pinned ownership' +printf 'new signed Retro payload\n' > "$TMP/repo/smartsafehub-app-retro-arcade-1.1.0-r1.apk" +# A package omitted from a new approval cannot be rescued by stale ownership. +printf 'unapproved-old-package\t1.0.0-r1\t%s\n' "$RETROSHA" >> "$TMP/owned/retro-arcade" +"$MANAGER" start install retro-arcade restore || fail 'unapproved old owner check did not queue' +grep -Fqx "$(printf 'code\tE28')" "$TMP/runtime/apps-operation.state" || fail 'unapproved old package re-pinned' +printf '%s\n' "$RESTORE_OLD_MANIFEST" > "$TMP/owned/retro-arcade" +printf '%s\n' "$RESTORE_OLD_MANIFEST" >> "$TMP/owned/retro-arcade" +"$MANAGER" start install retro-arcade restore || fail 'duplicate ownership check did not queue' +grep -Fqx "$(printf 'code\tE28')" "$TMP/runtime/apps-operation.state" || fail 'duplicate ownership authorized restore' +printf '%s\n' "$RESTORE_OLD_MANIFEST" > "$TMP/owned/retro-arcade" +# An unterminated or empty ownership record cannot authorize restoration. +printf '%s' "$RESTORE_OLD_MANIFEST" > "$TMP/owned/retro-arcade" +"$MANAGER" start install retro-arcade restore || fail 'truncated ownership check did not queue' +grep -Fqx "$(printf 'code\tE28')" "$TMP/runtime/apps-operation.state" || fail 'truncated ownership authorized restore' +printf '%s\n' "$RESTORE_OLD_MANIFEST" > "$TMP/owned/retro-arcade" +# A preinstalled system conntrack package must remain independent of Apps. +printf 'conntrack\n' > "$APPS_TEST_INSTALLED" +"$MANAGER" start install retro-arcade restore || fail 'approved new firmware restore not queued' +grep -Fqx "$(printf 'phase\tsuccess')" "$TMP/runtime/apps-operation.state" || fail 'new approved release was not restored' +grep -Fqx "$(printf 'smartsafehub-app-retro-arcade\t1.1.0-r1\t%s' "$RESTORE_NEW_SHA")" "$TMP/owned/retro-arcade" || fail 'ownership was not updated to newly approved version' +grep -Fqx conntrack "$APPS_TEST_INSTALLED" || fail 'restore removed preinstalled conntrack' +[ "$(wc -l < "$APPS_TEST_INSTALLED" | tr -d ' ')" -eq 2 ] || fail 'restore installed unrelated packages' +[ "$(wc -l < "$APPS_TEST_METRICS_LOG" | tr -d ' ')" -eq "$RESTORE_RECEIPTS" ] || fail 'automatic restore counted as user install' +"$MANAGER" start install retro-arcade restore || fail 'second restore not queued' +grep -Fqx "$(printf 'code\tE28')" "$TMP/runtime/apps-operation.state" || fail 'already restored app was installed twice' +# Ownership files cannot turn conntrack into a removable Apps package. +printf 'conntrack\t1.0-r1\t%s\n' "$RETROSHA" > "$TMP/owned/retro-arcade" +"$MANAGER" start remove retro-arcade || fail 'protected package remove did not queue' +grep -Fqx "$(printf 'code\tE42')" "$TMP/runtime/apps-operation.state" || fail 'conntrack was not protected during removal' +grep -Fqx conntrack "$APPS_TEST_INSTALLED" || fail 'protected conntrack was removed' +rm -f "$TMP/owned/retro-arcade" +# Multi-package restore must preserve existing ownership even when package +# records appear in a different order. It may re-pin the one missing APK but +# must never reinstall the other two or claim the system conntrack package. +cat > "$TMP/catalog.json" <> "$APPS_TEST_INSTALLED" +printf 'nlbwmon\n' >> "$APPS_TEST_INSTALLED" +cat > "$TMP/owned/network-usage-monitor" < "$APPS_TEST_INSTALLED" diff --git a/luci-app-smartsafehub/tools/check_test_coverage.py b/luci-app-smartsafehub/tools/check_test_coverage.py index 62e62c01f..96801b64c 100644 --- a/luci-app-smartsafehub/tools/check_test_coverage.py +++ b/luci-app-smartsafehub/tools/check_test_coverage.py @@ -153,7 +153,7 @@ def self_test() -> None: workflow_path = root / '.github/workflows/ci.yml' workflow_base = '''jobs: backend: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Guard run: sh tests/test-suite-integrity.sh @@ -162,7 +162,7 @@ def self_test() -> None: - name: OpenWrt BusyBox run: sh tests/test-openwrt-commands.sh --self-test frontend: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Test run: node --experimental-strip-types --test ../tests/runtime/*.test.mjs diff --git a/luci-app-ssr-plus/Makefile b/luci-app-ssr-plus/Makefile index 421fe9218..8080debfa 100644 --- a/luci-app-ssr-plus/Makefile +++ b/luci-app-ssr-plus/Makefile @@ -4,7 +4,7 @@ LUCI_TITLE:=luci-app-ssr-plus LUCI_PKGARCH:=all PKG_NAME:=luci-app-ssr-plus PKG_VERSION:=198 -PKG_RELEASE:=99 +PKG_RELEASE:=100 PKG_CONFIG_DEPENDS:= \ CONFIG_PACKAGE_$(PKG_NAME)_Iptables_Transparent_Proxy \ diff --git a/luci-app-ssr-plus/root/usr/share/shadowsocksr/clash_yaml.lua b/luci-app-ssr-plus/root/usr/share/shadowsocksr/clash_yaml.lua index 2099ede42..df619eb86 100644 --- a/luci-app-ssr-plus/root/usr/share/shadowsocksr/clash_yaml.lua +++ b/luci-app-ssr-plus/root/usr/share/shadowsocksr/clash_yaml.lua @@ -218,6 +218,7 @@ local function strip_runtime_conflicts(doc) doc.port = nil doc["external-controller"] = nil doc.secret = nil + doc.authentication = nil doc["allow-lan"] = nil if type(doc.dns) == "table" then doc.dns["fake-ip-range"] = nil @@ -323,6 +324,7 @@ local function merge(raw_path, overlay_path, output_path) local filled_groups = fill_empty_proxy_groups(raw_doc) local stripped_rules = strip_incompatible_script_rules(raw_doc) local merged = deep_merge(raw_doc, overlay_doc) + strip_runtime_conflicts(merged) local ok, rendered = pcall(lyaml.dump, { merged }) if not ok or not rendered then io.stderr:write("dump_failed\n") @@ -740,6 +742,115 @@ local function apply_trojan_tls_options(proxy, sid) end end +-- Xray camel → mihomo kebab(反向字段表) +local XHTTP_FIELD_MAP = { + headers = "headers", + xPaddingBytes = "x-padding-bytes", + noGRPCHeader = "no-grpc-header", + xPaddingObfsMode = "x-padding-obfs-mode", + xPaddingKey = "x-padding-key", + xPaddingHeader = "x-padding-header", + xPaddingPlacement = "x-padding-placement", + xPaddingMethod = "x-padding-method", + uplinkHTTPMethod = "uplink-http-method", + sessionIDPlacement = "session-placement", + sessionIDKey = "session-key", + sessionIDTable = "session-table", + sessionIDLength = "session-length", + seqPlacement = "seq-placement", + seqKey = "seq-key", + uplinkDataPlacement = "uplink-data-placement", + uplinkDataKey = "uplink-data-key", + uplinkChunkSize = "uplink-chunk-size", + scMaxEachPostBytes = "sc-max-each-post-bytes", + scMinPostsIntervalMs = "sc-min-posts-interval-ms", +} + +-- xmux 内部 camel → kebab +local XMUX_FIELD_MAP = { + maxConcurrency = "max-concurrency", + maxConnections = "max-connections", + cMaxReuseTimes = "c-max-reuse-times", + hMaxRequestTimes = "h-max-request-times", + hMaxReusableSecs = "h-max-reusable-secs", + hKeepAlivePeriod = "h-keep-alive-period", +} + +local function convert_xhttp_pair(key, value) + -- xmux → reuse-settings + if key == "xmux" and type(value) == "table" then + local out = {} + for k, v in pairs(value) do + local mapped = XMUX_FIELD_MAP[k] + if mapped then + out[mapped] = v + end + end + if next(out) == nil then return nil, nil end + return "reuse-settings", out + end + + -- downloadSettings → download-settings(只保留 xhttp 部分) + if key == "downloadSettings" and type(value) == "table" then + local out = {} + for k, v in pairs(value) do + if k == "xmux" then + local nk, nv = convert_xhttp_pair(k, v) + if nk then out[nk] = nv end + elseif k == "path" or k == "host" then + out[k] = v + elseif XHTTP_FIELD_MAP[k] then + if not (k == "headers" and type(v) == "table" and next(v) == nil) then + out[XHTTP_FIELD_MAP[k]] = v + end + end + end + if next(out) == nil then return nil, nil end + return "download-settings", out + end + + if key == "headers" and type(value) == "table" then + if next(value) == nil then + return nil, nil + end + return "headers", value + end + + local mapped = XHTTP_FIELD_MAP[key] + if mapped then + return mapped, value + end + + return nil, nil +end + +local function apply_xhttp_extra(opts, sid) + if get_server_field(sid, "enable_xhttp_extra", "0") ~= "1" then + return + end + local raw = get_server_field(sid, "xhttp_extra", "") + if raw == "" or not ok_jsonc or not jsonc then + return + end + + local decoded = nixio.bin.b64decode(raw) + if not decoded then + return + end + + local extra = jsonc.parse(decoded) + if type(extra) ~= "table" then + return + end + + for k, v in pairs(extra) do + local new_key, new_val = convert_xhttp_pair(k, v) + if new_key then + opts[new_key] = new_val + end + end +end + local function apply_v2ray_transport_options(proxy, sid) local transport = get_server_field(sid, "transport", "raw") if transport == "raw" or transport == "tcp" or transport == "" then @@ -782,6 +893,7 @@ local function apply_v2ray_transport_options(proxy, sid) host = string_or_nil(get_server_field(sid, "xhttp_host", "")), mode = string_or_nil(get_server_field(sid, "xhttp_mode", "")) } + apply_xhttp_extra(proxy["xhttp-opts"], sid) end end diff --git a/luci-app-vantage/Makefile b/luci-app-vantage/Makefile index 4e3c3adb9..ebea8dbc5 100644 --- a/luci-app-vantage/Makefile +++ b/luci-app-vantage/Makefile @@ -10,8 +10,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-vantage -PKG_VERSION:=1.0.15 -PKG_RELEASE:=28 +PKG_VERSION:=1.0.16 +PKG_RELEASE:=29 PKG_LICENSE:=GPL-3.0-or-later LUCI_TITLE:=Vantage dashboard diff --git a/luci-app-vantage/htdocs/luci-static/resources/vantage/app.css b/luci-app-vantage/htdocs/luci-static/resources/vantage/app.css index 59a48dac3..16fc8e038 100644 --- a/luci-app-vantage/htdocs/luci-static/resources/vantage/app.css +++ b/luci-app-vantage/htdocs/luci-static/resources/vantage/app.css @@ -385,6 +385,8 @@ html[data-darkmode="true"] .vt-app { } .vt-app .vt-editbar[hidden] { display: none; } .vt-app .vt-editbar-text { display: flex; flex-direction: column; gap: 2px; flex: 1 1 260px; min-width: 0; } +.vt-app .vt-edit-note { font-size: var(--vt-fs-sm); color: var(--vt-text-2); } +.vt-app .vt-edit-note[hidden] { display: none; } .vt-app .vt-editbar-text strong { font-size: var(--vt-fs-base); font-weight: var(--vt-fw-semibold); } .vt-app .vt-edit-hidden { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; } .vt-app .vt-edit-hidden:empty { display: none; } @@ -801,10 +803,10 @@ h2, h3{ .vt-app .vt-link-label { position: relative; z-index: var(--vt-z-raised); - display: grid; grid-template-rows: minmax(0, 1fr) 14px minmax(0, 1fr); justify-items: center; + display: grid; grid-template-rows: minmax(0, 1fr) 14px minmax(0, 1fr); grid-template-columns: minmax(0, 1fr); justify-items: center; align-self: stretch; padding: 0 6px; text-align: center; } -.vt-app .vt-link-below { grid-row: 3; align-self: start; display: flex; flex-direction: column; align-items: center; } +.vt-app .vt-link-below { grid-row: 3; align-self: start; display: flex; flex-direction: column; align-items: center; max-width: 100%; min-width: 0; } .vt-app .vt-link-rate { display: inline-flex; align-items: baseline; gap: 3px; white-space: nowrap; padding: 0 6px; border-radius: 6px; background: var(--vt-surface); @@ -817,14 +819,14 @@ h2, h3{ .vt-app .vt-link-up { font-size: var(--vt-fs-sm); } .vt-app .vt-link-up b { font-size: var(--vt-fs-base); font-weight: var(--vt-fw-semibold); color: var(--vt-text-2); } .vt-app .vt-link-up .vt-ico { color: var(--vt-text-3); } -.vt-app .vt-link-meta { font-size: var(--vt-fs-micro); color: var(--vt-text-3); font-family: var(--vt-mono); white-space: nowrap; margin-top: 2px; +.vt-app .vt-link-meta { font-size: var(--vt-fs-micro); color: var(--vt-text-3); font-family: var(--vt-mono); white-space: nowrap; margin-top: 2px; max-width: 100%; overflow: hidden; text-overflow: ellipsis; padding: 0 6px; border-radius: 6px; background: var(--vt-surface); } /* a full-tunnel VPN over the main line: its wire drawn as a pipe in its own colour around the traffic, the tunnel named under the line's rates */ .vt-app .vt-wire-tunnel { stroke-width: 9; stroke-linecap: round; stroke: color-mix(in oklab, var(--vt-tunnel) 24%, transparent); } .vt-app .vt-wire-tunnel.vt-wire-on { stroke: color-mix(in oklab, var(--vt-tunnel) 36%, transparent); } .vt-app .vt-link-tunnel { - display: inline-flex; align-items: center; gap: 4px; max-width: 100%; margin-top: 4px; padding: 1px 6px; + display: inline-flex; align-items: center; gap: 4px; max-width: 100%; min-width: 0; margin-top: 4px; padding: 1px 6px; border: 1px solid color-mix(in oklab, var(--vt-tunnel) 60%, var(--vt-line)); border-radius: 6px; background: var(--vt-surface); color: var(--vt-text-2); font-size: var(--vt-fs-micro); white-space: nowrap; } @@ -853,8 +855,8 @@ h2, h3{ .vt-app .vt-node-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; } /* the main line among others: the default route the kernel takes */ .vt-app .vt-node-tag { - display: inline-flex; align-items: center; height: 16px; padding: 0 5px; border-radius: 4px; - text-transform: none; letter-spacing: 0; font-weight: var(--vt-fw-medium); + display: inline-block; height: 16px; line-height: 16px; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + padding: 0 5px; border-radius: 4px; text-transform: none; letter-spacing: 0; font-weight: var(--vt-fw-medium); background: var(--vt-accent-soft); color: var(--vt-accent-text); } @@ -865,6 +867,10 @@ h2, h3{ .vt-app .vt-branch-ssids { display: flex; flex-direction: column; gap: var(--vt-sp-2); min-width: 0; } .vt-app .vt-branch-ssids .vt-node { max-width: 260px; } +/* medium widths: the rates need their column, and the branches have room to give */ +@container vthero (max-width: 1020px) { + .vt-app .vt-path { grid-template-columns: minmax(150px, 1.05fr) minmax(136px, .9fr) minmax(170px, 1.1fr) minmax(204px, 2fr); } +} @container vthero (max-width: 700px) { .vt-app .vt-path { grid-template-columns: minmax(0, 1fr); gap: 12px; min-height: 0; } .vt-app .vt-node-gw, .vt-app .vt-node-ap { justify-self: start; min-width: 220px; } diff --git a/luci-app-vantage/htdocs/luci-static/resources/vantage/layout.js b/luci-app-vantage/htdocs/luci-static/resources/vantage/layout.js index da9337e61..8dfba5c18 100644 --- a/luci-app-vantage/htdocs/luci-static/resources/vantage/layout.js +++ b/luci-app-vantage/htdocs/luci-static/resources/vantage/layout.js @@ -111,7 +111,8 @@ return baseclass.extend({ /* a layout as that option's value: 'hero:0:4 chart:0:2:h' */ encode: function(list) { - return list.map(function(e) { return e.id + ':' + e.c + ':' + e.w + (e.hide ? ':h' : ''); }).join(' '); + return list.filter(function(e) { return e && typeof e === 'object'; }) + .map(function(e) { return e.id + ':' + e.c + ':' + e.w + (e.hide ? ':h' : ''); }).join(' '); }, /* the template's data-vt-look-layout, 'wide=hero:0:4 chart:0:2:h;mid=default', diff --git a/luci-app-vantage/htdocs/luci-static/resources/vantage/model.js b/luci-app-vantage/htdocs/luci-static/resources/vantage/model.js index 23e562f05..2f4c2fb85 100644 --- a/luci-app-vantage/htdocs/luci-static/resources/vantage/model.js +++ b/luci-app-vantage/htdocs/luci-static/resources/vantage/model.js @@ -34,6 +34,8 @@ var MAX_LINES = 4; are left out: some ISPs give their access through them. */ var TUNNEL = /^(wireguard|openvpn|openconnect|openfortivpn|vpnc|sstp|gre|gretap|grev6|grev6tap|vti|vtiv6|xfrm|ipip|6in4|6to4|6rd|vxlan|vxlan6)$/; var TUNNEL_DEV = /^(wireguard|tun|tap|ipip|ip6tnl|sit|gre|gretap|ip6gre|ip6gretap|vti|vti6|xfrm|vxlan)$/; +/* IPv6 carried over the IPv4 line: part of that line, not a way out or a VPN */ +var IPV6_OVER = /^(6in4|6rd|6to4)$/; /* netifd lists a route's metric only when the route sets one; the interface's metric applies otherwise. The kernel stores an IPv6 @@ -72,17 +74,22 @@ function isTunnel(i, devs) { /* the interfaces with a default route, the line first: real lines before tunnels (a full-tunnel VPN rides over a line, see ridingTunnel), IPv4 before IPv6 (a softwire's IPv4 route among the IPv4 ones), then - the lowest metric, wan/wan6/*wan* on a tie, then the order netifd lists + the lowest metric, on a tie the one the kernel says it takes (`kernel`, + as ridingTunnel's; netifd replaces routes of equal metric, so that is + the last one added), then wan/wan6/*wan*, then the order netifd lists them */ -function rankRouted(up, devs) { +function rankRouted(up, devs, kernel) { + var taken = {}; + arr(kernel).forEach(function(x) { if (x && typeof x.dev === 'string') taken[x.family === 6 ? 1 : 0] = x.dev; }); return up.map(function(i, n) { var r = defRoute(i); /* a softwire's device is a tunnel's (ip6tnl), but it carries the line's IPv4, not a VPN */ var sw = SOFTWIRE.test(i.proto || ''); - return r ? { i: i, tunnel: !sw && isTunnel(i, devs), softwire: sw, family: r.target === '::' ? 1 : 0, - metric: metric(i, r), wan: wanRank(i), n: n } : null; + var family = r && r.target === '::' ? 1 : 0; + return r ? { i: i, tunnel: !sw && isTunnel(i, devs), softwire: sw, family: family, + metric: metric(i, r), taken: taken[family] === (i.l3_device || i.device) ? 0 : 1, wan: wanRank(i), n: n } : null; }).filter(Boolean).sort(function(a, b) { - return a.tunnel - b.tunnel || a.family - b.family || a.metric - b.metric || a.wan - b.wan || a.n - b.n; + return a.tunnel - b.tunnel || a.family - b.family || a.metric - b.metric || a.taken - b.taken || a.wan - b.wan || a.n - b.n; }); } /* A full-tunnel VPN over the line: the tunnel holding the default route @@ -123,7 +130,7 @@ function ridingTunnel(ifaces, ranked, devs, kernel) { parent device (two PPPoE sessions on eth0) stay two. The main line carries the full-tunnel VPN over it, if any (tunnel; ridingTunnel). */ function groupLines(ifaces, devs, kernel) { - var ranked = rankRouted(upIfaces(ifaces), devs), main = ranked[0], groups = []; + var ranked = rankRouted(upIfaces(ifaces), devs, kernel), main = ranked[0], groups = []; if (!main) return groups; var at = main.metric, rider = main.tunnel ? null : ridingTunnel(ifaces, ranked, devs, kernel); if (main.softwire) @@ -148,6 +155,9 @@ function groupLines(ifaces, devs, kernel) { if (g) add(g, r); else start(r, r.metric); }); + /* with several lines there's no telling which one it rides */ + if (groups.length === 1) + ranked.forEach(function(r) { if (r !== main && r.tunnel && r.family === 1 && IPV6_OVER.test(r.i.proto || '')) add(groups[0], r); }); return groups; } function cidrs(list) { @@ -172,7 +182,7 @@ function ownConduits(lines) { var shared = lines.map(function(x, k) { return x.statsDev !== x.dev && lines.some(function(y, j) { return j !== k && (y.statsDev === x.statsDev || y.dev === x.statsDev); }); }); - lines.forEach(function(x, k) { if (shared[k]) x.statsDev = x.dev; }); + lines.forEach(function(x, k) { if (shared[k]) { x.statsDev = x.dev; x.stats = x.ownStats; } }); return lines; } @@ -403,19 +413,20 @@ return baseclass.extend({ /* The other lines to the internet beside `main` (resolveUplink's): every other up interface with a default route in the main table (failover by metric, mwan3), in the kernel's order, as groupLines - makes them: a softwire or a tunnel (WireGuard, OpenVPN, GRE, - 6in4, ...) rides over another line and is not one of its own; a + makes them: a softwire or a tunnel (WireGuard, OpenVPN, GRE, ...) + rides over another line and is not one of its own; 6rd, 6in4 or + 6to4 IPv6 is part of the line when there is only one; a default in another table (policy routing) is not ordinary traffic's. A WAN that is down has no route: it stays a network until it comes up. On a bridge, `viaOf(next hop)` names the port the bridge learned that gateway on; without it a bridge with several wired ports keeps its own counters. -> [ line, ... ] as resolveUplink's, without main */ - extraUplinks: function(ifaces, devs, wirelessNames, main, viaOf) { + extraUplinks: function(ifaces, devs, wirelessNames, main, viaOf, kernel) { var self = this; if (!main) return []; devs = obj(devs); - return groupLines(ifaces, devs).slice(1).filter(function(g) { + return groupLines(ifaces, devs, kernel).filter(function(g) { return g.l3.indexOf(main.l3dev) < 0 && !g.members.some(function(i) { return i.interface === main.iface; }); }).map(function(g) { var r = defRoute(g.r.i), hop = r && r.nexthop; @@ -448,8 +459,12 @@ return baseclass.extend({ } } - var statsDev = dev; - if (d && d.devtype === 'dsa' && d['hw-tc-offload'] && d.conduit && devs[d.conduit]) + /* PPPoE: flow offloading takes downloads past the session's own + counters, and the port under it counts everything (#92). Lines + that share the port go back to their own (ownConduits). */ + var under = pick.proto === 'pppoe' && typeof pick.device === 'string' && pick.device !== l3 && devs[pick.device] ? pick.device : null; + var counted = under ? devs[under] : d, statsDev = under || dev; + if (!under && d && d.devtype === 'dsa' && d['hw-tc-offload'] && d.conduit && devs[d.conduit]) statsDev = d.conduit; var route = defRoute(pick), hop = route && route.nexthop; @@ -474,7 +489,8 @@ return baseclass.extend({ dns: arr(pick['dns-server']).filter(function(s) { return typeof s === 'string'; }), /* the interfaces the line is made of (wan, wan6) */ ifaces: [ pick.interface ], - stats: d && d.statistics ? d.statistics : null + stats: counted && counted.statistics ? counted.statistics : null, + ownStats: d && d.statistics ? d.statistics : null }; }, @@ -705,7 +721,7 @@ return baseclass.extend({ var uplinks = uplink ? ownConduits([ uplink ].concat(this.extraUplinks(raw.ifaces, devs, wlNames, uplink, function(hop) { var mac = macOf(hop); return mac && fdb[mac] ? fdb[mac].port : null; - }).slice(0, MAX_LINES))) : []; + }, kernel).slice(0, MAX_LINES))) : []; /* the network on each bridge (or bridge VLAN: br-lan.10), and its IPv4 subnets for the hosts' addresses */ var lans = Object.create(null); diff --git a/luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js b/luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js index f521c5b3b..653f9d722 100644 --- a/luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js +++ b/luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js @@ -499,6 +499,25 @@ function lookAttr(name) { function lookSaves() { return lookAttr('data-vt-look-save') === 'yes'; } +/* What Customize changed and the router doesn't have yet, as the options + it's saved in. Kept across a failed save or a reload, so Done sends + this and nothing else: a browser's earlier picks stay its own until + someone changes them. */ +var UNSAVED_KEY = 'vantage.view.unsaved'; + +function readUnsaved() { + var v = null; + try { v = JSON.parse(window.localStorage.getItem(UNSAVED_KEY) || 'null'); } catch (e) { v = null; } + return Array.isArray(v) ? v.filter(function(k) { return /^(chart|layout_(wide|mid|narrow)(_wired)?)$/.test(k); }) : []; +} + +function writeUnsaved(list) { + try { + if (list.length) window.localStorage.setItem(UNSAVED_KEY, JSON.stringify(list)); + else window.localStorage.removeItem(UNSAVED_KEY); + } catch (e) {} +} + /* Done hands the theme what changed; true if it took it (a login that may save). Without it, or for any other login, it stays in this browser. */ function offerView(values, done) { @@ -517,12 +536,21 @@ function readLayouts() { var out = Object.create(null); if (v && typeof v === 'object' && !Array.isArray(v)) Object.keys(v).forEach(function(k) { if (/^(wide|mid|narrow)(-wired)?$/.test(k) && Array.isArray(v[k])) out[k] = v[k]; }); - return layout.merge(out, layout.decode(lookAttr('data-vt-look-layout')), lookSaves()); + /* changed here and not saved yet: this browser's stands, until Done sends it */ + var saved = layout.decode(lookAttr('data-vt-look-layout')), unsaved = readUnsaved(); + Object.keys(saved).forEach(function(k) { if (unsaved.indexOf(layout.option(k)) >= 0) delete saved[k]; }); + return layout.merge(out, saved, lookSaves()); } +/* a login that can't save follows the router: what is the router's isn't + this browser's to keep */ function writeLayouts(v) { + var saved = lookSaves() ? null : layout.decode(lookAttr('data-vt-look-layout')), out = {}; + Object.keys(v).forEach(function(k) { + if (!saved || !saved[k] || layout.encode(saved[k]) !== layout.encode(v[k])) out[k] = v[k]; + }); try { - if (Object.keys(v).length) window.localStorage.setItem(LAYOUT_KEY, JSON.stringify(v)); + if (Object.keys(out).length) window.localStorage.setItem(LAYOUT_KEY, JSON.stringify(out)); else window.localStorage.removeItem(LAYOUT_KEY); } catch (e) {} } @@ -534,7 +562,7 @@ var CHART_KEY = 'vantage.chart.v1'; function readChartStyle() { var v = null; try { v = window.localStorage.getItem(CHART_KEY); } catch (e) { v = null; } - return series.chartPick(lookAttr('data-vt-look-chart'), v, lookSaves()); + return series.chartPick(lookAttr('data-vt-look-chart'), v, lookSaves() && readUnsaved().indexOf('chart') < 0); } function writeChartStyle(v) { @@ -1286,13 +1314,22 @@ return view.extend({ var l = this.lay; if (!l) return; var k = layout.key(l.tier, l.wired); - if (layout.same(list, layout.defaults(l.tier, l.wired))) delete this.layouts[k]; + if (layout.same(list, layout.defaults(l.tier, l.wired))) this.clearLayout(k); else this.layouts[k] = layout.compact(list); writeLayouts(this.layouts); + this.viewChanged(layout.option(k)); this.held = Object.create(null); this.doLayout(); }, + /* back to the default. The router's layout would come back on reload + (for a login that can't save, or before Done), so this browser keeps + an empty one of its own, which normalize reads as the default. */ + clearLayout: function(k) { + if (k in layout.decode(lookAttr('data-vt-look-layout'))) this.layouts[k] = []; + else delete this.layouts[k]; + }, + editTile: function(id, fn, say) { var l = this.lay; if (!l) return; @@ -1308,8 +1345,10 @@ return view.extend({ window.setTimeout(function() { txt(s, text); }, 30); }, - setEditing: function(on) { - if (!on && this.editing && this.saveView()) return; + /* leave: close without saving (Esc or the Customize button after a + failed save; Done tries again) */ + setEditing: function(on, leave) { + if (!on && this.editing && !leave && this.saveView()) return; this.editing = !!on; this.el.root.classList.toggle('vt-editing', this.editing); this.el.layoutBtn.setAttribute('aria-pressed', String(this.editing)); @@ -1319,7 +1358,7 @@ return view.extend({ this.held = Object.create(null); this.doLayout(); if (this.editing) { - this.viewStart = this.viewValues(); + this.viewFailed = false; this.editNote(lookAttr('data-vt-look-save') === 'relogin' ? _('Log in again to save this for every browser.') : ''); this.el.editDone.focus(); } @@ -1337,24 +1376,34 @@ return view.extend({ [ 'wide', 'mid', 'narrow' ].forEach(function(tier) { [ false, true ].forEach(function(wired) { var k = layout.key(tier, wired); - out[layout.option(k)] = self.layouts[k] ? layout.encode(self.layouts[k]) : 'default'; + out[layout.option(k)] = self.layouts[k] && self.layouts[k].length ? layout.encode(self.layouts[k]) : 'default'; }); }); return out; }, + /* this option changed under Customize, for a login that may save it */ + viewChanged: function(opt) { + if (!lookSaves()) return; + var list = readUnsaved(); + if (list.indexOf(opt) < 0) writeUnsaved(list.concat(opt)); + }, + /* Done: what changed goes to the theme in one write. True while that runs: Customize closes when it is saved, and stays open with a note if it was not, so the pick can be saved again. */ saveView: function() { - var self = this, now = this.viewValues(), then = this.viewStart || {}, out = {}; + var self = this, now = this.viewValues(), out = {}; if (this.viewSaving) return true; - Object.keys(now).forEach(function(k) { if (now[k] !== then[k]) out[k] = now[k]; }); + readUnsaved().forEach(function(k) { out[k] = now[k]; }); if (!Object.keys(out).length) return false; this.viewSaving = offerView(out, function(ok) { self.viewSaving = false; + self.viewFailed = !ok; if (!ok) { self.editNote(_('Could not save this for every browser. Try again.')); return; } - self.viewStart = now; + /* anything changed since it was sent goes out on this close */ + var then = self.viewValues(); + writeUnsaved(readUnsaved().filter(function(k) { return then[k] !== out[k]; })); self.setEditing(false); }); return this.viewSaving; @@ -1407,6 +1456,7 @@ return view.extend({ if (series.CHART_STYLES.indexOf(v) < 0 || v === this.chartStyle) return; this.chartStyle = v; writeChartStyle(v); + this.viewChanged('chart'); this.chartHide(); this.paintChart(); }, @@ -1414,7 +1464,7 @@ return view.extend({ buildEditBar: function() { var self = this, el = this.el; el.editHint = E('span', { 'class': 'vt-hint' }); - el.editNote = E('span', { 'class': 'vt-hint', 'role': 'status', 'hidden': 'hidden' }); + el.editNote = E('span', { 'class': 'vt-edit-note', 'role': 'status', 'hidden': 'hidden' }); el.editHidden = E('div', { 'class': 'vt-edit-hidden' }); el.editSay = E('span', { 'class': 'vt-sr', 'role': 'status', 'aria-live': 'polite' }); el.editBar = E('div', { 'class': 'vt-editbar', 'role': 'region', 'aria-label': _('Customize the dashboard'), 'hidden': 'hidden' }, [ @@ -1425,8 +1475,9 @@ return view.extend({ E('button', { 'type': 'button', 'class': 'vt-btn', 'click': function() { var l = self.lay; if (!l) return; - delete self.layouts[layout.key(l.tier, l.wired)]; + self.clearLayout(layout.key(l.tier, l.wired)); writeLayouts(self.layouts); + self.viewChanged(layout.option(layout.key(l.tier, l.wired))); self.held = Object.create(null); self.doLayout(); self.announce(_('Layout reset')); @@ -1607,7 +1658,7 @@ return view.extend({ el.liveText = E('span', { 'class': 'vt-live-text' }) ]), el.layoutBtn = E('button', { 'type': 'button', 'class': 'vt-btn vt-btn-ghost vt-layout-btn', 'aria-pressed': 'false', - 'title': _('Customize the dashboard'), 'click': L.bind(function() { this.setEditing(!this.editing); }, this) }, [ + 'title': _('Customize the dashboard'), 'click': L.bind(function() { this.setEditing(!this.editing, this.viewFailed); }, this) }, [ icon('layout'), E('span', {}, [ _('Customize') ]) ]) ]); @@ -3633,7 +3684,7 @@ return view.extend({ return; } if (ev.key === 'Escape' && this.editing && !this.drag) { - this.setEditing(false); + this.setEditing(false, this.viewFailed); return; } /* 'f' focuses the client filter; '/' and Ctrl+K belong to the theme */ diff --git a/luci-app-vantage/po/templates/vantage.pot b/luci-app-vantage/po/templates/vantage.pot index 19861a5b8..78afe2a50 100644 --- a/luci-app-vantage/po/templates/vantage.pot +++ b/luci-app-vantage/po/templates/vantage.pot @@ -1,7 +1,7 @@ msgid "" msgstr "Content-Type: text/plain; charset=UTF-8" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3521 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3572 msgid "%d MHz (%d–%d MHz)" msgstr "" @@ -29,19 +29,19 @@ msgid_plural "%d SSIDs · %d idle" msgstr[0] "" msgstr[1] "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1622 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2090 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2641 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1673 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2141 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2692 msgid "%d clients" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2978 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3029 msgid "%d connected" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2117 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2121 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2977 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2168 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2172 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3028 msgid "%d devices" msgstr "" @@ -67,11 +67,11 @@ msgctxt "vantage" msgid "%d min ago" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2976 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3027 msgid "%d of %d" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1509 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1560 msgid "%d of %d columns" msgstr "" @@ -101,7 +101,7 @@ msgid_plural "%d radios up" msgstr[0] "" msgstr[1] "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2633 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2684 msgid "%d radios" msgstr "" @@ -125,7 +125,7 @@ msgstr "" msgid "%d weak" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2652 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2703 msgid "%d–%d MHz of the %d–%d MHz band" msgstr "" @@ -143,27 +143,27 @@ msgstr "" msgid "%s (IPv%d)" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3343 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3394 msgid "%s (new this session)" msgstr "" #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:24 -#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:365 +#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:375 msgid "%s Gbit/s" msgstr "" #: luci-app-vantage/htdocs/luci-static/resources/vantage/fmt.js:166 #: luci-app-vantage/htdocs/luci-static/resources/vantage/wifi.js:103 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2659 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2673 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3075 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3514 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2710 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2724 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3126 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3565 msgid "%s MHz" msgstr "" #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:24 #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:51 -#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:365 +#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:375 msgid "%s Mbit/s" msgstr "" @@ -176,13 +176,13 @@ msgid "%s at %s; below %s roaming and throughput suffer" msgstr "" #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:83 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2334 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2385 msgctxt "vantage" msgid "%s available" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2679 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3522 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2730 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3573 msgid "%s dBm" msgstr "" @@ -194,13 +194,13 @@ msgstr "" msgid "%s device on a newer radio; uses more airtime per byte" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3548 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3599 msgid "%s error" msgid_plural "%s errors" msgstr[0] "" msgstr[1] "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3310 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3361 msgid "%s experience" msgstr "" @@ -216,11 +216,11 @@ msgstr "" msgid "%s has no carrier: cable unplugged or switch port down" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1475 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1526 msgid "%s hidden" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3592 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3643 msgid "%s in · %s out" msgstr "" @@ -236,7 +236,7 @@ msgstr "" msgid "%s is in no zone and accepts every connection" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1468 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1519 msgid "%s is narrower" msgstr "" @@ -244,17 +244,17 @@ msgstr "" msgid "%s is not up" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3034 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3085 msgid "" "%s is shared by %d devices (a switch or another access point behind it); its " "%s link is not theirs" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3033 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3084 msgid "%s is shared by %d devices behind the switch" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1471 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1522 msgid "%s is wider" msgstr "" @@ -270,23 +270,23 @@ msgstr "" msgid "%s may open the router's admin" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1590 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1641 msgid "%s moved" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1465 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1516 msgid "%s moved down" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1456 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1507 msgid "%s moved left" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1459 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1510 msgid "%s moved right" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1462 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1513 msgid "%s moved up" msgstr "" @@ -299,9 +299,9 @@ msgid "%s negotiated only %d Mbit/s; a cable pair or port may be faulty" msgstr "" #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:84 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2335 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2364 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3618 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2386 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2415 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3669 msgid "%s of %s" msgstr "" @@ -322,21 +322,21 @@ msgstr "" msgid "%s of frames to the device fail; interference likely" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3616 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3667 msgid "%s over %d core" msgid_plural "%s over %d cores" msgstr[0] "" msgstr[1] "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2428 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2479 msgid "%s radio" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3348 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3399 msgid "%s sent · %s retried · %s failed" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1515 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1566 msgid "%s shown" msgstr "" @@ -352,11 +352,11 @@ msgstr "" msgid "%s used" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2372 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2423 msgid "%s used of %s" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3031 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3082 msgid "%s · %d devices" msgstr "" @@ -364,7 +364,7 @@ msgstr "" msgid "%s · %s" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3340 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3391 msgid "%s · channel %d · %d MHz" msgstr "" @@ -380,27 +380,27 @@ msgstr "" msgid "%s: no carrier" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3522 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3573 msgid "(configured %d dBm, limited by regulatory rules)" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2089 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3167 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2140 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3218 msgid "(hidden)" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1622 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2090 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2641 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1673 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2141 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2692 msgid "1 client" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2978 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3029 msgid "1 connected" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2121 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2977 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2172 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3028 msgid "1 device" msgstr "" @@ -408,7 +408,7 @@ msgstr "" msgid "1 older device (Wi-Fi 4 or earlier) slows airtime" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2633 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2684 msgid "1 radio" msgstr "" @@ -436,20 +436,20 @@ msgstr "" msgid "60 GHz" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2841 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2892 msgid "Active" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3352 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3384 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3403 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3435 msgid "Address type" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3581 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3632 msgid "Addresses" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2681 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2732 msgid "Airtime" msgstr "" @@ -458,8 +458,8 @@ msgctxt "vantage" msgid "Alias" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2832 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2838 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2883 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2889 msgid "All" msgstr "" @@ -488,7 +488,7 @@ msgstr "" msgid "Busiest radio: %s of Wi-Fi traffic" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2299 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2350 msgid "Busy share of each CPU core" msgstr "" @@ -504,88 +504,88 @@ msgstr[1] "" #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:71 #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:72 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2292 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3616 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2343 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3667 msgctxt "vantage" msgid "CPU" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:250 +#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:260 msgid "Cache (reclaimable)" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3457 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3508 msgid "Cancel" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3335 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3386 msgctxt "vantage" msgid "Capabilities" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3573 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3624 msgid "Carrier" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3521 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3572 msgid "Centre frequency" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2672 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2723 msgid "Ch %d" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2070 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2640 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2121 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2691 msgid "Ch %d · %d MHz" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3514 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3565 msgid "Channel" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2070 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2121 msgid "Channel —" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2705 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2756 msgctxt "vantage" msgid "Clear filter" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1175 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2678 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2723 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2724 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1203 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2729 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2774 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2775 msgid "Clients" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3528 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3579 msgid "Clients on this radio" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3184 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3235 msgid "Close" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3090 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3141 msgid "Connected" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3060 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3111 msgid "Connected after you opened this page" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3341 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3392 msgid "Connected for" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3338 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3369 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3389 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3420 msgid "Connection" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2326 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2377 msgid "Core %d: %s" msgstr "" @@ -593,19 +593,19 @@ msgstr "" msgid "Could not read the firewall configuration" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3440 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3491 msgid "Could not save the name (no permission or the device refused it)." msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1356 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1403 msgid "Could not save this for every browser. Try again." msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3589 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3640 msgid "Counters since boot" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3526 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3577 msgid "Country" msgstr "" @@ -614,13 +614,13 @@ msgid "" "Custom rule files are configured (%s); rules in them are not listed here." msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1611 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1662 msgid "Customize" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1420 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1421 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1610 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1470 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1471 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1661 msgid "Customize the dashboard" msgstr "" @@ -628,16 +628,16 @@ msgstr "" msgid "DHCP" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3397 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3448 msgid "DHCP hostname" msgstr "" #: luci-app-vantage/htdocs/luci-static/resources/vantage/names.js:42 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3587 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3638 msgid "DNS" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2002 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2053 msgid "DNS %s" msgstr "" @@ -646,55 +646,55 @@ msgstr "" msgid "Dashboard" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2913 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2915 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3005 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3607 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2964 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2966 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3056 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3658 msgid "Device" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3424 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3475 msgid "Device name" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3453 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3504 msgid "Device type" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2991 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3042 msgid "" "Devices on the Ethernet ports appear here once they send traffic; the bridge " "forgets quiet ones after about five minutes." msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2071 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2642 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2667 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3519 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2122 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2693 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2718 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3570 msgid "Disabled" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3304 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3355 msgid "Disconnected %s; shown from the last sample." msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1434 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1485 msgctxt "vantage" msgid "Done" msgstr "" #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:45 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2071 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2642 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2667 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3519 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2122 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2693 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2718 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3570 msgctxt "vantage" msgid "Down" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2405 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2500 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2575 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2456 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2551 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2626 msgid "Download" msgstr "" @@ -702,57 +702,57 @@ msgstr "" msgid "Download (to clients)" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3346 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3397 msgctxt "vantage" msgid "Downloaded" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1496 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1547 msgid "" "Drag a tile by its handle or use its buttons. Done saves it for medium " "screens in every browser." msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1497 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1548 msgid "" "Drag a tile by its handle or use its buttons. Done saves it for narrow " "screens in every browser." msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1495 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1546 msgid "" "Drag a tile by its handle or use its buttons. Done saves it for wide screens " "in every browser." msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1496 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1547 msgid "" "Drag a tile by its handle or use its buttons. Saved in this browser for " "medium screens." msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1497 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1548 msgid "" "Drag a tile by its handle or use its buttons. Saved in this browser for " "narrow screens." msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1495 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1546 msgid "" "Drag a tile by its handle or use its buttons. Saved in this browser for wide " "screens." msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1447 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1498 msgid "Drag to move" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3592 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3643 msgctxt "vantage" msgid "Dropped" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3146 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3197 msgid "Edit" msgstr "" @@ -760,7 +760,7 @@ msgstr "" msgid "Enhanced Open (OWE)" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1674 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1725 msgid "Error" msgstr "" @@ -774,8 +774,8 @@ msgctxt "vantage" msgid "Excellent" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2915 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3084 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2966 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3135 msgid "Experience" msgstr "" @@ -793,20 +793,20 @@ msgstr "" msgid "Fair signal %s; lower rates at range" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2702 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2903 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2753 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2954 msgid "Filter by name, address, SSID…" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2903 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2954 msgid "Filter by name, address, port…" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2703 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2754 msgid "Filter clients" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3610 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3661 msgctxt "vantage" msgid "Firmware" msgstr "" @@ -819,24 +819,24 @@ msgstr "" msgid "Forwarding is accepted by default" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3348 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3399 msgid "Frames to device" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:251 +#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:261 msgctxt "vantage" msgid "Free" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3333 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3384 msgid "From device" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3073 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3124 msgid "From device: %s" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3568 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3619 msgid "From network" msgstr "" @@ -846,9 +846,9 @@ msgid "" "files are not shown." msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2000 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3025 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3584 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2051 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3076 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3635 msgid "Gateway" msgstr "" @@ -861,7 +861,7 @@ msgstr "" msgid "General Settings" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3334 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3385 msgctxt "vantage" msgid "Generation" msgstr "" @@ -871,13 +871,13 @@ msgctxt "vantage" msgid "GiB" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3352 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3384 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3403 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3435 msgid "Globally unique" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3352 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3384 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3403 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3435 msgid "Globally unique · %s" msgstr "" @@ -895,7 +895,7 @@ msgstr "" msgid "Good signal %s" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1382 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1431 msgid "Grid packets" msgstr "" @@ -911,36 +911,36 @@ msgstr "" msgid "Half duplex" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1071 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1099 msgctxt "vantage" msgid "Health checks" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1512 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3167 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1563 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3218 msgctxt "vantage" msgid "Hidden" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1474 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1525 msgid "Hide %s" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3353 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3385 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3404 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3436 msgid "IP addresses" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3582 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3633 msgid "IPv4" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3583 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3634 msgid "IPv6" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3350 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3382 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3401 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3433 msgid "Identity" msgstr "" @@ -948,11 +948,11 @@ msgstr "" msgid "In no zone: the default policies apply." msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3579 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3630 msgid "In the tunnel" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:249 +#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:259 msgctxt "vantage" msgid "In use" msgstr "" @@ -962,21 +962,21 @@ msgctxt "vantage" msgid "Input" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2049 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2100 msgid "Interface" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3576 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3627 msgid "Interface up for" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3367 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3554 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3566 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3418 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3605 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3617 msgid "Interfaces" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2000 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2051 msgid "Internet" msgstr "" @@ -1002,11 +1002,11 @@ msgid "" "itself." msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3611 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3662 msgid "Kernel" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2378 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2429 msgid "Kernel %s" msgstr "" @@ -1015,58 +1015,58 @@ msgctxt "vantage" msgid "KiB" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2717 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2768 msgid "Kind of client" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3342 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3393 msgctxt "vantage" msgid "Last activity" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2914 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3049 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3380 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2965 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3100 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3431 msgid "Last heard" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1432 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1483 msgid "Layout reset" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3059 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3110 msgid "Left %s" msgstr "" #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:174 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3061 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3112 msgctxt "vantage" msgid "Legacy" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2913 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3029 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3331 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3371 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3572 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2964 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3080 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3382 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3422 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3623 msgctxt "vantage" msgid "Link" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2915 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3072 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2966 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3123 msgid "Link rate" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1642 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2913 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2915 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3040 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3077 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1693 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2964 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2966 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3091 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3128 msgid "Live" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3615 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3666 msgid "Load" msgstr "" @@ -1082,24 +1082,24 @@ msgid_plural "Load %s · %d cores" msgstr[0] "" msgstr[1] "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3617 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3668 msgctxt "vantage" msgid "Load average" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2280 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2331 msgid "Load average over 1, 5 and 15 minutes" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2316 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2367 msgid "Load avg %s" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3613 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3664 msgid "Local time" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1323 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1362 msgid "Log in again to save this for every browser." msgstr "" @@ -1115,8 +1115,8 @@ msgstr "" msgid "MAC" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3351 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3383 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3402 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3434 msgid "MAC address" msgstr "" @@ -1124,11 +1124,11 @@ msgstr "" msgid "MCS %d" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1467 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1518 msgid "Make %s narrower" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1470 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1521 msgid "Make %s wider" msgstr "" @@ -1145,11 +1145,11 @@ msgid "Measuring…" msgstr "" #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:83 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2303 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2354 msgid "Memory" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3618 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3669 msgid "Memory in use" msgstr "" @@ -1158,63 +1158,63 @@ msgctxt "vantage" msgid "MiB" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1368 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1417 msgid "Mirrored" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1369 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1418 msgid "Mirrored, scale per direction" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3520 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3571 msgid "Mode" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3608 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3659 msgid "Model" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1464 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1515 msgid "Move %s down" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1455 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1506 msgid "Move %s left" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1458 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1509 msgid "Move %s right" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1461 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1512 msgid "Move %s up" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1447 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1498 msgid "Move %s: drag, or use the arrow keys" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3452 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3503 msgid "Name" msgstr "" #: luci-app-vantage/htdocs/luci-static/resources/vantage/names.js:134 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3492 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3543 msgid "Name contains invisible or control characters" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3354 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3386 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3405 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3437 msgid "Name from" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3010 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3061 msgid "Name from: %s" msgstr "" #: luci-app-vantage/htdocs/luci-static/resources/vantage/names.js:132 #: luci-app-vantage/htdocs/luci-static/resources/vantage/names.js:137 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3493 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3544 msgid "Name is longer than %d characters" msgstr "" @@ -1222,27 +1222,27 @@ msgstr "" msgid "Name must be text" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3295 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3364 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3346 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3415 msgid "Name this device" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2913 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2915 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3039 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3069 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3339 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3379 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2964 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2966 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3090 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3120 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3390 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3430 msgid "Network" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1175 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1788 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1789 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1203 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1839 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1840 msgid "Network path" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1387 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1436 msgid "Network path background" msgstr "" @@ -1250,13 +1250,13 @@ msgstr "" msgid "Networks: %s" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2843 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3060 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2894 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3111 msgctxt "vantage" msgid "New" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1947 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1998 msgid "No SSIDs" msgstr "" @@ -1264,24 +1264,24 @@ msgstr "" msgid "No clients" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2993 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3533 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3044 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3584 msgid "No clients connected" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3121 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3172 msgid "No clients connected." msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2984 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3035 msgid "No clients in this view" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2984 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3035 msgid "No clients match “%s”" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2001 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2052 msgctxt "vantage" msgid "No default route" msgstr "" @@ -1290,7 +1290,7 @@ msgstr "" msgid "No interface with a route" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3558 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3609 msgid "No interface with a route is up." msgstr "" @@ -1299,7 +1299,7 @@ msgid "No internet-facing zone found" msgstr "" #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:47 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3573 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3624 msgid "No link" msgstr "" @@ -1307,7 +1307,7 @@ msgstr "" msgid "No network interface with a route is up" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1928 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1979 msgid "No other interfaces are up" msgstr "" @@ -1317,7 +1317,7 @@ msgid "No problems detected" msgstr "" #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:105 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1951 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2002 msgid "No radios reported" msgstr "" @@ -1334,16 +1334,16 @@ msgstr "" msgid "No stations are associated right now" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2990 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3041 msgid "No wired devices heard" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3155 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3206 msgctxt "vantage" msgid "No wireless networks configured." msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2647 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2698 msgid "No wireless radios are reported by this device." msgstr "" @@ -1358,39 +1358,39 @@ msgstr "" msgid "No zones are configured." msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3121 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3172 msgid "Nobody is moving data right now." msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2680 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2731 msgid "Noise" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3336 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3387 msgid "Noise / SNR" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3524 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3525 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3575 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3576 msgid "Noise floor" msgstr "" #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:105 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1382 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1431 msgid "None" msgstr "" #: luci-app-vantage/htdocs/luci-static/resources/vantage/names.js:150 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3491 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3542 msgid "Not a station MAC address" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3286 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3337 msgid "Not connected" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3041 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3376 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3092 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3427 msgid "Not counted per device on a shared port" msgstr "" @@ -1398,7 +1398,7 @@ msgstr "" msgid "Not in effect" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2353 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2404 msgid "Not reported" msgstr "" @@ -1406,7 +1406,7 @@ msgstr "" msgid "Not reported by the driver" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3400 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3451 msgid "Nothing better known" msgstr "" @@ -1427,7 +1427,7 @@ msgstr "" msgid "Older %s device" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3061 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3112 msgid "Older Wi-Fi generation than the radio; uses more airtime" msgstr "" @@ -1435,7 +1435,7 @@ msgstr "" msgid "On this router" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2915 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2966 msgctxt "vantage" msgid "Online" msgstr "" @@ -1452,7 +1452,7 @@ msgctxt "vantage" msgid "Open" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2959 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3010 msgid "Open details of %s" msgstr "" @@ -1468,7 +1468,7 @@ msgstr "" msgid "Open network" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:836 +#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:852 msgid "OpenWrt device" msgstr "" @@ -1478,11 +1478,11 @@ msgid "" "new rules: what is open is unknown." msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1366 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1415 msgid "Overlaid" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1367 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1416 msgid "Overlaid, log scale" msgstr "" @@ -1490,11 +1490,11 @@ msgstr "" msgid "Overview" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3335 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3386 msgid "PMF" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1640 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1691 msgid "Paused while hidden" msgstr "" @@ -1508,7 +1508,7 @@ msgid "" "on to your network: what every router lets in." msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3609 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3660 msgctxt "vantage" msgid "Platform" msgstr "" @@ -1518,19 +1518,19 @@ msgctxt "vantage" msgid "Poor" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3370 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3421 msgid "Port" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:355 +#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:365 msgid "Port %d" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3034 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3085 msgid "Port %s" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2114 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2165 msgid "Port total: %s" msgstr "" @@ -1538,11 +1538,11 @@ msgstr "" msgid "Private" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3352 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3403 msgid "Private Wi-Fi address (randomised by the device)" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3384 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3435 msgid "Private address (randomised by the device)" msgstr "" @@ -1550,26 +1550,26 @@ msgstr "" msgid "Private device" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3606 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3657 msgid "Processes" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3575 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3626 msgid "Protocol" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2712 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2714 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2763 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2765 msgid "Quick filters" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3340 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3518 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3391 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3569 msgctxt "vantage" msgid "Radio" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2668 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2719 msgid "Radio details" msgstr "" @@ -1581,66 +1581,66 @@ msgstr "" msgid "Radio is enabled but not up" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2692 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3512 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2743 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3563 msgid "Radio settings" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1175 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2620 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2622 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1203 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2671 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2673 msgid "Radios" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2002 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2053 msgid "Reachability not probed" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3026 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3077 msgid "Reached through the uplink port %s, not connected to this device" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3566 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3617 msgid "Realtime graphs" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3590 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3641 msgctxt "vantage" msgid "Received" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1641 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1692 msgctxt "vantage" msgid "Reconnecting…" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2859 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2910 msgid "Remove filter: %s" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3458 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3509 msgid "Remove name" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3295 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3364 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3346 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3415 msgctxt "vantage" msgid "Rename" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1433 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1484 msgid "Reset" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3395 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3446 msgid "Reverse DNS of its address" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:848 +#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:864 msgid "Root (overlay)" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3586 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3637 msgid "Route metric" msgstr "" @@ -1652,20 +1652,20 @@ msgstr "" msgid "SSH (port 22)" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3450 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3501 msgid "Save" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3343 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3394 msgid "Seen since" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3591 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3642 msgctxt "vantage" msgid "Sent" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1370 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1419 msgid "Separate lanes" msgstr "" @@ -1673,57 +1673,57 @@ msgstr "" msgid "Settings to check" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3371 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3422 msgid "Shared by %d devices" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3030 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3081 msgid "Shared port" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1514 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1565 msgid "Show %s" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2985 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3036 msgid "Show all clients" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2289 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2340 msgid "Show or hide system details" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1789 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1840 msgid "Show or hide the network path" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2623 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2674 msgid "Show or hide the radio details" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2408 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2459 msgid "Show or hide the throughput chart" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3146 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3197 msgid "Show or hide the wireless networks" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2088 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3164 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2139 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3215 msgid "Show the clients of %s" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2114 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2165 msgid "Show the devices on %s" msgstr "" #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:119 #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:120 #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:128 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2915 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3066 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3319 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2966 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3117 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3370 msgid "Signal" msgstr "" @@ -1731,7 +1731,7 @@ msgstr "" msgid "Signal not reported" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3574 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3625 msgctxt "vantage" msgid "Speed" msgstr "" @@ -1742,24 +1742,24 @@ msgid_plural "Standard housekeeping: %d rules" msgstr[0] "" msgstr[1] "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1382 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1431 msgid "Starfield" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2994 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3045 msgid "Stations appear here as soon as they associate with one of the SSIDs." msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3519 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3570 msgid "Status" msgstr "" #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:93 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2306 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2357 msgid "Storage" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3459 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3510 msgid "Stored on this device in /etc/config/vantage" msgstr "" @@ -1775,12 +1775,12 @@ msgstr "" msgid "Strong signal, %s" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2363 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2414 msgctxt "vantage" msgid "Swap" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3374 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3425 msgid "Switch ports" msgstr "" @@ -1792,13 +1792,13 @@ msgstr "" msgid "Switched off" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1176 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2288 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2289 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1204 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2339 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2340 msgid "System" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3606 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3657 msgid "System settings" msgstr "" @@ -1811,19 +1811,19 @@ msgid "Telnet (port 23)" msgstr "" #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:76 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2320 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2371 msgid "Temperature %s" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:848 +#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:864 msgid "Temporary (RAM)" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2914 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2965 msgid "The bridge forgets a device after about five minutes without traffic" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2988 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3039 msgid "" "The bridges have learned so many addresses that listing them here would " "strain this device." @@ -1836,7 +1836,7 @@ msgid "" "reject in General Settings." msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3496 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3547 msgid "The device could not write /etc/config/vantage." msgstr "" @@ -1860,7 +1860,7 @@ msgid "" "an uplink in no zone would be open. Set Input to reject in General Settings." msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3398 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3449 msgid "The name the device announces (WPS)" msgstr "" @@ -1872,7 +1872,7 @@ msgstr "" msgid "The only measured client" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3025 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3076 msgid "The router this device sends its traffic to" msgstr "" @@ -1888,7 +1888,7 @@ msgstr "" msgid "The uplinks could not be checked" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2017 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2068 msgid "This access point" msgstr "" @@ -1896,11 +1896,11 @@ msgstr "" msgid "This address family is in no zone: the default policies apply." msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2017 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2068 msgid "This device" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3495 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3546 msgid "This device already stores %d names; remove some first." msgstr "" @@ -1908,7 +1908,7 @@ msgstr "" msgid "This device reports no radios" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3555 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3606 msgid "This line is down or has no default route now." msgstr "" @@ -1918,7 +1918,7 @@ msgid "" "the firewall pages." msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3503 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3554 msgid "This radio is not reported any more." msgstr "" @@ -1926,23 +1926,23 @@ msgstr "" msgid "This setup is beyond what this overview checks" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3286 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3337 msgid "This station is not associated right now." msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1175 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2400 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2402 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3325 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1203 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2451 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2453 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3376 msgctxt "vantage" msgid "Throughput" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1375 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1424 msgid "Throughput chart" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2393 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2444 msgid "Throughput per link; arrow keys read the values over time" msgstr "" @@ -1951,38 +1951,38 @@ msgctxt "vantage" msgid "TiB" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3332 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3383 msgid "To device" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3046 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3081 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3134 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3097 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3132 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3185 msgid "To device %s, from device %s" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3073 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3124 msgid "To device: %s" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3569 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3620 msgid "To network" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2987 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3038 msgid "Too many devices to list" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1176 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3098 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3099 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1204 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3149 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3150 msgctxt "vantage" msgid "Top talkers" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3345 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3375 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3515 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3396 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3426 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3566 msgid "Traffic" msgstr "" @@ -1998,16 +1998,16 @@ msgid "" "networks. Set Forward to reject in General Settings." msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2013 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2064 msgid "Traffic to the internet goes through %s, a tunnel over this line" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3578 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3629 msgid "Tunnel" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2679 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3522 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2730 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3573 msgid "Tx power" msgstr "" @@ -2018,16 +2018,16 @@ msgid "" msgstr "" #: luci-app-vantage/htdocs/luci-static/resources/vantage/names.js:272 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3285 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3336 msgctxt "vantage" msgid "Unknown device" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3494 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3545 msgid "Unknown device type" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3503 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3554 msgid "Unknown radio" msgstr "" @@ -2042,14 +2042,14 @@ msgid "Unnamed rule" msgstr "" #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:57 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2667 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3519 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3573 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2718 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3570 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3624 msgctxt "vantage" msgid "Up" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2313 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2364 msgid "Up %s" msgstr "" @@ -2058,25 +2058,25 @@ msgstr "" #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:49 #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:51 #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:57 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2426 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3558 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2477 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3609 msgctxt "vantage" msgid "Uplink" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2426 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2427 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2477 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2478 msgid "Uplink %s" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3551 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3562 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3602 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3613 msgid "Uplink · %s" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2406 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2502 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2576 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2457 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2553 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2627 msgid "Upload" msgstr "" @@ -2084,21 +2084,21 @@ msgstr "" msgid "Upload (from clients)" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3347 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3398 msgctxt "vantage" msgid "Uploaded" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3026 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3077 msgid "Upstream" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3612 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3663 msgid "Uptime" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3027 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3379 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3078 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3430 msgid "VLAN %d" msgstr "" @@ -2233,11 +2233,11 @@ msgstr "" msgid "WPS" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3355 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3406 msgid "WPS device name" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1674 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1725 msgid "Warning" msgstr "" @@ -2251,7 +2251,7 @@ msgid "Weak" msgstr "" #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:157 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2842 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2893 msgid "Weak signal" msgstr "" @@ -2275,27 +2275,27 @@ msgstr "" #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:106 #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:108 #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:110 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2896 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2947 msgid "Wi-Fi" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1958 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2896 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2009 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2947 msgid "Wired" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1176 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3145 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3146 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1204 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3196 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3197 msgctxt "vantage" msgid "Wireless networks" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3305 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3356 msgid "Wireless settings" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3394 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3445 msgid "Your name for this device" msgstr "" @@ -2333,15 +2333,15 @@ msgstr "" msgid "any zone (default policy)" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2673 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2724 msgid "auto" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3343 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3394 msgid "before this page opened" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3372 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3423 msgid "behind %s, VLAN %d" msgstr "" @@ -2350,16 +2350,16 @@ msgctxt "vantage" msgid "bit/s" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2534 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2585 msgid "collecting · %s of data" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2534 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2585 msgid "collecting · first samples" msgstr "" #: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:251 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2514 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2565 msgid "collecting…" msgstr "" @@ -2371,18 +2371,18 @@ msgstr "" msgid "dBm" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1877 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2015 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1928 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2066 msgid "default route" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2015 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2066 msgid "default route via %s" msgstr "" #: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:385 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3030 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3371 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3081 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3422 msgid "down" msgstr "" @@ -2391,7 +2391,7 @@ msgctxt "vantage" msgid "drop" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3424 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3475 msgctxt "vantage" msgid "e.g. Living room TV" msgstr "" @@ -2417,7 +2417,7 @@ msgstr "" msgid "from anywhere except %s" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:366 +#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:376 msgid "full duplex" msgstr "" @@ -2425,13 +2425,13 @@ msgstr "" msgid "gateway %s" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:366 +#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:376 msgid "half duplex" msgstr "" #: luci-app-vantage/htdocs/luci-static/resources/vantage/fmt.js:62 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2035 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3172 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2086 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3223 msgid "idle" msgstr "" @@ -2473,14 +2473,14 @@ msgctxt "vantage" msgid "kbit/s" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2298 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2533 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2349 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2584 msgid "last 5 min" msgstr "" #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:175 #: luci-app-vantage/htdocs/luci-static/resources/vantage/wifi.js:104 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3075 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3126 msgid "legacy" msgstr "" @@ -2492,45 +2492,45 @@ msgstr "" msgid "mDNS" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3396 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3447 msgid "mDNS announcement" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3327 #: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3378 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3429 msgid "measuring" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2509 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2560 msgid "measuring…" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2490 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2541 msgctxt "vantage" msgid "no data" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2008 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2059 msgid "no uplink" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3353 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3404 msgid "not known to this AP" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3385 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3436 msgid "not known to this device" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3525 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3576 msgid "not reported (driver placeholder %s)" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3336 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3387 msgid "not reported by the driver" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2530 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2581 msgctxt "vantage" msgid "now" msgstr "" @@ -2539,16 +2539,16 @@ msgstr "" msgid "now %s down, %s up" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3334 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3385 msgid "older than the radio (%s)" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2507 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2558 msgctxt "vantage" msgid "peak" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2299 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2350 msgid "per core" msgstr "" @@ -2557,11 +2557,11 @@ msgctxt "vantage" msgid "port forward" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3321 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3372 msgid "range %s … %s" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3568 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3619 msgid "received on %s" msgstr "" @@ -2574,7 +2574,7 @@ msgstr "" msgid "router port %s" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3569 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3620 msgid "sent on %s" msgstr "" @@ -2582,12 +2582,12 @@ msgstr "" msgid "short GI" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3049 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3380 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3100 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3431 msgid "static entry" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3373 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3424 msgid "switch port %s" msgstr "" @@ -2599,11 +2599,11 @@ msgstr "" msgid "the covered subnets of zone %s" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3377 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3428 msgid "the port in total: %s" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3372 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3423 msgid "the port: %s" msgstr "" @@ -2619,7 +2619,7 @@ msgstr "" msgid "traffic rule" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2020 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2071 msgid "up %s" msgstr "" @@ -2636,6 +2636,6 @@ msgstr "" msgid "zones %s and %s if they share a device" msgstr "" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2527 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2578 msgid "−%d min" msgstr "" diff --git a/luci-app-vantage/po/zh_Hans/vantage.po b/luci-app-vantage/po/zh_Hans/vantage.po index c30694957..ca3b3fee8 100644 --- a/luci-app-vantage/po/zh_Hans/vantage.po +++ b/luci-app-vantage/po/zh_Hans/vantage.po @@ -12,7 +12,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3521 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3572 msgid "%d MHz (%d–%d MHz)" msgstr "%d MHz(%d–%d MHz)" @@ -36,19 +36,19 @@ msgid "%d SSID · %d idle" msgid_plural "%d SSIDs · %d idle" msgstr[0] "%d 个 SSID · %d 个空闲" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1622 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2090 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2641 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1673 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2141 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2692 msgid "%d clients" msgstr "%d 个客户端" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2978 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3029 msgid "%d connected" msgstr "已连接 %d 个" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2117 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2121 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2977 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2168 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2172 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3028 msgid "%d devices" msgstr "%d 台设备" @@ -73,11 +73,11 @@ msgctxt "vantage" msgid "%d min ago" msgstr "%d 分钟前" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2976 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3027 msgid "%d of %d" msgstr "%d/%d" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1509 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1560 msgid "%d of %d columns" msgstr "%d/%d 列" @@ -104,7 +104,7 @@ msgid "%d radio up" msgid_plural "%d radios up" msgstr[0] "%d 个射频已上线" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2633 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2684 msgid "%d radios" msgstr "%d 个射频" @@ -128,7 +128,7 @@ msgstr "%d 项需要关注" msgid "%d weak" msgstr "%d 个信号弱" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2652 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2703 msgid "%d–%d MHz of the %d–%d MHz band" msgstr "%d–%d MHz 属于 %d–%d MHz 频段" @@ -146,27 +146,27 @@ msgstr "%s(%s)已启用但未上线" msgid "%s (IPv%d)" msgstr "%s(IPv%d)" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3343 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3394 msgid "%s (new this session)" msgstr "%s(本次新增)" #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:24 -#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:365 +#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:375 msgid "%s Gbit/s" msgstr "%s Gbit/s" #: luci-app-vantage/htdocs/luci-static/resources/vantage/fmt.js:166 #: luci-app-vantage/htdocs/luci-static/resources/vantage/wifi.js:103 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2659 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2673 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3075 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3514 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2710 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2724 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3126 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3565 msgid "%s MHz" msgstr "%s MHz" #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:24 #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:51 -#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:365 +#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:375 msgid "%s Mbit/s" msgstr "%s Mbit/s" @@ -179,13 +179,13 @@ msgid "%s at %s; below %s roaming and throughput suffer" msgstr "%s(%s);低于 %s 时漫游和吞吐量会受影响" #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:83 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2334 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2385 msgctxt "vantage" msgid "%s available" msgstr "可用 %s" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2679 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3522 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2730 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3573 msgid "%s dBm" msgstr "%s dBm" @@ -197,12 +197,12 @@ msgstr "%s 设备" msgid "%s device on a newer radio; uses more airtime per byte" msgstr "%s 设备连接在较新的射频上,每字节占用更多空口时间" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3548 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3599 msgid "%s error" msgid_plural "%s errors" msgstr[0] "%s 个错误" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3310 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3361 msgid "%s experience" msgstr "%s 体验" @@ -218,11 +218,11 @@ msgstr "overlay 空闲 %s(已用 %s)" msgid "%s has no carrier: cable unplugged or switch port down" msgstr "%s 无载波:网线未插或交换机端口关闭" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1475 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1526 msgid "%s hidden" msgstr "%s 已隐藏" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3592 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3643 msgid "%s in · %s out" msgstr "%s 入 · %s 出" @@ -238,7 +238,7 @@ msgstr "%s 被转发到 %s" msgid "%s is in no zone and accepts every connection" msgstr "%s 不属于任何区域,接受所有连接" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1468 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1519 msgid "%s is narrower" msgstr "%s 已变窄" @@ -246,18 +246,18 @@ msgstr "%s 已变窄" msgid "%s is not up" msgstr "%s 未上线" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3034 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3085 msgid "" "%s is shared by %d devices (a switch or another access point behind it); its " "%s link is not theirs" msgstr "" "%s 由 %d 台设备共用(其后接有交换机或另一个接入点);%s 的链路不属于这些设备" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3033 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3084 msgid "%s is shared by %d devices behind the switch" msgstr "%s 由交换机后的 %d 台设备共用" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1471 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1522 msgid "%s is wider" msgstr "%s 已加宽" @@ -273,23 +273,23 @@ msgstr "%s 链路" msgid "%s may open the router's admin" msgstr "%s 可能暴露了路由器管理界面" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1590 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1641 msgid "%s moved" msgstr "%s 已移动" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1465 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1516 msgid "%s moved down" msgstr "%s 已下移" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1456 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1507 msgid "%s moved left" msgstr "%s 已左移" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1459 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1510 msgid "%s moved right" msgstr "%s 已右移" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1462 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1513 msgid "%s moved up" msgstr "%s 已上移" @@ -302,9 +302,9 @@ msgid "%s negotiated only %d Mbit/s; a cable pair or port may be faulty" msgstr "%s 仅协商到 %d Mbit/s;网线线对或端口可能有故障" #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:84 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2335 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2364 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3618 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2386 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2415 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3669 msgid "%s of %s" msgstr "%s / %s" @@ -325,20 +325,20 @@ msgstr "发往设备的帧失败率:%s" msgid "%s of frames to the device fail; interference likely" msgstr "发往设备的帧失败率:%s,可能存在干扰" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3616 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3667 msgid "%s over %d core" msgid_plural "%s over %d cores" msgstr[0] "%s / %d 核" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2428 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2479 msgid "%s radio" msgstr "%s 射频" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3348 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3399 msgid "%s sent · %s retried · %s failed" msgstr "发送 %s · 重传 %s · 失败 %s" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1515 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1566 msgid "%s shown" msgstr "%s 已显示" @@ -354,11 +354,11 @@ msgstr "%s 已连接,速率 %s" msgid "%s used" msgstr "已用 %s" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2372 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2423 msgid "%s used of %s" msgstr "已用 %s / 共 %s" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3031 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3082 msgid "%s · %d devices" msgstr "%s · %d 台设备" @@ -366,7 +366,7 @@ msgstr "%s · %d 台设备" msgid "%s · %s" msgstr "%s · %s" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3340 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3391 msgid "%s · channel %d · %d MHz" msgstr "%s · 信道 %d · %d MHz" @@ -382,27 +382,27 @@ msgstr "%s · 链路较慢" msgid "%s: no carrier" msgstr "%s:无载波" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3522 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3573 msgid "(configured %d dBm, limited by regulatory rules)" msgstr "(配置 %d dBm,受管制规则限制)" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2089 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3167 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2140 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3218 msgid "(hidden)" msgstr "(隐藏)" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1622 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2090 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2641 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1673 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2141 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2692 msgid "1 client" msgstr "1 个客户端" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2978 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3029 msgid "1 connected" msgstr "已连接 1 个" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2121 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2977 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2172 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3028 msgid "1 device" msgstr "1 台设备" @@ -410,7 +410,7 @@ msgstr "1 台设备" msgid "1 older device (Wi-Fi 4 or earlier) slows airtime" msgstr "1 台旧设备(Wi-Fi 4 或更早)占用更多空口时间" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2633 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2684 msgid "1 radio" msgstr "1 个射频" @@ -438,20 +438,20 @@ msgstr "6 GHz" msgid "60 GHz" msgstr "60 GHz" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2841 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2892 msgid "Active" msgstr "活跃" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3352 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3384 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3403 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3435 msgid "Address type" msgstr "地址类型" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3581 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3632 msgid "Addresses" msgstr "地址" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2681 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2732 msgid "Airtime" msgstr "空口时间" @@ -460,8 +460,8 @@ msgctxt "vantage" msgid "Alias" msgstr "别名" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2832 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2838 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2883 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2889 msgid "All" msgstr "全部" @@ -490,7 +490,7 @@ msgstr "B" msgid "Busiest radio: %s of Wi-Fi traffic" msgstr "最繁忙射频:占 Wi-Fi 流量的 %s" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2299 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2350 msgid "Busy share of each CPU core" msgstr "每个 CPU 核心的繁忙占比" @@ -505,88 +505,88 @@ msgstr[0] "繁忙;负载 %s(%d 核);查看最繁忙进程" #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:71 #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:72 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2292 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3616 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2343 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3667 msgctxt "vantage" msgid "CPU" msgstr "CPU" -#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:250 +#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:260 msgid "Cache (reclaimable)" msgstr "缓存(可回收)" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3457 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3508 msgid "Cancel" msgstr "取消" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3335 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3386 msgctxt "vantage" msgid "Capabilities" msgstr "能力" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3573 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3624 msgid "Carrier" msgstr "链路状态" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3521 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3572 msgid "Centre frequency" msgstr "中心频率" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2672 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2723 msgid "Ch %d" msgstr "信道 %d" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2070 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2640 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2121 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2691 msgid "Ch %d · %d MHz" msgstr "信道 %d · %d MHz" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3514 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3565 msgid "Channel" msgstr "信道" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2070 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2121 msgid "Channel —" msgstr "信道 —" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2705 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2756 msgctxt "vantage" msgid "Clear filter" msgstr "清除筛选" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1175 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2678 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2723 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2724 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1203 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2729 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2774 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2775 msgid "Clients" msgstr "客户端" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3528 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3579 msgid "Clients on this radio" msgstr "此射频上的客户端" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3184 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3235 msgid "Close" msgstr "关闭" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3090 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3141 msgid "Connected" msgstr "已连接" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3060 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3111 msgid "Connected after you opened this page" msgstr "打开本页面后连接" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3341 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3392 msgid "Connected for" msgstr "已连接时长" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3338 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3369 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3389 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3420 msgid "Connection" msgstr "连接" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2326 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2377 msgid "Core %d: %s" msgstr "核心 %d:%s" @@ -594,19 +594,19 @@ msgstr "核心 %d:%s" msgid "Could not read the firewall configuration" msgstr "无法读取防火墙配置" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3440 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3491 msgid "Could not save the name (no permission or the device refused it)." msgstr "无法保存名称(无权限或设备拒绝)。" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1356 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1403 msgid "Could not save this for every browser. Try again." msgstr "无法为所有浏览器保存此设置,请重试。" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3589 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3640 msgid "Counters since boot" msgstr "自启动以来的计数器" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3526 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3577 msgid "Country" msgstr "国家" @@ -615,13 +615,13 @@ msgid "" "Custom rule files are configured (%s); rules in them are not listed here." msgstr "已配置自定义规则文件(%s);其中的规则不会在此列出。" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1611 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1662 msgid "Customize" msgstr "自定义" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1420 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1421 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1610 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1470 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1471 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1661 msgid "Customize the dashboard" msgstr "自定义仪表盘" @@ -629,16 +629,16 @@ msgstr "自定义仪表盘" msgid "DHCP" msgstr "DHCP" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3397 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3448 msgid "DHCP hostname" msgstr "DHCP 主机名" #: luci-app-vantage/htdocs/luci-static/resources/vantage/names.js:42 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3587 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3638 msgid "DNS" msgstr "DNS" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2002 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2053 msgid "DNS %s" msgstr "DNS %s" @@ -647,56 +647,56 @@ msgstr "DNS %s" msgid "Dashboard" msgstr "仪表盘" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2913 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2915 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3005 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3607 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2964 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2966 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3056 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3658 msgid "Device" msgstr "设备" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3424 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3475 msgid "Device name" msgstr "设备名" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3453 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3504 msgid "Device type" msgstr "设备类型" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2991 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3042 msgid "" "Devices on the Ethernet ports appear here once they send traffic; the bridge " "forgets quiet ones after about five minutes." msgstr "" "以太网端口上的设备发送流量后会显示在这里;网桥会在约五分钟无流量后忘记它们。" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2071 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2642 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2667 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3519 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2122 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2693 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2718 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3570 msgid "Disabled" msgstr "已禁用" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3304 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3355 msgid "Disconnected %s; shown from the last sample." msgstr "断开于 %s;显示最后一次采样的数据。" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1434 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1485 msgctxt "vantage" msgid "Done" msgstr "完成" #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:45 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2071 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2642 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2667 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3519 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2122 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2693 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2718 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3570 msgctxt "vantage" msgid "Down" msgstr "已下线" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2405 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2500 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2575 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2456 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2551 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2626 msgid "Download" msgstr "下载" @@ -704,60 +704,60 @@ msgstr "下载" msgid "Download (to clients)" msgstr "下载(到客户端)" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3346 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3397 msgctxt "vantage" msgid "Downloaded" msgstr "已下载" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1496 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1547 msgid "" "Drag a tile by its handle or use its buttons. Done saves it for medium " "screens in every browser." msgstr "" "拖动卡片手柄或使用其按钮移动。点击“完成”后,中屏布局将保存到所有浏览器。" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1497 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1548 msgid "" "Drag a tile by its handle or use its buttons. Done saves it for narrow " "screens in every browser." msgstr "" "拖动卡片手柄或使用其按钮移动。点击“完成”后,窄屏布局将保存到所有浏览器。" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1495 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1546 msgid "" "Drag a tile by its handle or use its buttons. Done saves it for wide screens " "in every browser." msgstr "" "拖动卡片手柄或使用其按钮移动。点击“完成”后,宽屏布局将保存到所有浏览器。" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1496 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1547 msgid "" "Drag a tile by its handle or use its buttons. Saved in this browser for " "medium screens." msgstr "拖动卡片手柄或使用其按钮移动。中屏布局保存在此浏览器中。" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1497 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1548 msgid "" "Drag a tile by its handle or use its buttons. Saved in this browser for " "narrow screens." msgstr "拖动卡片手柄或使用其按钮移动。窄屏布局保存在此浏览器中。" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1495 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1546 msgid "" "Drag a tile by its handle or use its buttons. Saved in this browser for wide " "screens." msgstr "拖动卡片手柄或使用其按钮移动。宽屏布局保存在此浏览器中。" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1447 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1498 msgid "Drag to move" msgstr "拖动以移动" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3592 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3643 msgctxt "vantage" msgid "Dropped" msgstr "已丢弃" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3146 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3197 msgid "Edit" msgstr "编辑" @@ -765,7 +765,7 @@ msgstr "编辑" msgid "Enhanced Open (OWE)" msgstr "增强开放 (OWE)" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1674 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1725 msgid "Error" msgstr "错误" @@ -779,8 +779,8 @@ msgctxt "vantage" msgid "Excellent" msgstr "极好" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2915 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3084 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2966 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3135 msgid "Experience" msgstr "体验" @@ -798,20 +798,20 @@ msgstr "信号一般" msgid "Fair signal %s; lower rates at range" msgstr "信号一般 %s;远距离速率较低" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2702 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2903 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2753 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2954 msgid "Filter by name, address, SSID…" msgstr "按名称、地址、SSID…筛选" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2903 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2954 msgid "Filter by name, address, port…" msgstr "按名称、地址、端口…筛选" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2703 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2754 msgid "Filter clients" msgstr "筛选客户端" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3610 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3661 msgctxt "vantage" msgid "Firmware" msgstr "固件" @@ -824,24 +824,24 @@ msgstr "从 %s 转发到 %s" msgid "Forwarding is accepted by default" msgstr "默认接受转发" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3348 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3399 msgid "Frames to device" msgstr "发往设备的帧" -#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:251 +#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:261 msgctxt "vantage" msgid "Free" msgstr "空闲" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3333 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3384 msgid "From device" msgstr "来自设备" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3073 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3124 msgid "From device: %s" msgstr "来自设备:%s" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3568 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3619 msgid "From network" msgstr "来自网络" @@ -851,9 +851,9 @@ msgid "" "files are not shown." msgstr "内容来自防火墙配置。由 UPnP 或自定义规则文件开放的端口不会显示。" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2000 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3025 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3584 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2051 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3076 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3635 msgid "Gateway" msgstr "网关" @@ -866,7 +866,7 @@ msgstr "Gbit/s" msgid "General Settings" msgstr "基本设置" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3334 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3385 msgctxt "vantage" msgid "Generation" msgstr "代际" @@ -876,13 +876,13 @@ msgctxt "vantage" msgid "GiB" msgstr "GiB" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3352 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3384 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3403 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3435 msgid "Globally unique" msgstr "全局唯一" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3352 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3384 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3403 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3435 msgid "Globally unique · %s" msgstr "全局唯一 · %s" @@ -900,7 +900,7 @@ msgstr "信号良好" msgid "Good signal %s" msgstr "信号良好 %s" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1382 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1431 msgid "Grid packets" msgstr "网格数据包" @@ -916,36 +916,36 @@ msgstr "HTTPS(端口 443)" msgid "Half duplex" msgstr "半双工" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1071 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1099 msgctxt "vantage" msgid "Health checks" msgstr "健康检查" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1512 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3167 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1563 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3218 msgctxt "vantage" msgid "Hidden" msgstr "隐藏" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1474 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1525 msgid "Hide %s" msgstr "隐藏 %s" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3353 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3385 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3404 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3436 msgid "IP addresses" msgstr "IP 地址" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3582 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3633 msgid "IPv4" msgstr "IPv4" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3583 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3634 msgid "IPv6" msgstr "IPv6" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3350 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3382 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3401 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3433 msgid "Identity" msgstr "身份" @@ -953,11 +953,11 @@ msgstr "身份" msgid "In no zone: the default policies apply." msgstr "不属于任何区域:适用默认策略。" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3579 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3630 msgid "In the tunnel" msgstr "隧道内" -#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:249 +#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:259 msgctxt "vantage" msgid "In use" msgstr "已用" @@ -967,21 +967,21 @@ msgctxt "vantage" msgid "Input" msgstr "入站数据" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2049 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2100 msgid "Interface" msgstr "接口" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3576 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3627 msgid "Interface up for" msgstr "接口已启用时长" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3367 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3554 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3566 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3418 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3605 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3617 msgid "Interfaces" msgstr "接口" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2000 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2051 msgid "Internet" msgstr "互联网" @@ -1011,11 +1011,11 @@ msgstr "" "它将来自互联网上任何地址的 %s 转发到 %s。无法读取接口列表,因此本页无法判断该" "地址是否为路由器本身。" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3611 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3662 msgid "Kernel" msgstr "内核" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2378 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2429 msgid "Kernel %s" msgstr "内核 %s" @@ -1024,58 +1024,58 @@ msgctxt "vantage" msgid "KiB" msgstr "KiB" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2717 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2768 msgid "Kind of client" msgstr "客户端类型" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3342 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3393 msgctxt "vantage" msgid "Last activity" msgstr "最后活动" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2914 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3049 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3380 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2965 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3100 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3431 msgid "Last heard" msgstr "最后活动" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1432 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1483 msgid "Layout reset" msgstr "布局已重置" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3059 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3110 msgid "Left %s" msgstr "离开于 %s" #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:174 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3061 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3112 msgctxt "vantage" msgid "Legacy" msgstr "旧版" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2913 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3029 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3331 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3371 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3572 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2964 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3080 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3382 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3422 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3623 msgctxt "vantage" msgid "Link" msgstr "链路" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2915 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3072 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2966 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3123 msgid "Link rate" msgstr "链路速率" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1642 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2913 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2915 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3040 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3077 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1693 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2964 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2966 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3091 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3128 msgid "Live" msgstr "实时" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3615 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3666 msgid "Load" msgstr "负载" @@ -1089,24 +1089,24 @@ msgid "Load %s · %d core" msgid_plural "Load %s · %d cores" msgstr[0] "负载 %s · %d 核" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3617 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3668 msgctxt "vantage" msgid "Load average" msgstr "平均负载" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2280 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2331 msgid "Load average over 1, 5 and 15 minutes" msgstr "1、5、15 分钟平均负载" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2316 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2367 msgid "Load avg %s" msgstr "平均负载 %s" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3613 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3664 msgid "Local time" msgstr "本地时间" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1323 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1362 msgid "Log in again to save this for every browser." msgstr "重新登录后即可为所有浏览器保存此设置。" @@ -1122,8 +1122,8 @@ msgstr "链路速率低于信号预期" msgid "MAC" msgstr "MAC" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3351 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3383 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3402 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3434 msgid "MAC address" msgstr "MAC 地址" @@ -1131,11 +1131,11 @@ msgstr "MAC 地址" msgid "MCS %d" msgstr "MCS %d" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1467 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1518 msgid "Make %s narrower" msgstr "使 %s 变窄" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1470 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1521 msgid "Make %s wider" msgstr "使 %s 加宽" @@ -1152,11 +1152,11 @@ msgid "Measuring…" msgstr "测量中…" #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:83 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2303 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2354 msgid "Memory" msgstr "内存" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3618 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3669 msgid "Memory in use" msgstr "已用内存" @@ -1165,63 +1165,63 @@ msgctxt "vantage" msgid "MiB" msgstr "MiB" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1368 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1417 msgid "Mirrored" msgstr "镜像" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1369 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1418 msgid "Mirrored, scale per direction" msgstr "镜像,每个方向单独刻度" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3520 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3571 msgid "Mode" msgstr "模式" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3608 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3659 msgid "Model" msgstr "型号" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1464 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1515 msgid "Move %s down" msgstr "下移 %s" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1455 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1506 msgid "Move %s left" msgstr "左移 %s" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1458 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1509 msgid "Move %s right" msgstr "右移 %s" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1461 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1512 msgid "Move %s up" msgstr "上移 %s" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1447 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1498 msgid "Move %s: drag, or use the arrow keys" msgstr "移动 %s:拖动或使用方向键" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3452 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3503 msgid "Name" msgstr "名称" #: luci-app-vantage/htdocs/luci-static/resources/vantage/names.js:134 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3492 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3543 msgid "Name contains invisible or control characters" msgstr "名称包含不可见或控制字符" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3354 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3386 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3405 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3437 msgid "Name from" msgstr "名称来源" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3010 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3061 msgid "Name from: %s" msgstr "名称来源:%s" #: luci-app-vantage/htdocs/luci-static/resources/vantage/names.js:132 #: luci-app-vantage/htdocs/luci-static/resources/vantage/names.js:137 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3493 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3544 msgid "Name is longer than %d characters" msgstr "名称超过 %d 个字符" @@ -1229,27 +1229,27 @@ msgstr "名称超过 %d 个字符" msgid "Name must be text" msgstr "名称必须是文本" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3295 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3364 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3346 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3415 msgid "Name this device" msgstr "命名此设备" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2913 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2915 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3039 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3069 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3339 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3379 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2964 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2966 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3090 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3120 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3390 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3430 msgid "Network" msgstr "网络" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1175 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1788 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1789 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1203 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1839 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1840 msgid "Network path" msgstr "网络路径" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1387 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1436 msgid "Network path background" msgstr "网络路径背景" @@ -1257,13 +1257,13 @@ msgstr "网络路径背景" msgid "Networks: %s" msgstr "网络:%s" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2843 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3060 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2894 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3111 msgctxt "vantage" msgid "New" msgstr "新增" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1947 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1998 msgid "No SSIDs" msgstr "无 SSID" @@ -1271,24 +1271,24 @@ msgstr "无 SSID" msgid "No clients" msgstr "无客户端" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2993 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3533 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3044 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3584 msgid "No clients connected" msgstr "无客户端连接" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3121 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3172 msgid "No clients connected." msgstr "没有客户端连接。" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2984 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3035 msgid "No clients in this view" msgstr "此视图中没有客户端" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2984 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3035 msgid "No clients match “%s”" msgstr "没有匹配“%s”的客户端" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2001 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2052 msgctxt "vantage" msgid "No default route" msgstr "无默认路由" @@ -1297,7 +1297,7 @@ msgstr "无默认路由" msgid "No interface with a route" msgstr "没有带路由的接口" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3558 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3609 msgid "No interface with a route is up." msgstr "没有带路由的接口处于启用状态。" @@ -1306,7 +1306,7 @@ msgid "No internet-facing zone found" msgstr "未找到面向互联网的区域" #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:47 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3573 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3624 msgid "No link" msgstr "无链路" @@ -1314,7 +1314,7 @@ msgstr "无链路" msgid "No network interface with a route is up" msgstr "没有带路由的网络接口处于启用状态" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1928 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1979 msgid "No other interfaces are up" msgstr "没有其他接口处于启用状态" @@ -1324,7 +1324,7 @@ msgid "No problems detected" msgstr "未检测到问题" #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:105 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1951 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2002 msgid "No radios reported" msgstr "未报告射频" @@ -1341,16 +1341,16 @@ msgstr "无信号" msgid "No stations are associated right now" msgstr "当前没有站点关联" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2990 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3041 msgid "No wired devices heard" msgstr "未发现有线设备" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3155 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3206 msgctxt "vantage" msgid "No wireless networks configured." msgstr "未配置无线网络。" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2647 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2698 msgid "No wireless radios are reported by this device." msgstr "此设备未报告任何无线射频。" @@ -1367,39 +1367,39 @@ msgstr "" msgid "No zones are configured." msgstr "尚未配置区域。" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3121 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3172 msgid "Nobody is moving data right now." msgstr "当前没有设备在传输数据。" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2680 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2731 msgid "Noise" msgstr "噪声" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3336 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3387 msgid "Noise / SNR" msgstr "噪声 / 信噪比" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3524 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3525 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3575 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3576 msgid "Noise floor" msgstr "底噪" #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:105 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1382 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1431 msgid "None" msgstr "无" #: luci-app-vantage/htdocs/luci-static/resources/vantage/names.js:150 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3491 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3542 msgid "Not a station MAC address" msgstr "不是站点 MAC 地址" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3286 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3337 msgid "Not connected" msgstr "未连接" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3041 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3376 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3092 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3427 msgid "Not counted per device on a shared port" msgstr "共用端口不按设备统计" @@ -1407,7 +1407,7 @@ msgstr "共用端口不按设备统计" msgid "Not in effect" msgstr "未生效" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2353 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2404 msgid "Not reported" msgstr "未报告" @@ -1415,7 +1415,7 @@ msgstr "未报告" msgid "Not reported by the driver" msgstr "驱动未报告" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3400 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3451 msgid "Nothing better known" msgstr "无更多信息" @@ -1436,7 +1436,7 @@ msgstr "OWE" msgid "Older %s device" msgstr "较旧的 %s 设备" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3061 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3112 msgid "Older Wi-Fi generation than the radio; uses more airtime" msgstr "比射频支持的 Wi-Fi 代际更旧;占用更多空口时间" @@ -1444,7 +1444,7 @@ msgstr "比射频支持的 Wi-Fi 代际更旧;占用更多空口时间" msgid "On this router" msgstr "在此路由器上" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2915 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2966 msgctxt "vantage" msgid "Online" msgstr "在线" @@ -1460,7 +1460,7 @@ msgctxt "vantage" msgid "Open" msgstr "开放" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2959 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3010 msgid "Open details of %s" msgstr "打开 %s 的详情" @@ -1476,7 +1476,7 @@ msgstr "对互联网开放:%d" msgid "Open network" msgstr "开放网络" -#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:836 +#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:852 msgid "OpenWrt device" msgstr "OpenWrt 设备" @@ -1488,11 +1488,11 @@ msgstr "" "基本设置中的选项 %s 的值被 firewall4 拒绝,因此不会加载新规则:无法确定开放了" "哪些内容。" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1366 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1415 msgid "Overlaid" msgstr "叠加" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1367 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1416 msgid "Overlaid, log scale" msgstr "叠加,对数刻度" @@ -1500,11 +1500,11 @@ msgstr "叠加,对数刻度" msgid "Overview" msgstr "概览" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3335 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3386 msgid "PMF" msgstr "PMF" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1640 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1691 msgid "Paused while hidden" msgstr "页面隐藏时暂停" @@ -1520,7 +1520,7 @@ msgstr "" "Ping 和其他控制消息、组播、DHCP 续租以及转发到你网络的 IPsec:每台路由器都会" "放行的流量。" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3609 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3660 msgctxt "vantage" msgid "Platform" msgstr "平台" @@ -1530,19 +1530,19 @@ msgctxt "vantage" msgid "Poor" msgstr "较差" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3370 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3421 msgid "Port" msgstr "端口" -#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:355 +#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:365 msgid "Port %d" msgstr "端口 %d" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3034 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3085 msgid "Port %s" msgstr "端口 %s" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2114 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2165 msgid "Port total: %s" msgstr "端口合计:%s" @@ -1550,11 +1550,11 @@ msgstr "端口合计:%s" msgid "Private" msgstr "私有" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3352 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3403 msgid "Private Wi-Fi address (randomised by the device)" msgstr "私有 Wi-Fi 地址(设备随机化)" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3384 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3435 msgid "Private address (randomised by the device)" msgstr "私有地址(设备随机化)" @@ -1562,26 +1562,26 @@ msgstr "私有地址(设备随机化)" msgid "Private device" msgstr "私有设备" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3606 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3657 msgid "Processes" msgstr "进程" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3575 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3626 msgid "Protocol" msgstr "协议" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2712 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2714 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2763 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2765 msgid "Quick filters" msgstr "快速筛选" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3340 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3518 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3391 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3569 msgctxt "vantage" msgid "Radio" msgstr "射频" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2668 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2719 msgid "Radio details" msgstr "射频详情" @@ -1593,66 +1593,66 @@ msgstr "射频已禁用" msgid "Radio is enabled but not up" msgstr "射频已启用但未上线" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2692 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3512 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2743 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3563 msgid "Radio settings" msgstr "射频设置" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1175 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2620 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2622 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1203 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2671 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2673 msgid "Radios" msgstr "射频" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2002 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2053 msgid "Reachability not probed" msgstr "未探测可达性" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3026 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3077 msgid "Reached through the uplink port %s, not connected to this device" msgstr "经由上行端口 %s 到达,并未连接到本设备" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3566 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3617 msgid "Realtime graphs" msgstr "实时图表" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3590 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3641 msgctxt "vantage" msgid "Received" msgstr "已接收" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1641 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1692 msgctxt "vantage" msgid "Reconnecting…" msgstr "重新连接中…" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2859 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2910 msgid "Remove filter: %s" msgstr "移除筛选:%s" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3458 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3509 msgid "Remove name" msgstr "移除名称" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3295 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3364 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3346 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3415 msgctxt "vantage" msgid "Rename" msgstr "重命名" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1433 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1484 msgid "Reset" msgstr "重置" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3395 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3446 msgid "Reverse DNS of its address" msgstr "其地址的反向 DNS" -#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:848 +#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:864 msgid "Root (overlay)" msgstr "Root(overlay)" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3586 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3637 msgid "Route metric" msgstr "路由跃点数" @@ -1664,20 +1664,20 @@ msgstr "路由器管理界面已开放:%s" msgid "SSH (port 22)" msgstr "SSH(端口 22)" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3450 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3501 msgid "Save" msgstr "保存" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3343 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3394 msgid "Seen since" msgstr "首次出现" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3591 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3642 msgctxt "vantage" msgid "Sent" msgstr "已发送" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1370 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1419 msgid "Separate lanes" msgstr "分开显示" @@ -1685,57 +1685,57 @@ msgstr "分开显示" msgid "Settings to check" msgstr "待检查的设置" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3371 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3422 msgid "Shared by %d devices" msgstr "%d 台设备共用" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3030 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3081 msgid "Shared port" msgstr "共用端口" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1514 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1565 msgid "Show %s" msgstr "显示 %s" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2985 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3036 msgid "Show all clients" msgstr "显示全部客户端" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2289 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2340 msgid "Show or hide system details" msgstr "显示/隐藏系统详情" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1789 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1840 msgid "Show or hide the network path" msgstr "显示/隐藏网络路径" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2623 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2674 msgid "Show or hide the radio details" msgstr "显示/隐藏射频详情" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2408 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2459 msgid "Show or hide the throughput chart" msgstr "显示/隐藏吞吐量图表" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3146 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3197 msgid "Show or hide the wireless networks" msgstr "显示/隐藏无线网络" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2088 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3164 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2139 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3215 msgid "Show the clients of %s" msgstr "显示 %s 的客户端" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2114 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2165 msgid "Show the devices on %s" msgstr "显示 %s 上的设备" #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:119 #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:120 #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:128 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2915 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3066 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3319 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2966 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3117 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3370 msgid "Signal" msgstr "信号强度" @@ -1743,7 +1743,7 @@ msgstr "信号强度" msgid "Signal not reported" msgstr "未报告信号" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3574 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3625 msgctxt "vantage" msgid "Speed" msgstr "速率" @@ -1753,24 +1753,24 @@ msgid "Standard housekeeping: %d rule" msgid_plural "Standard housekeeping: %d rules" msgstr[0] "标准常规规则:%d 条" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1382 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1431 msgid "Starfield" msgstr "星空" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2994 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3045 msgid "Stations appear here as soon as they associate with one of the SSIDs." msgstr "站点一旦关联到任一 SSID 便会显示在这里。" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3519 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3570 msgid "Status" msgstr "状态" #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:93 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2306 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2357 msgid "Storage" msgstr "存储" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3459 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3510 msgid "Stored on this device in /etc/config/vantage" msgstr "保存在本设备 /etc/config/vantage 中" @@ -1786,12 +1786,12 @@ msgstr "信号强 %s,链路 %s" msgid "Strong signal, %s" msgstr "信号强,%s" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2363 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2414 msgctxt "vantage" msgid "Swap" msgstr "交换分区" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3374 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3425 msgid "Switch ports" msgstr "交换机端口" @@ -1803,13 +1803,13 @@ msgstr "VLAN %d(%s)的交换机端口:%s" msgid "Switched off" msgstr "已关闭" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1176 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2288 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2289 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1204 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2339 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2340 msgid "System" msgstr "系统" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3606 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3657 msgid "System settings" msgstr "系统设置" @@ -1822,19 +1822,19 @@ msgid "Telnet (port 23)" msgstr "Telnet(端口 23)" #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:76 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2320 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2371 msgid "Temperature %s" msgstr "温度 %s" -#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:848 +#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:864 msgid "Temporary (RAM)" msgstr "临时(RAM)" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2914 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2965 msgid "The bridge forgets a device after about five minutes without traffic" msgstr "设备约五分钟无流量后,网桥会将其遗忘" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2988 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3039 msgid "" "The bridges have learned so many addresses that listing them here would " "strain this device." @@ -1849,7 +1849,7 @@ msgstr "" "该处适用默认入站数据策略且为接受,因此互联网上的任何主机都能访问路由器本身。" "请将其加入 wan 区域,或在基本设置中将入站数据设为拒绝。" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3496 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3547 msgid "The device could not write /etc/config/vantage." msgstr "设备无法写入 /etc/config/vantage。" @@ -1875,7 +1875,7 @@ msgstr "" "无法读取接口列表,且默认入站数据策略为接受:不属于任何区域的上行接口将会开" "放。请在基本设置中将入站数据设为拒绝。" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3398 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3449 msgid "The name the device announces (WPS)" msgstr "设备广播的名称(WPS)" @@ -1887,7 +1887,7 @@ msgstr "唯一已测客户端高于 %s" msgid "The only measured client" msgstr "唯一已测客户端" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3025 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3076 msgid "The router this device sends its traffic to" msgstr "此设备发送流量所经的路由器" @@ -1903,7 +1903,7 @@ msgstr "保存的名称包含无效的配置节 ID" msgid "The uplinks could not be checked" msgstr "无法检查上行接口" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2017 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2068 msgid "This access point" msgstr "此接入点" @@ -1911,11 +1911,11 @@ msgstr "此接入点" msgid "This address family is in no zone: the default policies apply." msgstr "此地址族不属于任何区域:适用默认策略。" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2017 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2068 msgid "This device" msgstr "此设备" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3495 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3546 msgid "This device already stores %d names; remove some first." msgstr "此设备已存储 %d 个名称;请先移除部分。" @@ -1923,7 +1923,7 @@ msgstr "此设备已存储 %d 个名称;请先移除部分。" msgid "This device reports no radios" msgstr "此设备未报告任何射频" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3555 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3606 msgid "This line is down or has no default route now." msgstr "此线路已断开,或当前没有默认路由。" @@ -1933,7 +1933,7 @@ msgid "" "the firewall pages." msgstr "本页无法判断开放了哪些内容。请重新加载;如果仍无效,请检查防火墙页面。" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3503 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3554 msgid "This radio is not reported any more." msgstr "驱动不再报告此射频。" @@ -1941,23 +1941,23 @@ msgstr "驱动不再报告此射频。" msgid "This setup is beyond what this overview checks" msgstr "此配置超出本概览的检查范围" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3286 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3337 msgid "This station is not associated right now." msgstr "此站点当前未关联。" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1175 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2400 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2402 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3325 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1203 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2451 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2453 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3376 msgctxt "vantage" msgid "Throughput" msgstr "吞吐量" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1375 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1424 msgid "Throughput chart" msgstr "吞吐量图表" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2393 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2444 msgid "Throughput per link; arrow keys read the values over time" msgstr "每条链路的吞吐量;方向键可查看历史值" @@ -1966,38 +1966,38 @@ msgctxt "vantage" msgid "TiB" msgstr "TiB" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3332 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3383 msgid "To device" msgstr "到设备" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3046 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3081 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3134 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3097 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3132 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3185 msgid "To device %s, from device %s" msgstr "到设备 %s,来自设备 %s" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3073 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3124 msgid "To device: %s" msgstr "到设备:%s" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3569 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3620 msgid "To network" msgstr "发往网络" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2987 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3038 msgid "Too many devices to list" msgstr "设备过多,无法列出" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1176 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3098 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3099 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1204 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3149 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3150 msgctxt "vantage" msgid "Top talkers" msgstr "流量大户" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3345 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3375 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3515 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3396 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3426 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3566 msgid "Traffic" msgstr "流量" @@ -2015,16 +2015,16 @@ msgstr "" "未被任何转发覆盖的互联网流量仍会到达你的其他网络。请在基本设置中将转发设为拒" "绝。" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2013 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2064 msgid "Traffic to the internet goes through %s, a tunnel over this line" msgstr "去往互联网的流量经过 %s,一条在此线路上运行的隧道" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3578 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3629 msgid "Tunnel" msgstr "隧道" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2679 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3522 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2730 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3573 msgid "Tx power" msgstr "发射功率" @@ -2036,16 +2036,16 @@ msgstr "" "已安装 UPnP:它运行时,你网络中的设备可以自行开放端口,这些端口不会在此列出。" #: luci-app-vantage/htdocs/luci-static/resources/vantage/names.js:272 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3285 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3336 msgctxt "vantage" msgid "Unknown device" msgstr "未知设备" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3494 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3545 msgid "Unknown device type" msgstr "未知设备类型" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3503 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3554 msgid "Unknown radio" msgstr "未知射频" @@ -2060,14 +2060,14 @@ msgid "Unnamed rule" msgstr "未命名的规则" #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:57 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2667 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3519 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3573 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2718 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3570 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3624 msgctxt "vantage" msgid "Up" msgstr "已上线" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2313 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2364 msgid "Up %s" msgstr "已运行 %s" @@ -2076,25 +2076,25 @@ msgstr "已运行 %s" #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:49 #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:51 #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:57 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2426 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3558 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2477 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3609 msgctxt "vantage" msgid "Uplink" msgstr "上行链路" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2426 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2427 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2477 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2478 msgid "Uplink %s" msgstr "上行链路 %s" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3551 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3562 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3602 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3613 msgid "Uplink · %s" msgstr "上行链路 · %s" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2406 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2502 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2576 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2457 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2553 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2627 msgid "Upload" msgstr "上传" @@ -2102,21 +2102,21 @@ msgstr "上传" msgid "Upload (from clients)" msgstr "上传(来自客户端)" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3347 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3398 msgctxt "vantage" msgid "Uploaded" msgstr "已上传" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3026 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3077 msgid "Upstream" msgstr "上游" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3612 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3663 msgid "Uptime" msgstr "运行时间" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3027 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3379 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3078 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3430 msgid "VLAN %d" msgstr "VLAN %d" @@ -2261,11 +2261,11 @@ msgstr "WPA3 个人版" msgid "WPS" msgstr "WPS" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3355 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3406 msgid "WPS device name" msgstr "WPS 设备名称" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1674 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1725 msgid "Warning" msgstr "警告" @@ -2279,7 +2279,7 @@ msgid "Weak" msgstr "弱" #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:157 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2842 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2893 msgid "Weak signal" msgstr "信号弱" @@ -2302,27 +2302,27 @@ msgstr "开放内容取决于 firewall4 如何处理 %s,本页无法跟踪。 #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:106 #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:108 #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:110 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2896 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2947 msgid "Wi-Fi" msgstr "Wi-Fi" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1958 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2896 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2009 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2947 msgid "Wired" msgstr "有线" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1176 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3145 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3146 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1204 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3196 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3197 msgctxt "vantage" msgid "Wireless networks" msgstr "无线网络" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3305 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3356 msgid "Wireless settings" msgstr "无线设置" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3394 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3445 msgid "Your name for this device" msgstr "你为此设备设置的名称" @@ -2360,15 +2360,15 @@ msgstr "任意区域" msgid "any zone (default policy)" msgstr "任意区域(默认策略)" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2673 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2724 msgid "auto" msgstr "自动" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3343 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3394 msgid "before this page opened" msgstr "打开本页之前" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3372 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3423 msgid "behind %s, VLAN %d" msgstr "位于 %s 之后,VLAN %d" @@ -2377,16 +2377,16 @@ msgctxt "vantage" msgid "bit/s" msgstr "bit/s" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2534 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2585 msgid "collecting · %s of data" msgstr "采集中 · 已采集 %s 数据" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2534 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2585 msgid "collecting · first samples" msgstr "采集中 · 首个样本" #: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:251 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2514 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2565 msgid "collecting…" msgstr "采集中…" @@ -2398,18 +2398,18 @@ msgstr "自定义规则文件" msgid "dBm" msgstr "dBm" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1877 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2015 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:1928 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2066 msgid "default route" msgstr "默认路由" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2015 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2066 msgid "default route via %s" msgstr "默认路由经由 %s" #: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:385 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3030 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3371 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3081 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3422 msgid "down" msgstr "已断开" @@ -2418,7 +2418,7 @@ msgctxt "vantage" msgid "drop" msgstr "丢弃" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3424 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3475 msgctxt "vantage" msgid "e.g. Living room TV" msgstr "例如:客厅电视" @@ -2444,7 +2444,7 @@ msgstr "来自任意地址" msgid "from anywhere except %s" msgstr "来自除 %s 外的任意地址" -#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:366 +#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:376 msgid "full duplex" msgstr "全双工" @@ -2452,13 +2452,13 @@ msgstr "全双工" msgid "gateway %s" msgstr "网关 %s" -#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:366 +#: luci-app-vantage/htdocs/luci-static/resources/vantage/model.js:376 msgid "half duplex" msgstr "半双工" #: luci-app-vantage/htdocs/luci-static/resources/vantage/fmt.js:62 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2035 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3172 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2086 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3223 msgid "idle" msgstr "空闲" @@ -2500,14 +2500,14 @@ msgctxt "vantage" msgid "kbit/s" msgstr "kbit/s" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2298 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2533 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2349 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2584 msgid "last 5 min" msgstr "最近 5 分钟" #: luci-app-vantage/htdocs/luci-static/resources/vantage/insight.js:175 #: luci-app-vantage/htdocs/luci-static/resources/vantage/wifi.js:104 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3075 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3126 msgid "legacy" msgstr "旧版" @@ -2519,45 +2519,45 @@ msgstr "已连接" msgid "mDNS" msgstr "mDNS" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3396 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3447 msgid "mDNS announcement" msgstr "mDNS 广播" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3327 #: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3378 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3429 msgid "measuring" msgstr "测量中" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2509 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2560 msgid "measuring…" msgstr "测量中…" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2490 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2541 msgctxt "vantage" msgid "no data" msgstr "无数据" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2008 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2059 msgid "no uplink" msgstr "无上行链路" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3353 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3404 msgid "not known to this AP" msgstr "此 AP 未识别" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3385 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3436 msgid "not known to this device" msgstr "此设备未识别" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3525 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3576 msgid "not reported (driver placeholder %s)" msgstr "未报告(驱动占位值 %s)" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3336 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3387 msgid "not reported by the driver" msgstr "驱动未报告" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2530 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2581 msgctxt "vantage" msgid "now" msgstr "现在" @@ -2566,16 +2566,16 @@ msgstr "现在" msgid "now %s down, %s up" msgstr "当前下行 %s,上行 %s" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3334 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3385 msgid "older than the radio (%s)" msgstr "比射频支持的标准更旧(%s)" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2507 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2558 msgctxt "vantage" msgid "peak" msgstr "峰值" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2299 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2350 msgid "per core" msgstr "每核" @@ -2584,11 +2584,11 @@ msgctxt "vantage" msgid "port forward" msgstr "端口转发" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3321 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3372 msgid "range %s … %s" msgstr "范围 %s … %s" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3568 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3619 msgid "received on %s" msgstr "在 %s 上接收" @@ -2601,7 +2601,7 @@ msgstr "拒绝" msgid "router port %s" msgstr "路由器端口 %s" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3569 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3620 msgid "sent on %s" msgstr "在 %s 上发送" @@ -2609,12 +2609,12 @@ msgstr "在 %s 上发送" msgid "short GI" msgstr "短 GI" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3049 -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3380 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3100 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3431 msgid "static entry" msgstr "静态条目" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3373 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3424 msgid "switch port %s" msgstr "交换机端口 %s" @@ -2626,11 +2626,11 @@ msgstr "已关闭" msgid "the covered subnets of zone %s" msgstr "区域 %s 覆盖的子网" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3377 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3428 msgid "the port in total: %s" msgstr "该端口合计:%s" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3372 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:3423 msgid "the port: %s" msgstr "端口:%s" @@ -2646,7 +2646,7 @@ msgstr "此路由器" msgid "traffic rule" msgstr "通信规则" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2020 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2071 msgid "up %s" msgstr "已运行 %s" @@ -2663,7 +2663,7 @@ msgstr "区域转发" msgid "zones %s and %s if they share a device" msgstr "区域 %s 和 %s(若两者共用设备)" -#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2527 +#: luci-app-vantage/htdocs/luci-static/resources/view/vantage/overview.js:2578 msgid "−%d min" msgstr "−%d 分钟" diff --git a/luci-theme-vantage/Makefile b/luci-theme-vantage/Makefile index 586153063..c7e5ce2f4 100644 --- a/luci-theme-vantage/Makefile +++ b/luci-theme-vantage/Makefile @@ -10,8 +10,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-theme-vantage -PKG_VERSION:=1.0.15 -PKG_RELEASE:=34 +PKG_VERSION:=1.0.16 +PKG_RELEASE:=35 PKG_LICENSE:=GPL-3.0-or-later LUCI_TITLE:=Vantage theme diff --git a/luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js b/luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js index 18673690d..b49a0c705 100644 --- a/luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js +++ b/luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js @@ -1430,7 +1430,7 @@ return baseclass.extend({ ev.preventDefault(); if (pal.items[pal.active]) this.runEntry(pal.items[pal.active].e); break; - case 'Escape': ev.preventDefault(); this.closePalette(); break; + case 'Escape': ev.preventDefault(); ev.stopPropagation(); this.closePalette(); break; case 'Tab': ev.preventDefault(); break; } }, @@ -1495,7 +1495,8 @@ return baseclass.extend({ setLook(name, value, apply) { const k = LOOK[name]; const pending = this.lookPending || (this.lookPending = {}); - if (k.ok.indexOf(value) < 0 || pending[name] === value) return; + const failed = this.lookFailed || (this.lookFailed = {}); + if (k.ok.indexOf(value) < 0 || (pending[name] === value && !failed[name])) return; if (this.lookState() !== 'yes') { storageSet(k.own, value); storageSet(k.before, value); @@ -1504,7 +1505,7 @@ return baseclass.extend({ } const seqs = this.lookSeqs || (this.lookSeqs = {}), seq = seqs[name] = (seqs[name] || 0) + 1; pending[name] = value; - this.lookFailed = false; + delete failed[name]; apply(); const latest = () => seqs[name] === seq; this.lookChain = (this.lookChain || Promise.resolve()).then(() => { @@ -1516,10 +1517,15 @@ return baseclass.extend({ storageSet(LOOK_SYNC, JSON.stringify({ name: name, value: value, at: Date.now() })); storageSet(k.before, value); if (latest()) delete pending[name]; - }, () => { if (latest()) this.lookFailed = true; }); + }, () => { if (latest()) failed[name] = true; }); }).then(apply); }, + /* a save that failed and wasn't done again: its pick stays shown, with a note */ + lookFailing() { + return Object.keys(this.lookFailed || {}).length > 0; + }, + themePref() { return this.lookPref('theme'); }, @@ -1605,8 +1611,12 @@ return baseclass.extend({ if (ev.key === LOOK_SYNC) { let m = null; try { m = JSON.parse(ev.newValue); } catch (e) {} - if (m && Object.prototype.hasOwnProperty.call(LOOK, m.name) && LOOK[m.name].ok.indexOf(m.value) >= 0) + if (m && Object.prototype.hasOwnProperty.call(LOOK, m.name) && LOOK[m.name].ok.indexOf(m.value) >= 0) { setLookAttr(this.root, m.name, m.value); + /* saved there: a pick that failed here is over */ + if (this.lookPending) delete this.lookPending[m.name]; + if (this.lookFailed) delete this.lookFailed[m.name]; + } } this.applyTheme(); this.applyBackdrop(); @@ -1641,11 +1651,14 @@ return baseclass.extend({ scrolling the page close it. */ /* one of Appearance's radio groups */ appearGroup(name, legend, opts, set) { + const look = { 'vt-colour': 'theme', 'vt-login': 'login', 'vt-backdrop': 'backdrop' }[name]; return E('fieldset', { 'class': 'vt-appear-set' }, [ E('legend', {}, [ legend ]), E('div', { 'class': 'vt-appear-track' }, opts.map(o => { const input = E('input', { 'type': 'radio', 'name': name, 'value': o[0] }); input.addEventListener('change', () => { if (input.checked) set(o[0]); }); + /* the checked option fires no change: after a failed save, this is the retry */ + input.addEventListener('click', () => { if (input.checked && (this.lookFailed || {})[look]) set(o[0]); }); return E('label', { 'class': 'vt-appear-opt' }, [ input ].concat(o[2] ? [ svg(o[2], 16) ] : [], [ E('span', {}, [ o[1] ]) ])); })) ]); @@ -1698,7 +1711,7 @@ return baseclass.extend({ const state = this.lookState(); const note = state === 'relogin' ? _('Log in again to save Appearance for every browser.') : state !== 'yes' ? _('Saved in this browser only.') - : this.lookFailed ? _('Could not save this for every browser. Try again.') : ''; + : this.lookFailing() ? _('Could not save this for every browser. Try again.') : ''; if (this.appearNote) { if (this.appearNote.textContent !== note) this.appearNote.textContent = note; this.appearNote.hidden = !note; diff --git a/luci-theme-vantage/po/templates/vantage-theme.pot b/luci-theme-vantage/po/templates/vantage-theme.pot index 719ca5668..89189ece1 100644 --- a/luci-theme-vantage/po/templates/vantage-theme.pot +++ b/luci-theme-vantage/po/templates/vantage-theme.pot @@ -70,7 +70,7 @@ msgstr "" #: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1313 #: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1316 #: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1320 -#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1664 +#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1677 #: luci-theme-vantage/ucode/template/themes/vantage/header.ut:132 msgctxt "vantage-theme" msgid "Appearance" @@ -80,7 +80,7 @@ msgstr "" msgid "Authorization Required" msgstr "" -#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1666 +#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1679 msgctxt "vantage-theme" msgid "Auto" msgstr "" @@ -150,7 +150,7 @@ msgstr "" msgid "Collapse navigation" msgstr "" -#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1665 +#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1678 msgid "Colour theme" msgstr "" @@ -173,11 +173,11 @@ msgctxt "vantage-theme" msgid "Continues in %s, then comes back here" msgstr "" -#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1701 +#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1714 msgid "Could not save this for every browser. Try again." msgstr "" -#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1666 +#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1679 msgctxt "vantage-theme" msgid "Dark" msgstr "" @@ -243,7 +243,7 @@ msgctxt "vantage-theme" msgid "Free" msgstr "" -#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1552 +#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1558 msgid "Grid packets" msgstr "" @@ -293,7 +293,7 @@ msgctxt "vantage-theme" msgid "Leaves unmatched unchanged" msgstr "" -#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1666 +#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1679 msgctxt "vantage-theme" msgid "Light" msgstr "" @@ -303,7 +303,7 @@ msgstr "" msgid "Log in" msgstr "" -#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1699 +#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1712 msgid "Log in again to save Appearance for every browser." msgstr "" @@ -313,7 +313,7 @@ msgid "Log out" msgstr "" #: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1316 -#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1668 +#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1681 msgid "Login background" msgstr "" @@ -332,8 +332,8 @@ msgstr "" msgid "Memory" msgstr "" -#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1948 -#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1965 +#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1961 +#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1978 msgctxt "vantage-theme" msgid "More actions" msgstr "" @@ -352,7 +352,7 @@ msgid "Network device table \"%h\"" msgstr "" #: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1320 -#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1657 +#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1670 msgid "Network path background" msgstr "" @@ -375,7 +375,7 @@ msgctxt "vantage-theme" msgid "No rules: %s" msgstr "" -#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1552 +#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1558 msgctxt "vantage-theme" msgid "None" msgstr "" @@ -462,7 +462,7 @@ msgctxt "vantage-theme" msgid "Rules table" msgstr "" -#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1700 +#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1713 msgid "Saved in this browser only." msgstr "" @@ -483,11 +483,11 @@ msgid_plural "Show %d more rules" msgstr[0] "" msgstr[1] "" -#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:2105 +#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:2118 msgid "Show full description" msgstr "" -#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:2094 +#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:2107 msgid "Show full help: %s" msgstr "" @@ -495,7 +495,7 @@ msgstr "" msgid "Skip to content" msgstr "" -#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1552 +#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1558 msgid "Starfield" msgstr "" diff --git a/luci-theme-vantage/po/zh_Hans/vantage-theme.po b/luci-theme-vantage/po/zh_Hans/vantage-theme.po index a451d451e..ea19dbddd 100644 --- a/luci-theme-vantage/po/zh_Hans/vantage-theme.po +++ b/luci-theme-vantage/po/zh_Hans/vantage-theme.po @@ -76,7 +76,7 @@ msgstr "任意数据包" #: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1313 #: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1316 #: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1320 -#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1664 +#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1677 #: luci-theme-vantage/ucode/template/themes/vantage/header.ut:132 msgctxt "vantage-theme" msgid "Appearance" @@ -86,7 +86,7 @@ msgstr "外观" msgid "Authorization Required" msgstr "需要授权" -#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1666 +#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1679 msgctxt "vantage-theme" msgid "Auto" msgstr "自动" @@ -156,7 +156,7 @@ msgstr "信道" msgid "Collapse navigation" msgstr "收起导航" -#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1665 +#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1678 msgid "Colour theme" msgstr "颜色主题" @@ -179,11 +179,11 @@ msgctxt "vantage-theme" msgid "Continues in %s, then comes back here" msgstr "转入 %s,随后返回此处" -#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1701 +#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1714 msgid "Could not save this for every browser. Try again." msgstr "无法为所有浏览器保存此设置,请重试。" -#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1666 +#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1679 msgctxt "vantage-theme" msgid "Dark" msgstr "深色" @@ -254,7 +254,7 @@ msgctxt "vantage-theme" msgid "Free" msgstr "空闲" -#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1552 +#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1558 msgid "Grid packets" msgstr "网格数据包" @@ -304,7 +304,7 @@ msgctxt "vantage-theme" msgid "Leaves unmatched unchanged" msgstr "未匹配流量保持原样" -#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1666 +#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1679 msgctxt "vantage-theme" msgid "Light" msgstr "浅色" @@ -314,7 +314,7 @@ msgstr "浅色" msgid "Log in" msgstr "登录" -#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1699 +#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1712 msgid "Log in again to save Appearance for every browser." msgstr "重新登录后即可为所有浏览器保存外观设置。" @@ -324,7 +324,7 @@ msgid "Log out" msgstr "退出登录" #: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1316 -#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1668 +#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1681 msgid "Login background" msgstr "登录背景" @@ -343,8 +343,8 @@ msgstr "伪装(NAT)" msgid "Memory" msgstr "内存" -#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1948 -#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1965 +#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1961 +#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1978 msgctxt "vantage-theme" msgid "More actions" msgstr "更多操作" @@ -363,7 +363,7 @@ msgid "Network device table \"%h\"" msgstr "网络设备表 \"%h\"" #: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1320 -#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1657 +#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1670 msgid "Network path background" msgstr "网络路径背景" @@ -386,7 +386,7 @@ msgctxt "vantage-theme" msgid "No rules: %s" msgstr "无规则:%s" -#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1552 +#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1558 msgctxt "vantage-theme" msgid "None" msgstr "无" @@ -473,7 +473,7 @@ msgctxt "vantage-theme" msgid "Rules table" msgstr "规则表" -#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1700 +#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1713 msgid "Saved in this browser only." msgstr "仅保存在此浏览器中。" @@ -493,11 +493,11 @@ msgid "Show %d more rule" msgid_plural "Show %d more rules" msgstr[0] "再显示 %d 条规则" -#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:2105 +#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:2118 msgid "Show full description" msgstr "显示完整描述" -#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:2094 +#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:2107 msgid "Show full help: %s" msgstr "显示完整帮助:%s" @@ -505,7 +505,7 @@ msgstr "显示完整帮助:%s" msgid "Skip to content" msgstr "跳至正文" -#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1552 +#: luci-theme-vantage/htdocs/luci-static/resources/menu-vantage.js:1558 msgid "Starfield" msgstr "星空" @@ -568,7 +568,9 @@ msgid "" "Vantage theme: save its Appearance (colour mode, login and network path " "backgrounds) and the dashboard's chart style and layouts for every browser, " "in /etc/config/vantage_theme only" -msgstr "Vantage 主题:为所有浏览器保存其外观设置(颜色模式、登录页与网络路径背景)以及仪表盘的图表样式和布局,仅写入 /etc/config/vantage_theme" +msgstr "" +"Vantage 主题:为所有浏览器保存其外观设置(颜色模式、登录页与网络路径背景)以" +"及仪表盘的图表样式和布局,仅写入 /etc/config/vantage_theme" #: luci-theme-vantage/htdocs/luci-static/resources/vantage-theme/nftflow.js:454 msgctxt "vantage-theme" diff --git a/mise/Makefile b/mise/Makefile index 62f6677b6..302c0e7ce 100644 --- a/mise/Makefile +++ b/mise/Makefile @@ -14,8 +14,8 @@ MISE_HASH_x86_64:=3832f39c325e343f81fe3d92b2447c5d1a5eea1bc85092bb7b6c2580622264 MISE_HASH_aarch64:=06186cfbfe947049b21d58575fb0ea800cc26ed1375f20f4b678cb3a9d679437 PKG_NAME:=mise -PKG_VERSION:=2026.11.2 -PKG_RELEASE:=51 +PKG_VERSION:=2026.11.3 +PKG_RELEASE:=52 PKG_SOURCE:=$(PKG_NAME)-v$(PKG_VERSION)-linux-$(MISE_ARCH)-musl.tar.gz PKG_SOURCE_URL:=https://github.com/jdx/mise/releases/download/v$(PKG_VERSION)/ PKG_HASH:=skip diff --git a/nexttrace/Makefile b/nexttrace/Makefile index caff631af..b24116f2b 100644 --- a/nexttrace/Makefile +++ b/nexttrace/Makefile @@ -1,8 +1,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=nexttrace -PKG_VERSION:=1.7.5 -PKG_RELEASE:=18 +PKG_VERSION:=1.7.6 +PKG_RELEASE:=19 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/nxtrace/NTrace-core/tar.gz/v$(PKG_VERSION)? diff --git a/pcat-manager/Makefile b/pcat-manager/Makefile index 49d6c2ec1..8af5dad98 100644 --- a/pcat-manager/Makefile +++ b/pcat-manager/Makefile @@ -1,51 +1,47 @@ +# +# This is free software, licensed under the GNU General Public License v2. +# See /LICENSE for more information. +# + include $(TOPDIR)/rules.mk PKG_NAME:=pcat-manager -PKG_RELEASE:=5 +PKG_RELEASE:=6 PKG_SOURCE_PROTO:=git -PKG_SOURCE_URL:=https://github.com/photonicat/rockchip_rk3568_pcat_manager.git -PKG_SOURCE_DATE:=2023-11-13 +PKG_SOURCE_DATE:=2023-03-17 +PKG_SOURCE_URL:=https://github.com/photonicat/rockchip_rk3568_pcat_manager PKG_SOURCE_VERSION:=0e61a4abf35c423420310311f1b2b059deba0cac PKG_MIRROR_HASH:=skip -PKG_LICENSE:=GPL-3.0-only +PKG_LICENSE:=GPL-3.0 PKG_LICENSE_FILES:=LICENSE -PKG_FLAGS:=nonshared -PKG_BUILD_PARALLEL:=1 -PKG_INSTALL:=1 - include $(INCLUDE_DIR)/package.mk -include $(INCLUDE_DIR)/nls.mk include $(INCLUDE_DIR)/meson.mk define Package/pcat-manager SECTION:=utils CATEGORY:=Utilities - TITLE:=PCat System Manager - DEPENDS:=@TARGET_rockchip_armv8 +glib2 +libgpiod +libjson-c +libusb-1.0 @BROKEN + TITLE:=photonicat watchdog URL:=https://photonicat.com + DEPENDS:=+glib2 +libgpiod +libjson-c +libusb-1.0 endef define Package/pcat-manager/description - PCat Manager is a system service for power management - and modem controlling. -endef - -define Package/pcat-manager/conffiles -/etc/pcat-manager-userdata.conf + Photonicat Manager is a system service for + power management and modem controlling. endef define Package/pcat-manager/install - $(INSTALL_DIR) $(1)/usr/sbin - $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/pcat-manager $(1)/usr/sbin/pcat-manager - - $(INSTALL_DIR) $(1)/etc - $(INSTALL_CONF) $(PKG_BUILD_DIR)/conf/pcat-manager.conf.sample $(1)/etc/pcat-manager.conf - + $(INSTALL_DIR) $(1)/etc/ + $(INSTALL_CONF) ./files/pcat-manager.conf $(1)/etc $(INSTALL_DIR) $(1)/etc/init.d $(INSTALL_BIN) ./files/pcat-manager.init $(1)/etc/init.d/pcat-manager + $(INSTALL_DIR) $(1)/usr/bin + $(INSTALL_BIN) \ + $(PKG_INSTALL_DIR)/usr/bin/pcat-manager \ + $(1)/usr/bin/pcat-manager endef $(eval $(call BuildPackage,pcat-manager)) diff --git a/pcat-manager/files/pcat-manager.conf b/pcat-manager/files/pcat-manager.conf new file mode 100644 index 000000000..454e6bb8e --- /dev/null +++ b/pcat-manager/files/pcat-manager.conf @@ -0,0 +1,28 @@ +[Hardware] +GPIOModemPowerChip=gpiochip4 +GPIOModemPowerLine=18 +GPIOModemPowerActiveLow=0 +GPIOModemRFKillChip=gpiochip4 +GPIOModemRFKillLine=20 +GPIOModemRFKillActiveLow=1 +GPIOModemResetChip=gpiochip0 +GPIOModemResetLine=8 +GPIOModemResetActiveLow=0 + +[PowerManager] +SerialDevice=/dev/ttyS0 +SerialBaud=115200 +AutoShutdownVoltageGeneral=0 +AutoShutdownVoltageLTE=0 +AutoShutdownVoltage5G=3600 +LEDHighVoltage=0 +LEDMediumVoltage=0 +LEDLowVoltage=0 +LEDWorkLowVoltage=0 +StartupVoltage=0 +ChargerLimitVoltage=0 +ChargerFastVoltage=0 +BatteryFullThreshold=4100 +BatteryDischargeTableNormal=4100;4040;3980;3920;3870;3820;3790;3770;3740;3680;3450 +BatteryDischargeTable5G=4050;3980;3930;3890;3850;3815;3780;3740;3700;3655;3610 +BatteryChargeTable=4100;4085;4067;4040;4003;3942;3880;3825;3780;3725;3600 diff --git a/pcat-manager/files/pcat-manager.init b/pcat-manager/files/pcat-manager.init index 1f647379f..e6a6b3829 100644 --- a/pcat-manager/files/pcat-manager.init +++ b/pcat-manager/files/pcat-manager.init @@ -1,12 +1,24 @@ #!/bin/sh /etc/rc.common -USE_PROCD=1 START=45 STOP=89 -start_service() { - procd_open_instance - procd_set_param command /usr/sbin/pcat-manager - procd_set_param respawn 3600 5 0 - procd_close_instance +start() { + service_start /usr/bin/pcat-manager -D +} + +stop() { + service_stop /usr/bin/pcat-manager +} + +restart() { + killall -USR1 pcat-manager + stop + start +} + +shutdown() { + touch /tmp/pcat-manager-shutdown.tmp + stop + rfkill block wwan 2>/dev/null || true }