diff --git a/.commitlintrc.yml b/.commitlintrc.yml
index dab71d5..ef03960 100644
--- a/.commitlintrc.yml
+++ b/.commitlintrc.yml
@@ -15,3 +15,5 @@ rules:
always,
[build, chore, ci, docs, feat, fix, merge, perf, refactor, revert, test],
]
+extends:
+ - '@commitlint/config-conventional'
diff --git a/.github/workflows/build-test-publish.yml b/.github/workflows/build-test-publish.yml
index a0ee908..29c7479 100644
--- a/.github/workflows/build-test-publish.yml
+++ b/.github/workflows/build-test-publish.yml
@@ -4,10 +4,11 @@
name: Build
on:
+ pull_request:
push:
branches: ["*"]
- pull_request: [master]
tags: ["v*.*.*"]
+ workflow_dispatch:
jobs:
test:
@@ -24,18 +25,13 @@ jobs:
- os: windows-latest
python_version: "3.11"
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Set up Python ${{matrix.python_version}}
- uses: actions/setup-python@v2
+ uses: actions/setup-python@v5
with:
+ cache: pip
+ cache-dependency-path: pyproject.toml
python-version: ${{matrix.python_version}}
- - uses: actions/cache@v2
- with:
- path: ~/.cache/pip
- key: ${{runner.os}}-pip-${{hashFiles('pyproject.toml')}}
- restore-keys: |
- ${{runner.os}}-pip-
- ${{runner.os}}-
- name: Upgrade Pip
run: |-
python -m pip install -U pip
@@ -46,14 +42,29 @@ jobs:
run: |-
python -m pytest --cov-report=term --cov=capella_model_explorer --rootdir=.
- publish:
- name: Publish artifacts
+ pre-commit:
runs-on: ubuntu-latest
- needs: test
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
+ - uses: actions/setup-python@v5
+ with:
+ cache: pip
+ cache-dependency-path: pyproject.toml
+ python-version: "3.11"
+ - name: Upgrade pip
+ run: python -m pip install -U pip
+ - name: Install pre-commit
+ run: python -m pip install 'pre-commit>=3,<4'
+ - name: Run Pre-Commit
+ run: pre-commit run --all-files
+
+ build:
+ name: Build wheel
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
- name: Setup Python
- uses: actions/setup-python@v2
+ uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install dependencies
@@ -67,10 +78,25 @@ jobs:
run: |-
python -m twine check dist/*
- name: Upload artifacts
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
+ with:
+ name: python-package-distributions
+ path: 'dist/*'
+ pypi:
+ name: Publish to PyPI
+ runs-on: ubuntu-latest
+ needs: [build, test]
+ if: startsWith(github.ref, 'refs/tags/v')
+ environment:
+ name: pypi
+ url: https://pypi.org/project/capella-model-explorer
+ permissions:
+ id-token: write
+ steps:
+ - name: Download built wheel
+ uses: actions/download-artifact@v4
with:
- name: Artifacts
- path: "dist/*"
- - name: Publish to PyPI (release only)
- if: startsWith(github.ref, 'refs/tags/v')
- run: python -m twine upload -u __token__ -p ${{ secrets.PYPI_TOKEN }} --non-interactive dist/*
+ name: python-package-distributions
+ path: dist/
+ - name: Publish to PyPI
+ uses: pypa/gh-action-pypi-publish@release/v1
diff --git a/.github/workflows/commit-check.yml b/.github/workflows/commit-check.yml
index f06701d..7b34e18 100644
--- a/.github/workflows/commit-check.yml
+++ b/.github/workflows/commit-check.yml
@@ -10,12 +10,15 @@ on:
jobs:
conventional-commits:
runs-on: ubuntu-latest
+ concurrency:
+ group: commit-check-pr-${{ github.event.pull_request.number }}
+ cancel-in-progress: true
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install commitlint
- run: npm install -g @commitlint/cli
+ run: npm install @commitlint/cli @commitlint/config-conventional
- name: Validate commit messages
id: conventional-commits
env:
@@ -25,14 +28,24 @@ jobs:
delim="_EOF_$(uuidgen)"
echo "validation-result<<$delim" >> "$GITHUB_OUTPUT"
r=0
- commitlint --from "$SHA_FROM" --to "$SHA_TO" >> "$GITHUB_OUTPUT" 2>&1 || r=$?
+ npx commitlint --from "$SHA_FROM" --to "$SHA_TO" >> "$GITHUB_OUTPUT" 2>&1 || r=$?
echo "$delim" >> "$GITHUB_OUTPUT"
exit $r
+ - name: Find conventional commit comment on PR
+ uses: peter-evans/find-comment@v3
+ if: always() && steps.conventional-commits.outcome == 'failure'
+ id: fc
+ with:
+ issue-number: ${{ github.event.pull_request.number }}
+ comment-author: 'github-actions[bot]'
+ body-includes: conventional commit
- name: Post comment if validation failed
+ uses: peter-evans/create-or-update-comment@v4
if: always() && steps.conventional-commits.outcome == 'failure'
- uses: actions/github-script@v6
- env:
- TEXT: |-
+ with:
+ comment-id: ${{ steps.fc.outputs.comment-id }}
+ issue-number: ${{ github.event.pull_request.number }}
+ body: |
The pull request does not conform to the conventional commit specification. Please ensure that your commit messages follow the spec:
' - f"Unexpected error: {type(e).__name__}: {str(e)}" + f"Unexpected error: {type(e).__name__}: {e}" '
' - f"object={repr(object)}\nmodel={repr(self.model)}" + f"object={object!r}\nmodel={self.model!r}" f"\n\n{trace}" "" ) return HTMLResponse(content=error_message) - def configure_routes(self): + def configure_routes(self): # noqa: C901 self.app.mount( f"{ROUTE_PREFIX}/assets", StaticFiles( @@ -206,12 +203,12 @@ def read_template(template_name: str): template_name = urlparse.unquote(template_name) if ( self.templates_index is None - or not template_name in self.templates_index.flat + or template_name not in self.templates_index.flat ): return { "error": ( - f"Template {template_name} not found or" - + " templates index not initialized" + f"Template {template_name} not found" + " or templates index not initialized" ) } base = self.templates_index.flat[template_name] @@ -226,7 +223,7 @@ def render_template(template_name: str, object_id: str): template_name = urlparse.unquote(template_name) if ( self.templates_index is None - or not template_name in self.templates_index.flat + or template_name not in self.templates_index.flat ): return {"error": f"Template {template_name} not found"} base = self.templates_index.flat[template_name] @@ -286,7 +283,9 @@ async def post_compare(commit_range: CommitRange): self.model, commit_range.head, commit_range.prev ) self.diff["lookup"] = create_diff_lookup(self.diff["objects"]) - return {"success": True} + if self.diff["lookup"]: + return {"success": True} + return {"success": False, "error": "No model changes to show"} except Exception as e: LOGGER.exception("Failed to compare versions") return {"success": False, "error": str(e)} @@ -301,8 +300,10 @@ async def post_object_diff(object_id: ObjectDiffID): @self.router.get("/api/commits") async def get_commits(): - result = model_diff.populate_commits(self.model) - return result + try: + return model_diff.populate_commits(self.model) + except Exception as e: + return {"error": str(e)} @self.router.get("/api/diff") async def get_diff(): @@ -364,9 +365,8 @@ def create_diff_lookup(data, lookup=None): "change": obj["change"], "attributes": obj["attributes"], } - if "children" in obj: - if obj["children"]: - create_diff_lookup(obj["children"], lookup) + if children := obj.get("children"): + create_diff_lookup(children, lookup) except Exception: LOGGER.exception("Cannot create diff lookup") return lookup diff --git a/capella_model_explorer/backend/model_diff.py b/capella_model_explorer/backend/model_diff.py index c4f7cce..a01d214 100644 --- a/capella_model_explorer/backend/model_diff.py +++ b/capella_model_explorer/backend/model_diff.py @@ -98,7 +98,7 @@ class ChangeSummaryDocument(te.TypedDict): objects: ObjectChanges -def init_model(model: capellambse.MelodyModel) -> t.Optional[str]: +def init_model(model: capellambse.MelodyModel) -> str | None: """Initialize the model and return the path if it's a git repository.""" file_handler = model.resources["\x00"] path = file_handler.path @@ -119,16 +119,15 @@ def populate_commits(model: capellambse.MelodyModel): path = init_model(model) if not path: return path - commits = get_commit_hashes(path) - return commits + return get_commit_hashes(path) def _serialize_obj(obj: t.Any) -> t.Any: if isinstance(obj, m.ModelElement): return {"uuid": obj.uuid, "display_name": _get_name(obj)} - elif isinstance(obj, m.ElementList): + if isinstance(obj, m.ElementList): return [{"uuid": i.uuid, "display_name": _get_name(i)} for i in obj] - elif isinstance(obj, (enum.Enum, enum.Flag)): + if isinstance(obj, enum.Enum | enum.Flag): return obj.name return obj @@ -168,7 +167,7 @@ def _get_revision_info( .strip() .split("\x00") ) - subject = description.splitlines()[0] + subject = description.splitlines()[0] if description.splitlines() else "" try: tag = subprocess.check_output( ["git", "tag", "--points-at", revision], @@ -197,8 +196,7 @@ def get_commit_hashes(path: str) -> list[RevisionInfo]: cwd=path, encoding="utf-8", ).splitlines() - commits = [_get_revision_info(path, c) for c in commit_hashes] - return commits + return [_get_revision_info(path, c) for c in commit_hashes] def _get_name(obj: m.ModelObject) -> str: @@ -232,7 +230,6 @@ def compare_objects( new_object: capellambse.ModelObject, old_model: capellambse.MelodyModel, ): - assert old_object is None or type(old_object) is type( new_object ), f"{type(old_object).__name__} != {type(new_object).__name__}" @@ -396,16 +393,16 @@ def _traverse_and_diff(data) -> dict[str, t.Any]: and "current" in value ): curr_type = type(value["current"]) - if curr_type == str: + if curr_type is str: diff = _diff_text( (value["previous"] or "").splitlines(), value["current"].splitlines(), ) updates[key] = {"diff": diff} - elif curr_type == dict: + elif curr_type is dict: diff = _diff_objects(value["previous"], value["current"]) updates[key] = {"diff": diff} - elif curr_type == list: + elif curr_type is list: diff = _diff_lists(value["previous"], value["current"]) updates[key] = {"diff": diff} elif key == "description": @@ -413,7 +410,7 @@ def _traverse_and_diff(data) -> dict[str, t.Any]: (value["previous"] or "").splitlines(), value["current"].splitlines(), ) - if prev == curr == None: + if prev is curr is None: continue updates[key] = {"diff": ""} value.update({"previous": prev, "current": curr}) @@ -462,8 +459,8 @@ def _diff_lists(previous, current): def _diff_description( previous, current -) -> t.Tuple[str, str] | t.Tuple[None, None]: - if previous == current == None: +) -> tuple[str, str] | tuple[None, None]: + if previous is current is None: return None, None dmp = diff_match_patch.diff_match_patch() diff = dmp.diff_main("\n".join(previous), "\n".join(current)) diff --git a/capella_model_explorer/backend/templates.py b/capella_model_explorer/backend/templates.py index 9e5bade..ca8fecd 100644 --- a/capella_model_explorer/backend/templates.py +++ b/capella_model_explorer/backend/templates.py @@ -4,7 +4,7 @@ import operator import traceback from pathlib import Path -from typing import Any, Dict, List, Optional +from typing import Any import capellambse import capellambse.model as m @@ -12,7 +12,7 @@ from pydantic import BaseModel, Field -def simple_object(obj) -> Dict[str, Any]: +def simple_object(obj) -> dict[str, Any]: if not obj: return {} if obj.name: @@ -50,9 +50,8 @@ def find_objects(model, obj_type=None, below=None, attr=None, filters=None): if filter == "not_empty": if attr: filtered.append(object) - else: - if attr == filter: - filtered.append(object) + elif attr == filter: + filtered.append(object) objects = filtered return objects @@ -63,11 +62,11 @@ class InstanceDetails(BaseModel): class TemplateScope(BaseModel): - type: Optional[str] = Field(None, title="Model Element Type") - below: Optional[str] = Field( + type: str | None = Field(None, title="Model Element Type") + below: str | None = Field( None, title="Model element to search below, scope limiter" ) - filters: Optional[Dict[str, Any]] = Field( + filters: dict[str, Any] | None = Field( {}, title="Filters to apply to the search" ) @@ -78,19 +77,19 @@ class Template(BaseModel): template: Path = Field(..., title="Template File Path") description: str = Field(..., title="Template Description") - scope: Optional[TemplateScope] = Field( + scope: TemplateScope | None = Field( default_factory=dict, title="Template Scope" ) - single: Optional[bool] = Field(None, title="Single Instance Flag") - isStable: Optional[bool] = Field(None, title="Stable Template Flag") - isDocument: Optional[bool] = Field(None, title="Document Template Flag") - isExperimental: Optional[bool] = Field( + single: bool | None = Field(None, title="Single Instance Flag") + isStable: bool | None = Field(None, title="Stable Template Flag") + isDocument: bool | None = Field(None, title="Document Template Flag") + isExperimental: bool | None = Field( None, title="Experimental Template Flag" ) - error: Optional[str] = Field(None, title="Broken Template Flag") - traceback: Optional[str] = Field(None, title="Template Error Traceback") - instanceCount: Optional[int] = Field(None, title="Number of Instances") - instanceList: Optional[List[Dict]] = Field(None, title="List of Instances") + error: str | None = Field(None, title="Broken Template Flag") + traceback: str | None = Field(None, title="Template Error Traceback") + instanceCount: int | None = Field(None, title="Number of Instances") + instanceList: list[dict] | None = Field(None, title="List of Instances") def find_instances(self, model: capellambse.MelodyModel): if self.single: @@ -106,8 +105,7 @@ def find_instances(self, model: capellambse.MelodyModel): attr=None, filters=self.scope.filters, ) - else: - return [] + return [] except Exception as e: self.error = f"Template scope error: {e}" self.traceback = traceback.format_exc() @@ -124,7 +122,7 @@ def compute_instance_list(self, model: capellambse.MelodyModel): class TemplateCategory(BaseModel): idx: str = Field(..., title="Category Identifier") - templates: List[Template] = Field( + templates: list[Template] = Field( default_factory=list, title="Templates in this category" ) @@ -137,7 +135,7 @@ def __add__(self, other): class TemplateCategories(BaseModel): - categories: List[TemplateCategory] = Field( + categories: list[TemplateCategory] = Field( default_factory=list, title="Template Categories" ) diff --git a/frontend/.eslintrc.cjs b/frontend/.eslintrc.cjs index 71ff1a6..dd09ade 100644 --- a/frontend/.eslintrc.cjs +++ b/frontend/.eslintrc.cjs @@ -13,7 +13,11 @@ module.exports = { ], ignorePatterns: ['dist', '.eslintrc.cjs'], parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, - settings: { react: { version: '18.2' } }, + settings: { + react: { + version: '18.2' + } + }, plugins: ['react-refresh'], rules: { 'react/jsx-no-target-blank': 'off', @@ -22,6 +26,6 @@ module.exports = { 'warn', { allowConstantExport: true } ], - 'max-len': ['error', { code: 79 }] + 'max-len': ['error', { code: 100, ignoreUrls: true, ignoreStrings: true }] } }; diff --git a/frontend/.prettierrc.js b/frontend/.prettierrc.js index d17e3d4..2947e97 100644 --- a/frontend/.prettierrc.js +++ b/frontend/.prettierrc.js @@ -4,11 +4,18 @@ */ module.exports = { - plugins: [require.resolve('prettier-plugin-tailwindcss')], + plugins: [ + require.resolve('prettier-plugin-tailwindcss'), + require.resolve('prettier-plugin-classnames'), + require.resolve('prettier-plugin-merge') + ], semi: true, tabWidth: 2, printWidth: 79, singleQuote: true, trailingComma: 'none', - bracketSameLine: true + bracketSameLine: true, + endOfLine: 'lf', + useTabs: false, + endingPosition: 'absolute-with-indent' }; diff --git a/frontend/.storybook/main.js b/frontend/.storybook/main.js index b11f63a..14e108f 100644 --- a/frontend/.storybook/main.js +++ b/frontend/.storybook/main.js @@ -3,14 +3,14 @@ /** @type { import('@storybook/react-vite').StorybookConfig } */ const config = { - stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"], + stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], addons: [ - "@storybook/addon-links", - "@storybook/addon-essentials", - "@storybook/addon-onboarding", - "@storybook/addon-interactions", + '@storybook/addon-links', + '@storybook/addon-essentials', + '@storybook/addon-onboarding', + '@storybook/addon-interactions', { - name: "@storybook/addon-postcss", + name: '@storybook/addon-postcss', options: { postcssLoaderOptions: { implementation: require('postcss') @@ -19,11 +19,11 @@ const config = { } ], framework: { - name: "@storybook/react-vite", - options: {}, + name: '@storybook/react-vite', + options: {} }, docs: { - autodocs: "tag", - }, + autodocs: 'tag' + } }; export default config; diff --git a/frontend/.storybook/preview.js b/frontend/.storybook/preview.js index b27ad0f..8c248a6 100644 --- a/frontend/.storybook/preview.js +++ b/frontend/.storybook/preview.js @@ -1,19 +1,19 @@ // Copyright DB InfraGO AG and contributors // SPDX-License-Identifier: Apache-2.0 -import '../src/index.css' +import '../src/index.css'; /** @type { import('@storybook/react').Preview } */ const preview = { parameters: { - actions: { argTypesRegex: "^on[A-Z].*" }, + actions: { argTypesRegex: '^on[A-Z].*' }, controls: { matchers: { color: /(background|color)$/i, - date: /Date$/i, - }, - }, - }, + date: /Date$/i + } + } + } }; export default preview; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f3f0d70..76ab84b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,6 +8,7 @@ "name": "frontend", "version": "0.0.0", "dependencies": { + "@icons-pack/react-simple-icons": "^10.0.0", "lucide-react": "^0.439.0", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -42,6 +43,8 @@ "globals": "^15.9.0", "postcss": "^8.4.45", "prettier": "^3.3.3", + "prettier-plugin-classnames": "^0.7.2", + "prettier-plugin-merge": "^0.7.1", "prettier-plugin-tailwindcss": "^0.6.6", "prop-types": "^15.8.1", "react-router-dom": "^6.26.2", @@ -2531,6 +2534,14 @@ "deprecated": "Use @eslint/object-schema instead", "dev": true }, + "node_modules/@icons-pack/react-simple-icons": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@icons-pack/react-simple-icons/-/react-simple-icons-10.0.0.tgz", + "integrity": "sha512-oU0PVDx9sbNQjRxJN555dsHbRApYN+aBq/O9+wo3JgNkEfvBMgAEtsSGtXWWXQsLAxJcYiFOCzBWege/Xj/JFQ==", + "peerDependencies": { + "react": "^16.13 || ^17 || ^18" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -6039,6 +6050,15 @@ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" }, + "node_modules/diff": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", @@ -10299,6 +10319,43 @@ "node": ">=6.0.0" } }, + "node_modules/prettier-plugin-classnames": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/prettier-plugin-classnames/-/prettier-plugin-classnames-0.7.2.tgz", + "integrity": "sha512-rocYqVSWV/YSJE+TA7b1IgYY9/I4bx0lxJjE5Iwv/kavNNEYhKh7Gl1+MQARQYgPisGMd5DU8Uj6ZEVX0KmTTA==", + "dev": true, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "prettier": "^2 || ^3", + "prettier-plugin-astro": "*", + "prettier-plugin-svelte": "*" + }, + "peerDependenciesMeta": { + "prettier-plugin-astro": { + "optional": true + }, + "prettier-plugin-svelte": { + "optional": true + } + } + }, + "node_modules/prettier-plugin-merge": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/prettier-plugin-merge/-/prettier-plugin-merge-0.7.1.tgz", + "integrity": "sha512-R3dSlv3kAlScjd/liWjTkGHcUrE4MBhPKKBxVOvHK7+FY2P5SEmLarZiD11VUEuaMRK0L7zqIurX6JcRYS9Y5Q==", + "dev": true, + "dependencies": { + "diff": "5.1.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "prettier": "^2 || ^3" + } + }, "node_modules/prettier-plugin-tailwindcss": { "version": "0.6.6", "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.6.tgz", diff --git a/frontend/package.json b/frontend/package.json index 1609fed..cf9ecee 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,9 +10,10 @@ "storybook": "storybook dev -p 6006", "build-storybook": "storybook build", "lint:fix": "eslint . --fix", - "format": "prettier --write ./**/*.{js,jsx,ts,tsx,css,md,json} --config ./.prettierrc.json" + "format": "prettier --write ./**/*.{js,jsx,ts,tsx,css,md,json} --config ./.prettierrc.js" }, "dependencies": { + "@icons-pack/react-simple-icons": "^10.0.0", "lucide-react": "^0.439.0", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -39,7 +40,6 @@ "dompurify": "^3.1.6", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", - "eslint-plugin-prettier": "^5.2.1", "eslint-plugin-react": "^7.35.2", "eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-react-refresh": "^0.4.11", @@ -47,6 +47,8 @@ "globals": "^15.9.0", "postcss": "^8.4.45", "prettier": "^3.3.3", + "prettier-plugin-classnames": "^0.7.2", + "prettier-plugin-merge": "^0.7.1", "prettier-plugin-tailwindcss": "^0.6.6", "prop-types": "^15.8.1", "react-router-dom": "^6.26.2", diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js index f139503..61dd434 100644 --- a/frontend/postcss.config.js +++ b/frontend/postcss.config.js @@ -6,6 +6,6 @@ module.exports = { plugins: { tailwindcss: {}, - autoprefixer: {}, - }, -} + autoprefixer: {} + } +}; diff --git a/frontend/public/static/env.js b/frontend/public/static/env.js index 83ba5c4..1f83af3 100644 --- a/frontend/public/static/env.js +++ b/frontend/public/static/env.js @@ -4,6 +4,6 @@ */ window.env = { - ROUTE_PREFIX: "__ROUTE_PREFIX__", - API_BASE_URL: "__ROUTE_PREFIX__/api", + ROUTE_PREFIX: '__ROUTE_PREFIX__', + API_BASE_URL: '__ROUTE_PREFIX__/api' }; diff --git a/frontend/src/APIConfig.js b/frontend/src/APIConfig.js index b0b75f1..422211d 100644 --- a/frontend/src/APIConfig.js +++ b/frontend/src/APIConfig.js @@ -5,8 +5,8 @@ var API_BASE_URL = window.env.API_BASE_URL; var ROUTE_PREFIX = window.env.ROUTE_PREFIX; -if (!process.env.NODE_ENV || process.env.NODE_ENV === "development") { - ROUTE_PREFIX = ""; +if (!process.env.NODE_ENV || process.env.NODE_ENV === 'development') { + ROUTE_PREFIX = ''; API_BASE_URL = `http://localhost:8000${ROUTE_PREFIX}/api`; } export { API_BASE_URL, ROUTE_PREFIX }; diff --git a/frontend/src/App.css b/frontend/src/App.css index ae634d3..071727e 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -44,3 +44,11 @@ .read-the-docs { color: #888; } + +html.dark .svg-display svg { + filter: hue-rotate(180deg) invert(80%) saturate(200%); +} + +html.dark .icon svg { + filter: none; +} diff --git a/frontend/src/components/AppInfo.jsx b/frontend/src/components/AppInfo.jsx new file mode 100644 index 0000000..8b69f11 --- /dev/null +++ b/frontend/src/components/AppInfo.jsx @@ -0,0 +1,37 @@ +// Copyright DB InfraGO AG and contributors +// SPDX-License-Identifier: Apache-2.0 + +import { useState, useEffect } from 'react'; +import { ROUTE_PREFIX } from '../APIConfig'; +import { SiGithub } from '@icons-pack/react-simple-icons'; + +export const AppInfo = () => { + const [currentVersion, setCurrentVersion] = useState(null); + + useEffect(() => { + fetch(`${ROUTE_PREFIX}/static/version.json`) + .then((response) => response.json()) + .then((data) => setCurrentVersion(data.git)) + .catch((error) => { + console.error(error); + setCurrentVersion({ version: 'Fetch failed' }); + }); + }, []); + + return ( +
Version: {currentVersion.version}
} + + Contribute on GitHub +