Merge branch 'inventree:master' into pr/ChristianSchindler/6305

This commit is contained in:
Matthias Mair 2024-04-02 19:25:36 +01:00 committed by GitHub
commit 839d9f9075
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
113 changed files with 54301 additions and 52524 deletions

View File

@ -1,33 +0,0 @@
version = 1
exclude_patterns = [
"docs/docs/javascripts/**", # Docs: Helpers
"docs/ci/**", # Docs: CI
"InvenTree/InvenTree/static/**", # Backend: CUI static files
"ci/**", # Backend: CI
"InvenTree/**/migrations/*.py", # Backend: Migration files
"src/frontend/src/locales/**", # Frontend: Translations
]
test_patterns = ["**/test_*.py", "**/test.py", "**/tests.py"]
[[analyzers]]
name = "shell"
[[analyzers]]
name = "javascript"
[analyzers.meta]
plugins = ["react"]
[[analyzers]]
name = "python"
[analyzers.meta]
runtime_version = "3.x.x"
[[analyzers]]
name = "docker"
[[analyzers]]
name = "test-coverage"
enabled = false

View File

@ -43,7 +43,9 @@ body:
label: "Deployment Method"
options:
- label: "Docker"
- label: "Package"
- label: "Bare metal"
- label: "Other - added info in Steps to Reproduce"
- type: textarea
id: version-info
validations:

View File

@ -16,6 +16,8 @@ jobs:
backport:
name: Backport PR
runs-on: ubuntu-latest
permissions:
contents: write
if: |
github.event.pull_request.merged == true
&& contains(github.event.pull_request.labels.*.name, 'backport')

View File

@ -27,6 +27,7 @@ jobs:
INVENTREE_MEDIA_ROOT: ./media
INVENTREE_STATIC_ROOT: ./static
INVENTREE_BACKUP_DIR: ./backup
INVENTREE_SITE_URL: http://localhost:8000
steps:
- name: Checkout Code

View File

@ -76,8 +76,8 @@ jobs:
python-version: ${{ env.python_version }}
- name: Version Check
run: |
pip install requests
pip install pyyaml
pip install requests==2.31.0
pip install pyyaml==6.0.1
python3 ci/version_check.py
echo "git_commit_hash=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
echo "git_commit_date=$(git show -s --format=%ci)" >> $GITHUB_ENV

View File

@ -92,7 +92,7 @@ jobs:
uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # pin@v3.0.1
- name: Check Version
run: |
pip install requests
pip install requests==2.31.0
python3 ci/version_check.py
mkdocs:
@ -110,7 +110,7 @@ jobs:
python-version: ${{ env.python_version }}
- name: Check Config
run: |
pip install pyyaml
pip install pyyaml==6.0.1
pip install -r docs/requirements.txt
python docs/ci/check_mkdocs_config.py
- name: Check Links
@ -156,7 +156,7 @@ jobs:
- name: Download public schema
if: needs.paths-filter.outputs.api == 'false'
run: |
pip install requests >/dev/null 2>&1
pip install requests==2.31.0 >/dev/null 2>&1
version="$(python3 ci/version_check.py only_version 2>&1)"
echo "Version: $version"
url="https://raw.githubusercontent.com/inventree/schema/main/export/${version}/api.yaml"
@ -175,7 +175,7 @@ jobs:
id: version
if: github.ref == 'refs/heads/master' && needs.paths-filter.outputs.api == 'true'
run: |
pip install requests >/dev/null 2>&1
pip install requests==2.31.0 >/dev/null 2>&1
version="$(python3 ci/version_check.py only_version 2>&1)"
echo "Version: $version"
echo "version=$version" >> "$GITHUB_OUTPUT"

View File

@ -19,7 +19,7 @@ jobs:
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
- name: Version Check
run: |
pip install requests
pip install requests==2.31.0
python3 ci/version_check.py
- name: Push to Stable Branch
uses: ad-m/github-push-action@d91a481090679876dfc4178fef17f286781251df # pin@v0.8.0

View File

@ -54,7 +54,7 @@ jobs:
# For private repositories:
# - `publish_results` will always be set to `false`, regardless
# of the value entered here.
publish_results: false
publish_results: true
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.

View File

