Repository navigation
Security
-
A link to the admin GUI's Users page could rewrite the query it ran. The list reads its filter from the url (
?attribute=email&value=…), and the value was written into the GraphQL query inside quotes as it was, so a value containing a quote could end the filter and add to the query — a link could make the list show users other than the ones it named. The value is now written as an escaped string, the attribute can only beemailoridand the page only a number. -
The admin GUI's user form wrote what was typed into its GraphQL query as it was. Creating a user put the email and password inside quotes unescaped, and editing one did the same with the email, so a password with a quote in it failed or added to the mutation, and one with
orin it was stored with a new line or a tab instead of the characters typed. Each is now written as an escaped string, and the user id in the edit and delete mutations as a quoted one. -
Links to the admin GUI's Background Jobs, Logs v2 and Network pages could rewrite the queries they ran, the same way. Background Jobs wrote the job type from the url into its GraphQL query unquoted and the job id inside quotes as it was. Logs v2 wrote the message filter into an SQL
ILIKE '%…%'as it was, and passed the whole url query on as the search, so?sql=…replaced the query and?stream_name=…chose the stream. Network wrote the status codes, the sort column and direction and the aggregation into its SQL as they were, and both details pages wrote the log id from the path intoWHERE _timestamp = …. Each value is now one the page knows (a job type, a sort column,ASCorDESC, a three-digit status code, a number) or an escaped string, and the search and stream can no longer be set from the url. A Network list opened without a sort also stops ending its query inundefined; it is sorted by time, newest first, as the form shows. -
The admin GUI rendered text from the instance as HTML. Notifications and the title of the details panel were written with
{@html}, so a constant name, a user's email, a background job's source name or — on the Network page — the method and path of any request someone sent to the instance became markup in a page that holds the instance's credentials. All of them are text now. The two places that wanted formatting, the update notification and the method badge on a network request, pass a Svelte snippet instead, so markup comes only from the GUI's own templates.
Fixes
-
The admin GUI's Logs and Network pages could break on what an earlier visit left in the browser's storage. The pinned logs and the Network presets were read from
localStoragewithJSON.parseand used as they came, so a value that did not parse stopped the page from pinning logs or opening its presets, and one that parsed to something other than a list broke pinning the same way. Both are now read the way the other saved preferences are: a value that cannot be read, or is not a list, is set aside for the defaults. The Database page also stopped saving whether its table list is hidden, which nothing ever read back. -
The admin GUI showed an empty list when the instance refused the query for it. A query answered with GraphQL errors left the Database tables and records, the Users list and details, the Background Jobs list and details and the Constants list empty, which reads exactly like "nothing here"; the Database page could also stop with an error. Each now shows an error with the instance's message. A request that never reached the instance still shows only the connection indicator.
-
The admin GUI's Logs page could show the same log twice. It asks for newer logs every 3 seconds, and when the instance took longer than that to answer, the next request asked from the same last log and both answers were added. A request now waits for the previous one.
-
Two notifications created in the same millisecond broke the notification list, which could happen once two failed loads were both reported. Each notification now has its own id.
-
The admin GUI's Logs v2 and Network pages searched from January 1970. The time limit sent the end of the range in microseconds, which is what the instance searches by, and the start in milliseconds, so every search with a date reached back to the epoch instead of three days. Both ends are in microseconds now. The chosen day was also read as UTC midnight before moving to the end of the local day, which lands on the day before anywhere west of UTC, and the date input offered the UTC day as today, which is yesterday for an hour or two after midnight in Europe; both now use the browser's own time zone. A date in the url that is not a day searches without a limit, as no date does, instead of sending an invalid one.
-
The admin GUI's Constants page said nothing when the instance refused a change. Creating, updating or deleting a constant that the instance refused read the constant's name from the refused answer, which has none, so the page stopped with an error and showed no notification; the delete failure was also reported as a success. Each now shows an error naming the constant.
-
The admin GUI's update notice could fail to appear, or fail silently. The home page compared the newest pos-cli version on npm with the running one before the instance had answered with it, so when npm answered first the "Update available" button never showed. And when the browser refused to copy the update command, the click failed without a word; it now shows an error with the command to run.
-
The admin GUI's Users list could show the users from before a filter was applied. The list was loaded once when the page opened and again after each filter, and whichever answer arrived last was shown. The list and the user details are now loaded by SvelteKit
loadfunctions from the url, which apply only the latest navigation; opening a user no longer loads the list again. -
The admin GUI's Database page could show records sorted, filtered or paged by an earlier choice than the one on screen. Each change to the sorting, the filter, the page or the deleted switch sent its own request and whichever answer arrived last was shown, so changing the sort field and then the order quickly could leave the list in the first order — measured, about 3 times in 10. The view now lives in the url (
?sort_by=id&sort_order=ASC&page=2…) and the records are loaded by the page's SvelteKitloadfunction, which applies only the latest navigation. That also makes a filtered or sorted view something you can reload, bookmark or go back to. The values read from the url are checked before they reach the GraphQL query, since a link can carry anything; one that is not valid falls back to the default view. -
The code of a background job is highlighted again in the admin GUI. The details panel showed a job's Liquid as plain text: the code block highlighted itself through a global
Prismwhile/prism.jswas still loading, and threwPrism is not defined. The block now loads the highlighter itself and highlights only its own element when it has loaded, so it no longer re-highlights every code block on the page or finds its own through a page-wide#codeid. -
Typing a filter on the admin GUI's Logs page threw an error whenever the list was scrolled to the bottom: the page scrolled to the first
<footer>in the document after every update, and the filter hides that footer. The page now keeps its own references to the list and its footer, and skips the scroll while there is nothing to scroll to. The record and user forms likewise look for the first validation error inside their own form, and do nothing if there is none, rather than taking the first alert anywhere on the page. -
The admin GUI's Background Jobs, Logs v2 and Network pages are loaded from the url, as Users and Database already were. Background Jobs reloaded its list from an effect on the form and read the url only when the page opened, so the browser's Back and Forward buttons changed the url but left the list as it was, and a slow answer could replace a newer one; all three lists and their details panels are now loaded by SvelteKit
loadfunctions, which apply only the latest navigation. Choosing another job type starts from its first page. A details link to a log, request or job that does not exist now says so instead of showing "Loading…" for good, and the Network details panel reads an error status fromlb_status_code, as the list does, rather than fromhttp_request_protocol.lb_status_code. -
Escape stopped closing the record and user forms once you had typed in them, and the form then could not be opened again. Each form closed itself through a document listener added with
{ once: true }, which the first key typed into the form used up; Escape after that closed the<dialog>natively but left the form mounted, and one never pressed outlived the form. Escape is now handled on the dialog itself. The Database page'sbshortcut also added a document listener on every visit and never removed it, and the details panel kept its resize listeners if it closed mid-drag; both are now removed with their component. -
The admin GUI's Background Jobs list kept showing the time a job would run as it was when the list loaded: "in 10 minutes" stayed "in 10 minutes". A timer updated each job every second, but through a variable the old component syntax did not watch, so nothing re-rendered. Two other things that ran on their own also stopped: the highlight on a record or constant just saved cleared itself and then re-armed its own timer, every 7 seconds for the rest of the session, and the Ctrl+K shortcut on the Database page added a listener on every visit without removing it. All three came with moving the GUI's single global store to Svelte 5 runes — one module per concern, each exporting
[something]State— and moving the components that read it in a$:statement to runes too, since a legacy$:stops a component's template from tracking rune state. -
The admin GUI's shared components are written in Svelte 5 runes. The header, notifications, icons, JSON tree, copy button, log diagnostic, connection indicator and the number and toggle fields no longer use the Svelte 4 syntax; the number field calls an
oninputprop and the toggle anonchangeprop instead of sending component events. The address of the gui server, copied into nine places, is oneserverUrlhelper. Hovering the Database link while its tables were still loading asked for them again; it now asks once. -
A record the admin GUI could not delete or restore is reported again. A refused deletion answered with errors and no
record_delete, and the message read its id from there, so it threw and no notification appeared; restoring did the same. Both now name the record that was asked for. The Database components (ContextMenu,Create,Delete,Restore) are written in Svelte 5 runes, withoncloseandonsuccessprops instead of component events, and the records table puts its rows in a<tbody>. -
The admin GUI's user form threw on a value that is not valid JSON or a valid array instead of scrolling to the message: it waited for
tick(), which it never imported. The Users components (ContextMenu,Create,Delete) are written in Svelte 5 runes, withonclose,onreloadandonsuccessprops instead of component events, and deleting a user now says "User … deleted" rather than "Record … deleted". -
A background job the admin GUI could not retry or delete is reported again. A refused retry or deletion answered with errors and no job, and the message read the job's id from there, so it threw and no notification appeared. Both now name the job that was asked for. The Background Jobs components (
Delete,Retry) and the Network filters presets are written in Svelte 5 runes, withonsuccessandoncloseprops instead of component events, and the presets tests run again: they intercept/api/logsv2, so they no longer need an instance that serves it.