Skip to content

MOOI-Project-UvA/rule-editor

Repository files navigation

Note:

This is branch of the editor focuses on expanding its functionality towards translating the generated FLINT norm specification into an executable form (eFLINT). Part of code contributed by https://github.com/Azoth4006/bsc-thesis.

screenshot_eflint.png

Figure 1: Screenshot of the "Make Interpretations executable" tab that translates the FLINT specification to eFLINT

Rule editor

The Rule Editor is an application built using web-based technologies that allows users to create interpretations of sources of norms in FLINT in a user-friendly and interactive way. The tool was built using Vue.js and Quasar. The app is deployed on Netlify and uses Netlify functions (serverless functions, including Edge Functions) to extend back-end capabilities.

Try it out

There are two public versions of the Rule Editor available:

Feel free to explore both!

Table of Contents

  1. Application Overview
  2. Features
  3. User Manual
  4. Data Model
  5. Getting Started
  6. Codebase Structure
  7. Development
  8. Environment Variables
  9. Contributing
  10. License

Application Overview

The Rule Editor is a web application for interpreting normative tasks. To use the editor, a task is defined and the normative text (sources) describing the task and the constraints for its execution are collected and imported into the editor. The editor allows users to mark components (e.g., articles, sections, or sentences) of the source as relevant or irrelevant to the task. Relevant sources can be annotated and used as building blocks for the interpretation.

The current version of the Editor enables users to express interpretations in FLINT. The Editor is designed to be easily extendable for other interpretation schemes. It allows users to get automated recommendations, while working on their interpretation by using the FlintFiller. This feature is experimental and available only for Dutch texts.

The Editor uses normative text in JSON or RDF format, according to the Source of Norms Ontology. Text documents in .txt, .xml, or .html format can be translated into this format by the Choppr tool.

Interpretations made using the Rule Editor can be stored as JSON or TriG files locally on your computer or remotely to a linked database/triple store. When choosing a Linked Data output, interpretation data conforms to the FLINT ontology. Linked Data on interpretations, sources, and the related task is bundled in the output according to the Calculemus ontology.

Features

User Manual

Interface overview

The interface of the Rule Editor consists of five main views:

  1. Set task
  2. Collect sources
  3. Interpret sources
  4. Make interpretations executable (not yet in use)
  5. Execute task (not yet in use)

Navigate between views using the tabs at the top of the page.

Set task (step 1)

In the current version one, it is recommended to start by defining a task and to edit three fields

Field Explanation Example
Editor Field to register the name of the person using the Rule editor John Doe
Label Label to refer to the task National Budget Cycle
Description Description of the task What is needed to arrive at an approved national budget?

It is possible to navigate across all views without restrictions.

Collect sources (step 2)

There are three ways of adding sources to the editor:

Field Explanation Example
Dropdown menu labelled "Add source from server" Field to add a source from the Rule editor server General Data Protection Regulation
Dropdown menu labelled "Add source from Triply" Field to add a source from the Triply linked data store General Data Protection Act
Button labelled "Upload source from local filesystem" A button that allows to add a source from local filesystem (in JSON-LD). Examples of source can be found here. AI_Act.json

Select the source or the sources you consider relevant in relation to the task you are working on.

Use the checkboxes to select or deselect text fragments. There are also buttons to select all or to deselect all.

You can always get back to this screen to add additional sources, or remove redundant sources or text fragments.

Once you have selected the relevant sources for your task, you can go to the next step by clicking the Interpret sources tab.

The only way to navigate between screens is by using the tabs at the top of the page.

Interpret sources (step 3)

The interpretation screen consists of four main panes:

  • Source pane: Contains the selected sources.
  • Source of selected frame: Indicates the source of the selected frame.
  • Frames: Lists the created frames by type.
  • Edit: Where you can create and modify FLINT frames.

Each pane is collapsible, allowing you to allocate more space to the pane you are focussing on.

Source pane

Expanding source text

You can use the expand and collapse icons to show or hide the text.

Selecting text

Select a text fragment (from a single word to multiple lines). When your selection is complete, a modal dialog appears, allowing you to:

  • Create a frame (act, claim, or fact), or
  • Link the selected text fragment to an existing frame.

You can also click on selected lines to delete the selection or open the frame in the Edit pane for further analysis.

Source of selected frame

This pane provides an overview of the linked text fragments (per source) for the currently selected frame.