@ -10,12 +10,14 @@ env:
node_version: 18
permissions:
contents: write
contents: read
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: write
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -10,7 +10,7 @@
# - Monitors source files for any changes, and live-reloads server
ARG base_image=python:3.11-alpine3.18
FROM ${base_image} as inventree_base
FROM ${base_image} AS inventree_base
# Build arguments for this image
ARG commit_tag=""
@ -92,7 +92,7 @@ RUN chmod +x init.sh
ENTRYPOINT ["/bin/ash", "./init.sh"]
FROM inventree_base as prebuild
FROM inventree_base AS prebuild
ENV PATH=/root/.local/bin:$PATH
RUN ./install_build_packages.sh --no-cache --virtual .build-deps && \
@ -100,9 +100,9 @@ RUN ./install_build_packages.sh --no-cache --virtual .build-deps && \
apk --purge del .build-deps
# Frontend builder image:
FROM prebuild as frontend
FROM prebuild AS frontend
RUN apk add --no-cache --update nodejs npm && npm install -g yarn
RUN apk add --no-cache --update nodejs npm && npm install -g yarn@v1.22.22
RUN yarn config set network-timeout 600000 -g
COPY InvenTree ${INVENTREE_HOME}/InvenTree
COPY src ${INVENTREE_HOME}/src
@ -117,7 +117,7 @@ COPY db_version ${INVENTREE_HOME}/db_version
# InvenTree production image:
# - Copies required files from local directory
# - Starts a gunicorn webserver
FROM inventree_base as production
FROM inventree_base AS production
ENV INVENTREE_DEBUG=False
@ -134,11 +134,9 @@ COPY InvenTree ./InvenTree
COPY --from=frontend ${INVENTREE_HOME}/InvenTree/web/static/web ./InvenTree/web/static/web
# Launch the production server
# TODO: Work out why environment variables cannot be interpolated in this command
# TODO: e.g. -b ${INVENTREE_WEB_ADDR}:${INVENTREE_WEB_PORT} fails here
CMD gunicorn -c ./gunicorn.conf.py InvenTree.wsgi -b 0.0.0.0:8000 --chdir ./InvenTree
FROM inventree_base as dev
FROM inventree_base AS dev
# Vite server (for local frontend development)
EXPOSE 5173
@ -146,11 +144,11 @@ EXPOSE 5173
# Install packages required for building python packages
RUN ./install_build_packages.sh
RUN pip install uv --no-cache-dir && pip install -r base_requirements.txt --no-cache
RUN pip install uv==0.1.26 --no-cache-dir && pip install -r base_requirements.txt --no-cache
# Install nodejs / npm / yarn
RUN apk add --no-cache --update nodejs npm && npm install -g yarn
RUN apk add --no-cache --update nodejs npm && npm install -g yarn@v1.22.22
RUN yarn config set network-timeout 600000 -g
# The development image requires the source code to be mounted to /home/inventree/

View File

@ -347,7 +347,7 @@ def get_secret_key():
# Create a random key file
options = string.digits + string.ascii_letters + string.punctuation
key = ''.join([random.choice(options) for i in range(100)])
key = ''.join([random.choice(options) for _idx in range(100)])
secret_key_file.write_text(key)
logger.debug("Loading SECRET_KEY from '%s'", secret_key_file)

View File

