mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-28 11:36:44 +00:00
feat: Re-Implement customize options (#8969)
* Extend api to also include customize functions * [FR] Re-Implement customize options Fixes #8818 * re-implement header * add splashscreen customisation * make simpler * fix rendering * bump api
This commit is contained in:
parent
e75ceb0719
commit
cfa248aad9
@ -20,6 +20,7 @@ from rest_framework.views import APIView
|
|||||||
|
|
||||||
import InvenTree.version
|
import InvenTree.version
|
||||||
import users.models
|
import users.models
|
||||||
|
from InvenTree import helpers
|
||||||
from InvenTree.mixins import ListCreateAPI
|
from InvenTree.mixins import ListCreateAPI
|
||||||
from InvenTree.templatetags.inventree_extras import plugins_info
|
from InvenTree.templatetags.inventree_extras import plugins_info
|
||||||
from part.models import Part
|
from part.models import Part
|
||||||
@ -198,6 +199,14 @@ class VersionTextView(ListAPI):
|
|||||||
class InfoApiSerializer(serializers.Serializer):
|
class InfoApiSerializer(serializers.Serializer):
|
||||||
"""InvenTree server information - some information might be blanked if called without elevated credentials."""
|
"""InvenTree server information - some information might be blanked if called without elevated credentials."""
|
||||||
|
|
||||||
|
class CustomizeSerializer(serializers.Serializer):
|
||||||
|
"""Serializer for customize field."""
|
||||||
|
|
||||||
|
logo = serializers.CharField()
|
||||||
|
splash = serializers.CharField()
|
||||||
|
login_message = serializers.CharField()
|
||||||
|
navbar_message = serializers
|
||||||
|
|
||||||
server = serializers.CharField(read_only=True)
|
server = serializers.CharField(read_only=True)
|
||||||
version = serializers.CharField(read_only=True)
|
version = serializers.CharField(read_only=True)
|
||||||
instance = serializers.CharField(read_only=True)
|
instance = serializers.CharField(read_only=True)
|
||||||
@ -214,6 +223,7 @@ class InfoApiSerializer(serializers.Serializer):
|
|||||||
default_locale = serializers.ChoiceField(
|
default_locale = serializers.ChoiceField(
|
||||||
choices=settings.LOCALE_CODES, read_only=True
|
choices=settings.LOCALE_CODES, read_only=True
|
||||||
)
|
)
|
||||||
|
customize = CustomizeSerializer(read_only=True)
|
||||||
system_health = serializers.BooleanField(read_only=True)
|
system_health = serializers.BooleanField(read_only=True)
|
||||||
database = serializers.CharField(read_only=True)
|
database = serializers.CharField(read_only=True)
|
||||||
platform = serializers.CharField(read_only=True)
|
platform = serializers.CharField(read_only=True)
|
||||||
@ -263,6 +273,12 @@ class InfoView(APIView):
|
|||||||
'debug_mode': settings.DEBUG,
|
'debug_mode': settings.DEBUG,
|
||||||
'docker_mode': settings.DOCKER,
|
'docker_mode': settings.DOCKER,
|
||||||
'default_locale': settings.LANGUAGE_CODE,
|
'default_locale': settings.LANGUAGE_CODE,
|
||||||
|
'customize': {
|
||||||
|
'logo': helpers.getLogoImage(),
|
||||||
|
'splash': helpers.getSplashScreen(),
|
||||||
|
'login_message': helpers.getCustomOption('login_message'),
|
||||||
|
'navbar_message': helpers.getCustomOption('navbar_message'),
|
||||||
|
},
|
||||||
# Following fields are only available to staff users
|
# Following fields are only available to staff users
|
||||||
'system_health': check_system_health() if is_staff else None,
|
'system_health': check_system_health() if is_staff else None,
|
||||||
'database': InvenTree.version.inventreeDatabase() if is_staff else None,
|
'database': InvenTree.version.inventreeDatabase() if is_staff else None,
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
"""InvenTree API version information."""
|
"""InvenTree API version information."""
|
||||||
|
|
||||||
# InvenTree API version
|
# InvenTree API version
|
||||||
INVENTREE_API_VERSION = 306
|
INVENTREE_API_VERSION = 307
|
||||||
|
|
||||||
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
||||||
|
|
||||||
|
|
||||||
INVENTREE_API_TEXT = """
|
INVENTREE_API_TEXT = """
|
||||||
|
|
||||||
|
v307 - 2025-01-29 : https://github.com/inventree/InvenTree/pull/8969
|
||||||
|
- Extend Info Endpoint to include customizations
|
||||||
|
|
||||||
v306 - 2025-01-28 : https://github.com/inventree/InvenTree/pull/8966
|
v306 - 2025-01-28 : https://github.com/inventree/InvenTree/pull/8966
|
||||||
- Adds "start_date" to PurchasesOrder API
|
- Adds "start_date" to PurchasesOrder API
|
||||||
- Adds "start_date" to SalesOrder API
|
- Adds "start_date" to SalesOrder API
|
||||||
|
@ -193,6 +193,15 @@ def getSplashScreen(custom=True):
|
|||||||
return static_storage.url('img/inventree_splash.jpg')
|
return static_storage.url('img/inventree_splash.jpg')
|
||||||
|
|
||||||
|
|
||||||
|
def getCustomOption(reference: str):
|
||||||
|
"""Return the value of a custom option from settings.CUSTOMIZE.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
reference: Reference key for the custom option
|
||||||
|
"""
|
||||||
|
return settings.CUSTOMIZE.get(reference, None)
|
||||||
|
|
||||||
|
|
||||||
def TestIfImageURL(url):
|
def TestIfImageURL(url):
|
||||||
"""Test if an image URL (or filename) looks like a valid image format.
|
"""Test if an image URL (or filename) looks like a valid image format.
|
||||||
|
|
||||||
|
@ -1,4 +1,11 @@
|
|||||||
import { ActionIcon, Container, Group, Indicator, Tabs } from '@mantine/core';
|
import {
|
||||||
|
ActionIcon,
|
||||||
|
Container,
|
||||||
|
Group,
|
||||||
|
Indicator,
|
||||||
|
Tabs,
|
||||||
|
Text
|
||||||
|
} from '@mantine/core';
|
||||||
import { useDisclosure } from '@mantine/hooks';
|
import { useDisclosure } from '@mantine/hooks';
|
||||||
import { IconBell, IconSearch } from '@tabler/icons-react';
|
import { IconBell, IconSearch } from '@tabler/icons-react';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
@ -10,7 +17,7 @@ import { navTabs as mainNavTabs } from '../../defaults/links';
|
|||||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||||
import { navigateToLink } from '../../functions/navigation';
|
import { navigateToLink } from '../../functions/navigation';
|
||||||
import * as classes from '../../main.css';
|
import * as classes from '../../main.css';
|
||||||
import { apiUrl } from '../../states/ApiState';
|
import { apiUrl, useServerApiState } from '../../states/ApiState';
|
||||||
import { useLocalState } from '../../states/LocalState';
|
import { useLocalState } from '../../states/LocalState';
|
||||||
import { useGlobalSettingsState } from '../../states/SettingsState';
|
import { useGlobalSettingsState } from '../../states/SettingsState';
|
||||||
import { useUserState } from '../../states/UserState';
|
import { useUserState } from '../../states/UserState';
|
||||||
@ -27,6 +34,7 @@ export function Header() {
|
|||||||
state.setNavigationOpen,
|
state.setNavigationOpen,
|
||||||
state.navigationOpen
|
state.navigationOpen
|
||||||
]);
|
]);
|
||||||
|
const [server] = useServerApiState((state) => [state.server]);
|
||||||
const [navDrawerOpened, { open: openNavDrawer, close: closeNavDrawer }] =
|
const [navDrawerOpened, { open: openNavDrawer, close: closeNavDrawer }] =
|
||||||
useDisclosure(navigationOpen);
|
useDisclosure(navigationOpen);
|
||||||
const [
|
const [
|
||||||
@ -40,11 +48,13 @@ export function Header() {
|
|||||||
] = useDisclosure(false);
|
] = useDisclosure(false);
|
||||||
|
|
||||||
const { isLoggedIn } = useUserState();
|
const { isLoggedIn } = useUserState();
|
||||||
|
|
||||||
const [notificationCount, setNotificationCount] = useState<number>(0);
|
const [notificationCount, setNotificationCount] = useState<number>(0);
|
||||||
|
|
||||||
const globalSettings = useGlobalSettingsState();
|
const globalSettings = useGlobalSettingsState();
|
||||||
|
|
||||||
|
const navbar_message = useMemo(() => {
|
||||||
|
return server.customize?.navbar_message;
|
||||||
|
}, [server.customize]);
|
||||||
|
|
||||||
// Fetch number of notifications for the current user
|
// Fetch number of notifications for the current user
|
||||||
const notifications = useQuery({
|
const notifications = useQuery({
|
||||||
queryKey: ['notification-count'],
|
queryKey: ['notification-count'],
|
||||||
@ -105,6 +115,12 @@ export function Header() {
|
|||||||
<NavHoverMenu openDrawer={openNavDrawer} />
|
<NavHoverMenu openDrawer={openNavDrawer} />
|
||||||
<NavTabs />
|
<NavTabs />
|
||||||
</Group>
|
</Group>
|
||||||
|
{navbar_message && (
|
||||||
|
<Text>
|
||||||
|
{/* biome-ignore lint/security/noDangerouslySetInnerHtml: <explanation> */}
|
||||||
|
<div dangerouslySetInnerHTML={{ __html: navbar_message }} />
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
<Group>
|
<Group>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
onClick={openSearchDrawer}
|
onClick={openSearchDrawer}
|
||||||
|
@ -19,7 +19,8 @@ export const emptyServerAPI = {
|
|||||||
installer: null,
|
installer: null,
|
||||||
target: null,
|
target: null,
|
||||||
default_locale: null,
|
default_locale: null,
|
||||||
django_admin: null
|
django_admin: null,
|
||||||
|
customize: null
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface SiteMarkProps {
|
export interface SiteMarkProps {
|
||||||
|
@ -1,9 +1,15 @@
|
|||||||
import { Trans, t } from '@lingui/macro';
|
import { Trans, t } from '@lingui/macro';
|
||||||
import { Center, Container, Paper, Text } from '@mantine/core';
|
import {
|
||||||
|
BackgroundImage,
|
||||||
|
Center,
|
||||||
|
Container,
|
||||||
|
Divider,
|
||||||
|
Paper,
|
||||||
|
Text
|
||||||
|
} from '@mantine/core';
|
||||||
import { useDisclosure, useToggle } from '@mantine/hooks';
|
import { useDisclosure, useToggle } from '@mantine/hooks';
|
||||||
import { useEffect } from 'react';
|
import { useEffect, useMemo } from 'react';
|
||||||
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom';
|
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom';
|
||||||
|
|
||||||
import { setApiDefaults } from '../../App';
|
import { setApiDefaults } from '../../App';
|
||||||
import { AuthFormOptions } from '../../components/forms/AuthFormOptions';
|
import { AuthFormOptions } from '../../components/forms/AuthFormOptions';
|
||||||
import {
|
import {
|
||||||
@ -18,6 +24,7 @@ import {
|
|||||||
doBasicLogin,
|
doBasicLogin,
|
||||||
followRedirect
|
followRedirect
|
||||||
} from '../../functions/auth';
|
} from '../../functions/auth';
|
||||||
|
import { generateUrl } from '../../functions/urls';
|
||||||
import { useServerApiState } from '../../states/ApiState';
|
import { useServerApiState } from '../../states/ApiState';
|
||||||
import { useLocalState } from '../../states/LocalState';
|
import { useLocalState } from '../../states/LocalState';
|
||||||
|
|
||||||
@ -39,6 +46,34 @@ export default function Login() {
|
|||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
|
|
||||||
|
const LoginMessage = useMemo(() => {
|
||||||
|
const val = server.customize?.login_message;
|
||||||
|
if (val) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Divider my='md' />
|
||||||
|
<Text>
|
||||||
|
<span
|
||||||
|
// biome-ignore lint/security/noDangerouslySetInnerHtml: <explanation>
|
||||||
|
dangerouslySetInnerHTML={{ __html: val }}
|
||||||
|
/>
|
||||||
|
</Text>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, [server.customize]);
|
||||||
|
|
||||||
|
const SplashComponent = useMemo(() => {
|
||||||
|
const temp = server.customize?.splash;
|
||||||
|
if (temp) {
|
||||||
|
return ({ children }: { children: React.ReactNode }) => (
|
||||||
|
<BackgroundImage src={generateUrl(temp)}>{children}</BackgroundImage>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return ({ children }: { children: React.ReactNode }) => <>{children}</>;
|
||||||
|
}, [server.customize]);
|
||||||
|
|
||||||
// Data manipulation functions
|
// Data manipulation functions
|
||||||
function ChangeHost(newHost: string | null): void {
|
function ChangeHost(newHost: string | null): void {
|
||||||
if (newHost === null) return;
|
if (newHost === null) return;
|
||||||
@ -75,7 +110,15 @@ export default function Login() {
|
|||||||
|
|
||||||
// Main rendering block
|
// Main rendering block
|
||||||
return (
|
return (
|
||||||
|
<SplashComponent>
|
||||||
<Center mih='100vh'>
|
<Center mih='100vh'>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: '10px',
|
||||||
|
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||||
|
boxShadow: '0 0 15px 10px rgba(0,0,0,0.5)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Container w='md' miw={400}>
|
<Container w='md' miw={400}>
|
||||||
{hostEdit ? (
|
{hostEdit ? (
|
||||||
<InstanceOptions
|
<InstanceOptions
|
||||||
@ -95,11 +138,17 @@ export default function Login() {
|
|||||||
</Text>
|
</Text>
|
||||||
{loginMode ? <AuthenticationForm /> : <RegistrationForm />}
|
{loginMode ? <AuthenticationForm /> : <RegistrationForm />}
|
||||||
<ModeSelector loginMode={loginMode} setMode={setMode} />
|
<ModeSelector loginMode={loginMode} setMode={setMode} />
|
||||||
|
{LoginMessage}
|
||||||
</Paper>
|
</Paper>
|
||||||
<AuthFormOptions hostname={hostname} toggleHostEdit={setHostEdit} />
|
<AuthFormOptions
|
||||||
|
hostname={hostname}
|
||||||
|
toggleHostEdit={setHostEdit}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Container>
|
</Container>
|
||||||
|
</div>
|
||||||
</Center>
|
</Center>
|
||||||
|
</SplashComponent>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -48,6 +48,12 @@ export interface ServerAPIProps {
|
|||||||
target: null | string;
|
target: null | string;
|
||||||
default_locale: null | string;
|
default_locale: null | string;
|
||||||
django_admin: null | string;
|
django_admin: null | string;
|
||||||
|
customize: null | {
|
||||||
|
logo: string;
|
||||||
|
splash: string;
|
||||||
|
login_message: string;
|
||||||
|
navbar_message: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AuthProps {
|
export interface AuthProps {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user