Frames

The Frames pane lists all created frames by type. You can filter frames by label using the search field at the top. Alternatively, you can switch to a network visualization, which shows how different frames are related. Switch between List and Network views using the provided radio buttons.

Edit

Here you can create, edit, and delete frames. Supported frame types are act, claim, and fact.

Facts

When starting your intepretation, begin by creating some facts.

You create a fact by selecting a text fragment. Click on the selected fragment and click on the fact button. On the right-hand side of the view a fact frame appears.

The selected text appears in the fields short name and full name. If necessary one can make changes in the text, e.g. change the conjugation of an action from the present perfect to the present simple tense.

For facts with a longer text fragments, add a short name.

A fact consists of:

  • the text fragment from a source
  • the full name, that is the same as the a literal text from the fragment, adjustments can be made regarding to:
    • the conjugation of the action,
    • punctuation issues,
    • making a readable sentence in case of the combination of multiple fragments from a single sentence, or even from multiple sentences,
    • making implicit information explicit (add a comment to explicitly lay down the implication).
  • a short name for longer text fragments.

Examples are given below.

Fact types and roles

You can classify facts as one of the following:

  • Agent
  • Action
  • Object
  • Duty

Which role a fact takes depends on how it is used in act frames or claim frames:

  • Agents: can be actors or recipients in act frames, or claimants/duty holders in claim frames.
  • Actions are verbs used in act frames.
  • Objects are the things an action refers to, or the thing created or terminated as the result of a valid act.
  • Duties refers to acts that must be executed by (or on behalf of) a duty holder for a claimant. In other words every duty is created by an act and can be terminated by another act.
An example: Article 4 and 5 GDPR

Search for agents (persons that can have the role of actor, recipient, claimant, or duty holder, e.g. a processor, a controller, or data subject ), actions (e.g. processing, collection, erasure), objects (e.g. personal data, or collected data), conditions (e.g. 1. Personal data shall be: (a) processed lawfully, fairly and in a transparent manner in relation to the data subject (‘lawfulness, fairness and transparency’);)

The data subject, processor and controller can be found in Article 4(1),(7) and (8).

The action verbs that can be derived from processing, collection, erasure in Article 4(2) are: process, collect and erase.

Article 5 is about the processing of personal data, Article 5(1)(b) is about collected personal data (the result of collecting personal data).

The condition that 1. Personal data shall be: (a) processed lawfully, fairly and in a transparent manner in relation to the data subject (‘lawfulness, fairness and transparency’); can, e.g., be transformed to the long name personal data shall be processed lawfully, fairly and in a transparent manner in relation to the data subject (‘lawfulness, fairness and transparency’). The short name could be, e.g., personal data shall be processed lawfully, fairly and in a transparent manner.

Acts

Create acts in two ways:

  1. Select sentences and mark them as an act frame,
  2. Select pre-existing fact frames and assign them a role in an act frame.

To create core-acts (the combination of an action, the actor performing the action, the object that is acted upon and the recipient of the result of the action), it is advised to select whole sentences.

Claims

A claim contains:

  • a duty
  • a claimant
  • a duty holder.

The claimant holds a claim that the duty holder fulfills the duty.

A duty should be linked to:

  • One or more acts that can create the duty
  • One or more acts that terminate the duty

Make interpretations executable (step 4)

Not yet available.

Execute task (step 5)

Not yet available.

Data Model

The tool uses an internal data structure that differs slightly from the JSON and RDF format in which the interpretations are stored.

