|
|
|
# Front End Architecture
|
|
|
|
|
|
|
|
> **Version:** 1.0 | **Last updated:** 2026-05-18 | **Author:** Tomi Karikj
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
## Table of Contents
|
|
|
|
|
|
|
|
1. [Project Overview](#1-project-overview)
|
|
|
|
2. [Tech Stack & Architecture](#2-tech-stack--architecture)
|
|
|
|
3. [Getting Started](#3-getting-started)
|
|
|
|
4. [Key Conventions](#4-key-conventions)
|
|
|
|
5. [Data Flow](#5-data-flow)
|
|
|
|
6. [Testing Strategy](#6-testing-strategy)
|
|
|
|
7. [CI/CD & Deployment](#7-cicd--deployment)
|
|
|
|
8. [Appendix](#8-appendix)
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
## 1. Project Overview
|
|
|
|
|
|
|
|
### 1.1 What is this product?
|
|
|
|
|
|
|
|
Perun Core is the shared front-end module of the **Svarog framework**. It provides the core application shell — login, navigation, admin console, plugin management, and a library of reusable React components (grids, forms, modals, dropdowns) — to every other Svarog module.
|
|
|
|
|
|
|
|
Other modules consume `perun-core` as an **npm dependency**. The compiled `www/perun-core.js` bundle is the primary artifact. Dependent projects import React components, Redux store utilities, and helper functions directly from it at runtime; they do not re-bundle this code.
|
|
|
|
|
|
|
|
### 1.2 Business context
|
|
|
|
|
|
|
|
- Perun Core is a **library first, application second**. Changes here can break all dependent Svarog modules, so stability matters more than speed.
|
|
|
|
- The compiled bundle (`www/perun-core.js`) is committed to the repository so dependent projects can consume it via `npm install` without running a build.
|
|
|
|
- On every push to `dev` or `main`, CI can auto-commit a freshly built bundle back to the branch (manual trigger — see §7).
|
|
|
|
|
|
|
|
### 1.3 Key links
|
|
|
|
|
|
|
|
| Resource | Link / Location |
|
|
|
|
|---|---|
|
|
|
|
| Repository | Internal GitLab |
|
|
|
|
| Issue tracker | Internal GitLab project board |
|
|
|
|
| CI/CD pipeline | `.gitlab-ci.yml` in repo root |
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
## 2. Tech Stack & Architecture
|
|
|
|
|
|
|
|
### 2.1 Core technologies
|
|
|
|
|
|
|
|
| Technology | Version & Notes |
|
|
|
|
|---|---|
|
|
|
|
| Framework | React 16.14 — kept at 16 for compatibility with older dependent modules |
|
|
|
|
| Language | JavaScript (ES2020+), transpiled with Babel |
|
|
|
|
| Styling | Plain CSS + CSS Modules for scoped component styles |
|
|
|
|
| State management | Redux 4 — `redux-updeep`, `redux-magic-async-middleware`, `redux-promise-middleware`, `redux-thunk` |
|
|
|
|
| HTTP | axios 1.x with global request/response interceptors |
|
|
|
|
| Routing | React Router DOM 5 (hash-based routing via `createHashHistory`) |
|
|
|
|
| i18n | react-intl v2 + react-intl-redux; labels fetched from the backend at startup |
|
|
|
|
| Forms | RJSF (`@rjsf/core` v5) with AJV8 validator |
|
|
|
|
| Grids | react-data-grid v5 (`ExportableGrid`, `GenericGrid`) |
|
|
|
|
| Icons | `@tabler/icons-react` 3.x |
|
|
|
|
| Build | Webpack 5, Babel loader, thread-loader (parallelised), filesystem cache |
|
|
|
|
| Linting | ESLint 9 with `eslint-plugin-react` and `eslint-plugin-react-hooks` |
|
|
|
|
| Analytics | react-ga (Google Analytics, optional) |
|
|
|
|
|
|
|
|
### 2.2 Folder structure
|
|
|
|
|
|
|
|
```
|
|
|
|
perun-core/
|
|
|
|
frontend/
|
|
|
|
assets/ — global CSS and theme files
|
|
|
|
client.js — Webpack entry point; exports the public API; bootstraps the app
|
|
|
|
components/ — Feature components (AdminConsole, Logon, Navbar, Modal, etc.)
|
|
|
|
config/ — svConfig (REST base URL) and labelBasePath
|
|
|
|
containers/ — Legacy Redux-connected container components
|
|
|
|
elements/ — Reusable UI primitives exported to dependent modules
|
|
|
|
base/ — Button, Dropdown, DependencyDropdown, InputElement
|
|
|
|
form/ — FormManager, GenericForm, CustomOnchangeFunction
|
|
|
|
grid/ — ExportableGrid, GenericGrid, GridManager, toolbar, row renderers
|
|
|
|
util/ — Icon, alertUser, alertUserV2
|
|
|
|
functions/ — Pure utility functions (utils.js, cookies.js)
|
|
|
|
loadConfiguration/ — Configurator HOC; loads DB-driven component config at mount
|
|
|
|
model/ — Redux store, reducers, actions
|
|
|
|
actions/ — Action creators (logon, grid, form, notifications, …)
|
|
|
|
reducers/ — One file per Redux slice
|
|
|
|
store.js — Store factory; injectAsyncReducer / removeAsyncReducer helpers
|
|
|
|
createReducers.js — Combines all static and async reducers
|
|
|
|
routes/ — Router setup, PluginManager, route-level page components
|
|
|
|
backend/ — Java OSGi bundle (Activator, PerunPluginInfo) — minimal, rarely touched
|
|
|
|
www/ — Webpack output; perun-core.js is committed here for npm consumers
|
|
|
|
docs/ — index.html.template for consuming projects
|
|
|
|
```
|
|
|
|
|
|
|
|
### 2.3 Architecture decisions
|
|
|
|
|
|
|
|
- **Hash-based routing** — `createHashHistory` is used because consuming projects are typically served from Java OSGi containers that don't support HTML5 push-state.
|
|
|
|
- **Redux-persist whitelist** — only specific slices are persisted to `sessionStorage` (see `whitelistRoot` in `client.js`). Dependent modules register their own slices via `persistBundleReducers`.
|
|
|
|
- **Async reducer injection** — plugins can call `injectAsyncReducer` / `removeAsyncReducer` to mount their own Redux slices at runtime without rebuilding the core bundle.
|
|
|
|
- **`window.server`** — the backend URL is injected by the consuming project's `config.js` file as `window.server` before the bundle loads. There is no `.env` equivalent for the API URL.
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
## 3. Getting Started
|
|
|
|
|
|
|
|
### 3.1 Prerequisites
|
|
|
|
|
|
|
|
| Tool | Required version |
|
|
|
|
|---|---|
|
|
|
|
| Node.js | >= 18.12.0 |
|
|
|
|
| npm | >= 8 |
|
|
|
|
| Java JDK | 11 (only needed for the Maven/OSGi bundle build) |
|
|
|
|
|
|
|
|
### 3.2 Local setup
|
|
|
|
|
|
|
|
```bash
|
|
|
|
# 1. Clone the repo
|
|
|
|
git clone <internal-gitlab-url>/perun-core.git
|
|
|
|
cd perun-core
|
|
|
|
|
|
|
|
# 2. Install dependencies
|
|
|
|
npm install
|
|
|
|
|
|
|
|
# 3. Create www/config.js pointing at your backend
|
|
|
|
echo "window.server = 'http://<host>:<port>/services'" > www/config.js
|
|
|
|
|
|
|
|
# 4. Create www/index.html (copy from docs/index.html.template, customize as needed)
|
|
|
|
|
|
|
|
# 5. Start the dev server
|
|
|
|
npm run dev
|
|
|
|
# Open http://localhost:8080 (or the port printed in terminal)
|
|
|
|
```
|
|
|
|
|
|
|
|
### 3.3 Environment variables
|
|
|
|
|
|
|
|
Set in a `.env` file at the project root (loaded by Webpack via `dotenv/config`):
|
|
|
|
|
|
|
|
| Variable | Purpose | Required |
|
|
|
|
|---|---|---|
|
|
|
|
| `GA_TRACKING_ID` | Google Analytics tracking ID | No |
|
|
|
|
|
|
|
|
For CI/CD set variables under **GitLab > Settings > CI/CD > Variables**.
|
|
|
|
|
|
|
|
Note: the backend URL is **not** an env variable — it is set as `window.server` in `www/config.js` at runtime.
|
|
|
|
|
|
|
|
### 3.4 Common scripts
|
|
|
|
|
|
|
|
| Command | What it does |
|
|
|
|
|---|---|
|
|
|
|
| `npm run dev` | Dev server with hot reload, source maps, debug on |
|
|
|
|
| `npm run build` | Production build to `www/` — minified, no source maps |
|
|
|
|
| `npm run build-dev` | Production build to `www/` — with source maps and debug |
|
|
|
|
| `npm run test` | ESLint with auto-fix (`eslint --fix frontend/`) |
|
|
|
|
|
|
|
|
### 3.5 Using your local build in a dependent project
|
|
|
|
|
|
|
|
After `npm run build` (or `build-dev`), copy `www/perun-core.js` into:
|
|
|
|
|
|
|
|
```
|
|
|
|
<dependent-project>/node_modules/perun-core/www/perun-core.js
|
|
|
|
```
|
|
|
|
|
|
|
|
This is the standard workflow because the bundle is not published to a registry.
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
## 4. Key Conventions
|
|
|
|
|
|
|
|
### 4.1 Naming
|
|
|
|
|
|
|
|
| Thing | Convention |
|
|
|
|
|---|---|
|
|
|
|
| Component files | PascalCase — `ExportableGrid.js` |
|
|
|
|
| Utility / hook files | camelCase — `utils.js`, `cookies.js` |
|
|
|
|
| CSS Modules files | `ComponentName.module.css` |
|
|
|
|
| Redux action name constants | camelCase keys in `actionNames.json` |
|
|
|
|
| Redux slice files | camelCase — `logonReducer.js` |
|
|
|
|
|
|
|
|
### 4.2 Component patterns
|
|
|
|
|
|
|
|
- Prefer **function components with hooks**. Class components exist in older code (e.g. `Configurator`) but must not be added.
|
|
|
|
- Keep data-fetching out of presentational components — use Redux actions or custom hooks in the parent.
|
|
|
|
- `react/prop-types` validation is disabled by ESLint config. Don't add prop-types to new components.
|
|
|
|
- Prefix intentionally unused variables with `_` to satisfy the `no-unused-vars` rule.
|
|
|
|
|
|
|
|
### 4.3 State management
|
|
|
|
|
|
|
|
| When to use | Mechanism |
|
|
|
|
|---|---|
|
|
|
|
| Transient UI state (open/closed, loading flag) | `useState` in the component |
|
|
|
|
| Data shared across components | Redux slice |
|
|
|
|
| Plugin-specific data | Async reducer injected via `injectAsyncReducer` |
|
|
|
|
|
|
|
|
### 4.4 Styling
|
|
|
|
|
|
|
|
- Global styles live in a **dedicated assets project**, organised by environment/application. They are loaded by the consuming project's `index.html` at runtime.
|
|
|
|
- `frontend/assets/css/style.css` exists for local development convenience — it lets you iterate on styles without deploying the assets project. It is **never committed with changes**.
|
|
|
|
- Component-scoped styles use CSS Modules (`*.module.css`). Webpack maps class names to `[name]-[local]`.
|
|
|
|
- No styled-components, Tailwind, or inline styles.
|
|
|
|
|
|
|
|
### 4.5 API calls
|
|
|
|
|
|
|
|
All HTTP calls go through **axios**. Two global interceptors are configured in `client.js`:
|
|
|
|
|
|
|
|
1. **Request** — injects the `sessionId` header from `store.getState().security.svSession`.
|
|
|
|
2. **Response error** — handles 401 (redirect to login + logout dispatch), 302, 502/503 globally.
|
|
|
|
|
|
|
|
The base URL comes from `window.server`. URLs are now constructed and passed directly by each caller — the `svConfig.triglavRestVerbs` map in `frontend/config/config.js` is legacy; do not add new entries to it.
|
|
|
|
|
|
|
|
The session token can also be embedded directly in the URL path (many backend endpoints expect it as a path segment). Retrieve it either via `mapStateToProps`:
|
|
|
|
|
|
|
|
```js
|
|
|
|
const mapStateToProps = state => ({ svSession: state.security.svSession })
|
|
|
|
```
|
|
|
|
|
|
|
|
or directly from the store outside of a component:
|
|
|
|
|
|
|
|
```js
|
|
|
|
const svSession = store.getState().security.svSession
|
|
|
|
```
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
## 5. Data Flow
|
|
|
|
|
|
|
|
### 5.1 Authentication
|
|
|
|
|
|
|
|
1. User submits credentials on the Logon screen.
|
|
|
|
2. `loginUser` action (axios POST) hits the backend login endpoint.
|
|
|
|
3. On success the response contains a `token` (the `svSession`). The `loginFulfilled` reducer stores it in `store.security.svSession`.
|
|
|
|
4. The axios request interceptor automatically attaches this token as `sessionId` on every subsequent request. It can also be embedded directly in the URL path when the endpoint expects it as a path segment — retrieve it via `mapStateToProps` or `store.getState().security.svSession`.
|
|
|
|
5. On 401, the response interceptor clears the session, dispatches `LOGOUT_FULFILLED`, and redirects to `/home/login`.
|
|
|
|
6. Session is persisted across page reloads via `redux-persist` (whitelisted slices include `security`).
|
|
|
|
|
|
|
|
There is no OAuth or JWT — the session token is an opaque server-side session ID.
|
|
|
|
|
|
|
|
### 5.2 API integration
|
|
|
|
|
|
|
|
- Base URL: `window.server` (set by the consuming project's `config.js`).
|
|
|
|
- URLs are constructed and passed directly by each caller.
|
|
|
|
- `dataToRedux` (`frontend/model/dataToRedux.js`) is a utility that fetches data and dispatches it directly to the Redux store — used mainly for i18n labels at startup.
|
|
|
|
- Errors are surfaced either via `alertUserV2` (toast/SweetAlert2 dialog) or by setting `title`/`message`/`status` in the relevant Redux slice.
|
|
|
|
|
|
|
|
### 5.3 Global state overview
|
|
|
|
|
|
|
|
| Redux slice | What it manages |
|
|
|
|
|---|---|
|
|
|
|
| `security` | Session token (`svSession`), login/logout state, user data |
|
|
|
|
| `configurator` | DB-driven component configurations loaded by the `Configurator` HOC |
|
|
|
|
| `intl` | Active locale and translated label messages |
|
|
|
|
| `gridConfig` | Grid column/row configuration per grid name |
|
|
|
|
| `selectedGridRows` | Currently selected rows per grid |
|
|
|
|
| `modal` | Active modal stack |
|
|
|
|
| `userInfo` | Logged-in user profile data |
|
|
|
|
| `businessLogicReducer` | Custom business logic flags shared across components |
|
|
|
|
| `moduleLinks` | Navigation links from the server |
|
|
|
|
| `clickedMenuReducer` | Currently active menu item |
|
|
|
|
| `routes` | Dynamically registered plugin routes |
|
|
|
|
| `globalRequestProcessor` | Global loading / in-flight request state |
|
|
|
|
| `notificationReducer` | Notification messages |
|
|
|
|
| `admConsoleFormData` | Admin Console form state |
|
|
|
|
| `checkForInvalidSession` | Flag to detect and handle expired sessions |
|
|
|
|
|
|
|
|
Dependent modules inject additional slices at runtime using `persistBundleReducers`.
|
|
|
|
|
|
|
|
### 5.4 Startup sequence
|
|
|
|
|
|
|
|
1. `client.js` is evaluated; axios interceptors and Redux store are created.
|
|
|
|
2. `persistBundleReducers(whitelistRoot)` is called → triggers `changeLanguageAndLocale`.
|
|
|
|
3. `changeLanguageAndLocale` calls `dataToRedux` to fetch i18n labels from the backend.
|
|
|
|
4. Once labels are loaded, the `Router` waits for plugin bootstrap (`waitForPlugins`).
|
|
|
|
5. When plugins are ready, `ReactDOM.render(<App />, app)` mounts the application.
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
## 6. Testing Strategy
|
|
|
|
|
|
|
|
### 6.1 What exists
|
|
|
|
|
|
|
|
There is currently **one automated check**: ESLint.
|
|
|
|
|
|
|
|
```bash
|
|
|
|
npm run test # runs: eslint --fix frontend/
|
|
|
|
```
|
|
|
|
|
|
|
|
There are no unit tests, integration tests, or E2E tests.
|
|
|
|
|
|
|
|
### 6.2 What to do before merging
|
|
|
|
|
|
|
|
1. `npm run test` — must pass with no errors.
|
|
|
|
2. `npm run build-dev` — must compile cleanly.
|
|
|
|
3. Manual smoke test in the browser against a running backend.
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
## 7. CI/CD & Deployment
|
|
|
|
|
|
|
|
### 7.1 Pipeline overview (`.gitlab-ci.yml`)
|
|
|
|
|
|
|
|
| Stage | Job | Trigger |
|
|
|
|
|---|---|---|
|
|
|
|
| `1_linter` | `npm ci` + ESLint + `npm run build` | Automatic on push |
|
|
|
|
| `2_build_and_install` | `mvn clean test` | Automatic on push |
|
|
|
|
| `3_mvn_install` | `mvn clean deploy` (publishes OSGi bundle to Maven repo) | Automatic — `dev` or `main` only |
|
|
|
|
| `3_mvn_install` | JS bundle auto-commit (`*-generate-js`) | **Manual** — maintainer triggers after review |
|
|
|
|
| `4_sonarqube` | SonarQube analysis | **Manual** |
|
|
|
|
|
|
|
|
### 7.2 Branches
|
|
|
|
|
|
|
|
| Branch | Purpose |
|
|
|
|
|---|---|
|
|
|
|
| `dev` | Active development; all feature branches merge here |
|
|
|
|
| `main` | Stable / release branch; only merge from `dev` when releasing |
|
|
|
|
|
|
|
|
### 7.3 Releasing a new bundle
|
|
|
|
|
|
|
|
1. Document all changes in `CHANGELOG.md` under a new version entry (e.g. `[v4.5.5] - 2026-05-18`).
|
|
|
|
2. Merge `dev` → `main`.
|
|
|
|
3. Push a version tag matching the release (e.g. `v4.5.5`).
|
|
|
|
4. CI builds and deploys the Maven artifact automatically.
|
|
|
|
5. A maintainer manually triggers the `*-generate-js` job, which:
|
|
|
|
- Runs `npm run build`
|
|
|
|
- Commits the updated `www/perun-core.js` back to the branch
|
|
|
|
6. Dependent projects run `npm install` to pick up the new commit.
|
|
|
|
|
|
|
|
### 7.4 Local build distribution
|
|
|
|
|
|
|
|
When you need a dependent project to use an unreleased build:
|
|
|
|
|
|
|
|
```bash
|
|
|
|
npm run build-dev
|
|
|
|
cp www/perun-core.js ../other-project/node_modules/perun-core/www/
|
|
|
|
```
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
## 8. Appendix
|
|
|
|
|
|
|
|
### 8.1 Glossary
|
|
|
|
|
|
|
|
| Term | Definition |
|
|
|
|
|---|---|
|
|
|
|
| `svSession` | The opaque session token returned by the backend on login; sent as `sessionId` on every request |
|
|
|
|
| `svConfig` | Global config object in `frontend/config/config.js` — holds `restSvcBaseUrl` and the legacy `triglavRestVerbs` map |
|
|
|
|
| `triglavRestVerbs` | Legacy map of named REST endpoint path templates in `svConfig` — deprecated, URLs are now passed directly by callers |
|
|
|
|
| `Configurator` | HOC that loads a DB-driven component configuration at mount and passes it as a `configuration` prop |
|
|
|
|
| `dataToRedux` | Utility that fetches data from the backend and dispatches it to the Redux store |
|
|
|
|
| `persistBundleReducers` | Function exported from `client.js`; dependent modules call it to register their Redux slices for persistence and trigger the app bootstrap |
|
|
|
|
| `injectAsyncReducer` | Adds a Redux reducer to the live store at runtime (used by plugins) |
|
|
|
|
| `PluginManager` | Manages dynamic route and reducer registration for Svarog plugins |
|
|
|
|
| `ExportableGrid` | Grid component that supports search, selection, and CSV/XLSX export |
|
|
|
|
| `GenericForm` | RJSF-based form component driven by a JSON Schema fetched from the backend |
|
|
|
|
| Svarog | The broader Java + React framework that `perun-core` belongs to |
|
|
|
|
| OSGi bundle | The Java packaging format used to deploy modules to the Svarog backend container |
|
|
|
|
|
|
|
|
### 8.2 Document history
|
|
|
|
|
|
|
|
| Version | Date | Author | Changes |
|
|
|
|
|---|---|---|---|
|
|
|
|
| 1.0 | 2026-05-18 | Tomi Karikj | Initial draft |
|
|
|
|
|
|
|
|
--- |
|
|
\ No newline at end of file |