This repository has been archived on 2026-10-11. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
HiNC-2025-win-desktop-Deploy/App/wwwroot/HiAPI-docsite/anatomy/platform/log-viewer.html
T
2026-09-09 17:35:23 +08:00

319 lines
20 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Log Viewer Page | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Log Viewer Page | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-LogViewer">
<h1 id="log-viewer-page">Log Viewer Page</h1>
<p>The Log Viewer is the read-only screen at <code>/preference/log</code>, route name <code>preference-log</code>, that
renders the web service's own log file for the current day. It is not a Control-Tree page and takes
no <code>?tree=</code> query: the whole page is one toolbar over one scrolling text area. Despite the route
path it is not an entry of the Preference dropdown, and it is not under <code>/util/</code>.</p>
<h2 id="reaching-the-page">Reaching the Page</h2>
<p>The only navigation into the page is the <code>Show Log</code> button on the menu bar. It sits in the bar's
right-hand group, after the active-page title and its separator and before the logout button, and it
carries the <code>article</code> icon with the tooltip <code>View the application log</code>. The button is rendered
unconditionally — no route, project, licence or authentication state hides it — so it is reachable
from every page in the shell. The Preference dropdown carries the Language submenu, the CSV and CL
Controller visibility checkboxes and Show Physics Options, and no log entry; the Page dropdown does
not list the route either.</p>
<p>The route sits inside the main layout, so the page renders under the same menu bar and footer as
every other page, and it is behind the login gate whenever that gate is on — the router's navigation
guard admits only the login route itself while the session is unauthenticated. The route carries an
i18n title key, so the browser tab reads <code>Log Viewer - HiNC</code> and the menu bar's active-page
indicator reads <code>Log Viewer</code>.</p>
<h2 id="what-it-reads">What It Reads</h2>
<p>Two endpoints, both on the project controller, and both served from the same file:</p>
<table>
<thead>
<tr>
<th>Request</th>
<th>Answer</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>GET /api/project/logs</code></td>
<td><code>{ date, content }</code> for today's file; <code>404</code> with a message when there is none</td>
</tr>
<tr>
<td><code>GET /api/project/download-log</code></td>
<td>the same file as a <code>text/plain</code> attachment; <code>404</code> with the same message when there is none</td>
</tr>
</tbody>
</table>
<p>Both compose <code>logs/log-{yyyy-MM-dd}.txt</code> beneath the process's current working directory and date the
name from the server's local clock. Neither supports a range or a tail: the whole file is read into
memory and returned in full on every call, so an auto-refresh at a short interval re-transfers the
entire log each tick. Any failure inside either handler answers <code>400</code> with the exception message.</p>
<p>The two client wrappers part company on that <code>404</code>. The wrapper over <code>logs</code> normalises it into an
empty result rather than an error, which is what gives the page a clean &ldquo;no log yet&rdquo; state
instead of a red panel; every other non-OK status there is raised as a typed error carrying the HTTP
status and the response body. The download wrapper normalises nothing — <code>404</code> included, every non-OK
status becomes that same typed error, a path the disabled-without-content <code>Download</code> button makes
hard to reach. Because the controller declares no anonymous carve-out, both endpoints fall under the
authenticated-user fallback policy when the login gate is enabled.</p>
<p>The date badge shows whatever date the last answer reported. On the <code>404</code> path that date is not the
server's — the client fills it from the browser's own clock in UTC — so with no log file present the
badge can read a different day than the file name the server would have used.</p>
<h2 id="refresh-and-the-auto-interval">Refresh and the Auto Interval</h2>
<p>The page loads once on mount. After that every reload is driven by one of three things: the
<code>Refresh</code> button, the <code>Retry</code> button inside the error panel, or the auto-refresh timer.</p>
<p>Auto-refresh is off by default. The <code>Auto</code> toggle arms it and the interval select beside it chooses
the period from a fixed list of <code>2 s</code>, <code>5 s</code>, <code>10 s</code> and <code>30 s</code>, defaulting to five seconds; the
select is disabled while the toggle is off, but it keeps its value. The period is picked from that
dropdown rather than typed, so it takes effect on selection. A change to either control tears the
existing timer down, and a fresh one starts only while the toggle is still armed: an interval change
therefore takes effect immediately but also restarts the countdown, while disarming the toggle
simply stops the timer. Arming the toggle schedules the first automatic load one full interval
later; it does not fetch straight away.</p>
<p>Ticks are not coalesced with a request already in flight — each tick starts the same load
unconditionally.</p>
<p>Neither the toggle nor the interval is persisted. They are page-local state, held in neither the
browser's local storage nor the server-side user preferences, so a browser reload returns both to
their defaults.</p>
<h3 id="the-page-is-kept-alive">The Page Is Kept Alive</h3>
<p>The shell caches routed pages in a keep-alive keyed on the project epoch. Leaving the Log Viewer
therefore deactivates it rather than unmounting it, and the page's teardown hook — the one that
stops the timer — does not run. Two consequences follow. An armed auto-refresh keeps polling the log
endpoint in the background after navigation away, until the keep-alive is rebuilt by a project change
or the browser reloads. And returning to the page does not re-run the initial load: the previous
content, its <code>last loaded</code> stamp, the toggle and the chosen interval are all still there, and the
view refreshes on the next tick or on <code>Refresh</code>.</p>
<h2 id="errors-and-scroll-position">Errors and Scroll Position</h2>
<p>A failed load fills the page's error state and also pushes one foreground line onto the shell's
routine-progress footer, so the failure is visible from any page. The error panel takes precedence
over content in the text area, so a failed refresh hides the text that was already loaded — but only
until the next attempt starts, because the error text is cleared before that request goes out. The
content itself is never discarded: it reappears the moment the attempt begins, and the error panel
returns only if that attempt fails as well. With auto-refresh armed against an endpoint that keeps
failing, the text and the panel therefore alternate on every tick.</p>
<p>After a successful load the view scrolls to the bottom, but only while it is already stuck there —
the scroll handler treats a distance of less than 30 px from the bottom as stuck. Scrolling up
therefore freezes the position through an auto-refresh, and scrolling back to the bottom re-arms the
follow behaviour.</p>
<h2 id="level-filtering-and-where-the-file-comes-from">Level Filtering, and Where the File Comes From</h2>
<p>The page applies no filtering of its own. There is no level selector, no category selector, no
search box and no date picker: it renders the file it is given, verbatim, in the order it was
written. Which entries reach the file at all is decided at write time, by the host's logging filters
rather than by anything on this screen — see <a class="xref" href="program-and-hosting.html">Program and Hosting</a> for those rules and how
they differ from the console provider's.</p>
<p>The sink is a daily file logger provider registered at startup against a <code>logs</code> folder under the
process's current working directory. It writes one entry per line as
<code>[yyyy-MM-dd HH:mm:ss.fff] [Level] Category - message</code>, appends the exception on its own lines when
there is one, serialises appends behind a lock and swallows every I/O failure, on the principle that
logging must never fault a request. Logging scopes are not rendered into the file. The application
writes one Information line immediately after the host is built, so the file for the day the service
starts has content before the first request arrives and a freshly started service shows something
rather than an empty state. The sink recomposes the file name from the clock on every append, so a
service left running past midnight has no file for the new day until something is logged, and the
page shows its empty state until then.</p>
<p>Only the current day's file is reachable. Previous days' files stay on disk and the page offers no
way to open one. A file that exists but is empty renders the same empty state as a missing one; only
the date badge can separate the two, since the empty file's date comes from the server's answer and
the missing file's from the browser's UTC clock.</p>
<p>The same controller also exposes a POST endpoint that appends a caller-supplied line to the same
daily file. It writes with its own formatting rather than through the logger provider, and nothing
in the shipped SPA calls it.</p>
<h2 id="copy-download-and-what-the-page-cannot-do">Copy, Download, and What the Page Cannot Do</h2>
<p><code>Copy</code> writes the loaded text to the system clipboard and reports the outcome as a toast, positive
or negative. <code>Download</code> fetches the file as a blob from the download endpoint and saves it
client-side under the name <code>log-&lt;date&gt;.txt</code>, repeating the date the last answer reported, and also
ends in a toast. Both buttons are disabled while there is nothing loaded.</p>
<p>The page cannot clear, truncate, rotate or delete the log — there is no destructive action on it at
all, and no endpoint behind it that would perform one. It cannot reach another day, filter by level
or text, or stream: refreshing is polling over the same whole-file request, with no hub connection
and no server push. It is a viewer.</p>
<h2 id="layout">Layout</h2>
<ul>
<li>Log Viewer Page — a viewport-locked page sized from the layout's measured header and footer, so
the window itself never scrolls
<ul>
<li>Top Tool Bar
<ul>
<li><code>Log Viewer</code> Title</li>
<li>Date Badge — outlined; carries the date the last answer reported, and reads <code>today</code> only
before any answer has set one. Coloured as the primary accent while content is loaded and grey
otherwise</li>
<li>Last-Loaded Caption — <code>last loaded: {time}</code>, shown once a load has succeeded. The time is
formatted in the <strong>application</strong> locale rather than the browser's, so it re-renders on a
language switch; the hover title carries the same instant as an ISO 8601 UTC string</li>
<li>Line Count Caption — shown only while content is loaded, where it reads <code>{count} lines</code>. The
plural string's <code>no lines</code> and <code>one line</code> forms are out of reach: a zero count is exactly the
state that hides the caption, and every writer ends its entry with a newline. The count is the
number of newline-separated segments, so a file ending in a newline counts one more than it
shows, and a multi-line exception counts as several</li>
<li><code>Auto</code> Toggle — arms auto-refresh; off by default. Tooltip <code>Re-fetch the log file periodically</code></li>
<li>Interval Select — <code>2 s</code> / <code>5 s</code> / <code>10 s</code> / <code>30 s</code>, defaulting to <code>5 s</code>; disabled while <code>Auto</code>
is off</li>
<li><code>Refresh</code> Button — reloads at once, and shows a spinner in place of its label while the
request is in flight. Tooltip <code>Reload the log file</code></li>
<li><code>Copy</code> Button — disabled without content. Tooltip <code>Copy log content to clipboard</code></li>
<li><code>Download</code> Button — disabled without content. Tooltip <code>Download today's log file</code></li>
</ul>
</li>
<li>Separator</li>
<li>Log Area — a dark, monospaced, scrolling region filling the rest of the page. Long lines are not
wrapped, so it scrolls in both directions. It shows exactly one of four things:
<ul>
<li>Error Panel — an error icon, the failure message, and a <code>Retry</code> Button that repeats the load</li>
<li>Empty Panel — a document icon, <code>No log file for today.</code>, and the hint
<code>Log file will appear once the webservice writes its first entry.</code></li>
<li>Log Content — the file's text, preformatted and read-only, with no folding, no line numbers and
no per-line affordance of any kind</li>
<li>Loading Spinner — with <code>Loading log...</code> beneath it, shown while a load is in flight and
nothing has been loaded yet</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/pages/LogViewerPage.vue</code> — the whole page: the toolbar, the four states of the
log area, the auto-refresh timer and its watcher, the stick-to-bottom scroll rule, and the copy
and download handlers.</li>
<li><code>wwwroot-src/src/api/logs.ts</code> — the typed wrappers over the two endpoints, the <code>404</code>-to-empty
normalisation with its browser-clock date fallback, and the error type carrying the HTTP status.</li>
<li><code>wwwroot-src/src/router/routes.ts</code> — the <code>preference/log</code> path, the <code>preference-log</code> route name and
the title key the tab and the menu bar's active-page indicator resolve.</li>
<li><code>wwwroot-src/src/router/index.ts</code> — the login guard the route passes through, and the retitle hook
that names the browser tab.</li>
<li><code>wwwroot-src/src/components/AppMenuBar.vue</code> — the always-visible <code>Show Log</code> button in the bar's
right-hand group, and the Preference dropdown that carries no log entry.</li>
<li><code>wwwroot-src/src/layouts/MainLayout.vue</code> — the keep-alive that caches this page rather than
unmounting it, and the project epoch that rebuilds the cache.</li>
<li><code>wwwroot-src/src/stores/appState.ts</code> — the message action that forwards a load failure to the
footer's foreground channel.</li>
<li><code>wwwroot-src/src/stores/routineProgress.ts</code> — the foreground-message store that failure lands in.</li>
<li><code>wwwroot-src/src/utils/pageFit.ts</code> — the page-height function that locks the page to the measured
viewport so only the log area scrolls.</li>
<li><code>wwwroot-src/src/i18n/en/logs.ts</code> — the page's own English strings: the title, the empty and
loading states, the plural line count and the tooltips.</li>
<li><code>wwwroot-src/src/i18n/en/menu.ts</code> — the <code>Show Log</code> label and its tooltip.</li>
<li><code>wwwroot-src/src/i18n/en/common.ts</code> — the shared <code>Auto</code>, <code>Refresh</code>, <code>Copy</code> and <code>Download</code> labels.</li>
<li><code>wwwroot-src/src/i18n/en/routes.ts</code> — the <code>Log Viewer</code> route title.</li>
<li><code>Environments/ProjectController.cs</code> — the two endpoints the page reads, both resolving today's file
under the working directory, plus the append endpoint nothing calls.</li>
<li><code>Common/DailyFileLoggerProvider.cs</code> — the sink: the per-day file, the entry format, the lock, the
swallowed I/O failures and the null scope.</li>
<li><code>Program.cs</code> — registers that provider and its level filters, and writes the one startup entry
right after the host is built.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="../shell/main-panel.html">Main Panel</a> — the menu bar whose right-hand <code>Show Log</code> button is the only navigation into this page</li>
<li><a class="xref" href="../shell/preference-menu.html">Preference Menu Dropdown</a> — the dropdown this route's path is named after, and which carries no log entry of its own</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>