From 067cb1fccbccd1f1458874d8c337bcb8e8d72177 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 13 Oct 2025 07:04:26 +1100 Subject: [PATCH 01/10] Catch error during auto-migrations (#10553) - Prevent process interlock - Prevent migration check if already running migrations --- src/backend/InvenTree/InvenTree/tasks.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/backend/InvenTree/InvenTree/tasks.py b/src/backend/InvenTree/InvenTree/tasks.py index 4e07f75bad..123ee4f966 100644 --- a/src/backend/InvenTree/InvenTree/tasks.py +++ b/src/backend/InvenTree/InvenTree/tasks.py @@ -670,6 +670,11 @@ def check_for_migrations(force: bool = False, reload_registry: bool = True) -> b Returns bool indicating if migrations are up to date """ + from . import ready + + if ready.isRunningMigrations() or ready.isRunningBackup(): + # Migrations are already running! + return False def set_pending_migrations(n: int): """Helper function to inform the user about pending migrations.""" @@ -720,6 +725,8 @@ def check_for_migrations(force: bool = False, reload_registry: bool = True) -> b if settings.DATABASES['default']['ENGINE'] != 'django.db.backends.sqlite3': raise e logger.exception('Error during migrations: %s', e) + except Exception as e: # pragma: no cover + logger.exception('Error during migrations: %s', e) else: set_pending_migrations(0) From 49f8f72a46c8ffe5e80168e35ab501da4c13502f Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 12 Oct 2025 22:06:14 +0200 Subject: [PATCH 02/10] chore(backend)!: fix spelling (#10557) * fix spelling * add changelog entry --- CHANGELOG.md | 1 + src/backend/InvenTree/InvenTree/version.py | 2 +- src/backend/InvenTree/common/currency.py | 2 +- src/backend/InvenTree/common/settings.py | 6 +++--- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8b070d33b..da05a36c85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Changed site URL check to allow protocol mismatches if `INVENTREE_SITE_LAX_PROTOCOL` is set to `True` (default) in [#10454](https://github.com/inventree/InvenTree/pull/10454) +- Changed call signature of `get_global_setting` to use `environment_key` instead of `enviroment_key` in [#10557](https://github.com/inventree/InvenTree/pull/10557) ### Removed diff --git a/src/backend/InvenTree/InvenTree/version.py b/src/backend/InvenTree/InvenTree/version.py index 08b8bea05d..ca699f0190 100644 --- a/src/backend/InvenTree/InvenTree/version.py +++ b/src/backend/InvenTree/InvenTree/version.py @@ -298,7 +298,7 @@ def inventree_identifier(override_announce: bool = False): from common.settings import get_global_setting if override_announce or get_global_setting( - 'INVENTREE_ANNOUNCE_ID', enviroment_key='INVENTREE_ANNOUNCE_ID' + 'INVENTREE_ANNOUNCE_ID', environment_key='INVENTREE_ANNOUNCE_ID' ): return get_global_setting('INVENTREE_INSTANCE_ID', default='') return None diff --git a/src/backend/InvenTree/common/currency.py b/src/backend/InvenTree/common/currency.py index f2fe3d76bd..307e54d6d5 100644 --- a/src/backend/InvenTree/common/currency.py +++ b/src/backend/InvenTree/common/currency.py @@ -48,7 +48,7 @@ def currency_codes() -> list: from common.settings import get_global_setting codes = get_global_setting( - 'CURRENCY_CODES', create=False, enviroment_key='INVENTREE_CURRENCY_CODES' + 'CURRENCY_CODES', create=False, environment_key='INVENTREE_CURRENCY_CODES' ).strip() if not codes: diff --git a/src/backend/InvenTree/common/settings.py b/src/backend/InvenTree/common/settings.py index 87a1e5dff0..c721d8c346 100644 --- a/src/backend/InvenTree/common/settings.py +++ b/src/backend/InvenTree/common/settings.py @@ -26,12 +26,12 @@ def global_setting_overrides() -> dict: return {} -def get_global_setting(key, backup_value=None, enviroment_key=None, **kwargs): +def get_global_setting(key, backup_value=None, environment_key=None, **kwargs): """Return the value of a global setting using the provided key.""" from common.models import InvenTreeSetting - if enviroment_key: - value = environ.get(enviroment_key) + if environment_key: + value = environ.get(environment_key) if value: return value From 6327707c0e01896cc619c2af602d6d915ff73efb Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Mon, 13 Oct 2025 00:01:53 +0200 Subject: [PATCH 03/10] fix(installer): make VERSION information accessible in invoke calls (#10558) * implement version loading in more contexts closes #10554 * factor version file out * ensure results do not contain new strings --- src/backend/InvenTree/InvenTree/config.py | 33 ++++++++++++++++++++- src/backend/InvenTree/InvenTree/settings.py | 7 +---- src/backend/InvenTree/InvenTree/version.py | 2 +- 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/config.py b/src/backend/InvenTree/InvenTree/config.py index f51cf924d3..600e9e7864 100644 --- a/src/backend/InvenTree/InvenTree/config.py +++ b/src/backend/InvenTree/InvenTree/config.py @@ -76,8 +76,9 @@ def get_root_dir() -> Path: def inventreeInstaller() -> Optional[str]: """Returns the installer for the running codebase - if set or detectable.""" - # First look in the environment variables, e.g. if running in docker + load_version_file() + # First look in the environment variables, e.g. if running in docker installer = os.environ.get('INVENTREE_PKG_INSTALLER', '') if installer: @@ -121,6 +122,11 @@ def get_testfolder_dir() -> Path: return get_base_dir().joinpath('_testfolder').resolve() +def get_version_file() -> Path: + """Returns the path of the InvenTree VERSION file. This does not ensure that the file exists.""" + return get_root_dir().joinpath('VERSION').resolve() + + def ensure_dir(path: Path, storage=None) -> None: """Ensure that a directory exists. @@ -592,3 +598,28 @@ def check_config_dir( pass return + + +VERSION_LOADED = False +"""Flag to indicate if the VERSION file has been loaded in this process.""" + + +def load_version_file(): + """Load the VERSION file if it exists and place the contents into the general execution environment. + + Returns: + True if the VERSION file was loaded (now or previously), False otherwise. + """ + global VERSION_LOADED + if VERSION_LOADED: + return True + + # Load the VERSION file if it exists + from dotenv import load_dotenv + + version_file = get_version_file() + if version_file.exists(): + load_dotenv(version_file) + VERSION_LOADED = True + return True + return False diff --git a/src/backend/InvenTree/InvenTree/settings.py b/src/backend/InvenTree/InvenTree/settings.py index 965fbc05de..a8ce065b04 100644 --- a/src/backend/InvenTree/InvenTree/settings.py +++ b/src/backend/InvenTree/InvenTree/settings.py @@ -23,7 +23,6 @@ from django.http import Http404, HttpResponseGone import structlog from corsheaders.defaults import default_headers as default_cors_headers -from dotenv import load_dotenv from InvenTree.cache import get_cache_config, is_global_cache_enabled from InvenTree.config import ( @@ -80,11 +79,7 @@ BASE_DIR = config.get_base_dir() # Load configuration data CONFIG = config.load_config_data(set_cache=True) - -# Load VERSION data if it exists -version_file = config.get_root_dir().joinpath('VERSION') -if version_file.exists(): - load_dotenv(version_file) +config.load_version_file() # Default action is to run the system in Debug mode # SECURITY WARNING: don't run with debug turned on in production! diff --git a/src/backend/InvenTree/InvenTree/version.py b/src/backend/InvenTree/InvenTree/version.py index ca699f0190..30aa8bd07d 100644 --- a/src/backend/InvenTree/InvenTree/version.py +++ b/src/backend/InvenTree/InvenTree/version.py @@ -269,7 +269,7 @@ def inventreeBranch(): branch = os.environ.get('INVENTREE_PKG_BRANCH', '') if branch: - return branch + return ' '.join(branch.splitlines()) if main_branch is None: return None From 022c48f2d346580a24833b24ec52ccf04f1c3778 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 13 Oct 2025 09:47:42 +1100 Subject: [PATCH 04/10] [API] Allow search of assembly fields in BOM API endpoint (#10561) * [API] Allow search of assembly fields in BOM API endpoint - Required so we can search the "used in" table * Bump API version --- src/backend/InvenTree/InvenTree/api_version.py | 5 ++++- src/backend/InvenTree/part/api.py | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py index 9d9cf990ca..9e059dfe7b 100644 --- a/src/backend/InvenTree/InvenTree/api_version.py +++ b/src/backend/InvenTree/InvenTree/api_version.py @@ -1,12 +1,15 @@ """InvenTree API version information.""" # InvenTree API version -INVENTREE_API_VERSION = 407 +INVENTREE_API_VERSION = 408 """Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" INVENTREE_API_TEXT = """ +v408 -> 2025-10-13: https://github.com/inventree/InvenTree/pull/10561 + - Allow search of assembly fields in BOM API endpoint + v407 -> 2025-10-09: https://github.com/inventree/InvenTree/pull/10538 - Breaking: Set error status code for plugin action call instead of just returning error data diff --git a/src/backend/InvenTree/part/api.py b/src/backend/InvenTree/part/api.py index 5c2af9911b..9f66d47796 100644 --- a/src/backend/InvenTree/part/api.py +++ b/src/backend/InvenTree/part/api.py @@ -1639,6 +1639,11 @@ class BomList( search_fields = [ 'reference', + 'part__name', + 'part__description', + 'part__IPN', + 'part__revision', + 'part__keywords', 'sub_part__name', 'sub_part__description', 'sub_part__IPN', From 30e91eb22691ea8568f2ba7e6391255d00447bf6 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 13 Oct 2025 09:51:07 +1100 Subject: [PATCH 05/10] Installer docs (#10552) * Better formatting * Tweak setup docs * Add information on process control and logs * Fix typo * Change `cli` to `invoke` * Update docs/docs/start/installer.md Co-authored-by: Matthias Mair * Update docs/docs/start/installer.md Co-authored-by: Matthias Mair * Remove available commands section from installer.md Removed section on available commands for InvenTree services. --------- Co-authored-by: Matthias Mair --- docs/docs/settings/error_codes.md | 2 +- docs/docs/start/installer.md | 72 +++++++++++++++++++++++++------ docs/mkdocs.yml | 3 +- 3 files changed, 62 insertions(+), 15 deletions(-) diff --git a/docs/docs/settings/error_codes.md b/docs/docs/settings/error_codes.md index 47253c4777..8c552ab10c 100644 --- a/docs/docs/settings/error_codes.md +++ b/docs/docs/settings/error_codes.md @@ -14,7 +14,7 @@ Only stable / production releases of InvenTree include the frontend panel. This If you want to use the frontend panel, you can either: - use a docker image that is version-tagged or the stable version -- use a package version that is from the stable or version stream - if you are and it is not working, run `sudo inventree run cli update` to re-run the upgrade +- use a package version that is from the stable or version stream - if you are and it is not working, run `sudo inventree run invoke update` to re-run the upgrade - install node and yarn on the server to build the frontend with the [invoke](../start/invoke.md) task `int.frontend-build` Raise an issue if none of these options work. diff --git a/docs/docs/start/installer.md b/docs/docs/start/installer.md index ec941c7e54..b8f5116c5b 100644 --- a/docs/docs/start/installer.md +++ b/docs/docs/start/installer.md @@ -37,7 +37,7 @@ The installer creates the following directories: | `/opt/inventree/` | InvenTree application files | | `/opt/inventree/data/` | InvenTree data files | -#### Performed steps +#### Performed Steps The installer script performs the following functions: @@ -111,7 +111,7 @@ To stop the automatic generation of an admin user, generate an empty file needs By default, InvenTree is served internally on port 6000 and then proxied via Nginx. The config is placed in `/etc/nginx/sites-enabled/inventree.conf` and overwritten on each update. The location can be set with the environment variable `SETUP_NGINX_FILE`. This only serves an HTTP version of InvenTree, to use HTTPS (recommended for production) or customize any further an additional config file should be used. -#### Extra python packages +#### Extra Python Packages Extra python packages can be installed by setting the environment variable `SETUP_EXTRA_PIP`. #### Database Options @@ -120,25 +120,51 @@ The used database backend can be configured with environment variables (before t ## Moving Data -To change the data storage location, link the new location to `/opt/inventree/data`. -A rough outline of steps to achieve this could be: -- shut down the app service(s) `inventree` and webserver `nginx` -- copy data to the new location -- check everything was transferred successfully -- delete the old location -- create a symlink from the old location to the new one -- start up the services again +To change the data storage location, link the new location to `/opt/inventree/data`. A rough outline of steps to achieve this could be: + +- Shut down the app service(s) `inventree` and webserver `nginx` +- Copy data to the new location +- Check everything was transferred successfully +- Delete the old location +- Create a symlink from the old location to the new one +- Start up the services again ## Updating InvenTree -To update InvenTree run `apt install --only-upgrade inventree` - this might need to be run as a sudo user. +To update InvenTree run the following command, which updates the InvenTree package to the latest version: + +```bash +apt install --only-upgrade inventree +``` + +Note that this command may need to be run as a sudo user. ## Controlling InvenTree ### Services -InvenTree installs multiple services that can be controlled with your local system runner (`service` or `systemctl`). -The service `inventree` controls everything, `inventree-web` (the [InvenTree web server](./processes.md#web-server)) and `inventree-worker` the [background worker(s)](./processes.md#background-worker). +InvenTree installs multiple services that can be controlled with your local system runner (`service` or `systemctl`): + +- `inventree` - The main InvenTree service that controls the web server and background worker(s) +- `inventree-web` - The InvenTree [web server](./processes.md#web-server) process(es) +- `inventree-worker` - The InvenTree [background worker(s)](./processes.md#background-worker) process(es) + +#### Restarting Services + +To restart the InvenTree services, use the following commands as necessary: + +```bash +# Restart all InvenTree services +inventree restart + +# Restart the web server only +inventree restart web + +# Restart the worker only +inventree restart worker +``` + +### Scaling Workers More instances of the worker can be instantiated from the command line. This is only meant for advanced users. @@ -180,6 +206,26 @@ For example, to print InvenTree version information: inventree run invoke version ``` +### Viewing Logs + +To view the logs of the InvenTree services, use the following commands: + +```bash +inventree logs +``` + +To view just the tail of the logs, use: + +```bash +inventree logs --tail +``` + +Or, to follow the logs in real-time: + +```bash +inventree logs --follow +``` + ## Architecture The packages are provided by [packager.io](https://packager.io/). They are built each time updates are pushed to GitHub and released about 10 minutes later. The local package index must be updated to see the new release in the package manager. diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 3f2b9591e4..2639207c87 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -132,9 +132,10 @@ nav: - Docker: - Introduction: start/docker.md - Installation: start/docker_install.md + - Installer: + - Installer: start/installer.md - Bare Metal: - Introduction: start/install.md - - Installer: start/installer.md - Production: start/bare_prod.md - Development: start/bare_dev.md - User Accounts: start/accounts.md From 466463ad74ae93fa61d2a1215773db6b561907cb Mon Sep 17 00:00:00 2001 From: Austen Hoogen <60664585+austenwho@users.noreply.github.com> Date: Sun, 12 Oct 2025 17:53:24 -0500 Subject: [PATCH 06/10] Enhancement: Support Redis ACL User Logins (#10551) * Adding support for modern Redis ACL user-baased auth * Reverting pre-config * Simplified to combine legacy and acl redis connection uris --------- Co-authored-by: Austen Hoogen --- CHANGELOG.md | 1 + docs/docs/start/config.md | 1 + src/backend/InvenTree/InvenTree/cache.py | 8 +++++++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da05a36c85..b47d981f1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added `order_queryset` report helper function in [#10439](https://github.com/inventree/InvenTree/pull/10439) - Added much more detailed status information for machines to the API endpoint (including backend and frontend changes) in [#10381](https://github.com/inventree/InvenTree/pull/10381) - Added ability to partially complete and partially scrap build outputs in [#10499](https://github.com/inventree/InvenTree/pull/10499) +- Added support for Redis ACL user-based authentication in [#10551](https://github.com/inventree/InvenTree/pull/10551) ### Changed diff --git a/docs/docs/start/config.md b/docs/docs/start/config.md index b3163a9a4a..228e19fc40 100644 --- a/docs/docs/start/config.md +++ b/docs/docs/start/config.md @@ -309,6 +309,7 @@ The following cache settings are available: | INVENTREE_CACHE_HOST | cache.host | Cache server host | *Not specified* | | INVENTREE_CACHE_PORT | cache.port | Cache server port | 6379 | | INVENTREE_CACHE_PASSWORD | cache.password | Cache server password | none | +| INVENTREE_CACHE_USER | cache.user | Cache server username | none | | INVENTREE_CACHE_CONNECT_TIMEOUT | cache.connect_timeout | Cache connection timeout (seconds) | 3 | | INVENTREE_CACHE_TIMEOUT | cache.timeout | Cache timeout (seconds) | 3 | | INVENTREE_CACHE_TCP_KEEPALIVE | cache.tcp_keepalive | Cache TCP keepalive | True | diff --git a/src/backend/InvenTree/InvenTree/cache.py b/src/backend/InvenTree/InvenTree/cache.py index fb3d050289..3d56a11815 100644 --- a/src/backend/InvenTree/InvenTree/cache.py +++ b/src/backend/InvenTree/InvenTree/cache.py @@ -43,6 +43,11 @@ def cache_password(): return cache_setting('password', None) +def cache_user(): + """Return the cash username.""" + return cache_setting('user', None) + + def is_global_cache_enabled() -> bool: """Check if the global cache is enabled. @@ -85,9 +90,10 @@ def get_cache_config(global_cache: bool) -> dict: if global_cache: # Build Redis URL with optional password password = cache_password() + user = cache_user() or '' if password: - redis_url = f'redis://:{password}@{cache_host()}:{cache_port()}/0' + redis_url = f'redis://{user}:{password}@{cache_host()}:{cache_port()}/0' else: redis_url = f'redis://{cache_host()}:{cache_port()}/0' From 6badc0148f7070f36379f5fbddd4b6a3f4b079ea Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 13 Oct 2025 12:25:21 +1100 Subject: [PATCH 07/10] [UI] Adjust login error messages (#10556) * Adjust config template - Don't hard-code cookie mode into template - Revert to the "default" values (which are the same) * [ui] better feedback on login error - Show error code, at least * Revert removed code * Adjust playwright tests --- src/backend/InvenTree/config_template.yaml | 10 +++---- src/frontend/src/functions/auth.tsx | 35 ++++++++++++++++++---- src/frontend/tests/pui_login.spec.ts | 4 +-- 3 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/backend/InvenTree/config_template.yaml b/src/backend/InvenTree/config_template.yaml index 78918a9829..141194fb99 100644 --- a/src/backend/InvenTree/config_template.yaml +++ b/src/backend/InvenTree/config_template.yaml @@ -110,7 +110,7 @@ sentry_enabled: False #sentry_dsn: https://custom@custom.ingest.sentry.io/custom # OpenTelemetry tracing/metrics - disabled by default - refer to the documentation for full list of options -# This can be used to send tracing data, logs and metrics to OpenTelemtry compatible backends +# This can be used to send tracing data, logs and metrics to OpenTelemetry compatible backends tracing: enabled: false @@ -142,9 +142,9 @@ allowed_hosts: # use_x_forwarded_proto: true # Cookie settings (nominally the default settings should be fine) -cookie: - secure: false - samesite: false +# cookie: +# secure: false +# samesite: false # Cross Origin Resource Sharing (CORS) settings (see https://github.com/adamchainz/django-cors-headers) cors: @@ -203,7 +203,7 @@ remote_login_header: HTTP_REMOTE_USER # - 'allauth.socialaccount.providers.github' # Add specific settings for social account providers (if required) -# Refer to the djngo-allauth documentation for more details: +# Refer to the django-allauth documentation for more details: # https://docs.allauth.org/en/latest/socialaccount/provider_configuration.html # social_providers: # github: diff --git a/src/frontend/src/functions/auth.tsx b/src/frontend/src/functions/auth.tsx index fec5f68cf9..4130bf0676 100644 --- a/src/frontend/src/functions/auth.tsx +++ b/src/frontend/src/functions/auth.tsx @@ -106,14 +106,37 @@ export async function doBasicLogin( } }) .catch(async (err) => { - if (err?.response?.status == 401) { - await handlePossibleMFAError(err); - } else if (err?.response?.status == 409) { + notifications.hide('auth-login-error'); + + if (err?.response?.status) { + switch (err.response.status) { + case 401: + await handlePossibleMFAError(err); + break; + case 409: + notifications.show({ + title: t`Already logged in`, + message: t`There is a conflicting session on the server for this browser. Please logout of that first.`, + color: 'red', + id: 'auth-login-error', + autoClose: false + }); + break; + default: + notifications.show({ + title: `${t`Login failed`} (${err.response.status})`, + message: t`Check your input and try again.`, + id: 'auth-login-error', + color: 'red' + }); + break; + } + } else { notifications.show({ - title: t`Already logged in`, - message: t`There is a conflicting session on the server for this browser. Please logout of that first.`, + title: t`Login failed`, + message: t`No response from server.`, color: 'red', - autoClose: false + id: 'login-error' }); } }); diff --git a/src/frontend/tests/pui_login.spec.ts b/src/frontend/tests/pui_login.spec.ts index 93b6388a71..549ff55611 100644 --- a/src/frontend/tests/pui_login.spec.ts +++ b/src/frontend/tests/pui_login.spec.ts @@ -9,8 +9,8 @@ import { doLogin } from './login.js'; test('Login - Failures', async ({ page }) => { const loginWithError = async () => { await page.getByRole('button', { name: 'Log In' }).click(); - await page.getByText('Login failed').waitFor(); - await page.getByText('Check your input and try again').waitFor(); + await page.getByText('Login failed', { exact: true }).waitFor(); + await page.getByText('Check your input and try again').first().waitFor(); await page.locator('#login').getByRole('button').click(); }; From ea868b31799e56ec9c8ae410c5bf061d4feaf0f1 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 13 Oct 2025 13:08:49 +1100 Subject: [PATCH 08/10] [UI] About InvenTree Tweak (#10565) - Hide version entries without data --- .../components/modals/AboutInvenTreeModal.tsx | 44 ++++++++++--------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/src/frontend/src/components/modals/AboutInvenTreeModal.tsx b/src/frontend/src/components/modals/AboutInvenTreeModal.tsx index 806e868e51..56feea6e2c 100644 --- a/src/frontend/src/components/modals/AboutInvenTreeModal.tsx +++ b/src/frontend/src/components/modals/AboutInvenTreeModal.tsx @@ -68,27 +68,29 @@ const AboutContent = ({ }); function fillTable(lookup: AboutLookupRef[], data: any, alwaysLink = false) { - return lookup.map((map: AboutLookupRef, idx) => ( - - {map.title} - - - {alwaysLink ? ( - - {data[map.ref]} - - ) : map.link ? ( - - {data[map.ref]} - - ) : ( - data[map.ref] - )} - {map.copy && } - - - - )); + return lookup + .filter((entry: AboutLookupRef) => !!data[entry.ref]) + .map((entry: AboutLookupRef, idx) => ( + + {entry.title} + + + {alwaysLink ? ( + + {data[entry.ref]} + + ) : entry.link ? ( + + {data[entry.ref]} + + ) : ( + data[entry.ref] + )} + {entry.copy && } + + + + )); } /* renderer */ if (isLoading) return Loading; From f22417fd1f6bd17ad1d80013c5bdeea7b6cf9998 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 13 Oct 2025 13:08:55 +1100 Subject: [PATCH 09/10] [UI] Fix stock actions (#10566) * Clear selected records when search term changes * Clear selection after performing stock actions --- src/frontend/src/tables/InvenTreeTable.tsx | 1 + src/frontend/src/tables/stock/StockItemTable.tsx | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/frontend/src/tables/InvenTreeTable.tsx b/src/frontend/src/tables/InvenTreeTable.tsx index cee6cc9a22..8335c9840f 100644 --- a/src/frontend/src/tables/InvenTreeTable.tsx +++ b/src/frontend/src/tables/InvenTreeTable.tsx @@ -352,6 +352,7 @@ export function InvenTreeTable>({ // Reset the pagination state when the search term changes useEffect(() => { tableState.setPage(1); + tableState.clearSelectedRecords(); }, [ tableState.searchTerm, tableState.filterSet.activeFilters, diff --git a/src/frontend/src/tables/stock/StockItemTable.tsx b/src/frontend/src/tables/stock/StockItemTable.tsx index a7a546e45d..4d8f98226a 100644 --- a/src/frontend/src/tables/stock/StockItemTable.tsx +++ b/src/frontend/src/tables/stock/StockItemTable.tsx @@ -504,7 +504,10 @@ export function StockItemTable({ return { items: table.selectedRecords, model: ModelType.stockitem, - refresh: table.refreshTable, + refresh: () => { + table.clearSelectedRecords(); + table.refreshTable(); + }, filters: { in_stock: true } From a466926aef138cb1efa755e6a60303d0d29f4fe2 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 13 Oct 2025 14:20:52 +1100 Subject: [PATCH 10/10] [UI] Remove placeholder text (#10569) - Remove TODO entry - Placeholder when adding external build order support --- src/frontend/src/pages/build/BuildDetail.tsx | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/frontend/src/pages/build/BuildDetail.tsx b/src/frontend/src/pages/build/BuildDetail.tsx index c67ef54c47..28337dde2b 100644 --- a/src/frontend/src/pages/build/BuildDetail.tsx +++ b/src/frontend/src/pages/build/BuildDetail.tsx @@ -238,17 +238,6 @@ export default function BuildDetail() { icon: 'manufacturers', hidden: !build.external }, - { - type: 'text', - name: 'purchase_order', - label: t`Purchase Order`, - icon: 'purchase_orders', - copy: true, - hidden: !build.external, - value_formatter: () => { - return 'TODO: external PO'; - } - }, { type: 'text', name: 'reference',