From 9650d7f232f405b679529ec6fbe986f46a93842e Mon Sep 17 00:00:00 2001 From: Quentin Pavy Date: Wed, 23 Oct 2024 13:22:12 +0200 Subject: [PATCH] feat(ips): init ips react app ref: MANAGER-15657 Signed-off-by: Quentin Pavy --- packages/manager/apps/ips/README.md | 3 + packages/manager/apps/ips/cucumber.js | 20 +++ .../apps/ips/e2e/features/error.feature | 12 ++ .../apps/ips/e2e/features/onboarding.feature | 7 ++ .../ips/e2e/step-definitions/error.step.ts | 53 ++++++++ .../e2e/step-definitions/onboarding.step.ts | 32 +++++ .../manager/apps/ips/e2e/utils/constants.ts | 7 ++ packages/manager/apps/ips/e2e/utils/index.tsx | 2 + .../manager/apps/ips/e2e/utils/network.ts | 28 +++++ packages/manager/apps/ips/index.html | 22 ++++ .../apps/ips/mocks/example/example-data.json | 11 ++ .../manager/apps/ips/mocks/example/example.ts | 30 +++++ packages/manager/apps/ips/mocks/index.ts | 1 + packages/manager/apps/ips/package.json | 59 +++++++++ .../manager/apps/ips/playwright.config.ts | 20 +++ packages/manager/apps/ips/postcss.config.js | 6 + .../dashboard/Messages_fr_FR.json | 7 ++ .../translations/ips/Messages_fr_FR.json | 6 + .../ips/error/Messages_fr_FR.json | 8 ++ .../translations/listing/Messages_fr_FR.json | 4 + .../onboarding/Messages_fr_FR.json | 13 ++ packages/manager/apps/ips/src/App.tsx | 35 ++++++ .../apps/ips/src/assets/error-banner-oops.png | Bin 0 -> 60234 bytes .../src/components/Breadcrumb/Breadcrumb.tsx | 25 ++++ .../apps/ips/src/components/Error/Error.scss | 18 +++ .../apps/ips/src/components/Error/Error.tsx | 52 ++++++++ .../ips/src/components/Loading/Loading.tsx | 12 ++ packages/manager/apps/ips/src/data/api/ips.ts | 64 ++++++++++ .../src/hooks/breadcrumb/useBreadcrumb.tsx | 49 ++++++++ .../ips/src/hooks/guide/useGuideUtils.tsx | 100 +++++++++++++++ packages/manager/apps/ips/src/index.scss | 1 + packages/manager/apps/ips/src/index.tsx | 51 ++++++++ packages/manager/apps/ips/src/ips.config.ts | 8 ++ packages/manager/apps/ips/src/pages/404.tsx | 7 ++ packages/manager/apps/ips/src/pages/index.tsx | 13 ++ .../manager/apps/ips/src/pages/layout.tsx | 31 +++++ .../apps/ips/src/pages/listing/index.tsx | 115 ++++++++++++++++++ .../apps/ips/src/pages/onboarding/index.scss | 10 ++ .../apps/ips/src/pages/onboarding/index.tsx | 66 ++++++++++ .../src/pages/onboarding/onboarding-img.png | Bin 0 -> 8250 bytes .../apps/ips/src/routes/routes.constant.ts | 11 ++ .../manager/apps/ips/src/routes/routes.tsx | 52 ++++++++ .../manager/apps/ips/src/tracking.constant.ts | 20 +++ packages/manager/apps/ips/src/vite-hmr.ts | 5 + packages/manager/apps/ips/tailwind.config.js | 14 +++ packages/manager/apps/ips/tsconfig.json | 27 ++++ packages/manager/apps/ips/vite.config.mjs | 8 ++ 47 files changed, 1145 insertions(+) create mode 100644 packages/manager/apps/ips/README.md create mode 100644 packages/manager/apps/ips/cucumber.js create mode 100644 packages/manager/apps/ips/e2e/features/error.feature create mode 100644 packages/manager/apps/ips/e2e/features/onboarding.feature create mode 100644 packages/manager/apps/ips/e2e/step-definitions/error.step.ts create mode 100644 packages/manager/apps/ips/e2e/step-definitions/onboarding.step.ts create mode 100644 packages/manager/apps/ips/e2e/utils/constants.ts create mode 100644 packages/manager/apps/ips/e2e/utils/index.tsx create mode 100644 packages/manager/apps/ips/e2e/utils/network.ts create mode 100644 packages/manager/apps/ips/index.html create mode 100644 packages/manager/apps/ips/mocks/example/example-data.json create mode 100644 packages/manager/apps/ips/mocks/example/example.ts create mode 100644 packages/manager/apps/ips/mocks/index.ts create mode 100644 packages/manager/apps/ips/package.json create mode 100644 packages/manager/apps/ips/playwright.config.ts create mode 100644 packages/manager/apps/ips/postcss.config.js create mode 100644 packages/manager/apps/ips/public/translations/dashboard/Messages_fr_FR.json create mode 100644 packages/manager/apps/ips/public/translations/ips/Messages_fr_FR.json create mode 100644 packages/manager/apps/ips/public/translations/ips/error/Messages_fr_FR.json create mode 100644 packages/manager/apps/ips/public/translations/listing/Messages_fr_FR.json create mode 100644 packages/manager/apps/ips/public/translations/onboarding/Messages_fr_FR.json create mode 100644 packages/manager/apps/ips/src/App.tsx create mode 100644 packages/manager/apps/ips/src/assets/error-banner-oops.png create mode 100644 packages/manager/apps/ips/src/components/Breadcrumb/Breadcrumb.tsx create mode 100644 packages/manager/apps/ips/src/components/Error/Error.scss create mode 100644 packages/manager/apps/ips/src/components/Error/Error.tsx create mode 100644 packages/manager/apps/ips/src/components/Loading/Loading.tsx create mode 100644 packages/manager/apps/ips/src/data/api/ips.ts create mode 100644 packages/manager/apps/ips/src/hooks/breadcrumb/useBreadcrumb.tsx create mode 100644 packages/manager/apps/ips/src/hooks/guide/useGuideUtils.tsx create mode 100644 packages/manager/apps/ips/src/index.scss create mode 100644 packages/manager/apps/ips/src/index.tsx create mode 100644 packages/manager/apps/ips/src/ips.config.ts create mode 100644 packages/manager/apps/ips/src/pages/404.tsx create mode 100644 packages/manager/apps/ips/src/pages/index.tsx create mode 100644 packages/manager/apps/ips/src/pages/layout.tsx create mode 100644 packages/manager/apps/ips/src/pages/listing/index.tsx create mode 100644 packages/manager/apps/ips/src/pages/onboarding/index.scss create mode 100644 packages/manager/apps/ips/src/pages/onboarding/index.tsx create mode 100644 packages/manager/apps/ips/src/pages/onboarding/onboarding-img.png create mode 100644 packages/manager/apps/ips/src/routes/routes.constant.ts create mode 100644 packages/manager/apps/ips/src/routes/routes.tsx create mode 100644 packages/manager/apps/ips/src/tracking.constant.ts create mode 100644 packages/manager/apps/ips/src/vite-hmr.ts create mode 100644 packages/manager/apps/ips/tailwind.config.js create mode 100644 packages/manager/apps/ips/tsconfig.json create mode 100644 packages/manager/apps/ips/vite.config.mjs diff --git a/packages/manager/apps/ips/README.md b/packages/manager/apps/ips/README.md new file mode 100644 index 000000000000..616579cd8100 --- /dev/null +++ b/packages/manager/apps/ips/README.md @@ -0,0 +1,3 @@ +# @ovh-ux/manager-ips-app + +> ips manager app diff --git a/packages/manager/apps/ips/cucumber.js b/packages/manager/apps/ips/cucumber.js new file mode 100644 index 000000000000..8e6abbfbca8f --- /dev/null +++ b/packages/manager/apps/ips/cucumber.js @@ -0,0 +1,20 @@ +const isCI = process.env.CI; + +module.exports = { + default: { + paths: ['e2e/features/**/*.feature'], + require: [ + '../../../../playwright-helpers/bdd-setup.ts', + 'e2e/**/*.step.ts', + ], + requireModule: ['ts-node/register'], + format: [ + 'summary', + isCI ? 'progress' : 'progress-bar', + !isCI && ['html', 'e2e/reports/cucumber-results-report.html'], + !isCI && ['usage-json', 'e2e/reports/cucumber-usage-report.json'], + ].filter(Boolean), + formatOptions: { snippetInterface: 'async-await' }, + retry: 1, + }, +}; diff --git a/packages/manager/apps/ips/e2e/features/error.feature b/packages/manager/apps/ips/e2e/features/error.feature new file mode 100644 index 000000000000..e20e56f5f592 --- /dev/null +++ b/packages/manager/apps/ips/e2e/features/error.feature @@ -0,0 +1,12 @@ +Feature: Error + + Scenario Outline: Display an error if request fails + Given The service to fetch the data is + When User navigates to Home page + Then User "" the list of data + Then User sees error + + Examples: + | apiOk | sees | anyError | + | OK | sees | no | + | KO | doesn't see | an | diff --git a/packages/manager/apps/ips/e2e/features/onboarding.feature b/packages/manager/apps/ips/e2e/features/onboarding.feature new file mode 100644 index 000000000000..6f9786c696be --- /dev/null +++ b/packages/manager/apps/ips/e2e/features/onboarding.feature @@ -0,0 +1,7 @@ +Feature: Onboarding page + + Scenario: User wants to find informations related to ips + Given User has 0 elements in the Listing page + When User navigates to Listing page + Then User gets redirected to Onboarding page + Then User sees 3 guides diff --git a/packages/manager/apps/ips/e2e/step-definitions/error.step.ts b/packages/manager/apps/ips/e2e/step-definitions/error.step.ts new file mode 100644 index 000000000000..fcbf5d4b453a --- /dev/null +++ b/packages/manager/apps/ips/e2e/step-definitions/error.step.ts @@ -0,0 +1,53 @@ +import { Given, When, Then } from '@cucumber/cucumber'; +import { expect } from '@playwright/test'; +import { ICustomWorld } from '../../../../../../playwright-helpers'; +import { ConfigParams, getUrl, setupNetwork } from '../utils'; +import { title } from '../../public/translations/listing/Messages_fr_FR.json'; +import { + manager_error_page_title, + manager_error_page_action_home_label, + manager_error_page_action_reload_label, +} from '../../public/translations/ips/error/Messages_fr_FR.json'; + +Given('The service to fetch the data is {word}', function( + this: ICustomWorld, + apiState: 'OK' | 'KO', +) { + this.handlersConfig.isKo = apiState === 'KO'; +}); + +When('User navigates to Home page', async function( + this: ICustomWorld, +) { + await setupNetwork(this); + await this.page.goto(this.testContext.initialUrl || getUrl('root'), { + waitUntil: 'load', + }); +}); + +Then('User {string} the list of data', async function( + this: ICustomWorld, + see: 'sees' | "doesn't see", +) { + if (see === 'sees') { + const titleElement = await this.page.getByText(title); + await expect(titleElement).toBeVisible(); + } +}); + +Then('User sees {word} error', async function( + this: ICustomWorld, + anyError: 'an' | 'no', +) { + if (anyError === 'an') { + await expect(this.page.getByText(manager_error_page_title)).toBeVisible(); + + await expect( + this.page.getByText(manager_error_page_action_home_label), + ).toBeVisible(); + + await expect( + this.page.getByText(manager_error_page_action_reload_label), + ).toBeVisible(); + } +}); diff --git a/packages/manager/apps/ips/e2e/step-definitions/onboarding.step.ts b/packages/manager/apps/ips/e2e/step-definitions/onboarding.step.ts new file mode 100644 index 000000000000..67b20b6e52c8 --- /dev/null +++ b/packages/manager/apps/ips/e2e/step-definitions/onboarding.step.ts @@ -0,0 +1,32 @@ +import { Given, When, Then } from '@cucumber/cucumber'; +import { expect } from '@playwright/test'; +import { ICustomWorld } from '../../../../../../playwright-helpers'; +import { ConfigParams, getUrl, setupNetwork } from '../utils'; + +Given('User has {int} elements in the Listing page', function( + this: ICustomWorld, + nb: number, +) { + this.handlersConfig.nb = nb; +}); + +When('User navigates to Listing page', async function( + this: ICustomWorld, +) { + await setupNetwork(this); + await this.page.goto(getUrl('listing'), { waitUntil: 'load' }); +}); + +Then('User gets redirected to Onboarding page', async function( + this: ICustomWorld, +) { + await expect(this.page).toHaveURL(getUrl('onboarding')); +}); + +Then('User sees {int} guides', async function( + this: ICustomWorld, + nbGuides: number, +) { + const guides = await this.page.locator('osds-tile'); + await expect(guides).toHaveCount(nbGuides); +}); diff --git a/packages/manager/apps/ips/e2e/utils/constants.ts b/packages/manager/apps/ips/e2e/utils/constants.ts new file mode 100644 index 000000000000..866e6dadabe9 --- /dev/null +++ b/packages/manager/apps/ips/e2e/utils/constants.ts @@ -0,0 +1,7 @@ +import { urls } from '../../src/routes/routes.constant'; + +export const appUrl = 'http://localhost:9001/app'; + +export type AppRoute = keyof typeof urls; + +export const getUrl = (route: AppRoute) => `${appUrl}/#${urls[route]}`; diff --git a/packages/manager/apps/ips/e2e/utils/index.tsx b/packages/manager/apps/ips/e2e/utils/index.tsx new file mode 100644 index 000000000000..24a453c58aa9 --- /dev/null +++ b/packages/manager/apps/ips/e2e/utils/index.tsx @@ -0,0 +1,2 @@ +export * from './network'; +export * from './constants'; diff --git a/packages/manager/apps/ips/e2e/utils/network.ts b/packages/manager/apps/ips/e2e/utils/network.ts new file mode 100644 index 000000000000..46d894b9a08e --- /dev/null +++ b/packages/manager/apps/ips/e2e/utils/network.ts @@ -0,0 +1,28 @@ +import { BrowserContext } from '@playwright/test'; +import { + ICustomWorld, + toPlaywrightMockHandler, + Handler, +} from '../../../../../../playwright-helpers'; +import { + GetAuthenticationMocks, + getAuthenticationMocks, +} from '../../../../../../playwright-helpers/mocks/auth'; +import { getExampleMocks, GetExampleMocksParams } from '../../mocks'; + +export type ConfigParams = GetAuthenticationMocks & GetExampleMocksParams; + +export const getConfig = (params: ConfigParams): Handler[] => + [getAuthenticationMocks, getExampleMocks].flatMap((getMocks) => + getMocks(params), + ); + +export const setupNetwork = async (world: ICustomWorld) => + Promise.all( + getConfig({ + ...((world?.handlersConfig as ConfigParams) || ({} as ConfigParams)), + isAuthMocked: true, + }) + .reverse() + .map(toPlaywrightMockHandler(world.context as BrowserContext)), + ); diff --git a/packages/manager/apps/ips/index.html b/packages/manager/apps/ips/index.html new file mode 100644 index 000000000000..ec4e92d05007 --- /dev/null +++ b/packages/manager/apps/ips/index.html @@ -0,0 +1,22 @@ + + + + + + + + OVHcloud + + + + + +
+ + + diff --git a/packages/manager/apps/ips/mocks/example/example-data.json b/packages/manager/apps/ips/mocks/example/example-data.json new file mode 100644 index 000000000000..16b09c4e47fd --- /dev/null +++ b/packages/manager/apps/ips/mocks/example/example-data.json @@ -0,0 +1,11 @@ +[ + { + "id": 20374 + }, + { + "id": 20375 + }, + { + "id": 20379 + } +] diff --git a/packages/manager/apps/ips/mocks/example/example.ts b/packages/manager/apps/ips/mocks/example/example.ts new file mode 100644 index 000000000000..77c033e7b392 --- /dev/null +++ b/packages/manager/apps/ips/mocks/example/example.ts @@ -0,0 +1,30 @@ +import { Handler } from '../../../../../../playwright-helpers'; +import exampleList from './example-data.json'; + +export type GetExampleMocksParams = { isKo?: boolean; nb?: number }; + +export const getExampleMocks = ({ + isKo, + nb = Number.POSITIVE_INFINITY, +}: GetExampleMocksParams): Handler[] => [ + { + url: '*', + response: isKo + ? { + message: 'Example error', + } + : exampleList.slice(0, nb), + status: isKo ? 500 : 200, + api: 'v6', + }, + { + url: '*', + response: isKo + ? { + message: 'Example error', + } + : exampleList.slice(0, nb), + status: isKo ? 500 : 200, + api: 'v2', + }, +]; diff --git a/packages/manager/apps/ips/mocks/index.ts b/packages/manager/apps/ips/mocks/index.ts new file mode 100644 index 000000000000..4356d0ac05ac --- /dev/null +++ b/packages/manager/apps/ips/mocks/index.ts @@ -0,0 +1 @@ +export * from './example/example'; diff --git a/packages/manager/apps/ips/package.json b/packages/manager/apps/ips/package.json new file mode 100644 index 000000000000..4dcb9a8e00df --- /dev/null +++ b/packages/manager/apps/ips/package.json @@ -0,0 +1,59 @@ +{ + "name": "@ovh-ux/manager-ips-app", + "version": "0.0.0", + "private": true, + "description": "ips manager app", + "repository": { + "type": "git", + "url": "git+https://github.com/ovh/manager.git", + "directory": "packages/manager/apps/ips" + }, + "license": "BSD-3-Clause", + "author": "OVH SAS", + "scripts": { + "build": "tsc && vite build", + "dev": "tsc && vite", + "start": "lerna exec --stream --scope='@ovh-ux/manager-ips-app' --include-dependencies -- npm run build --if-present", + "start:dev": "lerna exec --stream --scope='@ovh-ux/manager-ips-app' --include-dependencies -- npm run dev --if-present", + "start:watch": "lerna exec --stream --parallel --scope='@ovh-ux/manager-ips-app' --include-dependencies -- npm run dev:watch --if-present", + "test:e2e": "tsc && node ../../../../scripts/run-playwright-bdd.js", + "test:e2e:ci": "tsc && node ../../../../scripts/run-playwright-bdd.js --ci" + }, + "dependencies": { + "@ovh-ux/manager-config": "*", + "@ovh-ux/manager-core-api": "*", + "@ovh-ux/manager-core-utils": "*", + "@ovh-ux/manager-react-components": "*", + "@ovh-ux/manager-react-core-application": "*", + "@ovh-ux/manager-react-shell-client": "*", + "@ovh-ux/manager-tailwind-config": "*", + "@ovh-ux/request-tagger": "*", + "@ovhcloud/ods-common-core": "17.2.1", + "@ovhcloud/ods-common-theming": "17.2.1", + "@ovhcloud/ods-components": "17.2.1", + "@ovhcloud/ods-theme-blue-jeans": "17.2.1", + "@tanstack/react-query": "^5.51.21", + "@tanstack/react-query-devtools": "^5.51.21", + "axios": "^1.1.2", + "clsx": "^1.2.1", + "i18next": "^23.8.2", + "i18next-http-backend": "^2.4.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-i18next": "^14.0.5", + "react-router-dom": "^6.3.0", + "tailwindcss": "^3.4.4" + }, + "devDependencies": { + "@cucumber/cucumber": "^10.3.1", + "@ovh-ux/manager-vite-config": "*", + "@playwright/test": "^1.41.2", + "typescript": "^5.1.6", + "vite": "^5.2.13" + }, + "regions": [ + "CA", + "EU", + "US" + ] +} diff --git a/packages/manager/apps/ips/playwright.config.ts b/packages/manager/apps/ips/playwright.config.ts new file mode 100644 index 000000000000..feb249bcbe3f --- /dev/null +++ b/packages/manager/apps/ips/playwright.config.ts @@ -0,0 +1,20 @@ +import { defineConfig } from '@playwright/test'; + +export default defineConfig({ + workers: 3, + fullyParallel: false, + timeout: 30 * 1000, + reporter: [['html', { open: 'on-failure' }]], + expect: { + timeout: 20000, + }, + use: { + // Collect trace when retrying the failed test. + trace: 'retain-on-failure', + }, + testMatch: '**/*.e2e.ts', + webServer: { + command: 'yarn run dev', + url: 'http://localhost:9000/', + }, +}); diff --git a/packages/manager/apps/ips/postcss.config.js b/packages/manager/apps/ips/postcss.config.js new file mode 100644 index 000000000000..12a703d900da --- /dev/null +++ b/packages/manager/apps/ips/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/packages/manager/apps/ips/public/translations/dashboard/Messages_fr_FR.json b/packages/manager/apps/ips/public/translations/dashboard/Messages_fr_FR.json new file mode 100644 index 000000000000..f42a27b3366a --- /dev/null +++ b/packages/manager/apps/ips/public/translations/dashboard/Messages_fr_FR.json @@ -0,0 +1,7 @@ +{ + "title": "Dashboard page", + "error_service": "No services info", + "general_informations": "Informations générales", + "tab2": "Tab 2", + "back_link": "Retour à la liste" +} diff --git a/packages/manager/apps/ips/public/translations/ips/Messages_fr_FR.json b/packages/manager/apps/ips/public/translations/ips/Messages_fr_FR.json new file mode 100644 index 000000000000..c5bf7278da50 --- /dev/null +++ b/packages/manager/apps/ips/public/translations/ips/Messages_fr_FR.json @@ -0,0 +1,6 @@ +{ + "title": "Bienvenue uapp", + "crumb": "ips", + "tabs_2": "Tabs 2", + "onboarding": "Onboarding" +} diff --git a/packages/manager/apps/ips/public/translations/ips/error/Messages_fr_FR.json b/packages/manager/apps/ips/public/translations/ips/error/Messages_fr_FR.json new file mode 100644 index 000000000000..2c575c63588e --- /dev/null +++ b/packages/manager/apps/ips/public/translations/ips/error/Messages_fr_FR.json @@ -0,0 +1,8 @@ +{ + "manager_error_page_title": "Oops …!", + "manager_error_page_button_cancel": "Annuler", + "manager_error_page_detail_code": "Code d'erreur : ", + "manager_error_page_action_reload_label": "Réessayer", + "manager_error_page_action_home_label": "Retour à la page d'accueil", + "manager_error_page_default": "Une erreur est survenue lors du chargement de la page." +} diff --git a/packages/manager/apps/ips/public/translations/listing/Messages_fr_FR.json b/packages/manager/apps/ips/public/translations/listing/Messages_fr_FR.json new file mode 100644 index 000000000000..c882bc92cfec --- /dev/null +++ b/packages/manager/apps/ips/public/translations/listing/Messages_fr_FR.json @@ -0,0 +1,4 @@ +{ + "title": "Listing page", + "listing_resultats": "résultats" +} diff --git a/packages/manager/apps/ips/public/translations/onboarding/Messages_fr_FR.json b/packages/manager/apps/ips/public/translations/onboarding/Messages_fr_FR.json new file mode 100644 index 000000000000..2866b83cbef9 --- /dev/null +++ b/packages/manager/apps/ips/public/translations/onboarding/Messages_fr_FR.json @@ -0,0 +1,13 @@ +{ + "title": "ips", + "description": "Découvrez des services de stockage managés qui s’appuient sur le système de fichiers OpenZFS. Bénéficiez en quelques clics d’espaces de stockage centralisés pour entreposer ou sauvegarder vos données et fichiers.", + "orderButtonLabel": "Commander un ips", + "moreInfoButtonLabel": "En savoir plus sur ips", + "guideCategory": "Tutoriel", + "guide1Title": "Premiers pas avec un ips", + "guide1Description": "Découvrez comment gérer un NAS-HA depuis l'espace-client OVHcloud", + "guide2Title": "Monter votre NAS via un partage NFS", + "guide2Description": "Découvrez comment monter un NAS via un partage NFS", + "guide3Title": "Monter votre NAS sur Windows Server via CIFS", + "guide3Description": "Découvrez comment monter un NAS sur Windows Server via le protocole CIFS" +} diff --git a/packages/manager/apps/ips/src/App.tsx b/packages/manager/apps/ips/src/App.tsx new file mode 100644 index 000000000000..9bedb39c2cae --- /dev/null +++ b/packages/manager/apps/ips/src/App.tsx @@ -0,0 +1,35 @@ +import React, { useEffect, useContext } from 'react'; +import { QueryClientProvider, QueryClient } from '@tanstack/react-query'; +import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; +import { odsSetup } from '@ovhcloud/ods-common-core'; +import { ShellContext } from '@ovh-ux/manager-react-shell-client'; +import { RouterProvider, createHashRouter } from 'react-router-dom'; +import { Routes } from './routes/routes'; + +odsSetup(); + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 300_000, + }, + }, +}); + +function App() { + const { shell } = useContext(ShellContext); + const router = createHashRouter(Routes); + + useEffect(() => { + shell.ux.hidePreloader(); + }, []); + + return ( + + + + + ); +} + +export default App; diff --git a/packages/manager/apps/ips/src/assets/error-banner-oops.png b/packages/manager/apps/ips/src/assets/error-banner-oops.png new file mode 100644 index 0000000000000000000000000000000000000000..413028afad19a473424e11656d3c6b0400a79fdf GIT binary patch literal 60234 zcmeFZXE@vM`!`NRjnoP?Ba~RBX6-#=6m3;$?NYS1D6xs6R#eg&wP|TfZEBNhgVqW) zYeXrrDhO4pf1mI7^ZOmgeH{OX_rv>1t~|KjSI+l!Ugvnd&htvr4GTCEBQGNr6%~_- zvB51WDuCws_Zj*N=bz@5d*Jhd8gUD*M^!ZhS)-!Tp)xVhy^W;aoMWi*u#OZXe+Q*( zz7aHytB!A0?5pxl`{8MRJyrDQvm`miiQBKik}qqo8DFu=xqb_4tC)6{6H`>FuRlD}=I#*{G zwts8?KKt9@_NI=+)*SR`eJYJlsr|teV)%D{z{dNKA$Rg;I5c#m#&=HDOv&eHZ~VhT z)S!36xD7oAAYSM44SFdUFjE@#z-&@1G3JTKSed!U2e%be!MA~Kce9TO>KSvh%=QNt z>K;i08DsT|SjB;yFu)6nRNupJl<5cl0ui%J`vpvnwStIN=@XAjGfk3|GcobkeW3qN zW{eF@>&Aq>caTu_pudj&mX`x}sNWyV0epUQzSMtKL?*Mnd9^B05aS>b)w*2v)i1qc z6W}wwIQ{bCmg$`7opTxgBWg|k`8*E~VU6KKZRwqdN7f$|hDG0gllbo{Qxf#IN9(Pe z^B1o7Cb5`%C3-{++dTaDEoGJIzoiTy;#>p%o&C>GhoW^vH1{Gq3s`1hHb8n))B@pqBZxi7Ahj)9$G_REJhx- zJ}K|4>tv6S&is!aG?-XP48vvStBqND!|N+edk54<CE5Pjqj^pf8W2`)C;YK zYObq!NE>ja|3`_8v7eK=%MMb+K!bIrVVpCmnhcRySDntduI8vt9;=7GQsmDa^KIKg zU)?^;+t7yOqswOg)jwlwug3iD(+bUb-mWDp`b+;kM*me2k&S%yeF}>v1s#A{x|!9q z);ZotWRj-&-$(U`Yf0UDXJ3xS5cn_a|BCYehW{~^|Jyr(OlGy+<>gsMc?rM?l79GmCds*d1l$W%63CnE&tl={eJ}zR54(r>agE(ggVABKsd?IW!#=Cql!Pw2n6s zctpWop8oF{uj)8u-`QqwwTEd@wyi;HH7-2~i@(~op`E8&ek|!4yM6u(k-wR$qfa*y z@m_~VlU-U3I~|e3n!h2;D#VyYo?#@O+#_&SlOAx2$!d4A?TiRrHDvDm(!-*y5wXO# zKLmksZM16_nm9NnLw&|io`bz;|d-?qp1T8+8{PKb1NwV!D^*WlH^ z&O=DPwkER%xqDt95*k6q6%Rhnic@V_IGXs-nt#}R_ERQ0^h4V9_9FS)jXaq5HzBj)v)CLvruZOe64v$+OmfnCVS0$ zBeHNe<@hJ107_>Q`7|iZN}y&~SVmBbPl(F;kJ5(%Tl$KK2i2Sr}K9s<#i$z`CUzDtibA=XLerDPwaBen< z>f(C~VDcndPqo_biaZ#p_QujQM}`Artz8#TB4}C7CRJaIEb#c-&C}V#?|xm8tcNZ- z=+a=MDKzyaTKnvzEqCZCGayGpbh7~vYE4@3#qQ-=5pgyDzBEhb@)x<%9jhUw;ZA}wM z3ew2Y!J#yMan~(k(P#a@rwN_4N!KZEb+KDDA9I;-V=7H=f->53qmv zhf>eTODwB=D099PUSCG>aLj{yz$aId9BL9= zH)90y_z5^8)e~~h@xAy$Ib4uNR?yR^$Pwm~hY@J6{rW8}#DaNSN-NDK*UynPFx2Vp zwv(M-zh&<6-RNGAo2WXfw0EyOs4Zsh-^%IfAb$jDPiPr-jSW2|UhRGh==S+AH-TlL zHhM@o6>p|L@T8akf#QX!mvH^QiNOsFBvh(Sjt;EGw1x~O(+bH0`Il}dVW|=dfw3Am z-ux>8-IiTCm4lB_FY_<@Fh;5N_BQ^o7kZ|2Z3r=!G$XKu;Y-hfOk?JtM*?A_VmoX$ zd3@-C>z3MxrU}DI!(zrVye?}QF7%O;Ho?>7XEv~}MXriA+yu~JHDzdXvIdfg-$L@h zm4nbT{idT29p5uWGKL{CH|AA(qvd7XM)EcNYrebsxS6w7u*@}&@&r%VtUR4F;a<$; zRVTaG%OQDL>{_3=qX!#|7N)^7>56AB4hI0gt}|FB-2TB1Tlu?lbNJfd$5)xSE(0bV z!?)<}>4cvqUy=+P!HSCm2H4;bLI`d9hs6#E&Z3)*zVmm-_CE3ITaz1{1GE-C7?x|O zROgB-GmQ)|RI=sx+nF8Fo={Y+vq&~5ijhAJF4m1Lr@s@_oeo9i%LR)_x063Y#T!AM zhZRG;x>qB{3Q7;218sC~1&Vi%QYQolmK4g#Ht*p1K74V(Mrn{rOj*~^P?q+Q2XH<0 z%D!D+%qNU@9V`qy4pS$Qx!TVM6Z-yQzfE0*iLA6ESWdFmF6=?FmXZh$DNTKcC2ycU zv?$K&$POWkxpvP=;%Lp&ck!d6PqN(fXM%hC{zuMLw0EPcdq|r7fh4l*Afe@S@902$ ziw5yEFeAY0Z`PgM`nwC?GY+M`yQbTG2>pT{R!zAWZ|E{K+kW~%yhvO)SP`(rzT)+A zAcT<$1uXw%9}{pJ=#0aQ9q0OWw$R22%?v@&^WISsg$ zb6D-Y%_?$8bgg_s#RQ!pc~-jVP+8H6oT32{s7}E$-qJyJsMGPxCIvePADymxFb?FN+#L}s0 zq{?x!AUD{XFV*dgGP7Swz=*eI(CA%zBWX2tLghM&o(^|>FHeow8ilIdxfUJD{G-w2+_P>ebM12 zW^VJ!Yu&xNhE7Mc9ELhCs2KFXLb}3+&)A$Brl;?Agzb~pETdbuFB($;*>q`Tf&idU z60Hr+mUy$zyoj(p)~<8CD&*FP86z2|f9time7vKg)(LHRy64$Xr?Ur#>o#38SON9B zZj8%#qx327_TWjBfFr;o*((ySTJpm9=VxbRjiMTlnE29z#E%)9SPQj>8y|{*_D#HW zGU{q%cd>hQcoDS>$yx%7K0@Ed2U!>Yp4`*hayF&^`?GOa6_MSoMgxIHcE!lof|4x{#-cRT8R4?bF7aZb-=VqkWl;=D%d zzc9Nqlq|SdF@Q>Ph}mo|`kj^qbPfV@P}>QzJ zeYIrO`bxJ*uNreTrc_Ly$UfU3b{9!Var_oKW8Ly2Su$)Fv02-D&}$T=1eK|yRfaEF ztrck}p*8|)?F3s&X^#7P^-~*4Jt&}zP6`#y+|nNNN%|?nBvA7jB8*YSS&2t4*2yIzxzYg&93qsFvkA_ z7$2z>XnnaxcZ{vQqjMXW1Z)7RM54^0eDoJkX|MD?3VCj1U6eSYW$3kxT(^p}M~0oi z{Ecm4I)H`{@exyQirAYGS^)~c1}wRaC6#7%TUzQ~7kVxz)2y2HoB>1OSl0gCGW_^B zm7Qbj+QZzJFL`0w(*$E@H^;y^7tK!A zgddt~x8K)pb+}2=g{voe*Fr0mSKsxPPwm z?+F11Vyvh}pQTe!z^MVK(|m&Od$Va+fjH8z%bE2YrQ@!T|G3pa`)wRm^Klw)f;d1W zl?q_}V&Q36Sy3230Y;z<-XekKt^KMQCjN==W*^om@%QqE_0JGjQOO_+aY=fz_sh7~~g%2zpDR^(8> zzHsd;5U;k8v34+V&@st5xkWyqfO4bnR$MLca-f2*hd{q8&1qh^Di4$#U##?E-V~!{ zf(ico(nYe3So-lo;Yf}bWujo;bFSM_y&&1^8o9`-=1KV;(j1*&so=YxonZ#(BhiB+%y~e`SNaX z;0Qg*q*!Zz*?E=L5ta++U{zpv9qoNuW^c5V8p)OKVmlW5i8lJM`fh#M#v{kFnNrR8 z_tTLjELz@zS#ve6y;V{{&NKtJg9#EFDUO?yzEL6%{L3mfU@;V>ZE(QnBh*{Hgs5@V z0`$?Mv;7JyQ{?T_70cKTQ!eCNPb>WD5r@x1F`2Ec6JlzG>*X5me@HL6p>;sbTh+;x zQsuu3`gv_n;)PdZE-|aFVplx5AS#avF|_O&u*)+*Du&pY#m{!S_sExm2=qdzKtN*A z`vsK<{+(ihyBOLdB9A-hH{7K4qIain>(~0yd(U-CIFRux* zv_THL8I=xA9+7DHG&CmUMVsBlfbTm8xbk0UMCn)83?onU$JB#yg~TCdTRd|s!B9KA zQqE^-Uoxoy4@<5YO`mTGMGPL0lT=;#2+tV8mq^cA;%c=vbhYP?QUmgpww5qny?tK- zKyy`drhJk%tMT2^I_La=165!WQUd@$8GIJ=(9L3B>7(_+>E~B{(p6`aB8eyjGm!Tf za`z?=W+pqEx&|t*%3Lv1fvKS0TCRYZaWGw-8rD<}_|_rg86bHF3@ZH9Kk0EXlyMrl z^sv4ARa)oSf@pd*Om99~&PeAomY>1xkRh<a5e z_NP3Kx|aO6+WW!r7Hk7p?L4I(LJ&|BX;Gx36sg-e{|I;$>Rj3hbFXnP)Jnhno=atg zAaw6!L}I0XnpztYxVy-;*?IDndA(ra0 z>|3{Rj?1h@kdj`#+2Rx@)Xx@oAlSLpvG>3DGS(DAKC)jvIzE$iBMrXzi`vfN4nI;6$@ zR=E-KGvtSW8dfaFJsmNX?etezGxCy>SW;|yBU|3|!_;63@(ozR$L&=Oi%+r^f|zY^ zvrx6s!UZl9F`(-&f=!kCJD@vZ<@PH7;wqz-^*Jq?+Fi=_K+f)N)1W#K=oOE!Sd38h z(Y?WX$yp^d_S;R?8vPPuu*zSR5VRvJD;0TU@@(=TN;=zr9dMO5f|iGx-%r;Jfaz9a zz7SNWV9-g#5}#9v7#HENLvBPfW=)dt8ex?U?3lcRWPFCwoZsYh!+O@*)Fq^&xWZz` zZE;q`>SxKp*&`rPN(m?wBkZ%;L+3XxjotAqu>ng&;*z6qD(BH){;=*v!Sv@6P!5>O z)qaUdq4ndJUtFqH?^CimHXqEnHz3zRg}%!cC;Bq-YmYu8Im}GCg;=lu>J_FCmW_5#AxJJ zH^@zKYq_(dQ~t=~1MIrMCOJ0ozVc@@?LGOm*b6MHp8fvcn5~+OZS9vToQm1+24kHi z0}$7>y{GPLINTzFqJz7cPUU(bW8X>X!!Hj;>m9(S%iQ(#j8X#(cAI@U3TkGinq{VX zI_a<%Gb8mjuf_OxZU+A1%|zG8YV`~7Hm!2c-WR(U@nXPFR+($YnWcB2RBmX$*qU4@ zHAB`BI2L`l-pXow{XDx?cxlcsoUp%f?ms^#*ALmY9SAx6QzLjcaiIK9m4Ih!SFV4= z(=%U77wFFDoOcagsmAq0^9xu?(-CajQ2u0Iu{WO!Lu=N?^iHUm@emN(EnGvd_vbk} zVK3_$J;~D^6b7jIq$5=NtW?wF`2moxpIQ?O8TsAz3M@@5)r!FOJrQ%5$}U7g}=bMa751g-U&=up! zT`}`M4R!^q^dm*>AD`-S~z50RjJ9-MFe<|9lry6MnjLQ*d7!bAzggH@k zDt<7jx95+A*pGCp(r&@bkJfGJr8v*=9M9k@ekgxS3Rs^7~=HLt+m9@i=@ z3V{HC4p?Ay>Bl`CGD9Kz2T9x?kk-sTh$Am3Sr8X{9uqu)Y^z{_?N`R)4H+qgI*a1A z$8Lp!Pv~0Q-k?lSu{w*`)+lO6?I>_OuonnTiqmQr;S0V9u=na@cictl^lW9uDpP^k`*ma-K|JU7-AWaog~abvFbXH|_Cm68Z}v|$%w z{Eyo>8mhI)C=J~+!5f#H_%4(rzK% zogee?^e)R`4p&59N>q?y;tWCjXsztM>E)ViHsrNOQtpz4F+Rb_nO{Se${zlX0u$hZ z%_9wJCBs%GAkaECU)X|;L&ddC2vuy^h*E$V3crP%mt`_BM$YN-Lv_nRlyCivr6{ zq9mb*Kl-cA$?kSK%Z5V<06T93l3=b49qRWG4%6s=@kV@zfDXCk{4>`MlQtZ&qM%6+ z>h=WQoa&KiKM$PzdF9#4^47=^pGS|b%xUK63|DbX5x}qd=`aM6l42g$H`I}j@e@W?Qo|aIThmM0Z z$8#W0nES*#e(2I#*xchuCd}m{aU|BKDmA{m#WGxMX1A9+=$%|pJx$UcDMC0Z~V5<-Y}g_MVLV zaWgL{GE7)DyR_B7<~bKU0p>fG3{EKwYt(n~{jhrw z1a=k)2nLgMFF1f>h$UBW)P+FvL!8b>k(;p@270+}Vhk*Q$d8%0CObb4ORLk?B!GCw z(M&BAcIW*1OMZbRR+N%lttE1X)QI6{mOC~6X_-vHnWSPSOA}{Qot9Eb${ha_)kF4t zav5KHK9+@Vl~IiW{Rd7X$w2=dmUlzkC}@kcA`CZ&{c=V_ERGM8Ef|P zF$HO7eph8aV;1i@f3zWHQJF}Si=wsS;R$ln<~vB51XN9TJWE?Yy8+g%NDQvdQ){Wd z*?0U&OpCDAf0><`@`B^X!@T?pyNx2YKi-ap1Jg7J}<*|E6?@@hyoc7%s zd9h8g$rJ3)4xv-9^j^t5o6e4>+a_Z9-&Q#^?I?DM$RdSwIGAV`968i8r9G-N z)$V8ezQg)z5q3k;?MiK&1_fz~x9_nEfVNcS({1#*f=+j^ZnX-*+zK`#&7XXm>dhZW z+H(ZqX3&D)({2*h$}7MZN4IA}kMg_}@L}f|A<^yoR=v{T2iKYp(A(+m@A+x9kSg~i zgF`kUSArUUz1O@3hBVpguNcOvs#C`LZwB0Uwe+jhj_(%wC;cTX|LXcZ@AX}|8H$~) zbZk4&V@R*AOgG*LqGR*yGL3LT%pcQJO|!8gY5n_kz$q%LCIMLVJz4NIGG?Ti(-z1S zn`^~I1)=5E)or5^;(k`HO&tPh9vLy0Ew28dM%~ls{A#!vD7bY_>3hOxgXKy<{MNf` z7zd6dOA6Mx$R>_9g2SXhNeT3b@+lp%N@l&$C{kQnxPJQVAMRF+#}ft(is1^g*?hi<8Ld+Uyh**IkK~7<5e|2rkX*1w>D$ zDO{M4m0gfECU8X%nwcZf57Q$iuT0A-6;7bwdBI`3;Z{6iYYHJz`MehX7O`(1NmsY; zemOJ#b*UO@xXRBPXng|np5QxCfKw9V&q&6j4W0KINE!Vn}vtD&-5Bj!62zjJK9Cte(JUF!QQ3HmQb;B(XDaV#N1eh z5u4i~ptDpF<_meCy3OAe1EllZnwyYQMOrKAe!+hts+m;;mUSe^+ zV3z)8igYZkqq?6xcAr)UENQJSZIF%l+WW?T(J^=ESV(Vf9UdNJ?iaoMtGJafF!k*H zNS5Q>@Rjj&zoQwo6Fl-~p4O(2{(W!9!RmwOc^=o*qtqiGO945B|7n-O-*uo;Y7~q> zAb!X|D!$Z8LKIg0$^~%_g6Xd4(v`!#hg00ThL?aq)qZx~NK_|LxJGrom;HT~ZREO< z%o}>ozs5HIi+VdgKLD-9ZBvQ5ozT#QX@+bqd4TP zU8fVvL#-N*mAJ2$R>)QDlnSS^PwjjP*O(*~UN5FoMg9?m*pnwL3Q94;Y9>!x>Wl{J z^(-l>2l#_TpX5?4>)3j5DrB%_pITt=SBG?y8RPUtE0xt*nUEg8c22_5>p_B|<`#2U zziM#MqTQKisZ-GF(k7Bx*xm$|mkx_`6>cXIjfC1Dq2R7U{i+Hct6dJBq zA|Sk}AcX;N1Y}+f7TJ`K*hz8P#2K|2;G|QFWT+Oe3Nk#J9;b3Z8Y-Wnm^6X<)R1T# zoq!J#&TQJ8IO_ z#BpHe2D}rv*-c;R^yiCB7+FLi>Eh|Wp-fXZgP#WZHJ>JUdFPuY*HS}USviqkV@;{) zvqp=1xmN4nPWBEPP?qPDAO12#6~7ymR9B*?E~*vvVwBCTLxhb#3X5sxP&CtRV*1 zQeL!p=<^$N8&DM@43u(JiM)nkJWaNo!H6w^8~XgOvhsL&7@>r3f?uU(dDCCR0C9$c zRS5x%r)hGTP|Sr0T6HJ8A=^_5dnHg-?Z*}`lh$FnToD!)_lu{;v3=yDA(fj#+mu<8 z0}C#X38Slq{dp=zEtiy9%NTWqA7i`t*{GMI{!-xJCD$QISvm}BC=+zlC|RXha9O=Q zEnFymFvm6h&266?Go^JKJcF+PYqKl7tK_1@P-m+diWm=$y~;$C=rh#kobIL37t7YR z=ote4D7lEsIwk9tn@%;DU{Zx?EY{!~+7I^i6DU2xQ%1^@$yypCIUo861!sgme?1Yl zCYJEBGpRd3=-i}zC1<9?9%pPOJ!A?nsyLVQ4R=w%Fffiz6R2Su2K#XTb29Y0h}tHOPkTzW zt`L35Q;}w`v58(w6jNPZN}1s9f8NbI1ADruaA!2WT)^O(QE@5NubVFouA_|c2OJs$ zuS?x97u6(iI8`1JWUWFC&KIU}dnFsf0(?!HnMRGpn0ay<*l>6WK+iUT?)g1y=EPuC5&)OKbnQ+ks@fPLySK zhg#0g&l}5m-pjd=CoP^bQ`H+VcsIIOb)lm~1wNoF#tOO!*lMOXVyFXraKknB!h>(+ zA;_rwB6()gA-9Ymy&Hi09?sbRh80j?X$=ePZ{ZiwAYHLtD@~+wz33QJmZ(#pBfN8& zJ?#vb592~IdIu##Jugij(JHsDV%4WnCHWE!ZUjT2gmf=8GbHiyV$RE#>*-A}Ee!2J zX|3y%0`m;GXd5|YXj52U;An&%?@6LpKFJ?TfgBy;G>^O#m>1YBHEPF97{MnBbXa&c zF6C;C5VmS&#|q*=$1+7U^NS3UcVsZ{n%bo^N510MUK+UUxHeg4v~3bqgFWG0omJNl z4Va(lo-DO(Ud#c9BGrfU>NVvXEk|4KkmU=5#Rm;BL$&jEo&ugH7Jeo>))@}Hdd{`~ zw3OY?pQd%QIad3fb2aA72XQpK{EQaCtQD(np&=?F>lq?lRGj5ZuY&sePg#k^1x&Be z2--Yk`Vp_3L)y(%12oloiaZ2UE#TRxXdz(?O6R=@J_6|l z-beH?%SCBdksv&nCz4_!Z*)QJ^bz=?4>Yt^U>g=d88<@v$(qowCC*o+FURpCdDtsq zH!znDfs)+FWj&eJHoTacnwX>sZQJ5|`c{%g6=4{?NqScN@n@fK`eysu8=~sn5njuM zfe-o0d+ZL=u2NdEvPOLT-$+$+iUUGcg>qHBOKY)ECnSDIjwQ0{(lwJn`&`LwOOs${ zU&q15#^UtTv1xT*goXCf{kW#vVUn8!_s;j`8yLuGjTObOxUH@6Z(9@SvF`Spe-d9f zamVpN2A}=Ppe4}vybnqErT&>Qh_{@99(51JlBT-$94HJ&t5X}DOEGfLe~${xts0dM zwl^~XQlIn@RtSCni?vu%g1##10%W|cA$sh>d&354(0-?v%oFkTAN2++ncT6#JCSp6CSy)^L zMEDoGMY%^AlvcW@ob{E}GTuIcuW<8Mj;KziYsr_|$@bDz1-(KX^^5h`+aeA}L^h@} z2BWHz3f#g%@3z6{t8j6t0hj5t>qw-RTK;J#M6S7=CnCJIIdp2gS#oIyqoS+tnTEgP zch}olZ3pNzTGP9--%EX8(FAPuPOHq1eYf21s#RSIX%cK`RbBlVu;NOi5*$y%MFFxc zxMf5b2LtJK=l^P){uz$$r2DM6)jJ7^!Kt4CCfI z``ART^AgNSDpwy6+OGMLH5EpOf<~sY8>|U?GTu(^Hop*cXe1V9DwwJ}ywcyDkVVyd zXR1hHe(7o}pXAM_j{e|hi?PbNsg3GXl5O(K#3Yu;=cMI6_aqM9Fut!*Ck=eU#rL`H z9gM|lGTwUu*Ozb#q^xNL|n@( zAb->s`D*J!zCDuBKh#G+E2}Zco_WGl=l2M|`1!Y7+fw1r2y^YoqzygbZnhSznZn=Ab^oG54$>EL9$+FX`Vc=!A}~UOjJy4OxRf=ky(Y^Wr~*8((f*}#d28M- z&z!eU>8bA#FWCccQJWNvul&5JdwYl8*tsNk4p0~+a@jMDEfuJCAo>h+&AUWc6;Ce( z<$spQX*tdvYcxtNKn>TPPhNO|}3SD?Y;-LRGnNYUpgY0Ku#xRjLx%#dAMpU<;G#C8Tx>-4)rK&TR0uGw+t#;?z^5eKzf@TJi z4#?;sZ$$KH5BDkl+4zjowG*|1_uIQxmYVy&q~DxL(F388?lpqnu8I^&0k%T!&QJMH zyjK?w=<|(GZq9We5}WrWAny^(U&|xW=0(YSxfWbQu{1-+!g;NNNzraMFRTso>e%F z1U-K?LTw>SNWWta7G%ZX9J~Gs#j-_IHUCKVZL9TnJqJ(#u@`giYB%F9U7xB@W6{hx z+F^&~Zy@sTr~c@3ToSnt=YM>=kV``^4*iP)bGjui&Z}VMLUVC|Az~_K73ss1E*eoM zN>U$(28{A&Zr&LvPiCH2T+K!Vxpwqh1;SF8TGEO4efdvqFXv2e$)O=lqx~(Rmz|QX zt~^ikCMajd&!3E$2xu{?k9e^}ZfCCyXN#Qo4t?k8SBg-QH zF$ANp=TM>5ijD^FVl%|R}!f&=aEN!uhH3(@q%b6 z-RTq!oS7@rZPcbq4lm|EB0R+RzLufEt;JpV^tRH2s3}94Ey{xmuK2-H%bH+b$c`1e zLH{RpT9(Mip0lvGtK${UZaLHZ=+(XTI+7(#v09+N8=qrERdJ z*(si*oeb%fnEAUPf?QCQ)s&~tJu9rC(<6WwZ^Sel9d(ect(G3Tns@4SwwralbiGX! zUpEN3VAK9>YUR!7)re9MKZ>R!kIs%lN%5XqWS@W7>l)4&=8Cn2FOcBM2X+gxWZmAI z(WaiIbocrhj?)rZR5$%^H})DexEWL4`-m(J)WZ{_{1sd=Z<@Qo;O#2>=LS$0ZX8Y~x=QC`;neFhrX$aAS zFP*EUxLk5Zl}5Qv=e56VFbzc2Gd4;1ap~|U4~%jFgs%6a%gmz{ozzv>4{_1npS<~| zEZb|w@)}2rdF@oDP9j6eOZvjP;2WtwH?6NhxbyXfUEa=&1KF=7?>y`oQ)%Ou)f4U> zi}f+dqXewf?3xs8;Te5NQ>iR)nH#D$JH;M=l$h7~QIru2E}trX5q9rP!HgyQ8za?3 zp1ib>tK5VaOL#t(RvuyJABHlA@reNpybz6PidAadXIpLEq^pdPWT6ykat}-J%oR3lSFGj&WrL}QEb4%!%A){q+?IgO!!Xo?DUu2qeh6YMNLx8Dd&t+9FR=D zb|)ZTk=ks7`wCMJZ0KE&!2G$<8jTNz#zc;KE-sUx!b>-wdr9ei(4W z6EfY+@JRVWG&B0*Q0gAIdF0VK>9ROWdep8J&;B5>7H`5iB`VJ6vB7`tnb|V6k}v{} z%VQ$)4ol@fwx5bQOmFbMe89rozpVz-`RPU3%-AHpALc$Gp|bKh)LJKo z5~hBzU7t6Ly{gv4Fd>UBK%3lZ7-PT6zi5Qs!9R*iL9~_wqv2u3MwCS5)18|x*UszQ zyznOU!<}GrI)wYycZnzIjah#8*7lx`w%K~`tHUt?tI*>*HpfF-b+^To=wyU?RH)^r z2wP=5Hr^&Ej;Y{XsDZLW{N0In`$aWuMyAUiXAK!_s#PDXKhop}taS4V>3>}G^zFYv z8KdP~>5Et@Hh&B)sM`6`@xW&Tac$lfQN6%Bny8(-xHZpGP@}UEcWLy0fRphOimXag7S*sH6VCpeDOW8P z22HImbLnIDlKCy)QWK=@_G>zK8EY}Lrmwug>eOndPDxDMrvjaBe)DszywqQE{OM@i zV`!vhxcp@``R{ka*BO-Z?ApuFS2R&>Z}4R!CYR0Bp_?_C(*`zS_sk}sZE>K;T*vUn zb^9qA{8{x#6|*21$n!$--R@JwOf(UbiKV%N9$Rvo(dKt z3e4|Kl_DXf661GD@i|$ZA7LX+Vp?s(o`o&C4r3_)+X^eOIV%M zteur_DqEXs2UD)$RFg(Bg$4ZySD`4nf{7S8g0PAqS1ertK3kYtFg6iNiF*-n1Vj(; z%#rk>j~GOk#aHq*Gg9D;9hH{ygM-)O9amx<4jUKZ*G(ieMcOHH)s6?u=N8iW<5D$g zV+0vIN~itXW{99#E&y@)thMFobn=7icc|l7loqu+*ev*Ecn}_E;(}ej7e9T#5p?(r zPB(ri=l;FNBiwV!@b9d^L}CjF8Wixi*1R}HiLx@y#&Xo+M`#fsx@!@uNBHxPjXw7z z0+Lb#CSq7Z$U;l6Wg;IIa{cDjny3$LGP>Pha<8x3&YGCe6}<7pe7TJaGABGU#hsrf zS9o;aj4#@@=ZCg(=4ufB+z|_s-*^-%UwI*FI_nqS;a1Vc_G<0_8P%R_hrWqqJMGYS(k zDG#wyW7PYnr%($c*yPzesajcADl!swoyO9H%VFdPL!0vh$G(_&Q%kiT-C}GvY984{ zbg^gz0gz+;-0jZE7yJ;Hvq_Vgy!NS$!P6-hn%;WmuhMUmOKm6!1O4bWzX@QcqkWk& zbjc5Z(f8YPK05~=C)2FQ(_R`Ul{N<$S7yjgtwpK=d_u9K@?k$UB-LtW`?gO$+?9|+ z7p5j=6`tHQWx+Bj!DHE3!ZJ7{739+Oq&qZNep)9gD1APN=Y!$j27>Z$>?fW(ztlI( zcC(MUIvU+Q#5zCOkJ26-)fWuD4OlVjG95m=5{3mK5T5N?^% zq=xVLSkh7sq#}Rs#w^L#AC9(_9UqRRr-FdWpJ4)83N-L{1JCl9%GcO3e_%tv&`D&u z(}^&;&{vzgjb2tjtyV@i01F=l;SS*Ck?{V8&NL^an;${?tyQcd2#Xt@ z5W6YS7VtBXY{&L*9#REJhIbgMR{y4|onosXcy-_|xZ!(! zjarYT&6YEIq7dHZ*kR82lN?h@fB}oP%3FMkjit)s6RlNb4g1wrne$@3gJ>S?udJ@$ z5LvHcSkqMCx5Wtu^k~s54_t}pD#o#}oQxQ19l^dM$JjbXJ)>&#|K99+qrC@+ZE@J& z`}nH-19(t3Z-Etg3S;!uV~8UbH|MB^>&hm5UC!QloM-LpuB;g~(-f1bN5!_t2vc7uNU18)sI9b-F;e8hDPcyZUNpTko{MV$x39En^L&e{Xh zH;A?PSEXMJ3Q|ajf~ab$1fmDaEyF%tuni=m2{Z!foN0z%c|bw_yc;N)NDf@a)^Y{% z%`&l=DH?Ghx_yYU3;&=00@xYr{j&iF$wc%QE1Z$fGok$Hc%vBbvyQ)?q}$W1$clF! z@KyGgvTJfgI6ttU)LuSHqhZyY%5-*1MWrKJ)!(h8UuTGYa=lJZsil}9a>ibLILx`c zgkLF2D@KX|?I0aytT#)8lRR+r$I(V!U72?ljtWAlYkA~9zQa;5aWg1MV{*la>1<}x z_HGt=Q23?~baC{1#H-++-vyLJ7{0UvWy0LF#*8eydwY;ieX8LcG*(e!EJB zq_X3z&h=Mlvh^C>LZ#2|lw+oj-|s801|=(9O6z@bj!Z`C33;XQ)3#+=d-Q&44>W)9 zXr$F%*wJ_0TDs(PKVN*tdeSrU2KGjx<=D2sO#6v}Sk*tugbSdDL61$7_zjCx^SKh> zc}2PQH5TG+**Cdmx~=8GI=#;>r>S$;-k9GB|h0; zIg8=nRh4SL)6{D|w6^(jN!L=_TCiAIU5L&}g!!q0+9_r1_*bT&7?qg6Xw6t5vnOq$ ziq2}Ds3S!74?vb*^*ZyAs+N{wAf9r~VjM2Bu`Jksk)Le# zpu9ym(Ob=!QLnr(YI)6-?zpeSN)`ca?-0hhgi~D2M}}?XQ4Dr@W9;dDrCd47#Q{)$ z$a`ho^exY|MdQx(ygU+K2{{5+wfn*H>ONkn$%5iOSddz7R#g7%b~7jV!qL2#d(!IpVPy9=-#@VFdw<* zfOsE`VwfGDO31pOihxI0PkN)6D(f;E5}R1&<)KbW_j)K_P7+u8A^j%B{~u9r;t%!v z#r@kS$tWt@Fl3qf$dYx&Hbf=~m9nqdvm5)q6cxr+NyZjMCHs<{Fvy-PGj=l=48}Ir z;hyi~{@st?zi_>;bDi@(uh)5=^cwulZ$>5htP0b%cfazT5KDiLLtp71{!VC-`Gb=+ zc#}j}t-~}~dX^$v*O2Fn8xgX`@DenDtzjubBuntPiluiH7~0fRHT#~tqP3r;*P!g> z{{z0+xxaD?K{Hkqg21J%KMi}OaU0R{ z1n}D1GUHpvhnWo6ybipl6E4wIWnKQrbFjnc<@gdvbnqLQf+_m^KB(b65)S|iVD-< zD>t)BzAS(FnH$fyyv}#M57#v9EQmAc`D>g}O>j43dzo(9__b9*H{NH&d1G?Pp=jSW zTQ^`{3*N=|xvD#S&pEvUH#XTH;r7V)HPDcHi{P9%ucw$;5-VyQaBEwUJMHf78^ti< z6~Topb%ZS0&p|09?zRauq<*!m@i@_w+_&gT0?mqWeKdkYI_L8vsaN zmLMX4ncpzOU7up(Pf#8>%Va%5CB53oc9n6BSpr#pXp>8Q*m3~@pRA7*JK0kdR~;!% z;|+UuPgO19&8C}7&CG4)PNo;dbyK{kly zjthInY$z`?(ye$6E}MRd7i_*aSkpcPeImB|ZH{v-^yHz6)Q%jY1cenXAH(4I?YhT(cc{RWbS&sZuQJB20iBmJKy*eH7bY?tOQ(8ecCeKrC;YwCfx`L-x(eK_Mw^%FO^KR1|0>n+Q z`T3>#apSa$AKNa1!}t36Hez)m?{*l=9!YJL9_fpnIqL87A8t#JS0DWaBKHWz_Tk%4 zd#XP)XE=FeNe899N{*oLY=Jl?B{*NK7xc~)DgDcZA^v}mL@n|EaU`M=Xy}7H#mxVU zk&sx;WdFuI*`0a+=2cBS+d+0-q_0uq;DD6tHtXa7iL1p2bOA!tF|gI?X64POSm@C7 z2|+Uytt;=L`5^N~DMEkVcn6{5W*Gg5t81_83?KOIFG@EO3tfyxST|#UYDPU1(sVa{ z$b_!#2O&&;Tjk}_wLC@hKZoPpQA0s0cij`KF#JFHn)gOD8EnHIoFub0Qe}j4xt?KB z*PTE&X*JmW|7JdC6bKb08IT}`pYH;I0)y`HKWpcsQL3u_RWJq5CY|9?`kx06nJ&#) z??%2({c@3qcr~P0U3YGbGl3C@gB7b`u#+wNyS7!~L)@(nP%>dOjygx_>o`NS1ewk* ze-j>ik<~3}uhibOJ@pTmMO3LSth4hzpdJ82b816^FgX?xHFb6kCEIg;GyWYN8(#>3 z^Rum!U*R_U-|bq#=i7F5Mh3he+(#rDVCHUq*UP8*mzfloIpYG(!$e-|AD1AK?PFkq z$CH^cMM7`O_U#O8fpz`$+c1RJOo!1krbXT3o}N29PReP~#{Q*?u1QWaX$3D=CO-^Y zAU7H9`{9kX&n92yB#mFLzjbJEeP=t)D~%Pgo?@_hAZj(zr!Xh%x|AGKbcS9ztDroi z96$Pk69cPp_H~jk_us_W({Dmp5MRW8e`$_XDVWGb4tgfTVP&L>T z$Iynl9_q+(+hYrJiG2!ztJ}g|sr|hZ>j5CnrAfgv>(Z~qp}96!tE|M5WP zr@;FC=rTI@v%Mmo3=IQdgU2=B{wwN45v34wZd{4SI!t*0GZE4?`svQlh))Se%b}Ud z=9}SuOkm}Zd5l71?{6D1xiSCo@_MA@ehcoQrjc)3*%|U=Qu=>C8(1k|2a>zaIjiLc(<^gmy*_pU}XOti#vitnRk}WG`UI6u_>g?w4B6_ zbg4|E?p{tcUwJ1GK`uuv?Z$c<^#R5`xy7Z#YyE_izmG7>!eC9q*^uN?vi?{Zv6;V5g z{Hph&5-(?DqmP*$>M;6Kp{9s^XDI0DuJkp_FO&_?fvtC)A%Iw%@fA^uQy3iH#n&F6 zi$vWO@*fZ$*7VNH$WHzr+(zxt_iD`R?wf+a?A^kSGUV^TUhNsF*8&n-_TX zP}!n$X~8rA%5?Wgw%j}Sl=drMdGO|rO7S;&9#|6%xD^K3n*$UYlz;2_-n?+1?|G~n z5ZWl4FXu!`#QsPQCv}SKURN6~4uZ7#lHFr{$YVo-hYP8Oi@Gm*^cl1{WRrYezOP8r zX1(k1SNPskhve$fu~%#0FZ>o4NYho)ELN%_|e%fI3JQ`o8>_5vNh8)~gLRDX-qvIHV{ z3hp3EM;4YWU-6#KCl$r$)Y!jB;$IIJWK|B^M&Dk(luJgFayLIEimDwK@T=cCOhdpo zQZ)zt9H%`k{lvP>DplW6yu+ z2!1p?+OAfR*EJOOn^_uhAP`2x80->r6hD*_;=SBgLW%Soeup=YuXzkknP&+S4W2~? zT#a5$%c5aL8rX1QWiOD&AFc8}B?{aN%SrN53O&%)Rh_0qfKncR#Mj?G$bOU$UB1F# zQ1UZXa9~wv%VwNTZo~=$q)2#S(FG$5yJ|rNQ;CRBp*Fxz1dJA}76RO6-(N=K^OL>I z2w5hny^8dgz`p-z-^gC=&Cbm?$N~{wPTmy;AmD4t9W%0R?98Pg9rdb-D4 zCra-<;u&G<(wrZi8C;{-&m0mxI&B`gOmF3#{IGgiqVli$R4VZBRfgNjVPUxv@{cvG zeL~W26j8_g74UM4^O!P9G+9`Th>4xK)}op3>@d(NjUO>j-b)=mq;s4l|3%bYop5M~ z++fj*I1~u!Fl}9%ju$>)P$dsLsV}?)gx0JFg1f-F0n?$x7}v0Sq;XHrPdReZB{!|Q z1@6xO6+uCR@LC*HqpXPmT?2gWe%NA#i9QCXsdmnygqW4fnlAPX1L{SA0KY1hk71%z z&tqLgr{>KdU*(urc>~c%zKf8~VDEAtZH)fTqUzb^)>)~_)#U3v`;kkBs{wn*z4IDS ziFS0=->8`T@sFWATWECT*%7raxar-1AWAR45gGkjL~lo{b#j7r3hZ%+q^M{5 zT4F|Tg?o3ga#N1_`~yiyK>UU?U?&MVCA*OJMc+;rfmNnFP{b%;Ga zd%vn9ZKhWG(7z*KB$s`lV5V+pv!f%_qjn{=R($KM2NBnJP8wpd3A8!uYgH}r@OZ26 zT&g4z!BcLvQzA>1!g4DnSUO&NjE`U#mj!O?tJzd zzo34LRC&FpTCgO!WOO(&xuQGdo>xNI@ZYCIg5)OSRf>|O8K zf1N!${!^4iXVWL5lX|6}tbkE)Km2}rc9ps-JbX;iigAgt+eG>j1=Zbc>-^Jdp0^B3 zpzwUzNv`LB=Uv!d-5J&iya1Rqr5w}`G}KnF=Z4ERplEJQD4i0tyX~=eNGX>sOddE{ zqirh=c^9B0>nwzo87!cW&fS$!w-_Y8OJuu`gFV1Ed-EuxUUzs4K%OURwAYWMqZYbp zMZ8Y~E@|J!fCfuAe--6b7Mz%$yZdH$xeX@Y(4pmMg61I++e?RiLl^)nuX6(wk;`wl zF^8fzk3P#ml4wRJ1sym^G=U^%9rco)Pr2C7ITzMvnHJ3=X+@0As8us88WgC3ACIpN0aRqb$zLiV zr=)WIvP<)-X($zMXcHF0>inK?Stlk|-4{=UBAUpF_DGZ`EsJdf>CXbnNTv62lQ-xN zIS{=yY*(ExvH{;q5!j#v)~m<1iLGmBL6@F5*81g?hrdLlf2WVWstcG*6P7u2c*yb5 zhlJ~gs9sY{t2l4o0EkAO1jdz}kDT)XLr1gXxtmWW0*>|mRQ=3`{DmB0-K?hQU3VKJ zk}0J+Syfxj1hEc!_E`4F9Y_bGWYp}848=d#tq1*^+xS0p-Lrl!%V*HvL!a)L1hq{y znYO-#LHeM(3`pjUUjxC%%I&u!hxy8&CfC-LC&n*OiC>beN4S3;jpu|u$gQ!r(l;UI zh_)+Vvc_RwOpKVcY%S&Gev|Ii)4K0Wq3H@C0t;jH6WnCWcK{*;M^Gg4X(l-P1>nug@0d&4F3^W zTW?e2fX9GOk^$hb+4K67O~8#X%7ts+m8C8+O&6nIW?AVz@=NzL)a<_HJqr>kx*2rw zv-+JfSIyk?I;}R|JoYW98Y1^2lYhvtCiw#>$k2sUlGJUplTsM}5DyiTU7T)eN<_t` zIn1x8W*Ww~+z~(OJ%7_-9Kw@;!XvZ!vI|<*Cp$KwAq_koo+?oCegek+q_B?mvH%64T0$zf)eWqP3;UqF1S9csdf7;|Zz^{>x%-aP*GSsF)^C z^V;d|T^E{wljpsA!q?;ZBX_MjsBSIFAnGg!5C96jJ7FhGt@wUmH4$()UYA|wTo;&v zt7C2*EuWnnpA~F9Kj}c?aHhT#Y5FQ-c4m;Jb1O= z5fGt~nbiGyCb1~!_bBywbE8wTf_jS=mQkzLbgP}$9{FYX+UBNakm<>?cIBr~9eC)~ z7A6~f|Bvsv8=3I%)F0U!x&gyHMvf~tQeGDQtobP|nLZiOx)L)xj!JS`pTO2h<2Ed_ z6HY)khCKE#O}>mP^$E!VQc|o?rr!?OX+DGMZwXx zdj~2wVeO&~NeTA()IqpZa9%hgXN7!DlhG&gR#!e5QP_nhF}-OUkreSP`&VoY6(WWF z)|mUMnD!VgecOBv^Iy=l4v9uJhY=A)pYg%oLN0St-4cu4A4uW08Pb_O*le3Jz9gka3&wAjrohLq8eoN;`CD4t21XW8k)X zZNP}+`lOns(m_Mbk4|--`dqRT|0i49CVzMRUAHvw$I*LtbnXo~9mLOMlGdC`1(NIm zIqA>ff|yhU$IfpXcxKvn$|xu~u*WLvxT=807A5~P84tWV#;n*PCcys*IeEMt6FaXu zU5z&P{8PcWAY_>gUrG`%Ml%Y!Kx2A2BM#G@-AuC#_0U3UP@XVm7K3)ft|^5@%f!(A zA2u`Da^}~5ivE#8ovD7hlT$E?*+vJ>28U<#;6)~Iy~e(UD$V2W*!SBtfm;LfU%Y&$ zC)H1{9(!Xzr8ZCF614ASX|QC?&%Z7!>{gK+0z|b}n#F}Zh)Xcc2ZbNkbO9UY zU*!lAi8S>ZQ_jrJ8}kvV971qXpG@Uc+b?TgYq0AI=U|^W!!^b&osd*O`*X8Oq}ubv z5Xb@`R7#*_EFgkZF6p-JlgttBV)_>)aekN_kHF!1kaTR(mlVrmiU?e&2Qo+x&w0{p zOtT^t^HOn_XvKLD{MFm)GIf8oE+AT4*a@dI9@0snD3(x@fa@-ToG+d6Fjin zdtPvo1H`S~S2_?0^-W$>u@VLttKWwn*maoqD`O`1{aT(YN4VP8om?GS#UOHfoMY>I zmE*x!RHj~dSo|IQc6*|G&@UyiJGha*RRTc3WR$1NQ{`JYF@WIeQ%%XZKU4A&q2hZxnI}dE%M`S>gGZGeqAYVX~cna4VX{W!F1ndZt84zXrM`} z(G)coM$TC%r7(4)!&}uB56{ZZkIOBx3b<*2I$&R&DoadRu=vWrQE!29 z?SCIX1BqREn=AP!vArWCpzIcJ*6wx?@_U>(4W02tBxmp$(lhWMm2}qE)OcPZ;Kq?$ zQqd!8+<0=G=HTPX2K7eAMxiij62uOTb}eDVs|;m}HL=>p_F8&74)){!zGfOdP-0a+*+_tTs!kC%Y^@O@*K%iKn*=_46WLaY}}w5 zno~NrKV(p~ZhGEzGNqa`Q=Ut)Fu&uvxK3J;!p=r>@s6Gh(I^^dnVCC$VNlP-)$REPM+SygivwhKJn5Aif=f36%rJcq zMD6**ox*g3wQ$h;ZFGJi6MN1`60)LiR9$x;q30*7qggQjjetwV|4y&k8{m+IY6bjd zWd|rqmD+uZf_M|w_X0jvC$rUi@2RIE6@N#iUlnD#s(inRcxeS{Iw7b9`}<62n48M! zV1?I@Y=9d2x`IxsQ(c|gp<4<*n4qfBR=W*>4w1QfP{9ly0GHADjLmKxIg7mZL zn=3oB4xLzQ2gI#Z9|avMMRY9kS`WA2T1p1kr{9(EugFxLy%SX<-u)p!{WSnPtvQ*w zh5UN4WceR8<-qVp0tjyzZ*$zkvm5AP^+IlL{ENbpdhOTV>TwbU`oN{9j)g2Lp?chV zJz^YVbzJOZUm5T$f-WJxIbdrVQ-V(f<=wwQa8myQt@ZL zmz+??*AD1fFC_FQZ2S2?)Z~!Z^|-`Oe8WWQCpbU(Y_?LRhO^n?J!YO@!`ex3XdN= z{JbE#?i3BRfQx8;h*w5T!5t}Sem163wYZ8<+2uD%*@=(0w?*PX34zg2@j>yyuoADm zyCO34ecQTEC)cz2d$|!^?T~HF`18s7)b-J-#V#6{c}spc-%!{n4L{l(prRJ@vZ7oV zF>lM($SmPYv@JRR9;N0d$lb6+oBSXNcSBE!?PgU8_7k4!VgD%%7Fn&3Azp`0RZFGd zOB2)#WSoEC>>bca^m#K;&^2kBKX%$fkE_+z!_e}HIh({wymdxO>hJP#5LDV4AUB3}S!{hWT>e;>Wk$ZTq+53q@rnP#a zw3*%6+8|WdJUb$Qzt3c3;@gPL4{e_TnR=Ogsq?;S}KFt`C(Xl0`7^@y%k`U z1|emSuc1T@ws&*S!_KV)b8TTlb1VtzHC>WHH*tyh@`4QYjr^jGF=Gxd8N-Im$jzgY#4 zVNZsOWo{@{qhTPf(_y_A#O?sMX#K$fk*vwUp0AWv*SXz!?PA21B`Y;W{^b^1n& zEMPO=*5MML;;J*3%5Vkv>v8X+YRGoss3}MF_kbYnz=K@aqK%qPAsg>PQqkg>8>P8Y z4b$|CCg24}w~fD7u<{*M=%+dUx2N5$O^qJ>a?Oy3>@Tr&`FPqI3=Bx$L;~v<1fD6$ z0~U0agrFSwy;~^Hps+pGPrTfaS3vfa_I7-1x5LPFK(9|qpRmekWV^wU zVH}*Lt1p@i`vZbSub`6*tB$*YnT($$;p4cogo=)P%T$oNfk`>`j-FP9>J3#h)Zv8oU zHsOuaFb~}2Gq}t!oUcH-x3Bx-{Y7?+C07yj-em_C`p1v7EmF)}@(>}5KfSVd*GtuN zJoy?7N71F!qkpa6Ddpod1ibmWNn^kme9*?K;cl|$@N_Gqm?=^mEu*GYCf-8c75DSstc7^;1;W2o-`T1E2=08#YH*V zOYz3L6DvZiuCDQ`1pH@n5q?7GHAzn5M^?`F36kj&qi;?yPmyc$*WY-MS>4mK-4dKg zxE!^Lz-sKJlX1gfoP`L6a>Ks#TZ=ojl2Aa8#`g+ZX}SJ@Qc&s-%G~$$L*fgSiVoIoyH+iQa{9gb zW4X4Xoj-JnO(!`f?E%Fv8LI$4qeE&>WBBUI3WXt<5$`b2d$zx?271Mb4Uh0Q-}&TG zMTcHVy>93kNv%f2@6MtbpBRR|xgF#q=;8y?NWg;Ue%(RhAE&gHvRm);pMD&f?>TQd zGq!bbC=8v_6Y#q}aXU%H6^K3e^C>1Qy<_?eZ8Uhz8gV1Hxn&(}?kB76+%Niq*`Xwd6;k(d&%UXh^~+5q zUZnUrg^}L#%h?k?Mu;^ydto%2E#WAu(7EDsdd8p<=2SWhMVTBRzXlw?GWpTTa$8&F zWb*BLiifs39i&Pv;kx8nX==%qk7R}1&i7b|3WiHSap@Doaqp1l^D=krC*h4fW}XMU zyxZVhLRVnQgu*u|^F54LUPXOj^RbHy7vIRXw(7`V9f6P$$bMqq?+>AIF}ufF*UuM9|Ep&WX<({Zg7TEh3u1pNNS_q+>l z-6AMSa2C0=)<>{9IfeB9@s7(3)|B?R%O4re(%&!2`X5N`ctoPez{$YOmIsN>R{w0$ zP{I0SZIrEC>~boCUpG%S;lnLz;uy)i_>}0V&?1I^A4!f|RBIME%i##ox#CYs3!U4p zx6j3Tj5Qp`Z~R>_v_21cT$L0?vggMXMe29v+?1Fw!u0*RkI~5b-+G4ZaMKeg8pka4<#xOo4HKdtW@Yyn_6{5k zG!9=GDT08dftejk+5{gm1zjMUrE%jj-Nl`3eD@KErW$6)in1X*?8F)fi^ELxa{oDn zLG}EhVoTw$-Z++7MP=cHGxuArBB_w%+vEd^4nb4;R`+pKJOap&(&$so`S70!KHUowbQ< zp$-zHcS)5Nc+yAqi`BA#A z`x|i6cP3@PT|6?sRF<;*Sgs|?P~)X9H86Z?{Raj2@ZDa*KtU1Qud{U32u_T%q^>*M zP`1bd9MZY_MJM}Qu+@y6?e$3LCp^h0IWGDcLalhJXnc-jb~pSPlGXC6yoLd|FaC^? zUkzEYI<)gIDjcxF*SWAgUI=U_Tq&zy44bt98La%WESB=lN=UCt+lpDapp=1;BCto@ zkY6l`;xtG?;xCIo*x~vGdm^U{5}-+D`M}_kU}665p;TWfbgnPzQwit^gZk0P!bi~5 z%4RD5pI6{#db9AO;m#}(!e00@vzeaJvMl=@Vx!vp-LG_Fno$ur+!@D9Rypk1%x(t`8%sED<2|r-Q@lwit{Fj9xE8^NjUZ= zzSjt+)m{PM3rsw!7b}NMD>){jZMQz9aD3#UL2W%SX1s!ms*_Rryi?C?GLPLw^Zp!j z9l3Y1$GWvYOy8Fz7RML4>D%=DR>Y>+t47IeJWs$aH~3qbCl}?khna3b9e@|hnBq$BQuDeNGJm#nu=~$2|ClrUGtw{UmAph+TF-d-LE}bS z3L>w7YvW(nXho7WGiMvCDQ6g`Xo=Td#9zq%NC%roJH49ndpd>veO?a#jj_71*blYj zoUxWaqY>m(j%u+C8yxEBM{=EOm166M&mv6;)F?SU@H6mKmKpvj<&Cil^oU4FKXwN|CF#=IIjQkaL|lbU8~fUk7g7yzi8vQN0-ypQjG(h{z~#?~H#8!|-Pm1+8KR^{yUCvKK+Rq{r`S zO)NSX@P$WrW=mnWg|rxwXmqR9Kw=6rJY0iozG4uln%y(48 zN}o`?guOm(1&*|^2yCpI8<#p8uz3+rU2Vk0;B#h1_#+g@+Hf;YHD6AW2g9U(wmvx8 z=AWAc9{r{{bv`Ym$O@v8kl;P2($};QK@S>wt%HYGIyq+YqfcE%;^R_o3L{g6Wc72s z!7)UJ2e#_*5ecrzBOqJhRrUJt!as)4H3l0J3p)j)SfgbUUQK8Ok~zF~Ql1IyGgj|M zvok7}Lb^}iD3`MB^phA>DWLjTwr~x;lI}AwV9_&E8~<*hAayI2zmSs^j`>%yi6FHZ1?E6&3EOm#!SA{ zZD+tG^K-xZuL*8vv9t&c8{Z&{7@^wRSWBH*ky z&_CMVF&DKxLDw{_P14=%wP$IkA8JWoF2fur<>)3wubtvtcifvT`s}7 z+-#3x>OJ<+BWC665ewWn^s&=Zr_f`2#1=+5co3VEv>9%A5z3N?We(j|yq3klB#IXd zrFV&D@~=Ta>NB&=v-hsBeLhnF*D_!D0xJ3(8KCZ+7w`*uN3UgzBPbs0@=Fimn%9EjgB2yz}ApxB1@K_c>L3a23 zxkrh#rsOgITX;~Aw%{)y-=sQUarcAWzLC~1#nb~|!pvPJpw)w@g=uE4Bg#%X=OhzL zR@9dWYu0gbe-PX4LA996w?&U!a8pbQ?U??nA)8O%JL}nUcp9JkM&Z$J?nX{X|5EV0n$4MSVg$9Ju)G@i@LktaI&)jbC^{zw?_8O`@`+k)--^kWv_@_B%utA6LXz21l0=Si>F z;0hiy4WBw2&-+^~8un`6tg#2vPL!3exXv7P8xUat+spXSB!{18l-i5N8$R+L#P%5> z)Gye-aCL|iU-vZ^jf-0$IXQ2p=n&F%L^%_^JK=|#8c zhXmpD?6#alLAX~$R-=>r{W)@Pq>I~!>9L&CNUGq|iC%@%hb7TFKI~*OrN%w$GPgMz z-=og|+UY^bd2pYpqnCR+0anM$fhTb>V{R+jPB)D$P5Z1(IlI#X>{(BX@sp-1Cf(N7 zn5mD9q7+&Y^@2_=GDL_@f_gY(cDAyq1eU>Ny)-QgcZ9$Oc3m||kVe`5T# z`IzFr$)}S4Ct_)zR&rHAXIZv!6n&S$FDviAW~a zz>+5#Z|Q`vD}z%r-!%$a>ll;6 zufR`h%RqCPzL_eG8{W49-H|0Ip_k$^wR73~V3+mp@UaZG$ffr)Q|8IArT5vOj-d|< z<)s=!&B=n))~~ta@bbCsiO8AjWBBi`Uk(asc89dYSfA=!a)UHs;heUevds=n?-202 zHc{BNk21-bMIV4=HYa`;d4(XwNLK3lO%_xDpwDv`$^$1o2_FhQ9j;zj!rzki{Voz@W+=?6?l9%C z^r^P{`^c+GJ*np68OnRBCo%ev<$sZW8^&&R zX|l`sp`W16MK<0 zD9g*+IV`o+%$}l-Bqn%3QH!MNJ9;I8gPVh>l8nf=zUxb_BJ|#E-0Eu!!t~}l*r1sU z{r_d7cN!*|H%Gx|4l4FQR$6N z^8mG`?Y5!dpbII%u}O(BDQ!3Nh3A=|sdk&;1%bk9|P#R?NGU$_-aS? zerw}%`x->_iL2dq5P1?*p|1c5MP#tu}Ut@3jPB=ss!ZKq)y%#2Bk0+Rw%rA z=-^|OB3AW)h-jIV%~DD)@F?YwAFy7H#e(IBOKREvSf96;sutPHpI+jd)a2uNwV?7E z+vY^OHs7K@`2^pCO%Hkh1tDWDKvb7sH-;^tRRPkkFNm)(N}!?9^dIRCx0$pgHL*IOX!JMfqs_0q1XRSv+h-ngxv1HZvNDap zcIoWD*QxDOxE6?NU{mqbM%ciN9&RjB`8c9u^o(@wSw-QcV)`hyGMUxj`z?(e#gWBj zTa;;wvfT5+>+)dp^8$rtjj!+Myuy4vE!{D%vw67feQE6CnxV0HVAA_70$tY{uUjyw z?XY)3Di3GxNXa136vc5VUpf(pvZWb*Mig@_w9GHzt+Gtwy8 zoZSP$Q;SU-T&BlIIH;jnYXk%5r(1y~s5X(d3zs*2N4&c?PHxQ|4F)!t#505G0UTL* z!qTdQ{47zpBZU>Znyf0P(>PQ0d82X4#kO%}O=5~#Q|f$nr#rsCkQ90PCb{Md{4fGB zwVi%`l2yx}+gnK0xVu^F>u&+`I`E8JIlD;ilWr^-2p#;&b(MNYbEw{j*)QsX=J{(8 z6mdK#d`ZG)wWV8>Lxb*N6bs$tVOBfaWzG1RNzuGGwN7i&*n0vazf(#-IWaNO&x`XD zvjaHuv0q8fF^;8e{#9$Y-Ht#OmZ|gZoO;#0we)TbaKvHm&p!R|Zpdj6|K{r?>eH#w zb#FSg@GqCT)4xa4?>+n;0$5Z@V0kgn5#vjN_!7`9osXxWK`5)skCr!$n>{gwpavw$ za)ya+1BOJk=Aw~j3?FIrQ1w_KDO6pM*L0ncPT(o{ZJ!9UtOMHysRL2(U>G3Bh}zz( zV`LyaMjjaM2OPZ5s0&U2X7AOg23q+pu;ftj?3q*E!$lMDS(Y@589&O7IlRh5a#}J) zc7@zAwUZz{OBaGkX8*h=J%@j2GsO}x`O-{1VE;3BbMeXfFNw0I%FvqW5D=wHG0NVH z{=4(T5xYPxv7-u;DqSa6I)1l?+vc^e)#)$~+0E(RDh`fyF$(PT26{4fGE`6gR95Wf zlo{Hb1S=EjD%><~DdwmiV<^nF8etb9QcS?Lq zk+w({yFB*mAIiU^46Ex27fU*jM4<)0yc zFsk>Zc&M|=T6Y{aJK)cLGDy+C`h8KhGD+qr_!0>xTt~^$>#}#M2{(Qp8G-bVJJn>I zGGb&TVsmgHmyK$^;>>r9%>02A+(lQ%DSei*UUdg2~8Q!IVUDEU+)CCv8^ha}Y@eXWO$?)QYn^GqM6 z7D5GbUoqotZEGj*-@Isn&$Lh?T~^?DDCo)lZr7^kaVSzp$VaKq6%yr50?9!HF6Xz< z(V0TWrSin*5#dJ+uiIjQkX)f{4XS}g&+TN}6@$6BBJI*)q&rt4XZ2pY&)R@hozDm7 zBX;->i1u9k5#|v6qnIHbw7k8sES)GdhO1#4^~uG@~YuvbBt2jT97xSkicCx zgHrxJ3JaxIWOx!4Ll+mdka)$yr{5q7>M$-9I{TKR6;{}qyvaU0bP0thU-~voK9U5Apt}s%M32_d zLv}y_A$nnZVE7Nes0>AQ2Omd{-@bB`D`G=w>hG4#Xob)JJ%RsV>tp`*evw$rZ8yi#mWH1tN@M~rB*5WeVl?}O za$b(CGQZG5$J4|trq>RaYB+dgmx;8I7mn`=HW)Z7nBttQ*x1R&Ub_Gds|F~O?L_{N zqOW*}3F*R=9XQH*46iSh3ha%tSXIAsD^iPC!&(~QDjfYiT+&r^sf$$=$Xh&=SQc?z z#@Gt|wC8|Im0T963!P@$d?C!Sdo$$cw<|Bxx}%ZPph(pI{XBAUl$;41Wj+5$X&%uc zB<{20;yCAsI(eL-K==_A{0hgO3(kv#(xH*9~=J$}z4*_AT<`KG8glYe|K<5hqg~p3N6MA2Njt{hnl7 zb8=05Wp+go5OpYB<7L9X?#X$W1XiJEndBJ$oK)e!VHo_!sG*j`Uj7`Z8y@NdN{Llc z9>hYhg;89p?<=0|y_2v<=HIeu?T`Du{!!8`K|OEL5?nI@kSD-^LRv;S$Q^vI^-_Vb z&D?LLaQ^?D$Nyf5%!eDm>Oekr?_X$nNPjAF-e*wbRa-}IVa>6^Fu ztV)0vInm7xF|>c<20CHep2)n;Z8u0h;5F8Q^xu7G?Ux*UkGS-lj_X0`&5Qi->Xu54 zA;ILCJ9$N15f{(Q;0_bc{1=yAa^(Y4sG zd0!SuDHWh3m#L?T)k?zWC1q3lDqfbY-&5Ji`=ZWEC)X4w?_!H^osVwbM$v4IaxS)< z3T@bn7to5%xkBwQm6eao+&~*E;1pi;2wkJRhX-FkYm)hG&N|ozC+%5_uA-^RXzzgYkdGT0nWx>fRUW-BL^6x+yh7`kI6b8aHB9 z10G%vXS@HrY-g;?VuswwZB^-3W;00NC~3uoV|N{Hql~vLH#WWZTD|TcqqXThKnxPix3r!ACq}OwZlKalp$#~6GBcbK=w;e2%sX_bv@@z{YJ_Uu z-Z{$SH^)f?-oDR@Vn~mQ^=$dY21>P<;T5xh|Fn2~UaaQ4C;2(z%=>g+LFOmMzw0VQqX7NEcP2b+J#*|6fC z?eD{^fp(MMTE3;11QgfVrG&JfE77T`JpM{s3^6|Y_0s&?4On0z1m=Nr$!bxqyPP{? z-q?wXuCrt#Sy(mgw;#lk|0=Oj=o^LE`6eIp@n{U8{_eG{*p1P3Zw*KS(MH_bz8Ssr z$mu_ehg|&xwx?^9SWZOg!EoFwS=kGrfPADw3kA6aB zlG5x%kcM0Sx4Xtnn3`38YuR=EKe|C4L9k!L^mwk>IvY=aiI0@GZ{+H=U$%PgeL-{` znXq0r0v%nJjDap@in|`$tyyy)ThJSo@XyDyX#iJ#MJ~!ccBb-Ls0KH=f?h}CCDU-Z zitmC)I1iaoUS$HMimK7%kgmP1F}R!tv1#4ABE8X*p~gFaqYDqRNYECPQzjHL?oE0+ zMOFhycjc&p|BG{r|HZjGJI7w&MfSsp*O^yP=vdS!Hyt!2OEkA*nFw=?7H=Q0mPv)Q ziD`Jx)2k6zRF`(%LC8x6Rx;@vC3H`NE;3MDCPn?5#>8F|22H`}g47A}Pwvdt`8MO+ z%5~h4nT6&HjNpEc+x@AMXn#lk1h19~eTh7|M#ba`rr>tC(6bb!U8fT4b(K5zE@)AE zHc&Z2LtAa0Ng%+kL~gS}q?X=~4uqM8;anEtTV=al=HGxnIXxrW#k%+PbLDaI+YdPm z!Ge3D5G0VKs;ED9;~?DFbrB`}he%O!Lc#Kd_*^nWI0D zSbc`-;mZ#>FB1^z@_zx*hC(z>ufqS~>MaABe#5r^jgcZDC^bq!5b2N}Axela6$O#h zk<#5Tz#ka&kWNt$=^71Fl+huvu}L|)YXe3-^Zws2{?GI7`(oRRYuCx9nwhMU0w_^`H0H#U)C~pERnuOlGoMHsluz>ZMK%R2{;v17htWP?EJ*7xvqEwR+;Nw)~s$I?(3U zp|R>JQ(OhZ=h%jL$R|$9YtV|mKz<@IS+dBnim6~r*(P(c5x~3nWOlMOZr*VOCG27g zfFR1}B2OuH4DBB+Am%(B&J5+32{_y(?T5B=BQ$V8mhu_3iZ*(>1t82?TE$Fu!uu>S z2=|R3@&yr%wQO2W1}f);FQti~Xe~kwM+cR_P7w>4Ce@7+@s)+-Zk|AN`J#VpyB?$eaA8cfITrw;$rt}c4X@`*030yfWu zo~6Z82e`75___T&o^qi=UOG+6lpvf;-r)+TW!eBRT`q8>yA@=+yW;|{uzPVfIg|`>3l}g1wdit#u{-XwL1NI zw~~vK`g>|tIC~I4+7nUbo?QX^(EV|`z@GBWzoP3~EMrHxrwxN&ZX6c85O~w+oxN~k z9QcW$$ufBL3!h*YRE;sg3cT?lw+>ob_oX&S-3_-UO?FM#1nI0ar;Y5|c-xt3GFWB1 zbZ;Ou#%OYTKUkHVR|_b}O#Q25b}oShohD9j0w=wC6l9K9f%pelTqLpCIC#aJ`$JIlJj9>P{SYZZ#t2jvG zUJMJ(!bIf*H$?o|;0cK}dMqO}9N4QC4M29+$M3^_>gHI$eEq_-$+4QJFX>*?-`{Q` zH{Ad@q>Wv4{OWksRk&!W+4Fs7huTu2QmO271c$?N7OFKrM~{E*vaLM3t{z!Ed76Z0 zK}XXfODI>qI!J@c2+sM=qX48~Bp)+nsIvg%(2g^Kded$2DjTRQSLMR5(&fVZrh6In zb2QP?%KB@cq%(v|J+d^v8=6PmTbmdCQsHeM=pdj%{YZg^MXtOvgea2O)K!83SX#WAiyLVi0DsinVa$seVmw!Kr6qAXVVr? zhcG}KTma4^^Qv{Ns8^%MR=zbld^UkfW@Bmx(?M!*kD=+gUPl@Znb}v}(H6G@VzEzg zwJ@}S9UT&I%OfQy&R&-E(ad`4tesZSXRCSHU;gnwZ>y3sm}lizcyIht&~mP0+S<%G z_y1ef$Lmi9dCvnV0(z#od(}zQk;+HZiPX0P8loZvQ-$F`FbSwh^$|`TNFSOY&;F*P zh3$nCBP+!|;>jtqKoDP)QvXgDw)utnE;<=gvIuTPJ z&r|SB3pSM)@n=hR>VO$wn2?ex;sZ;x-Xum2`$Z)N@N``+w`uoAT73%3i z)r%E;x|p#U)c3$4^?WV_SHk3h=S2NzpY~6N*>!2DE;5JtXsA_jt|NE^hMlHSL6>>fh0Kpfu z6|M^DfgJP0NQaQWR2r_pYYc2#UujbR%zF>?QHwBlX5TMu7qsQjhxoy73~2%%pnRm1 zFWISZ9#Jz9jdqI;7o{Vb-~)l%&M%$+|O4f-R=Y5E#6tLt?$NU!%=U!ug(3+N|RW+GYO|j z@*I#w`{q)GW3K`#r7UEM9lQpp%!OdQgKxNCq%Q0Dn$S84#Z3T*!3kC?U6v#?3=^Kq zb_K)_4tyr+@#oh(4tY1dMhB)V#^#RCy2MHc5kV{mt=8s=RPz zmoF(`aXywMpPsKOL+|LgpTf57@4r4Pp{36&6)V|GCZ7hF4mKnEO!PX& zd(}A}wZxG#O$r=;87VFKVks3VZQ{9A;QW7X{*gH+mwEtX{6A0@@2DF;+RvA>wYwx|YDy)K+cM|+Wz!hm10jn#L5dc;>X zmgNS>tB)1unqE443d~=|J!o|N_`?{ic~V!l>JV}mkWK@E*q!~k<#*XUq9}<-NKx7- ziJ|T5O=D@k$S1f)@(x+cd^_EedItAwd_5Uk1hwK}RRA!f=vL@u9#lV83L4E26ufoG z?%WE`FMYh$a)KP98g{8_N1mZ0PW29tn2g$}itn27YcM=fNyu@k+W$3Yz2{g|w%ycT zQ3X)^>^6qtJCt(X7sn1)%37Hs?`}tRddX$W%gOx?JImPoSs7Quo&T>H1ybM8lS>1{ z`k)-WD%OGSaF@gmxD8`iB!eG-)Awd*gVKz}gAK0gmzdepB_%Z#GBS!boBGL4HC^1s zKG&eN0D#ZWwUSR!?3ZaId4E{56BOo@5+FWiese5Mt@h&^{?&sBYE;Q8-5esq;}0!IqW;o_DZ$zvVj&0 zPs&{Pu1i${O#2av&G)oOk)JB=O|0IHt@De0&5Jt5n7*BR>nVm(&iNOo%dLJKRF53| zMwnTEboo0z5>r)G;l!~&Xn}i2NZ-Jp6K4H7svYg!uFoPl$(APaHxGuH2f{PHBLGzI zm;kassec*lt!C&&ptLKaj7*2aTkO=~8uVej3LfxwW8x(O{-Fhj8+plcRsmBd2?^M~ z6#pk%bqv_(yj#b|bza+_Wau@T_iTLDs}?X4BzI znT~mnhfczBluNdZRZVtz+l>17DgZAXV!AjlQ@h%x{IHsJq|FN?!Egh`O+EtXk``99jEP-dD>LTnX&jrfpC0A&061atxIsxpMhQQf zn@_N|Mbz)d#4AOQ)QWv@J)&}%%7(DLA>rH-jkH)^obY-*jZEmrv4<0ta*Tst_#CDn z7xoMlUCYw{G~CZy|8{{i-87x?9ux6%+?MYg)ZEKZy!h&f411m3ZaZt#7qHOlrzGJH zEz^6fbX7~%M5qa*#&O7#9v8by!;ekydb)&t4)*yFTXB*c*XqwPmAwn(tyQFz*>kvd zVhdiuVsG$^&dv=VL!TRrDpPq9sH1A?o;pEyYHB| zD2$0g+gLvn;O1V%k=6q~cAs6HhE9^sy{1|>N*g{mI5mURgFb%*@7-2&evYX-dk8x~ z`k#!3+T>4>vn7g>t-uGj_hH8KRl|jos40WM zwb%sPWgVZW7c1Bj{%RrH79EZ`YN2dYY|I=%iYFtPx5z-Tsww2MuI}8!O!%!Mpe0oz z2|ZBSdHq&%N}6Tx3(v`xnN#eB;$-VB+2erUcq#5jx}36R_9xB$|;NN^6} zZ~N-tz2-8W82m8XToAR9V4g?(Vp6B!GI;fmzoCWS`k>nsOUG>EdUsfYIjJiKaGL~kWA=PmK3evx6YOC6s_~#u0@Yq6pa`-(u1pwKthNbq z3XZ;X^fET^m%EO1$l1Y_yPLh6FGd_aiuuBi=E0Q6vfh-dS)_?2#K(*5XBrX;V3U!Z zK1w%b?+{$!y%)6kl(##HsMKfZ3Q*yY!`u&=95An=wQDztpIo3f3Havm4dAC6f1F2^ zEGWc!kn5K(A6xOTAc>kmP)EiKDd@BElBC@|NkeEN`sB~@Z-cv56s1cO?_(>PZ)zE zxqQm)e3Zx&J~%L-xtB@Ao$5qL+dgX#LM~XO`vF!e`=!=!0>cL`xNw#f3u%EEs zKnGXrz-cx6`kOd4_<&-hP2vESkM*ahhUbUcg}g>#*zPINi>WRP5YM&ri-G`W^5Ucgsipu9Un(_}}Y04p#paD9CwehmboGXFh{59m{-twt;K- z*#?7i#oVVC5eK`S&YH)V2=~MEfjSbdmG9`iS)*R(#x*xnNE&X&d9(en-lAh?d$Ty= zBpunp|KI5M$SU;;z5{TT%h&@wM5cSoMTdV^etUbxH;wJ%v_{vF_DsbB&@EYG%Eo1) z2Z`-PzD=zIlm71B-1)IIu9QIj{0CcBb!MM#H3>`!t!G`a73FuUVX2Gl7Vz9%x{62S zD*%sHj;ilh&4El6QM7QO*l_)XUU!kc^`l9;%uwV z*gNW}@}FakK&=uhL0E-z8ToS*okn%R7B=r0^E%2qn6W>3a3OSEO)+ zB`0HOIydjXM&33eEtQ1hdPek;%;xl}ch8zv7O(P)Jr!6G$>}d56Y)X1qseD)68uKm zyxeh@Bh^lqnlD5Fba~|OX+uBZThE*Pxnz&8PNL8ovHLh^8OrDBU@@=d6vu-NZpX@* zd+$s*3)K7GwxQn72_F@R|2i&PXSR{4&%2ANoB3g3N%hXC+N-1@;-A~$tEfyNcW^R` zM_36X%6WcfA1}Y;+CsALy~&B(ZriWeJc=Mc%vSL@h{zII8=G7*Z`_~OycqKz4ygQA zr8T^Q8e$JW?(}0UX-w7<9@WDS)Z-3UyAJL_Nvlhaet*Z);?3pQ@npT(FJMV4F>kRm=KdA1B&G~}LYBddbgGwln5c^O>aHX*i$isR4-3xs) z$XEI%U2g0+3fMy05|n)VG(p30Qz&dr!} zp3_DOq@smCO$qK(2>=S&3Z0oCn^SAYe+~21gwtY^<%t`s3`aL&r})(;?&}E9sLx#q zyzxPn?P%rMC%rR0!+dJXQ{IrDcN>*vgY-iB%7fh8Ar23+A`WWYmh$#-n~o6`kfl^J zk(2A2z&!Dbod^XF*k;G<5C4_?wxU4`1#--&&W$5M@Udo1wDk!YdGT)8Wj5>(k8j6> zmRKF@l{5;?oNyv)M}ihxUU(1(eem@>tF9)G58wmw?oh4>5oa)_&vTUM+e9B2xUS82We!36+Ol0&8WEaEr@As>VvMR-nq zyZ_j6f|N&P)wN+L_Da7shE{i3CLq3JE2aJP2(5YkIm_$VRb^FE!qfYaO6RJ>%RiC9 zmxc5}*LY>n;N{-OEzwsqfNaM(lFMRTW%+P%8&;)tA!*=qK3`?0AN%)#@f}X#&@ton4Jf@4#tHyIb%((NY@|h&HhBSazq=AnwR%FPctsQ-w z&vUn}@uyQo9#MXBSI_Pc16N2@Va`_%#*cN3%r1Jf@wMxnv!u|!2)w+;&L|Qc$r3g= zr1Mc*Oc=mbjElKj%gDv_+k9VsNh*{;u~A_G>q{?kGgu1CYQG(3{&Zw>7}&AVqR_uS>>wrq6U&I8B^ zHKPLcC7^yjkg4G2*?4&9B>^|Svi0W{=!Je*Oud=ZtySqqj_T^&04X5%-mSV4O_V4g zcIy*k?OY@WF3?#A9ksT3Sm6rIXCPxR*p^nM+LvQYSq9NH6{?7uBj#g*|+4*gj`Nd)+=jLSg+c~SAW^V$o zDjO$E=0#gt)`>=W0zgF11l}*IUOQV_k*uiuYTq;Jo+adY8>9&hPdFxwE?AM&=+5%JU*Z60tnG*8rIMsLP*iw^qcHTW)qZfAif8(O!@JXFp)1>F1Bvzl8c(`wAYt#Tnf z-PkWRuL3q=2dixgCYr<@eQK`V<*^AOx!&gsfi1@f`43mVLUb!*9&W@lDGw2W!poN2 zC)#GOENX7b)qb=N4&Dv_tws@6K@az0y0k$i#{ULK-a0R416xnFju*#=#7Y_k9B*-X z$DsK`T$kfOx0LUL^;}}Qif)83s|&^iEBg;w*dk*DjmNFN>d9|8sCWdF^+_xY#(_%u zzjqrH(H->E<>{f6@W8jnld*IB!8vmLc69u3S@oV03FGYYBZ9%?cyd+5S}z6~@aI6A z-yrX5Fe+6e$s$uQI>ASM;lC;fIf{?m2TG;B)dH2dI-bMm;G8JkGSmyxE|U!Ym{<&% zw%~H-?J3bnDv=IM*7;#`kvm+mMLt7OxTlA-Cl|4)uQPXTaly6Q+ek;ZghknBGj0p6tQ- zIipraEUy@j4ZT+R%))mZ(PIxu-cIyf=uk7ARm~!JqvR_+Ty`KBgW+1ap=y><*JG+lm$%Q45}0e zN&vc%>`xb5hW+IkogYrv*TE$p{cym(+x3eFY81MJMoMBTm8y204HJu)nk@;H5I?s- z9JYPkEOVVI7cRSbJNI#@#^ZySUxW3spT$ZdUlu#QXL5#|n?Fz3L@esB7qu15Mb<4m zQh}h^V(B(4BhHAqI;KO{eYz7={Du_k6nRB=e#&0@xl%AZ?>&-J8V^FpA{v<^LBkvG z<=li5(xW+YD$H5O36w53bGeUfmyT)el(3xlcyc^J1k`*pk&hR@b8mKievRSs9xnkT z+|*g8_qioB2<6WSt3V-pBYJCUdeYx@!^l?;sD;R5qU7JYcg4Zs$*G+QdzKEP z@l-aE7mq6UcB760Twc3ckc-xK6nKW-r4q`K%N!RKqI12inP6$*EnZj1w2={ZBfHgp zkKwkH=hiABlP_D{eh>7BTBL2jQ;bFMMiHDf(`Vz4>^WOU!fLej9~H`Ye$L(lsmu0r z9CG-_2j76P#?#a#Qz^i?Mnszmp&Grixk;=4WSUyw9nqutl85k5df6KGG4xD}$pOCx z$S!XhJ~oCEd=t$uEaZw=H%!=3nA?0r(Bs2KRzAYKBc-`K6Kv8-WEb#gJ=@wwW*cIl z6%jX-5;inQ`iGT3|K4+KZ7qNqRw&_L#4OWq947!a%Rd8XxIVA~<2Ign#}yl8);z;B zEME}?{{RO%QcOiG5n68{e}B;eUy&kougUbLH2@S@JZj%DM^Qk@+_& zrl&cEyfS1Q-_!L7K!);eyBq+ecMvh8HM(O$%&fLpZTzG0@T45u7D0h@(-m*JdaL&6Em0oYrdC*Kc zV`(e-=dN8V*tkBb6~7rUd$DEuwSG|fc)>qnFlLFzv&&W;+u+4t(a`}0!}5O}REqXK zr#x-qe9ovxTs+#QV!ajN9lQ37lt#RdL&QiOHvwDerO5b!ww3gb0S(l&YpgMAyKB~p z1*;`XSN)E>8NGdExn!pwO2Ey|*?IjTq4l0|M%O~%p#E17H9!Wy#w~oTC|3}#l=N2$ z!})%c+o#1!BQJ{j1u7x)`X<{W)lWp7v_CaeP!tdsSSme*Fndz0arP$P8_n*vHdSwy zkRKLh%I`?80C*U6$r`8ym`1!tYRPI3t8_ZP{NAPTFieyDGW&3zr7;uc z?{=k~TUQHI4Y)gX#lo|FG)%L`g5Y{O)g)WT?co=n>!Z~xkt=I5tX$M@D(vEK**Mc? zhoPW}#RzdMEkjn(uJqO3k}cZ$VZLk|FvlDq4-IyNC6`n)(Oqwg&_ifqK zsdz^GT@xerHov4E?hBQLttDSaPl=bXC;$Y`KK_OlNEZyqn)epRxA=asZZkg1%hZlR)!HHr*rd#$-41)Z&|NigVrtZ2U$Ttk#d99zwgCWA*~5NM1#X35#CSGB5&pg#t-sbJU zo#d>^|?xzxY7H#mw8@*%CI zpv7_g4PL+3pu&W}%v_hm5{*yUM%<2D>LKV}Xr51kz#Ks2iJk_xx3PQM-NZOtuL*4` zcBEIthR=_9hd3AmR9O{09@DM9D-V3uN-bkMDI`zOuj&S}gqcaBl4nNq8bginu&EWB z=oM&6gnlO>r>a3n^9aMlihDK|);Num3L$tjt`ZMLLr#abeP7EgZJ-VdEulg}Lb+}{ zLL~RgQ_SskVVE~e)%qPZCd}njRjw8)Co;xQx@Wk8)z@37>vv!N?z7ECVS_kP@=aAVuu%N8AcB|0(>th82Dvzc$p5<48zj zPgPdRp6k7SVx4IAiz1HC8B6^u(~77O*j(_LTx0Csx~pQy{4v_Ml0Wu62lvA%#C3VD zJ{21mj*;Jr#xKADtqbg@ER-okCFIIZP{BQo?>d5aE#AWt_XDI!!qv6b}I^YNqh|+ zno5!dJsilYWw()F*&%*qv^pL5Q`p;4j}TD18{M`?)e_&z$s#PTHhI$2?Yj2brA9Mo z#G#6fUx!CQ=GvWVuS(zBBzMk@v0e%;O_wTn%f~gRs9mWx&*DNLwLw;FIp^k(oEVT@ zc^>sIj)r0`qqftEi5~QADnJEg=`O3&gczIT{cL8O;do4SZ^%Sf>Qu$i7G3_|+!dKC zK_WPWS^N34M2lUWSYpmXH%QZ>ua+|ua5qxddhOu&lD}`Hh{hUr?4x2rFG8+fPF$#9 zRaB#IRVzdtbi^x;sDEZ-zIG|tS9(=m<-kxy<4Hs)WnO$Gb#*$wX~x`!OT(7*N3ngu zC`+-uLooD*!Dd2T=L;K`^Ve!~LDEiR!8YE;?WHX?ov-a%*D5+^O`%mZ+uMP9hruno zpF=|ArRgSIOKN1JFUbU6W_Xy9T;m_Z#6^e7-lV|ZqIPAB`=$Wkr=m)iniW6RF%{4? zHFO)dmxGEz+AP5eN1U z4Hra;eg0?kpzPa^|EL9yo^ZvX5Br4B{QaC|--4(j#FJQ^rF#Uf%|(N#HZ@)5l(9qY z?;oUEzP9$$_&&rGb$usvUGe$EE4eK0^@racs1Bq~W|Gd3aAfgEef#R!c#LBv(76~<}=d5;f|Cm;Ma&ht->Hs)PUj@bib z$3@!SU$zsjBl*(GWF@m7{4ng$au$l-O&U1u8H?GPP&$EKR&8FtMk3a&scDVrIp((L z66M>wJv$xqvF`soNdK&Dn-@9DTWl!0xn^=)WBoP5t=FxcCsuCHRvEE{;&m!0I`AhG zI0|Ngba&m;Yv-50T@s?$nmR0NKD^oES_FEyQPzd6|wHfNr$ zj!MlWwy3S&P+b1CYtGr_wVY7R7`pf;CC4W%^ZVS9vbpUKSyUIpH&|MajUN412S15$ zb&B?+RS;Ebafo{9^OZ`9XX`DEQ+;irbfU>&hNQ`6vh$~Q@`4tqs%JwTcs=CwB*9YP z!~h%=*8C)|#VHwKElg(L_@M(S_%*|k)ra8Z>3fn;VtZ4$Pz-yIc{47%Ky2=Ikxq;M zWxL;DfagvrP{A={h&J&Dqx8ns!77JQ+c;E!!#I1I8AouukQi@CE1TI18Eg#ABROv8 z{!VOB|J_1{UQ}1S@Q32hthQgvzfPkG^qsbg`Gpm>w;a~II7K#fuvO-fyQc!F^typ4 zx`hcx9kyx1H69k&+Lh`sEksSy7VBY4FreL?-5WRTrPun%NO_p^(RvafxBN))cG z2k#w_otZnZ;Q6C?cY5A?GDlVL;0I?%b*tK<)^yNqf)qMUSJpzm>BXOE;6#=o|*O3w?s&2v^z0=ujHDHE^0`Wc{<)ghdtO2NuN7 zsRU7Ib9|iLMC_kZgc(1oSfg48ny{*5zmjCgubSNDns?Q@+l2LtJn^RAt_dOiyxT0ncx5lu=nM6q|LN-b$*-LlZifeCTT__& z0&(^Bm+KO(S4Zx6?f&8naPfg(4&v|6d&*d#N3m~AsTbTL zf2QkQE&zCsew1VXgg1b+cVe6@&%qnX{awTOF&|j(Y=eApJ)jAGiLTcMr_ZTX+#9KFG>C%ocC@`)Nf_=Aa#H3@ za!uCsrbB%dN9i93Yeo(GA z4i$0w?p|>n-S1BG+5CZEo*0fpO;5~N4d+aN9p%S!8-`E)k3ud!g&pQb{O%xj&pLlN zU)eNS^CPvcHypTJEUV!+MC0nYJt8c!OK7qvZn1#D|fbxC9bE#NS_e?NV4HOV<*&|4$tD(PF*W`pJfbxAS_| zjl+M6I^8uM`ED_&+F4C$V%~bHYYl^{jLT1!>Sl^6JmdX8|6wuq>m;ExC zS^YT2n!nI9ILa^Q_VO);ZYS?z-p^WYkIO?Nu>^VJ@hD^YyMc^7Nac`3mp@DvSKsR` z;B&}A($)_Cl#ig_yC06yF6b*qFRjE6Ls=Q}dmdEwa6))?vg6J}$X3nEH4l$33}y&|7jPV}}Ft$W`nLtH1|4 zd6%orW<%dPC!a?|WVefLtxg_Es2?A@5v>A{>zXfYT&>L#h0dUu=MgsuLa%LLBmy+F z-0HCK5P2*e?wozTq|`wC!l^3Pnqqb`RuY%%1DhZHM04gYfoKf3?cMy2ilbu{}(UH6U`*77aRWa+1T$d6L*7=|SkPT}cH zZybt-OmSje#CwDmoji~%;SQ(3i>Wibo@gdcEf|z?W|B{O=r)o@Rp{bbS`>#Y=NiRC z)59*fjE>pAjNcd6W?%_skVI4c=R>SdP8YlmZB`fCo5I(dR1Q-@>usPa)vrvoP@|68 zk1V`LJKHl(hVMQV93!*TQTx7%Pi^UDlMM65c<_!4#Kg>rOskZTt833=?1;rJ zS&BZUA-~tp_3$McmPhRu%aF~yvMN_fR5T~G9-@xq!=21RDx`7~{ku}Co4vaY`LJvKvoY0wnuTjb= zsd&+7g6?6GklelEbL-54gc2heHrskATtr+;gylKPBBjbL0dzS>O;?<(Z*_VjV6WC} zDbUAlv)*decg4(@G(z(8L(xr3Kskw{z^{5*7oyyn(|ko-M`|Jy=s3MV~b!gD%N>Uwffm7 zzbF0Bv^%4k{uGIZg#8iEiLhTR#V};{8)ds|;#%Lo>(=DwgAI7S^%T;_c6HGwoP?xy zO~~&0SRXLtRh(x#km=o>scJ@PAm*W_?ayg)5sRosk@=f4j8{op_oihLhM5JC(j|Py z%Km5bCAAgJb@rv5CQ*#k-*sOidC*RY=O;NEa>Y1s$NESjD3RtfhEPT>jc1nQu zg5cwIdi?_lyZz|yfsBA+fY~zu<{LWJ@+`Tt+41QhZLSa3!naAGm>Tu4%>K;pZOT( zIQe~4&v#<`o~vM0YCZ)wJq>%Y{s;Rx)k~2;d)2@d*p4mpq2Q4)SG0aNLNU%WtZMUb z*jv2e&OWt%1Kr_qht11*cw)R8w@v-@Er6N(4XWCcO)?r*+g22qH##hkKymESFrAh+ z=3Z*Y{de++xqrVXzR}z7#@EbJFS@=$uMUO|uRavDBfV$FeJ-C+Ft4PZgpJw(TcA(+ zw?C=^L*{c68VZU^yk38CDNq2XIyG=;O4gj_Ox<{-7@96YWps4&$SIvf>RbHcwt}VO z`r@(Yit9Bz;Vp6N^Knl!U7P=?elo%drxs#hvPXWVgjZ5H9}KzlXcbB2%fg!RYmDy3jw=&s|`bS-#Qq@=H@`ax3{x6gd(ebUHb1D+>w(?q$~0@S|HK zFwxm`i6wC*;J5Fd*qOv|&S;2V7x*fKICoC0QXR5_Zj{Y-Ksy(LEOx`q{y%s9HHB*S zz|zu!?%WcV#h>n#o1;14Sad0#x=|UzY})K&tW+n;YfDv%Bp?)dH9cAVM#dFj1 zsB~TVj3>V;K`!(OU4%D- z``+Eoh`4;k1KX;2!k?E#;?i!^ddtaTd%Ah!dg5{nb~u%ix_;@-HkV?iV~MW~a1JZN zt59{-UERdJo{Gmu)v*A6-!8M<1P@8eJR6r#lDP%^;S%*~2i~JhPwxWjezwPn+hP=$ z;NRb`a_7(-&QT&u&#^0H)|WRJZo4(+pEdaJcSf{Sw46D&g;5s#BGRU z06^GZS5u1yF9&ld=LM~&oEwIKU^rX1fC5#5HS{%iMB?w(MU^lKo}_*H#(8G|NE2PLx$*!FAwC|Ym1F2D2Tva-LG!N$FO zAO6;A44MkpXj8*KY?N>FatbKMh~2#f)8*P?1SUlwaRgv9c}MendD@ z|M9mxHxat0>|3Y7E4D0_s6%vOT$%sbn5yii4&Yblned1%E*NxX)PG`=rW_WuaPYnY zcC3hIpMa_a7be3(>#wtzQ*rd9p{~w-%Ja5Q2}Q3O_KiLk8Ld+dYi^0E5!k!M@|-GX z1d*5!3VX2KQ5n}MM`ZpsMGKRJGk9|RS|Y^2{spo=`t3hgTGHT;`r*IbM!woLFnY&jG?#^LjkCc0;lZ7Pe06??2csqd zzfa!rLmUp8X<czc^QSB2-gx3a%Uq- zn8HT%jzV$YM#DR!)v9$HZ`W?U-`z~q;BV%6>=Y;U(DtQ&%n}oQ5V-2I*m$W`v-q{2 z8jl3$4waz>f=1Pg0N|yB1el*jGH9^}qRVYFi77);iR}C+y(Gw6EG;LVeXEe}O=i$q zv@Ef1QK~6|)A)RsK{E2;h^MQ`aCqng>wM!i!a6o@jFW3))j%-0Dz|N86sm@%@Rr5^ zF$+aP5k9r0v1vxX^UlBYTq8Rb5@bKAf4_wFDv#H@j=B-Pa7QZPGQ`pqd@AVGMR>$5RPRhlws=e4KTef{Oke8VO4fmu#}GaqoK(YXb2=;*54t1ILOzUYPSf41Hjr09K`&xnsY{X-g+M#iD)n)N z+(Owk6qDyKT{)4yh0NwX`f9OWU-D)R!k3iQmlrRw9D?ZZJff@teLEQP4 z)WQ1CgRa*^E0SQGxMRH8_ZO1|NAv~db(P-q9F3i$)5r4n6qZTrd(L^~?J1@!duGpF zmodcsGQj(6F0ayl!ZoRX((Se+uF7n(ySkuN`m`msG7zvjCm z#D1o1ie7rYmapd@c^gFq@f*<*61;i3Ibm>-d$>Bso|$UkRH{Q~chJbsA4zYy*C1Pm z=rnqy2lm#duyp7P^Y=O=C=s)_OJ}zOFeR%*W#0)L651}ajMuPu0C;cqM-SMy_)i{M zCGSQ$XT;}#1Tdt)4ML0FEBUpu5&72xjvJ3ZOp4oPx0^S5-**$ox4eiZJf7&w|ARIC z;|sG0LvPX+ZXPd0tFhAPZ=Tf)sR}lDPviUv-@a^4o~#LM24qiDHm&Mel7*&xnLg)} zZaK2@SBGb*rOR&ObJGJxn)gcx`+saRh_aLe-gk`#x_eeW-It{05e{|$3040h!C|sE zV-Z9S(c@;=C(F(2xY6rU*UdP0o$*n*^xA<^5Ds6*E?4^)gMg^_MH{Vq!(FBCdNE3yA zzJOcVFlHs~)vR8Cp>U3O4NApXE zcYSJEeoLR*%D{_o8+^!gJra+Lk8FmzobI-ygisI+JTx+z&4nbT`hO;DI#)nW!?hz! zRY#RkzSW1eerq+s1rIqAz7z19fX~4JzBDIFdp;4ou{z^dhL@!Ab+`8NNWU*&NBc_7 z;%+$7H3txVU;#1MX?&hxO&o6Y8X0AFO0J6EuOBtGGRR}%y3_wE{@P3+6?tW>R5r;Y zxq}nXL#RpH-5)g;QxH{U_VBI-`A%i|C^4=`S2pM#sfzdf6fdx~EyJ6ZD z9^@ARaujAz#;bpBzN_Fz(_$rOQQog@=IJHJ7gi{F<98B#Tw2^rX`d&HyheNVEk8o5 zxdd(5s?1S83MwRIgaTo>!$rpS@$G(b>BX~CIvc2N$>2+lo5n3&~|0CTx*!d893lk-GR==9fv@@c~xEHBk@I&H~jBRcLax|JmY4v+` zPZWt*z>SZ-7PZ$r#wb%gOF)zuKgma``&yb*8jzf64XEG;+^E{gw@jZ(JHixOJj>g; zA&NzO8n~1Sz*O^{e!nMrf&cE8G$%OBx{}{6Dz6Acth=m*P3F>i0D~zj%amK{21q)TkkMV$c>sk#G6q=D;tF!41H>@ zNeqfn3TFr=A;bHg7p4OoNPwQCbhsov2)2^)F)z1Ysb4SD(!ugBJ1!;IsW=gScLS8$QR$rnIja_^FNz5MtP>)3Jl)%rKcbhI3q+-Wxi?{k z@XVTnTnSj|MKf~2#RJL#=*<9H?vYOohb+oYzmi?zExO;y8jS~<{g_Fx6U_^UO_dVskg`tb*Y|;hbdM2$z+{c3&)^*}Twn&3 z{_D@08Xdpp*($U#y=~!Ut_Jo7r0}DcU?U6$Goe+uIcrTd_c8NXpM+sw>S1DTYzIL& z$Rhg&0?Y!V-x#S2&DK~;yrWL)PHs;b6RHO&$boeG25o6%;t>J&SJ!{hWKhB|k7I%< zyuo)o*p(^%cfVQWJRXAn z)Q)@fr$gxeua}>Lyr~}mMMENm703abt2TkcG7JyS#Yo#Ub zPO%BQBmC0`*Q}P!*+GH;r9OGuzNt4Y2bXrbEAPe*JQ-?5kf$ue`%XgKfQq#MZ>@(m z%;>%W_jzbyiiX-@;2`$-u%jzBiqMmE#x?BnZQ6Nab~`SYrIm6*GX)h{yY~5eV78vC z=qCS-U!2o0J2{;6Ux=P~tM$qu60~$n<%I45fp?8oum7>8rGchvR9b-+Mcp7rwD;#V z=W`M5^~q@&Iw9+4kF!4oCVk%ZWiM!xw3DRu6l1WZ`q)0l8(ye$4!?yydsEEhxtG!1 z#4tpeNKOyDC7)?kNs<@F)8Y9$^%&jE4!X9v(PDrrf^r-mt`dngaJJJkv@qx_%9es{IDS&Uzk+*W%%a7}4b-e3gZlNnX*B06_~5$m z;5W0rT&s@pCaf`x8@*N~k(Dpamt#tLBA|>@7-b5_&BGG&yn+ipyW2<+fp32fhX}g?vUO z7VrfpXNTfcp-H|oRq5f&UL|Nls-FuLTiypp06 zsjQGI&d3E4mh;Dzmc;R9#$V|f-P(T=U?}!~syg>aCcOWTZzfjcTF7ma%MuZlSZs4k z2t`G?Of2eclKX9&i9#+jD#>LkQn_V9?yKB$m-{vMx!>6gpY{Fi`#-$SIj`sI^*oQ~ z*1$`(KI?InZ!afJMcgXwqT9AQud=TgfZ}50(wWln)V#7l;M~-++1FE+AEmFbd0SjM znOM_(UVh*HqZux_I)3sH$802u{|T&J)1=46y%<@_hrH#Huy32B^^gC@rnAl+DFGFR z*0LA-`Ju;5NN0&_O)NM2i%p?2j`OQg=BqvR((V@lTGS@%q{a14 zON+yo5~fV6>at9P14Nv&}gu6`js+Rn>*kW?ERPgg0t|;!1 zM9p4Eyv)XvYTr7JJ$QlpbRxo!?dJK!b#_&|G<6Hv=Ld*5*MIbJ>bDG6(}~qRT_Y*~ zirytK2S)@zJ^-xnkuj1?37knDNz-Bu?jb+1B3?to^O;YM>NsMlyn8aTLl9e>c)C=_fu2*9@n}0dw|VUw+3ag6J;{6eVV5a~??WSt0k=s| zNVTEd8MjGFJHO6mhYIRHSi5vcktkC#Ezv4D1I{+}VkzUxP+mY7)SO(_re z?htB(^_11exC2vSRAFL2@J0qHnbCrBPDUJWCM^GP!eGNJxbDqcKX||U( zc94S;b8=j0^N)-(Q?EWM!OsQLGHT#|@bP{ofcJ?YF@x8GR5}NmD+b6SerDF?hNs?}8L8Ln@8EH4m-|3YqPM!Hwe?R` z+}zOjzgg)B2`lY1)Adbmr1R-V9y}x!??rCB`~EanirOzdZtVGsq5K!8KSY~P@!$iQ zc`B>=D&sIeo?FRv7lo%(wY<_cAJ8F*rnW_IvEbeNDS=bZ&`sYmc5(TD6>qjWtM2hY zO0sX%^Zy$6u+H8a{z-pOd1cAgZTslog)2=!n2n2{LHf(_al7#oCz_4@e?$w<4j*~q zU;p&^U0&ds(7VC9H+0x5-#1{%-u7`N&Cd~~wB5r}f8@Ju=`E>-kc6~~$H%g61+^6nE~s5+*3PVO^wgUx?H4YLAve+(t+U=Ufo%++;dpkOz5Lyy;|ahg zxV7(%pMmN?Od}9c4HVA+0T{wf84(uq-wOXqKYCA*#t--eVX&Jf;26(!`b&fD?WLii z$qNL~t*Pna0{oIf`Z;Id!9>@j0AiKLaj*V$A=EoItiZ`dl4djZoAq{Kv!{S@UZtcJ zi1J&(D@Q@n-e!hjv!Tq6?t3>s_W63=j_8mPsyBt{n3Ry8f{(qAa4b>i*OXtg;H|wD zlcrssO)_(fLo%E4cRrW-Pr&$k-`UuTGSR$Llk8jC+wkq3`p2o}8f0(CiMboDDqKw= zbg>0wTnSQoVSa39eSu&&{t~R*S5RLfNzzIyCcPH^)Qy)%^CO zl7d#|Uv8>Xx-2=kY+pyhhdd2hpSmS@U#~X)Rp6pE!@z&3&P=j3x$MLcOI+j7v36cH zqA_9zk+*j;)avx=&o;ctN{_4I_;W)??fL*Zpcq-K2vVpK1});n(-M!1gP~evDMv5> z$TRV`CMtlj+leA(tw^632F3SP&RiPe`EKqHfR2yC*5&u(6)F8>(Q)B`xnJBw)z945 z*njeKJnVVB{>o$2alWT({Q;4z)nX$_Rz^cqJo;d3>1;SxNB3tbz>6>luR#Vb+61C_ zW9acmBt~AYNs~cW6ZQHbN?_bc`o7yLiTd{fYa*`T$8I;qiLpj1iHMQ>j}-KRNI zHNmN^-*q;%ursRh6eQRjZzmD9EEw$F&q52%GdCBkX7Lu_v>W_`9{Gk^PJD4x@ST41xI#G%BYYTy{3zC)_0DODVr z!h1K#DrVB~G|n^WQY0!sHRCmwi8^NUd%5YF`#Q}?0dyY7Prd(v8#_q7nXKy5MDOAg z3)%bt83~$*f@#oz4nFq0tE*ul*Pa~=O!Fj68a&$vM5thXR3I?G_(gjWxudK9fy^CH z8X5-i1OpQ^ZAJJB^~sqv3Ln(uIa&w;{BjtdXyG%A-x-k@=*rf7L5{h|D0z)(nc>#z zAt?ip5n^w+lP#ud!z}9J9LnH+TQ_0pji8#Q7hxKNi`g^b1el!D^Akn#Csoxt#|8z^ z_%CkuUqdnRx`b~V^e?p9)}5~3(p^6xg_&2<1|M*8HfbQn2Ho66AKI7S!)wtncQbw7wr_bFnyEKZCINhkqNg_OH$}0=3oNMwGtrC(5;u-VwL57dmJw?g)-;9TdU7{WnFi( za?2woxMtT8%lLry&T^+T1`oSbB@cnE7woll6pBk`H5a!ovLiP7XH%*tTT3itJ}-bf|??shCy+L4~xg9+4 z9Brq)Lw32L0%DwiPf(TlCRyc1(gN+zrPZy|=e4#+l*TqcUUuIn3!=aRsz?hecm<<6kr-x5;dZfPaULKERYB*eVT?!JvDpyrs}om z0>*#W8Fgdz!^2YIN+V2ObowcnPRRGt;$>^jIK%Of6}<+zJ9{r^$6wGi8M~#0#)o`g zO9f>?=-r>9C1!&>#Ts5I=ZW3RRb1iR0svm$4X)+4D%k)4K&`9b!22jmMV6q5OU6*&S->nVMe$D5s|7Np~j~!Oo)fQ5| zMegtAmcaxAy1B}W-P8O+8sVM5r{LIb1;Ptw3bT%9ElLbCNWKNBfn^(ue*;=V6uEDc!huBBD!Vpc%QpJCn}OG~UoG zc_Dn*(eT{U1gCar0^hiDsAA=YQ>VyzPhL!<%ow#4pX8ptZCs%#23cQ9M=sNc_k3F} zFUcM@*f^^y59dhQu#c$~rwBdyX`zZW=eFSP=uY*{7>yS?l&hn+M$NSng1UiT1AhN* zP>esEHAa?cEKSLW>v;_VFmweT!2Ac!+Wj+9w0Qh0Mh<#^{;HgSG%Zgx-rH+3Tu~~8 zOy>`z^pd&DLtDMa3(1`8tfrNJ#Fd2{{=2HyAWXILau@bv^T9UebSCL;uqr0X%_03G2f z8=h9wy1pG3LU5==vD3d^p#!~(U(i!>w81y1T@BJr&d+q4d7&SpaE zj(*U$q5>(;^>YS0y&^ie_x={m)D%dB2_f*%jCZE+!DXH1E?ldq+H3=q#Zz;v>#Ri_ z?eAq?i#vtzd@yVKD($03w{Ip1v-$Z%;}ILc&#H!V!06Vp)_X^Xt3)#?R`G)~2l=3& zK5wpoLE>}EUKPcj><#Jwl)r!AY9*16A_w63PUai`1VJ6c#CG?mfq}{fz}2r&K4*%_ z-cW%+?O4NQrn(ot%K5!E?UGw0Lz};4jj;0E$ zP8ssjKN!I?e=)y_R9eVC@6{1%hTR=z{QcGyq#;UHU#q(Av0zm>*MV*sG0J$GqhFn6 ztKFgsxpPnOyE$k6K~j)Ztf`CPcw*mljqR7;_H!YEQwHv|xzjo{ciYk}IZ(tl z#cTT#kO@GpT;|caPLM!a+o~zVIrV5XygZ&GD^(|}dN37xrpWqwe-2V2VQpS%6a_z3 zK4Ll`_Ap_6uoAIRGZ*Qpu(;Xc5CI_RJ|m!A+E+l+re+1bT;@k97zt& zY|`9rf?v{DOSVYMjqMd$mwn87%^4NF@$e1NO^z;c0RCXeF34UgIIL!$sRjFD;gJ z{DFWGY$DqbKQCb%1nKvYrWT}2 zBFP+%zX_b7`|3kJZhdGzN5RHVSqz+o=ON?r3<{`5D@k{XN3S zWd!VAW0=^ff;Sf*BV>*U{$M@g^^s4+<7?mI`z9u)t}fyx<3q6R@GetI5S$?&$I=TY zL#RSE0^)0WZwB7_cIm>_F?btOpR&s$Q8n{hA1VS`3GPyTl#`*tvR|*YM}^)SdF;Z8 zrD@nnl$%vrOwV5Z<#H}iwEl7wJXu#{kTul!3UEYlHK&DbS4qa1%!2F(<4+$Nh8f+t z0A0eH)*xucA3-$3ufxBYXEvUxWMrwLn&S{cfzH?Rnb!p36J53N)W3;HJ!kDrQGQIg z*}fgfi6kz2cfPx+PaO{icHA<8j8Ewk`46@zV28;m;aI;Z97 zRW4fy`9%2=2ALT5Oe~(0_je;pGwo;lBv%FmOIorLwI@sY8kOprR#fh;SWc?&6{R!Y zniCiw9Y~$)hAE8kBsYdSett_(bSgF-vuntG`c>S(LAisV=4FoP%~XlPp2Mq#w3|p8 z^)4uqHf|$;?GVzE2pBUzy>j^h?gIT)0d9H&>gb(~bbJ`LW6Z2t!LMvk0tBH-!#y%V58Wy;+$)#fbEPl(F@kRRGy{Z(@cp4#&5FDIMx}Rv$`1SP(XJpY{Nx)5qV{_LC9^2 z<{_?FZ2xLkspy>PqLgWoh4j)_i_zgV4~j}o%3cd|SJ|chIZ-QF?M^tK@spRr#VFE14fvU0`PUgubmtZ0Fk`k5wEe$Q}MB+5Z2@nX@()F3jjz0pb3BR zX{va-sl@n>TayL>+R;R!nmOp~&CBfo>ldvYrwhpqk7!i?v;F@3x3u9K=sN*EGo_xy zj4`TOn-7L0X-nfli6j9M2qgKrHrKvu(G6xj?S$+K&$*rL(i*(1BFl?8c{77xE??Vv z$=>^V`5ljRVu5DD%1$`zC9STmPUIsi#ksfGv)PH5o0ihJ2DGWTYRSN*U2J#9M5t{1 zVZGt@Zb}zzY2{DBCMIqA7Lazj>|_-#2nq&cj@?wt1{V^NvhEhxoHn&oA7u16UxZoq zBJ{El3?GRUzpY*}BG1#gN1C1vQB|mEJ{jtLnq4nio%iq_cANl5iDHYzeE!fkX0{Tm zyZsG_68wwm!-Kt2h=9QfoRcB19q4@$X>3bZQ`>JnR+&=14<4_ii>?A9Q)}aRC5cs; zW>VwprfuMeV3-8)I+~a(^#+h&T{VB(c3Y`MI`H$(ZnO?_sy-|BoF~A5%&$@D_Vc-? zCcV?>%0!RH)|4tf)(Eo0XixvX3S3(Q$2~68b=kE*+EWke_P|%3J>8vdm@+lpcedn49p|jzmCB*wf8orhbs0-axQwfih zCYh4?)iJI$EvMc)*+b86%AA_e-f@%O687JZxrE1dsb>93h2}30Sa65b#U7>!B(y5n z`}}70jlsJ^vQCCB{CkBK4BVTB-+=pZz}T)CUe3YPp5ii|$Y2sVRb^LhN#1$fsBPw) zf5IxGJ%k|3<-;9E@Z23(J5$dObcnHv0>s(D`)~3)elfjlNknPgpAdZ#4-lrUKDwtf zPX5-dOEK~o^<~t;5QTzjHLJ>xJX&bo0sV<{3B}ecNx^k8=Q&IA)8q`ccg@Z5O*&PB ze>RQ+ry>q}(j00Kt*-dGn>DzbS53(B&IX!kL1MqK(Pn;BS372WQgPbbd8HB`8;Ly9 zK)n`I+=_4);-sC;yye2vsSA!5nKG&nmiAm7Z6mZQAQv823-TJs>-AJ#Mbw#~w+6CZ ztC!R9ZnffH6>1x`uRh(?HE+&t&huh*CYf#1?>;gdhYQ+d&cbins$W*%K=mrPIhWon z?F3Elf6QHuMzbdT)c<juX_uLp!Yg(cv zH;eV<@HX7`SDr-pm%!`ox7XR|S}ex;|5`T@dYSp3*DbM8G|hLr1)f1())^1OO8$a= z1Ni1Rp0ZWrl_6&_jNwS?krtO&O{lg{N0h!UCXbHLMs_Z4Q-wc9x^+HjA4V;$TYEbg)r8!i}&V*6J-( z!Gc06*B1kxRsWA2C0Qhs@j)xt||$%DXT;? z3jsvLY^Gd5Jv)ccln0!*r1UoBaEC8aZ)#{{a1bkREJ{(X_%CB?k7?2^LNw-_8s7XN zZymH2O$th_a>e;D>P8!1Bd$HA3THcAN~#}EKmNG{Wg{gezqIu^*X*v;Pgz-Ga(>y^ zgFZ~r^x*IYiLcPwn!>dI=!#B?=}&IIn_X^Y32%HWTYcA>su9!@T6UhhH`zBcuApJD z1Ln{h6R6?3XhZQaS-iFEY=V^P{Tk4!UgtrGsgLtfj{C4L+CZ>f*@ z?&?@`9-yx}5`VR0|DYrRM%VrTJyw<~m)t#uu0S-T1&^$v)gh<`sWHb{ZRMb^PG}NMBDz_{iShZ zqgB-}P3$Xhaqcf2n;R8l0M3Wz?U-^H;6opKEz8<5-m9d|&s;1mRLrS3 zo0TNxoPsYiF_*Z@j2-B2dK+BWv^?7240jU^nR?YW;i8?LSzilVXsk?8+R$#0gS5tK zsqw*(jo0AY+q;GZe$JODLXcLi{k&gJWhCGgbF~3i+hNz5IoV(6u@?Q|Tn{gw!#96%@l9mAyB-ADx6SrS^s74IthE{l>?CtEZ z&_Zq%FJ=>s-lnO$hKvZN_fA4HP}z`ikixa6wIVd2Sme*tIZkt|kpTc~KM#nsm;uHe zuqHsX-kHRjZfHr*tIdtmo`en1Um2eBz(mjZX-ZHOp8@Z?ghs&m?5o&SRlrFLKVm_q zG^(pEA1evW(D3aun*u%T>r!OyLfbH{KNj14@$b98#|9z`8tSdFEoiwu4;?KnL5nia zu1k9l+v4w+2nucIk!EUfk7UQ9B&F&;i>(ic325w(Wb0QY8Xnbk+V~RS*4;4ZNXadf zu_607C#*Zaw0L}Sq5TCdSuo(Q%zX0G9QKsOERx11_-<01^$&zsvH&MjDvl2*@P2b& z1sl81$FWPdhcTpH&Q0C{JsI@@yt;x^iowuoy%@Z5HJG z!Y!_VHQ`ghGgyGo{+~xijzsuS4}QURY!J8c%0n(DWU*^)F#uPGkn%Z2DLnFRGcw7F zn>1Yu$|#gska(jQ>#P9xG2Q%_=>FNOLTjU4c_FAe`$d&j8TTAc7+t;mV0)(s*SGnH zqympE4D7sPK0~Djblh)iJ=&!h9mqx*N$myiw*W15lP>iJd3iX*`fu;4+hgaAkmy`l zw_eE{(#2_O`1bT7q^2vXaiigFuEn&CXvi9wgquR|l=tqxpcR7c4xPKzT66GhWenwT z{zYigBr}r!5!d`dij+_I;$g0pn|AhBXmx^GofR5Un@{3mZw8`^Lt2C&1qX}7hvTiqK@*-j!v z35&HRKZI|HiSbIVRb`to_O16uwBsA2z-{?4i=G^0pj+;)tv^{N$Yno?-eXSkjlE%d ztzt_v+Z%6MaOvFRO~%enD$;_~T^n4#rCRi7VEmuf`FE@Gak|_t0G|l;6yJz*O%I%* z2mn>j9OYP=BEge~?dBs`VlWAuxvN?S+A`hrgvJjy7LaOLDD*2~ z0rzFuM$vC(wBIFdb8+i-QX5tVK4ZgsIyx_hDd+tuwL$key1rR8T(4!IgI~?X?RE0j z7nqUsU$xTS^q`GT2Df68D3;z=7PS0YEeKhO6#dz%$=@InB+-89|Bb`_1jTB2S^kqs zbVwzBZ@c8(P>lN`FwRY)2GZd@=$uGk7khacCwn{Z5A7)q&TMEB^a9EWutU62?;wpj zr2`%sE~@~|$Ybj$hZG}-Aed54mRcX2vs%BcxIwbVw65^#?2d<}n4x}KuZU_b0f+Gu zSL-ppIP24@n;dXobAE?yYLdIBC1q)`+?w>V&_j9fh1Ha7nbYJ!-F5oq>>3lHE46w; z&dxgWg%o$srM1K|cDwY*A%{hE`BSXY)#S(dvZnWyR`T@LC8HTrW9}Ne#H82?2G(AK z9d4s|3SqbuQ$4KMUJ^9CUI~` z6!BhnSbJfO&6ai$IGBPV%XCh!FR8#}mb$ebH(=Cov>o)p zuBOvl*)eC|kuBu3#NPINJ-zx{P)uUQo}{zgKNhzWgm@^-C2i@*2dl!nwY{Rw=sFuvj8AThklSr#KV@j>P1MAid?qo=@fS4N^1JIwm>%d1INFerAKqFf!5XnVne8efn4od4JxNJ1~}fZj8*VM zj)bs-6QWt)o1D(6WXpLac#0QjHqVTSe>pf|pS1OEY*5f*qfym6ek?e7eoIYcfm0Wj zBR57r9wLv=!YGwCuW<)X=x6JBhv3T#4yz^AcIRxeLlDe!Ka2R=%Gtv&_JrePeovs` z`0K-#bt&hf!-c}aG4~=alY}mq_X;Cmv-|3@8mZt($aWEBgs|D}u?OHGa!3+GtMeb! zP>1&rM28T^cV?$k>`7#d9}q*Le)~PekgwpfAufE*P9HQem>gLl>}-pLA!C0yUoI!w z!uvlK34Q%7?~db4Fpug=!cQ+Ix@k#aTx=)wjMSNB2WBSqZg#j!+$^KPKbO$iOjGWO z|5DBGwDgq0w@X@z6Gc4TR7i9z{-oQ^jKb;O=vDgXvC-{=bk#-@M(6U1HT_gMMS28T zIr?|tXUPFe{Pb!{aTrUS>)WkdFbi$qi2b9=IM}H!M*Rn{OQSxIndqR%_DV_+x~b6-B?T%j`vaWuzM>+a~qB>#}|vx;Fx|EGy<+ zqu<9UFtFT>$m|H)U42Eu6^m?Mk?&C*%cYpde2Wdv)g5#zO#r>qFpBXK7#d$^>9+Xl{_N=lJx0@;9ie literal 0 HcmV?d00001 diff --git a/packages/manager/apps/ips/src/components/Breadcrumb/Breadcrumb.tsx b/packages/manager/apps/ips/src/components/Breadcrumb/Breadcrumb.tsx new file mode 100644 index 000000000000..651cb135b490 --- /dev/null +++ b/packages/manager/apps/ips/src/components/Breadcrumb/Breadcrumb.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { OsdsBreadcrumb } from '@ovhcloud/ods-components/react'; +import { + useBreadcrumb, + BreadcrumbItem, +} from '@/hooks/breadcrumb/useBreadcrumb'; +import appConfig from '@/ips.config'; + +export interface BreadcrumbProps { + customRootLabel?: string; + appName?: string; + items?: BreadcrumbItem[]; +} + +function Breadcrumb({ customRootLabel }: BreadcrumbProps): JSX.Element { + const label = customRootLabel || appConfig.rootLabel; + + const breadcrumbItems = useBreadcrumb({ + rootLabel: label, + appName: 'ips', + }); + return ; +} + +export default Breadcrumb; diff --git a/packages/manager/apps/ips/src/components/Error/Error.scss b/packages/manager/apps/ips/src/components/Error/Error.scss new file mode 100644 index 000000000000..c73220cd3be3 --- /dev/null +++ b/packages/manager/apps/ips/src/components/Error/Error.scss @@ -0,0 +1,18 @@ +.manager-error-page { + margin-left: auto; + margin-right: auto; + max-width: 600px; + width: 100%; + display: grid; + height: 100%; + overflow: hidden; + .manager-error-page-image { + img { + width: 100%; + } + } + .manager-error-page-footer { + text-align: right; + overflow: hidden; + } +} diff --git a/packages/manager/apps/ips/src/components/Error/Error.tsx b/packages/manager/apps/ips/src/components/Error/Error.tsx new file mode 100644 index 000000000000..66f2a411a7fa --- /dev/null +++ b/packages/manager/apps/ips/src/components/Error/Error.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { ShellContext } from '@ovh-ux/manager-react-shell-client'; +import { + ErrorMessage, + TRACKING_LABELS, +} from '@ovh-ux/manager-react-components/src/components/'; +import { ErrorBanner } from '@ovh-ux/manager-react-components'; + +interface ErrorObject { + [key: string]: any; +} + +function getTrackingTypology(error: ErrorMessage) { + if (error?.detail?.status && Math.floor(error.detail.status / 100) === 4) { + return [401, 403].includes(error.detail.status) + ? TRACKING_LABELS.UNAUTHORIZED + : TRACKING_LABELS.SERVICE_NOT_FOUND; + } + return TRACKING_LABELS.PAGE_LOAD; +} + +const Errors: React.FC = ({ error }) => { + const navigate = useNavigate(); + const location = useLocation(); + const { shell } = React.useContext(ShellContext); + const { tracking, environment } = shell; + const env = environment.getEnvironment(); + + React.useEffect(() => { + env.then((response) => { + const { applicationName } = response; + const name = `errors::${getTrackingTypology(error)}::${applicationName}`; + tracking.trackPage({ + name, + level2: '81', + type: 'navigation', + page_category: location.pathname, + }); + }); + }, []); + + return ( + navigate(location.pathname, { replace: true })} + onRedirectHome={() => navigate('/', { replace: true })} + /> + ); +}; + +export default Errors; diff --git a/packages/manager/apps/ips/src/components/Loading/Loading.tsx b/packages/manager/apps/ips/src/components/Loading/Loading.tsx new file mode 100644 index 000000000000..1f8e45b3d2ed --- /dev/null +++ b/packages/manager/apps/ips/src/components/Loading/Loading.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { OsdsSpinner } from '@ovhcloud/ods-components/react'; + +export default function Loading() { + return ( +
+
+ +
+
+ ); +} diff --git a/packages/manager/apps/ips/src/data/api/ips.ts b/packages/manager/apps/ips/src/data/api/ips.ts new file mode 100644 index 000000000000..d35d1e2ebab0 --- /dev/null +++ b/packages/manager/apps/ips/src/data/api/ips.ts @@ -0,0 +1,64 @@ +import { fetchIcebergV6, apiClient } from '@ovh-ux/manager-core-api'; + +export type GetipListParams = { + /** Filter the value of campus property (ilike) (alpha) */ + campus: any; + /** Filter the value of description property (like) */ + description: any; + /** Filter resources on IAM tags */ + iamTags: any; + /** Filter the value of ip property (contains or equals) */ + ip: any; + /** Filter the value of isAdditionalIp property (=) (alpha) */ + isAdditionalIp: any; + /** Filter the value of routedTo.serviceName property (like) */ + routedToserviceName: any; + /** Filter the value of type property (=) */ + type: any; + /** Filter the value of version property (=) (alpha) */ + version: any; +}; + +export const getipListQueryKey = ['get/ip']; + +/** + * List the ip.Ip objects : Your OVH IPs + */ +export const getipList = async (params: GetipListParams): Promise => + apiClient.v6.get('/ip', { data: params }); + +export type GetipIpParams = { + /** */ + ip?: any; +}; + +export const getipIpQueryKey = (params: GetipIpParams) => [ + `get/ip/${params.ip}`, +]; + +/** + * Your IP : Get this object properties + */ +export const getipIp = async (params: GetipIpParams): Promise => + apiClient.v6.get(`/ip/${params.ip}`); + +/** + * Get listing with iceberg V6 + */ +export const getListingIcebergV6 = async ({ + pageSize, + page, +}: { + pageSize: number; + page: number; +}) => { + const { data, status, totalCount } = await fetchIcebergV6({ + route: `/ip`, + pageSize, + page, + }); + if (status > 400) { + throw new Error(); + } + return { data, status, totalCount }; +}; diff --git a/packages/manager/apps/ips/src/hooks/breadcrumb/useBreadcrumb.tsx b/packages/manager/apps/ips/src/hooks/breadcrumb/useBreadcrumb.tsx new file mode 100644 index 000000000000..d61852b056a4 --- /dev/null +++ b/packages/manager/apps/ips/src/hooks/breadcrumb/useBreadcrumb.tsx @@ -0,0 +1,49 @@ +import { useEffect, useState, useContext } from 'react'; +import { useLocation } from 'react-router-dom'; +import { ShellContext } from '@ovh-ux/manager-react-shell-client'; + +export type BreadcrumbItem = { + label: string | undefined; + href?: string; +}; + +export interface BreadcrumbProps { + rootLabel?: string; + appName?: string; + projectId?: string; + items?: BreadcrumbItem[]; +} + +export const useBreadcrumb = ({ rootLabel, appName }: BreadcrumbProps) => { + const { shell } = useContext(ShellContext); + const [root, setRoot] = useState([]); + const [paths, setPaths] = useState([]); + const location = useLocation(); + const pathnames = location.pathname.split('/').filter((x) => x); + + useEffect(() => { + const fetchRoot = async () => { + try { + const response = await shell?.navigation.getURL(appName, '#/', {}); + const rootItem = { + label: rootLabel, + href: String(response), + }; + setRoot([rootItem]); + } catch { + // Fetch navigation error + } + }; + fetchRoot(); + }, [rootLabel, appName, shell?.navigation]); + + useEffect(() => { + const pathsTab = pathnames.map((value) => ({ + label: value, + href: `/#/${appName}/${value}`, + })); + setPaths(pathsTab); + }, [location]); + + return [...root, ...paths]; +}; diff --git a/packages/manager/apps/ips/src/hooks/guide/useGuideUtils.tsx b/packages/manager/apps/ips/src/hooks/guide/useGuideUtils.tsx new file mode 100644 index 000000000000..2971e624318e --- /dev/null +++ b/packages/manager/apps/ips/src/hooks/guide/useGuideUtils.tsx @@ -0,0 +1,100 @@ +import { useContext, useEffect, useState } from 'react'; +import { CountryCode } from '@ovh-ux/manager-config'; +import { ShellContext } from '@ovh-ux/manager-react-shell-client'; + +const docUrl = 'https://docs.ovh.com'; + +type GuideLinks = { [key in CountryCode]: string }; + +const GUIDE_LIST: { [guideName: string]: Partial } = { + guideLink1: { + DE: '/update-path', + ES: '/update-path', + IE: '/en/update-path', + IT: '/update-path', + PL: '/update-path', + PT: '/update-path', + FR: '/update-path', + GB: '/update-path', + CA: '/update-path', + QC: '/update-path', + WE: '/update-path', + WS: '/update-path', + US: '/update-path', + }, + guideLink2: { + DE: '/guide-link-2-path', + ES: '/guide-link-2-path', + IE: '/en/guide-link-2-path', + IT: '/guide-link-2-path', + PL: '/guide-link-2-path', + PT: '/guide-link-2-path', + FR: '/guide-link-2-path', + GB: '/guide-link-2-path', + CA: '/update-path', + QC: '/update-path', + WE: '/update-path', + WS: '/update-path', + US: '/update-path', + }, + guideLink3: { + DE: '/guide-link-3-path', + ES: '/guide-link-3-path', + IE: '/en/guide-link-3-path', + IT: '/guide-link-3-path', + PL: '/guide-link-3-path', + PT: '/guide-link-3-path', + FR: '/guide-link-3-path', + GB: '/guide-link-3-path', + CA: '/update-path', + QC: '/update-path', + WE: '/update-path', + WS: '/update-path', + US: '/update-path', + }, + /* + addNewGuideLink : { + DEFAULT: '/guide-link-3-path', + DE: '/guide-link-3-path', + ES: '/guide-link-3-path', + ... + } + */ +}; + +type GetGuideLinkProps = { + name?: string; + subsidiary: CountryCode | string; +}; + +function getGuideListLink({ subsidiary }: GetGuideLinkProps) { + const list: { [guideName: string]: string } = {}; + const keys = Object.entries(GUIDE_LIST); + keys.forEach((key) => { + list[key[0]] = docUrl + GUIDE_LIST[key[0]][subsidiary as CountryCode]; + }); + return list; +} + +interface GuideLinkProps { + [guideName: string]: string; +} + +function useGuideUtils() { + const { shell } = useContext(ShellContext); + const { environment } = shell; + const [list, setList] = useState({}); + + useEffect(() => { + const getSubSidiary = async () => { + const env = await environment.getEnvironment(); + const { ovhSubsidiary } = env.getUser(); + const guideList = getGuideListLink({ subsidiary: ovhSubsidiary }); + setList(guideList); + }; + getSubSidiary(); + }, []); + return list as GuideLinkProps; +} + +export default useGuideUtils; diff --git a/packages/manager/apps/ips/src/index.scss b/packages/manager/apps/ips/src/index.scss new file mode 100644 index 000000000000..65dd5f63a7df --- /dev/null +++ b/packages/manager/apps/ips/src/index.scss @@ -0,0 +1 @@ +@tailwind utilities; diff --git a/packages/manager/apps/ips/src/index.tsx b/packages/manager/apps/ips/src/index.tsx new file mode 100644 index 000000000000..d2e5dc36fa6c --- /dev/null +++ b/packages/manager/apps/ips/src/index.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { + ShellContext, + initShellContext, + initI18n, +} from '@ovh-ux/manager-react-shell-client'; +import App from './App'; +import '@ovhcloud/ods-theme-blue-jeans/dist/index.css'; +import './index.scss'; +import './vite-hmr'; + +import { UNIVERSE, SUB_UNIVERSE, APP_NAME, LEVEL2 } from './tracking.constant'; + +const trackingContext = { + chapter1: UNIVERSE, + chapter2: SUB_UNIVERSE, + chapter3: APP_NAME, + appName: APP_NAME, + pageTheme: UNIVERSE, + level2Config: LEVEL2, +}; + +const init = async (appName: string) => { + const context = await initShellContext(appName, trackingContext); + + await initI18n({ + context, + reloadOnLocaleChange: true, + defaultNS: appName, + ns: ['listing', 'dashboard', 'onboarding'], + }); + + const region = context.environment.getRegion(); + context.shell.tracking.setConfig(region, LEVEL2); + try { + await import(`./config-${region}.js`); + } catch (error) { + // nothing to do + } + + ReactDOM.createRoot(document.getElementById('root')!).render( + + + + + , + ); +}; + +init('ips'); diff --git a/packages/manager/apps/ips/src/ips.config.ts b/packages/manager/apps/ips/src/ips.config.ts new file mode 100644 index 000000000000..1bdac937e1dd --- /dev/null +++ b/packages/manager/apps/ips/src/ips.config.ts @@ -0,0 +1,8 @@ +export default { + listing: { + datagrid: { + serviceKey: 'ip', + }, + }, + rootLabel: 'ips', +}; diff --git a/packages/manager/apps/ips/src/pages/404.tsx b/packages/manager/apps/ips/src/pages/404.tsx new file mode 100644 index 000000000000..d052f1ebcbbf --- /dev/null +++ b/packages/manager/apps/ips/src/pages/404.tsx @@ -0,0 +1,7 @@ +import React from 'react'; + +export default function NotFound() { + // @TODO: add a redirection here in order to catch /:serviceName given from iframe + + return

404 - route not found

; +} diff --git a/packages/manager/apps/ips/src/pages/index.tsx b/packages/manager/apps/ips/src/pages/index.tsx new file mode 100644 index 000000000000..2b1fec26d6fe --- /dev/null +++ b/packages/manager/apps/ips/src/pages/index.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +export default function Ips() { + const { t } = useTranslation('ips'); + + return ( +
+

{t('title')}

+
Start your application
+
+ ); +} diff --git a/packages/manager/apps/ips/src/pages/layout.tsx b/packages/manager/apps/ips/src/pages/layout.tsx new file mode 100644 index 000000000000..81ae31937de8 --- /dev/null +++ b/packages/manager/apps/ips/src/pages/layout.tsx @@ -0,0 +1,31 @@ +import React, { useEffect, useContext } from 'react'; +import { defineCurrentPage } from '@ovh-ux/request-tagger'; +import { Outlet, useLocation, useMatches } from 'react-router-dom'; +import { + useOvhTracking, + useRouteSynchro, + ShellContext, +} from '@ovh-ux/manager-react-shell-client'; + +export default function Layout() { + const location = useLocation(); + const { shell } = useContext(ShellContext); + const matches = useMatches(); + const { trackCurrentPage } = useOvhTracking(); + useRouteSynchro(); + + useEffect(() => { + const match = matches.slice(-1); + defineCurrentPage(`app.ips-${match[0]?.id}`); + }, [location]); + + useEffect(() => { + trackCurrentPage(); + }, [location]); + + useEffect(() => { + shell.ux.hidePreloader(); + }, []); + + return ; +} diff --git a/packages/manager/apps/ips/src/pages/listing/index.tsx b/packages/manager/apps/ips/src/pages/listing/index.tsx new file mode 100644 index 000000000000..7219057e4640 --- /dev/null +++ b/packages/manager/apps/ips/src/pages/listing/index.tsx @@ -0,0 +1,115 @@ +import React, { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useNavigate, useLocation } from 'react-router-dom'; + +import { OsdsLink } from '@ovhcloud/ods-components/react'; +import { ODS_THEME_COLOR_INTENT } from '@ovhcloud/ods-common-theming'; +import { + Datagrid, + DataGridTextCell, + useResourcesIcebergV6, + dataType, + BaseLayout, +} from '@ovh-ux/manager-react-components'; + +import Loading from '@/components/Loading/Loading'; +import ErrorBanner from '@/components/Error/Error'; +import Breadcrumb from '@/components/Breadcrumb/Breadcrumb'; + +import appConfig from '@/ips.config'; + +export default function Listing() { + const myConfig = appConfig; + const serviceKey = myConfig.listing?.datagrid?.serviceKey; + const [columns, setColumns] = useState([]); + const navigate = useNavigate(); + const location = useLocation(); + const { t } = useTranslation('listing'); + const { + flattenData, + isError, + error, + totalCount, + hasNextPage, + fetchNextPage, + isLoading, + status, + sorting, + setSorting, + pageIndex, + } = useResourcesIcebergV6({ + route: `/ip`, + queryKey: ['ips', `/ip`], + }); + + const navigateToDashboard = (label: string) => { + const path = + location.pathname.indexOf('pci') > -1 ? `${location.pathname}/` : '/'; + navigate(`${path}${label}`); + }; + + useEffect(() => { + if (columns && status === 'success' && flattenData?.length > 0) { + const newColumns = Object.keys(flattenData[0]) + .filter((element) => element !== 'iam') + .map((element) => ({ + id: element, + label: element, + type: 'string', + cell: (props: any) => { + const label = props[element] as string; + if (typeof label === 'string' || typeof label === 'number') { + if (serviceKey === element) + return ( + + navigateToDashboard(label)} + > + {label} + + + ); + return {label}; + } + return
-
; + }, + })); + setColumns(newColumns); + } + }, [flattenData]); + + if (isError) { + return ; + } + + if (isLoading && pageIndex === 1) { + return ( +
+ +
+ ); + } + + const header = { + title: t('title'), + }; + + return ( + } header={header}> + + {columns && flattenData && ( + + )} + + + ); +} diff --git a/packages/manager/apps/ips/src/pages/onboarding/index.scss b/packages/manager/apps/ips/src/pages/onboarding/index.scss new file mode 100644 index 000000000000..995e5c3b0be6 --- /dev/null +++ b/packages/manager/apps/ips/src/pages/onboarding/index.scss @@ -0,0 +1,10 @@ +.tile-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + grid-gap: 30px; + padding-top: 3rem; + + @media (min-width: 1024px) { + grid-template-columns: repeat(3, 1fr); + } +} diff --git a/packages/manager/apps/ips/src/pages/onboarding/index.tsx b/packages/manager/apps/ips/src/pages/onboarding/index.tsx new file mode 100644 index 000000000000..93e59c16a128 --- /dev/null +++ b/packages/manager/apps/ips/src/pages/onboarding/index.tsx @@ -0,0 +1,66 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { Card, OnboardingLayout } from '@ovh-ux/manager-react-components'; +import useGuideUtils from '@/hooks/guide/useGuideUtils'; +import Breadcrumb from '@/components/Breadcrumb/Breadcrumb'; +import onboardingImgSrc from './onboarding-img.png'; + +export default function Onboarding() { + const { t } = useTranslation('onboarding'); + const link = useGuideUtils(); + + const tileList = [ + { + id: 1, + texts: { + title: t('guide1Title'), + description: t('guide1Description'), + category: t('guideCategory'), + }, + href: link?.guideLink1, + }, + { + id: 2, + texts: { + title: t('guide2Title'), + description: t('guide2Description'), + category: t('guideCategory'), + }, + href: link?.guideLink2, + }, + { + id: 3, + texts: { + title: t('guide3Title'), + description: t('guide3Description'), + category: t('guideCategory'), + }, + href: link?.guideLink3, + }, + ]; + + const title: string = t('title'); + const description: string = t('description'); + const imgSrc = { + src: onboardingImgSrc, + }; + + return ( + <> + + + {tileList.map((tile) => ( + + ))} + + + ); +} diff --git a/packages/manager/apps/ips/src/pages/onboarding/onboarding-img.png b/packages/manager/apps/ips/src/pages/onboarding/onboarding-img.png new file mode 100644 index 0000000000000000000000000000000000000000..1ac8d6473c95008e4a957539d3567870852b874e GIT binary patch literal 8250 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 KUaD&K;y(a7f*-B` literal 0 HcmV?d00001 diff --git a/packages/manager/apps/ips/src/routes/routes.constant.ts b/packages/manager/apps/ips/src/routes/routes.constant.ts new file mode 100644 index 000000000000..a685b2c8fbb4 --- /dev/null +++ b/packages/manager/apps/ips/src/routes/routes.constant.ts @@ -0,0 +1,11 @@ +export const subRoutes = { + root: '/ip', + onboarding: 'onboarding', + order: 'order', +}; + +export const urls = { + root: subRoutes.root, + onboarding: `${subRoutes.root}/${subRoutes.onboarding}`, + listing: `${subRoutes.root}`, +}; diff --git a/packages/manager/apps/ips/src/routes/routes.tsx b/packages/manager/apps/ips/src/routes/routes.tsx new file mode 100644 index 000000000000..3c538fa5cfd2 --- /dev/null +++ b/packages/manager/apps/ips/src/routes/routes.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { RouteObject } from 'react-router-dom'; +import { PageType } from '@ovh-ux/manager-react-shell-client'; +import NotFound from '@/pages/404'; +import { urls } from '@/routes/routes.constant'; + +const lazyRouteConfig = (importFn: CallableFunction): Partial => { + return { + lazy: async () => { + const { default: moduleDefault, ...moduleExports } = await importFn(); + return { + Component: moduleDefault, + ...moduleExports, + }; + }, + }; +}; + +export const Routes: any = [ + { + path: '/', + ...lazyRouteConfig(() => import('@/pages/layout')), + children: [ + { + id: 'listing', + path: urls.listing, + ...lazyRouteConfig(() => import('@/pages/listing')), + handle: { + tracking: { + pageName: 'listing', + pageType: PageType.listing, + }, + }, + }, + { + id: 'onboarding', + path: urls.onboarding, + ...lazyRouteConfig(() => import('@/pages/onboarding')), + handle: { + tracking: { + pageName: 'onboarding', + pageType: PageType.onboarding, + }, + }, + }, + ], + }, + { + path: '*', + element: , + }, +]; diff --git a/packages/manager/apps/ips/src/tracking.constant.ts b/packages/manager/apps/ips/src/tracking.constant.ts new file mode 100644 index 000000000000..77ba71b30117 --- /dev/null +++ b/packages/manager/apps/ips/src/tracking.constant.ts @@ -0,0 +1,20 @@ +export const LEVEL2 = { + EU: { + config: { + level2: '81', + }, + }, + CA: { + config: { + level2: '81', + }, + }, + US: { + config: { + level2: '81', + }, + }, +}; +export const UNIVERSE = 'Dedicated'; +export const SUB_UNIVERSE = 'Network'; +export const APP_NAME = 'ips'; diff --git a/packages/manager/apps/ips/src/vite-hmr.ts b/packages/manager/apps/ips/src/vite-hmr.ts new file mode 100644 index 000000000000..473d87630039 --- /dev/null +++ b/packages/manager/apps/ips/src/vite-hmr.ts @@ -0,0 +1,5 @@ +if (import.meta.hot) { + import.meta.hot.on('iframe-reload', () => { + window.location.reload(); + }); +} diff --git a/packages/manager/apps/ips/tailwind.config.js b/packages/manager/apps/ips/tailwind.config.js new file mode 100644 index 000000000000..657ab11bb87d --- /dev/null +++ b/packages/manager/apps/ips/tailwind.config.js @@ -0,0 +1,14 @@ +import path from 'path'; +import config from '@ovh-ux/manager-tailwind-config'; + +/** @type {import('tailwindcss').Config} */ +module.exports = { + ...config, + content: [ + './src/**/*.{js,jsx,ts,tsx}', + path.join( + path.dirname(require.resolve('@ovh-ux/manager-react-components')), + '**/*.{js,jsx,ts,tsx}', + ), + ], +}; diff --git a/packages/manager/apps/ips/tsconfig.json b/packages/manager/apps/ips/tsconfig.json new file mode 100644 index 000000000000..e2104f471575 --- /dev/null +++ b/packages/manager/apps/ips/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "lib": ["dom", "es2020"], + "noEmit": true, + "target": "es2020", + "types": ["vite/client", "node"], + "module": "ES2020", + "moduleResolution": "node", + "removeComments": true, + "outDir": "dist", + "esModuleInterop": true, + "isolatedModules": true, + "allowSyntheticDefaultImports": true, + "skipLibCheck": true, + "noImplicitAny": true, + "declaration": true, + "resolveJsonModule": true, + "allowJs": true, + "jsx": "react", + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["src"], + "exclude": ["node_modules", "dist", "types", "src/__tests__"] +} diff --git a/packages/manager/apps/ips/vite.config.mjs b/packages/manager/apps/ips/vite.config.mjs new file mode 100644 index 000000000000..f33ab6dc98cd --- /dev/null +++ b/packages/manager/apps/ips/vite.config.mjs @@ -0,0 +1,8 @@ +import { defineConfig } from 'vite'; +import { getBaseConfig } from '@ovh-ux/manager-vite-config'; +import { resolve } from 'path'; + +export default defineConfig({ + ...getBaseConfig(), + root: resolve(process.cwd()), +});