@ -95,7 +95,7 @@ def from_engineering_notation(value):
"""
value = str(value).strip()
pattern = f'(\d+)([a-zA-Z]+)(\d+)(.*)'
pattern = '(\d+)([a-zA-Z]+)(\d+)(.*)'
if match := re.match(pattern, value):
left, prefix, right, suffix = match.groups()
@ -198,7 +198,6 @@ def convert_physical_value(value: str, unit: str = None, strip_units=True):
break
except Exception as exc:
value = None
pass
if value is None:
if unit:

View File

@ -20,7 +20,7 @@ class InvenTreeExchange(SimpleExchangeBackend):
name = 'InvenTreeExchange'
def get_rates(self, **kwargs) -> None:
def get_rates(self, **kwargs) -> dict:
"""Set the requested currency codes and get rates."""
from common.models import InvenTreeSetting
from plugin import registry

View File

@ -17,11 +17,10 @@ class InvenTreeDateFilter(rest_filters.DateFilter):
def filter(self, qs, value):
"""Override the filter method to handle timezones correctly."""
if settings.USE_TZ:
if value is not None:
tz = timezone.get_current_timezone()
value = datetime(value.year, value.month, value.day)
value = make_aware(value, tz, True)
if settings.USE_TZ and value is not None:
tz = timezone.get_current_timezone()
value = datetime(value.year, value.month, value.day)
value = make_aware(value, tz, True)
return super().filter(qs, value)

View File

@ -69,7 +69,7 @@ def construct_format_regex(fmt_string: str) -> str:
for group in string.Formatter().parse(fmt_string):
prefix = group[0] # Prefix (literal text appearing before this group)
name = group[1] # Name of this format variable
format = group[2] # Format specifier e.g :04d
_fmt = group[2] # Format specifier e.g :04d
rep = [
'+',
@ -106,16 +106,16 @@ def construct_format_regex(fmt_string: str) -> str:
# Add a named capture group for the format entry
if name:
# Check if integer values are required
if format.endswith('d'):
chr = '\d'
if _fmt.endswith('d'):
c = '\d'
else:
chr = '.'
c = '.'
# Specify width
# TODO: Introspect required width
w = '+'
pattern += f'(?P<{name}>{chr}{w})'
pattern += f'(?P<{name}>{c}{w})'
pattern += '$'

View File

@ -248,11 +248,7 @@ def str2int(text, default=None):
def is_bool(text):
"""Determine if a string value 'looks' like a boolean."""
if str2bool(text, True):
return True
elif str2bool(text, False):
return True
return False
return str2bool(text, True) or str2bool(text, False)
def isNull(text):
@ -473,7 +469,7 @@ def DownloadFile(
return response
def increment_serial_number(serial: str):
def increment_serial_number(serial):
"""Given a serial number, (attempt to) generate the *next* serial number.
Note: This method is exposed to custom plugins.
@ -857,9 +853,9 @@ def hash_barcode(barcode_data):
barcode_data = str(barcode_data).strip()
barcode_data = remove_non_printable_characters(barcode_data)
hash = hashlib.md5(str(barcode_data).encode())
barcode_hash = hashlib.md5(str(barcode_data).encode())
return str(hash.hexdigest())
return str(barcode_hash.hexdigest())
def hash_file(filename: Union[str, Path], storage: Union[Storage, None] = None):

View File

@ -7,7 +7,7 @@ from rest_framework import permissions
import users.models
def get_model_for_view(view, raise_error=True):
def get_model_for_view(view):
"""Attempt to introspect the 'model' type for an API view."""
if hasattr(view, 'get_permission_model'):
return view.get_permission_model()

View File

@ -426,17 +426,17 @@ TEMPLATES = [
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'InvenTree.exceptions.exception_handler',
'DATETIME_FORMAT': '%Y-%m-%d %H:%M',
'DEFAULT_AUTHENTICATION_CLASSES': (
'DEFAULT_AUTHENTICATION_CLASSES': [
'users.authentication.ApiTokenAuthentication',
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
),
],
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
'DEFAULT_PERMISSION_CLASSES': (
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
'rest_framework.permissions.DjangoModelPermissions',
'InvenTree.permissions.RolePermission',
),
],
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
'DEFAULT_METADATA_CLASS': 'InvenTree.metadata.InvenTreeMetadata',
'DEFAULT_RENDERER_CLASSES': ['rest_framework.renderers.JSONRenderer'],
@ -462,8 +462,8 @@ REST_AUTH_REGISTER_SERIALIZERS = {
if USE_JWT:
JWT_AUTH_COOKIE = 'inventree-auth'
JWT_AUTH_REFRESH_COOKIE = 'inventree-token'
REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'] + (
'dj_rest_auth.jwt_auth.JWTCookieAuthentication',
REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'].append(
'dj_rest_auth.jwt_auth.JWTCookieAuthentication'
)
INSTALLED_APPS.append('rest_framework_simplejwt')
@ -1017,7 +1017,10 @@ if not ALLOWED_HOSTS:
# Ensure that the ALLOWED_HOSTS do not contain any scheme info
for i, host in enumerate(ALLOWED_HOSTS):
if '://' in host:
ALLOWED_HOSTS[i] = host.split('://')[1]
ALLOWED_HOSTS[i] = host = host.split('://')[1]
if ':' in host:
ALLOWED_HOSTS[i] = host = host.split(':')[0]
# List of trusted origins for unsafe requests
# Ref: https://docs.djangoproject.com/en/4.2/ref/settings/#csrf-trusted-origins

