mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
[PUI] Added AboutInventreeModal (#5813)
* updated typing to allow either link or action * fixed typing * made it possible to use an action instead of a link * added ServerInfo Modal skeleton * fixed anchor * added content to ServerInfo * Factored database lookup out * Extended status API to CUI level * extended ServerInfo to CUI level * Made modal larger * fixed default settings * Refactored urls into seperate functions * Refactored python version into seperate function * Added endpoint and modal for PUI version modal * switched to indirect imports to reduce imports * Added copy button * Added full copy button * added default * cleaned unused vars * cleaned unused vars * Refactored auth check for InfoView * implemented suggested changes * fixed check logic
This commit is contained in:
parent
43fac17796
commit
782f36cd48
@ -11,21 +11,52 @@ from rest_framework.response import Response
|
||||
from rest_framework.serializers import ValidationError
|
||||
from rest_framework.views import APIView
|
||||
|
||||
import InvenTree.version
|
||||
import users.models
|
||||
from InvenTree.filters import SEARCH_ORDER_FILTER
|
||||
from InvenTree.mixins import ListCreateAPI
|
||||
from InvenTree.permissions import RolePermission
|
||||
from part.templatetags.inventree_extras import plugins_info
|
||||
from plugin.serializers import MetadataSerializer
|
||||
from users.models import ApiToken
|
||||
|
||||
from .email import is_email_configured
|
||||
from .mixins import RetrieveUpdateAPI
|
||||
from .status import check_system_health, is_worker_running
|
||||
from .version import (inventreeApiVersion, inventreeDatabase,
|
||||
inventreeInstanceName, inventreeVersion)
|
||||
from .views import AjaxView
|
||||
|
||||
|
||||
class VersionView(APIView):
|
||||
"""Simple JSON endpoint for InvenTree version information."""
|
||||
|
||||
permission_classes = [
|
||||
permissions.IsAdminUser,
|
||||
]
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
"""Return information about the InvenTree server."""
|
||||
return JsonResponse({
|
||||
'dev': InvenTree.version.isInvenTreeDevelopmentVersion(),
|
||||
'up_to_date': InvenTree.version.isInvenTreeUpToDate(),
|
||||
'version': {
|
||||
'server': InvenTree.version.inventreeVersion(),
|
||||
'api': InvenTree.version.inventreeApiVersion(),
|
||||
'commit_hash': InvenTree.version.inventreeCommitHash(),
|
||||
'commit_date': InvenTree.version.inventreeCommitDate(),
|
||||
'commit_branch': InvenTree.version.inventreeBranch(),
|
||||
'python': InvenTree.version.inventreePythonVersion(),
|
||||
'django': InvenTree.version.inventreeDjangoVersion()
|
||||
},
|
||||
'links': {
|
||||
'doc': InvenTree.version.inventreeDocUrl(),
|
||||
'code': InvenTree.version.inventreeGithubUrl(),
|
||||
'credit': InvenTree.version.inventreeCreditsUrl(),
|
||||
'app': InvenTree.version.inventreeAppUrl(),
|
||||
'bug': f'{InvenTree.version.inventreeGithubUrl()}/issues'
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
class InfoView(AjaxView):
|
||||
"""Simple JSON endpoint for InvenTree information.
|
||||
|
||||
@ -40,11 +71,16 @@ class InfoView(AjaxView):
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
"""Serve current server information."""
|
||||
is_staff = request.user.is_staff
|
||||
if not is_staff and request.user.is_anonymous:
|
||||
# Might be Token auth - check if so
|
||||
is_staff = self.check_auth_header(request)
|
||||
|
||||
data = {
|
||||
'server': 'InvenTree',
|
||||
'version': inventreeVersion(),
|
||||
'instance': inventreeInstanceName(),
|
||||
'apiVersion': inventreeApiVersion(),
|
||||
'version': InvenTree.version.inventreeVersion(),
|
||||
'instance': InvenTree.version.inventreeInstanceName(),
|
||||
'apiVersion': InvenTree.version.inventreeApiVersion(),
|
||||
'worker_running': is_worker_running(),
|
||||
'worker_pending_tasks': self.worker_pending_tasks(),
|
||||
'plugins_enabled': settings.PLUGINS_ENABLED,
|
||||
@ -52,12 +88,35 @@ class InfoView(AjaxView):
|
||||
'email_configured': is_email_configured(),
|
||||
'debug_mode': settings.DEBUG,
|
||||
'docker_mode': settings.DOCKER,
|
||||
'system_health': check_system_health() if request.user.is_staff else None,
|
||||
'database': inventreeDatabase()if request.user.is_staff else None
|
||||
'system_health': check_system_health() if is_staff else None,
|
||||
'database': InvenTree.version.inventreeDatabase()if is_staff else None,
|
||||
'platform': InvenTree.version.inventreePlatform() if is_staff else None,
|
||||
'installer': InvenTree.version.inventreeInstaller() if is_staff else None,
|
||||
'target': InvenTree.version.inventreeTarget()if is_staff else None,
|
||||
}
|
||||
|
||||
return JsonResponse(data)
|
||||
|
||||
def check_auth_header(self, request):
|
||||
"""Check if user is authenticated via a token in the header."""
|
||||
# TODO @matmair: remove after refacgtor of Token check is done
|
||||
headers = request.headers.get('Authorization', request.headers.get('authorization'))
|
||||
if not headers:
|
||||
return False
|
||||
|
||||
auth = headers.strip()
|
||||
if not (auth.lower().startswith('token') and len(auth.split()) == 2):
|
||||
return False
|
||||
|
||||
token_key = auth.split()[1]
|
||||
try:
|
||||
token = ApiToken.objects.get(key=token_key)
|
||||
if token.active and token.user and token.user.is_staff:
|
||||
return True
|
||||
except ApiToken.DoesNotExist:
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
class NotFoundView(AjaxView):
|
||||
"""Simple JSON view when accessing an invalid API view."""
|
||||
|
@ -2,11 +2,14 @@
|
||||
|
||||
|
||||
# InvenTree API version
|
||||
INVENTREE_API_VERSION = 143
|
||||
INVENTREE_API_VERSION = 144
|
||||
|
||||
"""
|
||||
Increment this API version number whenever there is a significant change to the API that any clients need to know about
|
||||
|
||||
v144 -> 2023-10-23: https://github.com/inventree/InvenTree/pull/5811
|
||||
- Adds version information API endpoint
|
||||
|
||||
v143 -> 2023-10-29: https://github.com/inventree/InvenTree/pull/5810
|
||||
- Extends the status endpoint to include information about system status and health
|
||||
|
||||
|
@ -36,7 +36,7 @@ from plugin.urls import get_plugin_urls
|
||||
from stock.urls import stock_urls
|
||||
from web.urls import urlpatterns as platform_urls
|
||||
|
||||
from .api import APISearchView, InfoView, NotFoundView
|
||||
from .api import APISearchView, InfoView, NotFoundView, VersionView
|
||||
from .magic_login import GetSimpleLoginView
|
||||
from .social_auth_urls import SocialProviderListView, social_auth_urlpatterns
|
||||
from .views import (AboutView, AppearanceSelectView, CustomConnectionsView,
|
||||
@ -76,8 +76,9 @@ apipatterns = [
|
||||
# OpenAPI Schema
|
||||
re_path('schema/', SpectacularAPIView.as_view(custom_settings={'SCHEMA_PATH_PREFIX': '/api/'}), name='schema'),
|
||||
|
||||
# InvenTree information endpoint
|
||||
path('', InfoView.as_view(), name='api-inventree-info'),
|
||||
# InvenTree information endpoints
|
||||
path('version/', VersionView.as_view(), name='api-version'), # version info
|
||||
path('', InfoView.as_view(), name='api-inventree-info'), # server info
|
||||
|
||||
# Auth API endpoints
|
||||
path('auth/', include([
|
||||
|
@ -97,6 +97,27 @@ def inventreeDocsVersion():
|
||||
return INVENTREE_SW_VERSION # pragma: no cover
|
||||
|
||||
|
||||
def inventreeDocUrl():
|
||||
"""Return URL for InvenTree documentation site."""
|
||||
tag = inventreeDocsVersion()
|
||||
return f"https://docs.inventree.org/en/{tag}"
|
||||
|
||||
|
||||
def inventreeAppUrl():
|
||||
"""Return URL for InvenTree app site."""
|
||||
return f'{inventreeDocUrl()}/app/app',
|
||||
|
||||
|
||||
def inventreeCreditsUrl():
|
||||
"""Return URL for InvenTree credits site."""
|
||||
return "https://docs.inventree.org/en/latest/credits/"
|
||||
|
||||
|
||||
def inventreeGithubUrl():
|
||||
"""Return URL for InvenTree github site."""
|
||||
return "https://github.com/InvenTree/InvenTree/"
|
||||
|
||||
|
||||
def isInvenTreeUpToDate():
|
||||
"""Test if the InvenTree instance is "up to date" with the latest version.
|
||||
|
||||
@ -126,6 +147,11 @@ def inventreeDjangoVersion():
|
||||
return django.get_version()
|
||||
|
||||
|
||||
def inventreePythonVersion():
|
||||
"""Returns the version of python"""
|
||||
return sys.version.split(' ')[0]
|
||||
|
||||
|
||||
def inventreeCommitHash():
|
||||
"""Returns the git commit hash for the running codebase."""
|
||||
# First look in the environment variables, i.e. if running in docker
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from datetime import date, datetime
|
||||
|
||||
from django import template
|
||||
@ -222,7 +221,7 @@ def inventree_base_url(*args, **kwargs):
|
||||
@register.simple_tag()
|
||||
def python_version(*args, **kwargs):
|
||||
"""Return the current python version."""
|
||||
return sys.version.split(' ')[0]
|
||||
return version.inventreePythonVersion()
|
||||
|
||||
|
||||
@register.simple_tag()
|
||||
@ -302,21 +301,25 @@ def inventree_platform(*args, **kwargs):
|
||||
@register.simple_tag()
|
||||
def inventree_github_url(*args, **kwargs):
|
||||
"""Return URL for InvenTree github site."""
|
||||
return "https://github.com/InvenTree/InvenTree/"
|
||||
return version.inventreeGithubUrl()
|
||||
|
||||
|
||||
@register.simple_tag()
|
||||
def inventree_docs_url(*args, **kwargs):
|
||||
"""Return URL for InvenTree documentation site."""
|
||||
tag = version.inventreeDocsVersion()
|
||||
return version.inventreeDocUrl()
|
||||
|
||||
return f"https://docs.inventree.org/en/{tag}"
|
||||
|
||||
@register.simple_tag()
|
||||
def inventree_app_url(*args, **kwargs):
|
||||
"""Return URL for InvenTree app site."""
|
||||
return version.inventreeAppUrl()
|
||||
|
||||
|
||||
@register.simple_tag()
|
||||
def inventree_credits_url(*args, **kwargs):
|
||||
"""Return URL for InvenTree credits site."""
|
||||
return "https://docs.inventree.org/en/latest/credits/"
|
||||
return version.inventreeCreditsUrl()
|
||||
|
||||
|
||||
@register.simple_tag()
|
||||
|
@ -77,7 +77,7 @@
|
||||
<tr>
|
||||
<td><span class='fas fa-mobile-alt'></span></td>
|
||||
<td>{% trans "Mobile App" %}</td>
|
||||
<td><a href="{% inventree_docs_url %}/app/app">{% inventree_docs_url %}/app/app</a></td>
|
||||
<td><a href="{% inventree_app_url %}">{% inventree_app_url %}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class='fas fa-bug'></span></td>
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { ActionIcon, Menu, Tooltip } from '@mantine/core';
|
||||
import { Component } from 'react';
|
||||
|
||||
/**
|
||||
* A ButtonMenu is a button that opens a menu when clicked.
|
||||
|
28
src/frontend/src/components/items/CopyButton.tsx
Normal file
28
src/frontend/src/components/items/CopyButton.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { Button, CopyButton as MantineCopyButton } from '@mantine/core';
|
||||
import { IconCopy } from '@tabler/icons-react';
|
||||
|
||||
export function CopyButton({
|
||||
value,
|
||||
label
|
||||
}: {
|
||||
value: any;
|
||||
label?: JSX.Element;
|
||||
}) {
|
||||
return (
|
||||
<MantineCopyButton value={value}>
|
||||
{({ copied, copy }) => (
|
||||
<Button
|
||||
color={copied ? 'teal' : 'gray'}
|
||||
onClick={copy}
|
||||
title={t`Copy to clipboard`}
|
||||
variant="subtle"
|
||||
compact
|
||||
>
|
||||
<IconCopy size={10} />
|
||||
{label && label}
|
||||
</Button>
|
||||
)}
|
||||
</MantineCopyButton>
|
||||
);
|
||||
}
|
171
src/frontend/src/components/modals/AboutInvenTreeModal.tsx
Normal file
171
src/frontend/src/components/modals/AboutInvenTreeModal.tsx
Normal file
@ -0,0 +1,171 @@
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { Anchor, Badge, Group, Stack, Table, Text, Title } from '@mantine/core';
|
||||
import { ContextModalProps } from '@mantine/modals';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '../../App';
|
||||
import { ApiPaths, apiUrl, useServerApiState } from '../../states/ApiState';
|
||||
import { useLocalState } from '../../states/LocalState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
import { CopyButton } from '../items/CopyButton';
|
||||
|
||||
type AboutLookupRef = {
|
||||
ref: string;
|
||||
title: JSX.Element;
|
||||
link?: string;
|
||||
copy?: boolean;
|
||||
};
|
||||
|
||||
export function AboutInvenTreeModal({}: ContextModalProps<{
|
||||
modalBody: string;
|
||||
}>) {
|
||||
const [user] = useUserState((state) => [state.user]);
|
||||
const { host } = useLocalState.getState();
|
||||
const [server] = useServerApiState((state) => [state.server]);
|
||||
|
||||
if (user?.is_staff != true)
|
||||
return (
|
||||
<Text>
|
||||
<Trans>This information is only available for staff users</Trans>
|
||||
</Text>
|
||||
);
|
||||
|
||||
const { isLoading, data } = useQuery({
|
||||
queryKey: ['version'],
|
||||
queryFn: () => api.get(apiUrl(ApiPaths.version)).then((res) => res.data)
|
||||
});
|
||||
|
||||
function fillTable(
|
||||
lookup: AboutLookupRef[],
|
||||
data: any,
|
||||
alwaysLink: boolean = false
|
||||
) {
|
||||
return lookup.map((map: AboutLookupRef, idx) => (
|
||||
<tr key={idx}>
|
||||
<td>{map.title}</td>
|
||||
<td>
|
||||
<Group position="apart" spacing="xs">
|
||||
{alwaysLink ? (
|
||||
<Anchor href={data[map.ref]} target="_blank">
|
||||
{data[map.ref]}
|
||||
</Anchor>
|
||||
) : map.link ? (
|
||||
<Anchor href={map.link} target="_blank">
|
||||
{data[map.ref]}
|
||||
</Anchor>
|
||||
) : (
|
||||
data[map.ref]
|
||||
)}
|
||||
{map.copy && <CopyButton value={data[map.ref]} />}
|
||||
</Group>
|
||||
</td>
|
||||
</tr>
|
||||
));
|
||||
}
|
||||
/* renderer */
|
||||
if (isLoading) return <Trans>Loading</Trans>;
|
||||
|
||||
const copyval = `InvenTree-Version: ${data.version.server}\nDjango Version: ${
|
||||
data.version.django
|
||||
}\n${
|
||||
data.version.commit_hash &&
|
||||
`Commit Hash: ${data.version.commit_hash}\nCommit Date: ${data.version.commit_date}\nCommit Branch: ${data.version.commit_branch}\n`
|
||||
}Database: ${server.database}\nDebug-Mode: ${
|
||||
server.debug_mode ? 'True' : 'False'
|
||||
}\nDeployed using Docker: ${
|
||||
server.docker_mode ? 'True' : 'False'
|
||||
}\nPlatform: ${server.platform}\nInstaller: ${server.installer}\n${
|
||||
server.target && `Target: ${server.target}\n`
|
||||
}Active plugins: ${JSON.stringify(server.active_plugins)}`;
|
||||
return (
|
||||
<Stack>
|
||||
<Group>
|
||||
<Text>
|
||||
<Trans>Your InvenTree version status is</Trans>
|
||||
</Text>
|
||||
{data.dev ? (
|
||||
<Badge color="blue">
|
||||
<Trans>Development Version</Trans>
|
||||
</Badge>
|
||||
) : data.up_to_date ? (
|
||||
<Badge color="green">
|
||||
<Trans>Up to Date</Trans>
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge color="teal">
|
||||
<Trans>Update Available</Trans>
|
||||
</Badge>
|
||||
)}
|
||||
</Group>
|
||||
<Title order={5}>
|
||||
<Trans>Version Information</Trans>
|
||||
</Title>
|
||||
<Table>
|
||||
<tbody>
|
||||
{fillTable(
|
||||
[
|
||||
{
|
||||
ref: 'server',
|
||||
title: <Trans>InvenTree Version</Trans>,
|
||||
link: 'https://github.com/inventree/InvenTree/releases',
|
||||
copy: true
|
||||
},
|
||||
{
|
||||
ref: 'commit_hash',
|
||||
title: <Trans>Commit Hash</Trans>,
|
||||
copy: true
|
||||
},
|
||||
{
|
||||
ref: 'commit_date',
|
||||
title: <Trans>Commit Date</Trans>,
|
||||
copy: true
|
||||
},
|
||||
{
|
||||
ref: 'commit_branch',
|
||||
title: <Trans>Commit Branch</Trans>,
|
||||
copy: true
|
||||
},
|
||||
{
|
||||
ref: 'api',
|
||||
title: <Trans>API Version</Trans>,
|
||||
link: `${host}api-doc/`
|
||||
},
|
||||
{ ref: 'python', title: <Trans>Python Version</Trans> },
|
||||
{
|
||||
ref: 'django',
|
||||
title: <Trans>Django Version</Trans>,
|
||||
link: 'https://www.djangoproject.com/',
|
||||
copy: true
|
||||
}
|
||||
],
|
||||
data.version
|
||||
)}
|
||||
</tbody>
|
||||
</Table>
|
||||
<Title order={5}>
|
||||
<Trans>Links</Trans>
|
||||
</Title>
|
||||
<Table>
|
||||
<tbody>
|
||||
{fillTable(
|
||||
[
|
||||
{ ref: 'doc', title: <Trans>InvenTree Documentation</Trans> },
|
||||
{ ref: 'code', title: <Trans>View Code on GitHub</Trans> },
|
||||
{ ref: 'credit', title: <Trans>Credits</Trans> },
|
||||
{ ref: 'app', title: <Trans>Mobile App</Trans> },
|
||||
{ ref: 'bug', title: <Trans>Submit Bug Report</Trans> }
|
||||
],
|
||||
data.links,
|
||||
true
|
||||
)}
|
||||
</tbody>
|
||||
</Table>
|
||||
<Group>
|
||||
<CopyButton
|
||||
value={copyval}
|
||||
label={<Trans>Copy version information</Trans>}
|
||||
/>
|
||||
</Group>
|
||||
</Stack>
|
||||
);
|
||||
}
|
@ -9,6 +9,7 @@ import { useColorScheme, useLocalStorage } from '@mantine/hooks';
|
||||
import { ModalsProvider } from '@mantine/modals';
|
||||
import { Notifications } from '@mantine/notifications';
|
||||
|
||||
import { AboutInvenTreeModal } from '../components/modals/AboutInvenTreeModal';
|
||||
import { QrCodeModal } from '../components/modals/QrCodeModal';
|
||||
import { ServerInfoModal } from '../components/modals/ServerInfoModal';
|
||||
import { useLocalState } from '../states/LocalState';
|
||||
@ -61,7 +62,11 @@ export function ThemeContext({ children }: { children: JSX.Element }) {
|
||||
<Notifications />
|
||||
<ModalsProvider
|
||||
labels={{ confirm: t`Submit`, cancel: t`Cancel` }}
|
||||
modals={{ qr: QrCodeModal, info: ServerInfoModal }}
|
||||
modals={{
|
||||
qr: QrCodeModal,
|
||||
info: ServerInfoModal,
|
||||
about: AboutInvenTreeModal
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ModalsProvider>
|
||||
|
@ -13,7 +13,10 @@ export const emptyServerAPI = {
|
||||
debug_mode: null,
|
||||
docker_mode: null,
|
||||
database: null,
|
||||
system_health: null
|
||||
system_health: null,
|
||||
platform: null,
|
||||
installer: null,
|
||||
target: null
|
||||
};
|
||||
|
||||
export interface SiteMarkProps {
|
||||
|
@ -79,6 +79,15 @@ function serverInfo() {
|
||||
});
|
||||
}
|
||||
|
||||
function aboutInvenTree() {
|
||||
return openContextModal({
|
||||
modal: 'about',
|
||||
title: <Trans>About InvenTree</Trans>,
|
||||
size: 'xl',
|
||||
innerProps: {}
|
||||
});
|
||||
}
|
||||
|
||||
// TODO @matmair: Add the following pages and adjust the links
|
||||
export const aboutLinks: DocumentationLinkItem[] = [
|
||||
{
|
||||
@ -91,8 +100,7 @@ export const aboutLinks: DocumentationLinkItem[] = [
|
||||
id: 'about',
|
||||
title: <Trans>About InvenTree</Trans>,
|
||||
description: <Trans>About the InvenTree org</Trans>,
|
||||
link: '/about',
|
||||
placeholder: true
|
||||
action: aboutInvenTree
|
||||
},
|
||||
{
|
||||
id: 'licenses',
|
||||
|
@ -4,7 +4,6 @@ import { IconBellCheck, IconBellExclamation } from '@tabler/icons-react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { api } from '../App';
|
||||
import { StylishText } from '../components/items/StylishText';
|
||||
import { PageDetail } from '../components/nav/PageDetail';
|
||||
import { PanelGroup } from '../components/nav/PanelGroup';
|
||||
import { NotificationTable } from '../components/tables/notifications/NotificationsTable';
|
||||
|
@ -68,6 +68,7 @@ export enum ApiPaths {
|
||||
barcode = 'api-barcode',
|
||||
news = 'news',
|
||||
global_status = 'api-global-status',
|
||||
version = 'api-version',
|
||||
|
||||
// Build order URLs
|
||||
build_order_list = 'api-build-list',
|
||||
@ -158,6 +159,8 @@ export function apiEndpoint(path: ApiPaths): string {
|
||||
return 'news/';
|
||||
case ApiPaths.global_status:
|
||||
return 'generic/status/';
|
||||
case ApiPaths.version:
|
||||
return 'version/';
|
||||
case ApiPaths.build_order_list:
|
||||
return 'build/';
|
||||
case ApiPaths.build_order_attachment_list:
|
||||
|
@ -33,6 +33,9 @@ export interface ServerAPIProps {
|
||||
docker_mode: null | boolean;
|
||||
database: null | string;
|
||||
system_health: null | boolean;
|
||||
platform: null | string;
|
||||
installer: null | string;
|
||||
target: null | string;
|
||||
}
|
||||
|
||||
// Type interface defining a single 'setting' object
|
||||
|
Loading…
Reference in New Issue
Block a user