Skip to content

feat(desktop): OpenMES Desktop app (Tauri) + unattended install preset#186

Merged
jakub-przepiora merged 2 commits into
developfrom
feat/desktop-app
Jul 19, 2026
Merged

feat(desktop): OpenMES Desktop app (Tauri) + unattended install preset#186
jakub-przepiora merged 2 commits into
developfrom
feat/desktop-app

Conversation

@jakub-przepiora

@jakub-przepiora jakub-przepiora commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Summary

Brings the OpenMES Desktop app to develop, alongside mobile/. It's a Tauri (Rust + system webview) shell that bundles PHP + the backend and runs OpenMES as a local desktop app / LAN MES server — no separate PHP/Postgres install on the client.

What's included

  • desktop/ — the Tauri app:
    • src-tauri/ (Rust): resolves/serves the backend via bundled PHP, manages a local DB (SQLite on Linux, bundled PostgreSQL on Windows), tray + control panel, and an in-app self-updater (staged atomic swap of the managed backend copy; self-contained release ZIP, no composer/npm on the client).
    • TS control panel (src/), OpenMES icons, and build scripts producing self-contained installers: Windows .exe (NSIS) and Linux .deb/.rpm.
  • Backend INSTALLER_PRESET mode — an unattended install path the desktop shell drives: skips the env/database wizard steps and goes straight to admin creation. Touches InstallController, the install/* views and bootstrap/app.php; covered by InstallPresetTest.

Testing

  • InstallPresetTest: 6 passed on the develop base.
  • Desktop builds verified end-to-end this session: Windows .exe (86 MB) and Linux .deb (65 MB) both produced and self-contained; the in-app updater ran a full download → verify → migrate → atomic swap → restart cycle.

Notes

🤖 Generated with claude-flow

Summary by CodeRabbit

  • New Features

    • Added a standalone desktop application for Windows and Linux with local or remote database support.
    • Added server controls, database discovery, configuration management, logs, migration tools, and in-app updates.
    • Added unattended installation presets for faster setup and automatic database initialization.
    • Added recovery options and clearer startup error messages.
  • Documentation

    • Added desktop setup, runtime, build, and known-limitations documentation.
  • Style

    • Simplified installation wizard labels and refreshed installation guidance.

Brings the desktop platform app to develop alongside mobile/. A Tauri
(Rust + webview) shell that bundles PHP + the backend and runs OpenMES as
a local desktop app / LAN MES server, with an in-app self-updater.

- desktop/: Tauri app (src-tauri Rust, TS control panel, icons, build
  scripts for self-contained Windows .exe and Linux .deb/.rpm)
- Backend INSTALLER_PRESET mode: unattended install (skips env/db wizard
  steps, straight to admin creation) that the desktop shell drives —
  InstallController + install views + bootstrap/app.php
- InstallPresetTest covers the preset behaviour

The shop-floor workstation feature lands separately in #175.
@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5e3c7bfc-7e15-4564-a277-6673a90d7f7f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR adds a Tauri desktop application with embedded PHP/database runtime management, remote database setup, self-updating, platform build scripts, and a complete frontend. It also adds an INSTALLER_PRESET backend installation flow with admin-step handling and feature tests.

Changes

Tauri desktop application

Layer / File(s) Summary
Desktop project foundation
desktop/package.json, desktop/src-tauri/*, desktop/tsconfig.json, desktop/vite.config.ts, desktop/README.md
Adds Tauri, Rust, Vite, TypeScript, permissions, application configuration, ignored artifacts, and desktop documentation.
Runtime staging and platform builds
desktop/scripts/*, .gitignore
Adds backend/resource bundling, portable runtime downloads, and Linux/Windows installer build scripts.
Embedded server and database runtime
desktop/src-tauri/src/lib.rs
Adds configuration persistence, PHP and PostgreSQL provisioning, database discovery/testing, server lifecycle commands, LAN URLs, window management, and shutdown cleanup.
Desktop boot and control interface
desktop/index.html, desktop/src/main.ts, desktop/src/styles.css
Adds boot screens, local/remote database selection, server controls, settings, status/log views, error handling, and styling.
Self-contained updater
desktop/src-tauri/src/lib.rs, desktop/src/main.ts
Adds release selection, download and verification, extraction, migration, atomic swap/rollback, progress events, and updater tests.

Unattended backend installation

Layer / File(s) Summary
Preset installation pipeline
backend/app/Http/Controllers/InstallController.php, backend/bootstrap/app.php
Adds validated INSTALLER_PRESET handling, automated database setup and seeding, preset-aware admin creation, storage-path override support, and direct completion redirects.
Installation interface updates
backend/resources/views/install/*
Hides site and sample-data sections for preset installs and simplifies installation labels and completion text.
Preset installation coverage
backend/tests/Feature/InstallPresetTest.php
Tests preset routing, admin creation and validation, existing-user completion, unsupported presets, default wizard behavior, and marker-file handling.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant DesktopUI
  participant TauriBackend
  participant PHPServer
  participant Database
  User->>DesktopUI: launch desktop application
  DesktopUI->>TauriBackend: ensure runtime and database mode
  TauriBackend->>Database: discover or validate database
  DesktopUI->>TauriBackend: start server
  TauriBackend->>PHPServer: launch artisan server
  PHPServer->>Database: connect using selected configuration
  DesktopUI->>TauriBackend: poll server status
  TauriBackend-->>DesktopUI: return ready URL
  DesktopUI->>PHPServer: navigate to local application
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately reflects the two main changes: the new Tauri desktop app and the unattended install preset.
Docstring Coverage ✅ Passed Docstring coverage is 87.96% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/desktop-app

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@jakub-przepiora

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

🧹 Nitpick comments (5)
desktop/README.md (3)

12-12: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add language tag to fenced code block.

Specifying a language tag improves rendering and resolves markdownlint warnings.

📝 Proposed change
-```
+```text
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@desktop/README.md` at line 12, Update the fenced code block in the README to
include the text language tag, changing the opening fence from an unannotated
block to a text-labeled fence while preserving its contents and closing fence.

Source: Linters/SAST tools


75-77: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use correct Polish typography for closing quotes.

Consider using the standard Polish closing quote () instead of the straight double quote (").

📝 Proposed change
-  2015–2022 x64*. Instalator i `postgres.exe` są niepodpisane (SmartScreen →
-  „Więcej informacji → Uruchom mimo to"). Nie uruchamiać jako Administrator
-  (Postgres odmawia startu z konta admina).
+  2015–2022 x64*. Instalator i `postgres.exe` są niepodpisane (SmartScreen →
+  „Więcej informacji → Uruchom mimo to”). Nie uruchamiać jako Administrator
+  (Postgres odmawia startu z konta admina).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@desktop/README.md` around lines 75 - 77, Update the Polish quotation in the
installer warning within the README text to use the standard closing quote
character (”) instead of the straight double quote, preserving the surrounding
wording and formatting.

Source: Linters/SAST tools


22-24: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use correct Polish typography for closing quotes.

Consider using the standard Polish closing quote () instead of the straight double quote (").

📝 Proposed change
 Zamknięcie okna **nie wyłącza serwera** — aplikacja chowa się do traya.
-Pełne wyłączenie: tray → „Zakończ".
+Pełne wyłączenie: tray → „Zakończ”.
 
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@desktop/README.md` around lines 22 - 24, Update the Polish quoted tray label
in the README text to use the standard Polish closing quotation mark (”) instead
of the straight double quote, preserving the existing wording and formatting.

Source: Linters/SAST tools

desktop/index.html (1)

36-71: 🎯 Functional Correctness | 🔵 Trivial

Missing explicit database driver selection.

This form only collects host/port/database/username/password; there's no control to pick Postgres vs. MySQL explicitly, even though the copy on Line 27 advertises support for both. See related comment on desktop/src/main.ts where the driver is inferred purely from the port number.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@desktop/index.html` around lines 36 - 71, Update the db-remote-form to add an
explicit database-driver selector for Postgres and MySQL alongside the existing
connection fields. Ensure the selected driver is exposed through the form’s
existing submission flow so the connection logic can use it instead of relying
solely on the port number.
backend/app/Http/Controllers/InstallController.php (1)

46-61: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

installerPreset() silently breaks under config:cache.

The docblock itself flags the caveat: "Note: requires an uncached config (env() returns null after config:cache), which is the case for desktop/dev runs." If the packaged desktop/container runtime (or any future deployment path) ever runs config:cache or artisan optimize, INSTALLER_PRESET detection silently degrades to the normal interactive wizard with no error — a hard-to-diagnose regression for the entire unattended-install feature.

Prefer routing this through a dedicated config key (e.g. config('install.preset') backed by env('INSTALLER_PRESET') in a config file) so it survives caching, consistent with Laravel's general guidance against calling env() outside config files.

Please confirm the desktop/build staging scripts (desktop/scripts/*.sh) never run php artisan config:cache against the bundled backend before first launch.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/app/Http/Controllers/InstallController.php` around lines 46 - 61,
Update installerPreset() to read the preset through a dedicated cached
configuration key such as config('install.preset'), with that key defined in a
configuration file and backed by env('INSTALLER_PRESET') there; preserve
validation against self::DB_DRIVERS. Also inspect desktop/scripts/*.sh and
confirm the build or staging flow does not run php artisan config:cache against
the bundled backend before first launch.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/app/Http/Controllers/InstallController.php`:
- Around line 442-473: Validate db_database before it is consumed by the runtime
configuration flow, including the setupDatabase() validation path and the
preset-gated block in the install flow. Reject traversal segments such as “..”
or resolve the path and require it to remain under storage_path(), then only
pass the sanitized value to storage_path() and SQLite configuration.
- Around line 411-423: Move the validation currently performed by
`$request->validate()` in the install flow into a `CreateAdminRequest` Form
Request, adding a `rules()` method that reads the preset value from
`session('install_database_config')['preset']` and preserves the conditional
`site_name` and `site_url` rules. Update the controller action to type-hint and
use `CreateAdminRequest`, while retaining the existing validated defaults for
`site_name` and `site_url`.
- Around line 102-110: Update the preconfigured database connection failure
handling in InstallController so it does not flash $e->getMessage() to
unauthenticated users. Reuse the same PDO exception sanitization/classification
behavior and user-facing error wording established by setupDatabase(), while
preserving the redirect to the install.database route.

In `@desktop/index.html`:
- Around line 51-55: Correct the default database value in the `db-database`
input from `openmmes` to `openmes`, and update the corresponding `ServerConfig`
field `db_database` to use the same corrected default.

In `@desktop/scripts/fetch-runtimes.sh`:
- Around line 11-40: Update the runtime download flow in fetch-runtimes.sh to
pin PHP_WIN_URL to a fixed Windows PHP release instead of latest, define the
expected SHA-256 values for the PHP_LINUX_URL, PHP_WIN_URL, and PG_WIN_URL
archives, and verify each downloaded file’s checksum before tar or unzip
extraction; abort on any mismatch and remove the invalid temporary archive.

In `@desktop/src-tauri/src/lib.rs`:
- Around line 663-664: Update the command construction around Command::new and
the PHP -r invocation so config.db_password is no longer passed through args as
a positional CLI argument. Provide the password via a safer environment variable
or stdin, and adjust the PHP code to read it from that channel while preserving
the existing DSN, username, and database connection behavior.
- Around line 351-374: The PostgreSQL initialization currently enables trust
authentication and creates an unused password file. Update the initdb flow
around the `Command::new(...pg_exe("initdb"))` invocation to use SCRAM
authentication with a securely generated, persisted password, pass it via
`--pwfile`, and remove the temporary `.pgpw` write/delete logic; also update the
corresponding `DB_PASSWORD` configuration to use the same password instead of an
empty value.
- Around line 1428-1465: Update the update flow around migrate_on,
swap_in_place, and rollback_swap so rollback never starts old code against a
database already migrated to the new schema: create and retain a database
snapshot/backup before migration and restore it before each do_start rollback
path, or defer migration until the new version has booted successfully. Preserve
cleanup and failure reporting, and document the chosen forward-only migration
assumption if rollback cannot restore the schema.

In `@desktop/src-tauri/tauri.conf.json`:
- Around line 12-27: Update the Tauri app configuration by removing the
withGlobalTauri setting and replacing the null security.csp value with a
restrictive Content Security Policy that permits only the frontend’s required
resources and Tauri module usage. Keep the existing window configuration
unchanged.

In `@desktop/src/main.ts`:
- Around line 388-401: Replace the port-based driver inference in the
db-remote-form submit handler in desktop/src/main.ts (lines 388-401) with the
selected value from an explicit db-driver control. Add a Postgres/MySQL driver
select to the db-remote-form in desktop/index.html (lines 36-71), ensuring its
value populates the ServerConfig db_driver field.
- Around line 5-7: Update desktop/src/main.ts so all comments and source-level
text are written in English, including the areas around isSettingsWindow,
showMessage, humanizeError, and bootError. Introduce or reuse an i18n layer with
English keys and a Polish locale so user-facing messages remain Polish at
runtime. Replace inline Polish strings throughout the file with localized
lookups while preserving the existing UI behavior and error messaging.
- Around line 9-23: Update ServerConfig and the save_config serialization flow
so db_password is excluded from the JSON file and stored/retrieved through the
operating system keychain or credential store. Keep the remaining ServerConfig
fields persisted as before, and ensure runtime configuration still receives the
password from the secure store.
- Around line 363-418: Update ensureDbMode so its static controls receive event
listeners only once, while each invocation rebinds the current cfg and Promise
resolve state used by those handlers. Preserve the existing local, remote, back,
and submit behavior, but prevent stale closures from issuing duplicate IPC calls
or overwriting newer configuration after bootFlow retries.

---

Nitpick comments:
In `@backend/app/Http/Controllers/InstallController.php`:
- Around line 46-61: Update installerPreset() to read the preset through a
dedicated cached configuration key such as config('install.preset'), with that
key defined in a configuration file and backed by env('INSTALLER_PRESET') there;
preserve validation against self::DB_DRIVERS. Also inspect desktop/scripts/*.sh
and confirm the build or staging flow does not run php artisan config:cache
against the bundled backend before first launch.

In `@desktop/index.html`:
- Around line 36-71: Update the db-remote-form to add an explicit
database-driver selector for Postgres and MySQL alongside the existing
connection fields. Ensure the selected driver is exposed through the form’s
existing submission flow so the connection logic can use it instead of relying
solely on the port number.

In `@desktop/README.md`:
- Line 12: Update the fenced code block in the README to include the text
language tag, changing the opening fence from an unannotated block to a
text-labeled fence while preserving its contents and closing fence.
- Around line 75-77: Update the Polish quotation in the installer warning within
the README text to use the standard closing quote character (”) instead of the
straight double quote, preserving the surrounding wording and formatting.
- Around line 22-24: Update the Polish quoted tray label in the README text to
use the standard Polish closing quotation mark (”) instead of the straight
double quote, preserving the existing wording and formatting.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fd514032-d7b3-4734-89e3-e201b07d063d

📥 Commits

Reviewing files that changed from the base of the PR and between 6ff6dee and 5d97c09.

⛔ Files ignored due to path filters (21)
  • desktop/package-lock.json is excluded by !**/package-lock.json
  • desktop/src-tauri/Cargo.lock is excluded by !**/*.lock
  • desktop/src-tauri/icons/128x128.png is excluded by !**/*.png
  • desktop/src-tauri/icons/128x128@2x.png is excluded by !**/*.png
  • desktop/src-tauri/icons/32x32.png is excluded by !**/*.png
  • desktop/src-tauri/icons/64x64.png is excluded by !**/*.png
  • desktop/src-tauri/icons/Square107x107Logo.png is excluded by !**/*.png
  • desktop/src-tauri/icons/Square142x142Logo.png is excluded by !**/*.png
  • desktop/src-tauri/icons/Square150x150Logo.png is excluded by !**/*.png
  • desktop/src-tauri/icons/Square284x284Logo.png is excluded by !**/*.png
  • desktop/src-tauri/icons/Square30x30Logo.png is excluded by !**/*.png
  • desktop/src-tauri/icons/Square310x310Logo.png is excluded by !**/*.png
  • desktop/src-tauri/icons/Square44x44Logo.png is excluded by !**/*.png
  • desktop/src-tauri/icons/Square71x71Logo.png is excluded by !**/*.png
  • desktop/src-tauri/icons/Square89x89Logo.png is excluded by !**/*.png
  • desktop/src-tauri/icons/StoreLogo.png is excluded by !**/*.png
  • desktop/src-tauri/icons/icon.ico is excluded by !**/*.ico
  • desktop/src-tauri/icons/icon.png is excluded by !**/*.png
  • desktop/src/assets/tauri.svg is excluded by !**/*.svg
  • desktop/src/assets/typescript.svg is excluded by !**/*.svg
  • desktop/src/assets/vite.svg is excluded by !**/*.svg