View File

@ -85,7 +85,7 @@ for name, provider in providers.registry.provider_map.items():
cls
for cls in prov_mod.__dict__.values()
if isinstance(cls, type)
and not cls == OAuth2Adapter
and cls != OAuth2Adapter
and issubclass(cls, OAuth2Adapter)
]

View File

@ -27,7 +27,7 @@ def get_provider_app(provider):
return apps.first()
def check_provider(provider, raise_error=False):
def check_provider(provider):
"""Check if the given provider is correctly configured.
To be correctly configured, the following must be true:

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -379,11 +379,11 @@ class BomItemResource(InvenTreeResource):
report_skipped = False
clean_model_instances = True
exclude = ['checksum', 'id', 'part', 'sub_part', 'validated']
exclude = ['checksum', 'part', 'sub_part', 'validated']
level = Field(attribute='level', column_name=_('BOM Level'), readonly=True)
bom_id = Field(
id = Field(
attribute='pk', column_name=_('BOM Item ID'), widget=widgets.IntegerWidget()
)
@ -476,7 +476,6 @@ class BomItemResource(InvenTreeResource):
if is_importing:
to_remove += [
'level',
'pk',
'part',
'part__IPN',
'part__name',

View File

@ -267,6 +267,8 @@ class CategoryDetail(CategoryMixin, CustomRetrieveUpdateDestroyAPI):
except AttributeError:
pass
kwargs.setdefault('context', self.get_serializer_context())
return self.serializer_class(*args, **kwargs)
def update(self, request, *args, **kwargs):

View File

@ -30,7 +30,7 @@ class TMEPlugin(SupplierBarcodeMixin, SettingsMixin, InvenTreePlugin):
}
TME_IS_QRCODE_REGEX = re.compile(r'([^\s:]+:[^\s:]+\s+)+(\S+(\s|$)+)+')
TME_IS_BARCODE2D_REGEX = re.compile(r'(([^\s]+)(\s+|$))+')
TME_IS_OLD_BARCODE2D_REGEX = re.compile(r'(([^\s]+)(\s+|$))+')
# Custom field mapping
TME_QRCODE_FIELDS = {
@ -52,22 +52,19 @@ class TMEPlugin(SupplierBarcodeMixin, SettingsMixin, InvenTreePlugin):
key, value = item.split(':')
if key in self.TME_QRCODE_FIELDS:
barcode_fields[self.TME_QRCODE_FIELDS[key]] = value
return barcode_fields
elif self.TME_IS_BARCODE2D_REGEX.fullmatch(barcode_data):
# 2D Barcode format e.g. "PWBP-302 1PMPNWBP-302 Q1 K19361337/1"
elif self.TME_IS_OLD_BARCODE2D_REGEX.fullmatch(barcode_data):
# Old 2D Barcode format e.g. "PWBP-302 1PMPNWBP-302 Q1 K19361337/1"
for item in barcode_data.split(' '):
for k, v in self.ecia_field_map().items():
if item.startswith(k):
barcode_fields[v] = item[len(k) :]
else:
return {}
barcode_fields = self.parse_ecia_barcode2d(barcode_data)
# Custom handling for order number
if SupplierBarcodeMixin.CUSTOMER_ORDER_NUMBER in barcode_fields:
order_number = barcode_fields[SupplierBarcodeMixin.CUSTOMER_ORDER_NUMBER]
if SupplierBarcodeMixin.SUPPLIER_ORDER_NUMBER in barcode_fields:
order_number = barcode_fields[SupplierBarcodeMixin.SUPPLIER_ORDER_NUMBER]
order_number = order_number.split('/')[0]
barcode_fields[SupplierBarcodeMixin.CUSTOMER_ORDER_NUMBER] = order_number
barcode_fields[SupplierBarcodeMixin.SUPPLIER_ORDER_NUMBER] = order_number
return barcode_fields

View File

@ -13,12 +13,12 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='stockitemtestresult',
name='finished_datetime',
field=models.DateTimeField(blank=True, help_text='The timestamp of the test finish', verbose_name='Finished'),
field=models.DateTimeField(blank=True, help_text='The timestamp of the test finish', null=True, verbose_name='Finished'),
),
migrations.AddField(
model_name='stockitemtestresult',
name='started_datetime',
field=models.DateTimeField(blank=True, help_text='The timestamp of the test start', verbose_name='Started'),
field=models.DateTimeField(blank=True, help_text='The timestamp of the test start', null=True, verbose_name='Started'),
),
migrations.AddField(
model_name='stockitemtestresult',

View File

@ -11,7 +11,7 @@
[![OpenSSF Best Practices](https://bestpractices.coreinfrastructure.org/projects/7179/badge)](https://bestpractices.coreinfrastructure.org/projects/7179)
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/inventree/InvenTree/badge)](https://securityscorecards.dev/viewer/?uri=github.com/inventree/InvenTree)
[![Netlify Status](https://api.netlify.com/api/v1/badges/9bbb2101-0a4d-41e7-ad56-b63fb6053094/deploy-status)](https://app.netlify.com/sites/inventree/deploys)
[![DeepSource](https://app.deepsource.com/gh/inventree/InvenTree.svg/?label=active+issues&show_trend=false&token=trZWqixKLk2t-RXtpSIAslVJ)](https://app.deepsource.com/gh/inventree/InvenTree/)
[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=inventree_InvenTree&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=inventree_InvenTree)
[![Coveralls](https://img.shields.io/coveralls/github/inventree/InvenTree)](https://coveralls.io/github/inventree/InvenTree)
[![Crowdin](https://badges.crowdin.net/inventree/localized.svg)](https://crowdin.com/project/inventree)

View File

@ -90,7 +90,7 @@ function detect_envs() {
echo "# Using existing config file: ${INVENTREE_CONFIG_FILE}"
# Install parser
pip install jc -q
pip install jc==1.25.2 -q
# Load config
local CONF=$(cat ${INVENTREE_CONFIG_FILE} | jc --yaml)

View File

@ -45,6 +45,8 @@
root * /var/www/media
file_server
header Content-Disposition attachment
forward_auth {$INVENTREE_SERVER:"http://inventree-server:8000"} {
uri /auth/
}

View File

@ -48,7 +48,7 @@ pip==24.0
pip-tools==7.4.1
platformdirs==4.2.0
# via virtualenv
pre-commit==3.6.2
pre-commit==3.7.0
pycparser==2.21
# via cffi
pyproject-hooks==1.0.0

View File

@ -23,27 +23,27 @@
"@mantine/core": "<7",
"@mantine/dates": "<7",
"@mantine/dropzone": "<7",
"@mantine/form": "<7",
"@mantine/form": "<8",
"@mantine/hooks": "<7",
"@mantine/modals": "<7",
"@mantine/notifications": "<7",
"@naisutech/react-tree": "^3.1.0",
"@sentry/react": "^7.108.0",
"@sentry/react": "^7.109.0",
"@tabler/icons-react": "^3.1.0",
"@tanstack/react-query": "^5.28.8",
"@tanstack/react-query": "^5.28.9",
"@uiw/codemirror-theme-vscode": "^4.21.25",
"@uiw/react-codemirror": "^4.21.25",
"@uiw/react-split": "^5.9.3",
"axios": "^1.6.7",
"dayjs": "^1.11.10",
"easymde": "^2.18.0",
"embla-carousel-react": "^7.1.0",
"embla-carousel-react": "^8.0.0",
"html5-qrcode": "^2.3.8",
"mantine-datatable": "<7",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-grid-layout": "^1.4.4",
"react-hook-form": "^7.51.1",
"react-hook-form": "^7.51.2",
"react-is": "^18.2.0",
"react-router-dom": "^6.22.1",
"react-select": "^5.8.0",
@ -58,15 +58,15 @@
"@lingui/cli": "^4.7.2",
"@lingui/macro": "^4.7.2",
"@playwright/test": "^1.41.2",
"@types/node": "^20.11.20",
"@types/react": "^18.2.71",
"@types/react-dom": "^18.2.19",
"@types/node": "^20.12.2",
"@types/react": "^18.2.73",
"@types/react-dom": "^18.2.23",
"@types/react-grid-layout": "^1.3.5",
"@types/react-router-dom": "^5.3.3",
"@vitejs/plugin-react": "^4.2.1",
"babel-plugin-macros": "^3.1.0",
"typescript": "^5.3.3",
"vite": "^5.2.6",
"vite": "^5.2.7",
"vite-plugin-babel-macros": "^1.0.6"
}
}

View File

@ -15,7 +15,7 @@ import { useDisclosure } from '@mantine/hooks';
import { notifications } from '@mantine/notifications';
import { IconCheck } from '@tabler/icons-react';
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useLocation, useNavigate } from 'react-router-dom';
import { api } from '../../App';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
@ -32,6 +32,7 @@ export function AuthenticationForm() {
const [classicLoginMode, setMode] = useDisclosure(true);
const [auth_settings] = useServerApiState((state) => [state.auth_settings]);
const navigate = useNavigate();
const location = useLocation();
const [isLoggingIn, setIsLoggingIn] = useState<boolean>(false);
@ -52,7 +53,7 @@ export function AuthenticationForm() {
color: 'green',
icon: <IconCheck size="1rem" />
});
navigate('/home');
navigate(location?.state?.redirectFrom ?? '/home');
} else {
notifications.show({
title: t`Login failed`,

View File

@ -64,6 +64,7 @@ export type ApiFormFieldType = {
| 'string'
| 'boolean'
| 'date'
| 'datetime'
| 'integer'
| 'decimal'
| 'float'
@ -215,6 +216,7 @@ export function ApiFormField({
/>
);
case 'date':
case 'datetime':
return <DateField controller={controller} definition={definition} />;
case 'integer':
case 'decimal':

View File

@ -1,9 +1,13 @@
import { DateInput } from '@mantine/dates';
import dayjs from 'dayjs';
import customParseFormat from 'dayjs/plugin/customParseFormat';
import { useCallback, useId, useMemo } from 'react';
import { FieldValues, UseControllerReturn } from 'react-hook-form';
import { ApiFormFieldType } from './ApiFormField';
dayjs.extend(customParseFormat);
export default function DateField({
controller,
definition
@ -18,13 +22,16 @@ export default function DateField({
fieldState: { error }
} = controller;
const valueFormat =
definition.field_type == 'date' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss';
const onChange = useCallback(
(value: any) => {
// Convert the returned date object to a string
if (value) {
value = value.toString();
let date = new Date(value);
value = date.toISOString().split('T')[0];
value = dayjs(value).format(valueFormat);
}
field.onChange(value);
@ -50,7 +57,7 @@ export default function DateField({
value={dateValue}
clearable={!definition.required}
onChange={onChange}
valueFormat="YYYY-MM-DD"
valueFormat={valueFormat}
label={definition.label}
description={definition.description}
placeholder={definition.placeholder}

View File

@ -1,5 +1,5 @@
import { Container, Flex, Space } from '@mantine/core';
import { Navigate, Outlet } from 'react-router-dom';
import { Navigate, Outlet, useLocation, useNavigate } from 'react-router-dom';
import { InvenTreeStyle } from '../../globalStyle';
import { useSessionState } from '../../states/SessionState';
@ -9,8 +9,12 @@ import { Header } from './Header';
export const ProtectedRoute = ({ children }: { children: JSX.Element }) => {
const [token] = useSessionState((state) => [state.token]);
const location = useLocation();
if (!token) {
return <Navigate to="/logged-in" replace />;
return (
<Navigate to="/logged-in" state={{ redirectFrom: location.pathname }} />
);
}
return children;

View File

@ -88,6 +88,7 @@ export function formatPriceRange(
interface renderDateOptionsType {
showTime?: boolean;
showSeconds?: boolean;
}
/*
@ -106,6 +107,9 @@ export function renderDate(date: string, options: renderDateOptionsType = {}) {
if (options.showTime) {
fmt += ' HH:mm';
if (options.showSeconds) {
fmt += ':ss';
}
}
const m = dayjs(date);

View File

@ -150,7 +150,9 @@ export function checkLoginState(
// Callback function when login fails
const loginFailure = () => {
useSessionState.getState().clearToken();
if (!no_redirect) navigate('/login');
if (!no_redirect) {
navigate('/login', { state: { redirectFrom: redirect } });
}
};
if (useSessionState.getState().hasToken()) {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More