mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge branch 'inventree:master' into pr/ChristianSchindler/6305
This commit is contained in:
commit
839d9f9075
@ -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
|
|
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@ -43,7 +43,9 @@ body:
|
|||||||
label: "Deployment Method"
|
label: "Deployment Method"
|
||||||
options:
|
options:
|
||||||
- label: "Docker"
|
- label: "Docker"
|
||||||
|
- label: "Package"
|
||||||
- label: "Bare metal"
|
- label: "Bare metal"
|
||||||
|
- label: "Other - added info in Steps to Reproduce"
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: version-info
|
id: version-info
|
||||||
validations:
|
validations:
|
||||||
|
2
.github/workflows/backport.yml
vendored
2
.github/workflows/backport.yml
vendored
@ -16,6 +16,8 @@ jobs:
|
|||||||
backport:
|
backport:
|
||||||
name: Backport PR
|
name: Backport PR
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
if: |
|
if: |
|
||||||
github.event.pull_request.merged == true
|
github.event.pull_request.merged == true
|
||||||
&& contains(github.event.pull_request.labels.*.name, 'backport')
|
&& contains(github.event.pull_request.labels.*.name, 'backport')
|
||||||
|
1
.github/workflows/check_translations.yaml
vendored
1
.github/workflows/check_translations.yaml
vendored
@ -27,6 +27,7 @@ jobs:
|
|||||||
INVENTREE_MEDIA_ROOT: ./media
|
INVENTREE_MEDIA_ROOT: ./media
|
||||||
INVENTREE_STATIC_ROOT: ./static
|
INVENTREE_STATIC_ROOT: ./static
|
||||||
INVENTREE_BACKUP_DIR: ./backup
|
INVENTREE_BACKUP_DIR: ./backup
|
||||||
|
INVENTREE_SITE_URL: http://localhost:8000
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
|
4
.github/workflows/docker.yaml
vendored
4
.github/workflows/docker.yaml
vendored
@ -76,8 +76,8 @@ jobs:
|
|||||||
python-version: ${{ env.python_version }}
|
python-version: ${{ env.python_version }}
|
||||||
- name: Version Check
|
- name: Version Check
|
||||||
run: |
|
run: |
|
||||||
pip install requests
|
pip install requests==2.31.0
|
||||||
pip install pyyaml
|
pip install pyyaml==6.0.1
|
||||||
python3 ci/version_check.py
|
python3 ci/version_check.py
|
||||||
echo "git_commit_hash=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
echo "git_commit_hash=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||||
echo "git_commit_date=$(git show -s --format=%ci)" >> $GITHUB_ENV
|
echo "git_commit_date=$(git show -s --format=%ci)" >> $GITHUB_ENV
|
||||||
|
8
.github/workflows/qc_checks.yaml
vendored
8
.github/workflows/qc_checks.yaml
vendored
@ -92,7 +92,7 @@ jobs:
|
|||||||
uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # pin@v3.0.1
|
uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # pin@v3.0.1
|
||||||
- name: Check Version
|
- name: Check Version
|
||||||
run: |
|
run: |
|
||||||
pip install requests
|
pip install requests==2.31.0
|
||||||
python3 ci/version_check.py
|
python3 ci/version_check.py
|
||||||
|
|
||||||
mkdocs:
|
mkdocs:
|
||||||
@ -110,7 +110,7 @@ jobs:
|
|||||||
python-version: ${{ env.python_version }}
|
python-version: ${{ env.python_version }}
|
||||||
- name: Check Config
|
- name: Check Config
|
||||||
run: |
|
run: |
|
||||||
pip install pyyaml
|
pip install pyyaml==6.0.1
|
||||||
pip install -r docs/requirements.txt
|
pip install -r docs/requirements.txt
|
||||||
python docs/ci/check_mkdocs_config.py
|
python docs/ci/check_mkdocs_config.py
|
||||||
- name: Check Links
|
- name: Check Links
|
||||||
@ -156,7 +156,7 @@ jobs:
|
|||||||
- name: Download public schema
|
- name: Download public schema
|
||||||
if: needs.paths-filter.outputs.api == 'false'
|
if: needs.paths-filter.outputs.api == 'false'
|
||||||
run: |
|
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)"
|
version="$(python3 ci/version_check.py only_version 2>&1)"
|
||||||
echo "Version: $version"
|
echo "Version: $version"
|
||||||
url="https://raw.githubusercontent.com/inventree/schema/main/export/${version}/api.yaml"
|
url="https://raw.githubusercontent.com/inventree/schema/main/export/${version}/api.yaml"
|
||||||
@ -175,7 +175,7 @@ jobs:
|
|||||||
id: version
|
id: version
|
||||||
if: github.ref == 'refs/heads/master' && needs.paths-filter.outputs.api == 'true'
|
if: github.ref == 'refs/heads/master' && needs.paths-filter.outputs.api == 'true'
|
||||||
run: |
|
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)"
|
version="$(python3 ci/version_check.py only_version 2>&1)"
|
||||||
echo "Version: $version"
|
echo "Version: $version"
|
||||||
echo "version=$version" >> "$GITHUB_OUTPUT"
|
echo "version=$version" >> "$GITHUB_OUTPUT"
|
||||||
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -19,7 +19,7 @@ jobs:
|
|||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1
|
||||||
- name: Version Check
|
- name: Version Check
|
||||||
run: |
|
run: |
|
||||||
pip install requests
|
pip install requests==2.31.0
|
||||||
python3 ci/version_check.py
|
python3 ci/version_check.py
|
||||||
- name: Push to Stable Branch
|
- name: Push to Stable Branch
|
||||||
uses: ad-m/github-push-action@d91a481090679876dfc4178fef17f286781251df # pin@v0.8.0
|
uses: ad-m/github-push-action@d91a481090679876dfc4178fef17f286781251df # pin@v0.8.0
|
||||||
|
2
.github/workflows/scorecard.yml
vendored
2
.github/workflows/scorecard.yml
vendored
@ -54,7 +54,7 @@ jobs:
|
|||||||
# For private repositories:
|
# For private repositories:
|
||||||
# - `publish_results` will always be set to `false`, regardless
|
# - `publish_results` will always be set to `false`, regardless
|
||||||
# of the value entered here.
|
# 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
|
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||||
# format to the repository Actions tab.
|
# format to the repository Actions tab.
|
||||||
|
4
.github/workflows/translations.yml
vendored
4
.github/workflows/translations.yml
vendored
@ -10,12 +10,14 @@ env:
|
|||||||
node_version: 18
|
node_version: 18
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
18
Dockerfile
18
Dockerfile
@ -10,7 +10,7 @@
|
|||||||
# - Monitors source files for any changes, and live-reloads server
|
# - Monitors source files for any changes, and live-reloads server
|
||||||
|
|
||||||
ARG base_image=python:3.11-alpine3.18
|
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
|
# Build arguments for this image
|
||||||
ARG commit_tag=""
|
ARG commit_tag=""
|
||||||
@ -92,7 +92,7 @@ RUN chmod +x init.sh
|
|||||||
|
|
||||||
ENTRYPOINT ["/bin/ash", "./init.sh"]
|
ENTRYPOINT ["/bin/ash", "./init.sh"]
|
||||||
|
|
||||||
FROM inventree_base as prebuild
|
FROM inventree_base AS prebuild
|
||||||
|
|
||||||
ENV PATH=/root/.local/bin:$PATH
|
ENV PATH=/root/.local/bin:$PATH
|
||||||
RUN ./install_build_packages.sh --no-cache --virtual .build-deps && \
|
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
|
apk --purge del .build-deps
|
||||||
|
|
||||||
# Frontend builder image:
|
# 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
|
RUN yarn config set network-timeout 600000 -g
|
||||||
COPY InvenTree ${INVENTREE_HOME}/InvenTree
|
COPY InvenTree ${INVENTREE_HOME}/InvenTree
|
||||||
COPY src ${INVENTREE_HOME}/src
|
COPY src ${INVENTREE_HOME}/src
|
||||||
@ -117,7 +117,7 @@ COPY db_version ${INVENTREE_HOME}/db_version
|
|||||||
# InvenTree production image:
|
# InvenTree production image:
|
||||||
# - Copies required files from local directory
|
# - Copies required files from local directory
|
||||||
# - Starts a gunicorn webserver
|
# - Starts a gunicorn webserver
|
||||||
FROM inventree_base as production
|
FROM inventree_base AS production
|
||||||
|
|
||||||
ENV INVENTREE_DEBUG=False
|
ENV INVENTREE_DEBUG=False
|
||||||
|
|
||||||
@ -134,11 +134,9 @@ COPY InvenTree ./InvenTree
|
|||||||
COPY --from=frontend ${INVENTREE_HOME}/InvenTree/web/static/web ./InvenTree/web/static/web
|
COPY --from=frontend ${INVENTREE_HOME}/InvenTree/web/static/web ./InvenTree/web/static/web
|
||||||
|
|
||||||
# Launch the production server
|
# 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
|
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)
|
# Vite server (for local frontend development)
|
||||||
EXPOSE 5173
|
EXPOSE 5173
|
||||||
@ -146,11 +144,11 @@ EXPOSE 5173
|
|||||||
# Install packages required for building python packages
|
# Install packages required for building python packages
|
||||||
RUN ./install_build_packages.sh
|
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
|
# 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
|
RUN yarn config set network-timeout 600000 -g
|
||||||
|
|
||||||
# The development image requires the source code to be mounted to /home/inventree/
|
# The development image requires the source code to be mounted to /home/inventree/
|
||||||
|
@ -347,7 +347,7 @@ def get_secret_key():
|
|||||||
|
|
||||||
# Create a random key file
|
# Create a random key file
|
||||||
options = string.digits + string.ascii_letters + string.punctuation
|
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)
|
secret_key_file.write_text(key)
|
||||||
|
|
||||||
logger.debug("Loading SECRET_KEY from '%s'", secret_key_file)
|
logger.debug("Loading SECRET_KEY from '%s'", secret_key_file)
|
||||||
|
@ -95,7 +95,7 @@ def from_engineering_notation(value):
|
|||||||
"""
|
"""
|
||||||
value = str(value).strip()
|
value = str(value).strip()
|
||||||
|
|
||||||
pattern = f'(\d+)([a-zA-Z]+)(\d+)(.*)'
|
pattern = '(\d+)([a-zA-Z]+)(\d+)(.*)'
|
||||||
|
|
||||||
if match := re.match(pattern, value):
|
if match := re.match(pattern, value):
|
||||||
left, prefix, right, suffix = match.groups()
|
left, prefix, right, suffix = match.groups()
|
||||||
@ -198,7 +198,6 @@ def convert_physical_value(value: str, unit: str = None, strip_units=True):
|
|||||||
break
|
break
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
value = None
|
value = None
|
||||||
pass
|
|
||||||
|
|
||||||
if value is None:
|
if value is None:
|
||||||
if unit:
|
if unit:
|
||||||
|
@ -20,7 +20,7 @@ class InvenTreeExchange(SimpleExchangeBackend):
|
|||||||
|
|
||||||
name = 'InvenTreeExchange'
|
name = 'InvenTreeExchange'
|
||||||
|
|
||||||
def get_rates(self, **kwargs) -> None:
|
def get_rates(self, **kwargs) -> dict:
|
||||||
"""Set the requested currency codes and get rates."""
|
"""Set the requested currency codes and get rates."""
|
||||||
from common.models import InvenTreeSetting
|
from common.models import InvenTreeSetting
|
||||||
from plugin import registry
|
from plugin import registry
|
||||||
|
@ -17,11 +17,10 @@ class InvenTreeDateFilter(rest_filters.DateFilter):
|
|||||||
|
|
||||||
def filter(self, qs, value):
|
def filter(self, qs, value):
|
||||||
"""Override the filter method to handle timezones correctly."""
|
"""Override the filter method to handle timezones correctly."""
|
||||||
if settings.USE_TZ:
|
if settings.USE_TZ and value is not None:
|
||||||
if value is not None:
|
tz = timezone.get_current_timezone()
|
||||||
tz = timezone.get_current_timezone()
|
value = datetime(value.year, value.month, value.day)
|
||||||
value = datetime(value.year, value.month, value.day)
|
value = make_aware(value, tz, True)
|
||||||
value = make_aware(value, tz, True)
|
|
||||||
|
|
||||||
return super().filter(qs, value)
|
return super().filter(qs, value)
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ def construct_format_regex(fmt_string: str) -> str:
|
|||||||
for group in string.Formatter().parse(fmt_string):
|
for group in string.Formatter().parse(fmt_string):
|
||||||
prefix = group[0] # Prefix (literal text appearing before this group)
|
prefix = group[0] # Prefix (literal text appearing before this group)
|
||||||
name = group[1] # Name of this format variable
|
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 = [
|
rep = [
|
||||||
'+',
|
'+',
|
||||||
@ -106,16 +106,16 @@ def construct_format_regex(fmt_string: str) -> str:
|
|||||||
# Add a named capture group for the format entry
|
# Add a named capture group for the format entry
|
||||||
if name:
|
if name:
|
||||||
# Check if integer values are required
|
# Check if integer values are required
|
||||||
if format.endswith('d'):
|
if _fmt.endswith('d'):
|
||||||
chr = '\d'
|
c = '\d'
|
||||||
else:
|
else:
|
||||||
chr = '.'
|
c = '.'
|
||||||
|
|
||||||
# Specify width
|
# Specify width
|
||||||
# TODO: Introspect required width
|
# TODO: Introspect required width
|
||||||
w = '+'
|
w = '+'
|
||||||
|
|
||||||
pattern += f'(?P<{name}>{chr}{w})'
|
pattern += f'(?P<{name}>{c}{w})'
|
||||||
|
|
||||||
pattern += '$'
|
pattern += '$'
|
||||||
|
|
||||||
|
@ -248,11 +248,7 @@ def str2int(text, default=None):
|
|||||||
|
|
||||||
def is_bool(text):
|
def is_bool(text):
|
||||||
"""Determine if a string value 'looks' like a boolean."""
|
"""Determine if a string value 'looks' like a boolean."""
|
||||||
if str2bool(text, True):
|
return str2bool(text, True) or str2bool(text, False)
|
||||||
return True
|
|
||||||
elif str2bool(text, False):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def isNull(text):
|
def isNull(text):
|
||||||
@ -473,7 +469,7 @@ def DownloadFile(
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
def increment_serial_number(serial: str):
|
def increment_serial_number(serial):
|
||||||
"""Given a serial number, (attempt to) generate the *next* serial number.
|
"""Given a serial number, (attempt to) generate the *next* serial number.
|
||||||
|
|
||||||
Note: This method is exposed to custom plugins.
|
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 = str(barcode_data).strip()
|
||||||
barcode_data = remove_non_printable_characters(barcode_data)
|
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):
|
def hash_file(filename: Union[str, Path], storage: Union[Storage, None] = None):
|
||||||
|
@ -7,7 +7,7 @@ from rest_framework import permissions
|
|||||||
import users.models
|
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."""
|
"""Attempt to introspect the 'model' type for an API view."""
|
||||||
if hasattr(view, 'get_permission_model'):
|
if hasattr(view, 'get_permission_model'):
|
||||||
return view.get_permission_model()
|
return view.get_permission_model()
|
||||||
|
@ -426,17 +426,17 @@ TEMPLATES = [
|
|||||||
REST_FRAMEWORK = {
|
REST_FRAMEWORK = {
|
||||||
'EXCEPTION_HANDLER': 'InvenTree.exceptions.exception_handler',
|
'EXCEPTION_HANDLER': 'InvenTree.exceptions.exception_handler',
|
||||||
'DATETIME_FORMAT': '%Y-%m-%d %H:%M',
|
'DATETIME_FORMAT': '%Y-%m-%d %H:%M',
|
||||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
'DEFAULT_AUTHENTICATION_CLASSES': [
|
||||||
'users.authentication.ApiTokenAuthentication',
|
'users.authentication.ApiTokenAuthentication',
|
||||||
'rest_framework.authentication.BasicAuthentication',
|
'rest_framework.authentication.BasicAuthentication',
|
||||||
'rest_framework.authentication.SessionAuthentication',
|
'rest_framework.authentication.SessionAuthentication',
|
||||||
),
|
],
|
||||||
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
|
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
|
||||||
'DEFAULT_PERMISSION_CLASSES': (
|
'DEFAULT_PERMISSION_CLASSES': [
|
||||||
'rest_framework.permissions.IsAuthenticated',
|
'rest_framework.permissions.IsAuthenticated',
|
||||||
'rest_framework.permissions.DjangoModelPermissions',
|
'rest_framework.permissions.DjangoModelPermissions',
|
||||||
'InvenTree.permissions.RolePermission',
|
'InvenTree.permissions.RolePermission',
|
||||||
),
|
],
|
||||||
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
|
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
|
||||||
'DEFAULT_METADATA_CLASS': 'InvenTree.metadata.InvenTreeMetadata',
|
'DEFAULT_METADATA_CLASS': 'InvenTree.metadata.InvenTreeMetadata',
|
||||||
'DEFAULT_RENDERER_CLASSES': ['rest_framework.renderers.JSONRenderer'],
|
'DEFAULT_RENDERER_CLASSES': ['rest_framework.renderers.JSONRenderer'],
|
||||||
@ -462,8 +462,8 @@ REST_AUTH_REGISTER_SERIALIZERS = {
|
|||||||
if USE_JWT:
|
if USE_JWT:
|
||||||
JWT_AUTH_COOKIE = 'inventree-auth'
|
JWT_AUTH_COOKIE = 'inventree-auth'
|
||||||
JWT_AUTH_REFRESH_COOKIE = 'inventree-token'
|
JWT_AUTH_REFRESH_COOKIE = 'inventree-token'
|
||||||
REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'] + (
|
REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'].append(
|
||||||
'dj_rest_auth.jwt_auth.JWTCookieAuthentication',
|
'dj_rest_auth.jwt_auth.JWTCookieAuthentication'
|
||||||
)
|
)
|
||||||
INSTALLED_APPS.append('rest_framework_simplejwt')
|
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
|
# Ensure that the ALLOWED_HOSTS do not contain any scheme info
|
||||||
for i, host in enumerate(ALLOWED_HOSTS):
|
for i, host in enumerate(ALLOWED_HOSTS):
|
||||||
if '://' in host:
|
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
|
# List of trusted origins for unsafe requests
|
||||||
# Ref: https://docs.djangoproject.com/en/4.2/ref/settings/#csrf-trusted-origins
|
# Ref: https://docs.djangoproject.com/en/4.2/ref/settings/#csrf-trusted-origins
|
||||||
|
@ -85,7 +85,7 @@ for name, provider in providers.registry.provider_map.items():
|
|||||||
cls
|
cls
|
||||||
for cls in prov_mod.__dict__.values()
|
for cls in prov_mod.__dict__.values()
|
||||||
if isinstance(cls, type)
|
if isinstance(cls, type)
|
||||||
and not cls == OAuth2Adapter
|
and cls != OAuth2Adapter
|
||||||
and issubclass(cls, OAuth2Adapter)
|
and issubclass(cls, OAuth2Adapter)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ def get_provider_app(provider):
|
|||||||
return apps.first()
|
return apps.first()
|
||||||
|
|
||||||
|
|
||||||
def check_provider(provider, raise_error=False):
|
def check_provider(provider):
|
||||||
"""Check if the given provider is correctly configured.
|
"""Check if the given provider is correctly configured.
|
||||||
|
|
||||||
To be correctly configured, the following must be true:
|
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
@ -379,11 +379,11 @@ class BomItemResource(InvenTreeResource):
|
|||||||
report_skipped = False
|
report_skipped = False
|
||||||
clean_model_instances = True
|
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)
|
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()
|
attribute='pk', column_name=_('BOM Item ID'), widget=widgets.IntegerWidget()
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -476,7 +476,6 @@ class BomItemResource(InvenTreeResource):
|
|||||||
if is_importing:
|
if is_importing:
|
||||||
to_remove += [
|
to_remove += [
|
||||||
'level',
|
'level',
|
||||||
'pk',
|
|
||||||
'part',
|
'part',
|
||||||
'part__IPN',
|
'part__IPN',
|
||||||
'part__name',
|
'part__name',
|
||||||
|
@ -267,6 +267,8 @@ class CategoryDetail(CategoryMixin, CustomRetrieveUpdateDestroyAPI):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
kwargs.setdefault('context', self.get_serializer_context())
|
||||||
|
|
||||||
return self.serializer_class(*args, **kwargs)
|
return self.serializer_class(*args, **kwargs)
|
||||||
|
|
||||||
def update(self, request, *args, **kwargs):
|
def update(self, request, *args, **kwargs):
|
||||||
|
@ -30,7 +30,7 @@ class TMEPlugin(SupplierBarcodeMixin, SettingsMixin, InvenTreePlugin):
|
|||||||
}
|
}
|
||||||
|
|
||||||
TME_IS_QRCODE_REGEX = re.compile(r'([^\s:]+:[^\s:]+\s+)+(\S+(\s|$)+)+')
|
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
|
# Custom field mapping
|
||||||
TME_QRCODE_FIELDS = {
|
TME_QRCODE_FIELDS = {
|
||||||
@ -52,22 +52,19 @@ class TMEPlugin(SupplierBarcodeMixin, SettingsMixin, InvenTreePlugin):
|
|||||||
key, value = item.split(':')
|
key, value = item.split(':')
|
||||||
if key in self.TME_QRCODE_FIELDS:
|
if key in self.TME_QRCODE_FIELDS:
|
||||||
barcode_fields[self.TME_QRCODE_FIELDS[key]] = value
|
barcode_fields[self.TME_QRCODE_FIELDS[key]] = value
|
||||||
|
elif self.TME_IS_OLD_BARCODE2D_REGEX.fullmatch(barcode_data):
|
||||||
return barcode_fields
|
# Old 2D Barcode format e.g. "PWBP-302 1PMPNWBP-302 Q1 K19361337/1"
|
||||||
|
|
||||||
elif self.TME_IS_BARCODE2D_REGEX.fullmatch(barcode_data):
|
|
||||||
# 2D Barcode format e.g. "PWBP-302 1PMPNWBP-302 Q1 K19361337/1"
|
|
||||||
for item in barcode_data.split(' '):
|
for item in barcode_data.split(' '):
|
||||||
for k, v in self.ecia_field_map().items():
|
for k, v in self.ecia_field_map().items():
|
||||||
if item.startswith(k):
|
if item.startswith(k):
|
||||||
barcode_fields[v] = item[len(k) :]
|
barcode_fields[v] = item[len(k) :]
|
||||||
else:
|
else:
|
||||||
return {}
|
barcode_fields = self.parse_ecia_barcode2d(barcode_data)
|
||||||
|
|
||||||
# Custom handling for order number
|
# Custom handling for order number
|
||||||
if SupplierBarcodeMixin.CUSTOMER_ORDER_NUMBER in barcode_fields:
|
if SupplierBarcodeMixin.SUPPLIER_ORDER_NUMBER in barcode_fields:
|
||||||
order_number = barcode_fields[SupplierBarcodeMixin.CUSTOMER_ORDER_NUMBER]
|
order_number = barcode_fields[SupplierBarcodeMixin.SUPPLIER_ORDER_NUMBER]
|
||||||
order_number = order_number.split('/')[0]
|
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
|
return barcode_fields
|
||||||
|
@ -13,12 +13,12 @@ class Migration(migrations.Migration):
|
|||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='stockitemtestresult',
|
model_name='stockitemtestresult',
|
||||||
name='finished_datetime',
|
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(
|
migrations.AddField(
|
||||||
model_name='stockitemtestresult',
|
model_name='stockitemtestresult',
|
||||||
name='started_datetime',
|
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(
|
migrations.AddField(
|
||||||
model_name='stockitemtestresult',
|
model_name='stockitemtestresult',
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
[![OpenSSF Best Practices](https://bestpractices.coreinfrastructure.org/projects/7179/badge)](https://bestpractices.coreinfrastructure.org/projects/7179)
|
[![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)
|
[![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)
|
[![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)
|
[![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)
|
[![Crowdin](https://badges.crowdin.net/inventree/localized.svg)](https://crowdin.com/project/inventree)
|
||||||
|
@ -90,7 +90,7 @@ function detect_envs() {
|
|||||||
echo "# Using existing config file: ${INVENTREE_CONFIG_FILE}"
|
echo "# Using existing config file: ${INVENTREE_CONFIG_FILE}"
|
||||||
|
|
||||||
# Install parser
|
# Install parser
|
||||||
pip install jc -q
|
pip install jc==1.25.2 -q
|
||||||
|
|
||||||
# Load config
|
# Load config
|
||||||
local CONF=$(cat ${INVENTREE_CONFIG_FILE} | jc --yaml)
|
local CONF=$(cat ${INVENTREE_CONFIG_FILE} | jc --yaml)
|
||||||
|
@ -45,6 +45,8 @@
|
|||||||
root * /var/www/media
|
root * /var/www/media
|
||||||
file_server
|
file_server
|
||||||
|
|
||||||
|
header Content-Disposition attachment
|
||||||
|
|
||||||
forward_auth {$INVENTREE_SERVER:"http://inventree-server:8000"} {
|
forward_auth {$INVENTREE_SERVER:"http://inventree-server:8000"} {
|
||||||
uri /auth/
|
uri /auth/
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ pip==24.0
|
|||||||
pip-tools==7.4.1
|
pip-tools==7.4.1
|
||||||
platformdirs==4.2.0
|
platformdirs==4.2.0
|
||||||
# via virtualenv
|
# via virtualenv
|
||||||
pre-commit==3.6.2
|
pre-commit==3.7.0
|
||||||
pycparser==2.21
|
pycparser==2.21
|
||||||
# via cffi
|
# via cffi
|
||||||
pyproject-hooks==1.0.0
|
pyproject-hooks==1.0.0
|
||||||
|
@ -23,27 +23,27 @@
|
|||||||
"@mantine/core": "<7",
|
"@mantine/core": "<7",
|
||||||
"@mantine/dates": "<7",
|
"@mantine/dates": "<7",
|
||||||
"@mantine/dropzone": "<7",
|
"@mantine/dropzone": "<7",
|
||||||
"@mantine/form": "<7",
|
"@mantine/form": "<8",
|
||||||
"@mantine/hooks": "<7",
|
"@mantine/hooks": "<7",
|
||||||
"@mantine/modals": "<7",
|
"@mantine/modals": "<7",
|
||||||
"@mantine/notifications": "<7",
|
"@mantine/notifications": "<7",
|
||||||
"@naisutech/react-tree": "^3.1.0",
|
"@naisutech/react-tree": "^3.1.0",
|
||||||
"@sentry/react": "^7.108.0",
|
"@sentry/react": "^7.109.0",
|
||||||
"@tabler/icons-react": "^3.1.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/codemirror-theme-vscode": "^4.21.25",
|
||||||
"@uiw/react-codemirror": "^4.21.25",
|
"@uiw/react-codemirror": "^4.21.25",
|
||||||
"@uiw/react-split": "^5.9.3",
|
"@uiw/react-split": "^5.9.3",
|
||||||
"axios": "^1.6.7",
|
"axios": "^1.6.7",
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
"easymde": "^2.18.0",
|
"easymde": "^2.18.0",
|
||||||
"embla-carousel-react": "^7.1.0",
|
"embla-carousel-react": "^8.0.0",
|
||||||
"html5-qrcode": "^2.3.8",
|
"html5-qrcode": "^2.3.8",
|
||||||
"mantine-datatable": "<7",
|
"mantine-datatable": "<7",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-grid-layout": "^1.4.4",
|
"react-grid-layout": "^1.4.4",
|
||||||
"react-hook-form": "^7.51.1",
|
"react-hook-form": "^7.51.2",
|
||||||
"react-is": "^18.2.0",
|
"react-is": "^18.2.0",
|
||||||
"react-router-dom": "^6.22.1",
|
"react-router-dom": "^6.22.1",
|
||||||
"react-select": "^5.8.0",
|
"react-select": "^5.8.0",
|
||||||
@ -58,15 +58,15 @@
|
|||||||
"@lingui/cli": "^4.7.2",
|
"@lingui/cli": "^4.7.2",
|
||||||
"@lingui/macro": "^4.7.2",
|
"@lingui/macro": "^4.7.2",
|
||||||
"@playwright/test": "^1.41.2",
|
"@playwright/test": "^1.41.2",
|
||||||
"@types/node": "^20.11.20",
|
"@types/node": "^20.12.2",
|
||||||
"@types/react": "^18.2.71",
|
"@types/react": "^18.2.73",
|
||||||
"@types/react-dom": "^18.2.19",
|
"@types/react-dom": "^18.2.23",
|
||||||
"@types/react-grid-layout": "^1.3.5",
|
"@types/react-grid-layout": "^1.3.5",
|
||||||
"@types/react-router-dom": "^5.3.3",
|
"@types/react-router-dom": "^5.3.3",
|
||||||
"@vitejs/plugin-react": "^4.2.1",
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
"babel-plugin-macros": "^3.1.0",
|
"babel-plugin-macros": "^3.1.0",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"vite": "^5.2.6",
|
"vite": "^5.2.7",
|
||||||
"vite-plugin-babel-macros": "^1.0.6"
|
"vite-plugin-babel-macros": "^1.0.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ import { useDisclosure } from '@mantine/hooks';
|
|||||||
import { notifications } from '@mantine/notifications';
|
import { notifications } from '@mantine/notifications';
|
||||||
import { IconCheck } from '@tabler/icons-react';
|
import { IconCheck } from '@tabler/icons-react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import { api } from '../../App';
|
import { api } from '../../App';
|
||||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||||
@ -32,6 +32,7 @@ export function AuthenticationForm() {
|
|||||||
const [classicLoginMode, setMode] = useDisclosure(true);
|
const [classicLoginMode, setMode] = useDisclosure(true);
|
||||||
const [auth_settings] = useServerApiState((state) => [state.auth_settings]);
|
const [auth_settings] = useServerApiState((state) => [state.auth_settings]);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
const [isLoggingIn, setIsLoggingIn] = useState<boolean>(false);
|
const [isLoggingIn, setIsLoggingIn] = useState<boolean>(false);
|
||||||
|
|
||||||
@ -52,7 +53,7 @@ export function AuthenticationForm() {
|
|||||||
color: 'green',
|
color: 'green',
|
||||||
icon: <IconCheck size="1rem" />
|
icon: <IconCheck size="1rem" />
|
||||||
});
|
});
|
||||||
navigate('/home');
|
navigate(location?.state?.redirectFrom ?? '/home');
|
||||||
} else {
|
} else {
|
||||||
notifications.show({
|
notifications.show({
|
||||||
title: t`Login failed`,
|
title: t`Login failed`,
|
||||||
|
@ -64,6 +64,7 @@ export type ApiFormFieldType = {
|
|||||||
| 'string'
|
| 'string'
|
||||||
| 'boolean'
|
| 'boolean'
|
||||||
| 'date'
|
| 'date'
|
||||||
|
| 'datetime'
|
||||||
| 'integer'
|
| 'integer'
|
||||||
| 'decimal'
|
| 'decimal'
|
||||||
| 'float'
|
| 'float'
|
||||||
@ -215,6 +216,7 @@ export function ApiFormField({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
case 'date':
|
case 'date':
|
||||||
|
case 'datetime':
|
||||||
return <DateField controller={controller} definition={definition} />;
|
return <DateField controller={controller} definition={definition} />;
|
||||||
case 'integer':
|
case 'integer':
|
||||||
case 'decimal':
|
case 'decimal':
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
import { DateInput } from '@mantine/dates';
|
import { DateInput } from '@mantine/dates';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import customParseFormat from 'dayjs/plugin/customParseFormat';
|
||||||
import { useCallback, useId, useMemo } from 'react';
|
import { useCallback, useId, useMemo } from 'react';
|
||||||
import { FieldValues, UseControllerReturn } from 'react-hook-form';
|
import { FieldValues, UseControllerReturn } from 'react-hook-form';
|
||||||
|
|
||||||
import { ApiFormFieldType } from './ApiFormField';
|
import { ApiFormFieldType } from './ApiFormField';
|
||||||
|
|
||||||
|
dayjs.extend(customParseFormat);
|
||||||
|
|
||||||
export default function DateField({
|
export default function DateField({
|
||||||
controller,
|
controller,
|
||||||
definition
|
definition
|
||||||
@ -18,13 +22,16 @@ export default function DateField({
|
|||||||
fieldState: { error }
|
fieldState: { error }
|
||||||
} = controller;
|
} = controller;
|
||||||
|
|
||||||
|
const valueFormat =
|
||||||
|
definition.field_type == 'date' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss';
|
||||||
|
|
||||||
const onChange = useCallback(
|
const onChange = useCallback(
|
||||||
(value: any) => {
|
(value: any) => {
|
||||||
// Convert the returned date object to a string
|
// Convert the returned date object to a string
|
||||||
if (value) {
|
if (value) {
|
||||||
value = value.toString();
|
value = value.toString();
|
||||||
let date = new Date(value);
|
let date = new Date(value);
|
||||||
value = date.toISOString().split('T')[0];
|
value = dayjs(value).format(valueFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
field.onChange(value);
|
field.onChange(value);
|
||||||
@ -50,7 +57,7 @@ export default function DateField({
|
|||||||
value={dateValue}
|
value={dateValue}
|
||||||
clearable={!definition.required}
|
clearable={!definition.required}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
valueFormat="YYYY-MM-DD"
|
valueFormat={valueFormat}
|
||||||
label={definition.label}
|
label={definition.label}
|
||||||
description={definition.description}
|
description={definition.description}
|
||||||
placeholder={definition.placeholder}
|
placeholder={definition.placeholder}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Container, Flex, Space } from '@mantine/core';
|
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 { InvenTreeStyle } from '../../globalStyle';
|
||||||
import { useSessionState } from '../../states/SessionState';
|
import { useSessionState } from '../../states/SessionState';
|
||||||
@ -9,8 +9,12 @@ import { Header } from './Header';
|
|||||||
export const ProtectedRoute = ({ children }: { children: JSX.Element }) => {
|
export const ProtectedRoute = ({ children }: { children: JSX.Element }) => {
|
||||||
const [token] = useSessionState((state) => [state.token]);
|
const [token] = useSessionState((state) => [state.token]);
|
||||||
|
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
return <Navigate to="/logged-in" replace />;
|
return (
|
||||||
|
<Navigate to="/logged-in" state={{ redirectFrom: location.pathname }} />
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return children;
|
return children;
|
||||||
|
@ -88,6 +88,7 @@ export function formatPriceRange(
|
|||||||
|
|
||||||
interface renderDateOptionsType {
|
interface renderDateOptionsType {
|
||||||
showTime?: boolean;
|
showTime?: boolean;
|
||||||
|
showSeconds?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -106,6 +107,9 @@ export function renderDate(date: string, options: renderDateOptionsType = {}) {
|
|||||||
|
|
||||||
if (options.showTime) {
|
if (options.showTime) {
|
||||||
fmt += ' HH:mm';
|
fmt += ' HH:mm';
|
||||||
|
if (options.showSeconds) {
|
||||||
|
fmt += ':ss';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const m = dayjs(date);
|
const m = dayjs(date);
|
||||||
|
@ -150,7 +150,9 @@ export function checkLoginState(
|
|||||||
// Callback function when login fails
|
// Callback function when login fails
|
||||||
const loginFailure = () => {
|
const loginFailure = () => {
|
||||||
useSessionState.getState().clearToken();
|
useSessionState.getState().clearToken();
|
||||||
if (!no_redirect) navigate('/login');
|
if (!no_redirect) {
|
||||||
|
navigate('/login', { state: { redirectFrom: redirect } });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (useSessionState.getState().hasToken()) {
|
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
Loading…
Reference in New Issue
Block a user