Merge pull request #258 from sartography/feature/version-and-documentation-links
mostly frontend stuff: add version and doc links, autofix unused imports
This commit is contained in:
commit
7a0958538f
|
@ -185,13 +185,19 @@ def create_app() -> flask.app.Flask:
|
||||||
return app # type: ignore
|
return app # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
def get_version_info_data() -> dict[str, Any]:
|
||||||
|
version_info_data_dict = {}
|
||||||
|
if os.path.isfile("version_info.json"):
|
||||||
|
with open("version_info.json") as f:
|
||||||
|
version_info_data_dict = json.load(f)
|
||||||
|
return version_info_data_dict
|
||||||
|
|
||||||
|
|
||||||
def _setup_prometheus_metrics(app: flask.app.Flask, connexion_app: connexion.apps.flask_app.FlaskApp) -> None:
|
def _setup_prometheus_metrics(app: flask.app.Flask, connexion_app: connexion.apps.flask_app.FlaskApp) -> None:
|
||||||
metrics = ConnexionPrometheusMetrics(connexion_app)
|
metrics = ConnexionPrometheusMetrics(connexion_app)
|
||||||
app.config["PROMETHEUS_METRICS"] = metrics
|
app.config["PROMETHEUS_METRICS"] = metrics
|
||||||
if os.path.isfile("version_info.json"):
|
version_info_data = get_version_info_data()
|
||||||
version_info_data = {}
|
if len(version_info_data) > 0:
|
||||||
with open("version_info.json") as f:
|
|
||||||
version_info_data = json.load(f)
|
|
||||||
# prometheus does not allow periods in key names
|
# prometheus does not allow periods in key names
|
||||||
version_info_data_normalized = {k.replace(".", "_"): v for k, v in version_info_data.items()}
|
version_info_data_normalized = {k.replace(".", "_"): v for k, v in version_info_data.items()}
|
||||||
metrics.info("version_info", "Application Version Info", **version_info_data_normalized)
|
metrics.info("version_info", "Application Version Info", **version_info_data_normalized)
|
||||||
|
|
|
@ -162,6 +162,19 @@ paths:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/OkTrue"
|
$ref: "#/components/schemas/OkTrue"
|
||||||
|
|
||||||
|
/debug/version-info:
|
||||||
|
get:
|
||||||
|
operationId: spiffworkflow_backend.routes.debug_controller.version_info
|
||||||
|
summary: Returns information about the version of the application
|
||||||
|
tags:
|
||||||
|
- Status
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Returns version info if it exists.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/OkTrue"
|
||||||
|
|
||||||
/active-users/updates/{last_visited_identifier}:
|
/active-users/updates/{last_visited_identifier}:
|
||||||
parameters:
|
parameters:
|
||||||
|
|
|
@ -18,13 +18,13 @@ def setup_database_uri(app: Flask) -> None:
|
||||||
if app.config.get("SPIFFWORKFLOW_BACKEND_DATABASE_URI") is None:
|
if app.config.get("SPIFFWORKFLOW_BACKEND_DATABASE_URI") is None:
|
||||||
database_name = f"spiffworkflow_backend_{app.config['ENV_IDENTIFIER']}"
|
database_name = f"spiffworkflow_backend_{app.config['ENV_IDENTIFIER']}"
|
||||||
if app.config.get("SPIFFWORKFLOW_BACKEND_DATABASE_TYPE") == "sqlite":
|
if app.config.get("SPIFFWORKFLOW_BACKEND_DATABASE_TYPE") == "sqlite":
|
||||||
app.config[
|
app.config["SQLALCHEMY_DATABASE_URI"] = (
|
||||||
"SQLALCHEMY_DATABASE_URI"
|
f"sqlite:///{app.instance_path}/db_{app.config['ENV_IDENTIFIER']}.sqlite3"
|
||||||
] = f"sqlite:///{app.instance_path}/db_{app.config['ENV_IDENTIFIER']}.sqlite3"
|
)
|
||||||
elif app.config.get("SPIFFWORKFLOW_BACKEND_DATABASE_TYPE") == "postgres":
|
elif app.config.get("SPIFFWORKFLOW_BACKEND_DATABASE_TYPE") == "postgres":
|
||||||
app.config[
|
app.config["SQLALCHEMY_DATABASE_URI"] = (
|
||||||
"SQLALCHEMY_DATABASE_URI"
|
f"postgresql://spiffworkflow_backend:spiffworkflow_backend@localhost:5432/{database_name}"
|
||||||
] = f"postgresql://spiffworkflow_backend:spiffworkflow_backend@localhost:5432/{database_name}"
|
)
|
||||||
else:
|
else:
|
||||||
# use pswd to trick flake8 with hardcoded passwords
|
# use pswd to trick flake8 with hardcoded passwords
|
||||||
db_pswd = app.config.get("SPIFFWORKFLOW_BACKEND_DATABASE_PASSWORD")
|
db_pswd = app.config.get("SPIFFWORKFLOW_BACKEND_DATABASE_PASSWORD")
|
||||||
|
|
|
@ -129,9 +129,9 @@ class ProcessInstanceModel(SpiffworkflowBaseDBModel):
|
||||||
def serialized_with_metadata(self) -> dict[str, Any]:
|
def serialized_with_metadata(self) -> dict[str, Any]:
|
||||||
process_instance_attributes = self.serialized
|
process_instance_attributes = self.serialized
|
||||||
process_instance_attributes["process_metadata"] = self.process_metadata
|
process_instance_attributes["process_metadata"] = self.process_metadata
|
||||||
process_instance_attributes[
|
process_instance_attributes["process_model_with_diagram_identifier"] = (
|
||||||
"process_model_with_diagram_identifier"
|
self.process_model_with_diagram_identifier
|
||||||
] = self.process_model_with_diagram_identifier
|
)
|
||||||
return process_instance_attributes
|
return process_instance_attributes
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
"""APIs for dealing with process groups, process models, and process instances."""
|
"""APIs for dealing with process groups, process models, and process instances."""
|
||||||
|
from flask import make_response
|
||||||
from flask.wrappers import Response
|
from flask.wrappers import Response
|
||||||
|
|
||||||
|
from spiffworkflow_backend import get_version_info_data
|
||||||
|
|
||||||
|
|
||||||
def test_raise_error() -> Response:
|
def test_raise_error() -> Response:
|
||||||
raise Exception("This exception was generated by /debug/test-raise-error for testing purposes. Please ignore.")
|
raise Exception("This exception was generated by /debug/test-raise-error for testing purposes. Please ignore.")
|
||||||
|
|
||||||
|
|
||||||
|
def version_info() -> Response:
|
||||||
|
return make_response(get_version_info_data(), 200)
|
||||||
|
|
|
@ -415,9 +415,9 @@ class ProcessInstanceProcessor:
|
||||||
tld.process_instance_id = process_instance_model.id
|
tld.process_instance_id = process_instance_model.id
|
||||||
|
|
||||||
# we want this to be the fully qualified path to the process model including all group subcomponents
|
# we want this to be the fully qualified path to the process model including all group subcomponents
|
||||||
current_app.config[
|
current_app.config["THREAD_LOCAL_DATA"].process_model_identifier = (
|
||||||
"THREAD_LOCAL_DATA"
|
f"{process_instance_model.process_model_identifier}"
|
||||||
].process_model_identifier = f"{process_instance_model.process_model_identifier}"
|
)
|
||||||
|
|
||||||
self.process_instance_model = process_instance_model
|
self.process_instance_model = process_instance_model
|
||||||
self.process_model_service = ProcessModelService()
|
self.process_model_service = ProcessModelService()
|
||||||
|
@ -577,9 +577,9 @@ class ProcessInstanceProcessor:
|
||||||
bpmn_subprocess_definition.bpmn_identifier
|
bpmn_subprocess_definition.bpmn_identifier
|
||||||
] = bpmn_process_definition_dict
|
] = bpmn_process_definition_dict
|
||||||
spiff_bpmn_process_dict["subprocess_specs"][bpmn_subprocess_definition.bpmn_identifier]["task_specs"] = {}
|
spiff_bpmn_process_dict["subprocess_specs"][bpmn_subprocess_definition.bpmn_identifier]["task_specs"] = {}
|
||||||
bpmn_subprocess_definition_bpmn_identifiers[
|
bpmn_subprocess_definition_bpmn_identifiers[bpmn_subprocess_definition.id] = (
|
||||||
bpmn_subprocess_definition.id
|
bpmn_subprocess_definition.bpmn_identifier
|
||||||
] = bpmn_subprocess_definition.bpmn_identifier
|
)
|
||||||
|
|
||||||
task_definitions = TaskDefinitionModel.query.filter(
|
task_definitions = TaskDefinitionModel.query.filter(
|
||||||
TaskDefinitionModel.bpmn_process_definition_id.in_( # type: ignore
|
TaskDefinitionModel.bpmn_process_definition_id.in_( # type: ignore
|
||||||
|
|
|
@ -23,7 +23,7 @@ module.exports = {
|
||||||
ecmaVersion: 'latest',
|
ecmaVersion: 'latest',
|
||||||
sourceType: 'module',
|
sourceType: 'module',
|
||||||
},
|
},
|
||||||
plugins: ['react', 'sonarjs', '@typescript-eslint'],
|
plugins: ['react', 'sonarjs', '@typescript-eslint', 'unused-imports'],
|
||||||
rules: {
|
rules: {
|
||||||
// according to https://github.com/typescript-eslint/typescript-eslint/issues/2621, You should turn off the eslint core rule and turn on the typescript-eslint rule
|
// according to https://github.com/typescript-eslint/typescript-eslint/issues/2621, You should turn off the eslint core rule and turn on the typescript-eslint rule
|
||||||
// but not sure which of the above "extends" statements is maybe bringing in eslint core
|
// but not sure which of the above "extends" statements is maybe bringing in eslint core
|
||||||
|
@ -43,6 +43,16 @@ module.exports = {
|
||||||
'react/require-default-props': 'off',
|
'react/require-default-props': 'off',
|
||||||
'import/prefer-default-export': 'off',
|
'import/prefer-default-export': 'off',
|
||||||
'no-unused-vars': 'off',
|
'no-unused-vars': 'off',
|
||||||
|
'unused-imports/no-unused-imports': 'error',
|
||||||
|
'unused-imports/no-unused-vars': [
|
||||||
|
'warn',
|
||||||
|
{
|
||||||
|
vars: 'all',
|
||||||
|
varsIgnorePattern: '^_',
|
||||||
|
args: 'after-used',
|
||||||
|
argsIgnorePattern: '^_',
|
||||||
|
},
|
||||||
|
],
|
||||||
'@typescript-eslint/no-unused-vars': [
|
'@typescript-eslint/no-unused-vars': [
|
||||||
'error',
|
'error',
|
||||||
{
|
{
|
||||||
|
|
|
@ -85,6 +85,7 @@
|
||||||
"eslint-plugin-react": "^7.31.0",
|
"eslint-plugin-react": "^7.31.0",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"eslint-plugin-sonarjs": "^0.15.0",
|
"eslint-plugin-sonarjs": "^0.15.0",
|
||||||
|
"eslint-plugin-unused-imports": "^2.0.0",
|
||||||
"prettier": "^2.7.1",
|
"prettier": "^2.7.1",
|
||||||
"safe-regex": "^2.1.1",
|
"safe-regex": "^2.1.1",
|
||||||
"ts-migrate": "^0.1.30"
|
"ts-migrate": "^0.1.30"
|
||||||
|
@ -13286,6 +13287,36 @@
|
||||||
"eslint": "^7.5.0 || ^8.0.0"
|
"eslint": "^7.5.0 || ^8.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/eslint-plugin-unused-imports": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-3APeS/tQlTrFa167ThtP0Zm0vctjr4M44HMpeg1P4bK6wItarumq0Ma82xorMKdFsWpphQBlRPzw/pxiVELX1A==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"eslint-rule-composer": "^0.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.0.0",
|
||||||
|
"eslint": "^8.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@typescript-eslint/eslint-plugin": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/eslint-rule-composer": {
|
||||||
|
"version": "0.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz",
|
||||||
|
"integrity": "sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/eslint-scope": {
|
"node_modules/eslint-scope": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
|
||||||
|
@ -42460,6 +42491,21 @@
|
||||||
"@typescript-eslint/utils": "^5.58.0"
|
"@typescript-eslint/utils": "^5.58.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"eslint-plugin-unused-imports": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-3APeS/tQlTrFa167ThtP0Zm0vctjr4M44HMpeg1P4bK6wItarumq0Ma82xorMKdFsWpphQBlRPzw/pxiVELX1A==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"eslint-rule-composer": "^0.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"eslint-rule-composer": {
|
||||||
|
"version": "0.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz",
|
||||||
|
"integrity": "sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"eslint-scope": {
|
"eslint-scope": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
|
||||||
|
|
|
@ -113,6 +113,7 @@
|
||||||
"eslint-plugin-react": "^7.31.0",
|
"eslint-plugin-react": "^7.31.0",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"eslint-plugin-sonarjs": "^0.15.0",
|
"eslint-plugin-sonarjs": "^0.15.0",
|
||||||
|
"eslint-plugin-unused-imports": "^2.0.0",
|
||||||
"prettier": "^2.7.1",
|
"prettier": "^2.7.1",
|
||||||
"safe-regex": "^2.1.1",
|
"safe-regex": "^2.1.1",
|
||||||
"ts-migrate": "^0.1.30"
|
"ts-migrate": "^0.1.30"
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { defineAbility } from '@casl/ability';
|
||||||
import NavigationBar from './components/NavigationBar';
|
import NavigationBar from './components/NavigationBar';
|
||||||
|
|
||||||
import HomePageRoutes from './routes/HomePageRoutes';
|
import HomePageRoutes from './routes/HomePageRoutes';
|
||||||
|
import About from './routes/About';
|
||||||
import ErrorBoundary from './components/ErrorBoundary';
|
import ErrorBoundary from './components/ErrorBoundary';
|
||||||
import AdminRoutes from './routes/AdminRoutes';
|
import AdminRoutes from './routes/AdminRoutes';
|
||||||
import ProcessRoutes from './routes/ProcessRoutes';
|
import ProcessRoutes from './routes/ProcessRoutes';
|
||||||
|
@ -35,6 +36,7 @@ export default function App() {
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/*" element={<HomePageRoutes />} />
|
<Route path="/*" element={<HomePageRoutes />} />
|
||||||
|
<Route path="/about" element={<About />} />
|
||||||
<Route path="/tasks/*" element={<HomePageRoutes />} />
|
<Route path="/tasks/*" element={<HomePageRoutes />} />
|
||||||
<Route path="/process/*" element={<ProcessRoutes />} />
|
<Route path="/process/*" element={<ProcessRoutes />} />
|
||||||
<Route path="/admin/*" element={<AdminRoutes />} />
|
<Route path="/admin/*" element={<AdminRoutes />} />
|
||||||
|
|
|
@ -30,6 +30,7 @@ import { PermissionsToCheck } from '../interfaces';
|
||||||
import { usePermissionFetcher } from '../hooks/PermissionService';
|
import { usePermissionFetcher } from '../hooks/PermissionService';
|
||||||
import { UnauthenticatedError } from '../services/HttpService';
|
import { UnauthenticatedError } from '../services/HttpService';
|
||||||
import { SPIFF_ENVIRONMENT } from '../config';
|
import { SPIFF_ENVIRONMENT } from '../config';
|
||||||
|
import appVersionInfo from '../helpers/appVersionInfo';
|
||||||
|
|
||||||
// for ref: https://react-bootstrap.github.io/components/navbar/
|
// for ref: https://react-bootstrap.github.io/components/navbar/
|
||||||
export default function NavigationBar() {
|
export default function NavigationBar() {
|
||||||
|
@ -57,6 +58,15 @@ export default function NavigationBar() {
|
||||||
};
|
};
|
||||||
const { ability } = usePermissionFetcher(permissionRequestData);
|
const { ability } = usePermissionFetcher(permissionRequestData);
|
||||||
|
|
||||||
|
// default to readthedocs and let someone specify an environment variable to override:
|
||||||
|
//
|
||||||
|
let documentationUrl = 'https://spiffworkflow.readthedocs.io';
|
||||||
|
if ('DOCUMENTATION_URL' in window.spiffworkflowFrontendJsenv) {
|
||||||
|
documentationUrl = window.spiffworkflowFrontendJsenv.DOCUMENTATION_URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const versionInfo = appVersionInfo();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let newActiveKey = '/admin/process-groups';
|
let newActiveKey = '/admin/process-groups';
|
||||||
if (location.pathname.match(/^\/admin\/messages\b/)) {
|
if (location.pathname.match(/^\/admin\/messages\b/)) {
|
||||||
|
@ -81,6 +91,12 @@ export default function NavigationBar() {
|
||||||
return activeKey === menuItemPath;
|
return activeKey === menuItemPath;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let aboutLinkElement = null;
|
||||||
|
|
||||||
|
if (Object.keys(versionInfo).length) {
|
||||||
|
aboutLinkElement = <a href="/about">About</a>;
|
||||||
|
}
|
||||||
|
|
||||||
const profileToggletip = (
|
const profileToggletip = (
|
||||||
<div style={{ display: 'flex' }} id="user-profile-toggletip">
|
<div style={{ display: 'flex' }} id="user-profile-toggletip">
|
||||||
<Toggletip isTabTip align="bottom-right">
|
<Toggletip isTabTip align="bottom-right">
|
||||||
|
@ -99,6 +115,11 @@ export default function NavigationBar() {
|
||||||
</p>
|
</p>
|
||||||
<p>{UserService.getUserEmail()}</p>
|
<p>{UserService.getUserEmail()}</p>
|
||||||
<hr />
|
<hr />
|
||||||
|
{aboutLinkElement}
|
||||||
|
<a target="_blank" href={documentationUrl} rel="noreferrer">
|
||||||
|
Documentation
|
||||||
|
</a>
|
||||||
|
<hr />
|
||||||
<Button
|
<Button
|
||||||
data-qa="logout-button"
|
data-qa="logout-button"
|
||||||
className="button-link"
|
className="button-link"
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
const appVersionInfo = () => {
|
||||||
|
const versionInfoFromHtmlMetaTag = document.querySelector(
|
||||||
|
'meta[name="version-info"]'
|
||||||
|
);
|
||||||
|
let versionInfo: { [key: string]: string } = {};
|
||||||
|
if (versionInfoFromHtmlMetaTag) {
|
||||||
|
const versionInfoContentString =
|
||||||
|
versionInfoFromHtmlMetaTag.getAttribute('content');
|
||||||
|
if (
|
||||||
|
versionInfoContentString &&
|
||||||
|
versionInfoContentString !== '%REACT_APP_VERSION_INFO%'
|
||||||
|
) {
|
||||||
|
versionInfo = JSON.parse(versionInfoContentString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
versionInfo = {
|
||||||
|
version: '1.0.0',
|
||||||
|
git_sha: 'sdkfjksd',
|
||||||
|
sure: '3',
|
||||||
|
};
|
||||||
|
|
||||||
|
return versionInfo;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default appVersionInfo;
|
|
@ -562,3 +562,7 @@ svg.notification-icon {
|
||||||
.user-circle:nth-child(n+11) {
|
.user-circle:nth-child(n+11) {
|
||||||
background-color: #8e8e8e;
|
background-color: #8e8e8e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.version-info-column {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
|
@ -111,6 +111,8 @@ export interface ProcessReference {
|
||||||
is_primary: boolean;
|
is_primary: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ObjectWithStringKeysAndValues = { [key: string]: string };
|
||||||
|
|
||||||
export interface ProcessFile {
|
export interface ProcessFile {
|
||||||
content_type: string;
|
content_type: string;
|
||||||
last_modified: string;
|
last_modified: string;
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
// @ts-ignore
|
||||||
|
import { Table } from '@carbon/react';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import appVersionInfo from '../helpers/appVersionInfo';
|
||||||
|
import { ObjectWithStringKeysAndValues } from '../interfaces';
|
||||||
|
import HttpService from '../services/HttpService';
|
||||||
|
|
||||||
|
export default function About() {
|
||||||
|
const frontendVersionInfo = appVersionInfo();
|
||||||
|
const [backendVersionInfo, setBackendVersionInfo] =
|
||||||
|
useState<ObjectWithStringKeysAndValues | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleVersionInfoResponse = (
|
||||||
|
response: ObjectWithStringKeysAndValues
|
||||||
|
) => {
|
||||||
|
setBackendVersionInfo(response);
|
||||||
|
};
|
||||||
|
|
||||||
|
HttpService.makeCallToBackend({
|
||||||
|
path: `/debug/version-info`,
|
||||||
|
successCallback: handleVersionInfoResponse,
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const versionInfoFromDict = (
|
||||||
|
title: string,
|
||||||
|
versionInfoDict: ObjectWithStringKeysAndValues | null
|
||||||
|
) => {
|
||||||
|
if (versionInfoDict !== null && Object.keys(versionInfoDict).length) {
|
||||||
|
const tableRows = Object.keys(versionInfoDict).map((key) => {
|
||||||
|
const value = versionInfoDict[key];
|
||||||
|
return (
|
||||||
|
<tr key={key}>
|
||||||
|
<td className="version-info-column">
|
||||||
|
<strong>{key}</strong>
|
||||||
|
</td>
|
||||||
|
<td className="version-info-column">{value}</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h2 title="This information is configurable by specifying values in version_info.json in the app at build time">
|
||||||
|
{title}
|
||||||
|
</h2>
|
||||||
|
<Table striped bordered>
|
||||||
|
<tbody>{tableRows}</tbody>
|
||||||
|
</Table>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>About</h1>
|
||||||
|
{versionInfoFromDict('Frontend version information', frontendVersionInfo)}
|
||||||
|
{versionInfoFromDict('Backend version information', backendVersionInfo)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in New Issue