-
-
Notifications
You must be signed in to change notification settings - Fork 3
router console
Status: shipped in #54. The console tab and the one link (R7, R12, R15, R23–R30): in progress in #76. Router statistics from the console (R31–R46): in progress in #78.
eepview shows router information. It does not change the router configuration. All router configuration goes through the router's own console pages. eepview finds the console of the router in use and gives one link to it, "I2P Router Console". The user reaches every other console page from the console itself. When the router has no eepview helper, eepview also reads the router statistics from that console (R31–R46).
-
Detect. When a page that shows the links opens, eepview reads the console port from the router configuration files, when it finds them. Then it sends one loopback
GETto each candidate port. A port counts only when the answer looks like that router's console. - Show. The link shows in the router panel, on the home page and in Settings. With no console, it is hidden and one line says "No router console found".
-
Open. The link opens the console home page in the console tab: a tab of its own kind in the tab strip. Its webview,
console, sits in the content area like a web tab. It loads only the detected console origin,http://127.0.0.1:<port>. -
Read the statistics. Without a router helper,
router_stats()reads the figures of the router panel and the Network page from the detected console: one read-only loopbackGETper refresh, while those figures show (R31–R46).
The console tab is not a web tab, and its webview is not a tab-* webview. The webview is built in src-tauri/src/shell/console.rs only. It never uses the gatekeeper, and the gatekeeper never sees it. An engine rule list confines it to the console origin (R19). The tab-* webviews keep all five layers and still cannot reach loopback. ADR 0001 records this exception.
-
R1 Candidate ports. The candidates are, in this order, with no duplicates:
- the Java I2P console ports read from the Java I2P configuration (R2);
- the i2pd web console port read from
i2pd.conf(R2); - the Java I2P default,
7657; - the i2pd default,
7070.
-
R2 Configuration files.
- Java I2P: in the configuration folder, every file in
clients.config.d/(by file name), thenclients.config. The console is the client whoseclientApp.<n>.mainisnet.i2p.router.web.RouterConsoleRunner. Its port is the first whitespace-separated token ofclientApp.<n>.argsthat parses fully as a non-zerou16and does not follow-s(for example7657 ::1,127.0.0.1 ./webapps/gives7657). The token after-sis a TLS port and is skipped, so the TLS-only-s 7667 ::1,127.0.0.1 ./webapps/gives no port;127.0.0.1is not au16token. A client withclientApp.<n>.startOnLoad=falsegives no port. - i2pd: the
portkey in the[http]section ofi2pd.conf.enabled = falsein that section gives no port. Keys outside[http]are ignored.#starts a comment. - Folders. Java I2P: macOS
$HOME/Library/Application Support/i2p; Linux$HOME/.i2pand/var/lib/i2p/i2p-config; Windows%LOCALAPPDATA%\I2Pand%APPDATA%\I2P. i2pd: macOS$HOME/Library/Application Support/i2pd/i2pd.conf; Linux$HOME/.i2pd/i2pd.confand/etc/i2pd/i2pd.conf; Windows%APPDATA%\i2pd\i2pd.conf. A path whose variable is not set is left out. A missing or unreadable file gives no port.
- Java I2P: in the configuration folder, every file in
-
R3 Verification. A candidate counts only when one
GETtohttp://127.0.0.1:<port><probe path>answers200and the body has the marker of that console:Router Probe path Marker (all must be in the body) Java I2P /home/themes/console/andconsole.cssi2pd /?page=i2p_tunnelsA closed port, another status, a body without the marker, or the console of the other router type does not count. The markers do not depend on the console language.
-
R4 Loopback only. The probe connects only through
LoopbackAddrto127.0.0.1, with a 5 s timeout. It sends oneGET <path> HTTP/1.0in origin form, withHost: 127.0.0.1:<port>, so the answer is never chunked. It never follows a redirect. The probe code lives insrc-tauri/src/net/(ADR 0001 rule 2). -
R5 Result. The first candidate that passes R3 is the console. If none passes, there is no console. Only the detector makes a
VerifiedConsole. -
R6 On demand. eepview never probes at start. Detection runs only on
console_detect(). The UI calls it when the router panel opens, and when the home page or the Settings page loads while it is the active tab. While a console is known, eepview checks it again every 10 s (R21). When a trigger finds none, eepview retries (R20). A run that never shows these pages, such as the leak test withEEPVIEW_START_URL, opens no extra socket. When the result changes, eepview emitsconsole-changed. When the console goes away (R21) or moves to another port, the console tab closes (R30).
-
R7 Home page. The link opens the console home page. It is the probe page of R3:
Router Home page Java I2P /homei2pd /eepview links to no other console page. The user reaches the tunnels, the address book, the configuration and the logs from the console itself.
-
R8 One factory. Only
src-tauri/src/shell/console.rsbuilds the console webview. Its constructor takes a&VerifiedConsole. The webview label is exactlyconsole. It is a child of themainwindow, like the tab webviews, and there is no other console window. It is never atab-*webview, it is never in the tab-webview label map, and it never uses the content-webview factory. -
R9 Origin. The console view loads only URLs on the detected origin: scheme
http, host127.0.0.1, the detected port. -
R10 Navigation guard. For each navigation in the console view:
- same origin: it stays in the console view;
-
http(s)://*.i2p: the navigation is cancelled, and the URL opens in a new normal tab through the existing guard; - anything else: cancelled.
A new-window request on the same origin loads in the console view. One for
*.i2popens a new tab. Anything else is dropped. The engine never opens a window by itself. -
R11 Hardening. The console view has no
proxy_url(it is loopback), the engine rule list of R19, no IPC capability, WebRTC off in every frame (the same script as the tabs), downloads refused, and JavaScript on (the console needs it). It runs incognito, so nothing persists after exit. -
R12
console_open(). It takes no argument. With no console:{ok: false, reason: "no-console"}, and no tab and no webview are made. Otherwise it opens the console tab (R24), loads the console home page (R7) in it, and answers{ok: true}. -
R13
console_status(). It answers the currentConsoleInfo. It does not probe.
-
R14 Tabs unchanged. The five layers of the
tab-*webviews do not change. Atab-*webview never receives a loopback URL.proxy_urlstays only incontent.rs. No capability names theconsolewebview.
-
R15 One link. The router panel, the home page and the Router section of Settings show one link, "I2P Router Console", for the detected router. It is a button that calls
console_open(). No page holds anhttp://127.0.0.1link. With no console, the link is hidden and one line says "No router console found". The five per-page links of #54 (Console, Tunnels, Address book, Config, Logs) are gone. - R16 No router configuration in eepview. No eepview page changes router configuration. The Settings page has no bandwidth, share, relay (transit) or subscription control. Its Router section has the one console link (R15) instead. Pause and resume of the connection stay. The Router updates and Restore controls stay: they are managed-install controls (a disabled Phase 3 preview), not router configuration. The About list "Where eepview connects" names the console probe and the console tab.
- R17 Leak test. The leak test passes unchanged.
-
R18 Version, display only.
ConsoleInfo.versionis the router version read from the probe page, ornullwhen it is not found. It costs no extra request. Java I2P: the version in the console stylesheet link,console.css?<version>. i2pd: the first:</b> <version><br>value, where<version>is digits and dots (the label before it is translated). The router panel and the home page show it whenRouterStatus.versionisnull. It changes nothing else.
-
R19 Console rule list. Before its first load, the console view gets an engine rule list: block every URL, then allow only URLs on the console origin (R9) and
about:,data:,blob:. The view starts onabout:blankand loads the page only after the list is attached. If it cannot be attached, nothing loads (fail closed): the console tab closes, itsconsolewebview is destroyed, and a warning toast says "The router console could not be opened safely" (Core::console_failed). On Windows the same rule answers eachWebResourceRequestedwith 403. Linux has no engine filter yet (the same limit as L3b for tabs); there the console view relies on R10 and the router's own pages. -
R20 Retry after a miss. When a
console_detect()finds no console, eepview retries every 10 s for 2 minutes (12 retries), and stops at the first console found. A new trigger during the retries does not start a second retry loop. The UI also callsconsole_detect()whenrouter-statusturnsok(from any other state, not paused) while a page with the links shows: the home page or Settings as the active tab, or the open router panel. -
R21 Re-check misses. While a console is known, a re-check runs every 10 s. A re-check that finds a different console (another port or type) replaces it at once. A re-check that finds none counts a miss; the known console stays, with no event, until 3 misses in a row. The third miss clears it:
console-changedwithfound: false, and the console tab closes (R30). A re-check that finds the same console resets the count. -
R22 Stop.
shell::console::stop(app)ends every re-check and retry loop at its next tick (at most one tick, 10 s). After stop, no thread opens a connection to a console port. The shell calls it onRunEvent::Exit. A laterdetect_nowstarts the loops again.
-
R23 Tab kind. The console tab is a tab of kind
consolein the tab strip (TabInfo.kind). While it is the active tab, the content area shows theconsolewebview, at the place of a web tab. While another tab is active, theconsolewebview is hidden. ItsTabInfohaskind: "console",zoom: 1,jsOn: true(R11),bookmarked: falseandicon: null. The router state (verifying, down, paused) does not change what the console tab shows: the console is on loopback and does not go through the router proxy. -
R24 One console tab. There is at most one console tab.
console_open()with a console tab open selects it and loads the home page in it. With none, it makes one right after the active tab and selects it. It never opens a secondconsolewebview. One field of the core,console_tab, names the console tab; a tab has no console flag. -
R25 Tab state. The tab title is the document title of the console page. Until the first title arrives, it is
Router console. The tab URL is the URL theconsolewebview shows (main frame), andloadingfollows its page loads. A console page never enters history, never gets a site icon and is never bookmarked. The bookmark star and the JavaScript toggle are disabled in a console tab. Find in page and zoom do nothing there: the find shortcut andfind()leave the find bar closed and make no engine call. -
R26 Address bar. In a console tab the address bar shows the console URL and a
Router consolebadge in place of theI2Pbadge. Typing in the address bar and pressing Enter navigates like in any tab: the input goes through the normal address rules and the tab guard. When the input is allowed (an I2P address, an internal page or a search), the console tab becomes a normal tab (weborinternal) with a new back/forward list, and theconsolewebview is destroyed. When the input is refused (not-i2p,invalid), for example an edited console address, the answer is the refusal and the console tab stays as it is: no blocked page, no effect on theconsolewebview. A console address typed by hand never loads; onlyconsole_open()loads the console. -
R27 Back, forward, reload, stop. In a console tab, back, forward, reload, hard reload and stop act on the
consolewebview.canBackandcanForwardfollow the console pages loaded in that tab. -
R28 Close. Closing the console tab destroys the
consolewebview. "Reopen closed tab" never brings a console tab back. eepview does not restore tabs at start, so a restart never shows a console tab. -
R29 No console URL in a web tab. The core never makes a
WebOp(load, engine call, destroy) for the console tab. A console page URL never reaches atab-*webview: a load of a non-I2P URL in a web tab shows the blocked page, as before. ADR 0001 and the leak test do not change. -
R30 Console gone. When the console goes away or moves (R6, R21), the console tab closes and the
consolewebview is destroyed.
The router panel and the Network page (eepview://stats) show the router statistics. Until now they came only from the router helper (EEPVIEW_ROUTER_STATUS), which an external router does not have. Java I2P keeps I2PControl off by default, and eepview never turns it on: that is a router configuration change. So, without the helper, eepview reads the same figures from the console that detection already verified. It only reads. It never changes the router.
-
R31 Source order.
router_stats()takes the statistics from the first source that answers:- the router helper, when
EEPVIEW_ROUTER_STATUSand its token are set, and it answers200with JSON; - the console stored by detection (
shell::console::current), when one is stored and it answers200(R32, R33); - else no source: every field is
null.
When the helper answers, eepview does not ask the console.
router_stats()never runs detection: with no stored console it does not probe. No page shows which source gave the figures. - the router helper, when
-
R32 One read-only request. The console request is one loopback
GET <path> HTTP/1.0in origin form on the stored console origin (127.0.0.1:<port>), throughLoopbackAddr, with the headers of R4 (Host,User-Agent: eepview,Accept: text/html,Connection: close) and nothing else: no cookie, no body. It never sends aPOST, never follows a redirect, and never goes through a webview. The path is fixed per router type:Router Path Java I2P /xhr1.jsp?requestURI=/summaryframei2pd /The path never carries a
lang,actionorconsoleNonceparameter. (A Java I2P console saves?lang=in the router configuration.) The request code lives insrc-tauri/src/net/(ADR 0001 rule 2). The Java path is the sidebar data that the console's own script loads every 15 s, with the section list of the sidebar page (/summaryframe). It has the least markup of the pages that carry the figures. -
R33 Bounds. The whole request, connect and reads together, takes at most 3 s. It reads at most 256 KiB of the page. A peer that sends slowly cannot hold a call longer than 3 s. A closed port, a timeout before any answer, a status other than
200, or an answer that is not HTTP counts as "the console does not answer": R31 moves on. A body cut by the size cap or by the timeout is still parsed; R35 makes every cut valuenull. -
R34 Cadence. Rust starts no thread and no timer for the console statistics: each
router_stats()call makes at most one console request. The UI callsrouter_stats()every 5 s, and only while the figures show:- the router panel: while it is open (unchanged);
- the Network page: while
document.visibilityStateis"visible". It stops while the page is hidden, and refreshes at once when it shows again.
The Network page calls
console_detect()once when it loads (a new trigger for R6). The router panel and the Network page load the figures again when thatconsole_detect()answers, so a console found at that moment fills the figures at once. AfterRunEvent::Exit, no page callsrouter_stats(), so no console request runs. Aftershell::console::stop(R22),router_stats()asks no console either, until the next detection. A call that starts after the stop sends no request; a request already sent ends within 3 s (R33). -
R35 Fail closed, per field. Each field is parsed alone. A field that does not match its rule exactly is
null, and the UI shows "—". eepview never shows a guessed or a derived number.- An integer is one or more ASCII digits and fits in
u64. A decimal is ASCII digits, then optionally.and one or more digits. No sign, no group separator, no,as the decimal mark, no other digit set. A value that does not match isnull. - A value must end with the terminator its rule names. A value at the end of the body (cut by R33) is
null. - A unit conversion is exact on the decimal digits, then rounds to the nearest integer, a half rounds up. For example,
53.91KBps is 53 910 B/s, and12.34KiB/s is 12 636 B/s (12 636.16). - When an anchor (a Java I2P table
id, or an i2pd label) is in the body more than once, the fields it gives arenull. - A missing section gives
nullfor its fields only. The other fields still parse.
- An integer is one or more ASCII digits and fits in
-
R36 Java I2P figures. The parser reads tables by their
idand their rows by position. It never reads a row label or atitle, so it works in every console language. A row is a<tr…>…</tr>element in the table. Its value is the text of the last<td…>cell, up to</td>. When a table has another number of rows than the rule names, every field of that table isnull.Field Table and row Value form Result uptimeMs,uptimeResolutionMssb_generalorsb_shortgeneralwith 2 rows: row 1. Elsesb_advancedgeneralwith 4 rows: row 1, with 5 rows: row 2. The first of the three ids in this order wins.N <unit>R37 bandwidthBytesPerSecond.in1s,.out1ssb_bandwidth, any row count from 2 to 4: row 0A / B KBpsorA / B MBpsAandB(decimals) × 1 000 (K) or × 1 000 000 (M)bandwidthBytesPerSecond.in5m,.out5msb_bandwidthwith 4 rows: row 1. With 2 or 3 rows:null(the router is younger than 6 minutes).as above as above activePeerssb_peerswith 5 rows, orsb_peersadvancedwith 6 rows: row 0A / BA(peers with a connection now)floodfillsthe same table: row 3 NNknownRoutersthe same table: row 4 NNtunnels.exploratorysb_tunnelswith 4 rows: row 0NN(inbound + outbound)tunnels.clientsb_tunnelswith 4 rows: row 1NN(inbound + outbound)tunnels.participatingsb_tunnelswith 4 rows: row 2NNnetworkStatusthe class after sb_netstatusin the first<span class="sb_netstatus <class>">running,firewalled,testing,hidden,warn,error,clockskew,vmcommOK,FIREWALLED,TESTING,HIDDEN,WARN,ERROR,CLOCK_SKEW,VMCOMM; another class givesnullA / BisA, a space,/, a space,B. The rows Fast and High capacity (rows 1 and 2 ofsb_peers) have no field and are not read. Alwaysnullfrom Java I2P:version(R18 shows the console version),tunnels.in,tunnels.out(the sidebar gives only in + out together), and everytunnelBuildSuccessPercentfield: the sidebar shows no build success figure, and eepview does not compute one from other statistics. -
R37 Java I2P uptime. The console rounds the uptime down to one unit. The value is
N <unit>withNan integer. Only the English units parse:Unit uptimeMsuptimeResolutionMsmsN 1 secN × 1 000 1 000 minN × 60 000 60 000 hour,hoursN × 3 600 000 3 600 000 day,daysN × 86 400 000 86 400 000 Any other unit, such as
yearsor a translated unit, givesnullfor both fields. -
R38 i2pd figures. This rule comes from the i2pd source,
daemon/HTTPServer.cpp(ShowStatus,ShowUptime,ShowNetworkStatus), of i2pd 2.5x. No i2pd router was available to capture a real page. i2pd translates its labels and has no table ids, so the parser reads the English page only: when the body has no<html lang="en", every field isnull. A label is the exact text<b><label>:</b>(a space after it). Lines may end with\r\nor\n.Field Label Value form, then terminator Result uptimeMs,uptimeResolutionMsUptimeD day(s), H hour(s), M minute(s), S second(s), then<br>. Each part is<integer> <unit>, singular or plural. Days, hours and minutes are optional; seconds is always there; the order is fixed; parts are joined by,.the total in ms; resolution 1 000 networkStatusNetwork status(notNetwork status v6)exactly one of OK,Firewalled,Unknown,Proxy,Mesh,Stan, then<br>the word in upper case. A (Testing)or- <error>suffix givesnull.tunnelBuildSuccessPercent.totalTunnel creation success rateN%, then<br>NbandwidthBytesPerSecond.in1sReceived<amount> (X KiB/s), then<br>X(decimal) × 1 024bandwidthBytesPerSecond.out1sSentthe same X× 1 024knownRoutersRoutersN, then NfloodfillsFloodfillsN, then Ntunnels.participatingTransit TunnelsN, then<br>NAlways
nullfrom i2pd:version(R18),activePeers(the main page has no peer count),tunnels.in,tunnels.out,tunnels.exploratory,tunnels.client(theClient Tunnelsfigure of i2pd is the sum of every inbound and every outbound tunnel of the router, exploratory tunnels included, so it is not the client count of the contract; eepview does not read it, and adds no contract field for it),bandwidthBytesPerSecond.in5mand.out5m,tunnelBuildSuccessPercent.exploratoryand.client. The labelTotal tunnel creation success rateis a different label and is not read. -
R39 New
RouterStatsfields (contract v1.7).uptimeResolutionMs,floodfills,tunnels.client,tunnels.exploratoryandtunnelBuildSuccessPercent.total. See IPC contract.bandwidthBytesPerSecond.in1sand.out1shold the shortest window that the source gives, which the UI shows as "now": 1 s from the helper, and the current rate that the console gives from Java I2P and i2pd. (The Java I2P row is labeled "3 sec", but the console only shows the router's current rate there, so it is not a 3 s average.) From the helper:uptimeResolutionMsis1whenuptimeMsis set, elsenull.tunnels.clientisclientInbound + clientOutboundandtunnels.exploratoryisexploratoryInbound + exploratoryOutbound, eachnullwhen a part is missing.floodfillsandtunnelBuildSuccessPercent.totalarenull. -
R40 Bandwidth history from the console. When
router_stats()answers from the console, it adds that answer's bandwidth sample to the 10-minute history, as the watcher does for the helper. It adds no sample when the newest sample is less than 4 s old, so the panel and the Network page together still add one sample per 5 s. Thehistoryof the answer includes the new sample. With the console, the history grows only while a page shows the figures. -
R41 The UI reads the contract shape.
router_stats()answers theRouterStatsof the IPC contract.src/ui/contract.tsuses that same shape. The router panel and the Network page turn it into their view with one pure function,statsView(below):View field From RouterStatsuptimeSecondsfloor(uptimeMs / 1000)uptimeResolutionSecondsmax(1, floor(uptimeResolutionMs / 1000));nullwhenuptimeResolutionMsisnullrouterVersionversionnetworkStatus,activePeers,knownRouters,floodfillsthe same field bandwidthInBps,bandwidthOutBpsbandwidthBytesPerSecond.in1s,.out1sinboundTunnels,outboundTunnels,participatingTunnelstunnels.in,.out,.participatingclientTunnels,exploratoryTunnelstunnels.client,.exploratorybuildSuccessRatetunnelBuildSuccessPercent.total / 100; whentotalisnull,.exploratory / 100; elsenullhistory{ stepSeconds: 5, inBps, outBps }from the last contiguous run ofhistory(below), oldest first;nullwhenhistoryis emptyrouterKind,javaVersionnullbuildSuccessRatenever usestunnelBuildSuccessPercent.client: the build success of client tunnels alone is not the build success of the router, and the panel would show it as the router's rate. A helper that gives onlyclientshows "—". The history run.history[].tis the sample time in ms.statsViewkeeps only the last contiguous run of samples: it starts from the newest sample and goes back while the gap to the previous sample is at most 2 ×stepSeconds(10 000 ms, withstepSeconds5). The first larger gap ends the run, and the samples before it are dropped.inBpsandoutBpshold theinandoutof the kept samples, oldest first.statsViewnever adds, repeats or interpolates a sample. A single sample is a run. A missing ornullinput gives a view with every fieldnull. -
R42 Uptime to its resolution. The UI never shows a unit smaller than the uptime resolution.
formatUptime(seconds, resolutionSeconds)keeps today's form (<d> d <h> h,<h> h <m> min,<m> min) and drops each unit smaller thanresolutionSeconds: with a resolution of 3 600 s, 28 800 s is8 h, not8 h 0 min; with 86 400 s, 172 800 s is2 d. Anullor missing resolution, or one below 60 s, gives today's output. The router panel and the Network page passuptimeResolutionSeconds. -
R43 Tunnels line in the router panel. When
inboundTunnelsandoutboundTunnelsare bothnullandclientTunnelsorexploratoryTunnelsis not, the line is<client> client · <exploratory> exploratory · <participating> participating, each count asformatCountgives it ("—" whennull). Else it stays<in> in · <out> out · <participating> participating. i2pd gives neithertunnels.clientnortunnels.exploratory(R38), so its line is— in · — out · <participating> participating. -
R44 Where eepview connects. The About list row of the console names the statistics too: "Router console check, router statistics and the console tab".
-
R45 No console request after stop. After
shell::console::stop(R22),current_statsdoes not query the console: it sends no request and takes no figures from the console, untildetect_nowruns again. Arouter_stats()call that starts after the stop and before the nextdetect_nowanswers from the helper when the helper answers (R31), else with every fieldnull, and adds no console sample to the history (R40). -
R46 One deadline for the whole request. The 3 s of R33 cover the connect and every read of the request together, not each read alone. A peer that sends bytes slower than that gets figures from what arrived within 3 s: a value cut by the deadline is
null(R35), and when no complete HTTP answer head arrived within 3 s the console does not answer (R33). A call never lasts longer than 3 s plus the time to close the socket.
pub const JAVA_DEFAULT_PORT: u16 = 7657;
pub const I2PD_DEFAULT_PORT: u16 = 7070;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")] // "java" | "i2pd"
pub enum ConsoleKind { Java, I2pd }
pub fn probe_path(kind: ConsoleKind) -> &'static str; // R3, R7: also the home page
pub fn java_console_port(clients_config: &str) -> Option<u16>; // R2, one file's text
pub fn i2pd_console_port(i2pd_conf: &str) -> Option<u16>; // R2, one file's text
pub fn java_config_dirs(env: &dyn Fn(&str) -> Option<String>) -> Vec<PathBuf>; // R2, this OS
pub fn i2pd_config_files(env: &dyn Fn(&str) -> Option<String>) -> Vec<PathBuf>; // R2, this OS
pub fn java_ports_in(dir: &Path) -> Vec<u16>; // clients.config.d/* by name, then clients.config
pub fn i2pd_port_in(file: &Path) -> Option<u16>;
pub fn candidates(env: &dyn Fn(&str) -> Option<String>) -> Vec<(ConsoleKind, u16)>; // R1
pub fn judge(kind: ConsoleKind, answer: &crate::net::verify::Answer) -> bool; // R3
pub fn probe(kind: ConsoleKind, port: u16) -> Option<VerifiedConsole>; // R3, R4
pub fn detect(candidates: &[(ConsoleKind, u16)]) -> Option<VerifiedConsole>; // R5
pub fn detect_here() -> Option<VerifiedConsole>; // detect(&candidates(<process environment>))
/// A console that passed R3. Private fields: only `probe` and `detect` make one.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VerifiedConsole { /* private */ }
impl VerifiedConsole {
pub fn kind(&self) -> ConsoleKind;
pub fn port(&self) -> u16;
pub fn origin(&self) -> String; // "http://127.0.0.1:<port>"
pub fn home(&self) -> Url; // R7: origin + probe_path
pub fn route(&self, url: &Url) -> ConsoleNav; // R10
pub fn version(&self) -> Option<&str>; // R18
pub fn info(&self) -> ConsoleInfo;
pub fn rule_list(&self) -> serde_json::Value; // R19, WKContentRuleList JSON
pub fn engine_allows(&self, url: &str) -> bool; // R19, the same rule for one URL
}
pub const RECHECK_MISSES: u32 = 3; // R21
/// R21: the known console and miss count after one re-check that found `found`.
pub fn after_recheck(known: Option<&VerifiedConsole>, misses: u32,
found: Option<VerifiedConsole>) -> (Option<VerifiedConsole>, u32);
pub fn console_version(kind: ConsoleKind, body: &str) -> Option<String>; // R18
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConsoleNav { Stay, OpenTab, Cancel }
#[derive(Debug, Clone, PartialEq, Eq, Serialize)] // camelCase on the wire
pub struct ConsoleInfo { pub found: bool, pub kind: Option<ConsoleKind>,
pub origin: Option<String>, pub version: Option<String> }
impl ConsoleInfo { pub fn none() -> ConsoleInfo; } // found false, kind, origin and version nullTest helper, crate::net::testing (tests only): FakeConsole::start(kind: ConsoleKind) -> FakeConsole serves a page with that console's marker and the version 2.13.0 (Java: console.css?2.13.0; i2pd: <b>Version:</b> 2.13.0<br>) on a free loopback port. FakeConsole::port(&self) -> u16. FakeConsole::verified(&self) -> VerifiedConsole. FakeConsole::requests(&self) -> Vec<String> gives the request lines received.
// crate::core::Core holds one private field, `console_tab: Option<u32>` (R23, R24): the id of
// the console tab. It is the only place that says which tab is the console tab. `Tab` has
// no console flag, so two console tabs cannot exist.
// crate::core
pub enum View { Internal(String), Web(u32), Console(u32) } // Console: the console tab id, read from `console_tab`
pub enum Effect { /* as before */ Console(ConsoleOp) } // R27, R28: act on the `console` webview
#[derive(Debug, Clone, PartialEq)]
pub enum ConsoleOp {
Back, // R27: one step back in the console view
Forward, // R27: one step forward
Reload, // R27
HardReload, // R27: reload, bypassing the cache
Stop, // R27
Close, // R28, R30: destroy the `console` webview
}
// Only the operations the console tab needs. There is no variant for Find, FindClear or
// Zoom, so R25 and R27 hold by construction.
impl Core {
/// The id of the console tab, if one is open.
pub fn console_tab(&self) -> Option<u32>;
/// R24: selects the console tab, or makes one right after the active tab and selects
/// it, showing `url`. The shell calls it before it starts the load, so the first page
/// event finds the tab. No `WebOp`.
pub fn console_open(&mut self, url: &str) -> Vec<Effect>;
/// R25: the `console` webview started (`console_started`) or finished
/// (`console_finished`) a main-frame load of `url`. Updates url, loading and the
/// back/forward flags of the console tab. No history.
pub fn console_started(&mut self, url: &str) -> Vec<Effect>;
pub fn console_finished(&mut self, url: &str) -> Vec<Effect>;
/// R25: the document title of the console page changed.
pub fn console_title(&mut self, title: &str) -> Vec<Effect>;
/// R30: the console went away: closes the console tab, if open (with `ConsoleOp::Close`).
pub fn console_gone(&mut self) -> Vec<Effect>;
/// R19: the rule list could not be attached: `console_gone` plus a warning toast.
pub fn console_failed(&mut self) -> Vec<Effect>;
}The other commands keep their signatures and branch on the console tab: view() (R23), tab_infos() (kind: "console", R23), navigate() (R26), step(), reload(), stop() (R27), tab_close(), tab_reopen() (R28), find(), zoom(), bookmark_toggle(), shortcut(Find) (R25). Every path that builds a lazy web tab (tab_select(), tab_cycle(), tab_number(), the tab shown after tab_close(), router_changed(), resume()) skips the console tab, so it never turns into a blocked page.
pub const CONSOLE_LABEL: &str = "console";
pub struct ConsoleWebview;
impl ConsoleWebview {
/// R8, R24: builds the `console` webview in the main window (hidden, at the content
/// rect) and puts the chrome (`toolbar`, `status`, `popup`) above it again
/// (`view::raise_chrome`), or reuses the live one. Then it opens or selects the console
/// tab (`Core::console_open`), and only then loads the home page of `console` (after
/// the rule list, R19).
pub fn open<R: Runtime>(app: &AppHandle<R>, console: &VerifiedConsole)
-> tauri::Result<Webview<R>>;
}
/// Stores a detection result: emits `console-changed` when it changed, and closes the
/// console tab when the console went away or moved (R30).
pub fn set_console<R: Runtime>(app: &AppHandle<R>, console: Option<VerifiedConsole>);
pub const RETRY_EVERY: Duration = Duration::from_secs(10); // R20, R21
pub const RETRY_FOR: Duration = Duration::from_secs(120); // R20
/// Probes now (blocking), stores the result with `set_console`, and answers its info.
pub fn detect_now<R: Runtime>(app: &AppHandle<R>) -> ConsoleInfo;
/// The stored detection result.
pub fn current<R: Runtime>(app: &AppHandle<R>) -> Option<VerifiedConsole>;
/// R10: the navigation guard of the console view; its `on_navigation` callback is a
/// one-line call of this. True for the console origin (and the first `about:blank`). An
/// `http(s)://*.i2p` URL answers false and opens a new normal tab through the tab guard.
/// Anything else answers false.
pub fn navigation<R: Runtime>(app: &AppHandle<R>, console: &VerifiedConsole, url: &Url) -> bool;
/// R10: the new-window handler of the console view; its `on_new_window` callback is a
/// one-line call of this. Always `Deny`: the console origin loads in the console view, an
/// `http(s)://*.i2p` URL opens a new normal tab, anything else does nothing.
pub fn new_window<R: Runtime>(app: &AppHandle<R>, console: &VerifiedConsole, url: &Url)
-> NewWindowResponse<R>;
/// R25: a main-frame page load of the console view; its `on_page_load` callback is a
/// one-line call of this. Only a URL on the console origin reaches the core
/// (`Core::console_started` / `Core::console_finished`); any other URL is ignored.
pub fn page_load<R: Runtime>(app: &AppHandle<R>, console: &VerifiedConsole,
event: PageLoadEvent, url: &Url);
/// R25: the document title changed; its `on_document_title_changed` callback is a
/// one-line call of this (`Core::console_title`).
pub fn title_changed<R: Runtime>(app: &AppHandle<R>, title: &str);
/// R27, R28: carries out a `ConsoleOp` on the `console` webview (nothing when none exists).
pub fn run<R: Runtime>(app: &AppHandle<R>, op: &ConsoleOp);
/// R22: ends the re-check and retry loops at their next tick.
pub fn stop<R: Runtime>(app: &AppHandle<R>);
/// R30: closes the console tab (if open) and destroys the `console` webview (if any).
pub fn close<R: Runtime>(app: &AppHandle<R>);shell::view::sync shows the console webview for View::Console, at the content rect, and hides it for every other view (R23).
In eepview_lib::net::console (src-tauri/src/net/console.rs):
use crate::net::stats::RouterStats;
pub const STATS_TIMEOUT: Duration = Duration::from_secs(3); // R33, the whole request
pub const STATS_MAX_ANSWER: u64 = 256 * 1024; // R33, bytes
/// R32: "/xhr1.jsp?requestURI=/summaryframe" (Java I2P) or "/" (i2pd).
pub fn stats_path(kind: ConsoleKind) -> &'static str;
/// R35–R37: the Java I2P sidebar body (any status, any size) to statistics.
pub fn parse_java_summary(body: &str) -> RouterStats;
/// R35, R38: the i2pd main page body to statistics.
pub fn parse_i2pd_main(body: &str) -> RouterStats;
/// The parser of `kind`.
pub fn parse_console_stats(kind: ConsoleKind, body: &str) -> RouterStats;
/// R32, R33: one GET of `stats_path` on the console origin, parsed, with the request code of
/// `probe` and its own bounds. `None` when the console does not answer `200` (R33).
/// `history` is always empty here.
pub fn fetch_stats(console: &VerifiedConsole) -> Option<RouterStats>;In eepview_lib::net::stats (src-tauri/src/net/stats.rs):
// R39: new fields. Serialized as in the IPC contract (camelCase).
pub struct RouterStats { /* existing fields */, pub uptime_resolution_ms: Option<u64>,
pub floodfills: Option<u64> }
pub struct Tunnels { /* in, out, participating */, pub client: Option<u64>,
pub exploratory: Option<u64> }
pub struct BuildSuccess { /* exploratory, client */, pub total: Option<u64> }
/// R31: which source gave the figures. Never shown in the UI.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StatsSource { Helper, Console, None }
/// R31: the helper answer, or `None` when the helper does not answer `200` with JSON.
pub fn try_fetch(addr: LoopbackAddr, token: &str) -> Option<RouterStats>;
/// R31: the helper stats when `Some`; else calls `console` once and takes its stats when
/// `Some`; else all-null stats. `console` is not called when `helper` is `Some`.
pub fn pick(helper: Option<RouterStats>, console: impl FnOnce() -> Option<RouterStats>)
-> (RouterStats, StatsSource);
pub const MIN_SAMPLE_GAP_MS: u64 = 4_000; // R40
impl History {
/// R40: like `record`, but adds no sample when the newest one is less than
/// MIN_SAMPLE_GAP_MS older than `now`.
pub fn record_spaced(&mut self, now: u64, stats: &RouterStats);
}from_helper maps the new fields as R39 says.
In eepview_lib::shell::commands (src-tauri/src/shell/commands.rs):
/// R31, R40: the `router_stats()` answer. Asks the helper (when given), else the stored
/// console (`shell::console::current`, no probe); records a console sample (R40); fills
/// `history`. The `router_stats` command runs this off the main thread.
pub fn current_stats<R: Runtime>(app: &AppHandle<R>, helper: Option<(LoopbackAddr, String)>)
-> RouterStats;Test helper, crate::net::testing (tests only), in addition to FakeConsole::start: FakeConsole::serving(kind: ConsoleKind, path: &str, status: u16, body: &str) -> FakeConsole answers a request for exactly path (the request target, query included) with status and body, and every other request as start does, so verified() still passes the probe. requests() lists every request line it received.
Fixtures: src-tauri/tests/fixtures/console/java-2.13.0-xhr1-summaryframe.txt is a real answer body of /xhr1.jsp?requestURI=/summaryframe from Java I2P 2.13.0 (English, not advanced; the router ident, the console nonce and a tunnel hash are replaced). src-tauri/tests/fixtures/console/i2pd-2.58.0-main-synthetic.txt is an i2pd main page written from the i2pd source (R38), not captured; its addresses and ident are fake. The lines end with \n; i2pd sends \r\n, which R38 also accepts. The values that R36–R38 give for them:
| Field | Java fixture | i2pd fixture |
|---|---|---|
uptimeMs / uptimeResolutionMs
|
28 800 000 / 3 600 000 (8 hours) |
93 784 000 / 1 000 (1 day, 2 hours, 3 minutes, 4 seconds) |
networkStatus |
OK |
OK |
bandwidthBytesPerSecond in1s / out1s / in5m / out5m |
53 910 / 37 370 / 37 830 / 33 060 | 12 636 / 5 806 / null / null
|
activePeers / knownRouters / floodfills
|
1 678 / 4 905 / 1 570 |
null / 3 021 / 812 |
tunnels in / out / participating / client / exploratory |
null / null / 398 / 2 / 11 |
null / null / 157 / null / null
|
tunnelBuildSuccessPercent exploratory / client / total |
null / null / null
|
null / null / 42 |
version |
null |
null |
-
console_status() -> ConsoleInfo: the stored result, no probe. -
console_detect() -> ConsoleInfo: probes now (R6), off the main thread, stores and answers the result. -
console_open() -> {ok: boolean, reason?: "no-console"}(R12). No argument. -
TabInfo.kind:"internal" | "web" | "console"(R23). - Event
console-changed: ConsoleInfo -
router_stats() -> RouterStats(contract v1.7): R31–R40. The new fields are in the IPC contract.
type ConsoleInfo = { found: boolean; kind: "java" | "i2pd" | null; origin: string | null;
version: string | null };
type ConsoleOpenResult = { ok: boolean; reason?: "no-console" };export const NO_CONSOLE_TEXT = "No router console found";
export const CONSOLE_LINK_TEXT = "I2P Router Console"; // R15
export interface ConsoleLinkView {
found: boolean;
note: string | null; // NO_CONSOLE_TEXT when not found, else null
title: string | null; // "Java I2P console" | "i2pd web console" | null
label: string | null; // CONSOLE_LINK_TEXT when found, else null
}
export function consoleLink(info: ConsoleInfo | null | undefined): ConsoleLinkView;
/** R18: the version to show. statusVersion first; else the console version; else null. */
export function routerVersion(statusVersion: string | null, info: ConsoleInfo | null | undefined): string | null;
/** R20: true when next is ok, not paused, and prev was not ok (or not known). */
export function shouldRedetect(prev: RouterStatus | null, next: RouterStatus): boolean;export interface AddressBadge { text: string; title: string; kind: "i2p" | "console" }
/** R26: the badge before the address: "I2P" for a web tab, "Router console" for the
* console tab, none (null) for an internal page or no tab. */
export function addressBadge(tab: Pick<TabInfo, "kind" | "url"> | null | undefined): AddressBadge | null;The I2P badge keeps its title, "Opened over I2P". The console badge title is "The router's own console on this computer".
import type { RouterStats } from "../contract.ts"; // the IPC contract shape (R41)
export const HISTORY_STEP_SECONDS = 5;
export interface StatsView {
networkStatus: string | null;
uptimeSeconds: number | null;
uptimeResolutionSeconds: number | null;
routerKind: string | null;
routerVersion: string | null;
javaVersion: string | null;
bandwidthInBps: number | null;
bandwidthOutBps: number | null;
history: { stepSeconds: number; inBps: number[]; outBps: number[] } | null;
clientTunnels: number | null;
exploratoryTunnels: number | null;
inboundTunnels: number | null;
outboundTunnels: number | null;
activePeers: number | null;
participatingTunnels: number | null;
buildSuccessRate: number | null; // 0..1
knownRouters: number | null;
floodfills: number | null;
}
/** R41. */
export function statsView(stats: RouterStats | null | undefined): StatsView;Changed, in src/ui/lib/stats-view.ts and src/ui/lib/router-panel.ts:
/** R42: a unit smaller than resolutionSeconds is not shown. */
export function formatUptime(seconds: number | null, resolutionSeconds?: number | null): string;
// StatsLike and PanelStatsLike gain optional fields:
// uptimeResolutionSeconds?: number | null (statsText and panelText pass it on, R42)
// clientTunnels?: number | null; exploratoryTunnels?: number | null (PanelStatsLike, R43)src/ui/contract.ts RouterStats becomes the contract v1.7 shape. The dev mock (src/ui/mock.ts) answers that shape.
-
src-tauri/src/net/console/tests.rs(R1–R7, R9, R10, R18, R19, R21) -
src-tauri/src/shell/console/tests.rs(R6, R8, R10–R13, R20–R30 in the shell; the mock runtime fixtures incrate::shell::testing) -
src-tauri/src/core/console_tab/tests.rs(R23–R29 in the core) -
src-tauri/tests/architecture.rs(R4, R8, R11, R14) -
src/ui/lib/console-links.test.ts(R15, R18, R20) -
src/ui/lib/address.test.ts(R26) -
src-tauri/src/net/console/stats_from_console.rs(R32, R33, R35–R38, R46, with the fixtures; declared fromconsole.rsas#[cfg(test)] mod stats_from_console;) -
src-tauri/src/net/stats/requirement_tests.rs(declared fromstats.rsas#[cfg(test)] mod requirement_tests;: R31pick, R39, R40record_spaced) -
src-tauri/src/shell/commands/tests.rs(R31, R34 one request per call, R40 and R45 throughcurrent_stats) -
src/ui/lib/router-stats.test.ts(R41, with the history run),src/ui/lib/stats-view.test.ts(R42),src/ui/lib/router-panel.test.ts(R42, R43) -
src/ui/popup-router.test.tsandsrc/ui/popup-page.test.ts: theirrouter_statsstubs move to the contract v1.7 shape (R41) -
src/ui/lib/console-links.test.ts(R44)
- A console behind a password (Java I2P console password, i2pd
http.auth) is not detected. - i2pd with a
webrootother than/, or withhttp.addressother than127.0.0.1, is not detected. - Linux: the console view has no engine rule list yet (R19); see No-leak architecture.
- Find in page and zoom do nothing in the console tab (R25).
- Statistics from a Java I2P console: no build success rate, and no inbound and outbound split of the tunnels. The uptime has the coarse unit of the console ("8 hours"). The uptime needs an English console. The other figures work in every console language, but they need a router JVM that writes numbers with
.as the decimal mark and no group separator: Java I2P formats them with the JVM default locale, so on ade_DEorfr_FRsystem the bandwidth (53,91) and a long uptime (1,095 days) are "—". A future console that reorders the rows of a sidebar table without changing their count would give wrong figures; the fixture tests pin the 2.13.0 layout. - Statistics from i2pd: English console only (R30). Written from the i2pd source, not tested against a live i2pd.
- A sidebar section that the user removed in the Java I2P console settings gives "—" for its figures.
Generated from docs/wiki in the repository. Edit there, not here.