The definitions of the classes used in the tool can be found in the folder gui/src/model. The main classes are:

  • frame: This is a class representing a frame. It is the superclass of act, fact, and claim_duty. Each frame has the following attributes:

    • id: A unique id, generated when a frame is instantiated.
    • typeId: The name of the frame's type (one of 'fact','act','claim_duty')
    • shortName: A short label as displayed in the frame list.
    • fullName: The full name of the frame, displayed when hovering the frame in the frame list.
    • annotations: A list of annotation objects. See below for a detailed explanation of an annotation.
    • comments: A list of comment objects. These are remarks and considerations that the user can store as notes attached to the frame when creating the interpretation.
  • fact: A frame of type fact. In addition to the attributes inherited from frame it has:

    • subTypeIds: A possibly empty list of subtype id's. A fact can have zero, one, or more subtypes. Valid subtype id's are: 'agent', 'action', 'object', 'duty', 'condition'.
    • subDivision: An object of type booleanConstruct representing a subdivision of a fact. A fact can consist of other facts, but not necessarily. This attribute speficies how a fact is subdivided. See below for an explanation of a booleanConstruct.
  • act: A frame of type act. In addition to the attributes inherited from frame it has attributes for the roles of an act frame:

    • action: An object of type fact representing the action of an act
    • actor: An object of type fact representing the actor of an act
    • object: An object of type fact representing the object of an act
    • precondition: A booleanConstruct representing a fact or a combination of facts that form the precondition of an act
    • recipient: An object of type fact representing the recipient of an act
    • creates: A list of zero or more fact objects created by an act
    • terminates: A list of zero or more fact objects terminated by an act

    In addition, an act has auxiliary attributes, not part of the external data model:

    • activeField: The role of the act that is currently selected by the user
    • generateLabelAutomatically: If true, labels (shortName and fullName) are generated automatically for the act
  • claimduty: A frame of type claim-duty. In addition to the attributes inherited from frame it has attributes for the roles of a claim-duty frame:

    • duty: An object of type fact representing the duty of a claim-duty
    • claimant: An object of type fact representing the claimant of a claim-duty
    • holder: An object of type fact representing the holder of a claim-duty

    In addition, a claim-duty has auxiliary attributes, not part of the data model:

    • activeField: The role of the claim-duty that is currently selected by the user
    • generateLabelAutomatically: If true, labels (shortName and fullName) are generated automatically for the claim-duty
  • booleanConstruct: This class is used to specify a combination of frames. Frames are combined using functions, e.g. boolean operators like OR and AND. A booleanConstruct can be nested: it can combine other boolean constructs as in: booleanConstruct_1 AND booleanConstruct_2. A booleanConstruct is a tree, where the leafs are facts and all other nodes are booleanConstructs. It has the following attributes:

    • frame: This attributes holds a frame in case the booleanConstruct is a leaf, i.e. it is a single frame, not a combination of frames.
    • children: If the booleanConstruct is not a leaf (i.e. frame is not null), this attribute is a list of booleanConstruct objects.
    • operatorToJoinChildren: The function with which to join the children, e.g. a boolean operator.
    • isNegated (deprecated): If a frame is specified, this attribute tells whether the frame should be negated (i.e. the unary boolean function NOT is applied). [DEPRECATED] This property will be removed in future versions. Use operatorToJoinChildren instead.
  • sourceDocument: This class holds a source document, e.g. the content of a law like the Participatiewet. Its constructor reads a jsonLD object and parses it into a nested structure of sentence objects. The leafs of this structure are individual sentences. The nodes higher in the hierarchy represent paragraphs, chapters, etc. It has these attributes:

    • title: The title of the source document
    • sentenceTree: A nested structure of sentence objects
  • sentence: The text of a sentence, or a heading (i.e. label of a heading, paragraph, section). The text of a sentence (or heading) is divided in snippets (see below) so that an annotation of a frame can contain part of a sentence, and not necessarily complete sentences. Its most important attributes are:

    • sourceDocument: The sourceDocument this sentence is part of
    • snippets: A list of snippet objects that comprises this sentence
    • children: If this sentence is a section or other higher-level element in the document: a list of sentence objects that together form this section
  • snippet: An atomic piece of text. The text is specified as a character range within a sentence. The snippet refers to all annotations of which it is a part. Its attributes are:

    • id: A unique identifier
    • sentence: The sentence object the snippet is part of
    • characterRange: The start and end indices of the character range within the sentence. A snippet that starts from the beginning of the sentence has start index zero
    • annotations: The annotations associated with this snippet
  • annotation: This object links snippets to a frame. The snippets form the annotation of the frame. Its attributes are:

    • id: A unique identifier
    • frame: The frame object that this is the annotation of The link between snippets and annotation is stored in the snippets, see above. Auxiliary attributes used for drawing coloured lines under the source text are:
    • nrSnippets: Number of snippets of this annotation
    • verticalPosition: The vertical position of the coloured line that marks this annotation in the source text

Codebase Structure

