diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py
index 2c67860e97..390cbc0404 100644
--- a/InvenTree/InvenTree/settings.py
+++ b/InvenTree/InvenTree/settings.py
@@ -979,6 +979,10 @@ CUSTOM_LOGO = get_custom_file('INVENTREE_CUSTOM_LOGO', 'customize.logo', 'custom
CUSTOM_SPLASH = get_custom_file('INVENTREE_CUSTOM_SPLASH', 'customize.splash', 'custom splash')
CUSTOMIZE = get_setting('INVENTREE_CUSTOMIZE', 'customize', {})
+
+# Frontend settings
+PUI_SETTINGS = get_setting("INVENTREE_PUI_SETTINGS", "pui_settings", {})
+
if DEBUG:
logger.info("InvenTree running with DEBUG enabled")
diff --git a/InvenTree/config_template.yaml b/InvenTree/config_template.yaml
index 6f6d6aab33..62a190963e 100644
--- a/InvenTree/config_template.yaml
+++ b/InvenTree/config_template.yaml
@@ -240,6 +240,15 @@ remote_login_header: HTTP_REMOTE_USER
# hide_admin_link: true
# hide_password_reset: true
+# Platform UI options
+# pui_settings:
+# server_list:
+# my_server1:
+# host: https://demo.inventree.org/api/
+# name: InvenTree Demo
+# default_server: my_server1
+# show_server_selector: false
+
# Custom flags
# InvenTree uses django-flags; read more in their docs at https://cfpb.github.io/django-flags/conditions/
# Use environment variable INVENTREE_FLAGS or the settings below
diff --git a/InvenTree/web/templates/web/index.html b/InvenTree/web/templates/web/index.html
index bb007ee6dd..7d4d33c4a4 100644
--- a/InvenTree/web/templates/web/index.html
+++ b/InvenTree/web/templates/web/index.html
@@ -12,6 +12,7 @@
+ {% spa_settings %}
{% spa_bundle %}
diff --git a/InvenTree/web/templatetags/spa_helper.py b/InvenTree/web/templatetags/spa_helper.py
index a94c8a1288..3087e7c34e 100644
--- a/InvenTree/web/templatetags/spa_helper.py
+++ b/InvenTree/web/templatetags/spa_helper.py
@@ -7,9 +7,11 @@ from django import template
from django.conf import settings
from django.utils.safestring import mark_safe
-logger = getLogger("gwaesser_backend")
+logger = getLogger("InvenTree")
register = template.Library()
+PUI_SETTINGS = json.dumps(settings.PUI_SETTINGS)
+
@register.simple_tag
def spa_bundle():
@@ -36,3 +38,9 @@ def spa_bundle():
f"""
{imports_files}"""
)
+
+
+@register.simple_tag
+def spa_settings():
+ """Render settings for spa."""
+ return mark_safe(f"""""")
diff --git a/src/frontend/netlify.toml b/src/frontend/netlify.toml
index 0f7783e4d0..9dbf89b30c 100644
--- a/src/frontend/netlify.toml
+++ b/src/frontend/netlify.toml
@@ -2,9 +2,12 @@
# https://www.netlify.com/docs/netlify-toml-reference/
[build]
- command = "yarn run build --outDir dist"
+ command = "yarn run extract && yarn run compile && yarn run build --outDir dist"
publish = "dist"
+[build.environment]
+ VITE_DEMO = "true"
+
# Send requests to subpath
[[redirects]]
diff --git a/src/frontend/src/components/forms/AuthFormOptions.tsx b/src/frontend/src/components/forms/AuthFormOptions.tsx
index bdf91ccaaf..002005224e 100644
--- a/src/frontend/src/components/forms/AuthFormOptions.tsx
+++ b/src/frontend/src/components/forms/AuthFormOptions.tsx
@@ -19,9 +19,11 @@ export function AuthFormOptions({
-
-
-
+ {window.INVENTREE_SETTINGS.show_server_selector && (
+
+
+
+ )}
{server.version} | {server.apiVersion}
diff --git a/src/frontend/src/defaults/defaultHostList.tsx b/src/frontend/src/defaults/defaultHostList.tsx
index 111f380674..38cec156c3 100644
--- a/src/frontend/src/defaults/defaultHostList.tsx
+++ b/src/frontend/src/defaults/defaultHostList.tsx
@@ -2,18 +2,5 @@ import { t } from '@lingui/macro';
import { HostList } from '../states/states';
-export const defaultHostList: HostList = {
- 'mantine-u56l5jt85': {
- host: 'https://demo.inventree.org/api/',
- name: t`InvenTree Demo`
- },
- 'mantine-g8t1zrj50': {
- host: 'https://sample.app.invenhost.com/api/',
- name: 'InvenHost: Sample'
- },
- 'mantine-cqj63coxn': {
- host: 'http://localhost:8000/api/',
- name: t`Local Server`
- }
-};
-export const defaultHostKey = 'mantine-cqj63coxn';
+export const defaultHostList: HostList = window.INVENTREE_SETTINGS.server_list;
+export const defaultHostKey = window.INVENTREE_SETTINGS.default_server;
diff --git a/src/frontend/src/main.tsx b/src/frontend/src/main.tsx
index 000a15c091..d6a6e65512 100644
--- a/src/frontend/src/main.tsx
+++ b/src/frontend/src/main.tsx
@@ -4,6 +4,44 @@ import 'react-grid-layout/css/styles.css';
import 'react-resizable/css/styles.css';
import App from './App';
+import { HostList } from './states/states';
+
+// define settings
+declare global {
+ interface Window {
+ INVENTREE_SETTINGS: {
+ server_list: HostList;
+ default_server: string;
+ show_server_selector: boolean;
+ };
+ }
+}
+
+export const IS_DEV = import.meta.env.DEV;
+export const IS_DEMO = import.meta.env.VITE_DEMO === 'true';
+export const IS_DEV_OR_DEMO = IS_DEV || IS_DEMO;
+
+window.INVENTREE_SETTINGS = {
+ server_list: {
+ 'mantine-cqj63coxn': {
+ host: `${window.location.origin}/api/`,
+ name: 'Current Server'
+ },
+ ...(IS_DEV_OR_DEMO
+ ? {
+ 'mantine-u56l5jt85': {
+ host: 'https://demo.inventree.org/api/',
+ name: 'InvenTree Demo'
+ }
+ }
+ : {})
+ },
+ default_server: IS_DEMO ? 'mantine-u56l5jt85' : 'mantine-cqj63coxn', // use demo server for demo mode
+ show_server_selector: IS_DEV_OR_DEMO,
+
+ // merge in settings that are already set via django's spa_view or for development
+ ...((window.INVENTREE_SETTINGS || {}) as any)
+};
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
diff --git a/src/frontend/src/pages/Auth/Login.tsx b/src/frontend/src/pages/Auth/Login.tsx
index 2cd11b7005..0452669083 100644
--- a/src/frontend/src/pages/Auth/Login.tsx
+++ b/src/frontend/src/pages/Auth/Login.tsx
@@ -3,6 +3,7 @@ import { Center, Container } from '@mantine/core';
import { useToggle } from '@mantine/hooks';
import { useEffect } from 'react';
+import { setApiDefaults } from '../../App';
import { AuthFormOptions } from '../../components/forms/AuthFormOptions';
import { AuthenticationForm } from '../../components/forms/AuthenticationForm';
import { InstanceOptions } from '../../components/forms/InstanceOptions';
@@ -27,6 +28,7 @@ export default function Login() {
// Data manipulation functions
function ChangeHost(newHost: string): void {
setHost(hostList[newHost].host, newHost);
+ setApiDefaults();
fetchServerApiState();
}
diff --git a/src/frontend/src/vite-env.d.ts b/src/frontend/src/vite-env.d.ts
index 11f02fe2a0..f915651859 100644
--- a/src/frontend/src/vite-env.d.ts
+++ b/src/frontend/src/vite-env.d.ts
@@ -1 +1,9 @@
///
+
+interface ImportMetaEnv {
+ readonly VITE_DEMO: string;
+}
+
+interface ImportMeta {
+ readonly env: ImportMetaEnv;
+}
diff --git a/src/frontend/vite.config.ts b/src/frontend/vite.config.ts
index 092b5d90a3..fbc74ca6fc 100644
--- a/src/frontend/vite.config.ts
+++ b/src/frontend/vite.config.ts
@@ -1,6 +1,9 @@
import react from '@vitejs/plugin-react';
+import { platform } from 'node:os';
import { defineConfig, splitVendorChunkPlugin } from 'vite';
+const IS_IN_WSL = platform().includes('WSL');
+
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
@@ -16,8 +19,17 @@ export default defineConfig({
outDir: '../../InvenTree/web/static/web'
},
server: {
+ proxy: {
+ '/api': {
+ target: 'http://localhost:8000',
+ changeOrigin: true,
+ secure: true
+ }
+ },
watch: {
- usePolling: true
+ // use polling only for WSL as the file system doesn't trigger notifications for Linux apps
+ // ref: https://github.com/vitejs/vite/issues/1153#issuecomment-785467271
+ usePolling: IS_IN_WSL
}
}
});