diff --git a/honk/Makefile b/honk/Makefile index 3a51d1508..6130a7952 100644 --- a/honk/Makefile +++ b/honk/Makefile @@ -4,7 +4,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=honk PKG_VERSION:=0.0.1_beta82 -PKG_RELEASE:=2 +PKG_RELEASE:=3 # Raw upstream release tag. It keeps the dots in qualifiers (e.g. v0.0.1.beta.49) # because that is what the GitHub release asset name uses. @@ -67,7 +67,8 @@ define Package/honk/config endef define Package/honk/conffiles -/etc/honk/ +/etc/honk/config.dae +/etc/honk/config.d/ /etc/config/honk endef diff --git a/honk/files/config.d/api.dae b/honk/files/config.d/api.dae index b7af276bb..a6c825c39 100644 --- a/honk/files/config.d/api.dae +++ b/honk/files/config.d/api.dae @@ -6,7 +6,7 @@ experimental { enabled: true listen: '0.0.0.0:9527' secret: 'honk114514' - ui: '/etc/honk/dashboard' + ui: '/etc/honk/doona' config_write: true geosite_download_url: 'https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/release/geosite.dat' geoip_download_url: 'https://raw.githubusercontent.com/QiuSimons/geoip-moedove/refs/heads/main/geoip.dat' diff --git a/honk/files/config.dae b/honk/files/config.dae index 0cfcde79e..0db77a7dc 100644 --- a/honk/files/config.dae +++ b/honk/files/config.dae @@ -22,7 +22,7 @@ global { udp_check_dns: 'dns.google.com:53', '8.8.8.8', '2001:4860:4860::8888' } -# API access (native_api / clash_api) is configured separately in config.d/api.dae +# API access (native_api) is configured separately in config.d/api.dae # to allow web dashboard configuration without locking the config as read-only. # # Optional cache-file configuration: diff --git a/honk/files/honk.init b/honk/files/honk.init index 040156769..0bfd416b6 100644 --- a/honk/files/honk.init +++ b/honk/files/honk.init @@ -75,12 +75,12 @@ start_service() { set_chmod_640 cleanup_netns - hijack_resolv_conf + hijack_resolv_conf procd_open_instance "$CONF" procd_set_param env DAE_LOCATION_ASSET="/usr/share/v2ray" procd_set_param command "$PROG" --config "$config_file" - procd_set_param netdev "$HONK_HOST_IFACE" "$HONK_PEER_IFACE" + procd_set_param netdev "$HONK_HOST_IFACE" "$HONK_PEER_IFACE" procd_set_param limits core="unlimited" procd_set_param limits nofile="1000000 1000000" procd_set_param term_timeout 8 diff --git a/luci-app-honk/Makefile b/luci-app-honk/Makefile index 4e6d4c1aa..3b7007478 100644 --- a/luci-app-honk/Makefile +++ b/luci-app-honk/Makefile @@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-honk PKG_VERSION:=1.1.2 -PKG_RELEASE:=3 +PKG_RELEASE:=4 LUCI_TITLE:=LuCI support for honk LUCI_PKGARCH:=all diff --git a/luci-app-honk/po/zh_Hans/honk.po b/luci-app-honk/po/zh_Hans/honk.po index 683fbb5db..260c10e25 100644 --- a/luci-app-honk/po/zh_Hans/honk.po +++ b/luci-app-honk/po/zh_Hans/honk.po @@ -97,21 +97,9 @@ msgstr "未运行" msgid "Checking Dashboard and API configuration..." msgstr "正在检测控制面板与 API 配置..." -msgid "Checking Dashboard and Clash API configuration..." -msgstr "正在检测 Dashboard 与 Clash API 配置..." - -msgid "Enabling and restarting HONK..." -msgstr "正在开启并重启 HONK..." - -msgid "Enabling Clash API and restarting HONK..." -msgstr "正在启用 Clash API 并重启 HONK..." - msgid "Enabling Native API and restarting HONK..." msgstr "正在启用 Native API 并重启 HONK..." -msgid "One-click Enable Default Clash API" -msgstr "一键启用默认 Clash API 配置" - msgid "One-click Enable Default Native API" msgstr "一键启用默认 Native API 配置" @@ -124,20 +112,11 @@ msgstr "启用失败:" msgid "Unknown error" msgstr "未知错误" -msgid "Dashboard / Clash API Unconfigured" -msgstr "控制面板 / Clash API 未配置" - msgid "Dashboard / Native API Unconfigured" msgstr "控制面板 / Native API 未配置" -msgid "HONK has not enabled Clash API in its configuration file. Dashboard requires external controller port and dashboard UI path." -msgstr "HONK 尚未在配置文件中启用 Clash API。Dashboard 控制面板需要依赖 Clash API 提供的外部控制器端口与面板静态文件路径。" - -msgid "HONK has not enabled Native API in its configuration file. Doona requires native API listener and dashboard UI path." -msgstr "HONK 尚未在配置文件中启用 Native API。Doona 控制面板需要依赖 Native API 提供的监听端口与面板静态文件路径。" - -msgid "Please configure experimental.clash_api block in API Settings, and ensure external_controller and external_ui are configured." -msgstr "请在【API 设置】的配置文件中配置 experimental.clash_api 语法块,并确保配置了 external_controller 和 external_ui。" +msgid "HONK has not enabled Native API in its configuration file. Dashboard requires native API listener and dashboard UI path." +msgstr "HONK 尚未在配置文件中启用 Native API。控制面板需要依赖 Native API 提供的监听端口与面板静态文件路径。" msgid "Please configure experimental.native_api block in API Settings, and ensure listen and ui are configured." msgstr "请在【API 设置】的配置文件中配置 experimental.native_api 语法块,并确保配置了 listen 和 ui。" @@ -166,12 +145,6 @@ msgstr "开始下载并安装控制面板" msgid "%s UI Files Not Found" msgstr "未检测到 %s 面板文件" -msgid "Clash API is configured, but dashboard files are missing in the external UI directory. You can download and deploy it directly." -msgstr "Clash API 配置已就绪,但设定的外部控制面板目录中缺少面板文件。您可以点击下方按钮,使用 OpenWrt 原生轻量解压直接在线下载部署。" - -msgid "Target Directory (external_ui)" -msgstr "目标安装目录 (external_ui)" - msgid "Listen Address & Port" msgstr "监听地址与端口" @@ -188,7 +161,7 @@ msgid "HTTPS access detected:" msgstr "检测到当前通过 HTTPS 访问 LuCI:" msgid "Modern browsers may block HTTP iframe resources under HTTPS. If the dashboard fails to display, open it in a new tab." -msgstr "现代浏览器可能会阻止直接加载 HTTP 协议的 Clash API 面板。如无法正常显示,请点击右侧按钮在新标签页打开。" +msgstr "现代浏览器可能会阻止直接加载 HTTP 协议的控制面板。如无法正常显示,请点击右侧按钮在新标签页打开。" msgid "Open in New Tab" msgstr "在新标签页打开" @@ -196,9 +169,6 @@ msgstr "在新标签页打开" msgid "HONK service is currently not running:" msgstr "HONK 服务当前未运行:" -msgid "Clash API port (" -msgstr "Clash API 端口(" - msgid "Dashboard API port (" msgstr "Dashboard API 端口(" @@ -265,9 +235,6 @@ msgstr "关闭" msgid "Download error:" msgstr "触发下载异常:" -msgid "Configured (Hidden)" -msgstr "已设置 (隐藏)" - msgid "Not configured (Empty)" msgstr "未设置(留空)" @@ -337,9 +304,6 @@ msgstr "认证密钥已复制到剪贴板: " msgid "Token: " msgstr "认证密钥: " -msgid "Click or drag to select Token" -msgstr "点击或拖选认证密钥" - msgid "API Settings" msgstr "API 设置" diff --git a/luci-app-honk/root/etc/honk/dashboard/CHANGELOG.md b/luci-app-honk/root/etc/honk/dashboard/CHANGELOG.md deleted file mode 100644 index acbd0a6b5..000000000 --- a/luci-app-honk/root/etc/honk/dashboard/CHANGELOG.md +++ /dev/null @@ -1,330 +0,0 @@ -# Changelog - -This changelog follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [0.1.0-beta.8] - 2026-09-28 - -### Added - -- Each release attaches the eight honk-core builds of honk's `debug` pre-release, so honk does not have to be compiled: x86_64 and aarch64, glibc and musl, each with mimalloc or the system allocator. `honk-source-.tar.gz` holds the source of the honk commit they were built from and `HONK-SOURCE.txt` names that commit; `SHA256SUMS` lists every release asset except itself. The builds are attached until honk publishes a release with the native API. (#109, #117) -- Sign-in is a full page instead of a dialog over an empty window, with its own language menu and theme toggle. From 1024 pixels wide, a panel beside the form shows a construction scene that becomes a small Flappy Duck game when pressed. The form keeps working if the scene fails to load, and the scene is a still picture when reduced motion is requested. (#107, #114, #115) -- The public demo opens on the sign-in page with its account filled in: user name `demo`, password `demo`. (#107) -- The setup guide is a separate documentation site in English, Simplified Chinese and Traditional Chinese, maintained in [Zakkaus/doona-docs](https://github.com/Zakkaus/doona-docs). Sign-in and Settings > About link to it; the sign-in hint for a backend without the native API and the read-only outbound mode help open its troubleshooting sections. (#100) -- The documentation site has step-by-step installation pages for Debian or Ubuntu, Fedora or RHEL, Arch Linux, Gentoo, OpenWrt and other systems, and follows the layout of the React Spectrum docs. Its search ranks titles first and works from the keyboard, the 404 page is in the reader's language, and Chinese pages use the Spectrum CJK type scale. Colours are correct in Safari 17.0 to 17.4, and links into doona's source work when the pages are read on GitHub. The pages are shared under CC BY 4.0, and the header links to the demo. (Zakkaus/doona-docs#1, #2, #3, #6, #11, #13, #14) -- Help buttons explain unclear states and terms on Overview, Connections, Configuration, Rules, Policies, DNS, Events, Logs and Settings. (#98) -- Overview lists the features that are off in a full-width card, one row per cause. Where one applies, a row explains the cause, gives the configuration lines that turn the feature on, or links to the documentation or Settings. The rule list, validation and closing connections are listed when the backend lacks them. The count on Activity links to the card. (#102) -- A disabled action says why in a line beside its buttons, visible at every width, where before the reason was only in a tooltip that touch does not open, or missing. This covers Configuration, Activity, Policies, Rules, DNS, Nodes and Settings, and the dialogs for new rules, subscriptions and nodes. Screen readers read the line as the button's description. (#97, #127, #130, #133) -- Connections can collapse and expand each group, or all groups at once, while the list is grouped. (#93) -- On Events and Logs, pressing a row, or Enter or Space on it, shows its full text below the table, so text cut on a phone can be read; pressing the row again hides it. (#105) -- The demo shows its error states when opened with `?scenario=faults`; `?scenario=` returns to the healthy demo. (#103) - -### Changed - -- The demo behaves as a healthy honk: it seeds no failures, and switching the outbound mode works. (#103) -- The Overview header and the Activity status add a degraded or failed datapath to the engine state, in the matching tone. The Overview header links to the Datapath card. (#102) -- Backend features on Overview lists only the features that are on. (#102) -- Configuration shows each source's path once. A read-only source has one badge naming the reason (generated, subscription, read-only, contains secrets, redacted or text not returned) and no Validate button; when configuration writes are off, help beside the badge says how to turn them on. Every source with text has one line saying what it is and what can be done with it, and all source editors start at the same height. (#101) -- A writable source is edited in place, without an Edit button. Typing, pasting or tapping in a read-only source shows one notice with the reason. (#101) -- When a save finds that the file changed on disk, the source card, the modules and the quick setup show an alert and wait until the draft is kept over the change or discarded; previously, the draft was applied to the changed file without warning. A module draft carries over on its own when the change is outside its section. (#118) -- Read-only code editors show no caret or current-line highlight; the line a source was opened at keeps a marker. (#97) -- The must switch in the new-rule dialog reads Lock this outbound, with a line explaining what a locked outbound skips. (#91) -- The outbound mode card and the Arrange review label their button Apply and reload, as Configuration does, since all three write the configuration and reload it. (#126) -- Below 600 pixels wide, tables keep every column at its minimum width and scroll sideways, instead of dropping columns until the rest fit. Wider screens drop columns as before. (#128) -- On Policies, a large node group lays out its tiles like a small group: the same columns, tile width and edges, with room left for the scrollbar. (#125) -- The busiest-host tile on Connections takes two shares of its row. A host that still does not fit keeps its end visible and shows the full name on hover, focus or tap. (#99) -- When a subscription refresh leaves the datapath degraded, the notice says that new connections proxied through userspace are refused and points to Overview. (#98) -- The Chinese token sign-in heading and missing-token message use formal wording. (#107) -- README screenshots are rendered by the documentation site on each deploy and are no longer stored in the repository. (Zakkaus/doona-docs#6) -- NOTICE, `REUSE.toml`, the READMEs and the `OFL.txt` shipped with the fonts name Adobe as the copyright holder of Noto Sans TC and SC. The documentation site credits Adobe for the Spectrum icons, fonts and design. (#122, Zakkaus/doona-docs#12) - -### Fixed - -- Errors with the reused codes `invalid_request`, `unsupported_value` and `state_conflict` keep the backend's own message beside the summary. (#94) -- A backend that never answers no longer leaves a page loading. API reads fail when the headers, or the next part of the body, take more than 15 seconds to arrive, and writes when they take more than 30; the event and log streams keep their own limit. Sign-in and sign-out time out the same way. After a read timeout, Retry fetches the data again. A write timeout says the change may have been applied and asks for a reload before trying again. (#104, #118) -- Apply and reload is disabled when a refetch makes the open source read-only. Undo after Cancel no longer restores the discarded edits. (#101, #106) -- The must switch in the new-rule dialog aligns with the outbound picker. (#90) -- A connection opened by its link unfolds its collapsed group, and so does a selected connection that moves into another folded group. Outbound groups no longer unfold when the language changes, and an outbound named like a built-in label keeps its own group. (#93, #118) -- When an ordinary rule precedes the must rules in the routing block, doona's refusal to switch the outbound mode says to move the must rules to the top and names the rule. (#103) -- Trace field errors wait until a field is filled. Chrome no longer reports `apple-mobile-web-app-capable` as deprecated. (#103) -- Help buttons and status lights align with the centre of their text. (#98) -- Logs and the flow distribution say when the configuration forbids their recorder. Test all no longer blames TCP probe support when it is unavailable for another reason. (#98) -- The minimum password length applies only to the administrator password created at setup, not to signing in. (#107) -- A failed suspend or resume shows its translated message again (`lifecycle_failed`). (#115) -- Overview no longer gives honk's causes and fixes for resources an older backend leaves out of its capabilities; they are listed as not provided by the build. When another engine gives no reason, a read-only main configuration no longer claims one. (#118, #132) -- A read-only source is labelled as containing secrets only when its text defines a `native_api` or `clash_api` block; otherwise it shows as read-only. (#118) -- The health-check URL dialog sends only the fields that were edited and offers the check settings that the group's `mutable_config` lists. A save no longer replaces a change another client made to the same field: it fails as a conflict and the dialog stays open. (#106, #118, #132) -- The new-source dialog warns that a path is not included only when no loaded file includes it, and resolves include patterns from the main configuration's directory, as honk does. (#118, #131) -- A geodata update whose result cannot be confirmed is shown as unknown, not as failed. (#106) -- A new source whose read-back fails stays created, and a late result no longer reaches a reopened new-file dialog. A selected diagnostic stays selected across polls. In the demo, a new source whose name has a space, a non-ASCII character or `?` opens once created. (#106) -- The demo refuses source writes over honk's size limits, a second create of the same path and a create past the source limit, as honk does. Demo sign-in works over plain HTTP on a LAN address, where browsers do not provide `crypto.randomUUID`. (#118, #124, #131) -- The DNS device filter error appears under its field and is read with it by screen readers. The DNS log's time column fits relative times. (#105, #106) -- The note on the Arrange tab of Policies no longer mentions an Add menu on each row; it points to Add to group below the list and to Review and apply. (#111) - -### For contributors - -- CONTRIBUTING describes the architecture and the import allow-lists, and lint enforces the engine boundary. (#123) -- `tools/honk-commit.txt` pins the honk commit a release bundles; the release workflow fails unless honk's `debug` release, its target and its source tag name that commit. (#117) -- The API contract is pinned at api-standardize 3640713. (#116) -- The store, the configuration page, the engine explanations, the sign-in outcomes and the connection test were split into smaller modules, and unused code and duplicate strings were removed, without changing behaviour. (#108, #119, #120, #121, #126, #129) - -## [0.1.0-beta.7] - 2026-09-27 - -### Added - -- Overview shows the backend process's CPU usage. -- Connections shows each connection's upload and download rates, calculated from successive samples. -- Policies lets you edit a group's health-check URL. -- Configuration can create a new source file when the backend supports it. -- A failed first fetch of a new subscription offers Retry in its toast; a new build offers Reload. Toasts with actions remain until dismissed or used. -- Settings lets you place notifications at the top or bottom, centred or aligned to the end. - -### Changed - -- Toasts keep their status colours and separate the summary from backend details. Show all and action buttons share a footer on desktop and phones; request IDs stay in the console instead of the toast. -- Desktop tabs are 34 pixels tall, name lists use consistent sorting, and equally sized connection filter groups keep a stable order. -- Configuration shows module sections from read-only includes and groups repeated diagnostics. -- README screenshots reflect the current desktop and phone layouts in all three languages. - -### Fixed - -- Configuration completion reads groups from every source and preserves each group header's original quotes. Quick setup preserves the same names in generated routing rules. -- After a conflicting source save, the editor keeps the draft and uses the refreshed source as its next save's base. Reloading clears validation results from the previous generation. -- Activity finds the outbound mode line by its marker rather than its position in the file. -- DNS rejects an invalid device address without dropping the last valid filter. Closing one connection no longer clears another connection opened while the request was pending. -- The routing map waits for its nodes before settling. Connection rates discard baselines from lists no longer held by the store. -- Retrying a refused event stream reconnects it, recovered capability errors clear, and a refused first-page request is not repeated without a cursor. -- Settings can sign in again after signing out, distinguishes a rejected token from a connection failure, and reports unavailable session storage as a storage error. -- Geodata preset changes ask for confirmation when they lack categories used by the configuration. Operation errors identify the failed stage when the backend provides it. -- Paused logs show the held record count. Gap summaries omit a zero dropped count, and Activity notice summaries align in one column. -- Long backend versions end with an ellipsis; available backend capabilities use a status dot. -- A pending keyboard navigation prefix clears when the filter shortcut has no field to focus, and near-viewport content keeps its observer across callback changes. - -## [0.1.0-beta.6] - 2026-09-26 - -### Added - -- The side navigation shows the connected backend's name and version with a connection light. It opens a card with the connection state, API, build and backend URL, and buttons for About doona and for editing the backend in Settings. On phones the same card opens from the overflow menu. -- About states that doona talks only to the backend it is connected to and sends nothing elsewhere. -- Settings offers a mirrored layout for left-handed use. -- A right-to-left language lays the page out right to left; adding one needs only its catalogue. -- Node latencies carry a green, yellow or red dot, which keeps the tone visible in palettes that show latency text in the body colour. -- Hovering or tapping an Activity sparkline shows the sample's value and time. -- On touch screens, a tap reveals the full text of a truncated table cell. - -### Changed - -- On phones, the Connections filter field takes the toolbar row and the other filters fold into one menu. -- On phones, the busiest connection takes a row of its own, and a host too long for it keeps its end visible. -- On small phones, key-value facts use two columns, truncated drawer values wrap, table row actions stay reachable, and the column resizer is wider. -- On phones, the Activity node picker stays inside its tile, custom geodata URL fields use the full width, and the subscription remove button stays on its row. -- On short landscape phones, the top bar scrolls away instead of covering the page. -- A tab bar that overflows scrolls the selected tab into view and fades its cut edge. -- The Arrange hint no longer says the tray is on the right when it is below the groups. -- Heatmap time marks are larger. -- Translations use one term per concept, the English copy is tidier, several mistranslations are corrected, and Simplified Chinese distinguishes blocking from DNS interception. -- CONTRIBUTING describes how to correct a translation and how to propose a new language. - -### Fixed - -- A routing trace simulates the first IPv4 and first IPv6 answer and shows the full resolution, instead of refusing a name with several addresses. -- Buffer overflow gaps no longer appear on the Activity home card; Events still lists them. -- A configuration write the backend accepted is reported as unknown, not failed, when the following status poll fails. -- The add-rule dialog preselects no outbound until the groups are read, instead of showing a wrong one. -- Donut charts with more than sixty slices no longer draw a slice with a negative angle. -- Chart tooltips are announced politely, so moving through a chart does not interrupt the screen reader. -- A gap summary that names no record starts at its reason instead of a dash. -- Chart tooltips stay inside their card on narrow phones. - -## [0.1.0-beta.5] - 2026-09-26 - -### Added - -- Settings shows geodata presets, custom source URLs, automatic updates, a download route and update status when the backend supports them. -- The Connections Traffic tab shows node latency, including P50 and P90 across nodes and which nodes carry current connections, when health data is available. -- When rules are writable, connection details can create a routing rule from the destination and apply it immediately or hold it for later. A matched-rule link opens the rule list. -- The add-subscription dialog offers the refresh interval, User-Agent and cache option when the backend advertises them. -- Navigation groups pages into Overview, Traffic, Routing and Settings hubs, with a bottom bar on phones. -- The top bar has a separate reload action and shows the number of held rules to apply. -- Settings shows manual installation steps for Safari on iPhone and iPad, and Safari 26 or later on macOS, when no install prompt is available. - -### Changed - -- Until a language is chosen, doona follows the browser's first language preference; a saved choice still takes precedence. -- The Connections and flow records tables show the final node or outbound instead of the full chain. Details and CSV export retain the chain, a tooltip shows the path when a group precedes the node, and a saved hidden column stays hidden. -- Geodata settings save as they change. Custom URLs remain editable when their initial values came from the configuration file; restarting honk can restore URLs named in that file. -- Segmented controls that do not fit become a picker instead of scrolling or clipping. -- On narrow Activity tiles, sparklines sit below the value and use the tile width. -- On phones, top-bar options use submenus, and longer page action groups move extra actions into a menu. -- Initial page loads download less shell code, and visitors using a real backend no longer download demo code for offline use. -- Activity charts download less JavaScript. In measured large-list scenarios, node searches and probes, connection sorting and selection, and log bursts use less scripting time. -- The README includes a page tour and phone guidance, and demo links point to https://demo.daeuniverse.org/. -- Internal restructuring of charts, shared components, routes and styles leaves the UI and behavior unchanged. - -### Fixed - -- Restart-only configuration refusals name the affected settings, and a file written without a successful reload is reported as unapplied. -- Repeated configuration conflicts explain when the file on disk differs from the running configuration. -- Runtime settings refresh after activation, and retries no longer replay writes without an idempotency key. Configuration validation and routing traces, which write nothing, still retry after a temporary refusal. -- DNS statistics stay usable when a log page is refused for size. Failed reads show one retryable error, and early-ended pages show how many records loaded without assuming why they ended. -- Close all handles connection sets above the backend's bulk limit in batches and reports connections left open when a batch stops. -- Configuration validation and writes report the advertised size limit before sending an oversized request. -- Relative times and traffic and memory chart windows use the backend host's observed time when it is available. -- Event and log streams reconnect after missed heartbeats. Gaps from expired cursors are marked, including in exported logs. -- Unknown operation and probe results are shown as unknown instead of failed or unreachable; new backend enum values are shown instead of blank labels. -- Policies, fallback groups, recorded log levels, delayed retries and degraded subscription refreshes are described according to the backend's actual behavior. Known backend error codes have translations. -- Module diagnostics stay in place while typing and when switching sections, and code scrolled sideways no longer shows through the line-number gutter. -- Password sign-in works with the backend's public discovery response. -- The demo's traffic history advances with time, so Activity charts retain a full recent window after a tab has been in the background. -- The demo keeps refused settings patches atomic, updates required geodata categories after configuration changes and redacts secret-bearing parts of geodata URLs. -- Connection rules can be placed before the earliest writable rule. Rule creation waits for groups to load, and the matched-rule link remains available on read-only configurations. -- A rule value containing parentheses or `&&` is refused, because honk would read it as rule syntax. -- Held rules cannot be discarded or applied concurrently while an apply is running. Written rules leave the pending list even if reload fails or its result is unknown, and partial results count every written rule. -- Geodata controls follow backend capabilities, including older backends without download-route settings. Custom URLs cannot be edited while saving, so later input is not lost. -- DNS cache usage is read again after a flush or deletion; an older cache walk cannot restore stale usage afterward. -- Chart arrow keys keep working after the data shrinks. The Outbound downloads chart has one named keyboard stop, and its tooltip no longer sits under the centre total. -- Keyboard focus stays on a segmented control when resizing switches it between buttons and a picker. -- On phones, the bottom bar and the hub page switcher replace the history entry, so Back no longer retraces every tap. -- An arrow key pressed right after a phone submenu opens is no longer undone. -- A popover opened near the right edge keeps its width instead of shifting while it is placed. -- Screen readers announce a retry wait once instead of every second. -- A failed node read shows an error in the Connections latency card instead of silently removing it. -- Table columns and page width stay steady while rows load, tables change layout or pages change height. -- Table lines, row hover and other quiet fills stay visible on cards in the Rosé Pine dark, Nord and Kary palettes, and touching a chart point no longer leaves a hover state stuck. - -## [0.1.0-beta.4] - 2026-09-24 - -### Added - -- dae text outside the editor is highlighted with the editor's colours, in policy arrangement, rules, the configuration wizard, configuration issues and flow details. - -### Changed - -- Release assets carry the upstream version without `v`, and each package keeps its own format's version inside: `0.1.0~beta.4-1` for deb, rpm and ipk, `0.1.0beta4` for Arch. -- The API contract is pinned to the upstream api-standardize `honk` branch, which now includes rule sources named by id, recorder modes, password sign-in and DNS cache usage. -- Package recipes list every architecture and declare the licences of the bundled npm packages; the Gentoo ebuild maps alpha, beta and rc versions to their tags. - -### Fixed - -- The outbound mode card shows the current mode, read-only with the reason, when the main configuration cannot be written. -- The dae highlighter no longer colours digits inside names as numbers and keeps an address or CIDR as one number. - -## [0.1.0-beta.3] - 2026-09-24 - -### Added - -- A public demo on GitHub Pages with sample data from the built-in mock backend. -- A clear message when an engine has no native API, instead of a token prompt. - -### Changed - -- Releases no longer include a node_modules archive; package recipes install the prebuilt tarballs. -- The READMEs and guides state which honk builds serve the native API and lead with password sign-in. -- Card values keep secondary facts on a caption line, cards in a row stay equal in height, and the capabilities list uses columns. - -### Fixed - -- An updated build could miss the page language for offline start. -- The Events page left out events received before it opened. -- Layout on tablets and phones for Overview, DNS, the routing map and other pages. -- Sticky table headers and overlays showed content through them in the glass palette. -- Untranslated datapath errors and the rule editor's must switch. - -## [0.1.0-beta.2] - 2026-09-24 - -### Added - -- Password setup, sign-in and sign-out for supported backends. -- Group membership editing and group ordering by drag or menu. -- DNS query analysis and cache status, node latency charts, connection traffic plots and log level heatmaps. -- Configuration module editing, recorder settings and links from module cards to their sources. - -### Changed - -- Show routing as a device-aware tree and open a rule editor from a flow. -- Keep data tabs mounted and use shared fact cards, labels and confirmation dialogs. -- Provide Alpine, OpenWrt, Gentoo, Nix and nfpm package recipes for this beta release. - -### Fixed - -- Keep source edits within their rules or fields and validate typed values before writing them. -- Preserve each tab's backend session, show failed reads in place and keep loading layouts stable. -- Report DNS cache usage by entry count and cache only the active language for offline use. - -### Performance - -- Load search, page code and language fonts when needed; reuse warm pages. -- Reduce idle polling and event refetches, retain unchanged results and virtualize longer lists. - -## [0.1.0-beta.1] - 2026-09-21 - -### Added - -- A virtualized connections table, flow graph, rule distribution and richer runtime views. -- A configuration editor with validation, diagnostics, quick setup and rule editing. -- Subscription and node management, live logs, backend settings and search across pages. -- Installable offline shell, multilingual messages and release archives with optional fonts. - -### Changed - -- Consolidate the interface around the native API and shared controls. -- Show retained flows, rule distribution, traffic history and backend capabilities in the product pages. -- Package the beta for distribution and verify archive checksums in the release workflow. - -### Fixed - -- Guard unsaved configuration changes and hidden routes, and keep streams reconnecting after interruptions. -- Match live backend resource limits and operation responses. - -### Performance - -- Load feature pages on demand and virtualize the connections table and long node menus. - -## [0.1.0-alpha.2] - 2026-09-15 - -### Added - -- A typed native API client, contract-backed mock and resource hooks. -- Connection and flow details, per-network policy selection, routing traces and live events. -- DNS cache actions, runtime controls, traffic history and outbound usage. -- More light and dark palettes, a glass theme and a validation page. - -### Changed - -- Replace fixture-backed page data with native API operations and capability-aware navigation. -- Keep the glass theme on a gradient wallpaper after trying photo wallpapers. - -### Fixed - -- Apply the saved palette before first paint and keep long menus from scrolling sideways. -- Show diagnostic reasons and error counts clearly in validation. - -### Performance - -- Filter and virtualize large node lists and load picker sections in pages. - -## [0.1.0-alpha.1] - 2026-09-15 - -### Added - -- An activity dashboard and a themed shell with Rosé Pine, Catppuccin and Nord palettes. -- Themed tables, forms, dialogs, tabs, detail cards and status displays across the initial pages. - -### Changed - -- Adapt the top bar, cards and controls to narrow screens. -- Align control sizes, links, separators and alert dialogs with the shared UI kit. - -### Fixed - -- Keep table columns and action cells visible and prevent cards and controls from overflowing. - -[0.1.0-beta.8]: https://github.com/Zakkaus/doona/compare/v0.1.0-beta.7...v0.1.0-beta.8 -[0.1.0-beta.7]: https://github.com/Zakkaus/doona/compare/v0.1.0-beta.6...v0.1.0-beta.7 -[0.1.0-beta.6]: https://github.com/Zakkaus/doona/compare/v0.1.0-beta.5...v0.1.0-beta.6 -[0.1.0-beta.5]: https://github.com/Zakkaus/doona/compare/v0.1.0-beta.4...v0.1.0-beta.5 -[0.1.0-beta.4]: https://github.com/Zakkaus/doona/compare/v0.1.0-beta.3...v0.1.0-beta.4 -[0.1.0-beta.3]: https://github.com/Zakkaus/doona/compare/v0.1.0-beta.2...v0.1.0-beta.3 -[0.1.0-beta.2]: https://github.com/Zakkaus/doona/compare/v0.1.0-beta.1...v0.1.0-beta.2 -[0.1.0-beta.1]: https://github.com/Zakkaus/doona/compare/v0.1.0-alpha.2...v0.1.0-beta.1 -[0.1.0-alpha.2]: https://github.com/Zakkaus/doona/compare/v0.1.0-alpha.1...v0.1.0-alpha.2 -[0.1.0-alpha.1]: https://github.com/Zakkaus/doona/releases/tag/v0.1.0-alpha.1 diff --git a/luci-app-honk/root/etc/honk/dashboard/LICENSE b/luci-app-honk/root/etc/honk/dashboard/LICENSE deleted file mode 100644 index f288702d2..000000000 --- a/luci-app-honk/root/etc/honk/dashboard/LICENSE +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/luci-app-honk/root/etc/honk/dashboard/LICENSES/Apache-2.0.txt b/luci-app-honk/root/etc/honk/dashboard/LICENSES/Apache-2.0.txt deleted file mode 100644 index 33666dddf..000000000 --- a/luci-app-honk/root/etc/honk/dashboard/LICENSES/Apache-2.0.txt +++ /dev/null @@ -1,73 +0,0 @@ -Apache License -Version 2.0, January 2004 -http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - -"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. - -"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. - -"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - -"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. - -"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. - -"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. - -"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). - -"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. - -"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." - -"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: - - (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. - - You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - -To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. \ No newline at end of file diff --git a/luci-app-honk/root/etc/honk/dashboard/NOTICE b/luci-app-honk/root/etc/honk/dashboard/NOTICE deleted file mode 100644 index b9077d2cb..000000000 --- a/luci-app-honk/root/etc/honk/dashboard/NOTICE +++ /dev/null @@ -1,14 +0,0 @@ -Adobe Spectrum icons -Copyright 2024 Adobe. All rights reserved. -The Spectrum SVG artwork in src/ui/icons is adapted from @react-spectrum/s2. -The SVG geometry is unchanged; fills use currentColor and React wrappers are local. -Licensed under the Apache License, Version 2.0: -https://www.apache.org/licenses/LICENSE-2.0 -Source: https://github.com/adobe/react-spectrum - -Noto Sans TC and Noto Sans SC -Copyright 2014-2021 Adobe (http://www.adobe.com/), with Reserved Font Name -'Source'. -The fonts in public/fonts are the Google Fonts subsets of Noto Sans TC and SC -from https://github.com/notofonts/noto-cjk. -Licensed under the SIL Open Font License, Version 1.1; see public/fonts/OFL.txt. diff --git a/luci-app-honk/root/etc/honk/dashboard/README.md b/luci-app-honk/root/etc/honk/dashboard/README.md deleted file mode 100644 index 51855d04f..000000000 --- a/luci-app-honk/root/etc/honk/dashboard/README.md +++ /dev/null @@ -1,138 +0,0 @@ -
- -doona - -# doona - -**Web UI for the [daeuniverse](https://github.com/daeuniverse) engines: nodes, groups, rules and the configuration, from a browser.** - -**[Live demo](https://demo.daeuniverse.org/)** / **[Documentation](https://zakkaus.github.io/doona-docs/en/)** - -English / [简体中文](README.zh-CN.md) / [繁體中文](README.zh-TW.md) - -[Install](#install) / [Pages](#pages) / [Page tour](#page-tour) / [On a phone](#on-a-phone) / [Development](#development) - -
- -doona is a static web UI for the native API the daeuniverse engines share: honk today, dae once it implements the same contract. The engine serves it itself or any web server does; it shows what the engine is doing and manages nodes, groups, routing rules and configuration files. - -[Try the demo with sample data](https://demo.daeuniverse.org/). To see the error states, open it with [`?scenario=faults`](https://demo.daeuniverse.org/?scenario=faults); `?scenario=` returns to the healthy demo. - -![The activity page](https://zakkaus.github.io/doona-docs/screenshots/en/activity-light.webp) - -
-Every palette - -Eleven palettes support light and dark modes; Rosé Pine and Catppuccin include multiple dark flavours. Use the palette picker in the top bar. - -Every palette in light and dark - -
- -## Theme gallery - -The activity page in four palettes. Select a palette and mode from the top bar. - -| Theme gallery | Light | Dark | -| ------------- | ---------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | -| Rosé Pine | ![Rosé Pine Light](https://zakkaus.github.io/doona-docs/screenshots/en/theme-rose-pine-light.webp) | ![Rosé Pine Dark](https://zakkaus.github.io/doona-docs/screenshots/en/theme-rose-pine-dark.webp) | -| Catppuccin | ![Catppuccin Light](https://zakkaus.github.io/doona-docs/screenshots/en/theme-catppuccin-light.webp) | ![Catppuccin Dark](https://zakkaus.github.io/doona-docs/screenshots/en/theme-catppuccin-dark.webp) | -| Nord | ![Nord Light](https://zakkaus.github.io/doona-docs/screenshots/en/theme-nord-light.webp) | ![Nord Dark](https://zakkaus.github.io/doona-docs/screenshots/en/theme-nord-dark.webp) | -| Glass | ![Glass Light](https://zakkaus.github.io/doona-docs/screenshots/en/theme-glass-light.webp) | ![Glass Dark](https://zakkaus.github.io/doona-docs/screenshots/en/theme-glass-dark.webp) | - -## Status - -doona targets the native API implemented by honk's `feat/native-api` branch; that API is not released yet. The contract it is built against is pinned in [SOURCE.md](contract/api-standardize/SOURCE.md). Backends that omit newer resource keys are accepted: doona fills those keys as unavailable. With no backend configured, a built-in mock supplies demo data; every screenshot here shows the mock. - -## Install - -doona needs honk's native API, which only the `debug` release of [Glassyiris/honk `feat/native-api`](https://github.com/Glassyiris/honk/tree/feat/native-api) provides so far. Release archives (`doona-.tar.gz`, the optional `doona-fonts-.tar.gz` with Noto Sans TC and SC, and `SHA256SUMS`) are attached to tags on the [releases page](https://github.com/Zakkaus/doona/releases). Extract them into the directory that honk's `native_api` block names in `ui`, and honk serves doona at `/ui/`. - -From v0.1.0-beta.8 on, until honk publishes a release with the native API, each doona release also attaches prebuilt `honk-core-debug-[-stock].tar.gz` archives from that `debug` release, so no one needs to compile honk. `HONK-SOURCE.txt` names the honk commit they were built from, `honk-source-.tar.gz` holds that commit's source, and `SHA256SUMS` covers every release asset except itself. [Install honk](https://zakkaus.github.io/doona-docs/en/install.html#install) explains which archive fits a gateway. - -The [documentation](https://zakkaus.github.io/doona-docs/en/) covers the requirements, installing honk and doona, an example configuration, the first sign-in, checking each feature and troubleshooting. - -## Pages - -The policies page - -| Page | Shows | -| ------------- | ---------------------------------------------------------------------------------------------------------------------------- | -| Activity | Outbound mode, traffic and memory, active connections, node latency, outbound usage, top clients, notifications | -| Overview | Engine and eBPF state, process CPU, traffic counters, backend capabilities and the features that are off, the status as JSON | -| Connections | Live connections with source, destination, rule, chain, traffic and transfer rates; fold groups, close one or all | -| DNS | Queries with their answers, the cache, the log; a flush | -| Policies | Groups, their members and health; selection, pinning, probing, editing and health-check URLs | -| Rules | A routing tree from rules or devices through outbounds to nodes, the rule list with hits, the flow log, a trace | -| Nodes | Subscriptions and their refresh interval, inline nodes, add and remove, probe and join a group | -| Configuration | Create source files and edit them in place, diagnostics, validation, quick setup and export | -| Events, Logs | The backend event stream; the log stream with filters, pause and export | -| Settings | Backends, runtime settings and backend actions, language, appearance, palette and notification placement | - -A page is marked unavailable only when every resource it needs is unavailable. `Ctrl K` searches pages, connections, nodes, groups, rules and sources from anywhere. Which resources each page needs, and where doona keeps its own settings, are on the [features page](https://zakkaus.github.io/doona-docs/en/features.html#pages). Help buttons beside unclear states and terms explain them. - -Sign-in is a page of its own with the language menu and scheme toggle. From 1024 pixels wide, a panel beside the form shows a construction scene; pressing it starts a Flappy Duck game. The demo fills in the user name `demo` and the password `demo`. - -The rules page - -## Page tour - -### Arranging groups - -On the Arrange tab of Policies, drag a node or a subscription from the list on the right onto a group to add it. Keyboard dragging does the same, and Add to group below the list adds the selected rows to a group. The changes stay staged until you open Review and apply and press Apply. - -Dragging the node us-01 onto the gaming group - -### Traffic and connections - -The Traffic tab of Connections plots each connection's upload against its download, coloured by outbound; select a point to open that connection. The Connections tab groups live connections by device or by outbound, filters them by protocol and outbound, and exports them as CSV. - -The Traffic tab of the connections page - -Live connections grouped by device - -### DNS - -The Statistics tab shows the median and P95 resolution time, the cache hit rate and the failure rate. The charts below place each upstream's lookups on a latency scale and count how the queries ended. - -The Statistics tab of the DNS page - -### Log activity - -Above the log list, a heatmap counts records per level over time. A level's row header sets the minimum level the list shows. - -The log activity heatmap - -### Routing map - -The routing map on Rules follows traffic from rules, or from devices, through outbounds to nodes. Point at or select a rule, outbound or node to highlight the paths through it. - -Selecting a rule and then a node on the routing map - -### Node latency - -The Latency tab of Nodes plots each node's latest latency, and its moving average and the average of the last 10 measurements when the backend reports them. A switch groups the nodes by policy group or by protocol; unavailable nodes appear under their group. - -The Latency tab of the nodes page - -## On a phone - -Below 1024 pixels wide, the side navigation becomes a bottom bar with four hubs: Overview, Traffic, Routing and Settings. A hub opens on the page last viewed in it during the session, and its pages sit in a row above the content. Language, theme, palette and wordmark move into the top bar's overflow menu, a submenu each. - -Below 600 pixels wide, tables keep every column and scroll sideways; on wider screens they drop the columns that do not fit, in a set order. Toolbars wrap onto more rows. On Events and Logs, press a row to read its full text below the table. On Overview, DNS and Logs, the first page action stays a button and the rest move into a menu. - -Over HTTPS or on localhost, doona installs as an app. In Chrome and Edge, the About card in Settings offers Install as an app. Safari has no install prompt, so the card shows the steps instead. On iPhone and iPad, tap Share, then Add to Home Screen. In Safari 26 on macOS, choose File > Add to Dock. - -doona on phones: the connections table, the overflow menu and its palette submenu - -## Development - -The build, test and packaging commands, the source layout and the contract pin are on the [development page](https://zakkaus.github.io/doona-docs/en/development.html). See [CONTRIBUTING.md](CONTRIBUTING.md) before opening a pull request. - -## Support - -Report bugs and ask questions in the [issues](https://github.com/Zakkaus/doona/issues). Report backend issues to the connected engine's project: [honk](https://github.com/daeuniverse/honk) or [dae](https://github.com/daeuniverse/dae). See [SECURITY.md](.github/SECURITY.md) for reporting a vulnerability. - -## License and credits - -[GPL-3.0-only](LICENSE). Noto Sans TC and SC are copyright Adobe and licensed under the [Open Font License](public/fonts/OFL.txt); [NOTICE](NOTICE) credits the Adobe Spectrum icons (Apache-2.0). The duck is the maintainer's own artwork. diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/ActionGroup-90AzAQje.js b/luci-app-honk/root/etc/honk/doona/assets/ActionGroup-90AzAQje.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/ActionGroup-90AzAQje.js rename to luci-app-honk/root/etc/honk/doona/assets/ActionGroup-90AzAQje.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/AddCircle-Db3HxlxQ.js b/luci-app-honk/root/etc/honk/doona/assets/AddCircle-Db3HxlxQ.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/AddCircle-Db3HxlxQ.js rename to luci-app-honk/root/etc/honk/doona/assets/AddCircle-Db3HxlxQ.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/AreaChart-DtE4xcoZ.js b/luci-app-honk/root/etc/honk/doona/assets/AreaChart-DtE4xcoZ.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/AreaChart-DtE4xcoZ.js rename to luci-app-honk/root/etc/honk/doona/assets/AreaChart-DtE4xcoZ.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/AreaCurve-BriPYJva.js b/luci-app-honk/root/etc/honk/doona/assets/AreaCurve-BriPYJva.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/AreaCurve-BriPYJva.js rename to luci-app-honk/root/etc/honk/doona/assets/AreaCurve-BriPYJva.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/Arrange-Aggv2ZKG.js b/luci-app-honk/root/etc/honk/doona/assets/Arrange-Aggv2ZKG.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/Arrange-Aggv2ZKG.js rename to luci-app-honk/root/etc/honk/doona/assets/Arrange-Aggv2ZKG.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/Arrange-Dd3j3leY.css b/luci-app-honk/root/etc/honk/doona/assets/Arrange-Dd3j3leY.css similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/Arrange-Dd3j3leY.css rename to luci-app-honk/root/etc/honk/doona/assets/Arrange-Dd3j3leY.css diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/Config-BTdL4dU5.js b/luci-app-honk/root/etc/honk/doona/assets/Config-BTdL4dU5.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/Config-BTdL4dU5.js rename to luci-app-honk/root/etc/honk/doona/assets/Config-BTdL4dU5.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/Connections-L2aJE6s8.js b/luci-app-honk/root/etc/honk/doona/assets/Connections-L2aJE6s8.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/Connections-L2aJE6s8.js rename to luci-app-honk/root/etc/honk/doona/assets/Connections-L2aJE6s8.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/DaeCode-STEiRZuV.js b/luci-app-honk/root/etc/honk/doona/assets/DaeCode-STEiRZuV.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/DaeCode-STEiRZuV.js rename to luci-app-honk/root/etc/honk/doona/assets/DaeCode-STEiRZuV.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/Dns-vjK8rYCH.js b/luci-app-honk/root/etc/honk/doona/assets/Dns-vjK8rYCH.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/Dns-vjK8rYCH.js rename to luci-app-honk/root/etc/honk/doona/assets/Dns-vjK8rYCH.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/Donut-BhofZH9-.js b/luci-app-honk/root/etc/honk/doona/assets/Donut-BhofZH9-.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/Donut-BhofZH9-.js rename to luci-app-honk/root/etc/honk/doona/assets/Donut-BhofZH9-.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/Events-B-WlUhq0.js b/luci-app-honk/root/etc/honk/doona/assets/Events-B-WlUhq0.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/Events-B-WlUhq0.js rename to luci-app-honk/root/etc/honk/doona/assets/Events-B-WlUhq0.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/FactStrip-BgQ_tYB6.js b/luci-app-honk/root/etc/honk/doona/assets/FactStrip-BgQ_tYB6.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/FactStrip-BgQ_tYB6.js rename to luci-app-honk/root/etc/honk/doona/assets/FactStrip-BgQ_tYB6.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/ListLayout-CPapuywx.js b/luci-app-honk/root/etc/honk/doona/assets/ListLayout-CPapuywx.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/ListLayout-CPapuywx.js rename to luci-app-honk/root/etc/honk/doona/assets/ListLayout-CPapuywx.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/Login-CBpAitxd.js b/luci-app-honk/root/etc/honk/doona/assets/Login-CBpAitxd.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/Login-CBpAitxd.js rename to luci-app-honk/root/etc/honk/doona/assets/Login-CBpAitxd.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/LoginShowcase-CwRzXrsa.js b/luci-app-honk/root/etc/honk/doona/assets/LoginShowcase-CwRzXrsa.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/LoginShowcase-CwRzXrsa.js rename to luci-app-honk/root/etc/honk/doona/assets/LoginShowcase-CwRzXrsa.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/Logs-bjF3zhK-.js b/luci-app-honk/root/etc/honk/doona/assets/Logs-bjF3zhK-.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/Logs-bjF3zhK-.js rename to luci-app-honk/root/etc/honk/doona/assets/Logs-bjF3zhK-.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/NodeSearch-Dmxd0d8_.js b/luci-app-honk/root/etc/honk/doona/assets/NodeSearch-Dmxd0d8_.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/NodeSearch-Dmxd0d8_.js rename to luci-app-honk/root/etc/honk/doona/assets/NodeSearch-Dmxd0d8_.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/Nodes-CF-Y3hZu.js b/luci-app-honk/root/etc/honk/doona/assets/Nodes-CF-Y3hZu.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/Nodes-CF-Y3hZu.js rename to luci-app-honk/root/etc/honk/doona/assets/Nodes-CF-Y3hZu.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/Overview-Fg9wiBtP.js b/luci-app-honk/root/etc/honk/doona/assets/Overview-Fg9wiBtP.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/Overview-Fg9wiBtP.js rename to luci-app-honk/root/etc/honk/doona/assets/Overview-Fg9wiBtP.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/Policies-wpHv6bOW.js b/luci-app-honk/root/etc/honk/doona/assets/Policies-wpHv6bOW.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/Policies-wpHv6bOW.js rename to luci-app-honk/root/etc/honk/doona/assets/Policies-wpHv6bOW.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/PolicyPicker-DSSmYroN.js b/luci-app-honk/root/etc/honk/doona/assets/PolicyPicker-DSSmYroN.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/PolicyPicker-DSSmYroN.js rename to luci-app-honk/root/etc/honk/doona/assets/PolicyPicker-DSSmYroN.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/Rules-CSApE4tY.js b/luci-app-honk/root/etc/honk/doona/assets/Rules-CSApE4tY.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/Rules-CSApE4tY.js rename to luci-app-honk/root/etc/honk/doona/assets/Rules-CSApE4tY.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/Rules-l5FnUA9B.css b/luci-app-honk/root/etc/honk/doona/assets/Rules-l5FnUA9B.css similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/Rules-l5FnUA9B.css rename to luci-app-honk/root/etc/honk/doona/assets/Rules-l5FnUA9B.css diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/SearchDialog-h0qVIWxg.js b/luci-app-honk/root/etc/honk/doona/assets/SearchDialog-h0qVIWxg.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/SearchDialog-h0qVIWxg.js rename to luci-app-honk/root/etc/honk/doona/assets/SearchDialog-h0qVIWxg.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/Settings-Ajzo2fTI.js b/luci-app-honk/root/etc/honk/doona/assets/Settings-Ajzo2fTI.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/Settings-Ajzo2fTI.js rename to luci-app-honk/root/etc/honk/doona/assets/Settings-Ajzo2fTI.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/Sparkline-B2EO782x.js b/luci-app-honk/root/etc/honk/doona/assets/Sparkline-B2EO782x.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/Sparkline-B2EO782x.js rename to luci-app-honk/root/etc/honk/doona/assets/Sparkline-B2EO782x.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/Table-D0NFHHNQ.js b/luci-app-honk/root/etc/honk/doona/assets/Table-D0NFHHNQ.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/Table-D0NFHHNQ.js rename to luci-app-honk/root/etc/honk/doona/assets/Table-D0NFHHNQ.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/TimeCell-gSIuLEFc.js b/luci-app-honk/root/etc/honk/doona/assets/TimeCell-gSIuLEFc.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/TimeCell-gSIuLEFc.js rename to luci-app-honk/root/etc/honk/doona/assets/TimeCell-gSIuLEFc.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/Virtualizer--Vfl8O3n.js b/luci-app-honk/root/etc/honk/doona/assets/Virtualizer--Vfl8O3n.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/Virtualizer--Vfl8O3n.js rename to luci-app-honk/root/etc/honk/doona/assets/Virtualizer--Vfl8O3n.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/array-C0P64tl-.js b/luci-app-honk/root/etc/honk/doona/assets/array-C0P64tl-.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/array-C0P64tl-.js rename to luci-app-honk/root/etc/honk/doona/assets/array-C0P64tl-.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/auth-BGnwk8Hi.js b/luci-app-honk/root/etc/honk/doona/assets/auth-BGnwk8Hi.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/auth-BGnwk8Hi.js rename to luci-app-honk/root/etc/honk/doona/assets/auth-BGnwk8Hi.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/auth-DmBaDwOI.js b/luci-app-honk/root/etc/honk/doona/assets/auth-DmBaDwOI.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/auth-DmBaDwOI.js rename to luci-app-honk/root/etc/honk/doona/assets/auth-DmBaDwOI.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/dns-DQrpG8y_.js b/luci-app-honk/root/etc/honk/doona/assets/dns-DQrpG8y_.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/dns-DQrpG8y_.js rename to luci-app-honk/root/etc/honk/doona/assets/dns-DQrpG8y_.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/duck-night-CbKHyiul.webp b/luci-app-honk/root/etc/honk/doona/assets/duck-night-CbKHyiul.webp similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/duck-night-CbKHyiul.webp rename to luci-app-honk/root/etc/honk/doona/assets/duck-night-CbKHyiul.webp diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/files-BxEOxSF8.js b/luci-app-honk/root/etc/honk/doona/assets/files-BxEOxSF8.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/files-BxEOxSF8.js rename to luci-app-honk/root/etc/honk/doona/assets/files-BxEOxSF8.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/flows-C0byZ3sY.js b/luci-app-honk/root/etc/honk/doona/assets/flows-C0byZ3sY.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/flows-C0byZ3sY.js rename to luci-app-honk/root/etc/honk/doona/assets/flows-C0byZ3sY.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/fonts-sc-DWPGxLPK.css b/luci-app-honk/root/etc/honk/doona/assets/fonts-sc-DWPGxLPK.css similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/fonts-sc-DWPGxLPK.css rename to luci-app-honk/root/etc/honk/doona/assets/fonts-sc-DWPGxLPK.css diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/fonts-tc-B6Zt5HQN.css b/luci-app-honk/root/etc/honk/doona/assets/fonts-tc-B6Zt5HQN.css similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/fonts-tc-B6Zt5HQN.css rename to luci-app-honk/root/etc/honk/doona/assets/fonts-tc-B6Zt5HQN.css diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/geodata-DprMPspn.js b/luci-app-honk/root/etc/honk/doona/assets/geodata-DprMPspn.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/geodata-DprMPspn.js rename to luci-app-honk/root/etc/honk/doona/assets/geodata-DprMPspn.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/index-BMPxGQXj.css b/luci-app-honk/root/etc/honk/doona/assets/index-BMPxGQXj.css similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/index-BMPxGQXj.css rename to luci-app-honk/root/etc/honk/doona/assets/index-BMPxGQXj.css diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/index-CXHBmSa5.js b/luci-app-honk/root/etc/honk/doona/assets/index-CXHBmSa5.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/index-CXHBmSa5.js rename to luci-app-honk/root/etc/honk/doona/assets/index-CXHBmSa5.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/index-ChxA9-bE.js b/luci-app-honk/root/etc/honk/doona/assets/index-ChxA9-bE.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/index-ChxA9-bE.js rename to luci-app-honk/root/etc/honk/doona/assets/index-ChxA9-bE.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/layout-BRLew_nj.js b/luci-app-honk/root/etc/honk/doona/assets/layout-BRLew_nj.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/layout-BRLew_nj.js rename to luci-app-honk/root/etc/honk/doona/assets/layout-BRLew_nj.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/link-D6L0Eg5i.js b/luci-app-honk/root/etc/honk/doona/assets/link-D6L0Eg5i.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/link-D6L0Eg5i.js rename to luci-app-honk/root/etc/honk/doona/assets/link-D6L0Eg5i.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/locale-en-C8dLKl7q.js b/luci-app-honk/root/etc/honk/doona/assets/locale-en-C8dLKl7q.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/locale-en-C8dLKl7q.js rename to luci-app-honk/root/etc/honk/doona/assets/locale-en-C8dLKl7q.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/locale-zh-CN-DVMwebSP.js b/luci-app-honk/root/etc/honk/doona/assets/locale-zh-CN-DVMwebSP.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/locale-zh-CN-DVMwebSP.js rename to luci-app-honk/root/etc/honk/doona/assets/locale-zh-CN-DVMwebSP.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/locale-zh-TW-uoOhY-ul.js b/luci-app-honk/root/etc/honk/doona/assets/locale-zh-TW-uoOhY-ul.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/locale-zh-TW-uoOhY-ul.js rename to luci-app-honk/root/etc/honk/doona/assets/locale-zh-TW-uoOhY-ul.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/logo-obi05X1B.svg b/luci-app-honk/root/etc/honk/doona/assets/logo-obi05X1B.svg similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/logo-obi05X1B.svg rename to luci-app-honk/root/etc/honk/doona/assets/logo-obi05X1B.svg diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/nav-BIpfWn6-.js b/luci-app-honk/root/etc/honk/doona/assets/nav-BIpfWn6-.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/nav-BIpfWn6-.js rename to luci-app-honk/root/etc/honk/doona/assets/nav-BIpfWn6-.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/nav-BeFLfZq8.js b/luci-app-honk/root/etc/honk/doona/assets/nav-BeFLfZq8.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/nav-BeFLfZq8.js rename to luci-app-honk/root/etc/honk/doona/assets/nav-BeFLfZq8.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/nav-C2A1sZKm.js b/luci-app-honk/root/etc/honk/doona/assets/nav-C2A1sZKm.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/nav-C2A1sZKm.js rename to luci-app-honk/root/etc/honk/doona/assets/nav-C2A1sZKm.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/nav-D1cfWFA0.js b/luci-app-honk/root/etc/honk/doona/assets/nav-D1cfWFA0.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/nav-D1cfWFA0.js rename to luci-app-honk/root/etc/honk/doona/assets/nav-D1cfWFA0.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/outbounds-f8QoVoCV.js b/luci-app-honk/root/etc/honk/doona/assets/outbounds-f8QoVoCV.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/outbounds-f8QoVoCV.js rename to luci-app-honk/root/etc/honk/doona/assets/outbounds-f8QoVoCV.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/policyText-CAFy3qpo.js b/luci-app-honk/root/etc/honk/doona/assets/policyText-CAFy3qpo.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/policyText-CAFy3qpo.js rename to luci-app-honk/root/etc/honk/doona/assets/policyText-CAFy3qpo.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/probe-C8ejAKwA.js b/luci-app-honk/root/etc/honk/doona/assets/probe-C8ejAKwA.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/probe-C8ejAKwA.js rename to luci-app-honk/root/etc/honk/doona/assets/probe-C8ejAKwA.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/ranked-FVqORIQj.js b/luci-app-honk/root/etc/honk/doona/assets/ranked-FVqORIQj.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/ranked-FVqORIQj.js rename to luci-app-honk/root/etc/honk/doona/assets/ranked-FVqORIQj.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/recorder-DlDpPhd4.js b/luci-app-honk/root/etc/honk/doona/assets/recorder-DlDpPhd4.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/recorder-DlDpPhd4.js rename to luci-app-honk/root/etc/honk/doona/assets/recorder-DlDpPhd4.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/setup-B5f1cfyO.js b/luci-app-honk/root/etc/honk/doona/assets/setup-B5f1cfyO.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/setup-B5f1cfyO.js rename to luci-app-honk/root/etc/honk/doona/assets/setup-B5f1cfyO.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/useFilter-DZ8TXqn_.js b/luci-app-honk/root/etc/honk/doona/assets/useFilter-DZ8TXqn_.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/useFilter-DZ8TXqn_.js rename to luci-app-honk/root/etc/honk/doona/assets/useFilter-DZ8TXqn_.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/useGridSelectionCheckbox-BV96yL-N.js b/luci-app-honk/root/etc/honk/doona/assets/useGridSelectionCheckbox-BV96yL-N.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/useGridSelectionCheckbox-BV96yL-N.js rename to luci-app-honk/root/etc/honk/doona/assets/useGridSelectionCheckbox-BV96yL-N.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/vendor-editor-CF5uj9id.js b/luci-app-honk/root/etc/honk/doona/assets/vendor-editor-CF5uj9id.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/vendor-editor-CF5uj9id.js rename to luci-app-honk/root/etc/honk/doona/assets/vendor-editor-CF5uj9id.js diff --git a/luci-app-honk/root/etc/honk/dashboard/assets/vendor-react-Bf71BWbF.js b/luci-app-honk/root/etc/honk/doona/assets/vendor-react-Bf71BWbF.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/assets/vendor-react-Bf71BWbF.js rename to luci-app-honk/root/etc/honk/doona/assets/vendor-react-Bf71BWbF.js diff --git a/luci-app-honk/root/etc/honk/dashboard/icons/apple-touch-icon.png b/luci-app-honk/root/etc/honk/doona/icons/apple-touch-icon.png similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/icons/apple-touch-icon.png rename to luci-app-honk/root/etc/honk/doona/icons/apple-touch-icon.png diff --git a/luci-app-honk/root/etc/honk/dashboard/icons/icon-192.png b/luci-app-honk/root/etc/honk/doona/icons/icon-192.png similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/icons/icon-192.png rename to luci-app-honk/root/etc/honk/doona/icons/icon-192.png diff --git a/luci-app-honk/root/etc/honk/dashboard/icons/icon-512.png b/luci-app-honk/root/etc/honk/doona/icons/icon-512.png similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/icons/icon-512.png rename to luci-app-honk/root/etc/honk/doona/icons/icon-512.png diff --git a/luci-app-honk/root/etc/honk/dashboard/icons/maskable-512.png b/luci-app-honk/root/etc/honk/doona/icons/maskable-512.png similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/icons/maskable-512.png rename to luci-app-honk/root/etc/honk/doona/icons/maskable-512.png diff --git a/luci-app-honk/root/etc/honk/dashboard/index.html b/luci-app-honk/root/etc/honk/doona/index.html similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/index.html rename to luci-app-honk/root/etc/honk/doona/index.html diff --git a/luci-app-honk/root/etc/honk/dashboard/logo.svg b/luci-app-honk/root/etc/honk/doona/logo.svg similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/logo.svg rename to luci-app-honk/root/etc/honk/doona/logo.svg diff --git a/luci-app-honk/root/etc/honk/dashboard/manifest.webmanifest b/luci-app-honk/root/etc/honk/doona/manifest.webmanifest similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/manifest.webmanifest rename to luci-app-honk/root/etc/honk/doona/manifest.webmanifest diff --git a/luci-app-honk/root/etc/honk/dashboard/sw.js b/luci-app-honk/root/etc/honk/doona/sw.js similarity index 100% rename from luci-app-honk/root/etc/honk/dashboard/sw.js rename to luci-app-honk/root/etc/honk/doona/sw.js diff --git a/luci-app-honk/root/etc/uci-defaults/luci-honk b/luci-app-honk/root/etc/uci-defaults/luci-honk index 3ab20dd0f..8f9c1dff3 100644 --- a/luci-app-honk/root/etc/uci-defaults/luci-honk +++ b/luci-app-honk/root/etc/uci-defaults/luci-honk @@ -14,7 +14,7 @@ experimental { enabled: true listen: '0.0.0.0:9527' secret: 'honk114514' - ui: '/etc/honk/dashboard' + ui: '/etc/honk/doona' config_write: true geosite_download_url: 'https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/release/geosite.dat' geoip_download_url: 'https://raw.githubusercontent.com/QiuSimons/geoip-moedove/refs/heads/main/geoip.dat' @@ -22,6 +22,16 @@ experimental { } EOF fi + +# Smooth migration from legacy /etc/honk/dashboard to /etc/honk/doona +if [ -d /etc/honk/dashboard ] && [ ! -d /etc/honk/doona ]; then + mv /etc/honk/dashboard /etc/honk/doona +elif [ -d /etc/honk/dashboard ] && [ -d /etc/honk/doona ]; then + rm -rf /etc/honk/dashboard +fi +if [ -f /etc/honk/config.d/api.dae ]; then + sed -i "s|/etc/honk/dashboard|/etc/honk/doona|g" /etc/honk/config.d/api.dae 2>/dev/null || true +fi chmod -R u=rwX,go= /etc/honk 2>/dev/null || true chmod 700 /etc/honk /etc/honk/config.d /etc/honk/state 2>/dev/null || true diff --git a/luci-app-honk/root/usr/share/honk/download_dashboard.sh b/luci-app-honk/root/usr/share/honk/download_dashboard.sh index 0aa22ecef..f09e45592 100755 --- a/luci-app-honk/root/usr/share/honk/download_dashboard.sh +++ b/luci-app-honk/root/usr/share/honk/download_dashboard.sh @@ -1,8 +1,16 @@ #!/bin/sh # Download and install Dashboard for HONK -TARGET_DIR="${1:-/etc/honk/dashboard}" -DOWNLOAD_URL="${2:-https://github.com/Zephyruso/zashboard/releases/latest/download/dist-no-fonts.zip}" +TARGET_DIR="${1:-/etc/honk/doona}" +if [ -z "${2:-}" ]; then + if [ "$TARGET_DIR" = "/etc/honk/zashboard" ]; then + DOWNLOAD_URL="https://github.com/Zephyruso/zashboard/releases/latest/download/dist-no-fonts.zip" + else + DOWNLOAD_URL="https://github.com/Zakkaus/doona/releases/download/v0.1.0-beta.8/doona-0.1.0-beta.8.tar.gz" + fi +else + DOWNLOAD_URL="$2" +fi LOG_FILE="/tmp/honk_dashboard_download.log" STATUS_FILE="/tmp/honk_dashboard_download.status" @@ -166,6 +174,7 @@ if [ -n "$TARGET_DIR" ] && [ "$TARGET_DIR" != "/" ] && [ "$TARGET_DIR" != "/etc" rm -rf "${TARGET_DIR:?}"/* "${TARGET_DIR:?}"/.[!.]* 2>/dev/null || true fi cp -rf "$DEPLOY_SRC/"* "$TARGET_DIR/" +rm -rf "$TARGET_DIR"/*.md "$TARGET_DIR"/LICENSE* "$TARGET_DIR"/NOTICE "$TARGET_DIR"/LICENSES 2>/dev/null || true chmod -R 755 "$TARGET_DIR" if [ -f "$TARGET_DIR/index.html" ]; then diff --git a/luci-app-honk/root/usr/share/rpcd/acl.d/luci-app-honk.json b/luci-app-honk/root/usr/share/rpcd/acl.d/luci-app-honk.json index c123973ac..75126cc89 100644 --- a/luci-app-honk/root/usr/share/rpcd/acl.d/luci-app-honk.json +++ b/luci-app-honk/root/usr/share/rpcd/acl.d/luci-app-honk.json @@ -14,6 +14,7 @@ "/etc/honk/config.d/node.dae", "/etc/honk/config.d/route.dae", "/etc/honk/dashboard/*", + "/etc/honk/doona/*", "/etc/honk/zashboard/*" ] }, @@ -30,6 +31,7 @@ "/etc/honk/config.d/node.dae", "/etc/honk/config.d/route.dae", "/etc/honk/dashboard/*", + "/etc/honk/doona/*", "/etc/honk/zashboard/*" ] } diff --git a/luci-app-honk/root/usr/share/rpcd/ucode/honk.uc b/luci-app-honk/root/usr/share/rpcd/ucode/honk.uc index 8125205ac..002e8b5a9 100644 --- a/luci-app-honk/root/usr/share/rpcd/ucode/honk.uc +++ b/luci-app-honk/root/usr/share/rpcd/ucode/honk.uc @@ -265,23 +265,25 @@ function get_dashboard_info(req) { has_native_api: !!(parsed_native && parsed_native.enabled) }; - if (requested_type == "doona") { - if (!parsed_native || !parsed_native.enabled) { - return res; - } - res.configured = true; + let target_default_ui = (requested_type == "zashboard") ? "/etc/honk/zashboard" : "/etc/honk/doona"; + let other_default_ui = (requested_type == "zashboard") ? "/etc/honk/doona" : "/etc/honk/zashboard"; + + if (parsed_native && parsed_native.enabled) { res.external_controller = parsed_native.listen; res.host = parsed_native.host; res.port = parsed_native.port || "9527"; res.secret = parsed_native.secret; - res.external_ui = parsed_native.ui; res.geosite_download_url = parsed_native.geosite_download_url; res.geoip_download_url = parsed_native.geoip_download_url; - } else { - if (!parsed_clash) { - return res; + res.external_ui = (parsed_native.ui && parsed_native.ui != other_default_ui) ? parsed_native.ui : target_default_ui; + + if (parsed_native.listen && parsed_native.ui && parsed_native.ui != other_default_ui) { + res.configured = true; + } else { + res.configured = false; } - res.configured = true; + } else if (parsed_clash) { + res.configured = false; res.external_controller = parsed_clash.external_controller; res.host = parsed_clash.host; res.port = parsed_clash.port || "9090"; @@ -316,22 +318,11 @@ function download_dashboard(req) { } let api_cfg = get_api_config(); - let parsed_clash = api_cfg.parsed_clash; let parsed_native = api_cfg.parsed_native; - - let target_dir; - if (requested_type == "doona") { - target_dir = (parsed_native && parsed_native.ui) ? parsed_native.ui : "/etc/honk/dashboard"; - } else if (requested_type == "zashboard") { - target_dir = (parsed_clash && parsed_clash.external_ui) ? parsed_clash.external_ui : "/etc/honk/zashboard"; - } else { - if (parsed_native && parsed_native.ui) { - target_dir = parsed_native.ui; - } else if (parsed_clash && parsed_clash.external_ui) { - target_dir = parsed_clash.external_ui; - } else { - target_dir = "/etc/honk/dashboard"; - } + let other_default_ui = (requested_type == "zashboard") ? "/etc/honk/doona" : "/etc/honk/zashboard"; + let target_dir = (requested_type == "zashboard") ? "/etc/honk/zashboard" : "/etc/honk/doona"; + if (parsed_native && parsed_native.ui && parsed_native.ui != other_default_ui) { + target_dir = parsed_native.ui; } let script = "/usr/share/honk/download_dashboard.sh"; @@ -354,7 +345,7 @@ function switch_dashboard_api(target_type) { let api_file = get_api_file_path(); let config_file = get_config_file_path(); - // 1. Clean legacy api blocks from main config.dae to avoid read-only lock in doona + // 1. Clean legacy api blocks from main config.dae to avoid read-only lock in dashboard clean_legacy_api_from_config(config_file); system("mkdir -p /etc/honk/config.d"); @@ -363,18 +354,16 @@ function switch_dashboard_api(target_type) { let parsed_clash = api_cfg.parsed_clash; let parsed_native = api_cfg.parsed_native; + let target_ui = (target_type == "zashboard") ? "/etc/honk/zashboard" : "/etc/honk/doona"; + // Check if already correctly configured in api.dae if (!api_cfg.is_legacy) { - if (target_type == "doona") { - if (parsed_native && parsed_native.enabled && !parsed_clash && - parsed_native.config_write && - parsed_native.geosite_download_url && parsed_native.geoip_download_url) { - return { success: true, type: target_type, noop: true }; - } - } else { - if (parsed_clash && (!parsed_native || !parsed_native.enabled)) { - return { success: true, type: target_type, noop: true }; - } + if (parsed_native && parsed_native.enabled && !parsed_clash && + parsed_native.listen && + parsed_native.config_write && + parsed_native.geosite_download_url && parsed_native.geoip_download_url && + parsed_native.ui == target_ui) { + return { success: true, type: target_type, noop: true }; } } @@ -383,15 +372,13 @@ function switch_dashboard_api(target_type) { cleaned_api = remove_bracket_block(cleaned_api, /^[#\/]*\s*native_api\s*\{/); cleaned_api = replace(cleaned_api, /experimental\s*\{\s*\}/, ""); - let api_inner; - if (target_type == "doona") { - let sec = (parsed_native && parsed_native.secret && length(parsed_native.secret) >= 8) ? parsed_native.secret : "honk114514"; - let listen = (parsed_native && parsed_native.listen) ? parsed_native.listen : "0.0.0.0:9527"; - let ui = (parsed_native && parsed_native.ui) ? parsed_native.ui : "/etc/honk/dashboard"; - let geosite = (parsed_native && parsed_native.geosite_download_url) ? parsed_native.geosite_download_url : "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/release/geosite.dat"; - let geoip = (parsed_native && parsed_native.geoip_download_url) ? parsed_native.geoip_download_url : "https://raw.githubusercontent.com/QiuSimons/geoip-moedove/refs/heads/main/geoip.dat"; + let sec = (parsed_native && parsed_native.secret && length(parsed_native.secret) >= 8) ? parsed_native.secret : "honk114514"; + let listen = (parsed_native && parsed_native.listen) ? parsed_native.listen : "0.0.0.0:9527"; + let ui = target_ui; + let geosite = (parsed_native && parsed_native.geosite_download_url) ? parsed_native.geosite_download_url : "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/release/geosite.dat"; + let geoip = (parsed_native && parsed_native.geoip_download_url) ? parsed_native.geoip_download_url : "https://raw.githubusercontent.com/QiuSimons/geoip-moedove/refs/heads/main/geoip.dat"; - api_inner = + let api_inner = " native_api {\n" + " enabled: true\n" + " listen: '" + listen + "'\n" + @@ -401,20 +388,6 @@ function switch_dashboard_api(target_type) { " geosite_download_url: '" + geosite + "'\n" + " geoip_download_url: '" + geoip + "'\n" + " }\n"; - } else { - let ec = (parsed_clash && parsed_clash.external_controller) ? parsed_clash.external_controller : "0.0.0.0:9090"; - let ui = (parsed_clash && parsed_clash.external_ui) ? parsed_clash.external_ui : "/etc/honk/zashboard"; - let sec = (parsed_clash && parsed_clash.secret) ? parsed_clash.secret : ""; - let dm = (parsed_clash && parsed_clash.default_mode) ? parsed_clash.default_mode : "Rule"; - - api_inner = -" clash_api {\n" + -" external_controller: '" + ec + "'\n" + -" external_ui: '" + ui + "'\n" + -" secret: '" + sec + "'\n" + -" default_mode: '" + dm + "'\n" + -" }\n"; - } let default_block = "experimental {\n" + api_inner + "}\n"; let has_active_exp = match(cleaned_api, /(^|\n)[ \t]*experimental\s*\{/); diff --git a/luci-app-honk/root/www/luci-static/resources/honk/common.js b/luci-app-honk/root/www/luci-static/resources/honk/common.js index de154cf99..0da1377c1 100644 --- a/luci-app-honk/root/www/luci-static/resources/honk/common.js +++ b/luci-app-honk/root/www/luci-static/resources/honk/common.js @@ -85,12 +85,6 @@ var callHonkDownloadStatus = rpc.declare({ expect: { } }); -var callHonkEnableClashApi = rpc.declare({ - object: 'luci.honk', - method: 'enable_clash_api', - expect: { } -}); - var callHonkSwitchDashboardApi = rpc.declare({ object: 'luci.honk', method: 'switch_dashboard_api', @@ -436,7 +430,7 @@ function createConfigFileView(filePath, mapTitle, mapDesc, fieldTitle, successMs ' enabled: true', " listen: '0.0.0.0:9527'", " secret: 'honk114514'", - " ui: '/etc/honk/dashboard'", + " ui: '/etc/honk/doona'", " config_write: true", " geosite_download_url: 'https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/release/geosite.dat'", " geoip_download_url: 'https://raw.githubusercontent.com/QiuSimons/geoip-moedove/refs/heads/main/geoip.dat'", @@ -602,7 +596,6 @@ return baseclass.extend({ callHonkDownloadDashboard: callHonkDownloadDashboard, callHonkDownloadZashboard: callHonkDownloadDashboard, callHonkDownloadStatus: callHonkDownloadStatus, - callHonkEnableClashApi: callHonkEnableClashApi, callHonkSwitchDashboardApi: callHonkSwitchDashboardApi, readFile: readFile, writeFile: writeFile, diff --git a/luci-app-honk/root/www/luci-static/resources/view/honk/dashboard.js b/luci-app-honk/root/www/luci-static/resources/view/honk/dashboard.js index 4bfe7cf91..488235b91 100644 --- a/luci-app-honk/root/www/luci-static/resources/view/honk/dashboard.js +++ b/luci-app-honk/root/www/luci-static/resources/view/honk/dashboard.js @@ -28,15 +28,16 @@ return view.extend({ zashboard: { name: 'Zashboard', label: 'Zashboard', - apiName: 'Clash API', - defaultPort: '9090', + apiName: 'Native API', + defaultPort: '9527', defaultDir: '/etc/honk/zashboard', - exampleConfig: "experimental {\n clash_api {\n external_controller: '0.0.0.0:9090'\n external_ui: '/etc/honk/zashboard'\n secret: ''\n default_mode: 'Rule'\n }\n}", + exampleConfig: "experimental {\n native_api {\n enabled: true\n listen: '0.0.0.0:9527'\n secret: 'honk114514'\n ui: '/etc/honk/zashboard'\n config_write: true\n geosite_download_url: 'https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/release/geosite.dat'\n geoip_download_url: 'https://raw.githubusercontent.com/QiuSimons/geoip-moedove/refs/heads/main/geoip.dat'\n }\n}", buildUrl: function(info, targetHost, port, secret, protocol, forceFresh) { var hostPart = (targetHost.indexOf(':') !== -1 && targetHost.charAt(0) !== '[') ? '[' + targetHost + ']' : targetHost; var query = 'hostname=' + encodeURIComponent(targetHost) + '&port=' + encodeURIComponent(port) + - '&protocol=' + encodeURIComponent(protocol); + '&protocol=' + encodeURIComponent(protocol) + + '&type=dae'; if (secret) { query += '&secret=' + encodeURIComponent(secret); } @@ -56,8 +57,8 @@ return view.extend({ label: _('Doona'), apiName: 'Native API', defaultPort: '9527', - defaultDir: '/etc/honk/dashboard', - exampleConfig: "experimental {\n native_api {\n enabled: true\n listen: '0.0.0.0:9527'\n secret: 'honk114514'\n ui: '/etc/honk/dashboard'\n config_write: true\n geosite_download_url: 'https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/release/geosite.dat'\n geoip_download_url: 'https://raw.githubusercontent.com/QiuSimons/geoip-moedove/refs/heads/main/geoip.dat'\n }\n}", + defaultDir: '/etc/honk/doona', + exampleConfig: "experimental {\n native_api {\n enabled: true\n listen: '0.0.0.0:9527'\n secret: 'honk114514'\n ui: '/etc/honk/doona'\n config_write: true\n geosite_download_url: 'https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/release/geosite.dat'\n geoip_download_url: 'https://raw.githubusercontent.com/QiuSimons/geoip-moedove/refs/heads/main/geoip.dat'\n }\n}", buildUrl: function(info, targetHost, port, secret, protocol, forceFresh) { var hostPart = (targetHost.indexOf(':') !== -1 && targetHost.charAt(0) !== '[') ? '[' + targetHost + ']' : targetHost; var url = protocol + '://' + hostPart + ':' + port + '/ui/'; @@ -111,7 +112,7 @@ return view.extend({ function buildDashboardUrl(info, forceFresh) { var targetHost = getTargetHost(info.host); - var port = info.port || '9090'; + var port = info.port || profile.defaultPort || '9527'; var secret = info.secret || ''; var protocol = 'http'; return profile.buildUrl(info, targetHost, port, secret, protocol, forceFresh); @@ -171,13 +172,13 @@ return view.extend({ // State 1: Unconfigured var quickEnableMsg = E('span', { 'style': 'margin-left: 8px;' }); - var enableBtnText = (dashType === 'doona') ? _('One-click Enable Default Native API') : _('One-click Enable Default Clash API'); + var enableBtnText = _('One-click Enable Default Native API'); var btnQuickEnable = E('button', { 'type': 'button', 'class': 'cbi-button cbi-button-apply', 'click': function() { btnQuickEnable.disabled = true; - btnQuickEnable.innerText = (dashType === 'doona') ? _('Enabling Native API and restarting HONK...') : _('Enabling Clash API and restarting HONK...'); + btnQuickEnable.innerText = _('Enabling Native API and restarting HONK...'); honk.callHonkSwitchDashboardApi(dashType).then(function(resp) { btnQuickEnable.disabled = false; btnQuickEnable.innerText = enableBtnText; @@ -195,13 +196,9 @@ return view.extend({ } }, enableBtnText); - var unconfTitle = (dashType === 'doona') ? _('Dashboard / Native API Unconfigured') : _('Dashboard / Clash API Unconfigured'); - var unconfDescr = (dashType === 'doona') - ? _('HONK has not enabled Native API in its configuration file. Doona requires native API listener and dashboard UI path.') - : _('HONK has not enabled Clash API in its configuration file. Dashboard requires external controller port and dashboard UI path.'); - var unconfWarn = (dashType === 'doona') - ? _('Please configure experimental.native_api block in API Settings, and ensure listen and ui are configured.') - : _('Please configure experimental.clash_api block in API Settings, and ensure external_controller and external_ui are configured.'); + var unconfTitle = _('Dashboard / Native API Unconfigured'); + var unconfDescr = _('HONK has not enabled Native API in its configuration file. Dashboard requires native API listener and dashboard UI path.'); + var unconfWarn = _('Please configure experimental.native_api block in API Settings, and ensure listen and ui are configured.'); var stateUnconfigured = E('div', { 'class': 'cbi-section', 'style': 'display: none;' }, [ E('h3', {}, unconfTitle), @@ -222,7 +219,7 @@ return view.extend({ // State 2: Missing UI var metaUiDir = E('td', {}, profile.defaultDir); - var metaController = E('td', {}, '0.0.0.0:9090'); + var metaController = E('td', {}, '0.0.0.0:9527'); var metaSecret = E('td', {}, _('(Not set)')); var radioGithub = E('input', { 'type': 'radio', 'class': 'cbi-input-radio', 'name': 'dash_dl_src', 'value': profile.githubRelease, 'checked': 'checked' }); @@ -278,13 +275,11 @@ return view.extend({ var stateMissingUi = E('div', { 'class': 'cbi-section', 'style': 'display: none;' }, [ E('h3', {}, _('%s UI Files Not Found').format(profile.name)), E('div', { 'class': 'cbi-section-descr' }, - (dashType === 'doona') - ? _('Native API is configured, but dashboard files are missing in the UI directory. You can download and deploy it directly.') - : _('Clash API is configured, but dashboard files are missing in the external UI directory. You can download and deploy it directly.') + _('Native API is configured, but dashboard files are missing in the UI directory. You can download and deploy it directly.') ), E('table', { 'class': 'table', 'style': 'margin: 14px 0;' }, [ - E('tr', {}, [ E('th', { 'style': 'width: 25%;' }, _('Target Dashboard')), selectDashboardMissing ]), - E('tr', {}, [ E('th', {}, (dashType === 'doona') ? _('Target Directory (ui)') : _('Target Directory (external_ui)')), metaUiDir ]), + E('tr', {}, [ E('th', { 'style': 'width: 25%;' }, _('Target Dashboard')), E('td', {}, selectDashboardMissing) ]), + E('tr', {}, [ E('th', {}, _('Target Directory (ui)')), metaUiDir ]), E('tr', {}, [ E('th', {}, _('Listen Address & Port')), metaController ]), E('tr', {}, [ E('th', {}, _('API Secret')), metaSecret ]) ]), @@ -321,7 +316,7 @@ return view.extend({ E('a', { 'href': '#', 'target': '_blank', 'class': 'cbi-button cbi-button-action', 'style': 'white-space: nowrap; margin-left: 10px;' }, _('Open in New Tab')) ]); - var honkPortLabel = E('span', { 'class': 'honk_port_label' }, profile.defaultPort || '9090'); + var honkPortLabel = E('span', { 'class': 'honk_port_label' }, profile.defaultPort || '9527'); var honkStopAlert = E('div', { 'class': 'alert-message warning', 'style': 'display: none; margin-bottom: 10px; justify-content: space-between; align-items: center;' }, [ E('div', {}, [ E('strong', {}, _('HONK service is currently not running:') + ' '), @@ -579,7 +574,7 @@ return view.extend({ statusServicePill.className = 'label warning'; statusServicePill.innerText = _('Not Running'); honkStopAlert.style.display = 'flex'; - honkPortLabel.innerText = port || '9090'; + honkPortLabel.innerText = port || profile.defaultPort || '9527'; } } @@ -593,7 +588,7 @@ return view.extend({ if (!data.has_ui) { metaUiDir.innerText = data.external_ui || profile.defaultDir; - metaController.innerText = data.external_controller || (dashType === 'doona' ? '0.0.0.0:9527' : '0.0.0.0:9090'); + metaController.innerText = data.external_controller || ('0.0.0.0:' + (profile.defaultPort || '9527')); metaSecret.innerText = data.secret ? data.secret : _('Not configured (Empty)'); showState('missing_ui'); return; @@ -602,7 +597,7 @@ return view.extend({ // Ready state showState('ready'); var targetHost = getTargetHost(data.host); - var port = data.port || profile.defaultPort || '9090'; + var port = data.port || profile.defaultPort || '9527'; var fullUrl = buildDashboardUrl(data); updateHonkRunningState(data.running, port); diff --git a/luci-app-smartsafehub/CHANGELOG.md b/luci-app-smartsafehub/CHANGELOG.md index a68e2da92..2ce06d195 100644 --- a/luci-app-smartsafehub/CHANGELOG.md +++ b/luci-app-smartsafehub/CHANGELOG.md @@ -1,5 +1,192 @@ # 변경 기록 +## [0.2.23-r1] - 2026-09-28 + +### 개선 + +- 관리자 비밀번호 기능을 추가하고, 초기화 기능도 함께 제공합니다. +- 인터넷 설정을 새롭게 추가하고 기존의 LAN 메뉴와 합쳤습니다. + +### 수정 + +- 다크 모드에서 일부 UI의 가독성이 떨어진 부분을 수정하였습니다. + +## [0.2.22-r14] - 2026-09-28 + +### 개선 + +- 사이드바 `업데이트` 메뉴 badge를 개별 패키지 수가 아니라 업데이트 유형 단위로 표시하도록 변경했습니다. 관리 소프트웨어 업데이트가 여러 개여도 1개로 계산하고, 펌웨어 업데이트가 있으면 별도로 1개를 더해 badge가 최대 2까지만 표시됩니다. +- 접힌 사이드바의 접근성 라벨도 실제 의미에 맞게 업데이트 유형 개수를 안내하도록 정리했습니다. + +### 테스트 + +- 관리 소프트웨어 업데이트 수가 여러 개여도 사이드바 badge에서는 1개 유형으로만 계산하고 펌웨어와 합산해 최대 2가 되는 계약을 검증하도록 업데이트 UI 회귀 테스트를 보강했습니다. + +## [0.2.22-r13] - 2026-09-28 + +### 개선 + +- 물리 Reset 버튼으로 관리자 비밀번호 복구가 활성화된 경우 일반 로그인 화면을 거치지 않고 곧바로 관리자 비밀번호 복구 화면으로 진입하도록 흐름을 개선했습니다. +- 복구 중에는 전체 root 세션을 자동 생성하지 않고 `system_root_password_status`와 `system_root_password_set` 두 RPC만 허용하는 15분 제한 세션을 발급해 복구 화면의 권한 범위를 최소화했습니다. +- 복구 marker가 없거나 root 비밀번호가 이미 설정된 경우 공개 recovery bridge가 세션을 발급하지 않도록 방어 조건을 추가했습니다. + +### 테스트 + +- recovery bridge의 marker/빈 root 비밀번호 조건, 제한 RPC grant, 일반 로그인보다 우선하는 복구 진입 순서와 production asset version 계약을 검증하는 회귀 테스트를 추가했습니다. + +## [0.2.22-r12] - 2026-09-28 + +### 추가 + +- SmartSafeHub Reset Policy v1을 추가해 Reset 버튼을 5~9초 누르면 관리자 비밀번호만 복구하고, 10초 이상 누르면 OpenWrt factory reset으로 모든 사용자 설정을 초기화하도록 동작을 분리했습니다. +- 물리 비밀번호 복구 시 WAN/LAN, Wi-Fi, SafeShield와 라이선스 설정은 유지하고 root 관리자 비밀번호만 비운 뒤 복구 상태로 재부팅합니다. +- 비밀번호가 비어 있는 복구 구간에는 Dropbear를 중지/비활성화하고, 새 비밀번호 설정이 끝나면 복구 전 SSH 활성 상태를 복원합니다. +- 복구 상태에서는 최초 설정과 구분되는 관리자 비밀번호 복구 화면을 표시하고 완료 후 새 비밀번호로 다시 로그인하도록 했습니다. + +### 호환성 + +- SmartSafeHub 전용 Reset handler가 `/etc/rc.button/reset`을 소유하도록 변경했습니다. 펌웨어는 OpenWrt의 기본 reset handler를 포함하지 않도록 `CONFIG_TARGET_BUTTON_CUSTOMIZATION=y`와 `CONFIG_TARGET_BUTTON_CUSTOMIZATION_RESET_DISABLED=y`를 사용해야 합니다. +- 기존 OpenWrt reset handler가 남아 있는 구형 펌웨어에서는 5초 이상 누름이 즉시 factory reset으로 해석될 수 있으므로, 관리 소프트웨어 단독 업그레이드를 차단하고 먼저 호환 펌웨어 업데이트를 요구합니다. + +### 테스트 + +- Reset 5~9초/10초 경계, 구형 펌웨어 업그레이드 차단 계약, 비밀번호 복구 marker, Dropbear 보호/복원, 실패 rollback과 복구 전용 UI를 검증하는 회귀 테스트를 추가했습니다. + +## [0.2.22-r11] - 2026-09-28 + +### 추가 + +- 설정의 `시스템 관리` 영역에서 현재 관리자 비밀번호를 확인한 뒤 새 비밀번호로 변경할 수 있는 전용 카드를 추가했습니다. +- 비밀번호 변경 전에 rpcd `session.login`으로 현재 비밀번호를 검증하고, 임시 검증 세션은 즉시 폐기하도록 했습니다. +- 새 비밀번호는 최초 설정과 동일하게 8자 이상, 영문자와 숫자 각각 1자 이상 정책을 적용하며 현재 비밀번호와 동일한 값은 허용하지 않습니다. +- 변경 성공 시 현재 ubus/LuCI 로그인 세션을 종료하고 로그인 화면으로 이동해 새 비밀번호로 다시 인증하도록 했습니다. +- 최초 설정 화면과 설정 화면이 동일한 프런트엔드 비밀번호 정책 유틸리티를 공유하도록 정리했습니다. + +### 개선 + +- 시스템 관리 카드를 `관리자 비밀번호`와 `설정 백업 및 복원`의 2열 구성으로 정리하고 `시스템 도구`는 그 아래 전체 폭으로 배치했습니다. + +### 테스트 + +- 관리자 비밀번호 변경 RPC, ACL, 현재 비밀번호 검증, 세션 폐기, 공통 비밀번호 정책과 설정 UI 재로그인 흐름을 검증하는 회귀 테스트를 추가했습니다. + +## [0.2.22-r10] - 2026-09-28 + +### 수정 + +- 관리 소프트웨어 업데이트 설치 중 진행 패널에서 `text-sky-800` 및 `text-sky-950`이 다크 모드 색상으로 변환되지 않아 제목과 안내 문구가 배경에 묻히던 문제를 수정했습니다. +- 설치 진행 패널의 제목, 설명과 아이콘이 다크 모드에서도 동일한 sky 계열의 읽기 쉬운 대비를 유지하도록 색상 매핑을 보완했습니다. + +### 테스트 + +- 업데이트 UI 계약 테스트에 설치 진행 패널의 `text-sky-800` 및 `text-sky-950` 다크 모드 매핑 회귀 검증을 추가했습니다. + +## [0.2.22-r9] - 2026-09-28 + +### 개선 + +- 관리 소프트웨어뿐 아니라 새 펌웨어가 있는 경우에도 사이드바 `업데이트` 메뉴에 알림 badge를 표시합니다. +- 관리 소프트웨어와 펌웨어 업데이트가 동시에 있으면 하나의 badge에서 설치 가능한 업데이트 수를 합산해 표시합니다. +- 펌웨어 상태를 현재 페이지와 관계없이 백그라운드에서 조회해 네트워크, Wi-Fi, SafeShield 등 다른 화면에서도 펌웨어 업데이트 알림이 유지되도록 했습니다. + +### 테스트 + +- 사이드바 업데이트 badge가 관리 소프트웨어 업데이트 수와 펌웨어 `updateAvailable` 상태를 합산하는지, 펌웨어 상태 조회가 전역에서 활성화되는지 검증하는 회귀 조건을 추가했습니다. + +## [0.2.22-r8] - 2026-09-28 + +### 개선 + +- 고정 IPv4의 IP 주소, 기본 게이트웨이, 기본/보조 DNS를 LAN 공유기 주소와 동일한 4개 octet 입력 UI로 통일해 모바일과 데스크톱에서 주소 입력 방식을 일관되게 했습니다. +- LAN과 WAN이 같은 공용 IPv4 octet 컴포넌트를 사용하도록 정리해 입력 제약과 모바일 레이아웃이 기능별로 달라지지 않도록 했습니다. +- 사이드바와 페이지 제목의 `네트워크 설정`을 더 간결한 `네트워크`로 변경하고 대시보드 이동 문구도 `네트워크 보기`로 맞췄습니다. +- 보조 DNS의 4개 octet을 모두 비우면 선택 필드가 빈 값으로 정상 복원되도록 공용 입력 컴포넌트에서 처리합니다. + +### 테스트 + +- WAN 고정 IPv4의 주소/게이트웨이/DNS가 공용 octet 입력을 사용하는지, LAN도 같은 컴포넌트를 재사용하는지와 `네트워크` 메뉴명을 검증하는 회귀 조건을 추가했습니다. + +## [0.2.22-r7] - 2026-09-28 + +### 개선 + +- 사이드바의 `인터넷`과 `LAN` 메뉴를 `네트워크 설정` 하나로 통합해 WAN 연결과 내부 LAN/DHCP 구성을 한 화면에서 관리할 수 있도록 했습니다. +- 통합 화면에서도 WAN과 LAN의 저장되지 않은 변경 상태, 입력 검증, 저장/적용 및 재연결 동작은 서로 독립적으로 유지합니다. +- 기존 `#wan`, `#lan` hash는 `#network` 화면으로 계속 연결되도록 호환 경로를 유지하고, 대시보드의 네트워크 상세/충돌 해결 링크도 새 화면으로 통일했습니다. +- WAN 영역과 내부 네트워크 영역 사이에 명확한 구분선을 두어 하나의 화면에서도 각 설정 범위를 쉽게 구분할 수 있도록 했습니다. + +### 테스트 + +- 네트워크 설정 단일 route, 기존 WAN/LAN hash 호환, 메뉴 통합, 대시보드 링크 및 WAN/LAN 동시 새로고침을 검증하는 회귀 테스트를 추가했습니다. +- 기존 WAN, LAN, IPTV와 내비게이션 계약 테스트를 새 통합 메뉴 구조에 맞게 갱신했습니다. + +## [0.2.22-r6] - 2026-09-28 + +### 추가 + +- `네트워크 > 인터넷` WAN 관리 화면을 추가해 현재 연결 상태, IPv4 주소, 기본 게이트웨이, DNS와 연결 시간을 확인할 수 있도록 했습니다. +- WAN 연결 방식을 자동 IP(DHCP), PPPoE, 고정 IPv4로 변경할 수 있고 별도의 수동 WAN 재연결 기능을 제공합니다. +- PPPoE 비밀번호는 읽기 API에서 원문을 반환하지 않고 설정 여부만 노출하며, 사용자가 새 비밀번호를 입력하지 않으면 기존 값을 유지합니다. +- 고정 IPv4 설정에서 주소, CIDR 서브넷 마스크, 기본 게이트웨이와 기본/보조 DNS를 설정할 수 있습니다. +- WAN 설정 변경은 LAN 관리 연결을 재시작하지 않고 WAN 인터페이스만 지연 재연결하며, 재연결 예약 실패 시 UCI 설정을 이전 값으로 복구합니다. +- WAN 설정 변경을 최근 활동의 `인터넷 설정 변경` 이벤트로 표시합니다. + +### 문서 + +- README의 네트워크 관리 범위에 WAN DHCP/PPPoE/고정 IPv4 설정과 재연결 기능을 추가했습니다. + +### 테스트 + +- WAN RPC/ACL, UCI 저장, 입력 검증, PPPoE 비밀번호 비노출/보존, WAN-only 재연결, 메뉴/화면/최근 활동 연결을 검증하는 계약 테스트를 추가했습니다. +- OpenWrt ucode 런타임 fixture에서 PPPoE `/32` 주소, gateway/DNS 상태 해석과 기존 비밀번호를 노출하지 않는 무변경 저장을 검증합니다. + +## [0.2.22-r5] - 2026-09-28 + +### 정리 + +- 개발 중 잠시 사용했던 독립 `smartsafehub-safeshield-events` 서비스의 업그레이드 호환 처리와 관련 계약 테스트를 제거했습니다. 실제 SafeShield 비동기 갱신 관찰은 기존과 동일하게 통합 `smartsafehub-events` daemon이 담당합니다. +- 통합 이벤트 daemon의 SafeShield observer 상태/lock 파일명을 `safeshield-observer.*`로 정리해 더 이상 존재하지 않는 독립 서비스와 혼동되지 않도록 했습니다. + +### 테스트 + +- 이벤트 테스트가 새 SafeShield observer 상태 파일명을 사용하도록 갱신하고, 패키지/Cloud activity 계약에서 존재하지 않는 legacy 서비스의 stop/disable migration 요구사항을 제거했습니다. + +## [0.2.22-r4] - 2026-09-28 + +### 개선 + +- iPhone 15 Pro처럼 폭이 좁은 화면에서도 대시보드 `최근 활동`의 `전체 보기`와 `최근 24시간 보호 활동`의 `상세 통계` 링크가 제목 오른쪽에 유지되도록 섹션 헤더 배치를 정리했습니다. +- SafeShield 최근 24시간 보호 활동의 집계 시각을 절대 날짜 대신 `방금 전`, `N분 전`, `N시간 전` 같은 상대 시간으로 표시하고, 정확한 시각은 title 정보로 유지합니다. + +### 테스트 + +- 대시보드 계약 테스트에 최근 활동 액션의 제목 행 배치, 모바일 줄바꿈 방지, SafeShield 집계 상대 시간과 1분 주기 갱신 연동을 검증하는 회귀 조건을 추가했습니다. + +## [0.2.22-r3] - 2026-09-28 + +### 수정 + +- 일부 OpenWrt 환경의 `tr`이 POSIX 대소문자 클래스를 기대한 방식으로 처리하지 않아 SafeShield의 `pro` 플랜이 Health Reporter 상태에서 `prp`로 변형되던 문제를 수정했습니다. 라이선스/상태 토큰의 대소문자 정규화는 명시적인 ASCII 문자 매핑을 사용하도록 변경했습니다. +- 같은 대소문자 변환을 사용하던 Cloud 최근 활동 동기화의 라이선스 phase/plan/action 처리도 공통 ASCII 정규화 helper를 사용하도록 통일해 기기별 `tr` 구현 차이로 권한 판정이 달라지지 않도록 했습니다. + +### 테스트 + +- 공통 shell helper가 `pro`/`ULTIMATE` 같은 라이선스 토큰을 OpenWrt 호환 방식으로 정확히 대소문자 변환하는지 검증하는 회귀 테스트를 추가했습니다. +- Health Reporter에서 `pro` + `ACTIVE`가 `PRO` + `active`로 정규화되고 유료 권한으로 판정되는지 검증합니다. +- Cloud 최근 활동 동기화가 POSIX 문자 클래스 기반 `tr` 대소문자 변환을 다시 사용하지 않는지 검증합니다. + +## [0.2.22-r2] - 2026-09-28 + +### 수정 + +- iPhone 15 Pro처럼 폭이 좁은 모바일 화면에서 LAN 공유기 IP와 DHCP 시작/종료 주소의 4개 옥텟 입력이 카드 오른쪽을 벗어나 잘리던 문제를 수정했습니다. IPv4 입력을 WebKit의 input intrinsic width에 의존하는 flex 배치 대신 4개의 `minmax(0, 1fr)` 그리드로 고정해 모든 옥텟이 항상 카드 안에 표시됩니다. +- IPTV 사용 스위치가 모바일의 44px 터치 타깃 규칙에 의해 세로로 늘어나 thumb 위치가 어긋나던 문제를 수정했습니다. 다른 SmartSafeHub 설정 화면과 동일한 공통 스위치 크기를 사용하고 제목 행 안에 배치해 모바일에서도 안정적으로 표시됩니다. + +### 테스트 + +- LAN 계약 테스트에 모바일 IPv4 4열 그리드와 입력 폭 제한 회귀 검증을 추가했습니다. +- IPTV 계약 테스트에 공통 스위치 geometry 사용과 모바일 제목 행 배치 회귀 검증을 추가했습니다. + ## [0.2.22-r1] - 2026-09-27 ### 개선 diff --git a/luci-app-smartsafehub/Makefile b/luci-app-smartsafehub/Makefile index 2a58726cd..ed3575c56 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.22 -PKG_RELEASE:=1 +PKG_VERSION:=0.2.23 +PKG_RELEASE:=2 PKG_MAINTAINER:=Beomjun Kang PKG_LICENSE:=GPL-3.0-or-later @@ -27,6 +27,15 @@ endef define Package/luci-app-smartsafehub/preinst #!/bin/sh if [ -z "$${IPKG_INSTROOT}" ]; then + # SmartSafeHub Reset Policy v1 requires the firmware build to remove the + # stock OpenWrt /etc/rc.button/reset before this package owns that path. + # Refuse live upgrades on older firmware rather than risking a package file + # collision or showing 5-9 second recovery guidance while OpenWrt still + # interprets the same hold as an immediate factory reset. + if [ -f /etc/rc.button/reset ] && ! grep -Fq "SMARTSAFEHUB_RESET_POLICY='smartsafehub-v1'" /etc/rc.button/reset 2>/dev/null; then + echo "luci-app-smartsafehub: firmware update required for SmartSafeHub Reset Policy v1" >&2 + exit 1 + fi # r18 and earlier had Cloud activity sync enabled implicitly whenever the # device was eligible. Preserve that behavior across the r19 upgrade even # if opkg replaces an otherwise-unmodified conffile with the new default-off @@ -36,14 +45,6 @@ if [ -z "$${IPKG_INSTROOT}" ]; then mkdir -p /tmp/smartsafehub : > /tmp/smartsafehub/activity-cloud-sync-upgrade-enable fi - - # r12 initially ran SafeShield refresh observation in a separate daemon. - # Stop it before files are replaced so the unified event daemon is the only - # observer after this package is unpacked. - if [ -x /etc/init.d/smartsafehub-safeshield-events ]; then - /etc/init.d/smartsafehub-safeshield-events stop >/dev/null 2>&1 || true - /etc/init.d/smartsafehub-safeshield-events disable >/dev/null 2>&1 || true - fi fi exit 0 endef @@ -58,13 +59,6 @@ if [ -z "$${IPKG_INSTROOT}" ]; then rm -f /tmp/smartsafehub/activity-cloud-sync-upgrade-enable fi uci -q commit smartsafehub >/dev/null 2>&1 || true - # r12 initially shipped the SafeShield refresh observer as a separate procd - # service. Stop/disable it when upgrading to the unified event daemon while - # the legacy init script is still present. - if [ -x /etc/init.d/smartsafehub-safeshield-events ]; then - /etc/init.d/smartsafehub-safeshield-events stop >/dev/null 2>&1 || true - /etc/init.d/smartsafehub-safeshield-events disable >/dev/null 2>&1 || true - fi if [ -x /etc/init.d/smartsafehub-events ]; then /etc/init.d/smartsafehub-events enable /etc/init.d/smartsafehub-events restart >/dev/null 2>&1 || true @@ -82,6 +76,10 @@ if [ -z "$${IPKG_INSTROOT}" ]; then if [ -x /etc/init.d/smartsafehub-maintenance ]; then /etc/init.d/smartsafehub-maintenance enable fi + if [ -x /etc/init.d/smartsafehub-password-recovery ]; then + /etc/init.d/smartsafehub-password-recovery enable + /etc/init.d/smartsafehub-password-recovery start >/dev/null 2>&1 || true + fi if [ -x /etc/init.d/smartsafehub-health ]; then /etc/init.d/smartsafehub-health enable /etc/init.d/smartsafehub-health restart >/dev/null 2>&1 || true diff --git a/luci-app-smartsafehub/README.md b/luci-app-smartsafehub/README.md index 43a64c028..574c148ee 100644 --- a/luci-app-smartsafehub/README.md +++ b/luci-app-smartsafehub/README.md @@ -25,14 +25,15 @@ SmartSafeHub는 OpenWrt 공유기를 위한 통합 홈 게이트웨이 관리 UI - **반응형 UI**: 데스크톱과 모바일, 라이트/다크 모드를 지원합니다. - **장치 대시보드**: 시스템 상태, WAN, 리소스 사용량, 최근 활동과 진단 상태를 한 화면에서 확인합니다. -- **네트워크 관리**: LAN/DHCP 설정, WAN/LAN 대역 충돌 감지와 안전한 대역 추천을 제공합니다. +- **네트워크 관리**: WAN DHCP/PPPoE/고정 IPv4 연결과 재연결, LAN/DHCP 설정, WAN/LAN 대역 충돌 감지와 안전한 대역 추천을 제공합니다. - **Wi-Fi 관리**: SSID, 사용 여부와 WPA2/WPA3 보안 설정을 관리하고 실패 시 설정을 롤백합니다. - **연결 기기**: DHCP, ARP와 무선 정보를 결합해 현재 연결된 기기를 보여줍니다. - **SafeShield 통합**: DNS 보호 상태, 차단 통계, 사용자 Allow/Block 규칙과 차단 목록 갱신을 관리합니다. - **업데이트 관리**: SmartSafeHub 관리 소프트웨어와 펌웨어 업데이트를 확인하고 적용합니다. - **장치 진단**: 로컬 진단, 진단 정보 다운로드와 사용자가 동의한 원격 Health Reporter를 제공합니다. +- **관리자 보안**: 최초 관리자 비밀번호 설정과 현재 비밀번호 확인을 거친 비밀번호 변경, 변경 후 재로그인을 제공합니다. - **최근 활동**: 인터넷, 보호, 업데이트, 라이선스와 진단 상태 변화를 로컬 이벤트 타임라인으로 정규화합니다. -- **IPTV Beta**: SK Broadband와 LG U+ 환경을 위한 IGMP Proxy/Snooping 구성을 제공합니다. +- **IPTV (Beta)**: SK Broadband와 LG U+ 환경을 위한 IGMP Proxy/Snooping 구성을 제공합니다. 기능별 동작과 현재 제약은 [docs/FEATURES.md](docs/FEATURES.md)에서 확인할 수 있습니다. diff --git a/luci-app-smartsafehub/docs/ARCHITECTURE.md b/luci-app-smartsafehub/docs/ARCHITECTURE.md index 5bbd15474..8a217dda9 100644 --- a/luci-app-smartsafehub/docs/ARCHITECTURE.md +++ b/luci-app-smartsafehub/docs/ARCHITECTURE.md @@ -197,7 +197,7 @@ Shadow root에는 버전이 포함된 `app.css` 링크와 Preact mount point가 | route | hash | 화면 | |---|---|---| | `home` | `#home` | 장치 대시보드 | -| `lan` | `#lan` | LAN 및 DHCP | +| `network` | `#network` | WAN 인터넷 연결 + LAN 및 DHCP | | `wifi` | `#wifi` | Wi-Fi | | `devices` | `#devices` | 연결된 기기 | | `safeshield` | `#safeshield` | SafeShield | @@ -394,7 +394,7 @@ ucode module loader가 모듈을 캐시하므로 기능 모듈은 하나의 ubus #### `smartsafehub-network.uc` / `network-management.uc` -`smartsafehub-network.uc`는 LAN 구현을 별도 `smartsafehub_network` ubus 객체로 등록해 핵심 `smartsafehub` RPC와 장애 범위를 분리합니다. LAN 화면은 이 객체를 직접 호출하며 핵심 RPC가 같은 `rpcd` 프로세스의 다른 ucode 객체를 동기 `ubus.call()`로 다시 호출하지 않습니다. LAN 객체는 자체적으로 관리자 비밀번호 설정 상태를 확인하고 LuCI ACL에는 LAN 메서드만 최소 권한으로 노출합니다. 따라서 LAN 구현이 로드되지 않아도 `system_root_password_status`와 대시보드용 기존 RPC 객체는 유지됩니다. +`smartsafehub-network.uc`는 LAN 구현을 별도 `smartsafehub_network` ubus 객체로 등록해 핵심 `smartsafehub` RPC와 장애 범위를 분리합니다. `네트워크` 화면의 내부 네트워크 영역은 이 객체를 직접 호출하며 핵심 RPC가 같은 `rpcd` 프로세스의 다른 ucode 객체를 동기 `ubus.call()`로 다시 호출하지 않습니다. LAN 객체는 자체적으로 관리자 비밀번호 설정 상태를 확인하고 LuCI ACL에는 LAN 메서드만 최소 권한으로 노출합니다. 따라서 LAN 구현이 로드되지 않아도 `system_root_password_status`와 대시보드용 기존 RPC 객체는 유지됩니다. 기존 `network-management.uc` 파일명은 그대로 유지합니다. LAN backend 격리와 무관한 파일명 rename을 피하여 패치/checkout 과정에서 구현 모듈이 누락되는 회귀를 방지합니다. @@ -595,7 +595,7 @@ WifiPage form ### 7.4 대시보드 Internet 상태 -대시보드는 핵심 `smartsafehub.status`의 WAN 링크/IP/프로토콜과 `smartsafehub_network.lan_settings`의 WAN/LAN subnet 및 충돌 판정을 함께 사용합니다. `INTERNET` 개요 카드는 WAN이 올라와 있고 subnet 충돌이 없을 때 정상 상태와 다른 개요 카드와 동일한 `자세히 보기` 링크를 표시하며, WAN/LAN 대역이 겹치면 연결 자체가 up이어도 `네트워크 충돌` 경고와 `#lan` `해결하기` 링크를 우선 표시합니다. LAN 상태 조회가 실패해도 핵심 대시보드 상태 조회와 렌더링은 유지하고, 대역 관련 메타데이터만 확인 필요 상태로 처리합니다. +대시보드는 핵심 `smartsafehub.status`의 WAN 링크/IP/프로토콜과 `smartsafehub_network.lan_settings`의 WAN/LAN subnet 및 충돌 판정을 함께 사용합니다. `INTERNET` 개요 카드는 WAN이 올라와 있고 subnet 충돌이 없을 때 정상 상태와 다른 개요 카드와 동일한 `자세히 보기` 링크를 표시하며, WAN/LAN 대역이 겹치면 연결 자체가 up이어도 `네트워크 충돌` 경고와 `#network` `해결하기` 링크를 우선 표시합니다. LAN 상태 조회가 실패해도 핵심 대시보드 상태 조회와 렌더링은 유지하고, 대역 관련 메타데이터만 확인 필요 상태로 처리합니다. `네트워크 보호 활동 > 연결 상태` 카드는 연결 기기 수를 중복 표시하지 않고 WAN IP/프로토콜, 상위 네트워크, LAN 네트워크, 충돌 여부를 보여주는 네트워크 구성 요약 역할을 담당합니다. WAN IPv4가 RFC1918 사설 주소이면 `사설 네트워크`로 표시하되 이를 장애로 취급하지 않습니다. 대시보드 수동 새로고침은 기존 상태 소스와 함께 LAN 상태도 갱신합니다. @@ -839,9 +839,10 @@ SmartSafeHub의 휘발성 런타임 파일은 `/tmp/smartsafehub/` 한 단계 자동화 설정은 `/etc/config/smartsafehub`의 `updates` section에 보존됩니다. `smartsafehub-updater` procd 서비스는 기본 6시간 주기로 확인하며, 자동 설치는 기본 비활성화 상태입니다. 자동 설치를 활성화하면 지정 시각의 다음 실행 기회에 하루 한 번만 설치를 시도합니다. 공유기가 예약 시각 이후에 부팅된 경우 그날의 지난 예약을 즉시 소급 실행하지 않고 다음 예약 시각까지 기다립니다. -### LAN 입력 UX 안전장치 +### IPv4 입력 UX 안전장치 -LAN 화면은 공유기 IPv4 주소를 4개 octet으로 분리해 입력받고, DHCP 시작/종료 주소는 공유기 주소의 앞 3개 octet을 읽기 전용 prefix로 사용한다. 공유기 prefix가 바뀌면 DHCP host octet은 유지하면서 같은 prefix로 동기화한다. 이 프런트엔드 제약은 사용자 입력 오류를 줄이기 위한 것이며, 최종 subnet·DHCP 범위 검증은 계속 `network-management.uc` backend가 담당한다. +`네트워크` 화면의 내부 네트워크 영역은 공유기 IPv4 주소를 4개 octet으로 분리해 입력받고, DHCP 시작/종료 주소는 공유기 주소의 앞 3개 octet을 읽기 전용 prefix로 사용한다. 공유기 prefix가 바뀌면 DHCP host octet은 유지하면서 같은 prefix로 동기화한다. 이 프런트엔드 제약은 사용자 입력 오류를 줄이기 위한 것이며, 최종 subnet·DHCP 범위 검증은 계속 `network-management.uc` backend가 담당한다. +WAN 고정 IPv4의 주소, gateway와 DNS도 동일한 `Ipv4OctetInput` 컴포넌트를 사용해 LAN/WAN 입력 제약과 모바일 레이아웃을 공유한다. ### Hash route 새로고침 보존 diff --git a/luci-app-smartsafehub/docs/DEVELOPMENT.md b/luci-app-smartsafehub/docs/DEVELOPMENT.md index 0ea790cd4..e1803f2de 100644 --- a/luci-app-smartsafehub/docs/DEVELOPMENT.md +++ b/luci-app-smartsafehub/docs/DEVELOPMENT.md @@ -121,9 +121,11 @@ cd frontend npm ci ``` -### LAN/DHCP 주소 입력 안전장치 +### IPv4 주소 입력 안전장치 -LAN 설정 화면은 공유기 IPv4 주소를 4개의 octet 입력으로 분리해 받습니다. DHCP 시작/종료 주소는 공유기 주소의 앞 3개 octet을 고정해서 표시하고 마지막 octet만 수정할 수 있습니다. 공유기 주소의 앞 3개 octet을 변경하면 DHCP 범위도 같은 prefix로 즉시 동기화되어 서로 다른 대역을 실수로 저장하는 가능성을 줄입니다. 실제 저장 시에는 backend의 subnet/DHCP 범위 검증도 그대로 적용됩니다. +`네트워크` 화면의 내부 네트워크 영역은 공유기 IPv4 주소를 4개의 octet 입력으로 분리해 받습니다. DHCP 시작/종료 주소는 공유기 주소의 앞 3개 octet을 고정해서 표시하고 마지막 octet만 수정할 수 있습니다. 공유기 주소의 앞 3개 octet을 변경하면 DHCP 범위도 같은 prefix로 즉시 동기화되어 서로 다른 대역을 실수로 저장하는 가능성을 줄입니다. 실제 저장 시에는 backend의 subnet/DHCP 범위 검증도 그대로 적용됩니다. + +WAN 고정 IPv4의 주소, 기본 게이트웨이, 기본/보조 DNS도 `Ipv4OctetInput` 공용 컴포넌트를 사용합니다. 모든 octet을 비우면 빈 문자열로 정규화해 보조 DNS 같은 선택 필드를 완전히 지울 수 있으며, 실제 IPv4 유효성 검증은 저장 직전 프런트엔드와 backend에서 다시 수행합니다. ## 개발 서버 diff --git a/luci-app-smartsafehub/docs/FEATURES.md b/luci-app-smartsafehub/docs/FEATURES.md index e15ed11fe..ce47487d8 100644 --- a/luci-app-smartsafehub/docs/FEATURES.md +++ b/luci-app-smartsafehub/docs/FEATURES.md @@ -38,7 +38,7 @@ README에는 제품을 빠르게 파악하는 데 필요한 핵심 기능만 유 - 대시보드 장치 정보와 설정 페이지 시스템 상태 모두 SmartSafeHub 커스텀 펌웨어가 설치된 이미지에서는 Hub가 resolve한 제품 릴리즈 버전과 immutable build ID를 우선 표시하고, 메타데이터가 없는 기존 이미지만 OpenWrt 배포판/버전/리비전으로 대체 표시. 커널은 현재 실제 실행 중인 커널 버전을 표시 - 실제 부팅 시각, 실행 시간, 시스템 부하와 메모리 사용량 - WAN 연결 상태, 프로토콜과 IPv4 주소 -- 대시보드 `INTERNET` 개요 카드는 WAN 주소/프로토콜과 함께 `smartsafehub_network.lan_settings`의 WAN/LAN subnet 충돌 여부를 표시합니다. 정상일 때는 `네트워크 충돌 없음`과 다른 개요 카드와 동일한 `자세히 보기` 링크를 표시하고, 충돌 시에는 `네트워크 충돌`과 `해결하기` 링크를 표시해 `네트워크 > LAN`으로 바로 이동할 수 있습니다. +- 대시보드 `INTERNET` 개요 카드는 WAN 주소/프로토콜과 함께 `smartsafehub_network.lan_settings`의 WAN/LAN subnet 충돌 여부를 표시합니다. 정상일 때는 `네트워크 충돌 없음`과 다른 개요 카드와 동일한 `자세히 보기` 링크를 표시하고, 충돌 시에는 `네트워크 충돌`과 `해결하기` 링크를 표시해 `Network > 네트워크`로 바로 이동할 수 있습니다. - `네트워크 보호 활동 > 연결 상태` 상세 카드는 상위 WAN 네트워크, SmartSafeHub LAN 네트워크와 대역 충돌 상태를 함께 보여주며, RFC1918 사설 WAN 주소는 `사설 네트워크`로 표시해 상위 NAT 환경임을 구분할 수 있게 합니다. - 사설 WAN IPv4 판별 코드는 TypeScript `noUncheckedIndexedAccess` 계약을 따르며, `split()` 결과의 배열 인덱스를 직접 비교하지 않고 존재 여부를 확인한 octet 변수만 사용합니다. 프런트엔드 CI의 `npm run build`/`tsc --noEmit`가 이 타입 안전성을 검증합니다. - SafeShield 차단 목록, 연결 기기 목록, 관리 소프트웨어 업데이트의 최근 확인 시각은 별도 하단 섹션 대신 각 개요 카드에서 `차단 목록 갱신: 41분 전`처럼 `항목: 상대 시간` 형식으로 표시하며, 마우스를 올리면 정확한 시각을 확인할 수 있음 @@ -48,9 +48,10 @@ README에는 제품을 빠르게 파악하는 데 필요한 핵심 기능만 유 - `시스템 상태 > 리소스 사용량` 카드 아래에서 최신 로컬 장치 진단을 함께 요약해 표시. 정상일 때는 전체 상태와 마지막 진단 시각/검사 항목 수를 간결하게 보여주고, 주의·이상 항목이 있으면 최대 2건을 바로 노출하며 상세 진단은 설정 페이지에서 확인 - 장치 진단 요약의 정상/주의/이상 배경과 세부 항목은 라이트/다크 테마에 각각 맞는 대비를 사용하며, 다크 모드에서 반투명 밝은 배경이 남지 않도록 전용 테마 매핑을 적용 -## LAN 및 DHCP 관리 +## 네트워크 (WAN/LAN 및 DHCP) -- SmartSafeHub 내부 IPv4 주소와 DHCP 할당 시작/종료 주소를 `네트워크 > LAN`에서 관리 +- `Network > 네트워크` 한 화면에서 WAN 연결 상태/방식과 SmartSafeHub 내부 IPv4 주소, DHCP 할당 시작/종료 주소를 함께 관리 +- 고정 IPv4의 IP 주소, 기본 게이트웨이와 기본/보조 DNS는 LAN 공유기 주소와 같은 4개 octet 입력 UI를 사용하고, 서브넷 마스크는 CIDR/netmask 선택 방식으로 유지 - OpenWrt 25.12 기본 형식인 `list ipaddr '192.168.1.1/24'`와 이전 `option ipaddr + option netmask` 형식을 모두 읽을 수 있으며, 25.12 list 형식으로 저장된 경우 CIDR/list 표현을 유지해 변경 - WAN과 LAN IPv4 subnet이 겹치면 충돌 상태와 상위 네트워크 대역을 표시하고, 활성 인터페이스와 겹치지 않는 안전한 `/24` 사설 대역을 자동 추천 - 추천 대역 자동 변경 시 공유기 주소는 `.1`, DHCP pool은 `.100~.249`를 기본으로 구성하고 기존 DHCP 사용 여부와 임대 시간은 유지 @@ -121,7 +122,7 @@ README에는 제품을 빠르게 파악하는 데 필요한 핵심 기능만 유 - 하나의 업데이트 페이지에서 `펌웨어 업데이트`와 `관리 소프트웨어 업데이트`를 독립된 두 영역으로 관리하며, 펌웨어를 최상단의 주요 업데이트 영역으로 표시 - 관리 소프트웨어 업데이트는 하나의 카드 안에서 현재 상태와 자동 업데이트 설정을 데스크톱 2열·모바일 1열로 구성해 펌웨어 업데이트와의 범위를 시각적으로 구분하며, 두 업데이트 카드의 소개 문구는 충분한 폭이 있는 데스크톱에서 한 줄로 유지해 짧은 마지막 문장 조각만 다음 줄로 떨어지지 않도록 표시 - `luci-app-smartsafehub`의 설치 버전과 저장소 업데이트 버전, 새 버전의 릴리즈 요약과 배포일 표시 -- 홈 알림 배너와 업데이트 메뉴 badge로 설치 가능한 SmartSafeHub 애플리케이션 업데이트 표시 +- 홈 알림 배너로 설치 가능한 SmartSafeHub 관리 소프트웨어 업데이트를 표시하고, 사이드바의 업데이트 메뉴 badge는 개별 패키지 수가 아니라 `관리 소프트웨어 업데이트 있음=1`, `펌웨어 업데이트 있음=1`의 유형 단위로 합산해 최대 2까지만 표시합니다. 따라서 관리 소프트웨어 업데이트가 여러 개여도 badge가 과도하게 커지지 않으면서 어느 화면에서도 두 업데이트 종류의 존재를 빠르게 인지할 수 있습니다. - 관리 소프트웨어는 1·6·12·24시간 자동 확인 주기와 지정 시각 자동 설치를 지원하고, 명시적 설정이 없는 신규 설치에서는 Stable 채널만 자동 설치를 기본 활성화하고 Beta 채널은 비활성화. 펌웨어는 별도의 업데이트 영역에서 확인하며 자동 설치하지 않음 - 자동 업데이트의 확인 여부·주기·자동 설치 여부·설치 시각을 변경해 저장값과 달라지면 설정 영역 헤더에 amber 경고 아이콘과 `저장되지 않음` 상태를 표시하고, 모든 값을 원래 상태로 되돌리거나 저장이 성공하면 경고를 제거 - 데몬 시작 시 펌웨어는 10초 뒤, 관리 소프트웨어는 20초 뒤 최초 업데이트 확인을 수행합니다. 초기 네트워크가 아직 준비되지 않아 실패하면 60초 간격으로 최대 3회까지만 재시도하며, 이후에는 설정된 일반 확인 주기로 돌아갑니다. 관리 소프트웨어는 실패한 확인 시도 시각도 별도로 기록해 저장소 장애 중 `apk update`가 1분마다 반복되지 않도록 제한합니다. @@ -161,6 +162,9 @@ README에는 제품을 빠르게 파악하는 데 필요한 핵심 기능만 유 - 공장 초기화나 새 설치 후 root 관리자 비밀번호가 비어 있으면 SmartSafeHub의 다른 관리 기능을 열기 전에 초기 보안 설정 화면을 강제로 표시합니다. 최초 비밀번호는 8자 이상이며 영문자와 숫자를 각각 하나 이상 포함해야 합니다. 초기 설정 UI에서는 시스템 계정명을 별도로 노출하지 않고 사용자에게 `관리자 비밀번호`로 안내합니다. - 초기 비밀번호 설정은 OpenWrt/LuCI의 `luci.setPassword` 경로를 통해 root 계정에 적용합니다. OpenWrt 25.12와의 호환성을 위해 `username`과 `password`만 전달하며, 성공 직후 기존 빈 비밀번호 인증 세션을 종료해 새 비밀번호로 다시 로그인하도록 합니다. root 비밀번호가 이미 설정된 장치에서는 이 초기 설정 RPC로 기존 비밀번호를 덮어쓸 수 없습니다. +- 초기 설정 이후에는 `설정 > 시스템 관리 > 관리자 비밀번호`에서 현재 비밀번호와 새 비밀번호를 입력해 변경할 수 있습니다. 현재 비밀번호는 rpcd `session.login`으로 검증하며 검증용 임시 세션은 즉시 폐기합니다. 새 비밀번호는 최초 설정과 동일한 정책을 사용하고 현재 비밀번호와 같은 값은 거부하며, 변경 성공 후 현재 로그인 세션을 종료해 새 비밀번호로 다시 로그인하도록 합니다. +- SmartSafeHub Reset Policy v1에서는 전원이 켜진 상태에서 Reset 버튼을 5~9초 누른 뒤 놓으면 관리자 비밀번호만 복구합니다. WAN/LAN, Wi-Fi, SafeShield와 라이선스 설정은 유지하고, 복구 중에는 passwordless root SSH가 열리지 않도록 Dropbear를 일시 비활성화합니다. 새 비밀번호를 설정하면 복구 전 SSH 활성 상태를 복원합니다. +- Reset 버튼을 10초 이상 누르면 OpenWrt `factoryreset -y`를 사용하는 기기 초기화를 실행합니다. 1~4초 구간은 아무 작업도 하지 않으며 1초 미만의 짧은 누름은 기존과 같이 재부팅합니다. 물리 버튼 정책은 `/etc/rc.button/reset`의 SmartSafeHub 전용 handler가 단독으로 소유합니다. - 현재 펌웨어, 실행 시간, 메모리와 부하 표시 - 장치의 현재 시간, IANA 시간대와 NTP 자동 동기화 사용 상태 표시 - NTP 자동 동기화가 활성화된 장치에서 `지금 동기화`를 눌러 `sysntpd`의 새 동기화 요청을 즉시 시작하고 최신 장치 시간을 다시 확인 @@ -186,7 +190,7 @@ README에는 제품을 빠르게 파악하는 데 필요한 핵심 기능만 유 - 복원 archive는 16MB로 제한하고 gzip/tar 구조, `/etc/config` 포함 여부와 위험한 경로를 검사하며, 업데이트나 펌웨어 작업 중에는 복원을 차단 - 설정 복원 후 현재 펌웨어 이미지의 `firmware.json`을 기준으로 `current_build_id`를 다시 동기화하고 자동 재부팅해 이전 백업의 펌웨어 identity가 남지 않도록 처리 - 설정 백업에는 Wi-Fi 비밀번호, 관리자 설정, VPN 키와 라이선스 정보 등 민감한 설정이 포함될 수 있으므로 안전한 위치에 보관해야 하며, 펌웨어 이미지와 설치 패키지 자체는 포함하지 않음 -- 데스크톱의 시스템 관리 영역은 `설정 백업 및 복원`과 `시스템 도구`를 1:1 두 열로 배치하고, 모바일에서는 한 열로 쌓아 불필요한 세로 스크롤을 줄임 +- 데스크톱의 시스템 관리 영역은 `관리자 비밀번호`와 `설정 백업 및 복원`을 1:1 두 열로 배치하고 `시스템 도구`는 그 아래 전체 폭으로 표시합니다. 모바일에서는 모든 카드를 한 열로 쌓습니다. - 백업/복원 카드는 절반 폭에서도 읽기 쉽도록 `현재 설정 백업`과 `설정 복원`을 compact 세로 섹션으로 표시 - 공유기 재부팅은 명시적인 확인 절차를 유지하면서 `시스템 도구` 안의 compact action으로 제공 - 업데이트 관리는 전용 `업데이트` 메뉴에만 두고 설정 화면의 중복 업데이트 진입점은 제공하지 않음 @@ -282,7 +286,7 @@ Cloud 전송을 끄면 activity-sync daemon을 잠시 중지한 뒤 Cloud-only o - Wi-Fi 화면은 각 radio에서 선택한 기본 LAN AP 하나만 관리합니다. - 게스트 Wi-Fi, VLAN, mesh, 방화벽과 상세 패키지 설정은 기존 LuCI에서 관리합니다. -- LAN 화면은 기본 `network.lan`/`dhcp.lan`만 관리하며 다중 LAN, VLAN별 DHCP와 방화벽 zone 구성은 기존 LuCI에서 관리합니다. +- `네트워크` 화면의 내부 네트워크 영역은 기본 `network.lan`/`dhcp.lan`만 관리하며 다중 LAN, VLAN별 DHCP와 방화벽 zone 구성은 기존 LuCI에서 관리합니다. - WAN/LAN 충돌 감지는 IPv4 기준이며 WAN 상태는 `network.interface.wan` 객체를 기준으로 합니다. - SafeShield 기능은 별도 `safeshield` 패키지와 공식 ubus API 계약에 의존하며, SmartSafeHub는 SafeShield의 상태 파일이나 init script를 직접 다루지 않습니다. - 프런트엔드 개발 서버만으로는 LuCI ACL과 실제 ubus 동작을 완전히 재현할 수 없습니다. diff --git a/luci-app-smartsafehub/docs/OPERATIONS.md b/luci-app-smartsafehub/docs/OPERATIONS.md index b0b8a05c7..7b0519b52 100644 --- a/luci-app-smartsafehub/docs/OPERATIONS.md +++ b/luci-app-smartsafehub/docs/OPERATIONS.md @@ -45,7 +45,7 @@ ubus -v list smartsafehub ubus call smartsafehub status '{}' ``` -LAN/DHCP 구현은 기존 관리 RPC의 가용성을 보호하기 위해 별도 `smartsafehub_network` ubus 객체로 격리되어 있습니다. LAN 화면은 같은 `rpcd` 프로세스 안에서 다른 객체를 동기 프록시하지 않고 이 객체를 직접 호출합니다. `smartsafehub_network`는 자체적으로 관리자 비밀번호 설정 상태를 확인하며 LuCI ACL도 LAN 읽기/쓰기 메서드에만 제한됩니다. +LAN/DHCP 구현은 기존 관리 RPC의 가용성을 보호하기 위해 별도 `smartsafehub_network` ubus 객체로 격리되어 있습니다. `네트워크` 화면의 내부 네트워크 영역은 같은 `rpcd` 프로세스 안에서 다른 객체를 동기 프록시하지 않고 이 객체를 직접 호출합니다. `smartsafehub_network`는 자체적으로 관리자 비밀번호 설정 상태를 확인하며 LuCI ACL도 LAN 읽기/쓰기 메서드에만 제한됩니다. ```bash ubus -v list smartsafehub_network @@ -140,6 +140,20 @@ daemon의 startup/check 대기는 foreground `sleep`이 아니라 interrupt 가 `tests/test-license.sh`는 activate/status 동기화, stale activation lock 복구, activation 진단 필드 보존과 장기 sleep 중 SIGTERM 정상 종료를 검증합니다. 각 시나리오는 mock 환경을 명시적으로 초기화해 Linux `dash`와 macOS `/bin/sh`처럼 함수 앞 임시 환경 변수의 처리 차이가 있는 환경에서도 이전 실패 주기의 값이 다음 테스트에 누적되지 않도록 합니다. + +## SmartSafeHub Reset Policy v1 + +SmartSafeHub 펌웨어는 OpenWrt 기본 `/etc/rc.button/reset`과 제품 전용 Reset 정책이 동시에 존재하지 않도록 빌드해야 합니다. OpenWrt 소스 빌드 config에서 `CONFIG_TARGET_BUTTON_CUSTOMIZATION=y`와 `CONFIG_TARGET_BUTTON_CUSTOMIZATION_RESET_DISABLED=y`를 활성화하고, firmware image에 포함되는 `luci-app-smartsafehub`가 `/etc/rc.button/reset`을 제공합니다. + +- 1초 미만: 재부팅 +- 1~4초: 동작 없음 +- 5~9초: 관리자 비밀번호 복구 +- 10초 이상: `factoryreset -y` 후 재부팅 + +`0.2.22-r12` 이상 패키지는 실행 중인 장치에 OpenWrt 기본 reset handler가 남아 있으면 live package upgrade를 중단합니다. 이 경우 관리 소프트웨어만 먼저 올리지 말고 Reset Policy v1 config로 빌드한 펌웨어를 먼저 설치해야 합니다. 펌웨어 이미지에 패키지가 함께 포함되는 정상 빌드에서는 rootfs 생성 시 기본 handler가 제거된 상태이므로 충돌하지 않습니다. + +비밀번호 복구는 `/etc/smartsafehub/password-recovery` marker로 추적합니다. helper는 root 비밀번호만 비우고 Dropbear의 기존 enable 상태를 marker에 기록한 뒤 SSH를 중지/비활성화합니다. 재부팅 후 공개 recovery bridge는 marker가 존재하고 root 비밀번호가 비어 있을 때만 `system_root_password_status`와 `system_root_password_set` 두 RPC로 제한된 15분 ubus 세션을 발급하므로 일반 로그인 화면 없이 복구 UI로 바로 진입할 수 있습니다. 새 관리자 비밀번호 설정이 완료되면 marker를 삭제하고 이전 SSH enable 상태를 복원합니다. + ## 진단 다운로드 확인 시스템 화면의 **진단 정보 다운로드**를 눌렀을 때 JSON 파일이 생성되어야 합니다. 현재 구현에는 `smartsafehub.system_diagnostics` RPC가 없습니다. diff --git a/luci-app-smartsafehub/frontend/package-lock.json b/luci-app-smartsafehub/frontend/package-lock.json index b5f1a2a18..d692b0e5b 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.22", + "version": "0.2.23", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "luci-app-smartsafehub-frontend", - "version": "0.2.22", + "version": "0.2.23", "dependencies": { "chart.js": "4.5.1", "preact": "10.29.8" diff --git a/luci-app-smartsafehub/frontend/package.json b/luci-app-smartsafehub/frontend/package.json index c2aab1393..263d9df8f 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.22", + "version": "0.2.23", "type": "module", "engines": { "node": ">=24.0" diff --git a/luci-app-smartsafehub/frontend/src/api/passwordRecovery.ts b/luci-app-smartsafehub/frontend/src/api/passwordRecovery.ts new file mode 100644 index 000000000..3218e9598 --- /dev/null +++ b/luci-app-smartsafehub/frontend/src/api/passwordRecovery.ts @@ -0,0 +1,38 @@ +const RECOVERY_ENDPOINT = '/cgi-bin/smartsafehub-password-recovery'; +const SESSION_ID_PATTERN = /^[0-9a-f]{32}$/i; + +interface RecoverySessionResponse { + active?: boolean; + sessionId?: string; +} + +function isRecoverySessionResponse(value: unknown): value is RecoverySessionResponse { + return value !== null && typeof value === 'object' && !Array.isArray(value); +} + +export async function requestPasswordRecoverySession(): Promise { + const response = await fetch(RECOVERY_ENDPOINT, { + cache: 'no-store', + credentials: 'same-origin', + headers: { + Accept: 'application/json', + }, + redirect: 'error', + }); + + if (!response.ok) { + throw new Error(`Password recovery endpoint returned HTTP ${response.status}`); + } + + const payload: unknown = await response.json(); + if (!isRecoverySessionResponse(payload) || payload.active !== true) { + return null; + } + + const sessionId = payload.sessionId; + if (typeof sessionId !== 'string' || !SESSION_ID_PATTERN.test(sessionId)) { + throw new Error('Password recovery endpoint returned an invalid session id'); + } + + return sessionId; +} diff --git a/luci-app-smartsafehub/frontend/src/api/security.ts b/luci-app-smartsafehub/frontend/src/api/security.ts new file mode 100644 index 000000000..ee42f7cd0 --- /dev/null +++ b/luci-app-smartsafehub/frontend/src/api/security.ts @@ -0,0 +1,16 @@ +import type { RootPasswordStatus } from '../types/security'; +import { callApi } from './rpc'; + +export function changeRootPassword( + currentPassword: string, + newPassword: string, +): Promise { + return callApi( + 'smartsafehub', + 'system_root_password_change', + { + current_password: currentPassword, + new_password: newPassword, + }, + ); +} diff --git a/luci-app-smartsafehub/frontend/src/api/smartsafehub.ts b/luci-app-smartsafehub/frontend/src/api/smartsafehub.ts index d33a3b90a..7ac2fd947 100644 --- a/luci-app-smartsafehub/frontend/src/api/smartsafehub.ts +++ b/luci-app-smartsafehub/frontend/src/api/smartsafehub.ts @@ -35,6 +35,12 @@ import type { WifiUpdateInput, WifiUpdateResult, } from '../types/wifi'; +import type { + WanReconnectResult, + WanSettings, + WanSettingsInput, + WanUpdateResult, +} from '../types/wan'; import { callApi } from './rpc'; const API_OBJECT = 'smartsafehub'; @@ -222,6 +228,41 @@ export function requestSystemReboot(): Promise { return callApi(API_OBJECT, 'system_reboot', { confirm: 'reboot' }); } +export function fetchWanSettings(): Promise { + return callApi(LAN_API_OBJECT, 'wan_settings'); +} + +export function updateWanSettings( + input: WanSettingsInput, +): Promise { + return callApi( + LAN_API_OBJECT, + 'wan_update', + { + protocol: input.protocol, + pppoe_username: input.pppoeUsername, + pppoe_password: input.pppoePassword, + pppoe_password_changed: input.pppoePasswordChanged, + static_address: input.staticAddress, + static_prefix_length: input.staticPrefixLength, + static_gateway: input.staticGateway, + dns_primary: input.dnsPrimary, + dns_secondary: input.dnsSecondary, + confirm: 'apply', + }, + { timeoutMs: 10_000 }, + ); +} + +export function reconnectWan(): Promise { + return callApi( + LAN_API_OBJECT, + 'wan_reconnect', + { confirm: 'reconnect' }, + { timeoutMs: 10_000 }, + ); +} + export function fetchLanSettings(): Promise { return callApi(LAN_API_OBJECT, 'lan_settings'); } diff --git a/luci-app-smartsafehub/frontend/src/app/App.tsx b/luci-app-smartsafehub/frontend/src/app/App.tsx index 4ddcdba2b..8a90fcd61 100644 --- a/luci-app-smartsafehub/frontend/src/app/App.tsx +++ b/luci-app-smartsafehub/frontend/src/app/App.tsx @@ -9,6 +9,7 @@ import { useHashRoute } from '../hooks/useHashRoute'; import { useHealth } from '../hooks/useHealth'; import { useIptv } from '../hooks/useIptv'; import { useLan } from '../hooks/useLan'; +import { useWan } from '../hooks/useWan'; import { useSafeShieldActions } from '../hooks/useSafeShieldActions'; import { useSafeShieldRules } from '../hooks/useSafeShieldRules'; import { useSafeShieldStatistics } from '../hooks/useSafeShieldStatistics'; @@ -24,22 +25,26 @@ import { ConnectedDevicesPage } from '../pages/ConnectedDevicesPage'; import { HomePage } from '../pages/HomePage'; import { IptvPage } from '../pages/IptvPage'; import { LanPage } from '../pages/LanPage'; +import { WanPage } from '../pages/WanPage'; import { SafeShieldPage } from '../pages/SafeShieldPage'; import { SafeShieldRulesPage } from '../pages/SafeShieldRulesPage'; import { SettingsPage } from '../pages/SettingsPage'; import { UpdatePage } from '../pages/UpdatePage'; import { WifiPage } from '../pages/WifiPage'; -export function App() { +interface AppProps { + onAdministratorPasswordChanged: () => void; +} + +export function App({ onAdministratorPasswordChanged }: AppProps) { const route = useHashRoute(); const configurationBackup = useConfigurationBackup(); const activity = useActivityHistory(route === 'home' || route === 'activity'); const status = useStatus(route === 'home' || route === 'settings'); const updates = useSoftwareUpdates(true); - const firmware = useFirmwareUpdates( - route === 'system' || route === 'home' || route === 'settings', - ); - const lan = useLan(route === 'home' || route === 'lan'); + const firmware = useFirmwareUpdates(true); + const wan = useWan(route === 'network'); + const lan = useLan(route === 'home' || route === 'network'); const iptv = useIptv(route === 'iptv'); const wifi = useWifi(route === 'wifi'); const dashboardDevices = useConnectedDevices(route === 'home', false); @@ -61,8 +66,8 @@ export function App() { const current = route === 'activity' ? activity - : route === 'lan' - ? lan + : route === 'network' + ? wan : route === 'wifi' ? wifi : route === 'iptv' @@ -80,6 +85,42 @@ export function App() { let content: ComponentChildren; switch (route) { + case 'network': + content = ( +
+
+ void wan.reconnect()} + onRetry={() => void wan.refresh()} + onSave={wan.save} + /> +
+
+ void lan.refresh()} + onSave={lan.save} + /> +
+
+ ); + break; + case 'activity': content = ( void lan.refresh()} - onSave={lan.save} - /> - ); - break; - case 'wifi': content = ( 0; + const updateCount = + (managementSoftwareUpdateAvailable ? 1 : 0) + + (firmware.data?.updateAvailable ? 1 : 0); + return ( {content} diff --git a/luci-app-smartsafehub/frontend/src/app/AuthenticatedEntry.tsx b/luci-app-smartsafehub/frontend/src/app/AuthenticatedEntry.tsx index 824fec867..7c01ab62a 100644 --- a/luci-app-smartsafehub/frontend/src/app/AuthenticatedEntry.tsx +++ b/luci-app-smartsafehub/frontend/src/app/AuthenticatedEntry.tsx @@ -8,19 +8,23 @@ import { App } from './App'; type SetupPhase = 'checking' | 'required' | 'ready' | 'error'; interface AuthenticatedEntryProps { - onPasswordConfigured: () => void; + onAdministratorPasswordChanged: () => void; + onPasswordConfigured: (recovery: boolean) => void; } export function AuthenticatedEntry({ + onAdministratorPasswordChanged, onPasswordConfigured, }: AuthenticatedEntryProps) { const [phase, setPhase] = useState('checking'); + const [passwordRecovery, setPasswordRecovery] = useState(false); const refresh = useCallback(async () => { setPhase('checking'); try { const status = await readRootPasswordStatus(); + setPasswordRecovery(status.recovery); setPhase(status.configured ? 'ready' : 'required'); } catch { setPhase('error'); @@ -32,11 +36,16 @@ export function AuthenticatedEntry({ }, [refresh]); if (phase === 'ready') { - return ; + return ; } if (phase === 'required') { - return ; + return ( + onPasswordConfigured(passwordRecovery)} + recovery={passwordRecovery} + /> + ); } return ( diff --git a/luci-app-smartsafehub/frontend/src/app/routes.ts b/luci-app-smartsafehub/frontend/src/app/routes.ts index e8ab2f342..85fda5e18 100644 --- a/luci-app-smartsafehub/frontend/src/app/routes.ts +++ b/luci-app-smartsafehub/frontend/src/app/routes.ts @@ -1,7 +1,7 @@ export type AppRoute = | 'home' | 'activity' - | 'lan' + | 'network' | 'wifi' | 'iptv' | 'devices' @@ -34,11 +34,11 @@ export const ROUTES: readonly RouteDefinition[] = [ description: '현재 부팅 이후 인터넷, 보호, 업데이트와 진단 상태 변화를 확인합니다.', }, { - route: 'lan', - hash: '#lan', - label: 'LAN', - title: 'LAN', - description: '내부 네트워크 주소, DHCP 범위와 상위 네트워크 충돌을 관리합니다.', + route: 'network', + hash: '#network', + label: '네트워크', + title: '네트워크', + description: '인터넷 연결과 내부 네트워크 주소, DHCP 및 네트워크 충돌을 한 곳에서 관리합니다.', }, { route: 'wifi', diff --git a/luci-app-smartsafehub/frontend/src/components/ActivityTimeline.tsx b/luci-app-smartsafehub/frontend/src/components/ActivityTimeline.tsx index 1e28bbb42..85040b94f 100644 --- a/luci-app-smartsafehub/frontend/src/components/ActivityTimeline.tsx +++ b/luci-app-smartsafehub/frontend/src/components/ActivityTimeline.tsx @@ -72,6 +72,19 @@ function licensePlan(value: string | null): string | null { return value.toUpperCase(); } +function protocolLabelForActivity(protocol: string): string { + switch (protocol) { + case 'dhcp': + return '자동 IP(DHCP)'; + case 'pppoe': + return 'PPPoE'; + case 'static': + return '고정 IPv4'; + default: + return protocol.toUpperCase(); + } +} + export function activityPresentation(event: ActivityEvent): ActivityPresentation { switch (event.eventType) { case 'system.booted': @@ -222,6 +235,16 @@ export function activityPresentation(event: ActivityEvent): ActivityPresentation description: 'Wi-Fi 설정을 저장하고 무선 네트워크에 적용했습니다.', }; + case 'settings.wan.updated': { + const protocol = metadataString(event, 'protocol'); + return { + title: '인터넷 설정 변경', + description: protocol + ? `${protocolLabelForActivity(protocol)} 방식으로 WAN 설정을 변경했습니다.` + : 'WAN 인터넷 연결 설정을 변경했습니다.', + }; + } + case 'settings.lan.updated': return { title: 'LAN 설정 변경', diff --git a/luci-app-smartsafehub/frontend/src/components/DashboardSafeShieldActivity.tsx b/luci-app-smartsafehub/frontend/src/components/DashboardSafeShieldActivity.tsx index d59b78157..4dec21cf1 100644 --- a/luci-app-smartsafehub/frontend/src/components/DashboardSafeShieldActivity.tsx +++ b/luci-app-smartsafehub/frontend/src/components/DashboardSafeShieldActivity.tsx @@ -1,5 +1,5 @@ import type { ComponentChildren } from 'preact'; -import { formatNumber, formatTimestamp } from '../app/format'; +import { formatNumber, formatRelativeTime, formatTimestamp } from '../app/format'; import type { SafeShieldStatistics, SafeShieldStatisticsBucket, @@ -10,6 +10,7 @@ interface DashboardSafeShieldActivityProps { data: SafeShieldStatistics | null; error: string | null; loading: boolean; + nowTimestamp: number; refreshing: boolean; } @@ -66,6 +67,7 @@ export function DashboardSafeShieldActivity({ data, error, loading, + nowTimestamp, refreshing, }: DashboardSafeShieldActivityProps) { let content: ComponentChildren; @@ -136,25 +138,29 @@ export function DashboardSafeShieldActivity({ return (
-
-
-

- SafeShield activity -

-

+
+

+ SafeShield activity +

+
+

최근 24시간 보호 활동

-
-
- + 상세 통계 → - {data?.updatedAt ? ( -

- {refreshing ? '갱신 중…' : `집계 ${formatTimestamp(data.updatedAt)}`} -

- ) : null}
+ {data?.updatedAt ? ( +

+ {refreshing ? '갱신 중…' : `집계: ${formatRelativeTime(data.updatedAt, nowTimestamp)}`} +

+ ) : null}
{content} {error && data ? ( diff --git a/luci-app-smartsafehub/frontend/src/components/Ipv4OctetInput.tsx b/luci-app-smartsafehub/frontend/src/components/Ipv4OctetInput.tsx new file mode 100644 index 000000000..ed8ca9113 --- /dev/null +++ b/luci-app-smartsafehub/frontend/src/components/Ipv4OctetInput.tsx @@ -0,0 +1,60 @@ +interface Ipv4OctetInputProps { + disabled?: boolean; + label: string; + value: string; + onChange: (value: string) => void; +} + +export function splitIpv4(value: string): [string, string, string, string] { + const parts = value.split('.'); + return [parts[0] ?? '', parts[1] ?? '', parts[2] ?? '', parts[3] ?? '']; +} + +export function normalizeIpv4Octet(value: string): string { + return value.replace(/\D/g, '').slice(0, 3); +} + +function joinIpv4Octets(octets: [string, string, string, string]): string { + return octets.every((octet) => octet === '') ? '' : octets.join('.'); +} + +export function Ipv4OctetInput({ + disabled = false, + label, + value, + onChange, +}: Ipv4OctetInputProps) { + const octets = splitIpv4(value); + + const updateOctet = (index: number, nextValue: string) => { + const next = [...octets] as [string, string, string, string]; + next[index] = normalizeIpv4Octet(nextValue); + onChange(joinIpv4Octets(next)); + }; + + return ( +
+ {octets.map((octet, index) => ( +
+ updateOctet(index, event.currentTarget.value)} + pattern="[0-9]*" + spellcheck={false} + value={octet} + /> + {index < octets.length - 1 ? ( + + ) : null} +
+ ))} +
+ ); +} diff --git a/luci-app-smartsafehub/frontend/src/components/ProductNavigation.tsx b/luci-app-smartsafehub/frontend/src/components/ProductNavigation.tsx index a84729ba6..3315ca0ee 100644 --- a/luci-app-smartsafehub/frontend/src/components/ProductNavigation.tsx +++ b/luci-app-smartsafehub/frontend/src/components/ProductNavigation.tsx @@ -4,10 +4,10 @@ import { ROUTE_BY_NAME } from '../app/routes'; import type { AppRoute } from '../app/routes'; import { luciAdminUrl } from '../utils/luci'; import { - CableIcon, CloseIcon, ClockIcon, DevicesIcon, + GlobeIcon, HomeIcon, LogOutIcon, MenuIcon, @@ -42,7 +42,7 @@ const NAVIGATION_GROUPS: readonly { routes: readonly AppRoute[]; }[] = [ { label: 'Overview', routes: ['home', 'activity'] }, - { label: 'Network', routes: ['lan', 'wifi', 'iptv', 'devices'] }, + { label: 'Network', routes: ['network', 'wifi', 'iptv', 'devices'] }, { label: 'Security', routes: ['safeshield', 'rules'] }, { label: 'System', routes: ['system', 'settings'] }, ]; @@ -53,8 +53,8 @@ function NavigationIcon({ route }: { route: AppRoute }) { return ; case 'activity': return ; - case 'lan': - return ; + case 'network': + return ; case 'wifi': return ; case 'iptv': @@ -155,13 +155,14 @@ function NavigationItems({ {routeName === 'system' && updateCount > 0 ? ( collapsed ? ( {updateCount} ) : ( - + {updateCount} ) diff --git a/luci-app-smartsafehub/frontend/src/hooks/useHashRoute.ts b/luci-app-smartsafehub/frontend/src/hooks/useHashRoute.ts index 9ac2c5f35..5cf044a73 100644 --- a/luci-app-smartsafehub/frontend/src/hooks/useHashRoute.ts +++ b/luci-app-smartsafehub/frontend/src/hooks/useHashRoute.ts @@ -5,7 +5,9 @@ import type { AppRoute } from '../app/routes'; const HASH_ROUTES: Readonly> = { '#home': 'home', '#activity': 'activity', - '#lan': 'lan', + '#network': 'network', + '#wan': 'network', + '#lan': 'network', '#wifi': 'wifi', '#iptv': 'iptv', '#devices': 'devices', diff --git a/luci-app-smartsafehub/frontend/src/hooks/useWan.ts b/luci-app-smartsafehub/frontend/src/hooks/useWan.ts new file mode 100644 index 000000000..cabee111b --- /dev/null +++ b/luci-app-smartsafehub/frontend/src/hooks/useWan.ts @@ -0,0 +1,119 @@ +import { useCallback, useEffect, useRef, useState } from 'preact/hooks'; + +import { + fetchWanSettings, + reconnectWan, + updateWanSettings, +} from '../api/smartsafehub'; +import type { WanSettingsInput } from '../types/wan'; +import { errorMessage } from '../utils/errors'; +import { useAsyncResource } from './useAsyncResource'; + +export type WanFeedback = + | { kind: 'success' | 'warning' | 'error'; message: string } + | null; + +interface WanMutationState { + action: 'saving' | 'reconnecting' | null; + feedback: WanFeedback; +} + +const RUNTIME_REFRESH_DELAY_MS = 5_000; + +export function useWan(active: boolean) { + const resource = useAsyncResource({ + active, + fallbackError: '인터넷 연결 설정을 불러오지 못했습니다.', + loader: fetchWanSettings, + }); + const activeRef = useRef(active); + const [mutation, setMutation] = useState({ + action: null, + feedback: null, + }); + + useEffect(() => { + activeRef.current = active; + }, [active]); + + const scheduleRefresh = useCallback(() => { + window.setTimeout(() => { + if (activeRef.current) { + void resource.refresh(); + } + }, RUNTIME_REFRESH_DELAY_MS); + }, [resource.refresh]); + + const save = useCallback( + async (input: WanSettingsInput): Promise => { + setMutation({ action: 'saving', feedback: null }); + + try { + const result = await updateWanSettings(input); + resource.replaceData(result.settings); + setMutation({ + action: null, + feedback: { + kind: result.changed ? 'warning' : 'success', + message: result.changed + ? '인터넷 연결 설정을 저장했습니다. WAN을 다시 연결하는 동안 잠시 인터넷이 끊길 수 있습니다.' + : '변경된 인터넷 연결 설정이 없습니다.', + }, + }); + if (result.reconnectScheduled) { + scheduleRefresh(); + } + return true; + } catch (error) { + setMutation({ + action: null, + feedback: { + kind: 'error', + message: errorMessage(error, '인터넷 연결 설정을 저장하지 못했습니다.'), + }, + }); + return false; + } + }, + [resource.replaceData, scheduleRefresh], + ); + + const reconnect = useCallback(async (): Promise => { + setMutation({ action: 'reconnecting', feedback: null }); + + try { + const result = await reconnectWan(); + resource.replaceData(result.settings); + setMutation({ + action: null, + feedback: { + kind: 'warning', + message: 'WAN 재연결을 시작했습니다. 인터넷 연결이 다시 올라오면 상태를 자동으로 새로고침합니다.', + }, + }); + if (result.reconnectScheduled) { + scheduleRefresh(); + } + } catch (error) { + setMutation({ + action: null, + feedback: { + kind: 'error', + message: errorMessage(error, 'WAN 재연결을 시작하지 못했습니다.'), + }, + }); + } + }, [resource.replaceData, scheduleRefresh]); + + const dismissFeedback = useCallback(() => { + setMutation((current) => ({ ...current, feedback: null })); + }, []); + + return { + ...resource, + ...mutation, + save, + reconnect, + dismissFeedback, + }; +} diff --git a/luci-app-smartsafehub/frontend/src/login/LoginApp.tsx b/luci-app-smartsafehub/frontend/src/login/LoginApp.tsx index 06a10ddb5..6924dda02 100644 --- a/luci-app-smartsafehub/frontend/src/login/LoginApp.tsx +++ b/luci-app-smartsafehub/frontend/src/login/LoginApp.tsx @@ -332,6 +332,13 @@ export function LoginApp({ 입력한 계정 정보는 현재 공유기의 LuCI 인증 경로로만 전송됩니다.

+ + {showFallback ? (
- + + 전체 보기 → + + } + description="인터넷, 보호, 업데이트와 진단에서 실제 상태가 바뀐 시점을 확인합니다." + eyebrow="History" + id="dashboard-recent-activity-title" + title="최근 활동" + />
{activityLoading || (activityError && !activity) ? ( diff --git a/luci-app-smartsafehub/frontend/src/pages/InitialPasswordSetupPage.tsx b/luci-app-smartsafehub/frontend/src/pages/InitialPasswordSetupPage.tsx index 0c73af883..cda96b777 100644 --- a/luci-app-smartsafehub/frontend/src/pages/InitialPasswordSetupPage.tsx +++ b/luci-app-smartsafehub/frontend/src/pages/InitialPasswordSetupPage.tsx @@ -20,23 +20,11 @@ import { readColorTheme, } from '../utils/theme'; import type { ColorTheme } from '../utils/theme'; +import { passwordPolicy, passwordPolicySatisfied } from '../utils/password'; interface InitialPasswordSetupPageProps { onCompleted: () => void; -} - -interface PasswordPolicy { - length: boolean; - letter: boolean; - number: boolean; -} - -function passwordPolicy(password: string): PasswordPolicy { - return { - length: password.length >= 8, - letter: /[A-Za-z]/.test(password), - number: /[0-9]/.test(password), - }; + recovery: boolean; } function ThemeIcon({ theme }: { theme: ColorTheme }) { @@ -54,6 +42,7 @@ function Requirement({ met, children }: { met: boolean; children: string }) { export function InitialPasswordSetupPage({ onCompleted, + recovery, }: InitialPasswordSetupPageProps) { const passwordInput = useRef(null); const confirmationInput = useRef(null); @@ -66,7 +55,7 @@ export function InitialPasswordSetupPage({ const [theme, setTheme] = useState(readColorTheme); const policy = passwordPolicy(password); - const policySatisfied = policy.length && policy.letter && policy.number; + const policySatisfied = passwordPolicySatisfied(policy); const confirmationMatches = confirmation.length > 0 && password === confirmation; const themeLabel = theme === 'dark' ? '라이트 모드로 전환' : '다크 모드로 전환'; @@ -137,21 +126,28 @@ export function InitialPasswordSetupPage({ - +

SmartSafeHub - 보호 시작 + + {recovery ? '비밀번호 복구' : '보호 시작'} +

@@ -176,7 +172,9 @@ export function InitialPasswordSetupPage({
-

관리자 비밀번호 설정

+

+ {recovery ? '관리자 비밀번호 복구' : '관리자 비밀번호 설정'} +

diff --git a/luci-app-smartsafehub/frontend/src/pages/IptvPage.tsx b/luci-app-smartsafehub/frontend/src/pages/IptvPage.tsx index 3e7139d87..90f368d12 100644 --- a/luci-app-smartsafehub/frontend/src/pages/IptvPage.tsx +++ b/luci-app-smartsafehub/frontend/src/pages/IptvPage.tsx @@ -128,50 +128,47 @@ export function IptvPage({ ) : null}
-
-
-
- - - -
-

IPTV

-

- WAN의 멀티캐스트 방송을 LAN 셋톱박스로 전달합니다. -

+
+ + + +
+
+

IPTV

+
+ {dirty ? ( + + + ) : null} +
-
- -
- {dirty ? ( - - - ) : null} - +

+ WAN의 멀티캐스트 방송을 LAN 셋톱박스로 전달합니다. +

diff --git a/luci-app-smartsafehub/frontend/src/pages/LanPage.tsx b/luci-app-smartsafehub/frontend/src/pages/LanPage.tsx index dd67c74a3..b00c25585 100644 --- a/luci-app-smartsafehub/frontend/src/pages/LanPage.tsx +++ b/luci-app-smartsafehub/frontend/src/pages/LanPage.tsx @@ -2,6 +2,11 @@ import type { JSX } from 'preact'; import { useEffect, useMemo, useState } from 'preact/hooks'; import { CustomSelect } from '../components/CustomSelect'; +import { + Ipv4OctetInput, + normalizeIpv4Octet, + splitIpv4, +} from '../components/Ipv4OctetInput'; import type { LanFeedback } from '../hooks/useLan'; import type { LanSettings, LanSettingsInput } from '../types/lan'; import { @@ -51,16 +56,6 @@ function isIpv4(value: string): boolean { }); } - -function splitIpv4(value: string): [string, string, string, string] { - const parts = value.split('.'); - return [parts[0] ?? '', parts[1] ?? '', parts[2] ?? '', parts[3] ?? '']; -} - -function normalizeOctet(value: string): string { - return value.replace(/\D/g, '').slice(0, 3); -} - function lanPrefix(value: string): string | null { const parts = splitIpv4(value); if (parts.slice(0, 3).some((part) => part === '' || Number(part) > 255)) { @@ -88,46 +83,6 @@ function lanFormValues(data: LanSettings): LanSettingsInput { }; } -function Ipv4OctetInput({ - disabled, - value, - onChange, -}: { - disabled: boolean; - value: string; - onChange: (value: string) => void; -}) { - const octets = splitIpv4(value); - - const updateOctet = (index: number, nextValue: string) => { - const next = [...octets] as [string, string, string, string]; - next[index] = normalizeOctet(nextValue); - onChange(next.join('.')); - }; - - return ( -
- {octets.map((octet, index) => ( -
- {index > 0 ? . : null} - updateOctet(index, event.currentTarget.value)} - pattern="[0-9]*" - spellcheck={false} - value={octet} - /> -
- ))} -
- ); -} - function DhcpHostInput({ disabled, label, @@ -146,35 +101,38 @@ function DhcpHostInput({ const host = hostOctet(value); const updateHost = (nextValue: string) => { - const normalized = normalizeOctet(nextValue); + const normalized = normalizeIpv4Octet(nextValue); onChange(prefix && normalized !== '' ? `${prefix}.${normalized}` : normalized); }; return (