.
├── interpretations/            # List of example interpretations
├── gui/                        # UI code    
│    ├── netlify/              
│    │      ├── edge-functions/ # Edge functions
│    │      └── functions/      # Serverless functions
│    ├── public/                # Static assets (favicon, robots.txt, etc.)
│    ├── src/
│    │     ├── assets/
│    │     ├── components/      # The components performing functionalities are here used by the views in the views/ 
│    │     ├── helpers/         # Reusable functions and utilities that are used by components 
│    │     ├── model/           # The data model that the application is based on
│    │     ├── services/        # the calls to the endpoints are defined here 
│    │     ├── store/           # The centralized store for the components 
│    │     ├── views/           # The main views of the UI are defined here 
│    │     ├── App.vue          # Main Vue root component    
│    │     └── main.js          # Application bootstrap JavaScript entry point (mounts Vue app).
│    ├── .gitignore             # Git ignored files    
│    ├── Dockerfile
│    ├── .eslintrc.js           # ESLint code linting configuration.
│    ├── .prettierrc.json       # Prettier configuration    
│    ├── index.html             # application entry point, root DOM node   
│    ├── package-lock.json      # Records the full, exact dependency tree and versions
│    ├── package.json           # Project dependencies and scripts
│    ├── .env                   # Environment variables
│    └── vite.config.js         # Vite and dev server configuration, plugin registration
├── auth-service/               # Dedicated authentication API (cookie/session based)
│    ├── app.py
│    ├── requirements.txt
│    └── scripts/
├── .gitignore                  # Git ignored files   
├── docker-compose.yml
├── netlify.toml                # Netlify build, functions and routing configuration
├── LICENSE                     # Project license
└── README.md                   # Project documentation

Getting started

Prerequisites

  • Node.js (version 18.x or later recommended)
  • npm
  • Netlify CLI (version 17.38 has been used) for local function testing

Dependencies

  • To connect to TriplyDB an access token with TRIPLY is needed. You can register here and become more familiar with their environment here.
  • To use the Netlify CLI an access token to authenticate with Netlify is needed. You can obtain this token using the Netlify UI. See the docs.

To run the editor locally for development purposes. You can do the following:

Clone the repository

git clone git@gitlab.com:normativesystems/ui/interpretation-editor.git
cd interpretation-editor

Go to the gui directory

From project root, navigate to the gui folder:

cd gui/

Install the necessary dependencies

npm ci

Local development

Start the development server with hot reload

npm run dev

By default, vite allocates port 5137 to serve the application. Once the app is running, you can view the UI at localhost:5137. If you would like to use another port, you can also pass the additional --port=XXXXparameter:

npm run dev -- --port=XXXX

You can also pass the --open parameter to have the UI automatically opened in a new browser tab:

npm run dev -- --open

Have in mind that by following the previous steps, you will not be able to use Netlify's serverless functions. If you want to develop and test Netlify functions locally, use the following command:

netlify dev

This starts both SPA and Netlify Functions with live reload.

Building

To create a production version of your app:

npm run build

You can preview the production build with npm run preview.

Static frontend deployment (self-hosted)

If you want to run the GUI as static files (without netlify dev), use this flow.

  1. Configure frontend runtime targets in gui/.env.production (example values for local stack):

(or replace with actual URL instead of localhost if live in production)

VITE_AUTH_ENABLED=true
VITE_AUTH_API_BASE_URL=http://localhost:8101
VITE_EFLINT_API_BASE_URL=http://localhost:8000
VITE_EFLINT_EXECUTE_URL=http://localhost:8001
VITE_EFLINT_SERVER_BASE_URL=http://localhost:8080
VITE_MONGO_API_BASE_URL=http://localhost:8102
VITE_X_API_KEY=
  1. Build static assets from gui/:
npm run build
  1. Serve the generated dist folder (example):
npx serve -s dist -l 4173
  1. Open http://localhost:4173.

Production tip (Nginx static hosting)

If your server serves files from /var/www/rule-editor/dist, deploy updates with:

sudo rsync -av --delete ~/rule-editor/gui/dist/ /var/www/rule-editor/dist/

Then validate and reload Nginx:

sudo nginx -t
sudo systemctl reload nginx

Quick checks when you see 500:

  • Confirm index.html exists at /var/www/rule-editor/dist/index.html.
  • Ensure your server block uses the same root path (for example root /var/www/rule-editor/dist;).
  • Keep SPA fallback as try_files $uri $uri/ /index.html; and avoid proxying / to a stopped dev upstream (e.g. 127.0.0.1:8888).

Notes:

  • VITE_* values are embedded at build time, so rebuild after changing .env.production.
  • Set VITE_EFLINT_SERVER_BASE_URL for static deployments so session endpoints (/sessions, /reset, /spec/register, /statements, /query/holds) target your eflint_server instance.
  • For Mongo-backed project storage/loading, VITE_MONGO_API_BASE_URL must be set.
  • Netlify Functions paths (/api/serverless/*) are not available in pure static hosting; Triply-related actions will not work unless you replace those endpoints with your own backend.

Unified backend stack via Docker Compose (auth + eFLINT + mongo-api + MongoDB)

From the project root, you can start the self-hosted backend dependencies with one command.

  1. Create and fill stack environment file:
cp .env.stack.example .env.stack
  1. Add at least one auth user hash to AUTH_USERS_JSON (in .env.stack), for example:
cd auth-service
python scripts/generate_auth_user.py --username editor --env-file ../.env.stack
cd ..
  1. Start services:
docker compose up -d --build
  1. Check health endpoints:
curl http://localhost:8101/health
curl http://localhost:8000/health

This starts:

  • auth-service on http://localhost:8101
  • flint-to-eflint on http://localhost:8000
  • mongo-api on http://localhost:8102
  • mongodb on localhost:27017

Observability stack (Grafana + Prometheus + Loki)

The observability stack runs as a separate Docker Compose project that attaches to the backend network. The main app stack must be running first so the shared Docker network exists.

Services exposed locally:

  • grafana on http://127.0.0.1:3000
  • prometheus on http://127.0.0.1:9090
  • loki on http://127.0.0.1:3100
  • promtail and blackbox-exporter (internal only)

Setup:

  1. Create and edit the monitoring env file:
cp deploy/monitoring/monitoring.env.example deploy/monitoring/monitoring.env
# Edit: set GRAFANA_ADMIN_PASSWORD and verify MONITOR_TARGET_NETWORK
# Default network name (rule-editor_rule-editor) is correct if the project folder is named rule-editor.
# Confirm with: docker network ls | grep rule-editor
  1. Start the monitoring stack:
docker compose --env-file deploy/monitoring/monitoring.env -f deploy/monitoring/docker-compose.monitoring.yml up -d
  1. Open Grafana at http://127.0.0.1:3000 and log in.

Grafana is pre-provisioned with Prometheus and Loki datasources and a starter dashboard (Rule Editor Observability) showing:

  • health probe status for all core service /health endpoints
  • centralized container logs from the Docker Compose project

Stop monitoring stack:

docker compose --env-file deploy/monitoring/monitoring.env -f deploy/monitoring/docker-compose.monitoring.yml down

Deploy to Netlify

Want to deploy immediately to Netlify? Click this button

Deploy to Netlify Button

Clicking this button will create a new repo for you that looks exactly like this one, and sets that repo up immediately for deployment on Netlify.

Development

Netlify functions (Serverless API)

Netlify functions are in the functions/ directory. To test them locally with the app:

netlify dev

You can then make HTTP requests to /.netlify/functions/[function-name].

For adding new functions, simply create a new folder in the /functions folder and add an .mjs or .js file to it.

You can find more information in the Netlify Functions docs.

Netlify Edge functions

Edge functions live in /.netlify/edge-fuctions/. See Netlify Edge Functions docs.

Routing for Edge functions is configured in netlify.toml.

Environment variables

The application requires several environment variables to function properly, especially for accessing external services such as TriplyDB. Add these variables to your Netlify dashboard or a local .env file as appropriate.

Variable Explanation
TRIPLY_KEY_R API key required for reading from TriplyDB (Requires the creation of an account on the TRIPLY website and they can issue one for you)
TRIPLY_KEY_W API key required for writing to TriplyDB (Requires the creation of an account on the TRIPLY website and they can issue one for you)
TRIPLY_ENDPOINT Base URL of the TriplyDB instance/API (Requires the creation of an account on the TRIPLY website)
X_API_KEY Generated API key for authentication purposes. Used by the Edge function to redirect to the correct TRIPLY_DB serverless functions. Create your own.
ALLOWED_DOMAINS The domain from which the Edge function should expect the requests. If you run a local development server, you can set the value to http://localhost:[designated_port]
VITE_X_API_KEY API key required for fetching NLP predictions, and converting your interpretation in RDF and JSON to save them locally. Please contact us to generate one for you.

Create an .env file in the gui folder and define the environment variable there.

Optional self-hosted authentication (no Netlify dependency)

Authentication is implemented as a dedicated API service in auth-service/app.py. The eFLINT translation backend (flint-to-eflint/service/app.py) only validates signed session cookies and no longer stores user password hashes.

Authentication service environment variables:

Variable Explanation
AUTH_USERS_JSON Preferred. JSON object mapping username to Argon2 hash, e.g. {"editor":"$argon2id$...","alice":"$argon2id$..."}
AUTH_SESSION_SECRET Secret used to sign session cookies
AUTH_SESSION_TTL_SECONDS Session lifetime in seconds (default 28800)
AUTH_COOKIE_NAME Cookie name (default rule_editor_session)
AUTH_COOKIE_SECURE Set true in HTTPS production environments
AUTH_ALLOWED_ORIGINS Comma-separated CORS origins for GUI, e.g. http://localhost:5173,http://localhost:8888

eFLINT translation backend environment variables:

Variable Explanation
AUTH_SESSION_SECRET Must match auth-service secret to verify signed cookies
AUTH_COOKIE_NAME Must match auth-service cookie name
AUTH_ALLOWED_ORIGINS Comma-separated CORS origins for GUI, e.g. http://localhost:5173,http://localhost:8888

Frontend environment variables (in gui/.env):

Variable Explanation
VITE_AUTH_ENABLED Enable login gate (true / false, default true)
VITE_AUTH_API_BASE_URL Base URL for auth endpoints (optional; empty means same origin)
VITE_EFLINT_API_BASE_URL Base URL for /generate-eflint (optional; set this when eFLINT API is on another origin)
VITE_EFLINT_EXECUTE_URL Base URL for reasoner endpoints (/execute, /repl/*) (optional; empty means same origin)
VITE_EFLINT_SERVER_BASE_URL Base URL for eflint_server session endpoints (/sessions, /reset, /spec/register, /statements, /query/holds). Set explicitly for production/static hosting.
VITE_MONGO_API_BASE_URL Base URL for Mongo intermediate API (optional; falls back to Netlify functions when empty)

Generate an Argon2 hash locally:

python -c "from argon2 import PasswordHasher; print(PasswordHasher().hash('your-password'))"

Streamlined user creation (recommended):

cd auth-service
pip install -r requirements.txt
# cp secrets.env.example secrets.env
python scripts/generate_auth_user.py --username editor --env-file .env.stack
python scripts/generate_auth_user.py --username alice --env-file .env.stack

Then start auth service, backend, and frontend (example local development):

# auth-service
cd auth-service
uvicorn app:app --reload --host 0.0.0.0 --port 8101

# eFLINT backend
cd flint-to-eflint
pip install -r requirements.txt
uvicorn service.app:app --reload --host 0.0.0.0 --port 8000

# frontend
cd gui
npm install
npm run dev

This updates AUTH_USERS_JSON in auth-service/secrets.env and lets you add users incrementally without manually editing Argon2 hashes.

MongoDB artifact ownership (multi-user)

MongoDB saves are now scoped per user.

  • Every saved export stores a canonical owner_username.
  • Save versioning is computed per (owner_username, project_id).
  • Retrieval endpoints (projects list, versions list, task retrieval) are filtered by owner, so users only see their own saved artifacts.
  • For backward compatibility, legacy records without owner_username are matched using metadata.owner.

If you apply Mongo schema changes on an existing database, re-run the init/migration script from mongodb/init/01-init-rule-editor.js and backfill legacy documents where needed.

Contributing

We welcome contributions of all kinds! Whether you're fixing a bug, adding features, improving documentation, or just suggesting an idea, we’re happy to collaborate.

How to Contribute

  1. Fork this repository.
  2. Clone your fork (git clone https://gitlab.com/your-username/ui/interpretation-editor.git)
  3. Create a branch for your changes:
git checkout -b my-feature-or-bugfix
  1. Install dependencies
npm install
  1. Make your changes (and add tests if possible).
  2. Commit your changes:
git commit -am 'Add some feature'
  1. Push to your forked repository:
git push origin my-feature-or-bugfix
  1. Open a Merge Request (GitLab guide)

License

This project is licensed under the Apache License 2.0.

See the LICENSE file for the details.

About

No description, website, or topics provided.

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors