From 0766a4521a8516c626fcd96b29710e5d57ff99fa Mon Sep 17 00:00:00 2001 From: Nicholas Dalhaug Date: Fri, 14 Aug 2026 15:17:31 +0200 Subject: [PATCH 1/4] Move unnecessary parts to best practices --- README.md | 126 +++++++++++++++++-------------------- backend/README.md | 61 +++++++++++------- backend/best_practices.md | 23 +++++++ broker/README.md | 103 ++++++------------------------ broker/best_practices.md | 68 ++++++++++++++++++++ frontend/README.md | 70 +++++++-------------- frontend/best_practices.md | 35 +++++++++-- setup.sh | 13 ++-- 8 files changed, 263 insertions(+), 236 deletions(-) create mode 100644 backend/best_practices.md create mode 100644 broker/best_practices.md diff --git a/README.md b/README.md index c6b72eedd..f573cea92 100644 --- a/README.md +++ b/README.md @@ -4,111 +4,99 @@ [![Frontend](https://github.com/equinor/flotilla/actions/workflows/lint_frontend_package.yml/badge.svg)](https://github.com/equinor/flotilla/actions/workflows/lint_frontend_package.yml) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/0b37a44f66044dbc81fff906344b476e)](https://www.codacy.com/gh/equinor/flotilla/dashboard?utm_source=github.com&utm_medium=referral&utm_content=equinor/flotilla&utm_campaign=Badge_Grade) -Flotilla is the main point of access for operators to interact with multiple robots in multiple facilities. -The application consists of a [frontend](frontend) in React, a [backend](backend) in ASP.NET, and a Mosquitto MQTT [Broker](broker). +Flotilla is the main point of access for operators to interact with multiple robots in multiple facilities. +The application consists of a [frontend](frontend) in React, a [backend](backend) in ASP.NET, and a Mosquitto MQTT [broker](broker). -## Deployments - -We currently have 3 environments (Development, Staging, and Production) deployed to AKS under `robotics.equinor.com`. - -| Environment | Deployment | Status | -| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Development | [Frontend](https://flotilla.dev.robotics.equinor.com/)
[Backend](https://flotilla.dev.robotics.equinor.com/api/swagger) | [![Dev](https://github.com/equinor/flotilla/actions/workflows/deploy_to_development.yml/badge.svg)](https://github.com/equinor/flotilla/actions/workflows/deploy_to_development.yml) | -| Staging | [Frontend](https://flotilla.staging.robotics.equinor.com/)
[Backend](https://flotilla.staging.robotics.equinor.com/api/swagger) | [![Staging](https://github.com/equinor/flotilla/actions/workflows/deploy_to_staging.yml/badge.svg)](https://github.com/equinor/flotilla/actions/workflows/deploy_to_staging.yml) | -| Production | [Frontend](https://flotilla.robotics.equinor.com/)
[Backend](https://flotilla.robotics.equinor.com/api/swagger) | [![Production](https://github.com/equinor/flotilla/actions/workflows/promote_to_production.yml/badge.svg)](https://github.com/equinor/flotilla/actions/workflows/promote_to_production.yml) | - -## Setup - -For development, please fork the repository. Then, clone the repository: - -``` -git clone https://github.com/equinor/flotilla -``` - -Please see separate installation guides for the [frontend](frontend), [backend](backend), and [Broker](broker). For the environment setup, either run the script as described below or set it up manually as described in each component's guide. +## Prerequisites -### Automatic environment setup - -Run the [setup.sh](./setup.sh) to automatically set up your dev environment for the components. -This script will ask you for the `Client Secret` for the backend and the `MQTT broker server key` for the MQTT broker. - -## Run with make -Common commands for the project are in Makefiles. See [frontend makefile](./frontend/Makefile), [backend makefile](./backend/Makefile) and [root makefile](./Makefile). -This requires the CLI program `make`. - -Usage: -```bash -make # for example: make run -``` +| Tool | Version | Needed for | +| ------------------------------------------------------------------------------------------------------------ | ------- | -------------------------------- | +| [Docker](https://docs.docker.com/engine/install/) and [Docker Compose](https://docs.docker.com/compose/install/) | latest | Running the full stack | +| [.NET SDK](https://dotnet.microsoft.com/download) | 10.x | Running the backend directly | +| [Node.js](https://github.com/nodesource/distributions) | 24.x | Running the frontend directly | +| [pnpm](https://pnpm.io/installation) | latest | Frontend package management | +| `make` | any | The shorthand commands below |
-Installation MacOS +Installing make on MacOS ```bash brew install make ``` +
-Installation Windows +Installing make on Windows ```bash choco install make ``` -
-## Run with Docker + -Install [Docker](https://docs.docker.com/engine/install/ubuntu/) and [Docker Compose](https://docs.docker.com/compose/install/). +## Quick start -Build the Docker container: +For development, fork the repository first. Then clone it: -``` -docker compose build +```bash +git clone https://github.com/equinor/flotilla +cd flotilla ``` -Set up a .env file in the backend directory with the following environment variables: +Create the local configuration files: -``` -ASPNETCORE_ENVIRONMENT=Local -Local__DevUserId= -SARA__BaseUrl=https://localhost:8100/ -SARA__Scopes__0= +```bash +./setup.sh ``` -Start Flotilla by running: +The script creates `frontend/.env`, `backend/api/.env`, and `broker/.env` from their `.env.example` files. It prompts you for the **MQTT broker server key**, which is found in our key vault. Everything else can be configured manually afterwards — see [Configuration](#configuration). -``` -docker compose up +Start the full stack: + +```bash +make compose # docker compose up --build ``` -or +| Service | URL | +| ---------------- | --------------------------------------- | +| Frontend | | +| Backend Swagger | | +| Aspire dashboard | | -``` -docker compose up --build -``` +To run a single component instead, see the [frontend](frontend/README.md), [backend](backend/README.md), and [broker](broker/README.md) guides. -### Using the aspire dashboard +## Configuration -The docker-compose contains a container for the aspire dashboard to show telemetry and a opentelemetry collector to filter telemetry. -The aspire dashboard is available at http://localhost:18888 when run via docker compose. +Each component reads its configuration from a `.env` file. The matching `.env.example` file is the source of truth for the available variables. -To spin up only the broker, collector and aspire dashboard use -```bash -docker compose up broker aspire-dashboard otel-collector -``` +| Component | File | Template | Notes | +| --------- | ------------------- | --------------------------- | --------------------------------------------------------------------- | +| Frontend | `frontend/.env` | `frontend/.env.example` | Variables must be prefixed with `VITE_` to reach the application. | +| Backend | `backend/api/.env` | `backend/api/.env.example` | Set `Local__DevUserId` to your own user id for local development. | +| Broker | `broker/.env` | `broker/.env.example` | `TLS_SERVER_KEY` is a secret and is found in our key vault. | + +## Other make commands -## Connecting to the development database instead of initDb +Common commands are defined in the [root Makefile](./Makefile), the [backend Makefile](./backend/Makefile), and the [frontend Makefile](./frontend/Makefile): -Write in `backend/api/.env`: ```bash -Database__UseInMemoryDatabase=false -Database__PostgreSqlConnectionString=... +make compose # run the full stack in Docker +make broker # run only the MQTT broker +make broker-aspire # run the broker, OpenTelemetry collector, and Aspire dashboard ``` -The database connection string can be found in the keyvault in the development resource group in Azure. -Remember to add your IP address to the accepted IPs for cennecting to the database. + +## Deployments + +We currently have 3 environments (Development, Staging, and Production) deployed to AKS under `robotics.equinor.com`. + +| Environment | Deployment | Status | +| ----------- | --------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Development | [Frontend](https://flotilla.dev.robotics.equinor.com/)
[Backend](https://flotilla.dev.robotics.equinor.com/api/swagger) | [![Dev](https://github.com/equinor/flotilla/actions/workflows/deploy_to_development.yml/badge.svg)](https://github.com/equinor/flotilla/actions/workflows/deploy_to_development.yml) | +| Staging | [Frontend](https://flotilla.staging.robotics.equinor.com/)
[Backend](https://flotilla.staging.robotics.equinor.com/api/swagger) | [![Staging](https://github.com/equinor/flotilla/actions/workflows/deploy_to_staging.yml/badge.svg)](https://github.com/equinor/flotilla/actions/workflows/deploy_to_staging.yml) | +| Production | [Frontend](https://flotilla.robotics.equinor.com/)
[Backend](https://flotilla.robotics.equinor.com/api/swagger) | [![Production](https://github.com/equinor/flotilla/actions/workflows/promote_to_production.yml/badge.svg)](https://github.com/equinor/flotilla/actions/workflows/promote_to_production.yml) | ## Contributions Equinor welcomes all kinds of contributions, including code, bug reports, issues, feature requests, and documentation. -Please initiate your contribution by creating an [issue](https://github.com/equinor/isar/issues) or by forking the +Please initiate your contribution by creating an [issue](https://github.com/equinor/flotilla/issues) or by forking the project and making a pull request. Commit messages shall be written according to [this guide](https://cbea.ms/git-commit/). diff --git a/backend/README.md b/backend/README.md index 1136450cd..2b73507b6 100644 --- a/backend/README.md +++ b/backend/README.md @@ -2,9 +2,28 @@ The backend of Flotilla is an ASP.NET application. See the [.NET documentation](https://docs.microsoft.com/en-us/dotnet/fundamentals/) for framework concepts. -For general setup (environment, Docker, database connection), see the [root README](../README.md). +For development conventions and gotchas, see [best_practices.md](./best_practices.md). -## Common commands +## Prerequisites + +- [.NET SDK 10.x](https://dotnet.microsoft.com/download) +- [`dotnet-ef`](https://docs.microsoft.com/en-us/ef/core/cli/dotnet), if you need to work with migrations: + + ```bash + dotnet tool install --global dotnet-ef + ``` + +## Configuration + +The backend reads environment variables from `backend/api/.env`. Create it by running [`setup.sh`](../setup.sh) from the repository root, or by copying the template manually: + +```bash +cp api/.env.example api/.env +``` + +Set `Local__DevUserId` to your own user id for local development. + +## Run Common commands are defined in the [Makefile](./Makefile): @@ -13,26 +32,34 @@ make run # dotnet run --project api make build # dotnet build api make test # dotnet test make format # dotnet csharpier format . -make migration name=AddSomething # create a new EF Core migration ``` -## Database migrations (EF Core) +Swagger is served at . -The database model lives in [`api/Database/Models`](./api/Database/Models) and we use [Entity Framework Core](https://docs.microsoft.com/en-us/ef/core/) as an O/RM. When changing the model, add a [migration](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/). +To run the backend in Docker together with the rest of the stack, see the [root README](../README.md#quick-start). + +## Connecting to the development database -Install the EF Core CLI once: +By default the backend runs against an in-memory database. To use the development database instead, add the following to `backend/api/.env`: ```bash -dotnet tool install --global dotnet-ef +Database__UseInMemoryDatabase=false +Database__PostgreSqlConnectionString=... ``` +The connection string is found in the key vault in the development resource group in Azure. Remember to add your IP address to the accepted IPs for connecting to the database. + +## Database migrations (EF Core) + +The database model lives in [`api/Database/Models`](./api/Database/Models) and we use [Entity Framework Core](https://docs.microsoft.com/en-us/ef/core/) as an O/RM. When changing the model, add a [migration](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/). + Create a new migration (make sure you have the latest `main` and that no one else is adding a migration at the same time): ```bash make migration name=AddTableNamePropertyName ``` -This adds files under `backend/api/Migrations` that must be committed. `add` does not modify any database — it only describes the changes. +This adds files under `backend/api/Migrations` that must be committed. Adding a migration does not modify any database — it only describes the changes. To discard a migration you're not happy with: @@ -45,20 +72,12 @@ dotnet ef migrations remove - **Development**: after merging a PR that touches `backend/api/Migrations`, manually run the ["Run database migrations (Development)"](https://github.com/equinor/flotilla/actions/workflows/run_development_migrations.yml) workflow. - **Staging / Production**: applied automatically by the [deploy_to_staging](https://github.com/equinor/flotilla/blob/main/.github/workflows/deploy_to_staging.yml) and [promote_to_production](https://github.com/equinor/flotilla/blob/main/.github/workflows/promote_to_production.yml) workflows. -## Formatting +## Monitoring + +The backend is instrumented with [OpenTelemetry](https://opentelemetry.io/). Traces, metrics, and logs are exported via OTLP to a Grafana-compatible backend. -Formatting rules are defined in the [.editorconfig](../.editorconfig). We use [CSharpier](https://csharpier.com/) to auto-format on save (see [installation](https://csharpier.com/docs/About)). To check formatting locally: +Locally, telemetry can be inspected in the Aspire dashboard at . It is started as part of `make compose`, or on its own together with the broker and the OpenTelemetry collector: ```bash -make format +make broker-aspire ``` - -## SignalR - -We use SignalR to push event updates to the frontend via `SignalRService`. Event names must match what the frontend expects. - -Do **not** await SignalR sends — in the current library version, awaiting from an async thread can cause the thread to silently exit without an exception. Let SignalR run after the current thread completes and ignore the await warning. - -## Monitoring - -The backend is instrumented with [OpenTelemetry](https://opentelemetry.io/). Traces, metrics, and logs are exported via OTLP to a Grafana-compatible backend. Locally, telemetry can be inspected in the Aspire dashboard (see the [root README](../README.md#using-the-aspire-dashboard)). diff --git a/backend/best_practices.md b/backend/best_practices.md new file mode 100644 index 000000000..e53dc80d7 --- /dev/null +++ b/backend/best_practices.md @@ -0,0 +1,23 @@ +# Backend best practices + +Conventions and gotchas for working on the Flotilla backend. For installation and how to run the backend, see the [backend README](./README.md). + +## Formatting + +Formatting rules are defined in the [.editorconfig](../.editorconfig). We use [CSharpier](https://csharpier.com/) to auto-format on save (see [installation](https://csharpier.com/docs/About)). To format locally: + +```bash +make format +``` + +CI enforces this — a PR fails if the code is not CSharpier-formatted. + +## SignalR + +We use SignalR to push event updates to the frontend via `SignalRService`. Event names must match what the frontend expects. + +Do **not** await SignalR sends — in the current library version, awaiting from an async thread can cause the thread to silently exit without an exception. Let SignalR run after the current thread completes and ignore the await warning. + +## Migrations + +Any change to the model in [`api/Database/Models`](./api/Database/Models) requires a migration. Avoid adding a migration at the same time as someone else, since migrations are ordered and conflicts have to be resolved by regenerating them. See the [backend README](./README.md#database-migrations-ef-core) for the commands. diff --git a/broker/README.md b/broker/README.md index 980c842ac..a635b8e68 100644 --- a/broker/README.md +++ b/broker/README.md @@ -1,102 +1,35 @@ -# Mosquitto MQTT Broker +# Mosquitto MQTT broker -## Set up the broker +The Flotilla MQTT broker is a [Mosquitto](https://mosquitto.org/) broker with TLS and role-based authentication. -The broker expects a private key for its server x509 certificate used for TLS. -This must be provided through an environment variable called `TLS_BROKER_SERVER_KEY`. -This is a secret and should be treated as such. It can be found in our key vault. +For authentication details, testing, and certificate management, see [best_practices.md](./best_practices.md). -### Automatic environment setup +## Prerequisites -See [Flotilla readme](../README.md#automatic-environment-setup) +- [Docker](https://docs.docker.com/engine/install/) and [Docker Compose](https://docs.docker.com/compose/install/) -### Manual environment setup +## Configuration -The best way to pass this is to store it in a `.env` file in the broker folder in flotilla, and Docker Compose loads this by default on startup. -See [Using the “--env-file” option](https://docs.docker.com/compose/environment-variables/#using-the---env-file--option) for more information. +The broker expects the private key for its server x509 certificate, used for TLS, in the environment variable `TLS_SERVER_KEY`. This is a secret and should be treated as such — it is found in our key vault. -## Running the broker +Create the configuration by running [`setup.sh`](../setup.sh) from the repository root, which prompts you for the key. Alternatively, copy the template and fill it in manually: -From the flotilla root directory, run the following command: - -``` -docker compose up --build broker +```bash +cp .env.example .env ``` -If the address is already in use, you might have to kill the Mosquitto process `sudo pkill mosquitto`. - -## Authentication - -The broker has been set up with role-based authentication. -The current roles with their permissions per topic are described in the table below. - -| | read | write | -| -------- | ------ | ------ | -| admin | # | # | -| flotilla | isar/# | - | -| isar | isar/# | isar/# | - -To change the password see [this guide](https://mosquitto.org/documentation/authentication-methods/) -on how to manage the [password file](./mosquitto/config/passwd_file). - -To change the roles see [this guide](https://mosquitto.org/documentation/authentication-methods/) -on how to manage the role-based access control. -They are defined in the [access_control file](./mosquitto/config/access_control). - -## Installing broker outside of Docker +Docker Compose loads `broker/.env` by default on startup. See [Using the "--env-file" option](https://docs.docker.com/compose/environment-variables/#using-the---env-file--option) for more information. -To test the dockerized broker, the functions `mosquitto_sub` and `mosquitto_pub` are useful. -To gain access to them on your machine you will need to install the Mosquitto broker. +## Run -### Linux installation +From the repository root: -To install the Mosquitto broker run the following commands: - -``` -sudo apt-add-repository ppa:mosquitto-dev/mosquitto-ppa -sudo apt-get update -sudo apt-get install mosquitto -sudo apt-get install mosquitto-clients +```bash +make broker # docker compose up broker --build ``` -### Windows installation - -Go to the [official Mosquitto download page](https://mosquitto.org/download/) and download and install the -binaries for Windows. - -Then add the installation folder to your PATH variable for the commands to be available from your terminal. - -### Start non-Docker broker - -If running the broker outside Docker, you will need to manually create the `server-key.pem` file, containing the secret server key mentioned in the [setup section](#set-up-the-broker). -The Mosquitto config file expects this file to be stored in the [mosquitto/config/certs](./mosquitto/config/certs) folder. - -The broker may then be started with: +If the address is already in use, you may have to kill an existing Mosquitto process: +```bash +sudo pkill mosquitto ``` -mosquitto -p 1883 -c mosquitto/config/mosquitto.conf -``` - -## Testing the broker - -To test that the broker functions as expected the `mosquitto_sub` and `mosquitto_pub` tools that are wrapped in the installation may be used. -For access to all topics, you need to use the admin user. -The password for the admin can be found in our key vault. - -For the TLS encryption, you will need to reference the CA certificate. This is not a secret and can be found in [mosquitto/config/certs](./mosquitto/config/certs). - -First, subscribe to a topic: - -``` -mosquitto_sub -t topic_name -u admin -P secret_password --cafile ca-cert.pem -``` - -Then attempt to publish something to the same topic in a different terminal: - -``` -mosquitto_pub -t topic_name -m hei -u admin -P secret_password --cafile ca-cert.pem -``` - -## MQTT Broker Certificates - -If the CA and server certificates have expired, they should be generated by following the steps available on the [utility scripts](https://github.com/equinor/eq_robot_utility_scripts/blob/main/certificates/README.md) repository. diff --git a/broker/best_practices.md b/broker/best_practices.md new file mode 100644 index 000000000..20ddea3d5 --- /dev/null +++ b/broker/best_practices.md @@ -0,0 +1,68 @@ +# Broker best practices + +Reference material for the Flotilla MQTT broker. For installation and how to run the broker, see the [README](./README.md). + +## Authentication + +The broker has been set up with role-based authentication. The current roles with their permissions per topic are described in the table below. + +| | read | write | +| -------- | ------ | ------ | +| admin | # | # | +| flotilla | isar/# | - | +| isar | isar/# | isar/# | + +To change the password see [this guide](https://mosquitto.org/documentation/authentication-methods/) on how to manage the [password file](./mosquitto/config/passwd_file). + +To change the roles see [this guide](https://mosquitto.org/documentation/authentication-methods/) on how to manage the role-based access control. They are defined in the [access_control file](./mosquitto/config/access_control). + +## Installing the broker outside of Docker + +To test the dockerized broker, the functions `mosquitto_sub` and `mosquitto_pub` are useful. To gain access to them on your machine you will need to install the Mosquitto broker. + +### Linux + +```bash +sudo apt-add-repository ppa:mosquitto-dev/mosquitto-ppa +sudo apt-get update +sudo apt-get install mosquitto +sudo apt-get install mosquitto-clients +``` + +### Windows + +Go to the [official Mosquitto download page](https://mosquitto.org/download/) and download and install the binaries for Windows. + +Then add the installation folder to your PATH variable for the commands to be available from your terminal. + +### Starting a non-Docker broker + +If running the broker outside Docker, you will need to manually create the `server-key.pem` file, containing the secret server key described in the [README](./README.md#configuration). The Mosquitto config file expects this file to be stored in the [mosquitto/config/certs](./mosquitto/config/certs) folder. + +The broker may then be started with: + +```bash +mosquitto -p 1883 -c mosquitto/config/mosquitto.conf +``` + +## Testing the broker + +To test that the broker functions as expected, the `mosquitto_sub` and `mosquitto_pub` tools may be used. For access to all topics, you need to use the admin user. The password for the admin can be found in our key vault. + +For the TLS encryption, you will need to reference the CA certificate. This is not a secret and can be found in [mosquitto/config/certs](./mosquitto/config/certs). + +First, subscribe to a topic: + +```bash +mosquitto_sub -t topic_name -u admin -P secret_password --cafile ca-cert.pem +``` + +Then attempt to publish something to the same topic in a different terminal: + +```bash +mosquitto_pub -t topic_name -m hei -u admin -P secret_password --cafile ca-cert.pem +``` + +## Certificates + +If the CA and server certificates have expired, they should be generated by following the steps available in the [utility scripts](https://github.com/equinor/eq_robot_utility_scripts/blob/main/certificates/README.md) repository. diff --git a/frontend/README.md b/frontend/README.md index d6f247ea4..48ca284c8 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,75 +1,47 @@ # Flotilla frontend -This document describes how to run the frontend. For information on best practices related to development, [see the best practices document](./best_practices.md). +The Flotilla frontend is a React application written in TypeScript and built with Vite. -## Setup +For development conventions, folder structure, and formatting, see [best_practices.md](./best_practices.md). -The app uses TypeScript and React. For development, Node v24.x needs to be installed. Installation instructions can be found -[here](https://github.com/nodesource/distributions/blob/master/README.md). +## Prerequisites -The application reads environment variables from the `.env` file in the `frontend` folder. +- [Node.js 24.x](https://github.com/nodesource/distributions) +- [pnpm](https://pnpm.io/installation) -### Automatic environment setup +## Configuration -See [Flotilla readme](../README.md#automatic-environment-setup). +The application reads environment variables from `frontend/.env`. Create it by running [`setup.sh`](../setup.sh) from the repository root, or by copying the template manually: -### Manual environment setup +```bash +cp .env.example .env +``` -As a starting point, make a copy of the `.env.example` file and rename it to `.env`. +Only variables prefixed with `VITE_` are included in the application. They are parsed and defined in [config.ts](./src/config.ts). ## Install -To install the application, navigate to the frontend folder and run the following command: +From the `frontend` folder: -``` +```bash pnpm install --frozen-lockfile ``` ## Run -To start the app, run the following command in the root folder: - +```bash +pnpm dev # or: make run ``` -pnpm dev -``` - -This command runs the app in development mode. Open [http://localhost:3001](http://localhost:3001) to view it in the browser. -The page will reload if you make edits. You will also be able to see any lint errors in the console. +The app is served at in development mode. The page reloads when you make edits, and lint errors appear in the console. -## Run in Docker +To run the frontend in Docker, run the following from the repository root: -To run the frontend in Docker, run the following command in the root folder of Flotilla: - -``` +```bash docker compose up --build frontend ``` -## Authentication - -Authentication is implemented for the frontend following the [official Microsoft tutorial on OAuth2 flow in React](https://docs.microsoft.com/en-us/azure/active-directory/develop/tutorial-v2-react). - -## Formatting - -We use Prettier for formatting. -To test the formatting locally, run: - -``` -pnpm prettier_check -``` - -We recommend installing the [Prettier extension for VS Code](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) -and setting the `format on save` option for VS Code to true. -You can do this by going to `File` -> `Preferences` -> `Settings` and then searching for "Format On Save" and ticking the box. - -## Config - -The application reads custom environment variables from the `.env` file on startup. The variables need to be prefixed with `VITE_` to be included in the application. -These are parsed and defined in [config.ts](./src/config.ts). - -## Run locally with Staging and Prod Databases - -To run locally towards the databases, follow the steps below: +## Run against the Staging or Production backend -1. Change `UseInMemoryDatabase` to `false` in `appsettings.Local` and set `ASPNETCORE_ENVIRONMENT` to the correct environment in `launchSettings.json`. -3. Update `VITE_BACKEND_API_SCOPE` in the `.env` file located in the `frontend` folder. +1. Update `VITE_BACKEND_API_SCOPE` in `frontend/.env` to the scope of the environment you want to target. +2. Point the backend at that environment as well — see the [backend README](../backend/README.md#connecting-to-the-development-database). diff --git a/frontend/best_practices.md b/frontend/best_practices.md index 94654b542..ba07cf97d 100644 --- a/frontend/best_practices.md +++ b/frontend/best_practices.md @@ -4,6 +4,7 @@ - [Setup](#setup) - [Prettier](#prettier) - [ESLint](#eslint) + - [Authentication](#authentication) - [Folder structure](#folder-structure) - [Components](#react-components) - [React arguments](#react-arguments) @@ -18,20 +19,44 @@ ## Setup -See the [README](./README.md) for more information. +For installation and how to run the frontend, see the [README](./README.md). ### Prettier -We abide by the formatting provided by Prettier. To run it, type -npx prettier --write [path to source] +We abide by the formatting provided by Prettier. To format the code, run: + +```bash +pnpm exec prettier --write src +``` + +To check the formatting without changing anything, run: + +```bash +pnpm prettier_check +``` + +We recommend installing the [Prettier extension for VS Code](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) and enabling `format on save`. You can do this by going to `File` -> `Preferences` -> `Settings`, searching for "Format On Save", and ticking the box. ### ESLint We also avoid any warnings or errors from ESLint before we merge in any code. These warnings appear when compiling the code using -npm run dev + +```bash +pnpm dev +``` + but can also be run with -npx eslint [path to src] + +```bash +pnpm lint +``` + +`make format` runs Prettier, ESLint, and the unused-exports check in one go. + +## Authentication + +Authentication is implemented following the [official Microsoft tutorial on OAuth2 flow in React](https://docs.microsoft.com/en-us/azure/active-directory/develop/tutorial-v2-react). ## Folder structure diff --git a/setup.sh b/setup.sh index d387ede2f..a538a7774 100755 --- a/setup.sh +++ b/setup.sh @@ -46,10 +46,10 @@ fi if [ "$backend_abort" != "true" ]; then - echo "Local__DevUserId=$local_dev_username" >> $flotilla_dir/backend/api/.env - echo -e "Added local development username to .env file" + cp $flotilla_dir/backend/api/.env.example $flotilla_dir/backend/api/.env + echo -e "Created backend/api/.env file from backend/api/.env.example" - echo -e "Backup setup - Done!" + echo -e "Backend setup - Done!" echo -e "-----------------------------\n" fi @@ -65,7 +65,7 @@ if [ -f $flotilla_dir/broker/.env ]; then read reply if [ "$reply" = "n" ] || [ "$reply" = "N" ]; then - echo -e "\Broker setup - Aborted!\n" + echo -e "\nBroker setup - Aborted!\n" broker_abort="true" fi fi @@ -86,10 +86,9 @@ if [ "$broker_abort" != "true" ]; then done - echo "TLS_SERVER_KEY='$broker_server_key'" >> $flotilla_dir/broker/.env - echo -e "Added broker server key to .env file" + echo "TLS_SERVER_KEY='$broker_server_key'" > $flotilla_dir/broker/.env + echo -e "Created broker/.env file with the broker server key" - echo -e "Added broker server key to .env file" echo -e "Broker setup - Done!" echo -e "-----------------------------\n" #----------------------------- From a55b9f730e976dba71d1bee0724bd436839c484c Mon Sep 17 00:00:00 2001 From: Nicholas Dalhaug Date: Fri, 14 Aug 2026 16:01:30 +0200 Subject: [PATCH 2/4] Remove unused files --- ...=> monthly_frontend_dependency_update.yml} | 0 .github/workflows/notify_migrations_on_pr.yml | 2 +- ...R.yml => pnpm_audit_fix_and_create_pr.yml} | 0 .gitignore | 2 - README.md | 2 + .../Controllers/Models/AnalysisResultDto.cs | 25 -------- backend/api/Services/Models/BlobDownload.cs | 4 -- backend/api/Utilities/Extensions.cs | 11 ---- backend/api/Utilities/ObjectCloner.cs | 13 ----- backend/best_practices.md | 2 +- catalog-info.yaml | 12 ++-- frontend/best_practices.md | 27 +++++---- frontend/src/mediaAssets/Asset.png | Bin 4205 -> 0 bytes frontend/src/mediaAssets/taurob_inspector.jpg | Bin 102011 -> 0 bytes radixconfig.yaml | 54 ------------------ 15 files changed, 26 insertions(+), 128 deletions(-) rename .github/workflows/{monthly_npm_dependency_update.yml => monthly_frontend_dependency_update.yml} (100%) rename .github/workflows/{upgrade_package_lock_and_create_PR.yml => pnpm_audit_fix_and_create_pr.yml} (100%) delete mode 100644 backend/api/Controllers/Models/AnalysisResultDto.cs delete mode 100644 backend/api/Services/Models/BlobDownload.cs delete mode 100644 backend/api/Utilities/Extensions.cs delete mode 100644 backend/api/Utilities/ObjectCloner.cs delete mode 100644 frontend/src/mediaAssets/Asset.png delete mode 100644 frontend/src/mediaAssets/taurob_inspector.jpg delete mode 100644 radixconfig.yaml diff --git a/.github/workflows/monthly_npm_dependency_update.yml b/.github/workflows/monthly_frontend_dependency_update.yml similarity index 100% rename from .github/workflows/monthly_npm_dependency_update.yml rename to .github/workflows/monthly_frontend_dependency_update.yml diff --git a/.github/workflows/notify_migrations_on_pr.yml b/.github/workflows/notify_migrations_on_pr.yml index be861c8b9..6712adf64 100644 --- a/.github/workflows/notify_migrations_on_pr.yml +++ b/.github/workflows/notify_migrations_on_pr.yml @@ -15,4 +15,4 @@ jobs: uses: equinor/armada/.github/workflows/notify_migrations_on_pr.yml@main with: stage_one_workflow_run_id: ${{ github.event.workflow_run.id }} - migration_docs_url: https://github.com/equinor/flotilla/tree/main/backend#Database-model-and-EF-Core + migration_docs_url: https://github.com/equinor/flotilla/tree/main/backend#database-migrations-ef-core diff --git a/.github/workflows/upgrade_package_lock_and_create_PR.yml b/.github/workflows/pnpm_audit_fix_and_create_pr.yml similarity index 100% rename from .github/workflows/upgrade_package_lock_and_create_PR.yml rename to .github/workflows/pnpm_audit_fix_and_create_pr.yml diff --git a/.gitignore b/.gitignore index c2ee2cfd5..4e28ef514 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,6 @@ frontend/out/ *.log *.vs *.code-workspace -*.editorconfig # Environment variables *.env @@ -17,7 +16,6 @@ frontend/out/ # Backend backend/*/bin backend/*/obj -backend/api/Database/Maps/* backend/backend.sln.DotSettings.user # Broker diff --git a/README.md b/README.md index f573cea92..1bb170057 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,8 @@ Each component reads its configuration from a `.env` file. The matching `.env.ex | Backend | `backend/api/.env` | `backend/api/.env.example` | Set `Local__DevUserId` to your own user id for local development. | | Broker | `broker/.env` | `broker/.env.example` | `TLS_SERVER_KEY` is a secret and is found in our key vault. | +> **Note on Docker:** `backend/api/.env` is only read when the backend is run directly (`make run`). The backend *container* does not load it — [docker-compose.yml](./docker-compose.yml) sets `ASPNETCORE_ENVIRONMENT=Development` and reads `AZURE_CLIENT_SECRET` from a `.env` file in the repository root, which `setup.sh` does not create. To run the full stack against Azure AD you currently have to add `AZURE_CLIENT_SECRET=...` to a root `.env` yourself. + ## Other make commands Common commands are defined in the [root Makefile](./Makefile), the [backend Makefile](./backend/Makefile), and the [frontend Makefile](./frontend/Makefile): diff --git a/backend/api/Controllers/Models/AnalysisResultDto.cs b/backend/api/Controllers/Models/AnalysisResultDto.cs deleted file mode 100644 index d604ce887..000000000 --- a/backend/api/Controllers/Models/AnalysisResultDto.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Text.Json.Serialization; -using Api.Database.Models; - -namespace Api.Controllers.Models; - -public class AnalysisResultDto -{ - [JsonPropertyName("inspectionId")] - public string InspectionId { get; set; } = ""; - - [JsonPropertyName("analysisType")] - public AnalysisType AnalysisType { get; set; } - - [JsonPropertyName("value")] - public string? Value { get; set; } - - [JsonPropertyName("unit")] - public string? Unit { get; set; } - - [JsonPropertyName("confidence")] - public float? Confidence { get; set; } - - [JsonPropertyName("warning")] - public string? Warning { get; set; } -} diff --git a/backend/api/Services/Models/BlobDownload.cs b/backend/api/Services/Models/BlobDownload.cs deleted file mode 100644 index afe157996..000000000 --- a/backend/api/Services/Models/BlobDownload.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace Api.Services.Models -{ - public record BlobDownload(byte[] Content, string? ContentType); -} diff --git a/backend/api/Utilities/Extensions.cs b/backend/api/Utilities/Extensions.cs deleted file mode 100644 index 26d56bc8f..000000000 --- a/backend/api/Utilities/Extensions.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Api.Utilities -{ - public static class MyExtensions - { - public static void Reset(this System.Timers.Timer timer) - { - timer.Stop(); - timer.Start(); - } - } -} diff --git a/backend/api/Utilities/ObjectCloner.cs b/backend/api/Utilities/ObjectCloner.cs deleted file mode 100644 index 7352b746d..000000000 --- a/backend/api/Utilities/ObjectCloner.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Text.Json; - -namespace Api.Utilities -{ - public static class ObjectCopier - { - public static T Clone(T source) - { - string serialized = JsonSerializer.Serialize(source); - return JsonSerializer.Deserialize(serialized)!; - } - } -} diff --git a/backend/best_practices.md b/backend/best_practices.md index e53dc80d7..8bf283d12 100644 --- a/backend/best_practices.md +++ b/backend/best_practices.md @@ -4,7 +4,7 @@ Conventions and gotchas for working on the Flotilla backend. For installation an ## Formatting -Formatting rules are defined in the [.editorconfig](../.editorconfig). We use [CSharpier](https://csharpier.com/) to auto-format on save (see [installation](https://csharpier.com/docs/About)). To format locally: +Formatting is handled entirely by [CSharpier](https://csharpier.com/), which is opinionated and takes no configuration. The version is pinned in [.config/dotnet-tools.json](./.config/dotnet-tools.json). We recommend setting it up to format on save (see [installation](https://csharpier.com/docs/About)). To format locally: ```bash make format diff --git a/catalog-info.yaml b/catalog-info.yaml index 9d8bd55ca..40665c7c9 100644 --- a/catalog-info.yaml +++ b/catalog-info.yaml @@ -21,13 +21,13 @@ metadata: github.com/project-slug: equinor/flotilla github.com/team-slug: equinor/eq-robot-developer links: - - url: https://shared.dev.aurora.equinor.com/robotics-frontend/ + - url: https://flotilla.dev.robotics.equinor.com/ title: Flotilla Frontend Dev icon: public - - url: https://shared.aurora.equinor.com/robotics-staging-frontend/ + - url: https://flotilla.staging.robotics.equinor.com/ title: Flotilla Frontend Staging icon: public - - url: https://shared.aurora.equinor.com/robotics-prod-frontend/ + - url: https://flotilla.robotics.equinor.com/ title: Flotilla Frontend Prod icon: public spec: @@ -49,13 +49,13 @@ metadata: github.com/project-slug: equinor/flotilla github.com/team-slug: equinor/eq-robot-developer links: - - url: https://shared.dev.aurora.equinor.com/robotics-backend/swagger + - url: https://flotilla.dev.robotics.equinor.com/api/swagger title: Flotilla Backend Dev icon: public - - url: https://shared.aurora.equinor.com/robotics-staging-backend/swagger + - url: https://flotilla.staging.robotics.equinor.com/api/swagger title: Flotilla Backend Staging icon: public - - url: https://shared.aurora.equinor.com/robotics-prod-backend/swagger + - url: https://flotilla.robotics.equinor.com/api/swagger title: Flotilla Backend Prod icon: public spec: diff --git a/frontend/best_practices.md b/frontend/best_practices.md index ba07cf97d..680765cc8 100644 --- a/frontend/best_practices.md +++ b/frontend/best_practices.md @@ -60,17 +60,22 @@ Authentication is implemented following the [official Microsoft tutorial on OAut ## Folder structure -The frontend src folder is organized into 6 main folders. - -- Alerts contains code which displays alerts on the top of the page -- Contexts contain react contexts (see [the context section for more information](#contexts)) -- Displays contains visual react components which are used on more than one page -- Header contains code related to the page header -- Pages contains the bulk of the code, as all the code related to the website pages are kept here, if there are no other relevant folders -- Language contains translations between the supported languages for text on the Flotilla web page -- MediaAssets contains the static image files displayed on the page -- Models contains the data models -- Utils contains utility functions which are relevant in several parts of the code +The frontend `src` folder is organized into 8 main folders. + +- `api` contains the clients for the Flotilla backend and SARA, authentication config, and error handling +- `components` contains React components shared across more than one page, grouped into: + - `Alerts` — code which displays alerts at the top of the page + - `Contexts` — React contexts (see [the context section for more information](#contexts)) + - `Dialogs` — modal dialogs + - `Displays` — visual components reused across pages + - `Header` — code related to the page header + - `Styles` — shared styled-components and style constants +- `hooks` contains reusable React hooks +- `language` contains translations between the supported languages for text on the Flotilla web page +- `mediaAssets` contains the static image files displayed on the page +- `models` contains the data models +- `pages` contains the bulk of the code, as all the code related to the website pages is kept here +- `utils` contains utility functions which are relevant in several parts of the code ## Function syntax diff --git a/frontend/src/mediaAssets/Asset.png b/frontend/src/mediaAssets/Asset.png deleted file mode 100644 index afecff9cee509e5e01540ea19da7fcbc6149eb18..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4205 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 M(_F7g$LZPs0ERp==l}o! diff --git a/frontend/src/mediaAssets/taurob_inspector.jpg b/frontend/src/mediaAssets/taurob_inspector.jpg deleted file mode 100644 index fa2c2b013e7b303530fc0544f80696d7edfc125c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 102011 zcmeFZWmKDCxGfmmp|}&QK%r=H3sStednr&PxOXDd4kYB_lsYIzkY8EQ9Pt`8rn9bHXqtXw>)MX33?&|jAT zk^p2xL?lE+WF#acWMpI%G;B09R8%xPEG%?vLOdcOLOcQjVlo;EViF)J0Rbf=C6Jcx z{rmSs6dyh^(X-Idzo-9?o4~!j6%7>)7Yz-Uo`ise{{QxR?Ev7SA_5Qr2ynCjcw9IH zT)5XB01yCxLwbYyA87x#1qY9Sh=h!SiiVEyc0uhs06ZK50z4uD(i?`it9{>I2O#1i z;n8r4Bjc-@pwK!Ja0SHXpwdZHbrPygp3{H&<`jsAPDD&XO7@QO8%Xanwytj zP*_x4Qu?>LrnauWp|Po}yQjCWe_(KEYIPCI_Rj9!{=wnJ<<<4g?cM#u zRCIZ~skB5K@00ABg z!Rr=_q4Msj6$VVyHub<(omg=^FjwCRp0nR~SYZ?z{lwiIlSi!%{n19+U8IXecN)>QB7w z2tU67Vc&hS^y0{23z>=OGB+p#kBLN0HS+9wa=q+lbb}IZ=K=!JZ&!xnWHI9|%DeL~U)Ae@hC^&didCvPN(W1^tU9?C_f$_ySDofUxwHMx%c%UU*pm9ezs7bpTWi*+JEg8c|NH;%O{U0BU(8p zs^uEW#oUa4AfCPtee|9mqZK9tq+(-P>9?VlFEo@*)XM`_s?w||=1$5R;T!cmIm4F! zHXMv12)?jgk*Hb2a=zEQ?fH9DT2Fl|N9CEex}&x1iXY|7bgVr1PD;L0X=FKK2c8#} zEI`Y((A;g=(x~DTa+Lkz7gRcE;YgnMS1?91pc7Gh9)#{k%^2Yli!>ATB>zt=9+nEV zcm)7qtv&`3-yFlv{DAj;N8L@ewI?;dJFjaHi?3~OrDHoaWi zL9^sdSq@-ne{`nY|JpHf!PW}ih(vw`=(K$O^RH_v{S|vmZ zdC$*gAq)GpQBTdwwj}c<4L;@er_fQhdI$Zz`@1M%c8 zw>mH-&^zTVV_ku+_!DDVFfLE;$Q6i{K4SYZPA=PW+N3+}_j*jcI9*0}}nZA47 za%#JkU700~$z|-7A2(7Iv!du(ZB5aB{w$w%m~@nsnJd4!Ng$TnxqWSel zQu1w?>)co43{J`c%6=)xJ_H@n8CMj`DENqtx6Gk28!Oz(LCQ|oU=M+3r0a!6Y0NiY z-i zlX8I3Pu_mwF#7N-(<{I4X3>MC=T49<&)(gZGfKyRtN!7{px~q`GMbRGpj(o9uYd;V zey`9ox5_g4b;dRn>E}PwF`s%$nTHd7&C9dSwa}1muiSp&9}CMS492IA9CyHBo*P89 z{K@jzKQ0cWIuwC8UI14DRWYRhqJi2?9roa@KRrVHsi!+3HsM8_wk;Qmb03};j^*_b zwkd$tpdK|k_~I-!xvlpDiAy4|J9~@^{JUxPv=Zvqi{Dz!Mh>v>$~;=5n`+W8t9BQX zMaIvKw%+y8__M%G78*a4%D6^~uCyswax3E3c$1CcWN4tjmcg!qZnysQ7rK(-6)2ip z&wZd8B*leB8!Ime+*F^@rre~oV_A_=Y!9LS_??0Byt2dZcjeDD&FM`ue8W&fe;aNa zY0+#lM5o8wut%c8+n(ef&*uY>NF2$VW8cow*wnWjJM}D{GgM_w1a&ow6#fi)FZPg( zf)YDE<2{d9Dh1I0)ug##)_J5`l}=WRKY=1{>KK7Jx6_gW2r|g1S)i@AvExGF#yq-i z9^;MI;!*f-IqHw?cg{c5^D-9vnH*?^pGIm|IJ)V48Y;heMA&yg^)86w7^J_}?g?hZ z{u-c-3B3-#txuG-aW)pe$Jx?s5Ak7}w7#9Mk6AP|)hn&e_q`M^t!6vRD$~B zf=YCAo~kOZt`XxO(UDEMFuY7HurdH_vjnV6T!hpT%P+DDdy+sUPYVJ_mWYR*$duy5LsfeMn}&w|L3s0QvZ2TKGA;=Qn$zYU8rJiqW~ z_s!9{`T?#C{(?)olEoDng4=x^WLH<4!L{UH!$(b4qKKarwGGt`=IB`n;$t8`;i_b| zUt@CZjupIm=K=A79okF1;ohP5e0A#`D6~J6(x_*?hAhI&_SwD`z z-hMp#$j~TlUF040Cyely5=>1k@K?G7Ri`#+dR}~DX#eUkH|J{^Ba!XR-mH_KF|tf z6BYK&Ul0ro_YYj05s3F$nJd zq<_osJ+i+jU3*K2Y+dT+`R_T3Q$**iFJ8MXQ(J0jhV9_y?kYwh+6V}`d;doN%={#% zCb}yjSvy3#-qWPI_1h@fAD!MlE_VmV1}6~N`4JH55V(l!oyCs(RfY_h92Io%yic04 z$&$qj`RGszkJAl3gvh&85&j%jG#cKM7cp$I*k`UC%5SPqBKH9^jION$E+JIPTY7o` z*yQX^M&oi%bdmj303~*sHEq3WJ%##SbcN*u96rFMG#;u47)ar;Zd{)|m@7k7s2}Z$ zp7TeQ6KNo*gnqzCe{w6?_SjI#BX7o$H?tkHga<321|cW!-JTfoW(np7s}NxOYm4B| z1(z?$?)nQB=Px%BmYKNL_$xZksh=XGP6vm@NFfMnsS0Zjf*-X%c7pT&G%N!c^4qG3 zaWkLQNjLpy;}m(^OYKHYt-MmARJoV{drT{+BI38gFxUN6hDjowd}-B*&6c{MIn9~2 zffY$A-;lR%xijBKe~R@Qa*U?63I3XfRHIfJDEknA9U16__roGS3Y?Iv_@tO-{O^P1 z1niM!Fnn#r_el#Un3a1;L{HZBH)7SumoIx~63Q#Cm3Q(o=O@#P&B!#3eq*TX(chRz z`vl8e0WVTwrs5f;#0R}Du#b~Oec}+y_I>BfpnT-M2}CKA7*GW zJx*Mnw80UaEDW!JL=~8T{XLSem67g~z`X(tcP-??c$8|X)etuGKoR;V4PsA&AsH3@ zm+QJnsXm^sK*PB)S{aUlGel+jIU9C#e6d-!+6ntzH(X|H*`|k@Ygy5ZW%f~T_dxPb zb}e_CjGZ`GKC(^vqtg0M3NZjHR(j1V&KL}2H8jAczbG!S+36LqBHjOT3AX5DtvjNs zJ%Wk6q;vZsXx2`+T#SZS(n;vLv-a8grs+M-{a)$}H3CD{a zo6EMpw=iHgV)5IZ9vV2&CB~)5WI~{I3>ejPzwCpAUK85w0k*FxMGk+YFfvxa)6mDq zIazCX{c4&*=>-@^9v*X<5++h4+I?^w1-NM8e~ki5+q!jif~MGrJz$6rX_u5BvTgc? z2A9Q(osvW1KnPjccJRq~1w!NW;MP-U-w_5TgWE3)M#LAC`&o=Eviqp3pkhy!mjzXA zVAQ9_xw+;oLw1dG-=tu0wiMVIzVENWDaz>$Gm3!e;CM-;h!jAX=QhVR3#8KGbA$Z7 z^Tj$cIB2lE#%*F$q^I~7g(csoD$@?C7WBVxWxT}hWVXkAb51pU9K8G6Mx~ax%0`oR z$2+|z<#pPs?Bf(iH3`C3&z%*o0F1-i$JVj~z5HJU=Y47zh5VdRd5jIOfTLL~dzF?8 z!rtDo5{ElJVgx+dg~2U^A_N2qt?wC@bLZa-xYH}oR0LH}T=R|fjg|(BjDBy+eZmZ@ zZX43))I|fS7=r5*Ms~N{jT3Qj~CD_KAUx}vKHEFBHQ>ZKs9@ih3rT7&(e>j z+8DQ&96r((>FFlO6X?CL4i9;8!F+6*T4WKjtjHFpP^FH1=c0)K(5MOPOyHmn8lX)I z_%AoJpcD`c#tdR)yGaa*nIE>j?S8HMw9OP(NT~Z_WOk zA$$TJh|AU%FRo{@AbU&4`%MmzR&HrmEz0#1j##hl>luu+2#?Sj7pcR1_Yp#yg|_*h z@&Yur33IqTO5<1&6Vf#pgS5=`GP^qDS8eiGo|(O$vaA8-k@4dpKBaCq_UDAfG_oB4 zICV3)fG@%~*{Gz}4DvP7)?77k#^y96B}3Y(=XZ*K8)pg{n-(wi{U`do@K@TgL4w8o zDGTmZ+VbbvRmDBkL$rT?HB$Mx#~u>k?1hcY)PN(IZ}sI;bhkvb^RRogD~u0Sm-?oJ zkW{$jcy_&N4`ymfE2SNji1%6a{zPadZ5RFvp9`AGt4ki2ag1!U36MQSh<#yJV}XnL zQ+kyWqSz;kZ}V%<{|mnpR=vJTigJd8!?7O*b)LJKrfvH4hbL zfi4`mebm|6xMdBEO__Lu=HeCPA6-5$r1txEJnpS&B?Jkn-$;6WJx{r{8{?4QaO z&kcR@f8};>+=RF*f^8Bh+oaK_eQ^QgL}B+0InlFUF6yrU`?mq@LIbW*%8Wh2hxZlW z-a{to%TD(SAgi*4TJilY*J{d$?1`MD%^=C@AOo~Z5vaiZ4+16D2XZbx%N_cxU`}+@ zD&n?`L@|i$^$O@v21$VTM)qR-ezvWoGVKL#CK7JBG7ojr1dveA4`jU){satqHiqO& zJe?%|_(OFV!A0;Cu^Et;H^IXRwSqGjXbeJQEc4>qdckrQ{eZagzWuzXvA^*xr+q82 z6l~sa3Nyg`9X-hW3h-3Ep@g7M&pSH$3J(ue`xGtJA_$O&{!u2B&^dD3q?j`tLPUxd z7mK*JP2F^fq_d*@&72#NY&wCye-n*)2qV{#E-`;!pN`!ngBW4vD=nkMf;VSHlgRjM z#EA|?#t1#e3ALiay;pBM?0uM_(ZlH%Iva4UruNK+7^!HhC2rbWpfnTVVLOVQ4{^bQ z(IMGQZ=kgT!#hKNH<^Gy|0p?uF@beIYAP)x1`)7(pI2r^`F$qrNw!vBpVW*>!#dzi zh19n^Xb5yX(|Hm*5qp@)*6|p{;Cc=bHCEFX_li;$ZyBEMQgNL3GKr%o_N4KoOE=Fhut$)FWs3${8E^##HUNWt_SSvLy9HZ!7B1LI>u{ysSq5=x#%%A!j*0aFjxujf zAb+E?2)(Bu)B` z@iS5CkUbCdAU1!dPJS#_=1`1vhpu;sK~}fxpAm7zsAkX4ZhtmU`&0<^cAl{|Xts-| zm;z6tH>tL=D!gyw%ND>%oW@r54l9d8TyZ^1Z*26@lXl1OCb{=)Yz1vh@qRD%+I;+j z<~aJZ&V{l+4|~nQJQ{%-ZWC92H+yv@L^Vh(y3Mlm(dKf+YOWr?k1k0^bp=K08ywwL z*?&J6s?E7(fVv;e`-)0edacZDf_GBYgH>UoEBo*>SMOW66lu_7OTLTxW2W98^=Hz+ z7B8e`w=1Fr)ikm;_5vQhYI{STFi+xK&81F|Fv@VkTLyA%$9)XD)_1BG z)WAgjSWNIDLjX6n++UN)f}j(Fh6gu(!Q;xO8rY*vD)f?jsw)`S4^&z!WCDoyDkYL( zY78*FTmP_dCM!7S2QBL@6w?i5lNqXkvp$8b_K94{61Y*0Iz1*w{^nHB1bAgCnj_8l zC^+?^U3Na?gIpow1=ig;iwEnr#$4jh_T$?^D#((DUZV%ggPou@^`Jfr!__}^q#aPr_; z^Cfb7?K$IcIJ2IF)C3*eA3oxR*3n1g5y>OM_%eN&9C z6}!8~Q8zgYBoVT1BL<9lOf7(98y{>WZPMfNkyzz@6m6i_nDfe?=VT3Sh7C{AJZ-M{ zStqk zD~|mqwy!?bF-?8$l>Wo*FJz*55sti~T7aKFLMqEfvwf&2^SMn);XaIE7J4)I3YcRt z_|%CeA|^c)f3Izda>BZ0k493nBCiPQ@k;o;@-DA{UW*P_hB-l`c61?JKSXb`jNd3nw-dMwPKJaA>C1YsTA(c*;tcEPjfe9@Qz;6k)^-gR z>-}-bvP6tML?PL#1vq8G&mtwBj5gZlZU7!*RXy5Ou{eX3l^P1>YkYcKN11HJWEQ^~ zx7sqk@#0=V_u4Fu&hLAIx4-q=F!=KbvsY5$=!9)lZ-Y+vR9A2y#Ygwr#UAQ?-;T7~ z>sF~2_;ES*wMoWzKi$tq_L9b&vw-ESt{;dtxumBDZV2E`$a(_4rhW!>$Uc`v%bxR> zaU7LFzS6D4-6LEPczTN+hz-ngs=)<_5j5One|E=bZDFOOKq9TAkO7791d)8J!ZvJg zs-5uoyWLrIf`T*#LQm_~^O&w_7-nNgyNmL^dfUEmcH~SpwsET~@X|p6FUqYlw+I5m z86}UY4SuHMv=ve$Vx(<%m)WSSF8k}d{Ij4%x{qLqp-#I~Ejl`XPkPe`4Wv<9E25vM zi@MTtv5w>CMa~*Aq=G-=CY}S5%$Z0^3~*z!`u^tBM81DO{koJi_CPuA zUFWs3xF|R_W5TlihI+Ip2f9^#1;9gd|C3oUN)A)|vT_$iR`Wcu@-gP*beP`i{(Ou_ z_ibo>kS{d&#Bbt7blw3~{Ip~S#{Z~-Qac~)H?7T8Jdwk+x#Dv`^@TDZ>di0nY4G`I zlueYw=X2!0@n3ds_%WYVzxt7TR66$=6AJ4>_u~(m+NxPk@)FrGq(}x)sJEHuC@tZ2 z{Q2`#GlhHwzwTkt+k`?EnH>!Z_T5q(xCadcO8&5Kwh5lQ*U^t)xWG^=il z^b%n7jv-qb#MbX^xcIK3fqfL?_#y=_FdRug%ovO3cPflb2`3`)0_gM0Tdm${Y@Dk^ zO6T*_Oke~X#3rINh`)d<=2^`JecY1DiM;FIg}@Hn1VH}dJ36T~j{7Q02ah)5XYa{P zuwDTlXI!11VU5{nm@uOAL!|I8`bTxZqZ>TK-;d>{JQ8z9n!` zxuw1eDdGi{y5x><7_6nXppT(VYSCz#*-Kz?K>YB<^n zx=VZ66^?)TkV!gO6W&}VP)qu`T2^s%rrZhF>jxFAXeRbq*x!0(yilR2xGjZR;Rhz3 z5rvn_7}mFyg_Vz79s9?XlnIPr4U25fWP zAyVZnOuyCmTD*BT`6|wxO`r?VUODIa$kv6#^@Qz6t1-%(C6rLNr3T544-bK3on|Y` z;QA2Eu2j_t2x_idt$|Clce4FmRtU+kXRG*nOD<^}PftU{Rc*nd6q3VJjGq zWsdEN?TU}Bu}F{Dn+`A*X06M!?2N$2DaFI`stoXc$;s0n5yvK=3!35Bdb65*UUG0$ zq%CwD{jAIlH+G+iQzMe^o2pWD!Bf{ysI6Ilm5Yh2$AE|9LotPM|J#f|8><4Zm;KDA z(cR`Vn}dVgK1dD3JR2{jvaGc3MX+1tved|G!HR#v)^JN#VJTYW;z5pL_p?MtYX2i^ zImBa~lafrbS8|1$}P=CYl#d#rhdV|8*IH=D)>CA z$g21qU7qgX4JlEB1D@kCPWsY{`AJUhOouhgs^QJ3Ms=x$K@Js0+p57Fq=(_W^};cyWd) zB)8A9g~mPJYJI%oCqZD$D4W2!$i@zrnmoS7Vwjfy6DkF^!t2Gu9zwY)tNG7k&44>F z;@ldK&im5bH1#ZG{dXLpM%f*-jL9#g-7Yc)BD?)2c4SQAk%?4)!3)KU(PXz!5g(u% zu?8qQ_jtAcvWuLnfQT#?72a<6`tO6#M=k3s`M9!FXVfox`LQiY!yTG3Gnh=6n(9rX z(*fewJo39!B1()&2-_necoHws*Oeu?x+;B#q&i>2_G)EU$CT@EQgz1v?Y;tROgS6x zsNX?W?y_Y*Ip+=&{6PFbLyCl2Fg?~46@svVQNJTqrR}sYhn$PF6S7FK&5M-%ORXv>UHLc zKhR4UNG4Vb*!}Ex$)Wj8qyyzT8(ZQ~D2rhEIDm?kfwzkplXT$hx-xLM2W#V{Nh#c( zY?Y5nF~Z1f?`2oygRmUy+HY=ObX@!&RA#08_0ib~%eTeXtlalTXpCIq*c(rScGxFt zsuM7QY>X)n?>?^?Wue914@WzrZkL>oi4Wq>U<||amuo%Uy39%2vn?krd?$)`(%8#c z(@>?_GRT%t+RXyA#F~j8$QO^Zj?4u@50aj#U~{DpQk&ce6h(`7+ic_?+{#o0I8%MJ z9H>fQql-0D;f$>KF~GwZ!8$dz*f&QP8Z%1!^fCd;us(XUO|L#om73Y)`MEy`b}4H_ zz$VqF6!c5Xa4lR0!-#^{k-;at+PbvrNtOc?0W;UxDAsjr|ELJU(4==88a2NOWvlc+ znB7Bco%5{u?gY>f&97dzf}&^CJd&(xK`2&D{y?Xl+?Xi;1OSA&(b19Q-c1j<$$NWt zgIJY~`3gW^G3;;ZBVru08Om!w;eWIMCK}6~+(d^uDs0M;DJW=Zr6^MeQ?O6ZReB&V z{tK9`vz5&?7dWfcb_SL({P{@TJY#s~hHR_kPJIX97<_a>*%G6Kgg1QbW4^5HA>#OD!*lX_p2o_(mg$qPX&7$G@-_)oNb2#S<^p zj8e^tX9T2bi6poHf59~KfWL&Ld?AS`J4s!P_5RTt-$_ifjDpUyF>Q%X`#-nLMD}hw zcHMEwAYc0V{xCboxeHdCzv-QsQ*iY!K{eCDYBZE_0Q0F$pO{r!`OT4mJ=?}&v0qlM zPWurSwA#v$B3mniD2$I-uN!xp=%VyJRUjN7GQckP@Kc#wN8qN;Or{1OFwu)4gFceN zSzv63{1wpGa@jX)G({=9!`ji45)E@P-T4GZnd^dYFPMqeeB>U}_=__L#_`)2#bgQmq4wJu*DI;48B zqO6A)+}9JeQH-qZ>x6q}YoF5I#JT_xWv)qHj9h0yWHAeY>B&oi*7KrAX8SFhSzz># z;LlU<_~B%Z#6l}7Xsfm8oMoSz8;2Wj7&7HdzpS=8XBF@ic3cwxZhhN!YkWR!M4I-c z_IPQ9ThH_13{(BWyR#4pd7zjFpC7F&hx*vkyL%-#?LW#Oj&gm|4SD$_`DSFI60NX^(D%c|1shIKO*7(-+hjd;*MkK^OPd9bolJ=b#%ogEpBKoeW4t;LyjkGT#K&MQEK{0DGm9$0+Dw^)7J++abvfr1V~zp5fFVD^&KzNacAXR6TF zeJZ}qM*cW-^B22HmJEHUrM-rVU7-}|ja9uC(w0f&16Z{f?(JV9J z#ycV_Nl8(*?e5lO!h4~I4}SF^fLrvi@A*)8Fu4Q(=ruZSBBkeN2YmcO#S7|^a+pjZ zo-5Kd>letz5i`cuwR%MVvXBJZly;BwyK3Yoz3l^(zbM!S{kn1GTl__yv;vtGKRCHh zk6&zTr5!TVSFf#>Al!}tjBpXJ5w08xN=0QN7#r8kz&;qj_s=g+AdREy^q1xtf{C1J zEE33na76dS=wI=aKy^H{cA4S?*6UL76UltOYl)`Vz&#(uWT^zw<4JypchNn|8dkqX z*#-HRsfw2^OH{vRr6a{K;1{}@kFWqm zI>%Ty`YxpyKMl!(GVBkH-p^Mavt5Hx_mYJC@f0u7WC7_h4xE{w!L1Op)K%%I&u@lY z=59n46y3t3pF^wOk_6hk+N4*ug@an-e%Ru7WqML`$!tNs+1A9gqipn76_uzT>0*2* zd>ar%cNaPnCW6hdajdh#t*CUQ${ z+rK$P4aLAU8%yA3cScy#+1*;fV3a9p9^|Tm2@k;rSKvnDczowKu~{?b9Z0|VxYpwr zlzKIeCS_T#qU`L8DP;`y{EM`Z@%Z?p6;-uGJo_v|F&Ae36Pa@<@3|!6yuV#Uc}I+F zaX#eZNn}#2S)sz9!`V1X=g* zj=rlfDj^`~f{woT3ZMcd>I&W^7V|!Tl>ypR8c4Ot>W}Qw76D} z*x{A!Su3VE#@;IyU8#p1`jH;Y8?jx~#pfTA3D%ZR2RM|lnDx(w(vfr!z(vy+&DFH9 zT8P}%f@$})^-fxx5Loj$s~^G-I$i<%&_5qV@jURAm?u2+zkF;7`e}j!mFOh^eErYCzH7D4*t*`Ixg1t;5iee;nb%w!6cu-aV>3)Gw-kLiafJsqm{Ey zjK0%x7h!!keB(mqyUlK_!Rjlzu= zFW`5*ponW$8LZZCozTf7>FcCGp|nCqB!YBapIlIu;!lh|A8Ge0Btz)EUG`1>IPNZu zp1pWcQem;N47!5IkDBu0>Om$UHCoHS-p8zNgvB~x!p*1o8PjQ3($!6Yv4w3vUHw1f z4qjT-Lx+87X>}QxR=d|0s_NSFCHvT!-?*qEP@(APDv2WEz1|(0EixM6$Bz}q#><&f z&%fuDzTBi<3pg(vpn1W9y}U9%)a15K@5yPEcoZOJQnac6(}?a2C@azQd2>lqV{~|D z1Jlp}dsIs!)4xqmq1WY><=r=x1u7T=a)2uX{8aa&u5n@oBwt;4el(4TR&Xo0RQ>(AOOglV_TP~k?5C9G6niJR zG41PM$3Ay}^MtX>`BrZ^fkRFD3X05q@03zR28|+p-D^>vkpM8)5y=v;S4_{}LA#Y% zDkcqJ8O2Q1z1$7*if8G+zE-)UgF^d3@ocP7NuwzSairZm6LY&11}s5r<(Y{1`}r)7 z7(Hvfnq(A{&){PU{Yr*~;*^K|zQPx15z(mVvbPRm_6%x#- zkUJ*+2*-e^8Mtj*Fc(gPb@`Bt-i?T}bTI3`BpwpVvQbw8+~x?c2_#!VjI^{P51Yi) zT{-w3g7qZEuzg|ujaJF7rql&%2N{A(iTD8g5NWN`d{kj!+K|SWRvr4}&7MGUZC;lDnPLSxS>IC)lxPsh8_7S=Z!s*vF zn_0hklYC~F`=F{X!1D7EPcZD}gAZYL9b}UCg<@8~2PvnDUrzggCL3E#;YC5OIq;yC zhJxBgwnC>Q*gMJ-WjQ|e@FVmYw81nIH&l3O-@;|2e2;qs6HSyj>LlW|7_UopHQ$C8 zCrq($wm0|K*b@76C-LT>2JD+PwOxdQy}6UdmHZeQ9YcOYTxHB(0ium*`l-WRO#;<5 zQhfOA$rPq%0&<>_em_vX{}3=e->Z;^hWNx&GYQ9;^t5f4`FkhLGR;AUi#bTrjh`mg*-&e6AN z4*_BnL{O3Cro@?KdBn>`s{~AByT)fP7*9dIRm=)Achv3iaz9rc$7;$!mK_!t?B$+T zc+iwSS*Ey6-5}5@l%K#twbmWwTO1@4VxMB_avLA}G!VM}qA`IOt7~27>{{1|czH}p z+SV*PzGZCUH=aolqayLiCyO2QrkBo)Z1|Kh-q46RJ++;-5+ds6HUI9_C&5Q zDmY6Yx6X|)yd!H6)aP!BaAa(h`_Z~Ij8Nk%fCG??@}DAeOIt@3ROlSYHSKP^yA>Y> zW|t%+Xl@^eMZ9MmCUi4mBve87}(~>V^{M7gSQor+Kk5+h_VGLci$w+Roq= zApSVd&i!|Jz&uqZPWo^}j-jRLA)l^*$N4RXUnSKVCU3fWL8Et*IS>9kfZqlMGgA8w z7hn`Pv9YnCC1%+1(=ZK;QK0av7AUMm1|_&Aq%BeS9|mf+KLu&N2=T#8`~rfJqWr8Q zo?P<-)ebX(!=zJKeM$*kKc?-+%Gkc3kEaf$B6261A8RYvRbzTBiuIOdK|ci$@J~g+ z{Wn<3YV!t)Wro}*8N+znlBBNyb1y$e#?_q2RWvo7jJ#OvowpX&7d-J()#GVu=JL?O z@2-&&$H`46Hs3i|OgB24@aiKH!45m}AAh)=tVb=Gr9E!=lY%kN%Aet+ZCq@gu;ciZP?f|Chl0sc;}(}`cs1kdF$U#cZ) znoqh4>s{m!lT6ySL32(OJ9)M;KZFlmu;sQ@6Fon`YL7n$4G4^TT}D$$?ReyFU$4g2 z!vs;Tp^Qv!rSr)TRIBdfli^n{q`^vhUk}u-jPGO#x|(KFbt@@bdiN$%9z_tw_%z-; zNK7DTt2Kp$Ox@u0al*LI?qv-{Z_~U)OF@14(wT?8SB7H*SvQsv(97{74Ig*Pm7giL zkfNbU>53htY=rd7it&l)A%BLf-iuy*qCZUr2zP&&ZS{M!MJcAKp4| z-ENSzX9OXFdJ6RXJwu5gq<|%^RhA(K=PvQ)+1b6z{wX_Sb^R}6S-pl5g$bO*Su0_nyJxYKWP8eUN#!sH*H@cH5 zKOOx}h}>-I`Up${JI4Jna4NP9V`ONl?;vw%yFhg2A8`x&4l)XVOotI3Tn?X!;dnM4 zsgn90qQs}#|8^F`$@m;9Q{lN6#u`~Ww$U#@xG!F9kQ{wtMs#6FJ%mu(+R6kU5^@vd zP)JH8W#hl9ZfjpmZE;yyIfNxRn(Dz_a$2c;k@&Iasd}@eUODCDbk1IBHMf;ms`+D% z#(2MLCoq=6j~gz}-L&|~da!tq@@G2oUVYe?WCWNO2!X0QtV~$ddOy`SRJP`KsMU86 zQ~R7KBQiCyb!~`f^%w)};L)SnchU|#v$eOKm6WW_(w)m3my0u{mH*RkO(1JpW|)tEH5ODf!x%D}w8ydEe> z26!PlXZXbsu))fr+z`gy^mCCX>nj2qfmxhjmb(7UZabjGywYR1BWBqnWy{xyc!`b~ z$s!^mkAni4PDF%ds`!8I|09Tq)*GDlQ7k$T(EX0{CU_^QUamzKZK(?Mp4!)$pi~)Wqx?lh}Gk1;k4mAb1>|-h(kdr5>uXQ862uG zJ5 zh13=*=*K8J+kbdxylBKa55=&9zo^Kgzt-{pf@4zxiqxIHHk8z4Jl2Z)f}(|h4#MyC za^ZSZ4J^@wg}3*=T;n)i4b?SF)P2zkCl5Dcu3P2L-EsC6-IT_{rsiHqJT=7~f#psGvNtHeMD01cC;OfXBSrxn zS6iG(pM2oVbkWb?kD6Px&_^T#gCi3G^9!T#D?T64Im}Q3wekjS5!H-5i5@6~-4oyO zV)bSVes7y=xwb^6?=4WI#jowlxEs?uUxYAe$ietMr~|8>`G zY?isg85T_|H%P$djFJme*^=vh688%2D?lsWJhD6nEQj)N@ z%eM#yIG~6iSxFao4yQWqL~mLcWm4hp{75gjryATi(DOx6N*vRz*c!Zfka94_%l!v^ zYvI@Gj3-lDiIr=K1bu5Oows{p_vQiU8-7eO;TeL)8m`Zwpt%-ExMQmZkD1;3NPscDL-$mf*fHHGMH>(9 z@i*oFF(UR)m^2-K-*z7=RSW+89Je*pC3HFB`lhn-ULFdjl2+r;rfkfXu`2tF?I7ue zyX-YD#I(3uXKN=qSwvgE7J{2~=a~4y{|fL=9c&^um_YbuL$O;+wJKsN-#A@BXLFH= zcx%^x&d(D;PUzLO1&)|NNs?;oJ1#HPk~Jt%{})i%{blaNzN3DY_=QS}Y0C7)$ct~? z(GV-E+p@)CYf0bKk*BrwK`S;VLI@$ITv|ye=s%o4pYNm3->50fXAT~k@7ci0)VPUn2-4YltGn+oX{bOa1L`r^CmepQct+EzgdAgb_Ai0wgz1WwWB zziE$aMKi48B|26x$|*an&WJT-d>OE1MO<3bz`yP5L$Az4tntV>IBy;UbbbVju>l9T{sVT#^&HFjpMbR^J_PWKf!0}UD>HzFM?1s+&>ITno|;! z(=5Nw825IHH0QtDHTKLH`pETJuR_8Luc4 z)K&9V(B|w}Nn4tKJUE4Qf^(nJLnn3@vU_DldP&@Ta&tZI;{w$S zL_IN)r6tj%e9Uq_wIp5hp_ps0e%6{(T8LH&Ed(tYr$7|DFQ9JF3>dkG+m+#kRQcTT zq@*dWfg;CAP)y!RRdgYEKVa&AvV&9VX0fKy?q5su=;Qs70D@tc6_Bmgbo_E7s%h+^ zci-rn8)BBaqct8B$8r1PsVee484i`#=>K;{Ex%oq||27#DQ`{^$gge@!f@;Q|#~YV^wONCZl-g2{dexb~Z)42& zH)#mbBL0Twm+oyQ^|ULc`0>X|tzDgo<{Diy6;zUyX#o+qN~OJich39O-5!#@#P0`S z>ZL30AMoQ@x9)J!rUq1FwVtB3C%(!Giz?mXPX8EAa+Z~;16Os!k}fCOW)B@c1sJpk z3Y)WsFK&LXi&|WkqKKh>pM@f@>}62|a6B_K5vNTM{z(F5zUN;3Qeor4VsjCD8V9Eo z%rulJ$5Z;JiqIT=W-~MDPJ~Xsp4f{m$98_SE|K#_f@K_rX^N@z?A(c9eGiX<9xyD)+{?XF}7HeCD< z71z$GqLs_4QO(p~`mi1r8urus1Eu`YbZ9&`&+)ehsg=^|caYX7Er)yhd^9p6O`Qs0 zJaD4sIrap5HMUrrXK zN*HdOE3LT)T>tierqzVl+qL*)%+31(9(ac+S)hmQ+VV2cL_$hPh=5ZRfGvJPx zAJD~^G$qwMLss=S)+gmn&El4u_DCt1=tHKb0dicTuC}QoZKy6tX$#atlW5m^79R9w z>O@n0`pMnU=*Qr!@P|>C-kqf4%$MaIq+glU z@zg#z9Q$_}>h|H*n0*nswXi_`N7gqwqVXR^a8PT5dXYru#dYJD`iP)@34^kxy!n?& zZpso*_{L}QFyw!g(WFk$>n2%!K`Y zaKG{GsCw>oWVzfnyj#!uHe{+Kw14^_h*W8MmEPaywRc-Gu$`{;nA*V%{8wm70**SZssoc7#@ zEExYv;M%UAPJX|b^hVaniC^q0hoouTWd1pGkQP;($(84g*)VXN1FcPzt4t4#d8F09 zLv`C}c>JWqTU~a@K|RGZRGU6h#rfees&LAv&6Icb<=bgG#xJOHA|-oI`sYh?P|LUT zgI*$3o7C6J)vU8V1O?i)^3-Ae;PzfJ^}apL8z~BRXO6Y+^~1W%|0cvwNL`x@#71u@EfUIwH5a~+35IT4goxW6fn3z6mH!1q+5k@ zTZZiGR4ct6OmqM=;#FkNv3p&cy`>7)m_f4f)we0M{KmnZTl(k=1b?00I_D1c5E-0d zL^VIDxFauhEU`sVB^~J}W8Z>JQkXbWW%i*mduWl%@oen$ltJPpxQrb-xb_#b5@}SR z&%H>snQc6RmuYnv2+DF_#ke|F=`|s{;!@Csj;MJ5Jl^Oh zSfu}EbUF3aKR}J^y>2;bbiwGR;hcfqM1zB5Zei?ATQfd;QV1vxm~@px^b5u6Co$E| zjF3Nk-;S4jn1EUP{rZGX*0(YJ89tP~& zPMb`ZNJbYkLlo0aY}-unP{9#UU@FF*cRLa94)i?$v15BHd=nHEj3qZGp{}e9@ZNDi z7sH-=p#J8v=g+fr?rEQkjy90~ey@)HY89Wv&7l@;Ej5xHKq;IYkXL{=EC)r306tKR zJXoSQO^jGhro(yrAqi68H7RS%lC$tIu2z;jxi8o>;5Na=9R(@y)bAVe201d^|4pev z-RIy~!Fb%xYFo_&robQTGHHH3ggr~Mv51yU?6M8qg6gVB&adWJ)B%@+MO7?C>pCc@ z?vah?XG#*rgRBhei=a{6^qU;h8p#;FxvE>C&33-jokbJ)QE2bT*UQLs;#at%TJ(KB zq+`A>U`&Wu%CwE>uz~x*O$gGBX#Xws{rfPk_obQKCqeDs5M0uy&xZ&h@MTpf;tn*^ z?)BTgVbA{^^sjC!k|gbsR3{MH;_)efwXn5(**Mw6>Rw=G$inv66MQ9cN@{0v7Vj1C zpnVZiVb8D~lZj*cRvlVGzT;EaQfk*3`);q61!c5(_xj!*#e*OgT13iDonFUcbBTZ( zyjM0Zh+i5%k*{b?1k0pgo>#IP3Dv?#4Xkh14Xb+uk{$3QQW&N4Y|1a@rqp-RdfhCm zOSkf9FHn?L>Sur8miK-bx35j$(0xNJ^e*I>dXK!k85!Eena>w}cs=J8ymgym@X9NF zMml4Lk40d@I-eSNDXZ{)g}xGWfPEZT1cl3_4E@cKxEgLnV9Z2 zBD*x0$g)|EuOGMzST(l}65>cPmFtuPK=u zJ%t&Y1Xy_~PiJ&(!rnP)J^J!WJ<3_E_(@N~+~==#y}ug*EE{4%6~03le1haL1`I75 zj>@^*2A@(mF%e^Cm7*ET1G^0Bs3JXi=jKH5Gb-^~!(0hbfzaS}`(Br1eC6fsKI>$;d@TvOE!M!hagzKZ4^1Vw!&v#4t>3^D>WW` zW$Ew4HFf!NCd?fyzspNeCZWi49lto*k2(jft~jXa&L9=?yc~cMI`E2ecueWwKY)Ng zWeF0?&6qM+`CK@IqQtC3&ew)#+0M1!H#l2{;Rkk`IF+~)LU;6{Z1VuT31O6EGbMYX zbcC&DQg`-v{O37qsFVq88|@_tZOiH9V}IC${wb>L12=WCpXSEeuiKFU_wgKv&}iB7 z0^QU<9k#;Z)$7!s@aszJBLK;+%ppfs&zHQ-HHTIohw$z&HyWb-;$w|p*SSb0+j))G zzmkED&dy$+4;*u?i=lVsq{HEgEt8OhKN2sp!>9q&0g86n@Yvj_@i@mQ(mtA+ zC5BHe5u;q+5` ynJu@Wgkn5Si2djU*Axluty)DXqqG{Ia66q@U33gsfx@Do5;6B zZ7hP$Px%JHpM79|r!CuC%%ZaZsV9TOXcH*eujz8xqe-`-l<;%@Ba_~1SU$+Yor5-&-0GhJiog0Vw`zD@^5Ey+?q}yZo=S?K?Z2x% z%Tn|P<855N9HKpZ0WuDEiwzY3Pq?}qfGlP>O792zw?w7RCuO$jS?%#fh6j|6)hi3k zsq$AJ`R&ji_b4Fn3QFrIGGotqRL3PP!>$ks_`)AXt?@Cd)fHVU3u3GyeGoOJNYSaQ zNVoSjkov*C-Jhya#2#C$PtB1lZCHLS=LQ1}?L6nn`dc=RUpRli?s2tw#U@bPLzk^9 zNl+x(xwR`awt6^ad;g{~eQJcb3fs=@@>!VUsb5hb;zWEcc%gcuK22lF@_~mpjBvu8 zuU$Oe@p2)MvAKEC!^+nw;g7NVIGD;lO2PqxX9hW2C$!-EHmISw3yq4=#VFfU3KxR43{0`1;>SJRw&CGWtleZIrh zZXCPG{AxE%e|WC#U%Lfay~SnKL{3V+z|px^k(!GIzD&;fiX=i>a#VPE06YL=>}$FA zAB%IaKJo&Jh|^hf2u-vQcI#Ot#&xp}g4dKYM+RH`5NBToJ zj{fC%G@mK@K=a>9AzIV=2he;=XHjOlfqkuw1{XK~06{g!(?VXZx2e>C`w}4fQ@-K= ztsGV3(FhM_+;bMNX%$rz4>~MQ=r~P7=Y&|oK!UnG9olz!6T?PT>USvBTPzqqB8s3P z_UbU9E3p@?&L*P0hn6r^{YzNXx&5@;^Y~M)Q6>~_&mGOh#qPsa8-#ITjQs5QreVj? zPrdoVkD$0TQ)o8jt5k0yI~3`bUa>~a zP~=T&2PXuCaDJgsWJeiasHm3dDQ_pTpo^#Y_48UF?Mq+0+4C)oy5~e>Z;Ot6QkrEX z2osd^kH+C79om?UrNT5uH^mQ}$L1N+fp-dM#@~l0tkxh)Lf#*wdBLJ!>R0agv8Xij z%zFrFmq)H6XlS0bFuANR=HzB&^npO1$KXo!;z}W1E{e&9$tA9cg*7 z6v%zTtLJm1J`vD1{tuvEcXTyEFG*}=;pmtRa;Gu|;nig7LZe6NtuN&C7z&?rX}uX| z@vu-@w`aDuHL|o*7ut%7(3efYaE;+ny_~7DiRF*Wd}Irgcl=^(erO_pY6cHws@$k> zG2Hz&ROZp_sw$Ik6E8DUvKVBjDFg!|%)0rA%WVJ3@lsQqL;21A0RU)y_?yuu8s*wL z1#a=0JbL=L#FgRat`Cj<3g--JpF9-JY?$2NtEXCuVjkFaNvb?38;-OC32dG}xuO&q zaP@z0>jWYRNZIijh-+2Zpl)5()NXDP8+chA}h421nEQb^IW_8T(<3$%Dz!D$+L z{fShEWjIWRL1Fk`fx40bdX+5ti1_k@Z+Q(r-S)s#fygc2rrnD^FScn%FtRS`Qn06Z z1$t-4vTMM~lu+ZytW8vE`tgu71d~UQ!erO~0mvX`<(>Km!H#Ji z-&m=2FyVm;9SkX#b|%KaJL`XdETgkOP2Twks3Kf@Rk-2z*lcwpb8$5Em!Jk=a_#@a z2OPVr3nto8n+AUcc{QD)lq{DpFw7-LrOumhLHhT2?Q)Mrj#e2PdZ|O0;6gn)`~(rD zJim@h(VLeaaX8*w=Vz1p`pRQIzWVHjBjG*HFh`ypZ8%+DVj;ob=l&)nKWIW(bZ~sl z2l}Bd_lY50!%Yl7k%m`}<(!-P2nJ%>NYorUoKxAeu4W?15wY z`bH|6fy~EBaxLnCMhN$t79i&{#sZqI4-dfkjc{FGQ{A|SB|}iEC1kx1@Iw;fzCk#M7RipUas}Srpj$Nxl8wR zj|B3nto%5o{Z5<;KXf>f<4x!}b6&|OwcL>s>t^*>b1?H~{QT>ckvj~uaYJgu-);F2 zR=#LUsUdh#J1Gb~B#{^6f|h`W10|$%H^C9^{+jwm3Wn2I1s8(guhkAwF9%NUthN{9 zL;(-CF!6@w=K9t|s$CMrXPpOWxC+jz!FRq4omj+UoYP^aIhrbM$!fs_l(yY&1Mc00p52jrHzC4R!`ap|5wM^OWtlK zGdKo?{1{L2Bt?Bm`_I|y2|dT&X>=+I5-9XR49qTrQ0$lzx(v**cB%w!)Ed+-K7%v~ zusmKh{R7O~HB@SDxEUY?Gfa@#p&L-|h)JqOTQL`5G$G_Jii<5KJ33zlK@%TTQN3opnRhu{I8s ziHp*%lwIzr*2%Sjoobl>C@9ohsHRsN+Co6l;{Ah@i%Qz&;A(ZSfzF2HX)O5*d@S`q z5MI#1#mbQ9l4=6xl=Idjj?dP02jw#UfNeLH)BxArEdMM_FieTbhVI%@jODyMi}EHe zRQqe(jR{Hb07bFMW6@fGR{gA|(a=x2ggsI6of@xQrKvX;ewUuELGLN@MqT6Dd(MS| z%ly>O-Mee`nZLVjNj}SxoQL4Y=3RjF83SRuie)_fEI-!wkRMK~;kO*oEp}9`i)9h^ znZ6zBegLA7Yuc`h9n#u%reXpPuc+j)Bh3${cx70W+ zvu7|aFb7i&LGJ}v!}c~@=nxi8$wU7qmaIlqM@yhT+=-f2$%4hfC$tNcKv$H0%qT3L zxrTNZ6e*KU=ak1YROn6nO?g_WP&HiZ|BlAk4>p7?=<{9D>BNP*SP5iivV8;@&V}qoza+VKn;VGyQTxU5Q)Q>&YtB*gTu1;>XiXS)R^aE7-oCu-`ZDkG>voskFbIf z3-}Y32(iHq)Tu9a&>+sscaB{ zQz$`HpnFd6Cw?PVkcvA|Xuehm7twrQ5P3EbacXZKHOVO_SoOucVdQnLSQm%~ldj}O z?9A6(=aS)>xs9G;B1-cc353{0HeTl2Y?8YEl3KnkpDjhVqSkgdM$mWmh`h~*RRQ7? zR?NLsG9=L%O9XS`WsmrEWw<+w^m`2sgCx|Bj0xfyk|Q|d#`XN~KY+S$v_BPyEo?7- z081W(X)IUlwX4_AuJ>vhX8waj+GeiD1!GT_(dPa$10l9eDms(kjdfxCv3QKm>8P*6 z>S$dO9r@Rb{4Lk1s~oEu10)4%_&ZM&(kRFJ52f$hsq&gN{7km~4^q!62%c}^1o^$e zx=C77!@JnjCnl!9M!dCRg!yeYe#ExhtuX1sQRg*bRAQ)cm={s*ea!VKt5>>fp&6X6 zOM{>4YAYr#tgQ`g#pe7*y~L4S36v5rMl5caFzhvl#lj99F*)uM-g8Y5N1(7fapIOC z%9m4>Tr2U9Y2NmH3 zqx_4>hbN4dmllO!KN<7AXk}ywT<)XYDemvjUYa#Xy#=kgYFLX9ISS3BxCSvY z1^V8jaA2D{l~+c}tNQiRTvvxf_KqMArYH&vZyYQc%%^9fhhzRc>yGnjJ>q|Yf7xcg zYJh*O3k*^j%!OKKu!K6v^iu+k*{v~0Bqh)p!BeA?C$)WbO_T15c}*hNpF1pdx_a2| zBD;b$heur!Hq!-Xq~rcU;nYIo6&& zyGRHyV_jnP2H)`?ARdO8xhscJuw<-PsG<8qxQ|xT(0O{2rod~Lcy|!G#4&CG;exkq zX(XXb?GcbwJrlIkR(p*=v&$njjq+k)l;^dMd0>_BQ75kgXO0zcnaP}3&m$e~QNN

o%Yu{u4J?2) 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 diff --git a/radixconfig.yaml b/radixconfig.yaml deleted file mode 100644 index c02433ce9..000000000 --- a/radixconfig.yaml +++ /dev/null @@ -1,54 +0,0 @@ -apiVersion: radix.equinor.com/v1 -kind: RadixApplication -metadata: - name: flotilla -spec: - build: - secrets: - - TLS_SERVER_KEY - environments: - - name: dev - build: - from: main - - name: qa - - name: prod - components: - - name: frontend - src: frontend - ports: - - name: http - port: 3001 - publicPort: http - environmentConfig: - - environment: dev - variables: - VITE_AI_CONNECTION_STRING: "" - VITE_BACKEND_URL: "https://backend-flotilla-dev.playground.radix.equinor.com" - VITE_BACKEND_API_SCOPE: "api://ea4c7b92-47b3-45fb-bd25-a8070f0c495c/user_impersonation" - VITE_FRONTEND_URL: "https://frontend-flotilla-dev.playground.radix.equinor.com" - VITE_FRONTEND_BASE_ROUTE: "" - VITE_AD_CLIENT_ID: "f5993820-b7e2-4791-886f-f9f5027dc7be" - VITE_AD_TENANT_ID: "3aa4a235-b6e2-48d5-9195-7fcf05b459b0" - VITE_SARA_URL: "http://sara.robotics-analytics-dev.svc.cluster.local:8100" - VITE_SARA_API_SCOPE: "api://dd7e115a-037e-4846-99c4-07561158a9cd/user_impersonation" - - - name: backend - src: backend - ports: - - name: http - port: 8000 - publicPort: http - environmentConfig: - - environment: dev - variables: - Mqtt__Host: "broker" - ASPNETCORE_ENVIRONMENT: "Development" - secrets: - - AZURE_CLIENT_SECRET - - - name: broker - src: broker - ports: - - name: mqtt - port: 1883 - publicPort: mqtt From 613a79382168c0c9c477953419d8058d3a2ee874 Mon Sep 17 00:00:00 2001 From: Nicholas Dalhaug Date: Fri, 14 Aug 2026 17:05:05 +0200 Subject: [PATCH 3/4] Remove unused code --- .../ConfigurationBuilderExtensions.cs | 57 --- backend/api/Database/Models/Pointilla.cs | 15 - .../Database/Models/TransformationMatrices.cs | 58 --- backend/api/Options/AzureAdOptions.cs | 1 - backend/api/Utilities/Exceptions.cs | 4 - .../api/Utilities/HttpContextExtensions.cs | 15 +- backend/api/api.csproj | 3 - backend/api/appsettings.Development.json | 6 +- backend/api/appsettings.Production.json | 3 - backend/api/appsettings.Staging.json | 3 - backend/api/appsettings.Test.json | 3 - backend/api/appsettings.json | 11 - frontend/package.json | 22 +- frontend/pnpm-lock.yaml | 376 +----------------- frontend/src/language/en.json | 15 - frontend/src/language/no.json | 18 +- frontend/tsconfig.json | 2 +- 17 files changed, 8 insertions(+), 604 deletions(-) delete mode 100644 backend/api/Database/Models/TransformationMatrices.cs diff --git a/backend/api/Configurations/ConfigurationBuilderExtensions.cs b/backend/api/Configurations/ConfigurationBuilderExtensions.cs index 21727ccd9..f1c1866ea 100644 --- a/backend/api/Configurations/ConfigurationBuilderExtensions.cs +++ b/backend/api/Configurations/ConfigurationBuilderExtensions.cs @@ -2,63 +2,6 @@ { public static class ConfigurationBuilderExtensions { - ///

- /// Creates the AZURE_CLIENT_ID and AZURE_TENANT_ID configuration values for the - /// Environment Credentials - /// used by the application when dockerized. - /// - /// - /// - public static void AddAppSettingsEnvironmentVariables(this WebApplicationBuilder builder) - { - string? clientId = builder - .Configuration.GetSection("AzureAd") - .GetValue("ClientId"); - if (clientId is not null) - { - Environment.SetEnvironmentVariable("AZURE_CLIENT_ID", clientId); - Console.WriteLine("'AZURE_CLIENT_ID' set to " + clientId); - } - - string? tenantId = builder - .Configuration.GetSection("AzureAd") - .GetValue("TenantId"); - if (tenantId is not null) - { - Environment.SetEnvironmentVariable("AZURE_TENANT_ID", tenantId); - Console.WriteLine("'AZURE_TENANT_ID' set to " + tenantId); - } - } - - /// - /// Creates if don't already exist/sets all the configuration variables present on the .env file for the - /// Environment Credentials - /// used by the application when dockerized. - /// - /// - /// - /// - public static void AddDotEnvironmentVariables( - this WebApplicationBuilder builder, - string filePath - ) - { - if (!File.Exists(filePath)) - return; - - foreach (string line in File.ReadAllLines(filePath)) - { - string[] parts = line.Split('=', StringSplitOptions.RemoveEmptyEntries); - - if (parts.Length == 0 || parts[0].StartsWith('#')) - continue; - - Environment.SetEnvironmentVariable(parts[0], parts[1]); - } - - builder.Configuration.AddEnvironmentVariables(); - } - /// /// Configures the logger used by the application /// diff --git a/backend/api/Database/Models/Pointilla.cs b/backend/api/Database/Models/Pointilla.cs index 4b67c93fe..a24bdf231 100644 --- a/backend/api/Database/Models/Pointilla.cs +++ b/backend/api/Database/Models/Pointilla.cs @@ -16,19 +16,4 @@ public struct PointillaMapResponse public int ZoomMax { get; set; } public int TileSize { get; set; } } - - public struct PointillaMapQuery - { - public string PlantCode { get; set; } - public int FloorId { get; set; } - } - - public struct PointillaMapTilesQuery - { - public string PlantCode { get; set; } - public int FloorId { get; set; } - public int ZoomLevel { get; set; } - public int X { get; set; } - public int Y { get; set; } - } } diff --git a/backend/api/Database/Models/TransformationMatrices.cs b/backend/api/Database/Models/TransformationMatrices.cs deleted file mode 100644 index 8bac495fa..000000000 --- a/backend/api/Database/Models/TransformationMatrices.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using Microsoft.EntityFrameworkCore; - -#nullable disable -namespace Api.Database.Models -{ - [Owned] - public class TransformationMatrices - { - // In order to get a pixel coordinate P={p1, p2} from an asset coordinate E={e1, e2}, we need to - // perform the transformation: - // P = CE + D - [Required] - public double C1 { get; set; } - - [Required] - public double C2 { get; set; } - - [Required] - public double D1 { get; set; } - - [Required] - public double D2 { get; set; } - - public TransformationMatrices() - { - C1 = 0; - C2 = 0; - D1 = 0; - D2 = 0; - } - - public TransformationMatrices(TransformationMatrices copy) - { - C1 = copy.C1; - C2 = copy.C2; - D1 = copy.D1; - D2 = copy.D2; - } - - public TransformationMatrices(double[] p1, double[] p2, int imageWidth, int imageHeight) - { - C1 = imageWidth / (p2[0] - p1[0]); - C2 = imageHeight / (p2[1] - p1[1]); - D1 = -(C1 * p1[0]); - D2 = -(C2 * p1[1]); - } - - public List AsMatrix() - { - return - [ - [C1, C2], - [D1, D2], - ]; - } - } -} diff --git a/backend/api/Options/AzureAdOptions.cs b/backend/api/Options/AzureAdOptions.cs index 31fb4efc0..5f94cfb1f 100644 --- a/backend/api/Options/AzureAdOptions.cs +++ b/backend/api/Options/AzureAdOptions.cs @@ -5,6 +5,5 @@ public class AzureAdOptions public string ClientId { get; set; } = ""; public string ClientSecret { get; set; } = ""; public string TenantId { get; set; } = ""; - public string StorageAccount { get; set; } = ""; } } diff --git a/backend/api/Utilities/Exceptions.cs b/backend/api/Utilities/Exceptions.cs index 241b853fb..bfbd54640 100644 --- a/backend/api/Utilities/Exceptions.cs +++ b/backend/api/Utilities/Exceptions.cs @@ -68,16 +68,12 @@ public class MissionNotFoundException(string message) : Exception(message) { } public class InspectionNotFoundException(string message) : Exception(message) { } - public class InspectionNotAvailableYetException(string message) : Exception(message) { } - public class MissionTaskNotFoundException(string message) : Exception(message) { } public class MissionRunNotFoundException(string message) : Exception(message) { } public class RobotNotFoundException(string message) : Exception(message) { } - public class RobotInformationNotAvailableException(string message) : Exception(message) { } - public class RobotPreCheckFailedException(string message) : Exception(message) { } public class InspectionAreaExistsException(string message) : Exception(message) { } diff --git a/backend/api/Utilities/HttpContextExtensions.cs b/backend/api/Utilities/HttpContextExtensions.cs index 60f292909..e421152ba 100644 --- a/backend/api/Utilities/HttpContextExtensions.cs +++ b/backend/api/Utilities/HttpContextExtensions.cs @@ -1,20 +1,7 @@ -using System.Security.Claims; - -namespace Api.Utilities +namespace Api.Utilities { public static class HttpContextExtensions { - public static List GetRequestedRoles(this HttpContext context) - { - return context.User?.Claims.Where(c => c.Type == ClaimTypes.Role).ToList() - ?? new List(); - } - - public static List GetRequestedClaims(this HttpContext context) - { - return context.User?.Claims.ToList() ?? new List(); - } - public static string? GetUserObjectId(this HttpContext context) { return context.User?.FindFirst("oid")?.Value; diff --git a/backend/api/api.csproj b/backend/api/api.csproj index 143d16636..45b3f2a3c 100644 --- a/backend/api/api.csproj +++ b/backend/api/api.csproj @@ -11,7 +11,6 @@ - @@ -48,11 +47,9 @@ - - diff --git a/backend/api/appsettings.Development.json b/backend/api/appsettings.Development.json index d07ddc6f6..25f56bd40 100644 --- a/backend/api/appsettings.Development.json +++ b/backend/api/appsettings.Development.json @@ -3,9 +3,6 @@ "ClientId": "ea4c7b92-47b3-45fb-bd25-a8070f0c495c", "ClientSecret": "Fill in ASP.NET Secret Manager" }, - "ManagedIdentity": { - "ClientId": "12242230-9788-467d-938e-84caafd25b22" - }, "KeyVault": { "VaultUri": "https://robotics-dev-kv.vault.azure.net/" }, @@ -33,8 +30,7 @@ "User": "FlotillaBackendAuthDev" }, "OpenTelemetry": { - "Enabled": true, - "AzureMonitorExportEnabled": true + "Enabled": true }, "Redis": { "HostName": "robotics-dev-redis.northeurope.redis.azure.net" diff --git a/backend/api/appsettings.Production.json b/backend/api/appsettings.Production.json index 6dcb07352..315a5f3d0 100644 --- a/backend/api/appsettings.Production.json +++ b/backend/api/appsettings.Production.json @@ -3,9 +3,6 @@ "ClientId": "9eb21071-0895-4097-b0d3-dab03c9de8ac", "ClientSecret": "Fill in ASP.NET Secret Manager" }, - "ManagedIdentity": { - "ClientId": "3def0261-2f46-45e1-bd2b-7dc5c52b724f" - }, "KeyVault": { "VaultUri": "https://robotics-prod-kv.vault.azure.net/" }, diff --git a/backend/api/appsettings.Staging.json b/backend/api/appsettings.Staging.json index 91df02a8a..0ccdb7a66 100644 --- a/backend/api/appsettings.Staging.json +++ b/backend/api/appsettings.Staging.json @@ -3,9 +3,6 @@ "ClientId": "d89ab07a-3552-4a1f-9a18-9c819c13f2b8", "ClientSecret": "Fill in ASP.NET Secret Manager" }, - "ManagedIdentity": { - "ClientId": "d5d5ea06-4952-4843-899c-64aeb4e52240" - }, "KeyVault": { "VaultUri": "https://robotics-staging-kv.vault.azure.net/" }, diff --git a/backend/api/appsettings.Test.json b/backend/api/appsettings.Test.json index 31e584f51..a77ca862f 100644 --- a/backend/api/appsettings.Test.json +++ b/backend/api/appsettings.Test.json @@ -7,9 +7,6 @@ "KeyVault": { "UseKeyVault": false }, - "Authorization": { - "Roles": "Test.User" - }, "AllowedHosts": "*", "AllowedOrigins": [ "https://*.equinor.com/", diff --git a/backend/api/appsettings.json b/backend/api/appsettings.json index d493723d4..904b4d24e 100644 --- a/backend/api/appsettings.json +++ b/backend/api/appsettings.json @@ -11,22 +11,12 @@ "Instance": "https://login.microsoftonline.com", "AllowUsingClientSecret": false }, - "BlobStorage": { - "DefaultAuthType": "TokenCredential" - }, - "Echo": { - "BaseUrl": "https://echohubapi.equinor.com/api", - "Scopes": [ - "bf0b2569-e09c-42f0-8095-5a52a873eb7b/.default" - ] - }, "SARA": { "BaseUrl": "", "Scopes": [ "" ] }, - "IsarConnectionTimeout": 15, "Logging": { "LogLevel": { "Default": "Warning", @@ -64,7 +54,6 @@ "https://*.equinor.com/" ], "Database": { - "ConnectionString": "", "InitializeInMemDb": false, "SeedExampleDataPostgres": false, "Timeout": 30, diff --git a/frontend/package.json b/frontend/package.json index a03d9115b..34b285d0e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -2,7 +2,6 @@ "name": "flotilla", "version": "0.1.0", "private": true, - "homepage": ".", "type": "module", "dependencies": { "@azure/msal-browser": "^5.18.0", @@ -11,9 +10,7 @@ "@equinor/eds-icons": "^1.5.1", "@equinor/eds-tokens": "^2.3.2", "@microsoft/signalr": "^10.0.11", - "@nevware21/ts-async": "^0.5.5", "@tanstack/react-query": "^5.101.4", - "@types/node": "^25.9.5", "@types/react": "^19.2.18", "@types/react-dom": "^19.2.4", "@vitejs/plugin-react-swc": "^4.3.3", @@ -29,13 +26,11 @@ "react-chartjs-2": "^5.3.1", "react-dom": "^19.2.8", "react-leaflet": "^5.0.0", - "react-modal": "^3.16.3", "react-player": "^3.4.0", "react-router": "^8.3.0", "styled-components": "^6.5.0", "ts-custom-error": "^3.3.1", - "vite": "^7.3.6", - "vite-plugin-svgr": "^4.5.0" + "vite": "^7.3.6" }, "scripts": { "dev": "vite", @@ -45,25 +40,10 @@ "lint": "eslint src", "prettier_check": "prettier --check src" }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - }, "devDependencies": { "@eslint/js": "^9.39.5", "@types/leaflet": "^1.9.22", - "@types/luxon": "^3.7.3", "@types/react-big-calendar": "^1.16.3", - "@types/react-modal": "^3.16.3", - "@types/styled-components": "^5.1.36", "eslint": "^9.39.5", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^7.1.1", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 57d262309..c39beace5 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -29,15 +29,9 @@ importers: '@microsoft/signalr': specifier: ^10.0.11 version: 10.0.11 - '@nevware21/ts-async': - specifier: ^0.5.5 - version: 0.5.5 '@tanstack/react-query': specifier: ^5.101.4 version: 5.101.4(react@19.2.8) - '@types/node': - specifier: ^25.9.5 - version: 25.9.5 '@types/react': specifier: ^19.2.18 version: 19.2.18 @@ -83,9 +77,6 @@ importers: react-leaflet: specifier: ^5.0.0 version: 5.0.0(leaflet@1.9.4)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - react-modal: - specifier: ^3.16.3 - version: 3.16.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8) react-player: specifier: ^3.4.0 version: 3.4.0(@svta/cml-cta@1.0.6(@svta/cml-structured-field-values@1.1.3(@svta/cml-utils@1.5.0))(@svta/cml-utils@1.5.0))(@svta/cml-structured-field-values@1.1.3(@svta/cml-utils@1.5.0))(@svta/cml-utils@1.5.0)(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) @@ -101,9 +92,6 @@ importers: vite: specifier: ^7.3.6 version: 7.3.6(@types/node@25.9.5) - vite-plugin-svgr: - specifier: ^4.5.0 - version: 4.5.0(rollup@4.62.4)(typescript@5.9.3)(vite@7.3.6(@types/node@25.9.5)) devDependencies: '@eslint/js': specifier: ^9.39.5 @@ -111,18 +99,9 @@ importers: '@types/leaflet': specifier: ^1.9.22 version: 1.9.22 - '@types/luxon': - specifier: ^3.7.3 - version: 3.7.3 '@types/react-big-calendar': specifier: ^1.16.3 version: 1.16.3 - '@types/react-modal': - specifier: ^3.16.3 - version: 3.16.3 - '@types/styled-components': - specifier: ^5.1.36 - version: 5.1.36 eslint: specifier: ^9.39.5 version: 9.39.5 @@ -641,12 +620,6 @@ packages: os: [linux] libc: [glibc] - '@nevware21/ts-async@0.5.5': - resolution: {integrity: sha512-vwqaL05iJPjLeh5igPi8MeeAu10i+Aq7xko1fbo9F5Si6MnVN5505qaV7AhSdk5MCBJVT/UYMk3kgInNjDb4Ig==} - - '@nevware21/ts-utils@0.16.0': - resolution: {integrity: sha512-DDln15VUBSw5JjJlNno1UCSzgAGzHHklm7u695jXDCzwSn7W2F7B2VYHwLLcT1cOWQQBQAJK6e2/Y8oTG+W0Ig==} - '@popperjs/core@2.11.8': resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} @@ -688,15 +661,6 @@ packages: '@rolldown/pluginutils@1.0.1': resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} - '@rollup/pluginutils@5.4.0': - resolution: {integrity: sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - '@rollup/rollup-android-arm-eabi@4.62.4': resolution: {integrity: sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg==} cpu: [arm] @@ -838,74 +802,6 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - '@svgr/babel-plugin-add-jsx-attribute@8.0.0': - resolution: {integrity: sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@svgr/babel-plugin-remove-jsx-attribute@8.0.0': - resolution: {integrity: sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0': - resolution: {integrity: sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0': - resolution: {integrity: sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@svgr/babel-plugin-svg-dynamic-title@8.0.0': - resolution: {integrity: sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@svgr/babel-plugin-svg-em-dimensions@8.0.0': - resolution: {integrity: sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@svgr/babel-plugin-transform-react-native-svg@8.1.0': - resolution: {integrity: sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@svgr/babel-plugin-transform-svg-component@8.0.0': - resolution: {integrity: sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==} - engines: {node: '>=12'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@svgr/babel-preset@8.1.0': - resolution: {integrity: sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@svgr/core@8.1.0': - resolution: {integrity: sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==} - engines: {node: '>=14'} - - '@svgr/hast-util-to-babel-ast@8.0.0': - resolution: {integrity: sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==} - engines: {node: '>=14'} - - '@svgr/plugin-jsx@8.1.0': - resolution: {integrity: sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==} - engines: {node: '>=14'} - peerDependencies: - '@svgr/core': '*' - '@svta/cml-608@1.0.2': resolution: {integrity: sha512-ZEJ68330gcLKfvVv6Qifr1HR7+GldDUxzkjqSbqRK7jHtHSLSV1JAyDwlYe8+C7ABuY1bp8MpgJ4/Gg+jl+pLQ==} engines: {node: '>=20'} @@ -1102,11 +998,6 @@ packages: '@types/google_interactive_media_ads_types@3.697.1': resolution: {integrity: sha512-gRjJoPKQRjjTuySHqZe8Pnu6a5B0bKbTUD2ioDLPSWlEuBaAc2p1mzUzy42SCeWkyYffo2Mnv+blUGuvVvx3dQ==} - '@types/hoist-non-react-statics@3.3.7': - resolution: {integrity: sha512-PQTyIulDkIDro8P+IHbKCsw7U2xxBYflVzW/FgWdCAePD9xGSidgA76/GeJ6lBKoblyhf9pBY763gbrN+1dI8g==} - peerDependencies: - '@types/react': '*' - '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -1116,9 +1007,6 @@ packages: '@types/leaflet@1.9.22': resolution: {integrity: sha512-h3lhECYEKDasG7LFHu+GiHqAvsgLuQvlJvVZzJDGONo3sEL+wUOqSFLnwkZlK0qVxnxbuGFW8iBlJNYs5wgndA==} - '@types/luxon@3.7.3': - resolution: {integrity: sha512-pE7BSbKHiojpl4v7iEzdfFLXXJaH5RPJxI3Wr4x3HU59l83PJfhcUx4mGPWq245+DCAENAzRF/+ZoYr2tJCe/g==} - '@types/node@25.9.5': resolution: {integrity: sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg==} @@ -1133,15 +1021,9 @@ packages: peerDependencies: '@types/react': ^19.2.0 - '@types/react-modal@3.16.3': - resolution: {integrity: sha512-xXuGavyEGaFQDgBv4UVm8/ZsG+qxeQ7f77yNrW3n+1J6XAstUy5rYHeIHPh1KzsGc6IkCIdu6lQ2xWzu1jBTLg==} - '@types/react@19.2.18': resolution: {integrity: sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==} - '@types/styled-components@5.1.36': - resolution: {integrity: sha512-pGMRNY5G2rNDKEv2DOiFYa7Ft1r0jrhmgBwHhOMzPTgCjO76bCot0/4uEfqj7K0Jf1KdQmDtAuaDk9EAs9foSw==} - '@types/warning@3.0.4': resolution: {integrity: sha512-CqN8MnISMwQbLJXO3doBAV4Yw9hx9/Pyr2rZ78+NfaCnhyRA/nKrpyk6E7mKw17ZOaQdLpK9GiUjrqLzBlN3sg==} @@ -1360,10 +1242,6 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - camelcase@6.3.0: - resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} - engines: {node: '>=10'} - caniuse-lite@1.0.30001806: resolution: {integrity: sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==} @@ -1425,15 +1303,6 @@ packages: cookie-es@3.1.1: resolution: {integrity: sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==} - cosmiconfig@8.3.6: - resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} - engines: {node: '>=14'} - peerDependencies: - typescript: '>=4.9.5' - peerDependenciesMeta: - typescript: - optional: true - cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -1521,9 +1390,6 @@ packages: dom-helpers@6.0.1: resolution: {integrity: sha512-IKySryuFwseGkrCA/pIqlwUPOD50w1Lj/B2Yief3vBOP18k5y4t+hTqKh55gULDVeJMRitcozve+g/wVFf4sFQ==} - dot-case@3.0.4: - resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} - downshift@9.3.2: resolution: {integrity: sha512-5VD0WZLQDhipWiDU+K5ili3VDhGrXwlvOlSaSG1Cb0eS4XpssxVuoD09JNgju+bAzxB2Wvlwx+FwTE/FNdrqow==} peerDependencies: @@ -1536,17 +1402,10 @@ packages: electron-to-chromium@1.5.401: resolution: {integrity: sha512-H6ViHN68nGYlChEvlIU67fn8O2/tpbWQPwck98yaJmh+08LSvHiydzDQ6oXNccLU3kNRVIRS9A4mA7CG+i6fLQ==} - entities@4.5.0: - resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} - engines: {node: '>=0.12'} - entities@8.0.0: resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} engines: {node: '>=20.19.0'} - error-ex@1.3.4: - resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} - es-abstract-get@1.0.0: resolution: {integrity: sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==} engines: {node: '>= 0.4'} @@ -1658,9 +1517,6 @@ packages: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} - estree-walker@2.0.2: - resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} - estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} @@ -1680,9 +1536,6 @@ packages: resolution: {integrity: sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==} engines: {node: '>=12.0.0'} - exenv@1.2.2: - resolution: {integrity: sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw==} - expect-type@1.4.0: resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} engines: {node: '>=12.0.0'} @@ -1824,9 +1677,6 @@ packages: hls.js@1.6.16: resolution: {integrity: sha512-VSIRpLfRwlAAdGL4wiTucx2ScRipo0ed1FBatWkyt832jC4CReKstga6yIhYVwGu9LOBjuX9wzmRMeQdBJtzEA==} - hoist-non-react-statics@3.3.2: - resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} - html-encoding-sniffer@6.0.0: resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} @@ -1875,9 +1725,6 @@ packages: resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} - is-arrayish@0.2.1: - resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - is-async-function@2.1.1: resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} engines: {node: '>= 0.4'} @@ -2014,9 +1861,6 @@ packages: json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - json-parse-even-better-errors@2.3.1: - resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} @@ -2049,9 +1893,6 @@ packages: lie@3.1.1: resolution: {integrity: sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==} - lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - livekit-client@2.21.0: resolution: {integrity: sha512-RBUhPkV/sl1nzl8lokVlK5uATPwn0AlsudCBZXissw/kDl9yz8ac4pNJ43iPpMVoHOeBYH/BZ3vUC1adqa/zFQ==} peerDependencies: @@ -2081,9 +1922,6 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true - lower-case@2.0.2: - resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} - lru-cache@11.5.2: resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} engines: {node: 20 || >=22} @@ -2150,9 +1988,6 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - no-case@3.0.4: - resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} - node-exports-info@1.6.2: resolution: {integrity: sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag==} engines: {node: '>= 0.4'} @@ -2222,10 +2057,6 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} - parse-json@5.2.0: - resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} - engines: {node: '>=8'} - parse5@8.0.1: resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} @@ -2243,10 +2074,6 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} - pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -2329,12 +2156,6 @@ packages: react-lifecycles-compat@3.0.4: resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==} - react-modal@3.16.3: - resolution: {integrity: sha512-yCYRJB5YkeQDQlTt17WGAgFJ7jr2QYcWa1SHqZ3PluDmnKJ/7+tVU+E6uKyZ0nODaeEj+xCpK4LcSnKXLMC0Nw==} - peerDependencies: - react: ^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18 || ^19 - react-dom: ^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18 || ^19 - react-overlays@5.2.1: resolution: {integrity: sha512-GLLSOLWr21CqtJn8geSwQfoJufdt3mfdsnIiQswouuQ2MMPns+ihZklxvsTDKD3cR2tF8ELbi5xUsvqVhR6WvA==} peerDependencies: @@ -2479,9 +2300,6 @@ packages: siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - snake-case@3.0.4: - resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} - source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -2556,9 +2374,6 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - svg-parser@2.0.4: - resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==} - symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} @@ -2705,11 +2520,6 @@ packages: vimeo-video-element@1.7.2: resolution: {integrity: sha512-7QM7fvSZvTTSq4igxBuO6Gc+0u3Exgk4IaLNixVzilCPzHEf7SN8b6YLXSM5QCs0ineTJI4XjiUCSoIbabHvwg==} - vite-plugin-svgr@4.5.0: - resolution: {integrity: sha512-W+uoSpmVkSmNOGPSsDCWVW/DDAyv+9fap9AZXBvWiQqrboJ08j2vh0tFxTD/LjwqwAd3yYSVJgm54S/1GhbdnA==} - peerDependencies: - vite: '>=2.6.0' - vite@7.3.6: resolution: {integrity: sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3358,12 +3168,6 @@ snapshots: '@napi-rs/lzma-linux-x64-gnu@1.5.1': optional: true - '@nevware21/ts-async@0.5.5': - dependencies: - '@nevware21/ts-utils': 0.16.0 - - '@nevware21/ts-utils@0.16.0': {} - '@popperjs/core@2.11.8': {} '@react-aria/utils@3.34.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': @@ -3405,14 +3209,6 @@ snapshots: '@rolldown/pluginutils@1.0.1': {} - '@rollup/pluginutils@5.4.0(rollup@4.62.4)': - dependencies: - '@types/estree': 1.0.9 - estree-walker: 2.0.2 - picomatch: 4.0.5 - optionalDependencies: - rollup: 4.62.4 - '@rollup/rollup-android-arm-eabi@4.62.4': optional: true @@ -3490,76 +3286,6 @@ snapshots: '@standard-schema/spec@1.1.0': {} - '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - - '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - - '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - - '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - - '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - - '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - - '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - - '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - - '@svgr/babel-preset@8.1.0(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.29.7) - '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.29.7) - '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.29.7) - '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.29.7) - '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.29.7) - '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.29.7) - '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.29.7) - '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.29.7) - - '@svgr/core@8.1.0(typescript@5.9.3)': - dependencies: - '@babel/core': 7.29.7 - '@svgr/babel-preset': 8.1.0(@babel/core@7.29.7) - camelcase: 6.3.0 - cosmiconfig: 8.3.6(typescript@5.9.3) - snake-case: 3.0.4 - transitivePeerDependencies: - - supports-color - - typescript - - '@svgr/hast-util-to-babel-ast@8.0.0': - dependencies: - '@babel/types': 7.29.8 - entities: 4.5.0 - - '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.9.3))': - dependencies: - '@babel/core': 7.29.7 - '@svgr/babel-preset': 8.1.0(@babel/core@7.29.7) - '@svgr/core': 8.1.0(typescript@5.9.3) - '@svgr/hast-util-to-babel-ast': 8.0.0 - svg-parser: 2.0.4 - transitivePeerDependencies: - - supports-color - '@svta/cml-608@1.0.2': {} '@svta/cml-cmcd@2.3.2(@svta/cml-structured-field-values@1.1.3(@svta/cml-utils@1.5.0))(@svta/cml-utils@1.5.0)': @@ -3699,11 +3425,6 @@ snapshots: '@types/google_interactive_media_ads_types@3.697.1': {} - '@types/hoist-non-react-statics@3.3.7(@types/react@19.2.18)': - dependencies: - '@types/react': 19.2.18 - hoist-non-react-statics: 3.3.2 - '@types/json-schema@7.0.15': {} '@types/json5@0.0.29': {} @@ -3712,11 +3433,10 @@ snapshots: dependencies: '@types/geojson': 7946.0.16 - '@types/luxon@3.7.3': {} - '@types/node@25.9.5': dependencies: undici-types: 7.24.6 + optional: true '@types/prop-types@15.7.15': {} @@ -3730,20 +3450,10 @@ snapshots: dependencies: '@types/react': 19.2.18 - '@types/react-modal@3.16.3': - dependencies: - '@types/react': 19.2.18 - '@types/react@19.2.18': dependencies: csstype: 3.2.3 - '@types/styled-components@5.1.36': - dependencies: - '@types/hoist-non-react-statics': 3.3.7(@types/react@19.2.18) - '@types/react': 19.2.18 - csstype: 3.2.3 - '@types/warning@3.0.4': {} '@typescript-eslint/eslint-plugin@8.66.0(@typescript-eslint/parser@8.66.0(eslint@9.39.5)(typescript@5.9.3))(eslint@9.39.5)(typescript@5.9.3)': @@ -4033,8 +3743,6 @@ snapshots: callsites@3.1.0: {} - camelcase@6.3.0: {} - caniuse-lite@1.0.30001806: {} castable-video@1.1.16: @@ -4083,15 +3791,6 @@ snapshots: cookie-es@3.1.1: {} - cosmiconfig@8.3.6(typescript@5.9.3): - dependencies: - import-fresh: 3.3.1 - js-yaml: 4.3.1 - parse-json: 5.2.0 - path-type: 4.0.0 - optionalDependencies: - typescript: 5.9.3 - cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -4212,11 +3911,6 @@ snapshots: '@babel/runtime': 7.29.7 csstype: 3.2.3 - dot-case@3.0.4: - dependencies: - no-case: 3.0.4 - tslib: 2.8.1 - downshift@9.3.2(react@19.2.8): dependencies: '@babel/runtime': 7.29.7 @@ -4234,14 +3928,8 @@ snapshots: electron-to-chromium@1.5.401: {} - entities@4.5.0: {} - entities@8.0.0: {} - error-ex@1.3.4: - dependencies: - is-arrayish: 0.2.1 - es-abstract-get@1.0.0: dependencies: es-errors: 1.3.0 @@ -4491,8 +4179,6 @@ snapshots: estraverse@5.3.0: {} - estree-walker@2.0.2: {} - estree-walker@3.0.3: dependencies: '@types/estree': 1.0.9 @@ -4505,8 +4191,6 @@ snapshots: eventsource@2.0.2: {} - exenv@1.2.2: {} - expect-type@1.4.0: {} fast-deep-equal@3.1.3: {} @@ -4646,10 +4330,6 @@ snapshots: hls.js@1.6.16: {} - hoist-non-react-statics@3.3.2: - dependencies: - react-is: 16.13.1 - html-encoding-sniffer@6.0.0: dependencies: '@exodus/bytes': 1.15.1 @@ -4705,8 +4385,6 @@ snapshots: call-bound: 1.0.4 get-intrinsic: 1.3.0 - is-arrayish@0.2.1: {} - is-async-function@2.1.1: dependencies: async-function: 1.0.0 @@ -4865,8 +4543,6 @@ snapshots: json-buffer@3.0.1: {} - json-parse-even-better-errors@2.3.1: {} - json-schema-traverse@0.4.1: {} json-stable-stringify-without-jsonify@1.0.1: {} @@ -4899,8 +4575,6 @@ snapshots: dependencies: immediate: 3.0.6 - lines-and-columns@1.2.4: {} - livekit-client@2.21.0(@types/dom-mediacapture-record@1.0.22): dependencies: '@livekit/mutex': 1.1.1 @@ -4934,10 +4608,6 @@ snapshots: dependencies: js-tokens: 4.0.0 - lower-case@2.0.2: - dependencies: - tslib: 2.8.1 - lru-cache@11.5.2: {} lru-cache@5.1.1: @@ -4992,11 +4662,6 @@ snapshots: natural-compare@1.4.0: {} - no-case@3.0.4: - dependencies: - lower-case: 2.0.2 - tslib: 2.8.1 - node-exports-info@1.6.2: dependencies: array.prototype.flatmap: 1.3.3 @@ -5076,13 +4741,6 @@ snapshots: dependencies: callsites: 3.1.0 - parse-json@5.2.0: - dependencies: - '@babel/code-frame': 7.29.7 - error-ex: 1.3.4 - json-parse-even-better-errors: 2.3.1 - lines-and-columns: 1.2.4 - parse5@8.0.1: dependencies: entities: 8.0.0 @@ -5095,8 +4753,6 @@ snapshots: path-parse@1.0.7: {} - path-type@4.0.0: {} - pathe@2.0.3: {} picocolors@1.1.1: {} @@ -5193,15 +4849,6 @@ snapshots: react-lifecycles-compat@3.0.4: {} - react-modal@3.16.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8): - dependencies: - exenv: 1.2.2 - prop-types: 15.8.1 - react: 19.2.8 - react-dom: 19.2.8(react@19.2.8) - react-lifecycles-compat: 3.0.4 - warning: 4.0.3 - react-overlays@5.2.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8): dependencies: '@babel/runtime': 7.29.7 @@ -5422,11 +5069,6 @@ snapshots: siginfo@2.0.0: {} - snake-case@3.0.4: - dependencies: - dot-case: 3.0.4 - tslib: 2.8.1 - source-map-js@1.2.1: {} spotify-audio-element@1.0.4: {} @@ -5508,8 +5150,6 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svg-parser@2.0.4: {} - symbol-tree@3.2.4: {} tabbable@6.5.0: {} @@ -5642,7 +5282,8 @@ snapshots: react: 19.2.8 react-lifecycles-compat: 3.0.4 - undici-types@7.24.6: {} + undici-types@7.24.6: + optional: true undici@7.29.0: {} @@ -5672,17 +5313,6 @@ snapshots: '@vimeo/player': 2.29.0 media-played-ranges-mixin: 0.1.0 - vite-plugin-svgr@4.5.0(rollup@4.62.4)(typescript@5.9.3)(vite@7.3.6(@types/node@25.9.5)): - dependencies: - '@rollup/pluginutils': 5.4.0(rollup@4.62.4) - '@svgr/core': 8.1.0(typescript@5.9.3) - '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.9.3)) - vite: 7.3.6(@types/node@25.9.5) - transitivePeerDependencies: - - rollup - - supports-color - - typescript - vite@7.3.6(@types/node@25.9.5): dependencies: esbuild: 0.28.1 diff --git a/frontend/src/language/en.json b/frontend/src/language/en.json index 7f80d3dc2..1782bfda8 100644 --- a/frontend/src/language/en.json +++ b/frontend/src/language/en.json @@ -55,10 +55,6 @@ "History": "History", "Stop": "Stop", "Start": "Start", - "North": "North", - "East": "East", - "South": "South", - "West": "West", "Skip button pressed warning text": "You are about to skip the entire mission. If there are no more missions in the queue, the robot will return to the start position.", "Skip button pressed confirmation text": "Are you sure you want to skip the mission?", "Skip mission": "Skip mission", @@ -98,7 +94,6 @@ "Add mission to the queue": "Add mission to the queue", "Not yet performed": "Not yet performed", "Never": "Never", - "Idle": "Idle", "No inspections available": "No inspections available", "Queue mission": "Queue mission", "for": "for", @@ -136,7 +131,6 @@ "Dock banner text": "The robots are driving to the dock and will not run missions. To continue the mission press the 'Dismiss robots from dock' button.", "Dismiss dock banner text": "The robots will continue the missions.", "Completed Tasks": "Completed Tasks", - "Failed to retrieve missions": "Failed to retrieve missions", "Request error": "Request error", "Failed to delete mission from queue": "Failed to delete mission from queue", "Failed to rerun mission": "Failed to rerun mission", @@ -198,7 +192,6 @@ "Detection type": "Detection type", "leak": "Leak", "Inspection report for task": "Inspection report for task", - "Battery is too low to start a mission.": "Battery is too low to start a mission.", "Robot is sent to dock and cannot run missions. Queued missions will run when robot is dismissed from dock.": "Robot is sent to dock and cannot run missions. Queued missions will run when robot is dismissed from dock.", "Queued Missions": "Queued Missions", "Waiting for inspection result": "Waiting for inspection result", @@ -296,14 +289,6 @@ "Confidence": "Confidence", "Value": "Value", "Analysis": "Analysis", - "CO2 consentration: {0}%": "CO2 consentration: {0}%", - "Successful Missions": "Successful Missions", - "Partially Successful Missions": "Partially Successful Missions", - "Failed Missions": "Failed Missions", - "Mission Success Rate": "Mission Success Rate", - "Successful Tasks": "Successful Tasks", - "Failed Tasks": "Failed Tasks", - "Task Success Rate": "Task Success Rate", "Loading installation data": "Loading installation data", "RechargingWithMission": "Recharging", "GoingToRechargingWithMission": "Returning to recharge", diff --git a/frontend/src/language/no.json b/frontend/src/language/no.json index 9d79c184c..6b9c12f85 100644 --- a/frontend/src/language/no.json +++ b/frontend/src/language/no.json @@ -55,10 +55,6 @@ "History": "Historikk", "Stop": "Stopp", "Start": "Start", - "North": "Nord", - "East": "Øst", - "South": "Sør", - "West": "Vest", "Skip button pressed warning text": "Du er i ferd med å hoppe over hele oppdraget. Hvis det ikke er flere oppdrag i kø vil roboten bli sendt tilbake til startposisjonen sin.", "Skip button pressed confirmation text": "Er du sikker på at du vil hoppe over oppdraget?", "Skip mission": "Hopp over oppdraget", @@ -98,7 +94,6 @@ "Add mission to the queue": "Legg til oppdrag i kø", "Not yet performed": "Aldri kjørt", "Never": "Aldri", - "Idle": "Inaktiv", "No inspections available": "Ingen tilgjengelige inspeksjoner", "Queue mission": "Legg oppdraget i kø", "for": "for", @@ -136,7 +131,6 @@ "Dock banner text": "Robotene kjører til ladestasjonene og vil ikke kjøre oppdrag. For å fortsette oppdrag trykk på knappen 'Slipp robotene ut fra ladestasjon'.", "Dismiss dock banner text": "Roboten fortsetter på oppdragene.", "Completed Tasks": "Fullførte oppgaver", - "Failed to retrieve missions": "Kunne ikke hente ut oppdrag", "Request error": "Forespørselsfeil", "Failed to delete mission from queue": "Kunne ikke fjerne oppdrag fra køen", "Failed to rerun mission": "Kunne ikke kjøre oppdraget på nytt", @@ -159,7 +153,7 @@ "INFO": "INFO", "Dock": "Ladestasjon", "Dock successful text": "Robotene er i ladestasjonen og vil ikke kjøre oppdrag. For å fortsette oppdrag trykk på knappen 'Slipp robotene ut fra ladestasjon'.", - "Connection Issues": "Tilkoblings\u00adproblemer", + "Connection Issues": "Tilkoblings­problemer", "Pressure": "Trykk", "Add to queue": "Legg til i kø", "Failed to pause mission on {0}": "Kunne ikke pause oppdrag på robot {0}", @@ -198,7 +192,6 @@ "Detection type": "Deteksjonstype", "leak": "Lekkasje", "Inspection report for task": "Inspeksjonsrapport for oppgave", - "Battery is too low to start a mission.": "Batterinivået er for lavt til å starte oppdrag.", "Robot is sent to dock and cannot run missions. Queued missions will run when robot is dismissed from dock.": "Roboten er sendt til ladestasjon og kan ikke kjøre oppdrag. Oppdrag i kø vil kjøres når roboten slippes ut fra ladestasjon.", "Queued Missions": "Oppdragskø", "Waiting for inspection result": "Venter på inspeksjonsresultat", @@ -296,14 +289,6 @@ "Confidence": "Treffsikkerhet", "Value": "Verdi", "Analysis": "Analyse", - "CO2 consentration: {0}%": "CO2 konsentrasjon: {0}%", - "Successful Missions": "Vellykkede oppdrag", - "Partially Successful Missions": "Delvis vellykkede oppdrag", - "Failed Missions": "Mislykkede oppdrag", - "Mission Success Rate": "Suksessrate for oppdrag", - "Successful Tasks": "Vellykkede oppgaver", - "Failed Tasks": "Mislykkede oppgaver", - "Task Success Rate": "Suksessrate for oppgaver", "Loading installation data": "Laster installasjonsdata", "RechargingWithMission": "Lader", "GoingToRechargingWithMission": "Returnerer for å lade", @@ -318,7 +303,6 @@ "Selected inspection": "Utvalgt inspeksjon", "Selected analysis result": "Utvalgt analyseresultat", "Data View for Constant Level Oilers": "Datavisning for Nivåmåling", - "Analysis result not available": "Analyseresultat ikke tilgjengelig", "Go to data overview": "Gå til dataoversikt", "Latest inspection": "Siste inspeksjon", "Analysis result": "Analyseresultat", diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 88f0ebe1c..5b8956159 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -15,7 +15,7 @@ "noEmit": true, "noFallthroughCasesInSwitch": true, "jsx": "react-jsx", - "types": ["vite/client", "vite-plugin-svgr/client"], + "types": ["vite/client"], "baseUrl": "src", "outDir": "out", "sourceMap": true From 371fec2cafb58fd71584835ba20b924bf7e1d8d1 Mon Sep 17 00:00:00 2001 From: Nicholas Dalhaug Date: Fri, 14 Aug 2026 17:26:11 +0200 Subject: [PATCH 4/4] Resolve the identity the backend authenticates as --- .../CustomServiceConfigurations.cs | 58 ++++++++++++++++--- backend/api/Program.cs | 8 ++- 2 files changed, 55 insertions(+), 11 deletions(-) diff --git a/backend/api/Configurations/CustomServiceConfigurations.cs b/backend/api/Configurations/CustomServiceConfigurations.cs index 9695cd2aa..234586474 100644 --- a/backend/api/Configurations/CustomServiceConfigurations.cs +++ b/backend/api/Configurations/CustomServiceConfigurations.cs @@ -229,15 +229,57 @@ TokenCredential runtimeCredential ); } - public static TokenCredential CreateCredential(IConfiguration config) + /// + /// Resolves the identity the backend authenticates as. Used by both the Key Vault and the + /// runtime credential so they cannot disagree. Appsettings wins; AZURE_* is only a fallback. + /// In AKS, AZURE_CLIENT_ID is injected from the azure.workload.identity/client-id + /// annotation in robotics-infrastructure, which must match AzureAd:ClientId. + /// + private static (string? TenantId, string? ClientId) ResolveIdentity(IConfiguration config) { - string? tenantId = config["AzureAd:TenantId"]; - string? clientId = config["AzureAd:ClientId"]; - string? clientSecret = config["AzureAd:ClientSecret"]; + return ( + config["AzureAd:TenantId"] ?? config["AZURE_TENANT_ID"], + config["AzureAd:ClientId"] ?? config["AZURE_CLIENT_ID"] + ); + } + + /// + /// Options for the credential that reads Key Vault. It needs its own credential because + /// Key Vault is read before can run — the client secret + /// that method may use is itself stored there. DefaultAzureCredential keeps local + /// development working via az login, pinned to . + /// + public static DefaultAzureCredentialOptions CreateKeyVaultCredentialOptions( + IConfiguration config + ) + { + var (tenantId, clientId) = ResolveIdentity(config); + + var options = new DefaultAzureCredentialOptions(); + + if (!string.IsNullOrWhiteSpace(tenantId)) + options.TenantId = tenantId; - tenantId ??= config["AZURE_TENANT_ID"]; - clientId ??= config["AZURE_CLIENT_ID"]; - clientSecret ??= config["AZURE_CLIENT_SECRET"]; + if (!string.IsNullOrWhiteSpace(clientId)) + options.WorkloadIdentityClientId = clientId; + + return options; + } + + public static TokenCredential CreateCredential(IConfiguration config) + { + var (tenantId, clientId) = ResolveIdentity(config); + string? clientSecret = config["AzureAd:ClientSecret"] ?? config["AZURE_CLIENT_SECRET"]; + + Console.WriteLine( + $"Backend identity: clientId={clientId} (source: " + + ( + config["AzureAd:ClientId"] is not null + ? "appsettings" + : "AZURE_CLIENT_ID environment variable" + ) + + ")" + ); var workloadOptions = new WorkloadIdentityCredentialOptions(); if (!string.IsNullOrWhiteSpace(clientId)) @@ -247,7 +289,7 @@ public static TokenCredential CreateCredential(IConfiguration config) var workloadIdentity = new WorkloadIdentityCredential(workloadOptions); - bool allowUsingClientSecret = config.GetValue("AllowUsingClientSecret"); + bool allowUsingClientSecret = config.GetValue("AzureAd:AllowUsingClientSecret"); if ( allowUsingClientSecret diff --git a/backend/api/Program.cs b/backend/api/Program.cs index ffdbae631..cf0b2691b 100644 --- a/backend/api/Program.cs +++ b/backend/api/Program.cs @@ -30,14 +30,16 @@ builder.Configuration.AddEnvironmentVariables(); if (builder.Configuration.GetSection("KeyVault").GetValue("UseKeyVault")) { - // The ExcludeSharedTokenCacheCredential option is a recommended workaround by Azure for dockerization - // See https://github.com/Azure/azure-sdk-for-net/issues/17052 + // Key Vault is read before the runtime credential exists, so it uses its own credential + // pinned to the same identity. See CustomServiceConfigurations.ResolveIdentity. string? vaultUri = builder.Configuration.GetSection("KeyVault")["VaultUri"]; if (!string.IsNullOrEmpty(vaultUri)) { builder.Configuration.AddAzureKeyVault( new Uri(vaultUri), - new DefaultAzureCredential(new DefaultAzureCredentialOptions()) + new DefaultAzureCredential( + CustomServiceConfigurations.CreateKeyVaultCredentialOptions(builder.Configuration) + ) ); } else