📒 Files selected for processing (29)
  • .gitignore
  • backend/app/Http/Controllers/InstallController.php
  • backend/bootstrap/app.php
  • backend/resources/views/install/admin.blade.php
  • backend/resources/views/install/complete.blade.php
  • backend/resources/views/install/database.blade.php
  • backend/resources/views/install/environment.blade.php
  • backend/resources/views/install/welcome.blade.php
  • backend/tests/Feature/InstallPresetTest.php
  • desktop/.gitignore
  • desktop/README.md
  • desktop/index.html
  • desktop/package.json
  • desktop/scripts/build-linux.sh
  • desktop/scripts/build-windows.sh
  • desktop/scripts/bundle-resources.sh
  • desktop/scripts/fetch-runtimes.sh
  • desktop/src-tauri/.gitignore
  • desktop/src-tauri/Cargo.toml
  • desktop/src-tauri/build.rs
  • desktop/src-tauri/capabilities/default.json
  • desktop/src-tauri/icons/icon.icns
  • desktop/src-tauri/src/lib.rs
  • desktop/src-tauri/src/main.rs
  • desktop/src-tauri/tauri.conf.json
  • desktop/src/main.ts
  • desktop/src/styles.css
  • desktop/tsconfig.json
  • desktop/vite.config.ts

Comment on lines +102 to +110
try {
// No purge here: the connection is already configured by real env
// vars (and purging would destroy in-memory test databases).
config(['database.default' => $driver]);
DB::connection($driver)->getPdo();
} catch (\Exception $e) {
return redirect()->route('install.database')
->with('error', 'Preconfigured database is not reachable: '.$e->getMessage());
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Raw exception message exposed to an unauthenticated visitor.

$e->getMessage() from DB::connection($driver)->getPdo() is flashed directly into the redirect error, unlike setupDatabase() (Lines 272-288) which sanitizes/classifies PDO exceptions before display. Since install routes are reachable pre-auth (before an admin exists), this can leak internal connection details (host/port/driver internals) to anyone probing the not-yet-installed instance.

🛡️ Proposed fix
         } catch (\Exception $e) {
-            return redirect()->route('install.database')
-                ->with('error', 'Preconfigured database is not reachable: '.$e->getMessage());
+            return redirect()->route('install.database')
+                ->with('error', 'Preconfigured database is not reachable. Check the server logs for details.');
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
// No purge here: the connection is already configured by real env
// vars (and purging would destroy in-memory test databases).
config(['database.default' => $driver]);
DB::connection($driver)->getPdo();
} catch (\Exception $e) {
return redirect()->route('install.database')
->with('error', 'Preconfigured database is not reachable: '.$e->getMessage());
}
try {
// No purge here: the connection is already configured by real env
// vars (and purging would destroy in-memory test databases).
config(['database.default' => $driver]);
DB::connection($driver)->getPdo();
} catch (\Exception $e) {
return redirect()->route('install.database')
->with('error', 'Preconfigured database is not reachable. Check the server logs for details.');
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/app/Http/Controllers/InstallController.php` around lines 102 - 110,
Update the preconfigured database connection failure handling in
InstallController so it does not flash $e->getMessage() to unauthenticated
users. Reuse the same PDO exception sanitization/classification behavior and
user-facing error wording established by setupDatabase(), while preserving the
redirect to the install.database route.

Comment on lines +411 to +423
$isPreset = (bool) (session('install_database_config')['preset'] ?? false);

$validated = $request->validate([
'admin_username' => 'required|string|max:255|unique:users,username',
'admin_email' => 'required|email|max:255|unique:users,email',
'admin_password' => 'required|string|min:8|confirmed',
'site_name' => 'required|string|max:255',
'site_url' => 'required|url',
'site_name' => [$isPreset ? 'nullable' : 'required', 'string', 'max:255'],
'site_url' => [$isPreset ? 'nullable' : 'required', 'url'],
]);

$validated['site_name'] = $validated['site_name'] ?? 'OpenMES';
$validated['site_url'] = $validated['site_url'] ?? 'http://localhost';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Inline $request->validate() in the controller.

Lines 413-419 add new conditional rules directly inline in the controller rather than in a Form Request. As per coding guidelines, backend/app/Http/{Controllers,Requests}/**/*.php: "Validation must use Form Requests, never validate inline in controllers." Moving this to a CreateAdminRequest (with a rules() method reading session('install_database_config')['preset']) would restore compliance and improve testability.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/app/Http/Controllers/InstallController.php` around lines 411 - 423,
Move the validation currently performed by `$request->validate()` in the install
flow into a `CreateAdminRequest` Form Request, adding a `rules()` method that
reads the preset value from `session('install_database_config')['preset']` and
preserves the conditional `site_name` and `site_url` rules. Update the
controller action to type-hint and use `CreateAdminRequest`, while retaining the
existing validated defaults for `site_name` and `site_url`.

Source: Coding guidelines

Comment on lines +442 to 473
// Re-apply runtime DB config so Eloquent uses the correct connection.
// Preset installs skip this: their connection comes straight from real
// environment variables and is already active — re-applying it from
// session would mangle non-path values like ":memory:".
if (empty($dbConfig['preset'])) {
if ($driver === 'sqlite') {
$dbPath = $dbConfig['db_database'];
if (! str_starts_with($dbPath, '/')) {
$dbPath = storage_path($dbPath);
}
config(["database.connections.{$driver}.database" => $dbPath]);
} else {
config([
"database.connections.{$driver}.host" => $dbConfig['db_host'],
"database.connections.{$driver}.port" => $dbConfig['db_port'],
"database.connections.{$driver}.database" => $dbConfig['db_database'],
"database.connections.{$driver}.username" => $dbConfig['db_username'],
"database.connections.{$driver}.password" => $dbConfig['db_password'],
]);
}
config(["database.connections.{$driver}.database" => $dbPath]);
} else {
config([
"database.connections.{$driver}.host" => $dbConfig['db_host'],
"database.connections.{$driver}.port" => $dbConfig['db_port'],
"database.connections.{$driver}.database" => $dbConfig['db_database'],
"database.connections.{$driver}.username" => $dbConfig['db_username'],
"database.connections.{$driver}.password" => $dbConfig['db_password'],
]);

config(['database.default' => $driver]);

DB::purge($driver);
DB::reconnect($driver);
}

config([
'database.default' => $driver,
'app.name' => $validated['site_name'],
'app.url' => $validated['site_url'],
]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Unsanitized db_database flows into storage_path() — path traversal risk, reachable pre-auth.

Static analysis flags Line 449: "Avoid building paths from unsanitized input ... [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')." $dbConfig['db_database'] originates from setupDatabase()'s validation ('db_database' => 'required|string', no traversal check), so a value like ../../../../tmp/x reaches storage_path($dbPath) unfiltered. Because install routes are open to any visitor before the install marker exists, this is exploitable pre-auth, not just by a trusted admin. The underlying gap predates this diff (same pattern in setupDatabase() Lines 243-248) but is now duplicated into this newly-added preset-gated block.

Add a traversal check where db_database is validated/consumed (e.g. reject values containing .. or resolve+confirm the final path stays under storage_path()).

🧰 Tools
🪛 ast-grep (0.44.1)

[error] 449-449: Avoid building paths from unsanitized input
Context: storage_path($dbPath)
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(laravel-path-traversal-storage)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/app/Http/Controllers/InstallController.php` around lines 442 - 473,
Validate db_database before it is consumed by the runtime configuration flow,
including the setupDatabase() validation path and the preset-gated block in the
install flow. Reject traversal segments such as “..” or resolve the path and
require it to remain under storage_path(), then only pass the sanitized value to
storage_path() and SQLite configuration.

Source: Linters/SAST tools

Comment thread desktop/index.html
Comment on lines +51 to +55
<div class="row">
<label>
Baza
<input id="db-database" type="text" value="openmmes" />
</label>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Default database name likely has a typo (openmmes).

The suggested default value has a duplicated "m" — matches the app name "OpenMES" with an extra letter. This same typo appears in the Rust-side ServerConfig default (db_database: "openmmes".into()), so it's consistently wrong rather than intentional branding.

✏️ Suggested fix
-            <input id="db-database" type="text" value="openmmes" />
+            <input id="db-database" type="text" value="openmes" />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div class="row">
<label>
Baza
<input id="db-database" type="text" value="openmmes" />
</label>
<div class="row">
<label>
Baza
<input id="db-database" type="text" value="openmes" />
</label>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@desktop/index.html` around lines 51 - 55, Correct the default database value
in the `db-database` input from `openmmes` to `openmes`, and update the
corresponding `ServerConfig` field `db_database` to use the same corrected
default.

Comment on lines +11 to +40
PHP_LINUX_URL="https://dl.static-php.dev/static-php-cli/common/php-8.3.31-cli-linux-x86_64.tar.gz"
PHP_WIN_URL="https://windows.php.net/downloads/releases/latest/php-8.3-nts-Win32-vs16-x64-latest.zip"
PG_WIN_URL="https://repo1.maven.org/maven2/io/zonky/test/postgres/embedded-postgres-binaries-windows-amd64/16.4.0/embedded-postgres-binaries-windows-amd64-16.4.0.jar"

# ── Linux PHP (do buildu Linux) ──────────────────────────────────────────────
echo "[1/3] Linux static PHP…"
mkdir -p src-tauri/resources/php
if [ ! -f src-tauri/resources/php/php ]; then
curl -fL --retry 2 -o /tmp/php-linux.tar.gz "$PHP_LINUX_URL"
tar xzf /tmp/php-linux.tar.gz -C src-tauri/resources/php && rm /tmp/php-linux.tar.gz
chmod +x src-tauri/resources/php/php
fi

# ── Windows PHP (do buildu Windows) ──────────────────────────────────────────
echo "[2/3] Windows PHP…"
mkdir -p winphp-staging
if [ ! -f winphp-staging/php/php.exe ]; then
curl -fL --retry 2 -o /tmp/php-win.zip "$PHP_WIN_URL"
rm -rf winphp-staging/php && unzip -q /tmp/php-win.zip -d winphp-staging/php && rm /tmp/php-win.zip
fi

# ── Windows PostgreSQL (zonky) ───────────────────────────────────────────────
echo "[3/3] Windows PostgreSQL…"
if [ ! -f winphp-staging/pgsql/bin/postgres.exe ]; then
curl -fL --retry 2 -o /tmp/pg-win.jar "$PG_WIN_URL"
unzip -o -q /tmp/pg-win.jar postgres-windows-x86_64.txz -d /tmp
mkdir -p winphp-staging/pgsql
tar xf /tmp/postgres-windows-x86_64.txz -C winphp-staging/pgsql
rm /tmp/pg-win.jar /tmp/postgres-windows-x86_64.txz
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file map =="
git ls-files desktop/scripts/fetch-runtimes.sh desktop | sed -n '1,120p'

echo
echo "== outline =="
ast-grep outline desktop/scripts/fetch-runtimes.sh --view expanded || true

echo
echo "== script contents with line numbers =="
cat -n desktop/scripts/fetch-runtimes.sh | sed -n '1,220p'

echo
echo "== search for runtime download / hash verification usages =="
rg -n "sha256|checksum|verify|windows.php.net|static-php.dev|embedded-postgres-binaries-windows-amd64|fetch-runtimes" desktop . -g '!**/node_modules/**' -g '!**/dist/**' -g '!**/build/**'

Repository: Mes-Open/OpenMes

Length of output: 50373


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== inspect nearby files that consume the staged runtimes =="
rg -n "src-tauri/resources/php|winphp-staging|postgres.exe|php.exe|desktop/scripts/fetch-runtimes.sh" desktop -g '!**/node_modules/**' -g '!**/dist/**' -g '!**/build/**'

echo
echo "== list likely packaging files =="
git ls-files desktop | rg 'tauri|cargo|build|package|script|workflow|yml|yaml' || true

Repository: Mes-Open/OpenMes

Length of output: 4413


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== fetch-runtimes.sh =="
cat -n desktop/scripts/fetch-runtimes.sh | sed -n '1,220p'

echo
echo "== search for staging/resource paths in desktop tree =="
rg -n "src-tauri/resources/php|winphp-staging|fetch-runtimes.sh|php-8.3.31|embedded-postgres-binaries-windows-amd64" desktop -g '!**/node_modules/**' -g '!**/dist/**' -g '!**/build/**'

echo
echo "== files mentioning packaging/build =="
git ls-files desktop | rg '^(desktop/)?(package|Cargo|tauri|src-tauri|scripts|\.github/)' || true

Repository: Mes-Open/OpenMes

Length of output: 6839


Pin the Windows PHP download and add checksum checks for the runtime archives.
These runtime archives are pulled into the desktop installer bundle, but none are integrity-checked before extraction. PHP_WIN_URL also points at latest, so the build can drift over time. Pin that URL to a fixed release and verify a known SHA-256 for each download before unpacking.

🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 18-18: Writing to or reading from a hardcoded, predictable path under /tmp is vulnerable to symlink and TOCTOU attacks: a local attacker can pre-create the file (or a symlink pointing elsewhere) and hijack or corrupt the contents. Generate a unique, unpredictable temporary file with mktemp instead, e.g. tmpfile="$(mktemp)" (or mktemp -d for directories) and reference "$tmpfile".
Context: /tmp/php-linux.tar.gz
Note: [CWE-377] Insecure Temporary File.

(predictable-tmp-file-bash)


[warning] 19-19: Writing to or reading from a hardcoded, predictable path under /tmp is vulnerable to symlink and TOCTOU attacks: a local attacker can pre-create the file (or a symlink pointing elsewhere) and hijack or corrupt the contents. Generate a unique, unpredictable temporary file with mktemp instead, e.g. tmpfile="$(mktemp)" (or mktemp -d for directories) and reference "$tmpfile".
Context: /tmp/php-linux.tar.gz
Note: [CWE-377] Insecure Temporary File.

(predictable-tmp-file-bash)


[warning] 19-19: Writing to or reading from a hardcoded, predictable path under /tmp is vulnerable to symlink and TOCTOU attacks: a local attacker can pre-create the file (or a symlink pointing elsewhere) and hijack or corrupt the contents. Generate a unique, unpredictable temporary file with mktemp instead, e.g. tmpfile="$(mktemp)" (or mktemp -d for directories) and reference "$tmpfile".
Context: /tmp/php-linux.tar.gz
Note: [CWE-377] Insecure Temporary File.

(predictable-tmp-file-bash)


[warning] 27-27: Writing to or reading from a hardcoded, predictable path under /tmp is vulnerable to symlink and TOCTOU attacks: a local attacker can pre-create the file (or a symlink pointing elsewhere) and hijack or corrupt the contents. Generate a unique, unpredictable temporary file with mktemp instead, e.g. tmpfile="$(mktemp)" (or mktemp -d for directories) and reference "$tmpfile".
Context: /tmp/php-win.zip
Note: [CWE-377] Insecure Temporary File.

(predictable-tmp-file-bash)


[warning] 28-28: Writing to or reading from a hardcoded, predictable path under /tmp is vulnerable to symlink and TOCTOU attacks: a local attacker can pre-create the file (or a symlink pointing elsewhere) and hijack or corrupt the contents. Generate a unique, unpredictable temporary file with mktemp instead, e.g. tmpfile="$(mktemp)" (or mktemp -d for directories) and reference "$tmpfile".
Context: /tmp/php-win.zip
Note: [CWE-377] Insecure Temporary File.

(predictable-tmp-file-bash)


[warning] 28-28: Writing to or reading from a hardcoded, predictable path under /tmp is vulnerable to symlink and TOCTOU attacks: a local attacker can pre-create the file (or a symlink pointing elsewhere) and hijack or corrupt the contents. Generate a unique, unpredictable temporary file with mktemp instead, e.g. tmpfile="$(mktemp)" (or mktemp -d for directories) and reference "$tmpfile".
Context: /tmp/php-win.zip
Note: [CWE-377] Insecure Temporary File.

(predictable-tmp-file-bash)


[warning] 34-34: Writing to or reading from a hardcoded, predictable path under /tmp is vulnerable to symlink and TOCTOU attacks: a local attacker can pre-create the file (or a symlink pointing elsewhere) and hijack or corrupt the contents. Generate a unique, unpredictable temporary file with mktemp instead, e.g. tmpfile="$(mktemp)" (or mktemp -d for directories) and reference "$tmpfile".
Context: /tmp/pg-win.jar
Note: [CWE-377] Insecure Temporary File.

(predictable-tmp-file-bash)


[warning] 35-35: Writing to or reading from a hardcoded, predictable path under /tmp is vulnerable to symlink and TOCTOU attacks: a local attacker can pre-create the file (or a symlink pointing elsewhere) and hijack or corrupt the contents. Generate a unique, unpredictable temporary file with mktemp instead, e.g. tmpfile="$(mktemp)" (or mktemp -d for directories) and reference "$tmpfile".
Context: /tmp/pg-win.jar
Note: [CWE-377] Insecure Temporary File.

(predictable-tmp-file-bash)


[warning] 37-37: Writing to or reading from a hardcoded, predictable path under /tmp is vulnerable to symlink and TOCTOU attacks: a local attacker can pre-create the file (or a symlink pointing elsewhere) and hijack or corrupt the contents. Generate a unique, unpredictable temporary file with mktemp instead, e.g. tmpfile="$(mktemp)" (or mktemp -d for directories) and reference "$tmpfile".
Context: /tmp/postgres-windows-x86_64.txz
Note: [CWE-377] Insecure Temporary File.

(predictable-tmp-file-bash)


[warning] 38-38: Writing to or reading from a hardcoded, predictable path under /tmp is vulnerable to symlink and TOCTOU attacks: a local attacker can pre-create the file (or a symlink pointing elsewhere) and hijack or corrupt the contents. Generate a unique, unpredictable temporary file with mktemp instead, e.g. tmpfile="$(mktemp)" (or mktemp -d for directories) and reference "$tmpfile".
Context: /tmp/pg-win.jar
Note: [CWE-377] Insecure Temporary File.

(predictable-tmp-file-bash)


[warning] 38-38: Writing to or reading from a hardcoded, predictable path under /tmp is vulnerable to symlink and TOCTOU attacks: a local attacker can pre-create the file (or a symlink pointing elsewhere) and hijack or corrupt the contents. Generate a unique, unpredictable temporary file with mktemp instead, e.g. tmpfile="$(mktemp)" (or mktemp -d for directories) and reference "$tmpfile".
Context: /tmp/postgres-windows-x86_64.txz
Note: [CWE-377] Insecure Temporary File.

(predictable-tmp-file-bash)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@desktop/scripts/fetch-runtimes.sh` around lines 11 - 40, Update the runtime
download flow in fetch-runtimes.sh to pin PHP_WIN_URL to a fixed Windows PHP
release instead of latest, define the expected SHA-256 values for the
PHP_LINUX_URL, PHP_WIN_URL, and PG_WIN_URL archives, and verify each downloaded
file’s checksum before tar or unzip extraction; abort on any mismatch and remove
the invalid temporary archive.

Comment on lines +12 to +27
"app": {
"withGlobalTauri": true,
"windows": [
{
"label": "main",
"title": "OpenMES Desktop — panel sterowania",
"width": 940,
"height": 700,
"minWidth": 760,
"minHeight": 560
}
],
"security": {
"csp": null
}
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Secure the Tauri configuration by removing the global API and enabling CSP.

Disabling the Content Security Policy ("csp": null) and exposing the Tauri API globally ("withGlobalTauri": true) unnecessarily increases the attack surface in the event of an XSS vulnerability.

Since the frontend imports Tauri APIs directly as modules (e.g., import { invoke } from "@tauri-apps/api/core";), the global window.__TAURI__ is not needed. Consider removing it and enabling a restrictive CSP to mitigate potential security risks.

🛡️ Proposed security hardening
   "app": {
-    "withGlobalTauri": true,
     "windows": [
       {
         "label": "main",
         "title": "OpenMES Desktop — panel sterowania",
         "width": 940,
         "height": 700,
         "minWidth": 760,
         "minHeight": 560
       }
     ],
     "security": {
-      "csp": null
+      "csp": "default-src 'self'; style-src 'self' 'unsafe-inline';"
     }
   },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"app": {
"withGlobalTauri": true,
"windows": [
{
"label": "main",
"title": "OpenMES Desktop — panel sterowania",
"width": 940,
"height": 700,
"minWidth": 760,
"minHeight": 560
}
],
"security": {
"csp": null
}
},
"app": {
"windows": [
{
"label": "main",
"title": "OpenMES Desktop — panel sterowania",
"width": 940,
"height": 700,
"minWidth": 760,
"minHeight": 560
}
],
"security": {
"csp": "default-src 'self'; style-src 'self' 'unsafe-inline';"
}
},
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@desktop/src-tauri/tauri.conf.json` around lines 12 - 27, Update the Tauri app
configuration by removing the withGlobalTauri setting and replacing the null
security.csp value with a restrictive Content Security Policy that permits only
the frontend’s required resources and Tauri module usage. Keep the existing
window configuration unchanged.

Comment thread desktop/src/main.ts
Comment on lines +5 to +7
// Okno "settings" pokazuje panel od razu; okno "main" pokazuje splash i po
// starcie serwera przełącza się na UI OpenMES - panel widzi tylko przy błędzie.
const isSettingsWindow = getCurrentWindow().label === "settings";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Comments and UI strings throughout the file are in Polish.

Repo guideline requires English for code, text, and comments in .ts files, but this file's comments (e.g. Line 6: "Okno "main" pokazuje splash i po starcie serwera przełącza się na UI OpenMES") and all user-facing strings (showMessage, humanizeError, bootError, etc.) are Polish. This is pervasive across the file rather than isolated instances.

Given the target audience is clearly Polish-speaking operators, consider introducing an i18n layer (English keys + a Polish locale file) so the source code/comments are in English per guidelines while still shipping localized UI text.

As per coding guidelines, "All code, Blade/JSX text, validation messages, seeders, and comments must be in English" for **/*.{php,js,jsx,ts,tsx,blade.php}.

Also applies to: 271-296, 298-308, 361-363, 420-421, 459-460

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@desktop/src/main.ts` around lines 5 - 7, Update desktop/src/main.ts so all
comments and source-level text are written in English, including the areas
around isSettingsWindow, showMessage, humanizeError, and bootError. Introduce or
reuse an i18n layer with English keys and a Polish locale so user-facing
messages remain Polish at runtime. Replace inline Polish strings throughout the
file with localized lookups while preserving the existing UI behavior and error
messaging.

Source: Coding guidelines

Comment thread desktop/src/main.ts
Comment on lines +9 to +23
interface ServerConfig {
php_binary: string;
backend_dir: string;
host: string;
port: number;
autostart: boolean;
extra_env: string;
db_mode: string;
db_driver: string;
db_host: string;
db_port: number;
db_database: string;
db_username: string;
db_password: string;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Locate the Rust persistence logic for ServerConfig/save_config to check for encryption.
rg -n -A5 -B5 'fn save_config' desktop/src-tauri/src/lib.rs
rg -n -A5 'db_password' desktop/src-tauri/src/lib.rs

Repository: Mes-Open/OpenMes

Length of output: 1434


Store db_password outside the JSON config file. save_config serializes the full ServerConfig to disk, so db_password is written in plaintext. Move just this field to the OS keychain/credential store instead of flat-file storage.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@desktop/src/main.ts` around lines 9 - 23, Update ServerConfig and the
save_config serialization flow so db_password is excluded from the JSON file and
stored/retrieved through the operating system keychain or credential store. Keep
the remaining ServerConfig fields persisted as before, and ensure runtime
configuration still receives the password from the secure store.

Comment thread desktop/src/main.ts
Comment on lines +363 to +418
function ensureDbMode(cfg: ServerConfig): Promise<void> {
if (cfg.db_mode) return Promise.resolve();
show("splash", false);
show("db-choice", true);

return new Promise((resolve) => {
$("btn-db-local").addEventListener("click", async () => {
cfg.db_mode = "local";
await invoke("save_config", { config: cfg });
show("db-choice", false);
show("splash", true);
resolve();
});

$("btn-db-remote").addEventListener("click", () => {
show("db-choice", false);
show("db-remote", true);
scanAndFillDbForm();
});

$("btn-db-back").addEventListener("click", () => {
show("db-remote", false);
show("db-choice", true);
});

$<HTMLFormElement>("db-remote-form").addEventListener("submit", async (e) => {
e.preventDefault();
const msg = $("db-message");
const port = Number($<HTMLInputElement>("db-port").value) || 5432;
const candidate: ServerConfig = {
...cfg,
db_mode: "remote",
db_driver: port === 3306 ? "mysql" : "pgsql",
db_host: $<HTMLInputElement>("db-host").value.trim(),
db_port: port,
db_database: $<HTMLInputElement>("db-database").value.trim(),
db_username: $<HTMLInputElement>("db-username").value.trim(),
db_password: $<HTMLInputElement>("db-password").value,
};
msg.classList.remove("error");
msg.textContent = "Sprawdzam połączenie…";
try {
await invoke("test_db_connection", { config: candidate });
} catch (err) {
msg.classList.add("error");
msg.textContent = `Nie udało się połączyć: ${err}`;
return;
}
Object.assign(cfg, candidate);
await invoke("save_config", { config: cfg });
show("db-remote", false);
show("splash", true);
resolve();
});
});
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Listener accumulation in ensureDbMode across boot retries.

ensureDbMode is invoked every time bootFlow() runs (Line 436), which happens again on every retryBoot() (Lines 460-471, triggered from be-db/be-local/be-retry). Each call adds fresh addEventListener handlers to the same static elements (btn-db-local, btn-db-remote, btn-db-back, db-remote-form) without ever removing the previous ones. After N retries, a single click fires N handlers, each closing over a different (potentially stale) cfg/resolve. Consequences: duplicate save_config/test_db_connection IPC calls, and a stale handler can overwrite freshly-saved settings (host/port/php_binary/backend_dir) with an older snapshot via Object.assign(cfg, candidate) + save_config.

🛠️ Suggested fix — wire listeners once, rebind state per call
+let dbModeResolve: (() => void) | null = null;
+let dbModeCfg: ServerConfig | null = null;
+let dbModeListenersWired = false;
+
+function wireDbModeListeners() {
+  if (dbModeListenersWired) return;
+  dbModeListenersWired = true;
+
+  $("btn-db-local").addEventListener("click", async () => {
+    if (!dbModeCfg) return;
+    dbModeCfg.db_mode = "local";
+    await invoke("save_config", { config: dbModeCfg });
+    show("db-choice", false);
+    show("splash", true);
+    dbModeResolve?.();
+  });
+
+  $("btn-db-remote").addEventListener("click", () => {
+    show("db-choice", false);
+    show("db-remote", true);
+    scanAndFillDbForm();
+  });
+
+  $("btn-db-back").addEventListener("click", () => {
+    show("db-remote", false);
+    show("db-choice", true);
+  });
+
+  $<HTMLFormElement>("db-remote-form").addEventListener("submit", async (e) => {
+    e.preventDefault();
+    if (!dbModeCfg) return;
+    // ... existing body, using dbModeCfg instead of cfg, dbModeResolve?.() instead of resolve()
+  });
+}
+
 function ensureDbMode(cfg: ServerConfig): Promise<void> {
   if (cfg.db_mode) return Promise.resolve();
+  wireDbModeListeners();
+  dbModeCfg = cfg;
   show("splash", false);
   show("db-choice", true);
-  return new Promise((resolve) => { ... });
+  return new Promise((resolve) => { dbModeResolve = resolve; });
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@desktop/src/main.ts` around lines 363 - 418, Update ensureDbMode so its
static controls receive event listeners only once, while each invocation rebinds
the current cfg and Promise resolve state used by those handlers. Preserve the
existing local, remote, back, and submit behavior, but prevent stale closures
from issuing duplicate IPC calls or overwriting newer configuration after
bootFlow retries.

Comment thread desktop/src/main.ts
Comment on lines +388 to +401
$<HTMLFormElement>("db-remote-form").addEventListener("submit", async (e) => {
e.preventDefault();
const msg = $("db-message");
const port = Number($<HTMLInputElement>("db-port").value) || 5432;
const candidate: ServerConfig = {
...cfg,
db_mode: "remote",
db_driver: port === 3306 ? "mysql" : "pgsql",
db_host: $<HTMLInputElement>("db-host").value.trim(),
db_port: port,
db_database: $<HTMLInputElement>("db-database").value.trim(),
db_username: $<HTMLInputElement>("db-username").value.trim(),
db_password: $<HTMLInputElement>("db-password").value,
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Database driver is guessed from port number instead of being explicitly selected. Root cause: the remote-DB form has no driver control, so main.ts falls back to a port === 3306 heuristic that misclassifies MySQL/Postgres on non-default ports.

  • desktop/src/main.ts#L388-L401: replace the port === 3306 ? "mysql" : "pgsql" heuristic with a value read from an explicit driver control.
  • desktop/index.html#L36-L71: add a driver <select> (Postgres/MySQL) to db-remote-form so db-driver is set by user choice rather than inferred from port.
📍 Affects 2 files
  • desktop/src/main.ts#L388-L401 (this comment)
  • desktop/index.html#L36-L71
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@desktop/src/main.ts` around lines 388 - 401, Replace the port-based driver
inference in the db-remote-form submit handler in desktop/src/main.ts (lines
388-401) with the selected value from an explicit db-driver control. Add a
Postgres/MySQL driver select to the db-remote-form in desktop/index.html (lines
36-71), ensuring its value populates the ServerConfig db_driver field.

- installer: stop leaking raw PDO exception messages to pre-auth visitors
  (log instead); reject path-traversal in sqlite db_database before it hits
  storage_path(); move admin-step validation into a CreateAdminRequest Form
  Request (per project convention)
- tauri: harden config — withGlobalTauri=false + a CSP (permissive only for
  localhost so the served MES app's websockets/SSE keep working)
- panel: AbortController in ensureDbMode so boot retries don't stack duplicate
  DB-choice listeners
- updater: snapshot the SQLite DB before migrating and restore it on rollback,
  so restored old code never runs against the migrated schema
@jakub-przepiora

Copy link
Copy Markdown
Contributor Author

Thanks for the review — addressed in 3fd73aa6:

Fixed

  • InstallController — exception leak (pre-auth): the preconfigured-DB failure now logs the raw error and shows a generic message.
  • InstallController — path traversal in db_database: relative sqlite names containing .. are rejected before storage_path(), in both setupDatabase() and the preset-gated block.
  • InstallController — inline validation: admin-step rules moved to a new App\Http\Requests\Web\Install\CreateAdminRequest (reads the preset flag from the install session).
  • tauri.conf — hardening: withGlobalTauri: false + a CSP. The CSP is intentionally permissive for localhost/127.0.0.1 (http + ws) because the main window navigates to the locally-served MES app, whose live-sync uses websockets/SSE — a stricter default-src 'self' would break it.
  • main.ts — listener accumulation: ensureDbMode now uses an AbortController, so boot retries no longer stack duplicate DB-choice handlers.
  • updater rollback vs migrated schema: the SQLite DB is snapshotted before migrate and restored on any rollback path, so restored old code never runs against the new schema. (Remote/bundled-Postgres installs keep the forward-only behaviour — can't cheaply snapshot those.)

Intentional / not changed

  • index.html openmmes — not a typo; openmmes (double-m) is the project-wide default DB name.
  • lib.rs Postgres trust-auth with empty password — by design: the bundled Postgres binds only to 127.0.0.1, never the LAN.
  • Remote DB password as a PHP CLI arg / db_password in the local config file — local, single-user desktop context; noted as a possible follow-up hardening.
  • DB driver inferred from port (3306→mysql, else pgsql) — a convenience default; the user still confirms the connection.

Full backend suite stays green (1896 tests). @coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

git show --stat --oneline --decorate --no-renames 3fd73aa6
git diff 3fd73aa6^ 3fd73aa6 -- \
  backend/app/Http/Controllers/InstallController.php \
  backend/app/Http/Requests/Web/Install/CreateAdminRequest.php \
  backend/tests/Feature/InstallPresetTest.php \
  desktop/src-tauri/tauri.conf.json \
  desktop/src/main.ts \
  desktop/src-tauri/src/lib.rs

printf '\n--- PHP outlines ---\n'
ast-grep outline backend/app/Http/Controllers/InstallController.php --items all --type method,function --view expanded || true
if [ -f backend/app/Http/Requests/Web/Install/CreateAdminRequest.php ]; then
  ast-grep outline backend/app/Http/Requests/Web/Install/CreateAdminRequest.php --items all --view expanded || true
fi

printf '\n--- Rust updater-related locations ---\n'
rg -n -C 3 'snapshot|rollback|migrat|swap|backup' desktop/src-tauri/src/lib.rs

printf '\n--- TypeScript database choice listener locations ---\n'
rg -n -C 4 'ensureDbMode|AbortController|addEventListener|abort' desktop/src/main.ts

Length of output: 30246


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- Rust: database/runtime environment and update flow ---'
sed -n '430,570p' desktop/src-tauri/src/lib.rs
sed -n '1180,1250p' desktop/src-tauri/src/lib.rs
sed -n '1340,1510p' desktop/src-tauri/src/lib.rs

printf '%s\n' '--- TypeScript: DB scanning and boot/retry lifecycle ---'
sed -n '300,530p' desktop/src/main.ts

printf '%s\n' '--- References to SQLite DB filenames and DB_DATABASE ---'
rg -n -C 3 'openmes\.sqlite|DB_DATABASE|db_database|LARAVEL_STORAGE_PATH' \
  desktop/src-tauri/src/lib.rs \
  backend/app/Http/Controllers/InstallController.php \
  backend/config/database.php \
  backend/.env.example 2>/dev/null || true

printf '%s\n' '--- Tests related to updates and installer input validation ---'
rg -n -C 5 'run_update_apply|pre-update|snapshot|rollback|Invalid database path|CreateAdminRequest' \
  desktop/src-tauri/src/lib.rs \
  backend/tests 2>/dev/null || true

printf '%s\n' '--- Routes invoking admin creation ---'
rg -n -C 3 'createAdmin|install\.admin|install/admin' backend/routes backend/app 2>/dev/null || true

Length of output: 149086


@jakub-przepiora Thanks — the installer, CSP, and listener fixes look good. I found one remaining issue in the SQLite rollback handling:

  • desktop/src-tauri/src/lib.rs — SQLite snapshot failures are silently ignored. At Line 1437, fs::copy(&db_file, &bak).ok() converts a failed snapshot into None, but the update continues and migrations run. Likewise, Line 1443 ignores a restore failure. On a full/read-only application-data volume, a failed snapshot or restore means a later rollback can still start old backend code against the migrated schema—the state this change is meant to prevent.

    Please make snapshot creation mandatory before migration, and propagate restoration failures rather than restarting the old server. A focused test for snapshot/restore failure would protect this rollback guarantee.

@jakub-przepiora
jakub-przepiora merged commit 2ff45e8 into develop Jul 19, 2026
2 checks passed
jakub-przepiora added a commit that referenced this pull request Jul 19, 2026
* feat: required operators

* fix: styles

* test: fix e2e disposition flow and create English scenario variant

* [feat] Per-order machines to run supervisor view #89

* Update tests and localization

* fix to test can capture all step from multi users by logout and login

* fix test error

* clean: remove temporary development files and compiled output

* translate: translate Operator Workstation and Work Order details to Vietnamese

* fix: address code review comments for i18n localization and backend logic

* fix: address remaining PR comments on docker-compose mount, reset script fail-fast, and e2e spec safety

* fix: adjust lot pick layout font sizes and make E2E dropdown lot selection robust

* fix: localize Start and Complete step buttons under Operator panel

* style: polish LotPickModal row spacing and fonts to avoid vertical overlapping

* fix: wait for start API response before expecting step Complete button in vi spec

* fix: refine confirmBtn locator in E2E spec to avoid partial text matching

* fix: unconditionally scan and fill selects in E2E lot picking modal

* fix: refine createBtn button locator regex pattern

* fix: use explicit workstation button label in E2E spec to avoid strict createBtn helper mismatches

* fix: streamline startBtn visibility and click handler logic in E2E spec

* fix: add robust re-click startBtn fallback to start batch step in E2E spec

* translate: localize work order operator modals and actions to support vietnamese

* fix(materials): make material_type_id optional in StoreMaterialRequest

* chore: remove reset-test cmd files

* fix(i18n): address PR reviews for operator controllers translation gaps

* chore: remove car-production-buildout spec files

* fix(i18n): align media type badge localization mapping key with select options

* feat: mobile

* fix: restore removed migration 2026_06_18_120000 (make material_type_id nullable)
Also remove composer.phar and empty .rr.yaml from git tracking.

* Address order machines review feedback

* fix: logs

* feat: accepnt informations

* feat: finish up whole feature

* fix: address CodeRabbit review on customer/priority PR

- PriorityRuleRequest: reject non-"equals" conditions for the customer.tier
  source (a numeric comparison casts the tier string to 0 and matches nonsense).
- CustomerMetricsService: lock and re-check the work order inside the
  transaction so concurrent completions can't double-count revenue/orders.
- Priority Settings band editor: block save when any threshold is blank
  instead of silently coercing an empty field to 0.

Adds tests and en/pl strings. Full suite green (1690).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(workstation): shop-floor device registration + live admin roster (#175)

* feat(workstation): shop-floor device registration + live admin roster

Workstation client PCs self-register against the MAIN app by IP and
heartbeat; Admin → Structure → Workstation Devices shows them live
(online/offline derived from last_seen_at) via an Electric shape.

- Public, rate-limited API: POST /api/workstations/register + /heartbeat
- WorkstationDevice model (soft-delete + audit), migration with a partial
  unique index on device_uuid, factory
- Web admin roster page (ResourceTable) + forget (soft-delete)
- Registered in SoftDeleteRegistry + ShapeRegistry; i18n en/pl parity
- Tests: registration/heartbeat/validation, admin authz + soft-delete

* docs(roadmap): add workflow/UX orchestration items from field feedback

Operator/supervisor/scheduler workflow items surfaced by a high-volume
plant's feedback (unified production+scrap reporting, role-based entry
points, machine-counter reporting, shift board, start-of-shift + multi-day
Gantt scheduling, made-to-order vs stock order entry). Tagged 'probable'.

* ci(release): build desktop installers (win/mac/linux) + Android apk (#187)

Extends the release workflow so a tag/dispatch produces installers for
every platform, all attached to the same GitHub Release:

- web: existing self-contained ZIP (unchanged) — creates the release
- desktop: Tauri matrix (windows .exe/NSIS, macOS .dmg, linux .deb/.rpm),
  each bundling PHP + the backend; PHP runtime fetched per-OS, backend
  built (composer + vite) and injected via bundle-resources + --config
- mobile: Expo prebuild + Gradle assembleRelease -> Android .apk

Jobs fan out from the web job (needs: web) and upload to the release by
tag, so a desktop/mobile failure never blocks the web artifact.

* feat(desktop): OpenMES Desktop app (Tauri) + unattended install preset (#186)

* feat(desktop): OpenMES Desktop app (Tauri) + unattended install preset

Brings the desktop platform app to develop alongside mobile/. A Tauri
(Rust + webview) shell that bundles PHP + the backend and runs OpenMES as
a local desktop app / LAN MES server, with an in-app self-updater.

- desktop/: Tauri app (src-tauri Rust, TS control panel, icons, build
  scripts for self-contained Windows .exe and Linux .deb/.rpm)
- Backend INSTALLER_PRESET mode: unattended install (skips env/db wizard
  steps, straight to admin creation) that the desktop shell drives —
  InstallController + install views + bootstrap/app.php
- InstallPresetTest covers the preset behaviour

The shop-floor workstation feature lands separately in #175.

* fix(desktop): address CodeRabbit review on #186

- installer: stop leaking raw PDO exception messages to pre-auth visitors
  (log instead); reject path-traversal in sqlite db_database before it hits
  storage_path(); move admin-step validation into a CreateAdminRequest Form
  Request (per project convention)
- tauri: harden config — withGlobalTauri=false + a CSP (permissive only for
  localhost so the served MES app's websockets/SSE keep working)
- panel: AbortController in ensureDbMode so boot retries don't stack duplicate
  DB-choice listeners
- updater: snapshot the SQLite DB before migrating and restore it on rollback,
  so restored old code never runs against the migrated schema

* feat(work-orders): add elapsed-time "Age" column to work-order lists (#185)

* feat(work-orders): add elapsed-time "Age" column to work-order lists

Add an "Age" column (now - created_at) to the admin and supervisor work-order
lists, showing a compact human-readable duration (just now / 5m / 3h / 2d / 1y)
with the exact creation time on hover.

- New reusable elapsed() formatter in lib/i18n.js
- Opt-in `live: true` column flag on ResourceTable: a shared 30s clock ticks
  live cells via context, so tables without one schedule no timer
- Opt-in `sortAccessor` column flag so "Age" sorts by actual age (ascending =
  youngest first) while global search keeps using created_at
- Vitest setup covering the duration formatting; wired into CI + pre-commit

created_at was already synced on the work-order Electric shapes, so no schema
or shape change.

Closes #100

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* perf(work-orders): isolate live-clock ticks to a provider component

Move the 30s clock state out of ResourceTable into a small LiveClockProvider
wrapper, so a tick re-renders only the provider and the NowContext consumers
(the live cells) rather than the whole ResourceTable body. Previously the tick
re-ran the live query/filter and handed DataTable freshly allocated props
(visibleRows, the inline rangeLabel), defeating its memoization and reconciling
the entire grid every 30s.

Addresses CodeRabbit review on #185.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(routes): drop dangling __e2e__ block; 404 (not 500) on non-numeric report ids (#188)

- Remove the WIP `__e2e__` test-control route group and its import: it
  referenced App\Http\Controllers\TestControl\TenantController, the
  EnsureE2eEnabled middleware and config/e2e.php — none of which exist in the
  repo (leaked from 'feat: custom fields wip'). It broke `php artisan
  route:list` and would 500 on /__e2e__/* instead of the intended 404. No spec
  references it; reintroduce complete (routes + controller + middleware + config)
  if the isolated-tenant harness is wanted.
- Constrain reports.show / cost-reports.show to `whereNumber('workOrder')` so a
  non-numeric id (e.g. /admin/reports/net-requirements) 404s instead of hitting
  the controller with a string id and 500ing on an invalid-integer query.

* feat(bom): select multi bom in orders (#183)

* feat(bom): select multi bom in orders

* fix(refactor): code fixes reported in code review by CodeRabbit

---------

Co-authored-by: jakub-przepiora <jakub.przepioraa@gmail.com>

* fix(e2e): close unterminated test.describe in material-hold-release spec (#189)

The spec was missing the final `});` closing its `test.describe(...)` block,
so Playwright failed to parse it — and because collection aborts on the first
unparseable file, this broke the ENTIRE E2E suite (0 specs could run). E2E
isn't part of the CI test gate, so it slipped onto develop unnoticed.
Adds the missing close; the file now parses and lists its test.

* chore(release): v0.17.0

---------

Co-authored-by: Mateusz Łuczyński <mateusz.luczynski01@gmail.com>
Co-authored-by: AlexAnh <soibien@gmail.com>
Co-authored-by: ElNinio978 <Kacperwos2005@gmail.com>
Co-authored-by: JanKolo04 <jankolodziej04@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Mateusz Łuczyński <100802495+Svannte@users.noreply.github.com>
Co-authored-by: Jan Kołodziej <76879087+JanKolo04@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant