From 80da8ef2ea078211b366e4b9d25512077df3f35d Mon Sep 17 00:00:00 2001 From: Michael Malinowski Date: Tue, 14 Apr 2026 15:57:41 -0400 Subject: [PATCH 01/41] initial dashboard port --- anms-ui/angular-port/anms-ui/.editorconfig | 17 ++ anms-ui/angular-port/anms-ui/.gitignore | 44 ++++ anms-ui/angular-port/anms-ui/.prettierrc | 12 + anms-ui/angular-port/anms-ui/README.md | 0 anms-ui/angular-port/anms-ui/angular.json | 80 ++++++ anms-ui/angular-port/anms-ui/package.json | 40 +++ anms-ui/angular-port/anms-ui/proxy.conf.json | 62 +++++ .../angular-port/anms-ui/public/favicon.png | Bin 0 -> 777 bytes .../anms-ui/src/app/app.config.ts | 13 + anms-ui/angular-port/anms-ui/src/app/app.css | 0 anms-ui/angular-port/anms-ui/src/app/app.html | 1 + .../anms-ui/src/app/app.routes.ts | 55 +++++ .../angular-port/anms-ui/src/app/app.spec.ts | 23 ++ anms-ui/angular-port/anms-ui/src/app/app.ts | 12 + .../anms-ui/src/app/features/adms/adms.css | 0 .../anms-ui/src/app/features/adms/adms.html | 111 +++++++++ .../src/app/features/adms/adms.spec.ts | 22 ++ .../anms-ui/src/app/features/adms/adms.ts | 42 ++++ .../app/features/breadcrumb/breadcrumb.css | 0 .../app/features/breadcrumb/breadcrumb.html | 11 + .../features/breadcrumb/breadcrumb.spec.ts | 22 ++ .../src/app/features/breadcrumb/breadcrumb.ts | 26 ++ .../dashboard-home/dashboard-home.css | 18 ++ .../dashboard-home/dashboard-home.html | 129 ++++++++++ .../dashboard-home/dashboard-home.spec.ts | 22 ++ .../dashboard-home/dashboard-home.ts | 18 ++ .../src/app/features/dashboard/dashboard.css | 1 + .../src/app/features/dashboard/dashboard.html | 22 ++ .../app/features/dashboard/dashboard.spec.ts | 22 ++ .../src/app/features/dashboard/dashboard.ts | 38 +++ .../anms-ui/src/app/features/help/help.css | 0 .../anms-ui/src/app/features/help/help.html | 34 +++ .../src/app/features/help/help.spec.ts | 22 ++ .../anms-ui/src/app/features/help/help.ts | 35 +++ .../agents/agent-modal/agent-modal.css | 17 ++ .../agents/agent-modal/agent-modal.html | 69 ++++++ .../agents/agent-modal/agent-modal.spec.ts | 22 ++ .../agents/agent-modal/agent-modal.ts | 63 +++++ .../app/features/management/agents/agents.css | 13 + .../features/management/agents/agents.html | 136 +++++++++++ .../features/management/agents/agents.spec.ts | 22 ++ .../app/features/management/agents/agents.ts | 118 +++++++++ .../features/management/agents/crud/crud.css | 0 .../features/management/agents/crud/crud.html | 43 ++++ .../management/agents/crud/crud.spec.ts | 22 ++ .../features/management/agents/crud/crud.ts | 74 ++++++ .../management/agents/reports/reports.css | 12 + .../management/agents/reports/reports.html | 73 ++++++ .../management/agents/reports/reports.spec.ts | 22 ++ .../management/agents/reports/reports.ts | 120 +++++++++ .../features/management/builder/builder.css | 0 .../features/management/builder/builder.html | 1 + .../management/builder/builder.spec.ts | 22 ++ .../features/management/builder/builder.ts | 9 + .../features/management/monitor/monitor.css | 5 + .../features/management/monitor/monitor.html | 8 + .../management/monitor/monitor.spec.ts | 22 ++ .../features/management/monitor/monitor.ts | 9 + .../page-not-found/page-not-found.css | 4 + .../page-not-found/page-not-found.html | 6 + .../page-not-found/page-not-found.spec.ts | 22 ++ .../features/page-not-found/page-not-found.ts | 14 ++ .../src/app/features/status/status.css | 0 .../src/app/features/status/status.html | 51 ++++ .../src/app/features/status/status.spec.ts | 22 ++ .../anms-ui/src/app/features/status/status.ts | 27 +++ .../anms-ui/src/app/layout/header/header.css | 7 + .../anms-ui/src/app/layout/header/header.html | 13 + .../src/app/layout/header/header.spec.ts | 22 ++ .../anms-ui/src/app/layout/header/header.ts | 20 ++ .../src/app/layout/sidebar/sidebar.css | 29 +++ .../src/app/layout/sidebar/sidebar.html | 113 +++++++++ .../src/app/layout/sidebar/sidebar.spec.ts | 22 ++ .../anms-ui/src/app/layout/sidebar/sidebar.ts | 45 ++++ .../anms-ui/src/app/shared/api-adm.service.ts | 44 ++++ .../anms-ui/src/app/shared/api-adm.spec.ts | 16 ++ .../anms-ui/src/app/shared/api.service.ts | 229 ++++++++++++++++++ .../anms-ui/src/app/shared/api.spec.ts | 16 ++ .../anms-ui/src/app/shared/constants.ts | 12 + .../src/app/shared/data-share-service.spec.ts | 16 ++ .../src/app/shared/data-share.service.ts | 20 ++ .../anms-ui/src/app/shared/routes.enum.ts | 10 + .../anms-ui/src/app/shared/utils.spec.ts | 16 ++ .../anms-ui/src/app/shared/utils.ts | 16 ++ .../src/app/store/modules/adm-service.spec.ts | 16 ++ .../src/app/store/modules/adm-service.ts | 160 ++++++++++++ .../src/app/store/modules/agents.service.ts | 131 ++++++++++ .../src/app/store/modules/agents.spec.ts | 16 ++ .../store/modules/service-status.service.ts | 161 ++++++++++++ .../app/store/modules/service-status.spec.ts | 17 ++ .../flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2 | Bin 0 -> 104732 bytes .../environments/environment.development.ts | 12 + .../anms-ui/src/environments/environment.ts | 12 + anms-ui/angular-port/anms-ui/src/favicon.png | Bin 0 -> 777 bytes anms-ui/angular-port/anms-ui/src/index.html | 14 ++ anms-ui/angular-port/anms-ui/src/main.ts | 6 + .../anms-ui/src/material-theme.scss | 38 +++ anms-ui/angular-port/anms-ui/src/styles.css | 38 +++ .../angular-port/anms-ui/tsconfig.app.json | 15 ++ anms-ui/angular-port/anms-ui/tsconfig.json | 33 +++ .../angular-port/anms-ui/tsconfig.spec.json | 15 ++ 101 files changed, 3352 insertions(+) create mode 100644 anms-ui/angular-port/anms-ui/.editorconfig create mode 100644 anms-ui/angular-port/anms-ui/.gitignore create mode 100644 anms-ui/angular-port/anms-ui/.prettierrc create mode 100644 anms-ui/angular-port/anms-ui/README.md create mode 100644 anms-ui/angular-port/anms-ui/angular.json create mode 100644 anms-ui/angular-port/anms-ui/package.json create mode 100644 anms-ui/angular-port/anms-ui/proxy.conf.json create mode 100644 anms-ui/angular-port/anms-ui/public/favicon.png create mode 100644 anms-ui/angular-port/anms-ui/src/app/app.config.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/app.css create mode 100644 anms-ui/angular-port/anms-ui/src/app/app.html create mode 100644 anms-ui/angular-port/anms-ui/src/app/app.routes.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/app.spec.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/app.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/adms/adms.css create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/adms/adms.html create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/adms/adms.spec.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/adms/adms.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/breadcrumb/breadcrumb.css create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/breadcrumb/breadcrumb.html create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/breadcrumb/breadcrumb.spec.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/breadcrumb/breadcrumb.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/dashboard/components/dashboard-home/dashboard-home.css create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/dashboard/components/dashboard-home/dashboard-home.html create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/dashboard/components/dashboard-home/dashboard-home.spec.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/dashboard/components/dashboard-home/dashboard-home.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/dashboard/dashboard.css create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/dashboard/dashboard.html create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/dashboard/dashboard.spec.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/dashboard/dashboard.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/help/help.css create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/help/help.html create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/help/help.spec.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/help/help.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/management/agents/agent-modal/agent-modal.css create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/management/agents/agent-modal/agent-modal.html create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/management/agents/agent-modal/agent-modal.spec.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/management/agents/agent-modal/agent-modal.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/management/agents/agents.css create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/management/agents/agents.html create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/management/agents/agents.spec.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/management/agents/agents.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/management/agents/crud/crud.css create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/management/agents/crud/crud.html create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/management/agents/crud/crud.spec.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/management/agents/crud/crud.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/management/agents/reports/reports.css create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/management/agents/reports/reports.html create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/management/agents/reports/reports.spec.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/management/agents/reports/reports.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/management/builder/builder.css create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/management/builder/builder.html create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/management/builder/builder.spec.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/management/builder/builder.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/management/monitor/monitor.css create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/management/monitor/monitor.html create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/management/monitor/monitor.spec.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/management/monitor/monitor.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/page-not-found/page-not-found.css create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/page-not-found/page-not-found.html create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/page-not-found/page-not-found.spec.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/page-not-found/page-not-found.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/status/status.css create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/status/status.html create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/status/status.spec.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/features/status/status.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/layout/header/header.css create mode 100644 anms-ui/angular-port/anms-ui/src/app/layout/header/header.html create mode 100644 anms-ui/angular-port/anms-ui/src/app/layout/header/header.spec.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/layout/header/header.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/layout/sidebar/sidebar.css create mode 100644 anms-ui/angular-port/anms-ui/src/app/layout/sidebar/sidebar.html create mode 100644 anms-ui/angular-port/anms-ui/src/app/layout/sidebar/sidebar.spec.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/layout/sidebar/sidebar.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/shared/api-adm.service.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/shared/api-adm.spec.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/shared/api.service.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/shared/api.spec.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/shared/constants.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/shared/data-share-service.spec.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/shared/data-share.service.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/shared/routes.enum.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/shared/utils.spec.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/shared/utils.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/store/modules/adm-service.spec.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/store/modules/adm-service.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/store/modules/agents.service.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/store/modules/agents.spec.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/store/modules/service-status.service.ts create mode 100644 anms-ui/angular-port/anms-ui/src/app/store/modules/service-status.spec.ts create mode 100644 anms-ui/angular-port/anms-ui/src/assets/fonts/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2 create mode 100644 anms-ui/angular-port/anms-ui/src/environments/environment.development.ts create mode 100644 anms-ui/angular-port/anms-ui/src/environments/environment.ts create mode 100644 anms-ui/angular-port/anms-ui/src/favicon.png create mode 100644 anms-ui/angular-port/anms-ui/src/index.html create mode 100644 anms-ui/angular-port/anms-ui/src/main.ts create mode 100644 anms-ui/angular-port/anms-ui/src/material-theme.scss create mode 100644 anms-ui/angular-port/anms-ui/src/styles.css create mode 100644 anms-ui/angular-port/anms-ui/tsconfig.app.json create mode 100644 anms-ui/angular-port/anms-ui/tsconfig.json create mode 100644 anms-ui/angular-port/anms-ui/tsconfig.spec.json diff --git a/anms-ui/angular-port/anms-ui/.editorconfig b/anms-ui/angular-port/anms-ui/.editorconfig new file mode 100644 index 00000000..f166060d --- /dev/null +++ b/anms-ui/angular-port/anms-ui/.editorconfig @@ -0,0 +1,17 @@ +# Editor configuration, see https://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.ts] +quote_type = single +ij_typescript_use_double_quotes = false + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/anms-ui/angular-port/anms-ui/.gitignore b/anms-ui/angular-port/anms-ui/.gitignore new file mode 100644 index 00000000..854acd5f --- /dev/null +++ b/anms-ui/angular-port/anms-ui/.gitignore @@ -0,0 +1,44 @@ +# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files. + +# Compiled output +/dist +/tmp +/out-tsc +/bazel-out + +# Node +/node_modules +npm-debug.log +yarn-error.log + +# IDEs and editors +.idea/ +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# Visual Studio Code +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/mcp.json +.history/* + +# Miscellaneous +/.angular/cache +.sass-cache/ +/connect.lock +/coverage +/libpeerconnection.log +testem.log +/typings +__screenshots__/ + +# System files +.DS_Store +Thumbs.db diff --git a/anms-ui/angular-port/anms-ui/.prettierrc b/anms-ui/angular-port/anms-ui/.prettierrc new file mode 100644 index 00000000..d6c16d7e --- /dev/null +++ b/anms-ui/angular-port/anms-ui/.prettierrc @@ -0,0 +1,12 @@ +{ + "printWidth": 100, + "singleQuote": true, + "overrides": [ + { + "files": "*.html", + "options": { + "parser": "angular" + } + } + ] +} diff --git a/anms-ui/angular-port/anms-ui/README.md b/anms-ui/angular-port/anms-ui/README.md new file mode 100644 index 00000000..e69de29b diff --git a/anms-ui/angular-port/anms-ui/angular.json b/anms-ui/angular-port/anms-ui/angular.json new file mode 100644 index 00000000..1a32f409 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/angular.json @@ -0,0 +1,80 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "cli": { + "packageManager": "npm" + }, + "newProjectRoot": "projects", + "projects": { + "anms-ui": { + "projectType": "application", + "schematics": {}, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular/build:application", + "options": { + "browser": "src/main.ts", + "tsConfig": "tsconfig.app.json", + "assets": [ + { + "glob": "**/*", + "input": "public" + } + ], + "styles": ["src/material-theme.scss", "src/styles.css"] + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kB", + "maximumError": "1MB" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "4kB", + "maximumError": "8kB" + } + ], + "outputHashing": "all" + }, + "development": { + "optimization": false, + "extractLicenses": false, + "sourceMap": true, + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.development.ts" + } + ] + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular/build:dev-server", + "options": { + "proxyConfig": "proxy.conf.json" + }, + "configurations": { + "production": { + "buildTarget": "anms-ui:build:production" + }, + "development": { + "buildTarget": "anms-ui:build:development" + } + }, + "defaultConfiguration": "development" + }, + "test": { + "builder": "@angular/build:unit-test" + } + } + } + } +} diff --git a/anms-ui/angular-port/anms-ui/package.json b/anms-ui/angular-port/anms-ui/package.json new file mode 100644 index 00000000..d4597ac7 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/package.json @@ -0,0 +1,40 @@ +{ + "name": "anms-ui", + "version": "0.0.0", + "scripts": { + "ng": "ng", + "start": "ng serve", + "build": "ng build", + "watch": "ng build --watch --configuration development", + "test": "ng test" + }, + "private": true, + "packageManager": "npm@10.9.0", + "dependencies": { + "@angular/cdk": "^21.2.1", + "@angular/common": "^21.2.0", + "@angular/compiler": "^21.2.0", + "@angular/core": "^21.2.0", + "@angular/forms": "^21.2.0", + "@angular/material": "^21.2.1", + "@angular/platform-browser": "^21.2.0", + "@angular/router": "^21.2.0", + "@types/lodash": "^4.17.24", + "bootstrap": "^5.3.8", + "bootstrap-icons": "^1.13.1", + "lodash": "^4.17.23", + "material-design-icons-iconfont": "^6.7.0", + "ngx-toastr": "^20.0.5", + "rxjs": "~7.8.0", + "tslib": "^2.3.0" + }, + "devDependencies": { + "@angular/build": "^21.2.1", + "@angular/cli": "^21.2.1", + "@angular/compiler-cli": "^21.2.0", + "jsdom": "^28.0.0", + "prettier": "^3.8.1", + "typescript": "~5.9.2", + "vitest": "^4.0.8" + } +} diff --git a/anms-ui/angular-port/anms-ui/proxy.conf.json b/anms-ui/angular-port/anms-ui/proxy.conf.json new file mode 100644 index 00000000..0e2542be --- /dev/null +++ b/anms-ui/angular-port/anms-ui/proxy.conf.json @@ -0,0 +1,62 @@ +{ + "/authn": { + "target": "http://anms-test:8084", + "secure": false, + "changeOrigin": true, + "logLevel": "debug" + }, + "/api": { + "target": "http://anms-test:8084", + "secure": false, + "changeOrigin": true, + "logLevel": "debug" + }, + "/agents": { + "target": "http://anms-test:8084", + "secure": false, + "changeOrigin": true, + "logLevel": "debug" + }, + "/adms": { + "target": "http://anms-test:5555", + "secure": false, + "changeOrigin": true, + "logLevel": "debug" + }, + "/core": { + "target": "http://anms-test:8084", + "secure": false, + "changeOrigin": true, + "logLevel": "debug" + }, + "/grafana": { + "target": "http://anms-test:8084", + "secure": false, + "changeOrigin": true, + "logLevel": "debug" + }, + "/docs": { + "target": "http://anms-test:5555", + "secure": false, + "changeOrigin": true, + "logLevel": "debug" + }, + "/release": { + "target": "http://anms-test:5555", + "secure": false, + "changeOrigin": true, + "logLevel": "debug" + }, + "/openapi.json": { + "target": "http://anms-test:5555", + "secure": false, + "changeOrigin": true, + "logLevel": "debug" + }, + "/authn/": { + "target": "http://anms-test:8084", + "secure": false, + "changeOrigin": true, + "logLevel": "debug" + } +} diff --git a/anms-ui/angular-port/anms-ui/public/favicon.png b/anms-ui/angular-port/anms-ui/public/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..39d32dbada06c04fd1ef6253a9439ef7661c829d GIT binary patch literal 777 zcmV+k1NQuhP)&$2OFH`ih~xy|6C{WOBsl@e30O*K;@n_CqE9+XW~8ySSnGJd4oDtp<*+=PH`lL@p#LXU=6x$8BM3gBZ!5ZBOWUI&4y zD8$HZVh!;5cED6Jmjn`WhmkG_>SrP!$^>XX&PxWG(vEPVsnLwwC$7T++pLh%p+;xN zijgNI?cW)OP^L@ZwM2`YtP>W>RAwzqVwAAZTF-{-fSK%tWYJLwPHXnrpQgI!bf*o9!uNvuv}lzjM(2&Ar#l27!Pb_Zc>LE~K4Gg=fPKIrykw( diff --git a/anms-ui/angular-port/anms-ui/src/app/app.routes.ts b/anms-ui/angular-port/anms-ui/src/app/app.routes.ts new file mode 100644 index 00000000..de5423f2 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/app.routes.ts @@ -0,0 +1,55 @@ +import { Routes } from '@angular/router'; +import {Dashboard} from './features/dashboard/dashboard'; +import {Help} from './features/help/help'; +import {DashboardHome} from './features/dashboard/components/dashboard-home/dashboard-home'; +import {Monitor} from './features/management/monitor/monitor'; +import {Agents} from './features/management/agents/agents'; +import {Builder} from './features/management/builder/builder'; +import {Status} from './features/status/status'; +import {Adms} from './features/adms/adms'; +import {PageNotFound} from './features/page-not-found/page-not-found'; + +export const routes: Routes = [ + { path: '', redirectTo: '/dashboard', pathMatch: 'full' }, + { + path: 'dashboard', + component: Dashboard, + children: [ + { + path: '', + redirectTo: 'home', + pathMatch: 'full' + }, + { + path: 'home', + component: DashboardHome + }, + { + path: 'monitor', + component: Monitor + }, + { + path: 'agents', + component: Agents + }, + { + path: 'builder', + component: Builder + }, + { + path: 'status', + component: Status + }, + { + path: 'adms', + component: Adms + }, + { + path: 'help', + component: Help, + }, + ] + + }, + { path: '**', component: PageNotFound } +]; diff --git a/anms-ui/angular-port/anms-ui/src/app/app.spec.ts b/anms-ui/angular-port/anms-ui/src/app/app.spec.ts new file mode 100644 index 00000000..e9c46769 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/app.spec.ts @@ -0,0 +1,23 @@ +import { TestBed } from '@angular/core/testing'; +import { App } from './app'; + +describe('App', () => { + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [App], + }).compileComponents(); + }); + + it('should create the app', () => { + const fixture = TestBed.createComponent(App); + const app = fixture.componentInstance; + expect(app).toBeTruthy(); + }); + + it('should render title', async () => { + const fixture = TestBed.createComponent(App); + await fixture.whenStable(); + const compiled = fixture.nativeElement as HTMLElement; + expect(compiled.querySelector('h1')?.textContent).toContain('Hello, anms-ui'); + }); +}); diff --git a/anms-ui/angular-port/anms-ui/src/app/app.ts b/anms-ui/angular-port/anms-ui/src/app/app.ts new file mode 100644 index 00000000..c7d7b524 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/app.ts @@ -0,0 +1,12 @@ +import { Component, signal } from '@angular/core'; +import { RouterOutlet } from '@angular/router'; + +@Component({ + selector: 'app-root', + imports: [RouterOutlet], + templateUrl: './app.html', + styleUrl: './app.css' +}) +export class App { + protected readonly title = signal('anms-ui'); +} diff --git a/anms-ui/angular-port/anms-ui/src/app/features/adms/adms.css b/anms-ui/angular-port/anms-ui/src/app/features/adms/adms.css new file mode 100644 index 00000000..e69de29b diff --git a/anms-ui/angular-port/anms-ui/src/app/features/adms/adms.html b/anms-ui/angular-port/anms-ui/src/app/features/adms/adms.html new file mode 100644 index 00000000..13868091 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/adms/adms.html @@ -0,0 +1,111 @@ +
+ + @if (admService.hasAdms()) { + + + + + + + + + + + + + + @for (adm of admService.adms(); track adm) { + + + + + + + + + } + +
EnumerationNameNamespaceVersionUse Description
{{ adm.enumeration }} + {{ adm.name }} + {{ adm.namespace }}{{ adm.version_name }}{{ adm.use_desc }}
+
+ } + + + @if (admService.loading()) { + +
+
+ Loading... +
+
+
+ } + + +
+ +
+ + +
+
+
+ +
+ +
+
+ + + @if (admService.hasRequestError()) { + +
+ {{ admService.requestError() }} +
+ + + @if (admService.hasUploadErrors()) { + + + + + + + + + + @for (error of admService.uploadErrors(); track error) { + + + + + + } + +
Object TypeNameIssue
{{ error.obj_type }}{{ error.name }}{{ error.issue }}
+ } +
+ } +
diff --git a/anms-ui/angular-port/anms-ui/src/app/features/adms/adms.spec.ts b/anms-ui/angular-port/anms-ui/src/app/features/adms/adms.spec.ts new file mode 100644 index 00000000..35f792be --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/adms/adms.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { Adms } from './adms'; + +describe('Adms', () => { + let component: Adms; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [Adms], + }).compileComponents(); + + fixture = TestBed.createComponent(Adms); + component = fixture.componentInstance; + await fixture.whenStable(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/anms-ui/angular-port/anms-ui/src/app/features/adms/adms.ts b/anms-ui/angular-port/anms-ui/src/app/features/adms/adms.ts new file mode 100644 index 00000000..b5865c53 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/adms/adms.ts @@ -0,0 +1,42 @@ +import {AfterViewInit, Component, inject} from '@angular/core'; +import {AdmService} from '../../store/modules/adm-service'; +import * as _ from 'lodash'; +import {FormsModule} from '@angular/forms'; + +@Component({ + selector: 'app-adms', + imports: [ + FormsModule + ], + templateUrl: './adms.html', + styleUrl: './adms.css', +}) +export class Adms implements AfterViewInit { + protected admService = inject(AdmService); + protected file: any = null; + + ngAfterViewInit(): void { + if(!this.admService.hasAdms()) { + this.admService.getAdms(); + } + } + + protected hasValidFile() { + return (!_.isNull(this.file) ); + } + + protected async uploadFile() { + let yang_file: File = this.file; + this.file = null; + await this.admService.uploadAdm(yang_file); + if (!_.isNil(this.admService.requestError()) && this.admService.requestError() !== '') { + // toastr.error(this.requestError); + console.log(this.admService.requestError()); + } + else if (!_.isNil(this.admService.uploadStatus()) && this.admService.uploadStatus() !== '') { + // toastr.success(this.uploadStatus);uploadStatus + console.log(this.admService.uploadStatus()); + this.admService.getAdms(); + } + } +} diff --git a/anms-ui/angular-port/anms-ui/src/app/features/breadcrumb/breadcrumb.css b/anms-ui/angular-port/anms-ui/src/app/features/breadcrumb/breadcrumb.css new file mode 100644 index 00000000..e69de29b diff --git a/anms-ui/angular-port/anms-ui/src/app/features/breadcrumb/breadcrumb.html b/anms-ui/angular-port/anms-ui/src/app/features/breadcrumb/breadcrumb.html new file mode 100644 index 00000000..22b0d005 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/breadcrumb/breadcrumb.html @@ -0,0 +1,11 @@ +
+ +
diff --git a/anms-ui/angular-port/anms-ui/src/app/features/breadcrumb/breadcrumb.spec.ts b/anms-ui/angular-port/anms-ui/src/app/features/breadcrumb/breadcrumb.spec.ts new file mode 100644 index 00000000..5c80c6a7 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/breadcrumb/breadcrumb.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { Breadcrumb } from './breadcrumb'; + +describe('Breadcrumb', () => { + let component: Breadcrumb; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [Breadcrumb], + }).compileComponents(); + + fixture = TestBed.createComponent(Breadcrumb); + component = fixture.componentInstance; + await fixture.whenStable(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/anms-ui/angular-port/anms-ui/src/app/features/breadcrumb/breadcrumb.ts b/anms-ui/angular-port/anms-ui/src/app/features/breadcrumb/breadcrumb.ts new file mode 100644 index 00000000..75c8081a --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/breadcrumb/breadcrumb.ts @@ -0,0 +1,26 @@ +import {Component, inject} from '@angular/core'; +import {NavigationEnd, Router} from '@angular/router'; +import {filter} from 'rxjs'; +import {DataShareService} from '../../shared/data-share.service'; + +@Component({ + selector: 'app-breadcrumb', + imports: [], + templateUrl: './breadcrumb.html', + styleUrl: './breadcrumb.css', +}) +export class Breadcrumb { + router = inject(Router); + dataShareService = inject(DataShareService); + + currentUri: string = ''; + + constructor() { + this.router.events + .pipe(filter(event => event instanceof NavigationEnd)) + .subscribe((event: NavigationEnd) => { + this.currentUri = event.urlAfterRedirects; + this.dataShareService.setBreadcrumbs(this.currentUri); + }); + } +} diff --git a/anms-ui/angular-port/anms-ui/src/app/features/dashboard/components/dashboard-home/dashboard-home.css b/anms-ui/angular-port/anms-ui/src/app/features/dashboard/components/dashboard-home/dashboard-home.css new file mode 100644 index 00000000..05e50489 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/dashboard/components/dashboard-home/dashboard-home.css @@ -0,0 +1,18 @@ +.icon-with-text { + display: flex; + align-items: center; + justify-content: space-between; +} + +.card-dimensions { + width: 500px; + height: 230px; +} + +details { + font-family: sans-serif; + width: 50%; + border: 1px solid lightgrey; + border-radius: 5px; + margin: 0.5rem; +} diff --git a/anms-ui/angular-port/anms-ui/src/app/features/dashboard/components/dashboard-home/dashboard-home.html b/anms-ui/angular-port/anms-ui/src/app/features/dashboard/components/dashboard-home/dashboard-home.html new file mode 100644 index 00000000..c3d85fd0 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/dashboard/components/dashboard-home/dashboard-home.html @@ -0,0 +1,129 @@ +
+
+
+
+
+ query_stats + Monitor + +
+
+
+

+ + The Monitor view uses Grafana to display data stored in the ANMS databases, + which are populated with information collected + from the Managers and Agents in the network. + +

+ +
+
+
+ +
+
+
+
+ engineering + Agents + +
+
+
+

+ The Agents view can be used to search for Agents known to the ANMS, + get additional information on these Agents, and add new + Agents to the system. +

+
+
+
+ +
+
+
+
+ build + Build + +
+
+
+

+ The Build view is for used for generating ARIs, + translating string ARIs to CBOR, and sending those ARIs to the ANMS database + and/or Agent(s). +

+
+
+
+ +
+
+
+
+ checklist + Status + +
+
+
+

+ The Status view provides a summary of the overall health and status + of the ANMS services. If all ANMS containers are up and running, + a green "Good" label is included in the Status tab header. +

+
+
+
+ +
+
+
+
+ schema + ADMs + +
+
+
+

+ The ADMs view of the ANMS displays the Application Data Models (ADMs) + known to the system, and allows a user to upload additional ADM files. +

+
+
+
+ +
+
+
+
+ help + Help + +
+
+
+

+ The Help view of the ANMS display provides access to developer resources and product documentation. +

+
+
+
+ + +
diff --git a/anms-ui/angular-port/anms-ui/src/app/features/dashboard/components/dashboard-home/dashboard-home.spec.ts b/anms-ui/angular-port/anms-ui/src/app/features/dashboard/components/dashboard-home/dashboard-home.spec.ts new file mode 100644 index 00000000..3712b29c --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/dashboard/components/dashboard-home/dashboard-home.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DashboardHome } from './dashboard-home'; + +describe('DashboardHome', () => { + let component: DashboardHome; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [DashboardHome], + }).compileComponents(); + + fixture = TestBed.createComponent(DashboardHome); + component = fixture.componentInstance; + await fixture.whenStable(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/anms-ui/angular-port/anms-ui/src/app/features/dashboard/components/dashboard-home/dashboard-home.ts b/anms-ui/angular-port/anms-ui/src/app/features/dashboard/components/dashboard-home/dashboard-home.ts new file mode 100644 index 00000000..bee5816f --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/dashboard/components/dashboard-home/dashboard-home.ts @@ -0,0 +1,18 @@ +import { Component } from '@angular/core'; +import {MatIcon} from "@angular/material/icon"; +import {MatListItemIcon} from "@angular/material/list"; +import {RouterLink} from '@angular/router'; +import {MatMiniFabButton} from '@angular/material/button'; + +@Component({ + selector: 'app-dashboard-home', + imports: [ + MatIcon, + MatListItemIcon, + RouterLink, + MatMiniFabButton + ], + templateUrl: './dashboard-home.html', + styleUrl: './dashboard-home.css', +}) +export class DashboardHome {} diff --git a/anms-ui/angular-port/anms-ui/src/app/features/dashboard/dashboard.css b/anms-ui/angular-port/anms-ui/src/app/features/dashboard/dashboard.css new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/dashboard/dashboard.css @@ -0,0 +1 @@ + diff --git a/anms-ui/angular-port/anms-ui/src/app/features/dashboard/dashboard.html b/anms-ui/angular-port/anms-ui/src/app/features/dashboard/dashboard.html new file mode 100644 index 00000000..0630b1ab --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/dashboard/dashboard.html @@ -0,0 +1,22 @@ +
+
+ +
+ +
+
+ +
+
+ +
+
+
+ +
+
+ +
+
+
+
diff --git a/anms-ui/angular-port/anms-ui/src/app/features/dashboard/dashboard.spec.ts b/anms-ui/angular-port/anms-ui/src/app/features/dashboard/dashboard.spec.ts new file mode 100644 index 00000000..2ce950a7 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/dashboard/dashboard.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { Dashboard } from './dashboard'; + +describe('Dashboard', () => { + let component: Dashboard; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [Dashboard], + }).compileComponents(); + + fixture = TestBed.createComponent(Dashboard); + component = fixture.componentInstance; + await fixture.whenStable(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/anms-ui/angular-port/anms-ui/src/app/features/dashboard/dashboard.ts b/anms-ui/angular-port/anms-ui/src/app/features/dashboard/dashboard.ts new file mode 100644 index 00000000..59dceada --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/dashboard/dashboard.ts @@ -0,0 +1,38 @@ +import {AfterViewInit, Component, inject, OnDestroy, OnInit} from '@angular/core'; +import {Header} from '../../layout/header/header'; +import {MatDivider} from '@angular/material/list'; +import {Sidebar} from '../../layout/sidebar/sidebar'; +import {RouterOutlet} from '@angular/router'; +import {Breadcrumb} from '../breadcrumb/breadcrumb'; +import {Constants} from '../../shared/constants'; +import {ServiceStatusService} from '../../store/modules/service-status.service'; + +@Component({ + selector: 'app-dashboard', + imports: [ + Header, + MatDivider, + Sidebar, + RouterOutlet, + Breadcrumb + ], + templateUrl: './dashboard.html', + styleUrl: './dashboard.css', +}) +export class Dashboard implements AfterViewInit, OnDestroy { + private serviceStatus = inject(ServiceStatusService); + + private statusWorkerId: number = -1; + + ngAfterViewInit(): void { + this.statusWorkerId = setInterval(() => { + this.serviceStatus.updateStatus(); + }, Constants.status_refresh_rate); + } + + ngOnDestroy(): void { + if(this.statusWorkerId !== -1) { + clearInterval(this.statusWorkerId); + } + } +} diff --git a/anms-ui/angular-port/anms-ui/src/app/features/help/help.css b/anms-ui/angular-port/anms-ui/src/app/features/help/help.css new file mode 100644 index 00000000..e69de29b diff --git a/anms-ui/angular-port/anms-ui/src/app/features/help/help.html b/anms-ui/angular-port/anms-ui/src/app/features/help/help.html new file mode 100644 index 00000000..0fe21f80 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/help/help.html @@ -0,0 +1,34 @@ +
+
+

Asynchronous Network Management System (ANMS) documentation

+ +
+ +
+

Developer Resources

+
    +
  • + +
  • +
  • + This powers the 'Monitor' tab. Tip: Press the 'Esc' key + in the Monitor tab after clicking within the display to exit Kiosk mode and access the full UI interface. +
  • +
  • Adminer (DB Management) + if ANMS is configured with 'dev' profile. +
  • +
+
+ +
+ NOTE: This page is a placeholder. If you have suggestions to improve this page, or ANMS in general, share it with + us + on github. +
+
diff --git a/anms-ui/angular-port/anms-ui/src/app/features/help/help.spec.ts b/anms-ui/angular-port/anms-ui/src/app/features/help/help.spec.ts new file mode 100644 index 00000000..eb917de1 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/help/help.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { Help } from './help'; + +describe('Help', () => { + let component: Help; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [Help], + }).compileComponents(); + + fixture = TestBed.createComponent(Help); + component = fixture.componentInstance; + await fixture.whenStable(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/anms-ui/angular-port/anms-ui/src/app/features/help/help.ts b/anms-ui/angular-port/anms-ui/src/app/features/help/help.ts new file mode 100644 index 00000000..3697cdf8 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/help/help.ts @@ -0,0 +1,35 @@ +import {Component} from '@angular/core'; +import {MatButton} from '@angular/material/button'; + +enum Resource { + API_DOCS, + GRAFANA, +} + +@Component({ + selector: 'app-help', + imports: [ + MatButton, + ], + templateUrl: './help.html', + styleUrl: './help.css', +}) +export class Help { + protected resource = Resource; + // FIXME: use environment.ts or BASE_URL, etc. + // private baseHostname = window.location.hostname; + private baseHostname = 'http://anms-test'; + private baseHost = window.location.host; + + + protected openUrl(resource: Resource) { + switch(resource) { + case Resource.API_DOCS: + window.open(`/docs`, '_self'); + break; + case Resource.GRAFANA: + window.open(`/grafana`, '_self'); + break; + } + } +} diff --git a/anms-ui/angular-port/anms-ui/src/app/features/management/agents/agent-modal/agent-modal.css b/anms-ui/angular-port/anms-ui/src/app/features/management/agents/agent-modal/agent-modal.css new file mode 100644 index 00000000..c1e4f3ef --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/management/agents/agent-modal/agent-modal.css @@ -0,0 +1,17 @@ +.agent-info { + padding: 16px; + margin-bottom: 16px; + background-color: grey; + display: inline-block; + border-radius: 10px; + color: black; + + .badge { + display: inline-block; + padding: 0.25rem 0.6rem; + border-radius: 10rem; + background-color: #3f51b5; + color: #fff; + font-size: 0.8rem; + } +} diff --git a/anms-ui/angular-port/anms-ui/src/app/features/management/agents/agent-modal/agent-modal.html b/anms-ui/angular-port/anms-ui/src/app/features/management/agents/agent-modal/agent-modal.html new file mode 100644 index 00000000..bd0083b0 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/management/agents/agent-modal/agent-modal.html @@ -0,0 +1,69 @@ +
+
+

Agent Details

+
+
+ +
+
+ +@if (agentInfo) { + +
+
+ Agent: + + {{ agentInfo.agent_endpoint_uri }} + +
+ +
+ Registered Agents ID: + + {{ agentInfo.registered_agents_id }} + +
+ +
+ First Registered: + + {{ agentInfo.first_registered }} + +
+ +
+ Last Registered: + + {{ agentInfo.last_registered }} + +
+
+ + RPTT: {{rptt}} + + + + + + + +
+} + + + + diff --git a/anms-ui/angular-port/anms-ui/src/app/features/management/agents/agent-modal/agent-modal.spec.ts b/anms-ui/angular-port/anms-ui/src/app/features/management/agents/agent-modal/agent-modal.spec.ts new file mode 100644 index 00000000..c933085f --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/management/agents/agent-modal/agent-modal.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import {AgentModal} from './agent-modal'; + + +describe('AgentModal', () => { + let component: AgentModal; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [AgentModal], + }).compileComponents(); + + fixture = TestBed.createComponent(AgentModal); + component = fixture.componentInstance; + await fixture.whenStable(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/anms-ui/angular-port/anms-ui/src/app/features/management/agents/agent-modal/agent-modal.ts b/anms-ui/angular-port/anms-ui/src/app/features/management/agents/agent-modal/agent-modal.ts new file mode 100644 index 00000000..de37f1cb --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/management/agents/agent-modal/agent-modal.ts @@ -0,0 +1,63 @@ +import { + MAT_DIALOG_DATA, + MatDialogActions, + MatDialogContent, + MatDialogRef, + MatDialogTitle +} from '@angular/material/dialog'; +import {AgentsService} from '../../../../store/modules/agents.service'; +import {MatButton} from '@angular/material/button'; +import {Component, Inject, inject, OnInit} from '@angular/core'; +import {ReportOption, Reports} from '../reports/reports'; +import {Crud, Operation} from '../crud/crud'; + +export interface AgentInfo { + agent_endpoint_uri: string; + registered_agents_id: string; + first_registered: string; + last_registered: string; + selected?: boolean; +} + +@Component({ + selector: 'app-agent-modal', + templateUrl: 'agent-modal.html', + imports: [ + MatDialogContent, + MatDialogActions, + MatButton, + MatDialogTitle, + Reports, + Crud, + ], + styleUrls: ['agent-modal.css'] +}) +export class AgentModal implements OnInit { + protected agentsService = inject(AgentsService); + + protected agentInfo: AgentInfo; + + protected rptt: ReportOption[] = []; + protected operations: Operation[] = []; + + constructor( + private dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: AgentInfo, + ) { + this.agentInfo = data; + } + + ngOnInit(): void { + if (this.agentInfo?.registered_agents_id != null) { + this.agentsService.setAgentId(this.agentInfo.registered_agents_id); + this.agentsService.reloadAgent(); + } + + this.rptt = this.agentsService.rptt(); + this.operations = this.agentsService.operations(); + } + + close(): void { + this.dialogRef.close(); + } +} diff --git a/anms-ui/angular-port/anms-ui/src/app/features/management/agents/agents.css b/anms-ui/angular-port/anms-ui/src/app/features/management/agents/agents.css new file mode 100644 index 00000000..d2ac357a --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/management/agents/agents.css @@ -0,0 +1,13 @@ +.footer { + position: fixed; + bottom: 0; +} + + +.paginator { + width: 600px; + background-color: transparent; + font-family: system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue","Noto Sans","Liberation Sans",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"; + font-size: 14px; + font-weight: 500; +} diff --git a/anms-ui/angular-port/anms-ui/src/app/features/management/agents/agents.html b/anms-ui/angular-port/anms-ui/src/app/features/management/agents/agents.html new file mode 100644 index 00000000..29df53c4 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/management/agents/agents.html @@ -0,0 +1,136 @@ +
+
+
+ +
+ + + +
+
+ +
+
+
+ +
+
+

Select Agent to view

+
+
+ +
+
+ + + + + + + + + + + + + + @for (agent of agentsService.currentAgents(); track agent) { + + + + + + + + } + +
+ + Select All + Agent Endpoint URIFirst RegisteredLast Registered
+ + + {{ agent.agent_endpoint_uri }}{{ agent.first_registered }}{{ agent.last_registered }}
+ + +
+
+
+ Agent Address + + + +
+
+
+
+
+ + + +
+
+ +
+
+ + + + + + + + + + + + +
diff --git a/anms-ui/angular-port/anms-ui/src/app/features/management/agents/agents.spec.ts b/anms-ui/angular-port/anms-ui/src/app/features/management/agents/agents.spec.ts new file mode 100644 index 00000000..02cd1076 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/management/agents/agents.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { Agents } from './agents'; + +describe('Agents', () => { + let component: Agents; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [Agents], + }).compileComponents(); + + fixture = TestBed.createComponent(Agents); + component = fixture.componentInstance; + await fixture.whenStable(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/anms-ui/angular-port/anms-ui/src/app/features/management/agents/agents.ts b/anms-ui/angular-port/anms-ui/src/app/features/management/agents/agents.ts new file mode 100644 index 00000000..4b62eb90 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/management/agents/agents.ts @@ -0,0 +1,118 @@ +import {AfterViewInit, Component, inject} from '@angular/core'; +import {AgentsService} from '../../../store/modules/agents.service'; +import {ApiService} from '../../../shared/api.service'; +import {FormsModule} from '@angular/forms'; +import * as toastr from 'toastr'; +import {MatPaginator, PageEvent} from '@angular/material/paginator'; +import {MatDialog} from '@angular/material/dialog'; +import {AgentInfo, AgentModal} from './agent-modal/agent-modal'; + +@Component({ + selector: 'app-agents', + imports: [ + FormsModule, + MatPaginator + ], + templateUrl: './agents.html', + styleUrl: './agents.css', +}) +export class Agents implements AfterViewInit { + protected agentsService = inject(AgentsService); + protected apiService = inject(ApiService); + protected dialog = inject(MatDialog); + + protected info: string = ''; + protected selectAll: boolean = false; + protected node: any; + protected pageSizes = [10, 20, 50, 100]; + + + ngAfterViewInit(): void { + this.agentsService.reloadAgents(); + this.apiService.apiAmpVersion().subscribe( + { + next: (response: any) => { + if (response.build_date != null) { + this.info = response.build_date + " " + response.build_time; + } + }, + error: (err: any) => { + console.error(err); + this.info = "failed to reach manager"; + } + }); + } + + protected handleSearchStringChange($event: any) { + this.agentsService.setSearchString($event.target.value); + } + + protected handlePageEvent(pageEvent: PageEvent, paginator: MatPaginator) { + if(this.agentsService.getPageSize() !== pageEvent.pageSize) { + this.agentsService.setPageSize(pageEvent.pageSize); + this.agentsService.setPage(1); + paginator.firstPage() + } else { + this.agentsService.setPage(pageEvent.pageIndex + 1); + } + + this.agentsService.reloadAgents(); + } + + protected toggleSelectAll() { + this.agentsService.currentAgents().forEach((agent) => { + this.selectAgent(this.selectAll, agent); + }); + } + + protected selectAgent(event: any, agent: any) { + if (agent && event != agent.selected) { + let agentUpdated = {...agent}; + let agentIndex = this.getAgentIndexById(agentUpdated.registered_agents_id); + agentUpdated.selected = event; + this.agentsService.updateAgent(agentIndex, agentUpdated); + } + } + + protected onClick(nodes: string) { + let nodeList = nodes.split(","); + nodeList.forEach((node) => { + this.apiService + .apiPostAgent(node.trim()) + .subscribe({next: (response) => { + const results = response.status + " " + response.statusText; + toastr.success(results); + }, + error: (err: any) => { + console.error(err); + toastr.error("Failed to add agent to node: " + node); + } + }); + }); + } + + protected selectedAgents() { + return this.agentsService.currentAgents().filter((agent) => { + return agent.selected == true; + }); + } + + protected goToAgentDetails(agentInfo: AgentInfo) { + const dialogRef = this.dialog.open(AgentModal, { + width: '80vw', + maxWidth: '1000px', + data: agentInfo + }); + + dialogRef.afterClosed().subscribe(() => { + }); + } + + protected goToManageModal() { + // this.showManageModal = true; + } + + private getAgentIndexById(agentId: any) { + return this.agentsService.currentAgents().findIndex(agent => agent.registered_agents_id === agentId); + } +} diff --git a/anms-ui/angular-port/anms-ui/src/app/features/management/agents/crud/crud.css b/anms-ui/angular-port/anms-ui/src/app/features/management/agents/crud/crud.css new file mode 100644 index 00000000..e69de29b diff --git a/anms-ui/angular-port/anms-ui/src/app/features/management/agents/crud/crud.html b/anms-ui/angular-port/anms-ui/src/app/features/management/agents/crud/crud.html new file mode 100644 index 00000000..5da90fd1 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/management/agents/crud/crud.html @@ -0,0 +1,43 @@ +
+
Operation:
+ + +
+ +
+ + +
+ @for (param of params; track param) { +
+ +
+ } + + +
+
diff --git a/anms-ui/angular-port/anms-ui/src/app/features/management/agents/crud/crud.spec.ts b/anms-ui/angular-port/anms-ui/src/app/features/management/agents/crud/crud.spec.ts new file mode 100644 index 00000000..3bec17ff --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/management/agents/crud/crud.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { Crud } from './crud'; + +describe('Crud', () => { + let component: Crud; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [Crud], + }).compileComponents(); + + fixture = TestBed.createComponent(Crud); + component = fixture.componentInstance; + await fixture.whenStable(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/anms-ui/angular-port/anms-ui/src/app/features/management/agents/crud/crud.ts b/anms-ui/angular-port/anms-ui/src/app/features/management/agents/crud/crud.ts new file mode 100644 index 00000000..ba4dfa3c --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/management/agents/crud/crud.ts @@ -0,0 +1,74 @@ +import {Component, inject, Input} from '@angular/core'; +import {ApiService} from '../../../../shared/api.service'; +import {ToastrService} from 'ngx-toastr'; +import {FormsModule} from '@angular/forms'; + +export interface Operation { + command_name: string; + command_parameters: string[]; + agent_parameter_id: number | string; + // any other fields you may have +} + +interface ParamModel { + name: string; + value: string; +} + +@Component({ + selector: 'app-crud', + imports: [ + FormsModule + ], + templateUrl: './crud.html', + styleUrl: './crud.css', +}) +export class Crud { + @Input() agentId!: number | string; + @Input() operations: Operation[] = []; + private apiService = inject(ApiService); + private toastr = inject(ToastrService) + + selected: Operation | -1 = -1; + params: ParamModel[] = []; + finalValues: Record = {}; + + onOperationChange(): void { + // Clear existing params + this.params = []; + + if (this.selected === -1 || !this.selected) { + return; + } + + const commandParameters = this.selected.command_parameters || []; + commandParameters.forEach((value) => { + this.params.push({name: value, value: ''}); + }); + } + + onClick(): void { + if (this.selected === -1 || !this.selected) { + this.toastr.warning('Please select an operation first.'); + return; + } + + this.finalValues = {}; + this.params.forEach((param) => { + this.finalValues[param.name] = param.value; + }); + + this.apiService + .apiPutCRUD(this.agentId, this.selected.agent_parameter_id, this.finalValues) + .subscribe({ + next: (response: any) => { + this.toastr.success(`${response.statusText} ${response.data}`); + this.selected = -1; + this.params = []; + }, error: (error: any) => { + console.error('crud error', error); + this.toastr.error(error); + } + }); + } +} diff --git a/anms-ui/angular-port/anms-ui/src/app/features/management/agents/reports/reports.css b/anms-ui/angular-port/anms-ui/src/app/features/management/agents/reports/reports.css new file mode 100644 index 00000000..ffde3ece --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/management/agents/reports/reports.css @@ -0,0 +1,12 @@ +.spacing-table { + margin: 16px 0; +} + +.select-max-width { + max-width: 600px; +} + +.scrollable-div { + max-height: 600px; + overflow-y: auto; +} diff --git a/anms-ui/angular-port/anms-ui/src/app/features/management/agents/reports/reports.html b/anms-ui/angular-port/anms-ui/src/app/features/management/agents/reports/reports.html new file mode 100644 index 00000000..49c868f0 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/management/agents/reports/reports.html @@ -0,0 +1,73 @@ +
+
Reports sent:
+ + + @if (loading) { +
+ Loading... +
+ } + +
+ +
+ + + @for (_ of tableItems; track _; let index = $index) { + + +
+ + + + @for (header of tableHeaders[index]; track header) { + + } + + +
+ {{ header }} +
+
+ +
+ @if (!loading && selected) { + + + @for (header of tableHeaders[index]; track header) { + + + + } + + + @for (row of tableItems[index]; track row) { + + @for (cell of row; track cell) { + + } + + } + +
+ {{ header }} +
+ {{ cell }} +
+ } +
+
+ } +
diff --git a/anms-ui/angular-port/anms-ui/src/app/features/management/agents/reports/reports.spec.ts b/anms-ui/angular-port/anms-ui/src/app/features/management/agents/reports/reports.spec.ts new file mode 100644 index 00000000..b150686b --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/management/agents/reports/reports.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { Reports } from './reports'; + +describe('Reports', () => { + let component: Reports; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [Reports], + }).compileComponents(); + + fixture = TestBed.createComponent(Reports); + component = fixture.componentInstance; + await fixture.whenStable(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/anms-ui/angular-port/anms-ui/src/app/features/management/agents/reports/reports.ts b/anms-ui/angular-port/anms-ui/src/app/features/management/agents/reports/reports.ts new file mode 100644 index 00000000..dbf58eb7 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/management/agents/reports/reports.ts @@ -0,0 +1,120 @@ +import {Component, inject, Input, OnInit} from '@angular/core'; +import {ToastrService} from 'ngx-toastr'; +import {ApiService} from '../../../../shared/api.service'; +import {FormsModule} from '@angular/forms'; + +export interface ReportOption { + exec_set: string; + nonce_cbor: string; +} + +@Component({ + selector: 'app-reports', + templateUrl: './reports.html', + styleUrls: ['./reports.css'], + imports: [ + FormsModule + ] +}) +export class Reports implements OnInit { + @Input() agentName!: string; + @Input() rptts: ReportOption[] = []; + @Input() registeredAgentsId!: string; + + apiService = inject(ApiService); + toastr = inject(ToastrService); + + selected?: ReportOption; + tableHeaders: string[][] = []; + tableItems: any[][][] = []; + title = ''; + reports: Record = {}; + reportsHeader: Record = {}; + loading = true; + + constructor() { } + + ngOnInit(): void { + this.loading = true; + console.log('rptts', this.rptts); + + // preload all reports like in mounted() + const preloadPromises = this.rptts.map((rpt, index) => + this.apiService + .apiEntriesForReport(this.registeredAgentsId, rpt.nonce_cbor) + .subscribe({ + next: (res: any) => { + console.log('report', index, res); + this.reports[index] = res; + }, error: (error: any) => { + console.error('reports error', error); + } + }) + ); + + Promise.all(preloadPromises) + .finally(() => { + this.loading = false; + }); + } + + async onReportSelect(): Promise { + if (!this.selected) { + return; + } + + this.loading = true; + this.tableHeaders = []; + this.tableItems = []; + + const nonce_cbor = this.selected.nonce_cbor; + + try { + const res: any = this.apiService.apiEntriesForReport( + this.registeredAgentsId, + encodeURIComponent(nonce_cbor) + ); + + this.processReport(res.data); + + const key = JSON.stringify(this.selected); + this.reports[key] = this.tableItems; + this.reportsHeader[key] = this.tableHeaders; + } catch (error: any) { + console.error('reports error', error); + this.toastr.error('reports error: ' + error); + } finally { + this.loading = false; + } + } + + private processReport(report: any): void { + let rpt: any[] = []; + + if (this.selected && this.selected.exec_set in report) { + rpt = report[this.selected.exec_set]; + } else if (this.selected) { + rpt = report[this.selected.nonce_cbor]; + } + + if (!rpt || rpt.length === 0) { + return; + } + + // header from first item keys + const holdHeader: string[] = Object.keys(rpt[0]); + + const currTableItems: any[][] = []; + + for (const item of rpt) { + const row: any[] = []; + for (const h of holdHeader) { + row.push(item[h]); + } + currTableItems.push(row.flat()); + } + + this.tableHeaders.push(holdHeader); + this.tableItems.push(currTableItems); + } +} diff --git a/anms-ui/angular-port/anms-ui/src/app/features/management/builder/builder.css b/anms-ui/angular-port/anms-ui/src/app/features/management/builder/builder.css new file mode 100644 index 00000000..e69de29b diff --git a/anms-ui/angular-port/anms-ui/src/app/features/management/builder/builder.html b/anms-ui/angular-port/anms-ui/src/app/features/management/builder/builder.html new file mode 100644 index 00000000..af41d55b --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/management/builder/builder.html @@ -0,0 +1 @@ +

builder works!

diff --git a/anms-ui/angular-port/anms-ui/src/app/features/management/builder/builder.spec.ts b/anms-ui/angular-port/anms-ui/src/app/features/management/builder/builder.spec.ts new file mode 100644 index 00000000..ebd1aa59 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/management/builder/builder.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { Builder } from './builder'; + +describe('Builder', () => { + let component: Builder; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [Builder], + }).compileComponents(); + + fixture = TestBed.createComponent(Builder); + component = fixture.componentInstance; + await fixture.whenStable(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/anms-ui/angular-port/anms-ui/src/app/features/management/builder/builder.ts b/anms-ui/angular-port/anms-ui/src/app/features/management/builder/builder.ts new file mode 100644 index 00000000..425e6a54 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/management/builder/builder.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-builder', + imports: [], + templateUrl: './builder.html', + styleUrl: './builder.css', +}) +export class Builder {} diff --git a/anms-ui/angular-port/anms-ui/src/app/features/management/monitor/monitor.css b/anms-ui/angular-port/anms-ui/src/app/features/management/monitor/monitor.css new file mode 100644 index 00000000..42cf5864 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/management/monitor/monitor.css @@ -0,0 +1,5 @@ +iframe { + border: none; + height: 100vh; + width: 100%; +} diff --git a/anms-ui/angular-port/anms-ui/src/app/features/management/monitor/monitor.html b/anms-ui/angular-port/anms-ui/src/app/features/management/monitor/monitor.html new file mode 100644 index 00000000..bb919662 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/management/monitor/monitor.html @@ -0,0 +1,8 @@ +
+ +
diff --git a/anms-ui/angular-port/anms-ui/src/app/features/management/monitor/monitor.spec.ts b/anms-ui/angular-port/anms-ui/src/app/features/management/monitor/monitor.spec.ts new file mode 100644 index 00000000..1a2c92bb --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/management/monitor/monitor.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { Monitor } from './monitor'; + +describe('Monitor', () => { + let component: Monitor; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [Monitor], + }).compileComponents(); + + fixture = TestBed.createComponent(Monitor); + component = fixture.componentInstance; + await fixture.whenStable(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/anms-ui/angular-port/anms-ui/src/app/features/management/monitor/monitor.ts b/anms-ui/angular-port/anms-ui/src/app/features/management/monitor/monitor.ts new file mode 100644 index 00000000..50f43f5a --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/management/monitor/monitor.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-monitor', + imports: [], + templateUrl: './monitor.html', + styleUrl: './monitor.css', +}) +export class Monitor {} diff --git a/anms-ui/angular-port/anms-ui/src/app/features/page-not-found/page-not-found.css b/anms-ui/angular-port/anms-ui/src/app/features/page-not-found/page-not-found.css new file mode 100644 index 00000000..79736f44 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/page-not-found/page-not-found.css @@ -0,0 +1,4 @@ +.notfound { + text-align: center; + padding: 2rem; +} diff --git a/anms-ui/angular-port/anms-ui/src/app/features/page-not-found/page-not-found.html b/anms-ui/angular-port/anms-ui/src/app/features/page-not-found/page-not-found.html new file mode 100644 index 00000000..81a286b1 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/page-not-found/page-not-found.html @@ -0,0 +1,6 @@ +
+

{{ pageName }}

+ This is not the page you are looking for. +
+ Go to Dashboard +
diff --git a/anms-ui/angular-port/anms-ui/src/app/features/page-not-found/page-not-found.spec.ts b/anms-ui/angular-port/anms-ui/src/app/features/page-not-found/page-not-found.spec.ts new file mode 100644 index 00000000..8024e435 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/page-not-found/page-not-found.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PageNotFound } from './page-not-found'; + +describe('PageNotFound', () => { + let component: PageNotFound; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [PageNotFound], + }).compileComponents(); + + fixture = TestBed.createComponent(PageNotFound); + component = fixture.componentInstance; + await fixture.whenStable(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/anms-ui/angular-port/anms-ui/src/app/features/page-not-found/page-not-found.ts b/anms-ui/angular-port/anms-ui/src/app/features/page-not-found/page-not-found.ts new file mode 100644 index 00000000..92f376f8 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/page-not-found/page-not-found.ts @@ -0,0 +1,14 @@ +import { Component } from '@angular/core'; +import {RouterLink} from '@angular/router'; + +@Component({ + selector: 'app-page-not-found', + imports: [ + RouterLink + ], + templateUrl: './page-not-found.html', + styleUrl: './page-not-found.css', +}) +export class PageNotFound { + protected pageName = "Page Not Found"; +} diff --git a/anms-ui/angular-port/anms-ui/src/app/features/status/status.css b/anms-ui/angular-port/anms-ui/src/app/features/status/status.css new file mode 100644 index 00000000..e69de29b diff --git a/anms-ui/angular-port/anms-ui/src/app/features/status/status.html b/anms-ui/angular-port/anms-ui/src/app/features/status/status.html new file mode 100644 index 00000000..f41f7fe7 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/status/status.html @@ -0,0 +1,51 @@ +
+ @if (serviceStatus.hasUpdateError()) { +
+ {{ serviceStatus.updateError() }} +
+ } + + @if (serviceStatus.hasData()) { + + + + + + + + + @if (serviceStatus.errorServices()) { + @for (errorService of serviceStatus.errorServices() | keyvalue: utils.sortKeyValueRecords; track errorService.key) { + + + + + } + } + + @for (normalService of serviceStatus.normalServices() | keyvalue: utils.sortKeyValueRecords; track normalService.key) { + + + + + } + +
Service NameStatus
{{ errorService.key }}{{ errorService.value }}
{{ normalService.key }}{{ normalService.value }}
+ } + + @if (serviceStatus.loading()) { +
+
+ Loading... +
+
+ } + +
+ +
+
diff --git a/anms-ui/angular-port/anms-ui/src/app/features/status/status.spec.ts b/anms-ui/angular-port/anms-ui/src/app/features/status/status.spec.ts new file mode 100644 index 00000000..38397a7b --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/status/status.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { Status } from './status'; + +describe('Status', () => { + let component: Status; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [Status], + }).compileComponents(); + + fixture = TestBed.createComponent(Status); + component = fixture.componentInstance; + await fixture.whenStable(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/anms-ui/angular-port/anms-ui/src/app/features/status/status.ts b/anms-ui/angular-port/anms-ui/src/app/features/status/status.ts new file mode 100644 index 00000000..3c75d43a --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/features/status/status.ts @@ -0,0 +1,27 @@ +import {Component, inject, OnInit} from '@angular/core'; +import {KeyValuePipe} from '@angular/common'; +import {Utils} from '../../shared/utils'; +import {ServiceStatusService} from '../../store/modules/service-status.service'; + +@Component({ + selector: 'app-status', + imports: [ + KeyValuePipe + ], + templateUrl: './status.html', + styleUrl: './status.css', +}) +export class Status implements OnInit { + + protected serviceStatus = inject(ServiceStatusService); + protected utils = inject(Utils); + + ngOnInit(): void { + this.updateServiceStatus(); + } + + protected updateServiceStatus() { + this.serviceStatus.updateStatus(); + } + +} diff --git a/anms-ui/angular-port/anms-ui/src/app/layout/header/header.css b/anms-ui/angular-port/anms-ui/src/app/layout/header/header.css new file mode 100644 index 00000000..98feb359 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/layout/header/header.css @@ -0,0 +1,7 @@ +.spacer { + flex: 1 1 auto; +} + +.cursor-pointer { + cursor: pointer; +} diff --git a/anms-ui/angular-port/anms-ui/src/app/layout/header/header.html b/anms-ui/angular-port/anms-ui/src/app/layout/header/header.html new file mode 100644 index 00000000..6f6adaa9 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/layout/header/header.html @@ -0,0 +1,13 @@ +
+ + AMMOS ANMS + Version - + + + + +
diff --git a/anms-ui/angular-port/anms-ui/src/app/layout/header/header.spec.ts b/anms-ui/angular-port/anms-ui/src/app/layout/header/header.spec.ts new file mode 100644 index 00000000..9ef7403f --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/layout/header/header.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { Header } from './header'; + +describe('Header', () => { + let component: Header; + let fixture: ComponentFixture
; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [Header], + }).compileComponents(); + + fixture = TestBed.createComponent(Header); + component = fixture.componentInstance; + await fixture.whenStable(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/anms-ui/angular-port/anms-ui/src/app/layout/header/header.ts b/anms-ui/angular-port/anms-ui/src/app/layout/header/header.ts new file mode 100644 index 00000000..e4161a08 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/layout/header/header.ts @@ -0,0 +1,20 @@ +import { Component } from '@angular/core'; +import {MatToolbar} from '@angular/material/toolbar'; +import {MatIcon} from '@angular/material/icon'; +import {MatIconButton} from '@angular/material/button'; +import {MatTooltip} from '@angular/material/tooltip'; +import {RouterLink} from '@angular/router'; + +@Component({ + selector: 'app-header', + imports: [ + MatToolbar, + MatIcon, + MatIconButton, + MatTooltip, + RouterLink, + ], + templateUrl: './header.html', + styleUrl: './header.css', +}) +export class Header {} diff --git a/anms-ui/angular-port/anms-ui/src/app/layout/sidebar/sidebar.css b/anms-ui/angular-port/anms-ui/src/app/layout/sidebar/sidebar.css new file mode 100644 index 00000000..189a1789 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/layout/sidebar/sidebar.css @@ -0,0 +1,29 @@ +.nav-bar-width { + width: 180px; +} + +.nav-bar-width-collapsed { + width: 55px; + animation: flash-background-frames 1.5s infinite; +} + +/* Animations */ +.fade-in { + animation: fadeIn 1000ms ease-out; +} + +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +.list-item-title { + font-size: 16px; + font-weight: 400; + line-height: 1.5; + font-family: Roboto, sans-serif; +} + +.status-icon-indicator { + border-radius: 5px; +} diff --git a/anms-ui/angular-port/anms-ui/src/app/layout/sidebar/sidebar.html b/anms-ui/angular-port/anms-ui/src/app/layout/sidebar/sidebar.html new file mode 100644 index 00000000..e63a9dfd --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/layout/sidebar/sidebar.html @@ -0,0 +1,113 @@ + diff --git a/anms-ui/angular-port/anms-ui/src/app/layout/sidebar/sidebar.spec.ts b/anms-ui/angular-port/anms-ui/src/app/layout/sidebar/sidebar.spec.ts new file mode 100644 index 00000000..2f291a9a --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/layout/sidebar/sidebar.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { Sidebar } from './sidebar'; + +describe('Sidebar', () => { + let component: Sidebar; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [Sidebar], + }).compileComponents(); + + fixture = TestBed.createComponent(Sidebar); + component = fixture.componentInstance; + await fixture.whenStable(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/anms-ui/angular-port/anms-ui/src/app/layout/sidebar/sidebar.ts b/anms-ui/angular-port/anms-ui/src/app/layout/sidebar/sidebar.ts new file mode 100644 index 00000000..448b0dbe --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/layout/sidebar/sidebar.ts @@ -0,0 +1,45 @@ +import {AfterViewInit, Component, inject} from '@angular/core'; +import {MatDivider, MatListItem, MatListItemIcon, MatNavList} from "@angular/material/list"; +import {MatIcon} from "@angular/material/icon"; +import {MatTooltip} from '@angular/material/tooltip'; +import {NgClass} from '@angular/common'; +import {RouterLink} from '@angular/router'; +import {RoutesEnum} from '../../shared/routes.enum'; +import {DataShareService} from '../../shared/data-share.service'; +import {ServiceStatusService} from '../../store/modules/service-status.service'; + +@Component({ + selector: 'app-sidebar', + imports: [ + MatDivider, + MatIcon, + MatListItem, + MatListItemIcon, + MatNavList, + MatTooltip, + NgClass, + RouterLink, + ], + templateUrl: './sidebar.html', + styleUrl: './sidebar.css', +}) +export class Sidebar implements AfterViewInit { + protected showNavTitle = false; + protected readonly RoutesEnum = RoutesEnum; + protected readonly dataShareService = inject(DataShareService); + protected readonly serviceStatus = inject(ServiceStatusService); + + protected managementDetails: any; + + ngAfterViewInit(): void { + this.managementDetails = document.getElementById('managementDetails'); + this.managementDetails.open = false; + } + + protected toggleManagementDetails() { + this.managementDetails.open = !this.managementDetails.open; + if(this.managementDetails.open) { + this.showNavTitle = true; + } + } +} diff --git a/anms-ui/angular-port/anms-ui/src/app/shared/api-adm.service.ts b/anms-ui/angular-port/anms-ui/src/app/shared/api-adm.service.ts new file mode 100644 index 00000000..a141f824 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/shared/api-adm.service.ts @@ -0,0 +1,44 @@ +import {inject, Injectable} from '@angular/core'; +import {HttpClient} from '@angular/common/http'; +import {Constants} from './constants'; +import {Observable} from 'rxjs'; + +@Injectable({ + providedIn: 'root', +}) +export class ApiAdmService { + private http = inject(HttpClient); + // FIXME: URL + // private adm_url = Constants.BASE_API_URL + ''; + private adm_url = '/api/core/adms'; + + private createAuthenticationHeader(){ + return { + Authorization: 'Bearer ' + Constants.USER_DETAILS.token + }; + } + + //Main API + public apiGetAdms(): Observable { + return this.http.get(this.adm_url, {headers: {accept: 'application/json'}}); + } + public apiGetAdm(admEnum: any, namespace: any): Observable { + return this.http.get(this.adm_url + "/" + admEnum + "/" + namespace); + } + + public apiUpdateAdm(file: File): Observable { + const auth_headers = this.createAuthenticationHeader(); + const formData = new FormData(); + console.log(file); + + formData.append('adm', file); + const headers = { + ...auth_headers, + 'Content-Type': 'multipart/form-data' + }; + // FIXME: upload URL + // FIXME: unable to test upload CORS error - might be something to do with CAM login session token, etc. + // return this.http.post(this.adm_url, formData, {headers}); + return this.http.post('/core/adms', formData, {headers}); + } +} diff --git a/anms-ui/angular-port/anms-ui/src/app/shared/api-adm.spec.ts b/anms-ui/angular-port/anms-ui/src/app/shared/api-adm.spec.ts new file mode 100644 index 00000000..6f7c7834 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/shared/api-adm.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { ApiAdmService } from './api-adm.service'; + +describe('ApiAdm', () => { + let service: ApiAdmService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(ApiAdmService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/anms-ui/angular-port/anms-ui/src/app/shared/api.service.ts b/anms-ui/angular-port/anms-ui/src/app/shared/api.service.ts new file mode 100644 index 00000000..4314d08c --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/shared/api.service.ts @@ -0,0 +1,229 @@ +import {inject, Injectable} from '@angular/core'; +import {Constants} from './constants'; +import {HttpClient} from '@angular/common/http'; +import {Observable} from 'rxjs'; + +@Injectable({ + providedIn: 'root', +}) +export class ApiService { + private http = inject(HttpClient); + + public createAuthenticationHeader() { + return { + Authorization: 'Bearer ' + Constants.USER_DETAILS.token + }; + }; + + public apiGetHello(): Observable { + return this.http.get('hello'); + } + + public apiGetUsers(): Observable { + return this.http.get('users', { + headers: this.createAuthenticationHeader() + }); + } + + public apiGetUserById(userId: any): Observable { + const id = encodeURIComponent(userId) ? encodeURIComponent(userId) : userId; + return this.http.get('users/' + id, { + headers: this.createAuthenticationHeader() + }); + } + + public apiGetUserByUsername(userName: string): Observable { + const name = encodeURIComponent(userName) ? encodeURIComponent(userName) : userName; + console.info("name: ", name); + return this.http.get('users/' + name, { + headers: this.createAuthenticationHeader() + }); + } + + public apiCreateUserProfile(payload: any): Observable { + return this.http.post('users', + {data: payload}, + { + headers: this.createAuthenticationHeader() + }); + } + + public apiUpdateUserProfile(userName: any, payload: any): Observable { + return this.http.put('users/' + encodeURIComponent(userName), + {data: payload}, + { + headers: this.createAuthenticationHeader() + }) + } + + public apiPutUserProfile(userId: any, payload: any): Observable { + return this.http.put('users/' + encodeURIComponent(userId) + '/profile', + {data: payload}, + { + headers: this.createAuthenticationHeader() + }); + } + + public apiChangePassword(userId: any, payload: any): Observable { + return this.http.post('users/' + encodeURIComponent(userId) + '/password', + {data: payload}, + { + headers: this.createAuthenticationHeader() + }); + } + + public apiEnableUser(userId: any): Observable { + return this.http.patch('users/enable/' + encodeURIComponent(userId), + {}, + { + headers: this.createAuthenticationHeader() + }); + } + + public apiDisableUser(userId: any): Observable { + return this.http.patch('users/disable/' + encodeURIComponent(userId), + {}, {headers: this.createAuthenticationHeader()}); + } + + public apiToggleUserMfa(userId: any, newValue: any): Observable { + return this.http.patch('users/mfa/' + encodeURIComponent(userId), + {newState: newValue}, + { + headers: this.createAuthenticationHeader() + }); + } + + // agents api + public apiQueryForAgents(payload: any): Observable { + let params: any = {}; + if (payload.page) { + params['page'] = encodeURIComponent(payload.page); + } + if (payload.size) { + params['size'] = encodeURIComponent(payload.size); + } + + if (payload.searchString === '') { + return this.http.get('/api/agents', {params: params}); + } else { + const searchString = encodeURIComponent(payload.searchString); + return this.http.get(`/api/agents/search/${searchString}`, {params: params}); + } + } + + public apiGetAgent(agentId: any): Observable { + return this.http.get(`/api/agents/id/${agentId}`); + } + + // ari all + public apiQueryForARIs(): Observable { + return this.http.get('/api/build/ari/all'); + } + + // ari by id + public apiQueryForARIById(meta_id: any, obj_id: any): Observable { + return this.http.get(`build/ari/id/${meta_id}/${obj_id}`); + } + + public apiAmpVersion(): Observable { + return this.http.get('/api/nm/version'); + + } + + public apiDeregister(nodeEID: any): Observable { + return this.http.get('nm/agents', nodeEID); + } + + public apiEntriesForReport(obj_agent_id: any, correlator_nonce: any): Observable { + return this.http.get(`/api/report/entries/table/${obj_agent_id}/${correlator_nonce}`); + } + + public apiEntriesForReportTemplate(agentId: any): Observable { + console.log("agentId: ", agentId); + return this.http.get(`/api/report/entry/name/${agentId}`); + } + + public apiEntriesForOperations(agentId: any): Observable { // get the names of crude operations + return this.http.get(`agents/parameter/name/${agentId}`); + } + + public apiPutCRUD(agentId: any, optId: any, params: any): Observable { + return this.http.put(`agents/parameter/send/${agentId}/${optId}`, params); + } + + public apiBuildControl(nodeEID: any): Observable { + return this.http.put('nm/agents', nodeEID, {headers: {},}); + } + + public apiSendRawCommand(nodeEID: any, command: any): Observable { + return this.http.put('nm/agents/eid/' + nodeEID + '/hex', {"data": command}); + } + + public apiPrintAgentReports(nodeEID: any): Observable { + return this.http.get('nm/agents/eid/' + nodeEID + '/reports/json'); + } + + public apiClearAgentReports(nodeEID: any): Observable { + return this.http.put('nm/agents/eid/' + nodeEID + '/clear_reports', null); + } + + public apiClearAgentTables(nodeEID: any): Observable { + return this.http.put('nm/agents/eid/' + nodeEID + '/clear_tables', null); + } + + public apiWriteAgentReportstofile(nodeEID: any): Observable { + return this.http.get('nm/agents', nodeEID); + } + + public apiPostAgent(node: any): Observable { + return this.http.post('/api/nm/agents', {'data': node}); + } + + public apiGetAgents(): Observable { + return this.http.get('nm/agents/'); + } + + public apiGetDbStatus(): Observable { + return this.http.get('sys_status/db_status'); + } + + public apiGetAlerts(): Observable { + return this.http.get('/api/alerts/incoming', {headers: {accept: 'application/json'}}); + } + + public apiAcknowledgeAlerts(index: any): Observable { + return this.http.put('/api/alerts/acknowledge/' + index, null); + } + + public apiGetServiceStatus(): Observable { + return this.http.get('/api/core/service_status', {headers: {accept: 'application/json'}}); + } + + public apiPutTranscodedHex(cbor: string): Observable { + return this.http.put('transcoder/ui/incoming/' + cbor + '/hex', null); + } + + public apiPutTranscodedString(ari: string): Observable { + return this.http.put('transcoder/ui/incoming/str', {"ari": ari}); + } + + public apiGetTranscoderLogById(id: any): Observable { + return this.http.get(`transcoder/ui/log/id/${id}`); + } + + public apiQueryForTranscoderLog(payload: any): Observable { + let params: any = {}; + if (payload.page) { + params['page'] = encodeURIComponent(payload.page); + } + if (payload.size) { + params['size'] = encodeURIComponent(payload.size); + } + if (payload.searchString === '') { + return this.http.get('transcoder/ui/log', {params: params}); + } else { + const searchString = encodeURIComponent(payload.searchString); + return this.http.get(`transcoder/ui/log/search/${searchString}`, {params: params}); + } + } +} diff --git a/anms-ui/angular-port/anms-ui/src/app/shared/api.spec.ts b/anms-ui/angular-port/anms-ui/src/app/shared/api.spec.ts new file mode 100644 index 00000000..a5b88a08 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/shared/api.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { ApiService } from './api.service'; + +describe('Api', () => { + let service: ApiService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(ApiService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/anms-ui/angular-port/anms-ui/src/app/shared/constants.ts b/anms-ui/angular-port/anms-ui/src/app/shared/constants.ts new file mode 100644 index 00000000..98fd4c41 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/shared/constants.ts @@ -0,0 +1,12 @@ +import {environment} from '../../environments/environment'; + +export const Constants = { + uiversion: environment.VUE_APP_UI_VERSION, + status_refresh_rate: environment.VUE_APP_STATUS_REFRESH_RATE, //ms -the rate of updating services' status + service_info: environment.SERVICE_INFO, + // FIXME: see index.html on how these are initialized + BASE_API_URL: 'http://anms-test:8040', + USER_DETAILS: { + token: '' + } +} diff --git a/anms-ui/angular-port/anms-ui/src/app/shared/data-share-service.spec.ts b/anms-ui/angular-port/anms-ui/src/app/shared/data-share-service.spec.ts new file mode 100644 index 00000000..4e62ad93 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/shared/data-share-service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { DataShareService } from './data-share.service'; + +describe('DataShareService', () => { + let service: DataShareService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(DataShareService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/anms-ui/angular-port/anms-ui/src/app/shared/data-share.service.ts b/anms-ui/angular-port/anms-ui/src/app/shared/data-share.service.ts new file mode 100644 index 00000000..f485b043 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/shared/data-share.service.ts @@ -0,0 +1,20 @@ +import {Injectable, Signal, signal} from '@angular/core'; + +@Injectable({ + providedIn: 'root', +}) +export class DataShareService { + private breadcrumbsSignal = signal(''); + private updateStatusSignal = signal(false); + + public readonly breadcrumbs: Signal = this.breadcrumbsSignal.asReadonly(); + public readonly updateStatus: Signal = this.updateStatusSignal.asReadonly(); + + public setBreadcrumbs(breadcrumbs: string) { + this.breadcrumbsSignal.set(breadcrumbs); + } + + public setUpdateStatus(status: boolean) { + this.updateStatusSignal.set(status); + } +} diff --git a/anms-ui/angular-port/anms-ui/src/app/shared/routes.enum.ts b/anms-ui/angular-port/anms-ui/src/app/shared/routes.enum.ts new file mode 100644 index 00000000..d147b626 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/shared/routes.enum.ts @@ -0,0 +1,10 @@ +export enum RoutesEnum { + DASHBOARD = '/dashboard', + HOME = '/dashboard/home', + MONITOR = '/dashboard/monitor', + AGENTS = '/dashboard/agents', + BUILD = '/dashboard/build', + STATUS = '/dashboard/status', + ADMS = '/dashboard/adms', + HELP = '/dashboard/help' +} diff --git a/anms-ui/angular-port/anms-ui/src/app/shared/utils.spec.ts b/anms-ui/angular-port/anms-ui/src/app/shared/utils.spec.ts new file mode 100644 index 00000000..5026e6b8 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/shared/utils.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { Utils } from './utils'; + +describe('Utils', () => { + let service: Utils; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(Utils); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/anms-ui/angular-port/anms-ui/src/app/shared/utils.ts b/anms-ui/angular-port/anms-ui/src/app/shared/utils.ts new file mode 100644 index 00000000..3e5a90fc --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/shared/utils.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@angular/core'; +import {KeyValue} from '@angular/common'; + +@Injectable({ + providedIn: 'root', +}) +export class Utils { + + public sortKeyValueRecords(a: KeyValue, b: KeyValue, isDesc?: boolean) { + // sort by key in descending order if isDesc is true, otherwise in ascending order by default + if(isDesc) { + return a.key > b.key ? -1 : (b.key > a.key ? 1 : 0); + } + return a.key > b.key ? 1 : (b.key > a.key ? -1 : 0); + } +} diff --git a/anms-ui/angular-port/anms-ui/src/app/store/modules/adm-service.spec.ts b/anms-ui/angular-port/anms-ui/src/app/store/modules/adm-service.spec.ts new file mode 100644 index 00000000..f86e3c4e --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/store/modules/adm-service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AdmService } from './adm-service'; + +describe('AdmService', () => { + let service: AdmService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(AdmService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/anms-ui/angular-port/anms-ui/src/app/store/modules/adm-service.ts b/anms-ui/angular-port/anms-ui/src/app/store/modules/adm-service.ts new file mode 100644 index 00000000..5daa3b3a --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/store/modules/adm-service.ts @@ -0,0 +1,160 @@ +import {Injectable, signal, computed, inject} from '@angular/core'; +import {HttpClient, HttpErrorResponse} from '@angular/common/http'; +import {catchError, tap, delay} from 'rxjs/operators'; +import {of, throwError} from 'rxjs'; +import {ApiAdmService} from '../../shared/api-adm.service'; + +export interface Adm { + data_model_id: number, + namespace_type: string, + enumeration: number, + version_name: string, + name: string, + namespace: string, + use_desc: string +} + +export interface ErrorDetail { + obj_type: any, + name: any, + issue: any +} + +export interface UploadResponse { + message?: string; + data?: any; +} + +export interface ErrorResponse { + status: number; + message?: string; + error_details?: ErrorDetail[]; +} + +@Injectable({ + providedIn: 'root' +}) +export class AdmService { + private apiAdm = inject(ApiAdmService); + + // State signals (replacing Vuex state) + private admsSignal = signal([]); + private requestErrorSignal = signal(''); + private uploadErrorsSignal = signal([]); + private uploadStatusSignal = signal(''); + private loadingSignal = signal(true); + + // Computed getters (replacing Vuex getters) + adms = this.admsSignal.asReadonly(); + requestError = this.requestErrorSignal.asReadonly(); + uploadErrors = this.uploadErrorsSignal.asReadonly(); + uploadStatus = this.uploadStatusSignal.asReadonly(); + loading = this.loadingSignal.asReadonly(); + + // API base URL - adjust as needed + private readonly API_BASE_URL = '/api/adms'; // Update with your actual API URL + + /** + * Fetch all ADMs from the API + * Equivalent to Vuex action: getAdms + */ + public getAdms() { + this.loadingSignal.set(true); + this.requestErrorSignal.set(''); + + try { + this.apiAdm.apiGetAdms() + .pipe( + delay(1000), // Simulating the sleep function from Vue code + tap((data) => { + if (!data) { + throw new Error('Receiving no data from request'); + } + }), + catchError((error: HttpErrorResponse) => { + return throwError(() => error); + }) + ).subscribe((response => { + this.admsSignal.set(response || []); + this.loadingSignal.set(false); + })); + } catch (error: any) { + this.admsSignal.set([]); + this.requestErrorSignal.set(error?.message || 'An error occurred'); + this.loadingSignal.set(false); + } + } + + /** + * Upload an ADM file + * @param admFile - The file to upload + */ + public uploadAdm(admFile: File) { + this.requestErrorSignal.set(''); + this.uploadErrorsSignal.set([]); + + try { + this.apiAdm.apiUpdateAdm(admFile) + .pipe( + tap((res) => { + if (!res) { + throw new Error('Receiving no data from request'); + } + }), + catchError((error: HttpErrorResponse) => { + return throwError(() => error); + }) + ).subscribe((response) => { + const message = + response?.data?.message || + response?.message || + 'Update success'; + + this.uploadStatusSignal.set(message); + }); + } catch (error: any) { + const response = error?.error as ErrorResponse; + const status = error?.status || 500; + const message = response?.message || 'Internal server error'; + const errors = response?.error_details || []; + + this.requestErrorSignal.set(`${status}: ${message}`); + this.uploadErrorsSignal.set(errors); + } + } + + /** + * Reset upload state + * Helper method to clear upload-related signals + */ + resetUploadState(): void { + this.uploadStatusSignal.set(''); + this.uploadErrorsSignal.set([]); + this.requestErrorSignal.set(''); + } + + /** + * Reset all state + * Helper method to reset the entire service state + */ + resetState(): void { + this.admsSignal.set([]); + this.requestErrorSignal.set(''); + this.uploadErrorsSignal.set([]); + this.uploadStatusSignal.set(''); + this.loadingSignal.set(true); + } + + public hasRequestError() { + return this.requestError() != ""; + } + + public hasUploadErrors() { + return this.uploadErrors().length > 0; + } + + public hasAdms() { + //return !this.loading && this.adms.length > 0; + return this.adms().length > 0; + } +} diff --git a/anms-ui/angular-port/anms-ui/src/app/store/modules/agents.service.ts b/anms-ui/angular-port/anms-ui/src/app/store/modules/agents.service.ts new file mode 100644 index 00000000..f8dd09c4 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/store/modules/agents.service.ts @@ -0,0 +1,131 @@ +import {computed, inject, Injectable, signal} from '@angular/core'; +import {ApiService} from '../../shared/api.service'; +import {first} from 'rxjs'; +import {AgentInfo} from '../../features/management/agents/agent-modal/agent-modal'; +import {ReportOption} from '../../features/management/agents/reports/reports'; + +@Injectable({ + providedIn: 'root', +}) +export class AgentsService { + private api = inject(ApiService); + + // --- Signals for state --- + agents = signal([]); + alerts = signal([]); + agent = signal({}); + rptt = signal([]); + operations = signal([]); + count = signal(0); + page = signal(1); + pageSize = signal(10); + searchString = signal(''); + agentId = signal(undefined); + + // --- Computed values (getters) --- + currentAgents = computed(() => this.agents()); + currentAgent = computed(() => this.agent()); + newAgentAlerts = computed(() => this.alerts()); + getRptt = computed(() => this.rptt()); + getOperations = computed(() => this.operations()); + getCount = computed(() => this.count()); + getPage = computed(() => this.page()); + getPageSize = computed(() => this.pageSize()); + getSearchString = computed(() => this.searchString()); + + constructor() {} + + // --- Actions --- + + reloadAgents() { + const params = { + searchString: this.searchString(), + page: this.page(), + size: this.pageSize() + }; + + this.api.apiQueryForAgents(params) + .pipe(first()) + .subscribe(res => { + const agents = res.items.map((agent: any) => ({ + ...agent, + selected: false + })); + this.agents.set(agents); + this.count.set(res.total); + }); + } + + addAlert(alert: any) { + this.alerts.update((alerts: any) => alerts.push(alert)); + } + + setPage(page: number) { + this.page.set(page); + } + + setPageSize(pageSize: number) { + this.pageSize.set(pageSize); + } + + reloadAgent() { + + this.agent.set({}); + const agentId = this.agentId(); + if (!agentId) return; + + this.api.apiGetAgent(agentId).pipe(first()).subscribe(res => { + const registered_agents_id = res.registered_agents_id; + this.api.apiEntriesForReportTemplate(registered_agents_id).pipe(first()).subscribe({ + next: rres => { + this.rptt.set(rres); + console.log("get agent rptt", this.rptt()); + }, + error: err => { + console.error("get agent rptt error", err); + this.rptt.set([]); + } + }); + + + this.api.apiEntriesForOperations(registered_agents_id).pipe(first()).subscribe({ + next: ores => this.operations.set(ores), + error: err => { + console.error("get agent CRUD operations error", err); + this.operations.set([]); + } + }); + this.agent.set(res); + }); + } + + setAgentId(agentId: string) { + this.agentId.set(agentId); + } + + updateAgent(agentIndex: number, agent: any) { + this.agents.update((arr: any) => { + arr[agentIndex] = agent; + return arr; + }); + } + + setSearchString(str: string) { + this.searchString.set(str); + } + + removeAlert(index: number) { + this.alerts.update(arr => arr.splice(index, 1)); + } + + // -- Optionally, a way to set all agents at once (like mutation) -- + setAgents(agents: any[]) { this.agents.set(agents); } + setAgent(agent: any) { this.agent.set(agent); } + setAlerts(alerts: any[]) { this.alerts.set(alerts); } + setRptt(rptt: any[]) { this.rptt.set(rptt); } + setOperations(operations: any[]) { this.operations.set(operations); } + setCount(count: number) { this.count.set(count); } + setPageNumber(page: number) { this.page.set(page); } + setPageSizeNumber(pageSize: number) { this.pageSize.set(pageSize); } + setSearchStringValue(searchString: string) { this.searchString.set(searchString); } +} diff --git a/anms-ui/angular-port/anms-ui/src/app/store/modules/agents.spec.ts b/anms-ui/angular-port/anms-ui/src/app/store/modules/agents.spec.ts new file mode 100644 index 00000000..f0c532fc --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/store/modules/agents.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AgentsService } from './agents.service'; + +describe('Agents', () => { + let service: AgentsService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(AgentsService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/anms-ui/angular-port/anms-ui/src/app/store/modules/service-status.service.ts b/anms-ui/angular-port/anms-ui/src/app/store/modules/service-status.service.ts new file mode 100644 index 00000000..4295e64b --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/store/modules/service-status.service.ts @@ -0,0 +1,161 @@ +import {inject, Injectable, signal} from '@angular/core'; + +import {throwError} from 'rxjs'; +import {catchError, finalize} from 'rxjs/operators'; +import {Constants} from '../../shared/constants'; +import * as _ from 'lodash'; +import {ApiService} from '../../shared/api.service'; + +export interface Alert { + id: string; + // Add other alert properties as needed +} + +export interface ServiceStatus { + [key: string]: string; +} + +@Injectable({ + providedIn: 'root' +}) +export class ServiceStatusService { + + // private http = inject(HttpClient); + private api = inject(ApiService); + + // Signals for state management + private _errorServices = signal<{ [key: string]: string }>({}); + private _normalServices = signal<{ [key: string]: string }>({}); + private _updateError = signal(''); + private _loading = signal(false); + private _alerts = signal([]); + private _alertIds = signal([]); + + // Public signals for components to subscribe to + public readonly errorServices = this._errorServices.asReadonly(); + public readonly normalServices = this._normalServices.asReadonly(); + public readonly updateError = this._updateError.asReadonly(); + public readonly loading = this._loading.asReadonly(); + public readonly alerts = this._alerts.asReadonly(); + public readonly alertIds = this._alertIds.asReadonly(); + + + updateStatus(): void { + this._loading.set(true); + this._updateError.set(''); + + // Get alerts + // FIXME: test display alerts + // FIXME: this.http.get('/api/alerts/incoming') + this.api.apiGetAlerts().pipe( + catchError(error => { + console.error('update status error', error); + return throwError(() => error); + }) + ).subscribe({ + next: (res) => { + this._alerts.set(res); + + // TODO rethink tracking alerts for multiple accounts + res.forEach((alert: any) => { + if (!this._alertIds().includes(alert.id)) { + this._alertIds.update(ids => [...ids, alert.id]); + } + }); + }, + error: (error) => { + console.error('Error fetching alerts:', error); + } + }); + + // Get service status + // FIXME: ('/api/core/service_status') + this.api.apiGetServiceStatus().pipe( + catchError(error => { + console.error('update status error', error); + this._loading.set(false); + this._errorServices.set({}); + this._normalServices.set({}); + this._updateError.set(error.message || 'Unknown error'); + return throwError(() => error); + }), + finalize(() => { + // This will be handled in the next observable chain + }) + ).subscribe({ + next: (res) => { + let jsonStatus: ServiceStatus = {}; + + try { + jsonStatus = typeof res === 'object' ? res : JSON.parse(JSON.stringify(res)); + } catch (e) { + console.error(e); + jsonStatus = {}; + } + + // Filter out status + let errorServices: { [key: string]: string } = {}; + let normalServices: { [key: string]: string } = {}; + + Constants.service_info.names.forEach((name: string) => { + if (jsonStatus[name] === undefined) { + errorServices[name] = Constants.service_info.error_status[0]; + } else if (!Constants.service_info.normal_status.includes(jsonStatus[name])) { + errorServices[name] = jsonStatus[name]; + } else { + normalServices[name] = jsonStatus[name]; + } + }); + + // Simulate sleep delay + setTimeout(() => { + this._normalServices.set(normalServices); + this._errorServices.set(errorServices); + this._loading.set(false); + }, 1000); + }, + error: (error) => { + console.error('Error fetching service status:', error); + setTimeout(() => { + this._normalServices.set({}); + this._errorServices.set({}); + this._updateError.set(error.message || 'Unknown error'); + this._loading.set(false); + }, 1000); + } + }); + } + + setAlert(index: number): void { + const alertId = this._alerts()[index]?.id; + if (alertId) { + // FIXME: this.http.post(`/api/acknowledge-alerts/${alertId}`, {}) + this.api.apiAcknowledgeAlerts(alertId).subscribe({ + next: () => { + // Handle successful acknowledgment + console.log(`Alert ${alertId} acknowledged`); + }, + error: (error) => { + console.error('Error acknowledging alert:', error); + } + }); + + // Update local state (if needed) + // Note: In a real app, you'd want to properly update the alert visibility + // This would depend on how your alert data structure works + } + } + + public hasUpdateError() { + return this.updateError() !== ""; + } + + public hasData() { + return !_.isEmpty(this.normalServices()); + } + + public numberErrorServices() { + // return Object.keys(this.errorServices()).length; + return 100; + } +} diff --git a/anms-ui/angular-port/anms-ui/src/app/store/modules/service-status.spec.ts b/anms-ui/angular-port/anms-ui/src/app/store/modules/service-status.spec.ts new file mode 100644 index 00000000..12de1b8c --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/app/store/modules/service-status.spec.ts @@ -0,0 +1,17 @@ +import { TestBed } from '@angular/core/testing'; + +import {ServiceStatusService} from './service-status.service'; + +describe('StatusStore', () => { + let service: ServiceStatusService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(ServiceStatusService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); + diff --git a/anms-ui/angular-port/anms-ui/src/assets/fonts/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2 b/anms-ui/angular-port/anms-ui/src/assets/fonts/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..454917f479c5445718711cd4f96572b2066b3e8d GIT binary patch literal 104732 zcmV({K+?Z=Pew8T0RR910ht^C4gdfE1b_el0hqx60RR9100000000000000000000 z0000Q92*cEgP9TrU;xEv5eN#6!6=HLA`80!00A}vBm1dIum|2a%IS;Ue5l zbHOiIP=wPz$J4f}JVL^6hpJGLWH-gu#Bmh6`qIVi2yK)Q{zIeGgGHGgIWxyjB^}kBYkpX;%7v z!%ZF>y`b-yJit2~<`)x=7Pl0c3^3p@?4~T@gkPeMbrt9NvU&viCPT7=hsF+eHPKpa zA@(=-iKD)VQS6n&a0>A`xXNCet!OA(+VcR%@vdyb6mAH`ind{%#$iTZGAIY_Rq#?t zl~hUG$8p3VMMNN?n~VI7$J#Ixt;@p|iZI7ENAXcYrbkjFb`_NhZMg2S}RVgH8a!9Oi6dwj4}vgfc)k%pp07=M$*|Gx$@*oLV!56lVn?^q;X)x z0RQQzBP`(0^SRj{TdORxypZ>R2;> zwF-9G5=Q8PcF#?x_D{9#NZX|AOqH4T&ydX4)XwbS7iVdwzx{uFL!_G*#!7(vCm%A& zV&y-KrR;nlfsnw?hqZ$w4n&4Kae}o&6sVxPb&zyfQCq=+b#xHz0vz?Jc8evpcG%k1 z4E@jf{S5rgz+VJu9T@?yP{=vi^~x2Jj4U_%q~w)m*g=o znBCbAUah9SiWRFX0aO4>0u@-n7To`mVE+3c3d8sW&q_7LncW>xI;AwdGx_ijon9CX znpW5DwhlQ|jg%{AaKoh(KlK2<{F=5rkh8NWR40U;76pRj<2%aThz8)M)fy;%{~+T! zSIx(FHTvsyZjjP(OA)8+MUfCo$cAoEd)?2@W|G_f)6hyLtw)rSPTadidH^Y`wJd+sCnqALqDj4+^S9w+?gq>6$j zC@?rMyqncS3oY*vx%1e_nXQ&*@{^p&TGK)sTCvCqcwA;YK86(K|G!*jzxG&l6|_0f zEW?0-6SfDqN@ZIY+uB5zt8|rKZVf?bx9uS?&;&f(Z5QljmmOGVX3m)U93fnhg%IEP zUZ(7OhH*=usg~QPj3LX=>Kyiv>B-vqFx54gocr!;zNRz62t_Y^O(=@~@GC~>jS$8- zrIji0eceF!{oQ|ffPpj<#7P++L52Z!KV1KQ`Zl^72ABXhKqx~tmzwEU)wbWnj><7J zc>n(Zy-8m3wFRzum5W}`odJ$?zOR}oi%;wB%>G=M5Y@csr2>7!5-cZjzH)`|5aB1hp4J;Y=eSL%`eC@DpBIAB z>FVq6L!Kzz%8AaVUuvwxcnrbez+z5u@S#=87AD+y!r$%Bz5Btss*7E$TG126>9B?c z+N$&Zl2f%VQ5k;4@Qcz@Zk%h~{vfAt2s?NJE%PiMgACv#zOzlhi z0R+vr;I~vp{{MGZS}V<(nBrQOBrZt0?sHx5)vHFo>V~O83qUtO0@VNsRD-10AT?bE zP<5fD1A?46AgTFFN}4Y_e@W^aeNO^{W(MOqUb9wOYvb`YS{wf)`{4%RX8Fd(1$nUl zJC}ADph9WLic7Ek2LO7SIQgO143L$X^|RCV?Cg8ftla)9v8y?=nx}YLx+DD|ho_`t z4c@O;xu}ni3Zk~yF^w)%Yy*WF)$EAlDuMqCj3`v|E*ecv1k`L)6+lLJlcx#Z^;|wiPA*<4R`;I`83x(g=6e~-3@fV zMuWg>Gzc^Z3Is?BL8Lo?63`SS=7C_%uO>)(Y1uPH%M)ZxqWP1YYJrBR0YFLvfTAr@ zmPeu_PY8+9mw$-zj}u}#{^N#pIqvC_uctX2mj~;f_jQlmTe0N^K=L|;WFndQBn9*j zeYNZp*Z0RxF8Be$bk$v*vD5LUnB2s~A{ozxwq<)(fEWvP1TjIu<3xM&EWZ5PCXhpb zdzj!FRkz$R=U({zzqWLt<*s8@RgE!Xj1dtL5i!;}_I-C{6-8u1)yMgoy_&)VLI`6L zV*(SHAP|8NLWsbu!G6})LkVq<_5cMU0ty1UyLb63yR`pU|!U|LlTg`<2zeB0?ZE1tFwPGSknr6#efW2oQoW zrEaBUHAl9Z;CVAB_c|5#cb|sCoTI9~1CYc3c*n%WY~P;NVyiP4YcAlwk)O&UMyid# zqKgDb2brxvDw|SLsPcU`Z*LF#h4bI-#rlT)es&mJ`1`NxPE@k+W)*hutR2&pUK`(r zfMaC#=6KGxw*wsN2q!tmg|2q1J3Q2fj(IFg&C5w~Xr@eAQId-9*`(1CG@8>H9ZFGJT8Q+yD2F!VV-J#zerFlBj{RYo)V5 zt`ib0b(C&wFH}*5V?+f&G7~aHV-!5fjB>NwXXYsEEuSAwREFglW>}p|D`^|;qQiBp zF4x_9S|2ro9#RzuSPalOgF#|ppr^++W2Rg)3d}@WTnAz@a{hC4aq609{!-$HB#BR{ zt>D24w;LLqLapuA;AGz%8^O-@GG^&ZHkVg%^dIbm5 zvW1Z_IFJ}#9W#lE2;xDOk%RPpWv0kUG4ut>IaMvAN3?UC?J1&W=5^7by#eD`)b!$^ zXn0onRxNkj#kQ!}+S28V6}l)kvzEwyW&(@Mum>!dx=b|LJZ$GTQzVEYn>>anBK;Jo zov4znRGPk8FpkWIbk~NM?A5)Q93Z^?7)WmT?dgiffqx*0LC9|TBbZghTVD#!QBxy@~q7(HsyeJ`-M?pb-Lc(xr4z|Qy+Z5^KhAJzcff2Cmy z7KUCY&4~H)@yQc{`>M%Dz7|lR3qfvhitwbaYU`JK(1?WnS|zP1Z#P!SZlQ`IYGuq^ zw02TR`nko!Y9c8ce70a#y<6r6&^X|#{<9;oid~Um(JPPkmPdd^uLG3wp@Tl37iJA! z^4krvAnFQFYb_`cOqg{x%|2b$*lG=)fVUu(LK2)#!vhBe6oDnd9424I>`JY$a!u}( ztwdFekxWWdJ_VF-@o;ZkI;gtgfP0PR3h+R=I<-+JrBmEkh1@l{7oqln&$GHmLxVJo8(yQ?>fwY zoIYYinhEKR#yog(aBk|`&3SsVEMbK=skO)?{nhvE~Ps!WXH_|7+Tdhsw@5ZzJ+&tPNKem^l|w)3HNT{K`%1@5UV zt+#Zbf)7SyRUX`b1H2@4JYzjwpU)}h(A|lywB|qI? zXq)zE@oqyC#Rk^?y_w3|%eKIyGwcFhD+Ir>#SU69)$dF^#NV@hvuvyCJ8T zP?J9@z&*I1$Xs!&P-6J(2)>I!9D|%A?yUX`Ax!;wSL2$RX^lToIAPX1mJnC-E$>F2 z@)f{{sE8cRHW-gbgobo*Hps0al^1g%l8ICzsV?OQ5OOqxKmfzaCYPqDMOuqcD%aRc z`9vw#U1>VZE1A(Gno4tN6>XrMbdb)~oqAboZJE%5Oh0@P3J^FlAcgkaBhnI)Gtwt| zeX49#$i_sLC@By-I8~Gwl^$4ObXT#rqD=%3V_VOoNViWcCD!+fLVF)a5mtdggm%)Z zWiew3{Ck_vD+~AbhDMT#$Jx_&SiwwL>mgeC{~W5Pz()#?F!MmDCAU2!f+KC6-;aPF7MV6{h^BSc z;v9HAoj7|vtvGW%jW~ThwK#P>l{gud#_hiNIxsTyyn=LfA@}Y*+5=j(D5px5D`#&M zPkYm`Q`tFxRovE?WP??4=>h8OWtE*KP7nt_O&p1bpudzTcAJDT;54z3iGcFyDpoRc zM3@oFM(&25O->gxc*TOiRZN&M?wlgVB5=>^sUTL^8H;%UJ@ki>^S#`0#BFT^3C!%m zwA(iuS5*JdKw-1u*(*L3qj6SEEl6PSz?3R$r>5-!=(-sK_xiG_n(P<9X_LqB50!sS#8}rD?$R!0~b*Lq3P6ibY=q4h!@lr{!K7$CKt<5VvvURwCrCLRzp2q zVEL!zg3i}bO%Sx=f9#GPJ-9(BDLpzJax0T|&F2q`!_l47>M)4rjR-AVsH?%8qhArZ z;?5L2lrLyof4zLO-`C?&%yvGwZQ&Mwz5rw`DHteP$TGE?epT2+T6-yElVm6P%dz6%R z%|Q(HzLfY@N)c?JU=@-@a|b3Ky|nG@bDt#QX&G$>IV!LqHh9>Y^0P4BEkhu?q9OKf z57+jdK6%pvs+MHngY2Qb@>wj6c(gflj6{oOUPIN|Ua@b~FEY6lbGRDB!bH?%50PQ2 zXgjXhus%5mbSNFT<(OEx-X}18*~bxB30|}GV<^Pui=F^Kj^J{hZl7dr+e6-Sc!09T zjQQs>3<@L3>-BBO3d<`ymFF!G?KORJG>Y8Z(ko9<=L=|v(;yq4r_-bqjUv_gZ6D1b z4d%iSRy~(yJcYjanj`&AJMqZ#??|73gU!hED1pLC-FF1`y?gjsI~F;4oSM{{^oNr3 zbpbw!KFZuc?4RaLrcj$aQ?AaD<|QONly%!3qWUCM$?6zlZ)xfDIHVQHBo-bofq~{u z=SMt>!x zU`3T~oT3@Yb|gb`swHl#nN- zCjauD=UhP|TghhAJ`fySDS;xPA(2nbshi3JVMLibnHN82sZ?rNClc4#W$Zv9+UZFU zibi)OvvTgt+6)eVxh3BvMzmlKs_K{#vh6^IQomS9VGQ7dQtFnzVC1~5_dJPQTMhRT zBZl@-4LHqTQ<_|U-Xda@GzzgD5HhlDqlDVF5!8lAM@?NJ7>P5=RgK_9q9aFc>_G9d-8=3|}B=Z?9 zq{sFm6m6#|9oNJMXg+)w0yXFMQnQ2GB&h%!e&Te|sL$)usR=8xsU{n~a^b{jW(ZSsShBaJ)W|fTz61Md z0G;5&AydGVDn}=A%7a_LCnQ*T8M8MJuq>5czezdJmeFc`a;N?@-Qm+n%-Ur2> zD;FulO{f`psmNmpn(SF>Ow1uwr@ z<5UlIwj^a?KNc_eEM<-17uS4Qqaq;gBilICLBr@4KL#V3SaLt{=++M6$6_J%7jLz{?&*n1`-5&^eU1 z`qFY3bmo<%q9auGk<17J7DkPGpCUr!&lk{?3BzSUd5MzgjSFL*>K?y-ii{*A<*%Ut zlil$w)|jRfjTjNHH$}@MxH%w&lb7dJ>^o%tNRDPyFRXj;SQdk5q{(Cu05f2i%VAp2 z19RK8mf2iD+A^33^M7Q`U}r>=LEgSE53+9AdE>zu2C^)^8^4!RvbcW9{|PL4yPws40DAzP6JiLI6UtSE0(jHc{L3} zSG0$aAPi^kd4lGZb0>n;VW-6%)m#u)*zc&xl~fjM9R*Z(q}O`(H*-+aHk+bq~j<;XbIP0b z2TXv*qbyah#7|Hk67}S<;3(vu3}$rlsow}%cN@P@ij4RQw7dE~4Y1EAZwSy>5uanF zqZ1{JP>_HCZ{~-l?;Sq|9hZBP;LC<-N}mAniI82En~%;Qp)Fua*9yxT(!Svcz_pP^ zlyEl!L4K6i`da8DY}!xwmPVZIVt07P7XTL`f*#Eg7{wL`+lxAp+G413sgqM|Th-4< zwwsDPFuwpx``V>LB=i&=j}oKGBbm7*jQ4pv!g{` zt&IwTyE=tBaz5&-z0b)!ee-U@rZ;r`O1#1T=u(8q+Ivjbt%QG}x@RuZjEpbel^@0D z8vn!68Il?qm)jSWB}gB($~#IhL)I``j;pYD{zB%-GLVr7(J;oDRNH#!N1Nw>r)^$R)U8+>?{>HaOYSu<` zd&6E1fOadU5_jwn#;nSZ!|HNTg!}t>w~C|48t+R5M~BjB-n7g5toUA+gk+Hw{*f2@ z;ou7kW{P1PHd&gs+O80BO${FIJf8K+Oqaf}gV_RQZ>;IWrHo`2>o0|_gu*KDdlgL8 z)72YVQ;K##^>e!TCC%kr(ep+6OAl5fpY=jH1~(&D7ZPQm1-hzTnXdfz7As|tF7S}_ zG3q2Nr{G7KCks01>%v;>UVP1Ig$ycLCz=VfloWagFe!F>(^3HvrGpJELSMX;AK=>Y zf{SGN^a!)Xy7FQm7+Kn9Nm%(pOU}G-J!yG#wgMgHmWW83y91SfZ$6k*{i%BlI>Hf7 zQ_Uyx3#=8*@RN@oE)Zf~>TI5hz%fi?-~K+p!f9BIT)EjN-IP94&_J7kz;=6Z-9`y- zmRq6^rzWmbAIF7an!+kmm9TB5Lc<5rv=0xIwJIX5bZ7b;2UNK95Rov#~=4A%Z^V&m+FV`mQO12JJj;?)#!F~QWj zn#%i7*R#?l)upw|hx=HU=_n)XHh!RzHn^VapUjvj*jChGZ1lLS#iKD1EqP2NDk}&o zMBX;O_(sMHxgj~JA~w$CB)zT*itksXad%@Kw?ODcEjP#c#WgE#_AAP(KTGO8MOf3< z4z$B{pjQi%NWsPMm*iO%1U9&yX_x{k~cxAlT8YSL^a#p zm&g$kTOXF0Gl(Qs;l3}*Qka5QHlfob1L`2zolA1CPSAl(GYT>3ipCE?(kAkKNs5HZ z)~En3mDFEw$BCeYZK6oxcl-)g`^P3wVnjO4bhW4o6Z+V-ZzT~8 zhuXJZlJG7NB4#9XQKqrIK(Hd1oh*|Q_^uD`Tf@4q`CnNCnDy7=qZa8I81%QFJr$`?)aN~GN)zOR*=N^C&9zMluY(7$QC{u*w2wCWlaT<3Q|m|QxHQU4OHwx zAX%7uobd_}q2Ot?p_bZxex4!3smbz`$qb}SaUFrasn*tg%o30@y55|=-G}Rgixx)b z2I$Z@44rjRo5IislI!D(X$Ev-2DZ6g9h(9d{ZM}ZWj7mFs&$i-8X=Ehi|Yi@w%;r& zQ>Y{CG$recpUcyOI<@yhoGzo~G=+r)p_2{Mlh2gq8dI zi6fcM0Q>5$ZMYLS#*$0NJj}2w=`JyFifoGBeaWqjxj@@Qt}TosuSFR-q{OK*{X}+t zSQORVMwOO#qJ081H_}?5OpORFZAMx1TPLFl(t(48`WQ0Z8f|a)VdUy}Ri~A6dz5J& zi*E#PdsRsv_hZQD1PYdwpGL5%Uh&)0%y{}+i~#s%$Xb3$giQFI@;r!N`p;bm3(aYI zEP&5%$2rDQ>BGRtQ1p;;y|7ZFdkCKLJhZ5#NH4+|FN)SO650TebYviX$G@8DIY4 zQs;Y-!aadhVW!PUOAwj;7Y8W%ctxMv#B?-1X$qKLL+^b?1IT?h2h8CN>;nojg=b>M z$VqchYCxf16pqnzP1V|C0#eWf<*3bNaegv2_^jb)ib zWN-aLUpM$F9SCL)PeSvWnUK~aUXh^Sb3vCJ3J&&$mM#Xh%i|(gIvDwTX9jJ^%k~}4 z6u!XTgb8h&!nrlmo%$czMR6<9~>cr0RZAg!CtM#EtnNlIi`;NS67Z7uJo5}u^pMEcR=$^L z+%q(BZ7D{Abf1GoPi{n@jb0Kl>m}0NiB`d%{;eU%Wx;wYHqQM9hWI8m)YF{;)Im*m zSOc14c;Ffw-G~4t)n^ypQ9Rc5QP;;*hGCD`6chgGAx`SPAXiWOWGj-(brpi?Y)&4l z&AF#7+wL0(Q1|XWrXViw(cJJ6(M&K`ZmteT+tK2>VaVtOM>hq@8oqao1`OW0+ybTOX!<|RG#EENM7bSY z%UX9TiujFPF@$J`!CZhI5Q!|R(}jUY>{JVA^66m=!19U972RV6RU>pjqsgfC3P;hP z@0qj4AtA4sIKkD>P@`Rrx*|S7NGdi_o5H%dk?u*dYZL|d-4U$w<{}iGAW^TDWTaLll zhK=^;vVmvc*aCUWkl(Q}-lt>dl+A8eH66m(KJLZ{d9VI>=Ej_lOVp z8FXeu!55ka!h(Lvd{7w-gqD^BTk8jo%fW0^2z4mk1%?&G^az#L;ncLrbdVsFk6R~E z@VcBuu+wx!YduXx!PeHYVxMH51kU@0a$&_E5p?j3lp6+pjelbyG?5BIU2c6yHL=bY z6;Xvc)_5bE;XPw`pqacz(xM3t4@I^tO#&j~swxe2QRXC5MtAk}76RPVES12D)V3cZ zO~_TL%E=7Qs><1Nfs5a+UU#K_tIDQ*PAmny-?AGE0L7Mlq}n$J;4s@}K^b8!c@r#I zD11%bCqj`A?FV7pVna}$|GuQYC&xLbV?!o2tYNLEX5fx|gw^8fI9w$ByM1%51DHV` zaF<9uKUSuMDfXMzNCzj46)GDVeBA}a-;NALwfv>G3lZ}CYi=(jVS6^1vus3PeIge+ z#A&W>6W_)kSw|>liA`&E6H(9-t7v7*i5|G$-o2FLOFHs53VW5}b)r&-^iDP)acWP6 zuTu&|INqU7Ea0FSH(akIhP8EHu6LsXPYEaZCMBw<2Sc+PR8p@54KL&Y6Zca5E(H)U zyrtqyZ&Q0%J60z_fA|x`EC}8$^eQd5(2{7FG)jAI5ZJ)P{EY@ZlWcJ$45{)S*mZem=Zb|#% z{+;Q~9|yCOF8$ic%Mab-9Y>HbKybJMhs6=ZNTB9VDn>54Taa$HCJW@FFP=yIDqNI? zfZ^nz|4B_zD?aD>bNuGO?yE;Ms%#`seFuqlE_bs;KSeJ`%FhsuqHK&IYA1vo6G;>Y zqlYdzW7PSD$!NJ`Qe~rR(qboF>#JKCdUYcmKU_;ERpYE^ly};7x7Jz5G8Dw=;Kzv= zr^pe#p#-J1!g)vBs3Z>HZV5!w$dMU_R8vbzfGoNEnFOR);WNmvXNv$cSski%LO`N+ z&Rri6?4!j6Uh>C?CYDyfuCYECfChl5&Z>P8@+yM%*&+u!(ICGym$JP%3N^zG0&RNF zUVQex+DoNj4(#7)&5c^v@U{iN*Pa-{YSkVDp!eO2_5tL;kJdH2%uP-jw9=Q_OWCdl zLaQDNmUGz+$EBE^c3y-NTYA@+c1r9EnziHphsBi9)6rV%F@MQ>pnf-uJ;=JZ(umC`QM3%} z*W;*Z=+4p4LD;SG{VxESCvQajoTIF=b`o6^ka&ftkmd?Qh%+4JP(|5xQpg*8gZEkS z)QZ6Qn7bkN5k3ni!)_D-I68E>;3U}RJ!sKVO)TDYmtFW(PhI55&ZYAZqL1c4icxl8Ic$gTD7UDycdrM+1z_c#gh*D;X%qt}u zieNW(Hzv23lNXvX&AxqYXgD>EwSdvTzpKz6w5YGjDRM)O+Y8PQ#UzL1I@l! z{X3I=3_W#Z6$F7x#n{$4?7V~P5kCqIh(1yC%;}fWm{U2brIm51bLfLx9Cvt6SzqOc(yi*FqEIc@i_$)jDML5eQF9oQyIb!ZA=#-Lae}%r z^fdU4>Lr-cD*@A*+nUWsASgp)S{kOO24o(ru}Fz)Q)aW9Rq81rqebv(PUE6)Bp~aW zBVC%<$OS^aE1RueqGKH{y7fT9GqK=xg@5wkR=Nm3DV+nDBHwU7K-#b{d0a)YT5mNV7R#0)`j$m&P2r8dNi?`MxM|=i zW1PBv@Mu*`1`b5-59)4(!;nog-#@#{B0L~d>KGqfS~nIA&y5aovwN;+b3_xqhK1v4 zTHJu+XxA`lHR=MDKEFF1LEGwB!&$+w-;>9(QdgZ+o*mY-j9{i>!lP$Nxx)ZrZR}a; zuu$IcY&lSF9?5sFAV)_Sx&fq6S@s)mng{nr6I6EBfYMkfB4EeN?!pN`wg-kj5;!O} zqzlpV<5?Ur0j@h(2cNhEC<5}kSFhhfl6J-7w9AdIlh(DgyW z);JIYXY%a(+v(YuNAAkiQt<{Y0sdhKo_5WCvP5!!5H`-F!QB%Yh|b_jFz`_(iz7#; z%9OlO_?WueSmYR(JrXvC(>U#ABz$O`;?Pxe5f0is@~H`)0OF@&oly} zMuuKA($QkTp?{4>!C=P@y@61Ooz&9DVh!s&BZDl-%L@<>76c;8sY2clWshQTZ^&^j zU0pNWX}M2K&1Iv6F6RFz_qjqq|4Xd| zMS#)Kpo)(~lYZ7}Z>H@0YqraAneFO-aQZI4lKYp#*(Yc7F^Px*Yd9Q$Lf{1odlrl& z3=BA?qt~-p%4kp*l~5|Q8q95sJR92hW=Cwe3pW>^l395ny+I9r0}n(H@MwcnRaG+^%BjoRK1d8a@7Ei&B%s>N znG5t)uFxANrmeEU*K9(hBP360S7_jl zO)pQ>IQMeP>u^Y`l z0wd`Vu?o|<50-9B2L9IW@#I##2U?m+sPz~6334Qrf<91%F ze6~QkJoZ(aFi0Hx%5Nf)`$AM6N~xD*-mLjAVW zn0~@@N49$MsNw9nZWD-BcJo|{kv-1jKM{!9xZrr!{K}Fw;mqf08?(weg6T>hagC6M zxdU=m(n4MQu-PDau;oyPEm}Y+iLYg)+Mi@>@a_6XjU&XX+GzpZTpaoU{g&; z<7t-1D$P}lk~&s}VGpx3`Gx~B@R^{T$=Wh!_L~k4G3|U<^iaDWN#xim=6Tj!A)_0D z%o-^Li5dtKv^np>g28%DCJ>npH)uqt6eI5@ksg5>$pGhS!IRVYnPE#XrZz@za~4lC z`aDV+UloP^WMPkKd@OOpew`Kh)QmlkQjRxA(M(;3ix-X|1z4-$Jy!t)mo$e!4Ro8; z+?+@{4W5C8ObH8{p`=iou~06ZZpOuwplZCNEPjX9A}5=fkaC8Ja0fgqK3ykG`q^fw zbm^ZafaRq~9-zoC*%_^g8y?EQv*s%K97ow(2f(dy-^9CV#DRxR8$vo$WC7&+DV!#6 z^ZL*SoIBl6*HmkYeO|0>*SADMlIzaNCfwAw>7vDY;D>OIrY-ksNy)<82Z+s;o!nuq7{V z9DR5Foj8$Oy3~DM<0JlvifoaIL0>*##;{N8@xNFw#(x}To~HwL&&Rk{xc+l3DR~)A z+`k*2R%M0}uwa>T9>EMdPybv23KPCPM^XlB32$4v`!}2BQ3Xw7{s)6->6nn7v~_%P zFeSFo`MRO#uu-2`nY*N1UgH06e8q3!c^79=At5k)M9f=+A!p(ado0c?WhhLlL*>gP0@NQtn)Ke(kng!R=&%#O!cx-tUfqNR6q z{xH0Z=N`73A{_Xpnn81oL76blaU&50)9tyqdP-*;zkm@8&vT_FU}BA}%r}Un{@0;a zhY3ObuMzU_{Pw&<;brmGkaWJ~Rw%ouX2mUkMfrk@#liWqmB3Dt&-i~=T!i8J^FhGe zh2l>)OOlTVw#PEj!pAaVJ)i&f8CLF`YV@c1|c)T~HMEv%jw!L>YZ{8g6F z9Wl1`zJk%{3vMheuxPyoSc8KSMJ60tT8QWuS+gqiEa>NxEV){6pjrc6qY(I{FK1mN8^a^usaL8 zfmY-0UISW;0PJAJ3ZPzWN2-WbKmltw%-zEf+jnnc@>h|cXuZsXO`pL0?N5Y+At3A5 zG60YJx|ku%%5Je9DNdxfEkTlvK{r{Cl=_(8`5w*}!WI6}|2{$MuE2BtvCM1)&?ny| zhg6aAa$FLjRymY_l2RthFY4sAnKi^02#+9NBmxXKlC4KpZmYae_(a>&_fO*Q7^iyZ zNw#!9`5Y~uG+_!gBML9T%Gj9(5*M)0^S><5dQQnNIwtxQe8a3Hp{P+wL`p{NZ0_FD z&10ALE(tkHkr0_09KlG)XMXKVtFHHZWosMdRqY!GE-jxO7+zRyWXbyF3&(Y`|DMv9 zFFJbZ!K1QI7WASI$iY91BP8$YarifUt6E?DZ{qH$$9Arw&k`1rEbo7UfOr@Oqj`{&v)7bFQX@8?Ue z*_NZK6|$wqiHh9ADP#w1y3eU1+*xc*@~LjdR#;JG`m?HL60O1P4{g&nZA7fmw`j_0 zOX$t1XN96yr`}+oO%@tnKK!5r`|BVpnMFUj(2&?p>lz5g*XXK|kmi}O#S}ZL#zYx5 zEi!da!^RJcQs|i~zH+lh^pKvq)duf=?Gy|B&5@U@OjSVUsEv*$NzYhp=rt=*dJvdA zdn7hDud2Yr+)DunxDjzJeh7;Yk#zqm501t$U+D^S_nUoaVOfF?Qm);>z7;{XOR70o z41rmSo_V`vXc0?HG$yo!&!E>F%dRxhW zM^qDw11Dn4!xc;+3KfAvz?l*6!0|{8-A(l5>55S%{>!0)l=`O$fT#5yuv6Vk={`RR z6q$zWuJ=ZDeV4)HpLUMm2EW|D0zfA_`}ig)YQWt2M#W1k4R-J%%i7_!MaN(7#a1-? z^9@e&qFrT!#thnwmv~_|Ei+wCi9q9KG=u)!xQ!#+xSV^T5evd!-?Cz*L+KH#S6+}a z$WxDxhK*U~2$-B?*Og8-v|-4U%b?TZKN+m6HzNw1?s(>;S{9-b6bFrEZjwCH0XER1 zbnR-HeH!ewjhB%1Sh)^{^UNOMm(jJt5i#|Q^Kqjz;b{z~UCZJ{yV2K%jcYbvzVm41 zXsNj-OdyR_jpH()#*WRR;%V1-$hT`T{=s~m9Mc7G`l}mATZJXgUi}sn6sf50Fw=N~ zqFRKEiwAI>b~Tqm?b-z6wQ8E<9mIa?PT@20+C*Tq5fX=IG>>kI>)5q}!D4X10?g7}KdSa3|#^j74<#Sjj;O``-axgAMak zWTFQ-;4JKDMCW06ux$Fi!_+kP|9u$NkTLXCpRU9bB53+Gispcqxm;9}BgY%*ibui} zBmwnvyoPc205aCT^r~Ylyyx3oG}_!L|IsI$$LD;|$mT{Kqq5QsA;8fdj`9ZSV)nt& z1{jFav1m2Z;SJ_E9p6I@;5Q}>8O~b4pzX>j_Z!QDqCGK6r!TwoP7qzGnytLir3i+W z(0!0lr%PF{;b8qnpm*xb8?8+6o?IWrcQRPk;~DC`BIA%VYE%r78|&R@V>CQY8)xlr zP$(z`=r%EL9Dc6#*lh!Ua=zOJ7!OF?SK4r;^8`kcds%cN+5v;zwqG)$lCX03p)uqi0-;H9-Y_=-P$0nXEpc*==Vs2imW#p(>OZRtp^u` zVQH3!IKOa-1b7<_gjP#47k}l-;F2|Kg1Z{lkS!5!*s$m-ZNPU_?pPHB#8X3s$8E8OB}Xu zc~Er5?USz4+WJ|4+b7MMk{1tsQRakyI;mvsBi7eWu}VY>Bh3m>3yQ$f0B-)hl5|Zb zuc%8ROSr$yBcHKnJ_D^j2^z(2G2D=N^I|@}X)5ZcrzY+K5!zm8ou!R6X3!>jz+FvZ z;U{c=29IJIe4{N56Z6OC25b7ZqIOEZo2-e)lTmW}mfwQ8#f3eti9HJ)KQ zS<~3im7>MR<6tH>(2QiaL`Huy!pc)u;$! zrFLUuD+biL7!eo>$66pZ1h`-|h0bQvEq_+nfd7jZV1ZmU7V0#aJJxvvmG72xS%T&v zr{oOqim-8-_0i5{VNn=ZW)|r=VcH*YwGx9)nZjH{POb>_*>*}5QFFs!sGVT+2p@2z zP}(6co#YuRHHn6BAjdN1fCKTQpYutDl0`~YDa4&^vl?6mT~ixNk7i3ip#Tw!-VgziGT?~rj5doC8?FbQRjiu z54~4KOLb%EE``4BSy6TO_qpQb;CTIQ=40e#m9#5!-XZf^c5lc(6c-w|%SXqr|Q2xfJG_Nst+GoT7sBxITsG@FQd zM%bN@sUI*1^TZxt5q^uSZ&@UoQgOy#1kTZ>d(Fa|2Cuy)qyLO#E2vdBo+!fPsLz?5 z;})4)_W*220`p69UQ}Cf7dSOY*}BKq%Su+%B^+98B#Wp%H}kx}{Uz8rbK+l`%SU$@ zrnb7lhd0uQ)~-cOXG1-X?w5k#N1hP;X9=Mm1$d1HnBGq@XH|wqf%aJ4hYO(B%v)joUIZj0GNk+we)VY5ZNsoBy))FD4w+ZA$VPf@gZjAZY8D z<5L0e8i|1GSQinUU~QMhe#;2#(6H}|!K02ti+c-Bvq{wRd-yJ5I7Mb<4e^LQfc&e7 zew>1}zlDRH3|MfisuCMI{1mR)sz~^?PY#_c7m<_Q*o-M;GhGMpu(XwS#AO<$_FGcDH(G=oF?uz| zH<|b!dnm_psY4v$VmG?SD}2LW$s$=8`r&EBPW~K--0EI3W{~-Yd^z-SN2~)1dZ49r z)C{ujP$#s*45$0ti->*B)*#_Vare{joR(oI|1BO;Fi>HL6X?2iVgF!r&&kp%)?#9zBT4Jkh{e9aak>H6X9MgddN3W1FM|D(tRqNZw z`_aP;FP2Rxx*;O??l#Wd0Ox483)d$fJX~&GjDPG6cr(`{SpVfn%0j@&3LIbqg}pM` z!w9OpsbaFw2n8;^k%lInlIO^tQR*>;m{&{5G|eam z_1?~kIAvrHrxJOb*v;_w5Mp)nVm5=rQp=AGG44ISG!QHqgio5IJW?{xBlrlh2Tx#soeHR>H7|ZPa2`KVInFNP_*(H7sOQ zuX(~*hJp~vWCY6v1th#Q7sL4UnbRS6>dF+AV7fO~9OLC4Pi7NzX5l`6#vQsoS-<%a zG%Z^3{w&Cby@CX&XLCoQ+?eiA(;4$g9yjOIZodPY3GliYacx9(OHqPNX)jHP4vZDd z$p}lR@ds3TN21VWc;u|V|0Uc&9Pcq|Gn^tr-+@m>=$bgn2ET_l)HcO4?YSzFI6cxg z^P5?;9|lX?JzaS)=^2pm_kK*OWFh^Ed}KC-{Q38ThX{kR8go7OMDX5?jOibjUo8+D z`Zd?s@+LN~6)%8f0({vbutT=f-HXfGnqh{P+N#ZT-SOuEBo@YD?L|5Z_F*9%X49y7 zj1z)6J{*=FpN*h5Zs+oUH)HmQE|w34dbms@0!E0t7y@3ZJ*7YgCU@AFLk4jx6q@%58O33Y9}Gi+z(Gt1a$-rQ6c4=UWes&o=Q-;p zZGveJn00DUi^epiIcC-m>FJo!xjTH+*kYqOX3`#|0rRw!{k65^YkF zR@Bg2^KET%t_^HzE8E-CO&*x}k=@w6Jz8?tktHR$v50kS6PEl^k>1Ld59{G-VvgJ+rKCk$?Y$TLaMtVU)CBR9Nr^%E9Z&8wE$Wf_Q zqcKAhJ1p|nRv8at#){YG!U`{<*t+Uzx=D*x?Xm?)pBb#NeU4NnkBV8E`qnN2`CxYZ z0Dr(yaNr>&f+*rig}{>rB0i<5cccHE@5H9}wH&DGtI%*?{q2~Om24%FOpf#_iI7N1ngT6m9Qlcz4$tZx=fZu)57)^b+*IiPsB#YV zH{0ycwqqV;$V&6u7B?L0k4rg64hb<3?96 z2v`eN!z?$CSlU@jrXze-S8ZyUJ^=Y@t$HS%5IuF*O;>=n4@im}p~W@dAlpNG#V;CE zsCv~`zXr;q)@U?1>m9+75&6%N699M);9_S39PAD}_6OlXfQ8_eb!%3wShi%*f_Zah zj2km-&;aQn;Q?l1WGF_hB5-h0s1N99X=XUd270yY2**U|q1rpCB0MQw_s= zJ*9FnmqKbn7UY&0`z6KsJ|q$AKc@GW@UmHc`i*J%-0pfYc>j2RF?hcyyx$Dq{p+pf zm1gk#3;~`o2Jrl2D1ElNnDK@dbBDCK%-kb;4gIr=V$6JN32caTL%l0SpiX~)@F=*y ze)4#+)nNL_Z4DgU*87Gp_J92=<@T*_44y?yZYYsvgPbh)b|EE?TX9z8xy8I>Z|$Z< zJRA?FmnuS4XnV?({?zF`nqj&8(4J^$9yN6AU*8jbj>Kl`T5>+SxHCwGZrYCJ=4n6% zO{7G_Dhs~|im-^}nZt=x6&{zaJ@|^l>nXwiarOUqNBDdVfz^nw*nKe^ zcQ2v#?~{G^#m6ERgbl&NB_tAzj)MskD~=p0MwDpD62(iBAjMJ{(xpk2ML`LdNh4d4 zd|HKc3KYwet5UhL7%@X)sX!DIJ5Jnq@e?qpRwV#$Cm+82Vut|6AwrXzDxFZW(J3jR zPnS0Y`%yP*J^Akv8TrU3BS&UVD{5M8xcG!b#H3U#>^$P6$x)?2j|p=Ytk`ho!c%}C zArhrXl_p)L9J%roDORdhy#|dsbeUUR-r7C9xc+D-$BqLRZqhVzY3URwRHRsmQhEmc zlBGzKu3C*+b?P;=Px~}!)~d~j(IC4_nlf$1ta%F-Em^T@&4x`|w(Z!p@4%rWC&a0! zWy_P_>RdCf7}w(@i8oW=;IdFGYHjO6`O4^U!lc4o4MWTeOIpg=abU|HCKd%HtFS0Z zvO1$1d*@>I=OeJ4jB@tr)dcM3Z-YzSaGRU43fm()?Wzp`xzI4It|g1~lOliYmsc`deB2Ft8qyLiJGpjbkv1CJqbFy;=|xI3(@<@p3Hra6j}Nh>1*%Oz04q^z3@WTeogko&i}L= zKVbwJZwOBnOhpl%Js8o3@Df31Q-oJ|bj~-tVpXE@m#%DC0uw(Fd6gxfGGz=@s${5IH6wNE7;DtbM5|Wj+I6tdt&f$#I}Chggdld% zkFD5a(gFudR(V*r#mAmK0S+Arbm~;FbLT=_xf1HuoiGm`gnRblr#J5+eEAaPk3XX4 zACJVm1E4^N1&cxgD!gijkA(pI_`wh$fSw>hB7_LhButnakt+)_FmeQ{V31!rp+Er& zMT)X1Q9?(V@~15ot}e*b=vkly27P=D1_@n;40$tR1c5PQBuuU?$O;!8_QsRt2wuE& z^XAQ(4<9|_%On5a5&sY`yr4nD3oTk1_^$%UF(u09byprfPlxki-IYK2CP2{N5wZmc zVWJ?35d%-0IBXIm;F2T>pA;E{WXU2WM-CNv@>nQPz)q1O9!ivmQ>ILs3Kepr3cgmg zga&o$^k~pvLX#GA+O%2Gp~HqQJvUn6X4A%%!kkA(JI*Ic(S} zV#iJ?d-iHMa8l2ivkop?baCZsjvF_N+__uk!NV3$o_2Zha>$#v3qE{Yt5Ctw++ByF z+q8|yjvHii%R-L3y6<84P>Axs;Ez8p{quAmu`ZffX(sK!gYiqC~M1Et-QEF>J+(WiLlAOnLHAC{Tb^p+drn z6cJUdn4}UVWRb14Fd*n88k@UkRijrG^2Zj6gNhV8v7wp zVcfXwnV>nQCrPnm%9M7~rd69YYrvd2AoJ!WSg?S`qD2grEL;5{k!;PHNbA=9v}u!! z9Xl-T+V%eZ;CX7Wo*Zc~Gpg`Qr-~7CP4jr42b!0eej=-2&z{M9@nXuWS9spMY4+}& zln);mefmT;e+bOt04$0C0%`#QVgLe!Q($1OAVE?F1xg(R2yS7+b`J-RH+b+o!-ww` z0YdMH5V=E)*aH$IzK|mIgbbM%b|@GVj`{aqlC3;!>Z>Xdt_~dB>>c$ z5he!f#e^^8;xjx^+2c zR%D6XC7mle$@`>Cnya7_Ons2=Le{F`@8gF1!C`3{#wR5r*2*IJTS3huV7qY>5SI>P zJ~yrKB!)E4%3G;~n(cwe#c(7k@0j|L*bZF;IFa01;Gq^)x1<8)#8E5_Vmu_ydo{>} zG#VFdV<2lVI+~-@WlCMx#bG?vRA+P!L-fAeS?&d|K3MlSJNCf8*BlH(cZwC4CsX9Y zmC-$EEkV2qMZo8sU9f{tEZ1c(gHYWDlDaKuRpFaYQ}Kj zkC6?I^Ow}i2?K?^z8Hgx3w$h=C?|JEV8Ve)E80e!15(n)x{TyunUFyE9WYZ%V0j721M1EEI?! z1nLGDZN35-15yX0BToxF^*82;5TJsj<1Qwo5X9!qg@}S18`0A{gPLoQ5PE3$+*-T+ z9^X!dmEIxqt7d40)q!g20YLPU7MK)x5I}z$vJGg)#EI_;bDm^79*Q;Q=i<2)YFt(f z>3q7}rGyNVBda{hQi0R%K?X_Zk(YtF=XItPbDLy(P>IF(^5_Jx4cwa|aD%ImsrA6> z?KY|1WbJU4EK`|V;N?1O26C`r&F15S<6d`78iHXGv^25vT{ zv~B4rLgXT{YQ(#Za{5W#;iRmw;oX!-XG*FlQfrNsqFl z+Q0y4{&hwoNfX*t)@U-v%J=RS!rKBdVPd~y&kN9~wYH#zP_XfG3dF-6gyi|A%8V2zITJ}=GT%WrUF@E&D*^2byw74jc5IHh~vf~=;nBqH4~;gy_ePc zDyff7JqPYTS+8Cr{PSqUpK8OOE`lp!M%ol6lOT}_J7iLGAuFI{H)b6VgGZ&|uaMVE zC}c~bEADMXPbeB0a@)b9!5TBL6cYe;q9x+|Q+O1MSQ%D01AV_`=jr_Cc@~=4j9701 zSS_rmBGm>nUYx}e(XgT!XU=d$`DdK2*ynUj0)qDG()ArAZt<%nf+} zO;yc0!e(%Whe*Y7iUemTy>q76DH z-vBVLSz$`tJ>&}UOkf!doEHl*A_T>*v9p(i5o##85*z1Llp1D^ITWv&L?XH)+1*UN zdUKF3E@tPpmnv7HtC<-rGhI_}Uc~NlT2ge3`&A9NH|ZG|DSam+JTxciq)nTtJ4Bu+ zFxwaW<+IN|Lk3`r5xPc_HtAv07Xwzz{oC9|aMah2J| znO$U#MDMpzPCzBEzIgREbD@5iMGpBfKh(uk(p>~pG?0H%u7Whpsz>A7>hdF08 zLsof1BzPldF&}GtjSlOYNr!0~v@ldLY>BxPBwnXP9_n~A>3C149^XiLi;)+n>+ZKXD@ zh3l_}p_$%}4;4eK;B<+R6xii6x}3X>HQcPCr8PxWfo&9TMN@8G6bky86-PT?9 z<+(mBZ;a4_@QRKv;3&Bo`&Qh`XwNaKsy_B?<<2(7QOJ)N7?m_D6JDe1gS$*pd$7`_ z(`>uC_;^Iyi*8u|p#=c%V5}+L14g0C#us|Y@iLq+GSVzf} zwN_LZ>xz2A5Man=k=u9e*wwGcs@R|&@Qb(3p^QF$$&Z=V&HBT^{thN$!R>i>dPw|= zZY1wt_joi>UiMN0%6G&EW>1NHYJ+8S;qNP}XUjOEnPK$9u87w1f1 z-rWG^P5qRSUFCUGvMPE>E=g}qmy6X9dK1IXlQRg-Gr3_Di}UDR@K<` zJr{Td?Pw`EtUPh&M!^!^b!EkbzmZ8h3z#<6L+J@;?WUoe67M8YQIbS} z1$a1U+$J^ULL+0A8Mf&p08XbePf3$5nyZoE>9xw0SylBS4R~Y}&D=irG?LSLD zqXNxMHgp!bhwpt_8%U{D&h zu7CciBW$iaHLfW7(MQSCi1P2Py71j&W|NIa>Sl9Pe03{#Zuv_WeR)j()K25*BS?BY z>B?oIM*L<(G+A4C0Z`$Y9)!lhLXl*3w4*6Lo_w6pPJ_Ybm6E84v0SKoeO5lM!ftC&!Md^nJ+^v5pPYlT3+CkMaN?JZ?m^RYNFYrH?{IK{lE%6 zlgE|h_{l^Ioy|aJh8x`iOpG2aoNY?>+O32z{$M}ad-iH}8NR_cOI5twS$1MuF@9BS z7R#iW#U|PUZ>FSjtixze-t8Vy5A`Ci0)OoQZPj<~!%Pp5pLVu0pwV)SWeVVese6g^ zsmMJUq55k_?!GJfY;;SW%4XCT--xdMI~P{9>G-_w4l~U0d;7^6%CTaM@>k|t&u=(< z>yWu;{2Bz-?HpZE{$cQc9C)ZwNDrT)q0htE5&^xm?cf z>C=gpW@b(|a~t!M;P~>QhGyU6U)CZvvY|}DkW5bw)p9_N!rK9M`stM82@x01bcB(0 zU<7!}GC6h&YfP6qtGF|QG5k=oV-;nh&3JA0jT$D1=zHvmQ<%DF747i{8x*SGHLCj? z+||BLX)rrvDYg+2Hc6Hy@60?P=z(liKRaGMMYEgAPsYw99O3;}=`Kc3O~)L*HBo;0 zAJ~)4UF_byiksEqvQZ&zUYvXkIndnH3K9RG(?Va_A-v%L!t>~-9>eiak=?C#_B*08 zJy;<#^lB`J{P_=wW&F(5BZW^D)3THsr;nWt=< z6|@diA~o8{DYEZ%V>=&BV{1kVI`Uy#Zg!_SKPj@WijC+Mh5~dmiZlMhbs;w1#YMc< z_U57H-pjHh;Q?KBMlesH>9FbW$9Z{*PiCNKZhxe|hMH^S7Ee4?y)I7+H4iMZD#N{PAXKqC60Ga6J+MW*g-MEjHLJu z8#28>m+louGkwu+R;;s;#+iGZ_aY}zeK_`zez9KtL}H>o^Xb`u#gPgqg;Ogb%o1Q5 zGVQJTvS1G1bOXrSx=RuC)Z9*^ZWgt1`xdpEqWNQ58s*JLby?9 zYFm@rb;SAg)39QMVDRK)F|1yMM;JQ!!kbrDA}S}AJ&DMGEhfU?vu^;GJDH`1Fd17w zWc|@}rfkP}VR-j;Q&SOnYcRU78>n{Io=&soyJlWNb7%=D=xTb`>ur?b^y~dt>}?UF zzhXk#!7Vl$J;syLvFqcXC5AKPQt1A=7vObWUGvWSsT-ed{a^T(mK3S|UiH>UHB>X$ zt;G;Zu@6yqer{w?jrO1)aM)*RINtD7j*1G2^WY^VP2!7r25!Ok<6MfB zayOJ_h0N@WXm1F?zKZY3zCY|!R=(1Zl4XOcDlh@efK3x&8sI}9V0btA10Sw%+Npou z@xf1tGox?F&?7}RNs%n7Al7=G6PhAb&#ZBIduaR+kM$tKSxrk$hZ5FqslVf||D%|l zwQ0(#DXIQK=_WSAR{3*$n3l#^M3_peG_G5qj=sRM-bb6CV83O|JFn~P_ME80`flB} z7fKmtPv8OdPQQS| zm60s3p7}i~ho!g@iR3Ukh#R3UUx=!iQ0EZAhOTJ+7mwUM6K^Lju$WOHpw&;Ltc`D%4Q-fXwkL_oUwi z8g@H`MW=`S$BN;-tB>7ZhnM1mh_6kSkyQ;0>*a}N@n!F0T@FYJrg_RVwMj5wNuJges^Ge;T1_pVYl{SgSZ@TzxtAtq4~&Xx8nXpbc$9;awn%gL*sj- z5e7XJ{eADd78RkL^+@JX1;a=gU^Z5BB@A7&T*a!nQ7sV;1E%G9Yzc+%b9j|Stw~2C zh0hhH#b7Icl;DpinV6ui!8(lrs-G_brXYrqdsoAhM4W9f3yCd=eny|dD4k_MADSSq zB4Z0nn0s>duB_O4f@=fErrQVb`Zsk2L4nF443F5%CQOXdQ^?Nn#|LeM!D2%N1DJHI zV_nC~cH5)8v_qZ5NbSPqCd>X(My)Dz3q>&z(`i=(n-YY5UssE@;Hud7i3mc#Lcg$@ zwZ1irrAD8#62 zilw^2s(bXhlJtA2$pD~ZBU~E>5lojsx*1KS_|Hbrk6!t^xIdG=IRLhm`)m*S$t(Us zJ4$q$zcS4JIHi|=P9;NhUpdMEr3?cmiX(FRD=P=Z)~GrLcBEppl$|NSDWrW@&WnZo zi{r%cL+~$Lq*XkZTXw6$uhDG3o`^A2Y`x-HVVM_h#BOU<9Z=eC9T1+i4@g_hv@Y1H z0jX6(#JM$(BGWi-_gQQ= zH;k04E2WUPsZ@QOQqMmjlpwVoyY53W0roAKx=g>yDnQX@}o<{=tTMaHH7f{;=jZflO=sWnIMBWUkxGJAaEPF^|9Qb z0Dtt`%D}WLLDh&4kj$e45Y(djFlhp2$Qml8SJX_Z-@3XIi8zup8rOD1-robv;{hV% z!Aypcy8_o>tm&W^*gAzHaOZ{kB84JdmGsZ*>>liJVTQkO5a#fp0m8gm~%a4*uHdF!NwUc?O?mciM zx7Mo)OJW5Be+r|nv6)k!1W;{Y6npUjL14q@$0RWzcRYS#vahBn535!2)NAF|wjVS* z-I(!TwwHD(NeoY7je)H1abPER&e=hz(A(GxA<7~|E(K%DR^%<7B#%rA+Jp`X3pw?> zugfY#RT#W{PK5f8m#+I*a@<<)iQ-xv;gv;6!Hp8V-tEQywMZj>E4lX0>R0S>U*4 z#ng`L#41hKX%Dn2zeAFv>g8{?ig(@WMvHzALs-J;VEx9f#JL1vK(482$@Y@y9;G>@ zlV)}u$T~B&bZ&G=XqI|Yy!;$}O8v!C@W;3mmXj*u9kx5mr*BM4b=BI7#4OChc~>EK zlaYSAV@~T9#|Sne2o)KY*4|H$+u+=Mdi4p5$+fQ53Rrftg*_yE0CqoYk3S-88}|K_ zaJPd2?KLpAkn5@W!yRML`=y%k^f~e`u6^=}J+z#UA+U6a@wU|_&9Gt4!a6hDv>?NO zcfopsBTf=11W5p%1I<9|OJ7-PLO(=x^rB$t$$@n=x-XgS!(K*2)Iu3|KUrqG4+=d6 zA;W9P5{_Vyu*U2Fcic3ff%5m9Z zaNAs1j*w=hV{-gfEhmzF=?4m9ToUT+z(PcuWPx2~mXba9}Q4~yQB;*DI*qXF%& zh22gA7?BOaGL)HK3N@K|Y}SA<(e-R5<7W{#6e8CWK!}_&9X(kCjDsNXmXXHvC({U< z#IV~z8Z(&JDUgm4;G@k*GloMTK(vB(%C%i}!4<`>>c!d8mTn$Us~^%X zp}f~-Rig$a2#L3%=8>&URJ-kdkbQ1ZE($6C|H(+3gz;zxBmMl}43CU_PFx+|CwX2%|ge0EUhCASGcD1-ub2b;T|G4g52YIUvS57QZeE8#%JrVSpI(o%*SYSUTWxwy`EF_p z=dTLVtUFYESv>$5UXum~&MH%#Y1p9%%knX?+E&Yil`ahl?l%{oc!bllkh$56ReptzAj=&{(oB|a<`c5m4o}LL(07z2 z7?P$}N?xE&^fjZU+d2G%aI*y)7Q;u&Ku-Y$Ec4@x{d~0+me-<-@6DLDA}}8=6IOr> z4=<`e$lSfX$(`JH4=tPNugozT%y8wj5JG{wHv3%_-D3FmSa@6PbwX_g^u8JrT+sb! zngplLtKmf<5#uJ6G)x0Vvkv55^7f{?aRZ=@EVh&4-mYn)QpfaI!vLkfi?eA7hcW&+ z&i@DgHT;zU@fj6D@0#e^>fQVhBapO)-7y!%>snPW$0ZPFsKZF@lJ6*=VW?ttSyI|;PxkeJR?zmeAZ=N8pF0gY0U zBTnX`_tMg*8{OANwO^5zXyG6TY~Ytv5Qz|QZKHfLd?3N;{&fa&47%>-SwXtRez3qc z+IDv-7kRBLQj5ZHm3YG!zruPH8^QOHNi*zyM0qc;dAtMh;(R+{y}#! z37#AQ1=JwR9Ip@P{((X;te`2~bZr@(BHRXy0eH(;@hPzTAUPNSshkMW4Fhs#)aYy}Qag6zM$z`~UHB2L6VH+zN{bsf2?c(T)$i zMIg+2#_FMlD4;zLc_mw~(Az6UKvmgnfOM1J>0$OIN;O8xfTlH3R6*B~I}S752eevl zSf}zgYfwANg+_cxRp|BC!g!4ZKn3zX-IEgoZHfnaIyRX9-LnOl7dTT8+z=l#{tfs@ zYe@P$_isVq%xnlT`aphH82QY(WxJW%O!_wOZWHl%KZW6x+~W7Mn0IEcjhTj}sNJlDAw! z;VtE?B0FF$aZUX=-Va4a=Y|$UbPjXU_XhL822kJCR>F3HJ z!eA4D41iW+o`2aFO^}d&Ur``N3T8T^a`_^4De+Kq6yG`z4#@W@FoyVh4`~Pj+9I27 z#s}}_3jt$tK-EO&)u{xjvUi{g^8(b#BsAqnbf?<0ER1jAr@XyS;u>2CAzT1eKuGqf za$|30uyA!E+G_^Q=)`@uN$+$_j5lakMqs}YEt9Hc;DmFqw81F&wt6z~jCV~G3*2#e z2@rg;sT6}4;}i+jBb$M0J%f?;;Cl}xgi%X;N@x>ppe?;kKx*IVA4v?$b8*<^72RXG zK=bN1_J5z)1cPta+zzuy+D97On4)hL?_%WHkno-Nw*GPUZg8Rk`$x|{+Bt4~GFPYs zJy1v#ey06l0)9mXdJqkkYs-GXna3LTEHtI$9e~1P5tx@|aVWdn`KI>w;;Ije@7DYj zMXji$>TDPXd+H;H9DZSawVqYC*FJZ2KefWLKC)bF41A4QTf{lkQXZCG`Ap1Y^4VJ+#*U@1M<1RLmFVW+JLPIodb2uzEatw}H3RDw+Sj zPhB71-{G?^@(R@gNU_9pVUnO~Wz_j}CWWDql9AGhc?7c>vQ%;k8%Km;&7ftd(NT$9 zs$ug4xC$-F^ZSIUpA5#R-5aw>h!Lr*JWBPAiaF=FGlO>sPPoPCx6^jaKe3AdYjzcx z|7b315X~}1oE} z?WuCRDA09v=-m;o(Ch&VTJ$`d@;~Tu0%LNY{=MW2-KX~jhRgDn;!3xX>xEmO$lHpO zzbWYfBylU!yvI{9f%nmgFLR8L1E@p`rH2WX!6pKp0 z^U}^jiguS8$dPD=v@JepTb$NviA1)k&eD&Rm8;~;Z(=P^&L#U)mPqh!U8jLve&VBc zAOO7?_e_eJAzCr>rMXEO%u<`qsg5y4OQwMB^Xczq(p+g(gm;iw;-;a@=vR2fUymLQRu@OT5DDsK#0 zifequ24SPVR7JLC*AbH$v6r;iY>clEd<$)1eE8v39VJNOZ(@{;36E6q(MG=Zejr%J zTY~h*l)$nPJJn^pKf-lOz8?0Pwms7)_Iuf{Q7jeV^!?D?m$OA7+*1g5ih*=*K+?VO zBPw?9>;etiqAL7HJbT$?Ux;Jij32PfiMR%!X?fGF1~Q@d^BHcgrG~C&EoE4)|Nbuc z>tpnHZ~Mz#I2Ne`BCmsj;@`pOUN8Ug%ND;i2-}is$g|3-g}+T$sO_W;A8uvM_AXpt zj9NH}yjtn~x1VDaBR%wm&K`P0IO32$bRO)2V&aFin>n4f$osf4K6PJB@)|mBobi|m zmTbqBJ|T!Z7_zVg%X5Y-#?1y%nU#mA0`$X0_MS8tB{>mZ-0z`)@$Z4#D)>K%V2+mi_ESto!8LfeG`TEYyQcsw9%G2j{^?e= zha9RRTm_M|fGF|{D@^rTlK^M0Z<9dJwi2*f2`rOCWWhm=wAv_@zba1*%WUT@<{%#d z)TGzr9-~&f)y96brQ`HA)lLEhI@QrtD)flhV1_>m#_nVpEyS3b7|jlZ_$d{}u!d!P zgwvF+xbYrvZ4x@>XyS@wt0)2s5GfY+N-^D(0VzsB zgwe0|nPWc<@McY~;oZpo>?Sl(55Z7bm_DPgOR3uJ7`5^Y=W3!W!k50@?es^xF(>FbT zJU{)kc zNGcn&Z%24G0gmH_vajv#+E$*Lriv@GwNm{z0ikY<$#kCux z{Wy3n+X$1#ujpO~J$R=wjN-fdqPs_*f2h&zUH&L5@D-OCz&@CG3L>5s6|vw*#e*oR z6GF?GAQi5u6iYy@^MO<}WTrLtsgu123a>JsNRho*w67WRz>KS}S^;@tjcbrJ>#N^j zxi{=h7F*#@JCSU<V*4GvuOU;%CSmz%hrawEV#)?49-X&Af|e` z(+v$^anjOHUHSkVH=<`3KdTxpm)q|U?+(n@Ej_GB2U}?W%OT!^}O_IQ8>tT3Aoc^zV}J} zE(K9ZGnslUZ*WNoGX!c_Q>aoaAw^@ZHkhUz@q9-&mJ7c7m#=^#Bv7grvaF;_XxC{DG%&2u?H8`jYFO*y89*<63U|No}J_@ zn`COO-C=6L8#Vvk%m&apY=Pqi8Suu13=qitp1wKW#u!nq1|T16P5TJrlB%_ccUDe3 z@XQiHChmbu7A?M8;Q%d1zc7L~8LyfnWI!)BR(BQJpQivQNkW+F!rQ!Ev z_c|DgcmY()8rJ^mZ^#$h;Fc!DtBpGd_m#rswIPdK>3(S}mRph%F`Go417>qCH&tr& zJZ{Ecn(rHyfzwUzqvZTzG82;%ssFun00q%iiS(IpNI9TkYbo|NAv5NdAGq!+eJAo; z%=HZnPQJBCWqYgjxEMnG<9aFT0&D>OroGA_{J&YpF^z50j^oCZ{JeW>7kx98uQX)T zFla4vQo)1DEm26a9!m?`Hi!c6A{fTc;f-WS?bA2`EL>G4##Kd$Nc}|xeG>oBz zM<^HQlB|7^mNiJGj8Wy)3PqYe>#i3WZ9L@bx?=c6xb9t(2d;SSSg`&nSD>Bo`K^?o z@#Jf*@!(5o^f|%ku{8FA0Kv#!Bkj(ZEOc%?gqX@d)8R(+xcSNzI>gT2fV~Bb*=K)D zWPD<&X{Ud@0mi3Ts(3weQR)nOgkjG2826~z9XCThSEH_Ph?o3~*jXMBc zHoAY-uC661-Cr{PiH%YoCdlG?nWIpcV&rM!%W%og0f@z+I#za`JodO9jtfER8b)gV zu;_vaGk@w1ka!YXN|CTP`)GCfuZ=a3_DI|V;F=YyY+5lYJ=FbV+uCaJ)Dbur4YV~Y z*8c+v>a@;vFj!??B)K4N-EYxCov(pv8()|!&o(h63o)MxPV-!z^P(Zb^fHQ zLLhqhd`Cf4t{&g{AX7swJ!lmg?!(PG$!*rX-PV8z3@xQLGaNvMNELCB1{v9-DlL%>@*WU^AIWp2j`r?Qw;qWg&3V=R-I zw14tG(kfwJSfMk-C|N9G@<^HhBG*u0f)?#qg0UP>ht8=%h};c!93{5noGg;?hh_^> zwUGvFc>MKmZy`QlDT}Oca{HwE;~4Y1trE5@exHQ#o1ba{!VGy{4441MyX4eGS}Hs&H~AQRtBgs|Ihb`06oD0ooq00iY8Im8W* z7zOs|TqvtfDpxlOVvbpOGCIrzv-DSl87><3EhX^CuQtg#Fw^@G877nMxmP8muuhL>3I4NgY%L-+y%$3k?qyaeNOO2yu8j@1Bb-_~@Nvj1>( z`{DA3m`giOG92aikd9=qc{gtpMiQ9-j($fb>>&=kjjykpYs|k7J{vH)h1WQl`-)_T zD)2(U`(&n=yq2&I-nn6|wUqN1QO?Z^SYO^f{TZ|YH6lX-woq8t#`}5W$yDV%amFv4 zM~yIxMj#@qHH%V>FgcK(+ZJSz?EnXcou0F}(*dhq(Hzj#Vb;`)`XYuJA)Z#qd|KxJ zh!Y-JGMI#26n5(Zzzm0d7Y};DL3j-5a`*i)Q~M{03V2DN`sT!7o^_S(;?Mjx2stG^ z@DVv}%Rz9kFda}sav@lKYs6+z#1*J(X|W9e^Rb^c97k-~Abua`^M`}w zw1|mp;Eb6uct7so&~H$9n-`^Dla@7mpHbXQ9bN7g^zt zmCRjnZ=?FA+EW_6AuKWfff46(Z{U7F8SDo>((a73Y`}s8`R{_J~V`Z z1vP_s`Gox9;=4tA|2)YR7=z)fFIfWa(mvDIXB(w`w0YslcfQ6(j9arf4+Rrw4c@dP3ld zX?6E;yq|9klft$wKVYi|F=gvK116fXnKm&omVBH3J9c7o-(Y85Y!~uxPum@3A^fXm zCq5A4#RQgNDtE$a(}Nyl&mF2J@oL##r{0Sbb2pg_c@;DU@yP2##q<`j?o?=NqnE(( z44A=NKTpw(w%&*gX8H_8%GfOkhL=%Y;zi#6rPvN#uv2W6sva_$Ge(ju!jN~;tgw6~NF-@0CBg8zmqyeDX5Uox`b zKt~EVon134K7Avs7ESMd)zaAF=ZAF8k}}XAhe{Jy$*hY}_05@xNSH zd7<;Vl0h-ZUVrBp*C*@OkY8QHxW59fJ}A_0-@ac1BCP9cckl9BtKdqi8zI1Es{n@r z563@&L+xZR_-w#_SLO8j(#f+9dZ3NnhVzH*C@!=A#eA&y;vK-bht=27w?);)*-4dz zYUUouOL4YM#3`na7AN4am*NxmR53kf8}~&!oz$3+S*A&n9uo!4YjoLUe}i~2Q}W3n zatm2Y6IIsd>pTP}xtxVMsxVf;xm%)B~q}rk_zK zE9z)IF7|`L8J!f!tRrk4EN7jh{=3{<->kylG;g;L1^M7-<#F7N9>|S;mN?&i8b9Bl>iHc= zoi>C6-LkE9+6H~VF($Lz#Ebbd$XLsU*mQ6n(I;1oB)9Pcw`7qq5j{`Shs(u%jF(g< z5JBeDvX{kNDduQ>Yq#Icj!!)aL#y1g%x+eNeJU?A5Ah&Z@wZ#e9tDKyfgP- zm%ZXQ&leTTl??G#Jgnvw!<0<28A*rg#9hUSsVA* z%C1N94b?wmKb&8mhxO@cgGJuEOOodUARUzd> zs0KM^d1m_UOO3M~S=esh?-Oz_RViEqeR2aDVsEY${t^)@CMvkCKlLQXIoEQTatbYS zn(E!7Q$WY+e}_^kXyVB)x71A{pgi%8s1Gz~@&5Lq(>E~D^HFmR!zi!b+?+_${^wp7 z8)xI>{ejvc0oQ=JlAdDrJkobQMn_I#S(`G8AJZFH$B&Dv5O*FB9(M6vcid>FFE!z# zg5ZFOO54EiA#Z?~=u)nyDuYD>T@S>N+s^N&Cl+#huKIpGM6kWezK5){d4-krNSda^ zJiDAp{2?c8GbX*@qEzQxzN-U%;$$&>Lm~zt%ZhQ0P?Wo7#!bcSH5nX6vcO_KRb__Y zN`WMdBuglcQzdD`gVabDqn=XT)R6k6%ztytFZ$c)ikB)ky~^K|??zB8?!b4VR@4aT z&Wkp>>ppwmR*Zax%_>=`ot}59;5z^1>xx zzT{Z?Ds#>s zD?gS=?L31=*0D6xlTxjgdEO!h>ZpVGy2nUW5h&_cU6M&?0Ie&HxDEJm7R^g)=D2p6 zJ<;fsC~^XqBd?G50GZTB{8d*Q&Gf$?9bs`t#mUWPNavqEu%`5xiunXWXM{fYxYhShIY5Q>6KI`qv!;@O~7AsQ4f4t|zUKo@)3yf7%rPsV;B@$&T z8p~Ys{>w`-Ng+;~7&K!*aUw0kw2ILN1;Rpb0~3AP+(5x)pjBDX{o8daiv>VABCUQ` zbDlmx>78Q#H0-Yi1$}k9&NqZv==*Pg0|2^KJg%H)uCC3z+N^*1Wi;c?){zT}cTnPL z#OsoMCZ4-saHY6>$cl1E@!BzrSTHe2)o1ZDzXAx+S7jnaWt7v# zRN1Ve6nsl7UQ651pgx8yv%li_3O&uR7A=@3r_2X?oO^#N+-452?f1RtQB7}as86m2 zxGPySZgGNV#UN=|dQL?~B2kAi{*ZfzltnHsf;w054IBif%8iF_d}Fh|%8`5Z{+9+W zzTCZ@9g=9&nOtIZbn%ad7wf_+0*b-lHxS~qIotGF8{kf|?nVA)9p=`iepWwD7Hp?m z26MF>;jNth-U7{^128)P9+8Bk*_lm`v6HfuzAg69ej%b@YN#<^9f8I3Rmn8;>IAk?!Q zF#E*L(Zm2@YHd-hpF&_##P~PFgHj_LKYD*$ zx`(9#$=1jM)9|PJ7)22XK6?cs5{h}$${K#m4;X;%pX|vSU)uvrj{Y%edWbpf+A5N1 zi%t*eyupVq_i!M{bY1SRg0t+qsFVnxdsQ&cZAgoS@)H|rP{Av#dDV&o_eq>RDe(!_ z*++)?*82Mh!vFv1L*t0b<8$|mXS6+~z6jCI#g{gnCppe4B?&(VnJoD_Sc0ysk**^- z@d{_kA~$CuarQm(AC9JuB4kt^M*e7GW1u1pmFj#ijdInzj#|Lph7C0d(*epedKN2IV!DytJdN~f4~~XP4^Skq-=&7M)41hYUb$J8nm{8I%0j?`2sD^~ z;OdRcEF?2o10mpVEs7%!MA9db^QDtlE}g!@r<-Yua^AMuu+CdBZvis>mYmiq6%P-Q zmQOWms<&9>vcs3f*<+_dMy5Uln=4A1ms)r0^$Z3xlfKapAg_%j-c;)0_-19Qq{)rt zfSVMJYsKrjOFiuPy*4T}Gm|a-4ycLiyx3`5?%d7}e`0ebT+I->D#YC8Dm*UwET$N8 zpy%9Ikndq%TNY<3q3OZuzPnF-b&5=os;%IxvPSiqKkAuF4%I0C5b_Y)@tKPLc4ljm3jV5!C zsHrfa!y|0?ixRdr6Sri7SwSwVDUqZA)Yld()1 z@@NxqH=eX`DyekEwxhI>;-KLZ-M6dT?a$nLtdrbR&o&4CC;(D2D7t!~PSEw>`p;cZ zo^*H`&VYH38yv8+eNyQdEs!oKwNz8<`dLYitEUrPLL!ML(zjz8KO>IDJGkIa*e}g) zqAWTNjN1;8`3T_p94C0~ixN1kd!FZt|5ZIzHo;g(Jqa$yf+3Ac=wjC4c5nmh?q;rcGm0=8KOp*%3*o z%S2D)X|^JgqhH2w|2cj>c4i2Nu!`g*AfdoRg8wtN!NwE*H|dJPnDI*qR#VhM{k6TR zZ-k%(D09`uhO)5I6a6hqH&ecw>1D_^J#{QDz>?OMGlafH`NZR{@Bnu$jgEZPxUG%B1IW(1bsOXP-LCF1x+3f1L*(@ z^O+A7Zt9|DR?jMw-|E6rb3;r+Q_gHhGr*qUI5~~v!zPcZc-ndLa+nXz_|&T8$SlL? z^bh*shBw+%b`^{3ayP;@2hbKO5_z3bgByQy2Z-S(V@RtbrB{pZpT=@GC2X|{z-?>N z0X5N9ZYfa{eIb)k)o744e^t1 z@6NDc9ZSQBBUf(S$}@p{^D{-$)~2loJnA;?I8pT}PfTQ+i*D_%x46&~ zAEUF1-7cc0hL#TaQ*@EbB*JDKTQta$dExo0dRO$T+`OsLQz~xr+B-t$6%yVXU1R8} zAZ5RVuy#dDE!{#y6MEM`hT0-PhfkD>we5}4jyt2j%|KNW%|f^FYd$6fZW~^3{$TN0 zHX;*f?YKR&x22x~JYk}VoiJ)K14((jXg#)U-2^%D%k$I90o%CyWC|}|DvwUgcl)d`UtxA4|O7s&)hLxvV)n4cJ|zT9}g(7Rx(C7^&|d3hDdK^*KGIW9w*u> z3YImHdv5(=f$X7#C$Z<4|Ar`%&Ga`7pLh^-lN+7RhSAWG5A2URpUic-(e;ThM!*dl zeV4ffzHvQ<;hhJJ-@$Vi_#`9z*;5mBn|c_z&~&7dpfR-8A~S%~IQvYb(K1+%rBYx@ z{5d^FJj_@Z*@>4Rz4CS`7xK<?@|EF$2HC|0je{Sg5 zV*rHn7*+XsnT?lqW8kgvC#b(&*7g7m`i88dJnv|Z419~HMb9rnvyLG zP`i6Z_Al*R0#!92=(g`D$F#;t0L^Czz)n0Q z{=*=N`y2^~GOR#H#_A#wP((BVoyF73tXq=#4UrBcB-L+P^F90X4Ufe`lI>IgBO>ST_4^wGb1nI>mt_=8}`e_qM9UmOM;e`~*U zH2?Sw93Sv|UZLlvp}H3u$N?tYTG*bV@4nMMWXW;v(d+-8+I`6-Q9d!Kqs5*QjX$+N zNdHP(kB@GRX_pR>ku}K5Yw6C+YZ3YbuBz8h%U?;I2(5d9s->p#m9xs%%_kCEyK2)J zZ%e8O`X0Ma;)a$)r3Da2 zE&&^=f1%#0?pUA*8MQfWx~2ZePb6jbDlK1=?FT3PNYE4ah$eyaSAN9%o)~}aV(bol z{o1>L&_uSGhcz>Te$>%{xAV~{G%?v`G*@#oHFgdb%B^*3a`s$L(=Sdpx7i+Bu=c%p zM?9e9mQ~c*%o?G)m;H4X$Y9E}V)=K&V(*?P+?RjVx`*+*yc<#rABx>w{Q>TLB7nSv zI&wD=(}vMsfM&dg-#4`k9tfeO7;hgZPX@*vc6Ss^dm$?=F19wDcTXJp3I!~C>Wyc7 zIB!>M>dh6rR&V{jYY)AV2hNdVQ+}{a-{6bu_cGf7AWVQHdM)C6aGL~>F0`LGJ4%wf zCif9Xf$hU+h_|4K?Qdir;gk0~*@4t5AbfuBLNC_Y{bk?s@FQ8UgXz0q>f1NnINove z%V6w?4w{PxETnT6NH5sBNak2!AIGFhyVRb`)+WNB4=L!ZG}~3I>BIfmIkwa=rH-ZZ zX%Ipt$&4v8U#RsuTwQqv-rJ>fK`P8;@?kd6H|7<2|0_aW>Z2VS{N>Cl&SfJAe(;b2 z2ARe#_}XwRz zL}J8!(=QNH7}$eVTQcRQ&rU8HlV)>r_KsI9V2(#!k^RsR5`E%h=*$>!jn4pxvRb@R z+iw45{bM=gxLADLEN7|y_62@MzWf1?>yLs24qnA^xG(wWA7_^Qx*ej0x)8TCu-Qu= z#meoUY=xHKB_6)x046*GB9|OEHt})Y4xfU1oHu~ z7AKVezGV|TTHZk$dd;4FAhA*9SlS2NpfDtX@n-Im*DYr&{G}ZUz?s<+;~Cgb3%0$m zd>D36Vyi~S?#v4GcWWj`fCE&HCk{&vv1EAhy}4-g;UWPiuXzM5gJ^ZEGC{p13J0+o z9P4i(tR&@oYz4QgQQ{=s35nMe-EzO^0y-e>MK3CDYFQPXHY=OnR<;k^2V>u9;2_*rwv*K| zT2$k*Z&etL3F_kr0l|z^?(|XrRIO3#@q&@b&iFY{E@&R|ppTdmOzJ+m0SRAgB*#?# z2QoaJOKGNWb(%D~wEm}M=WCwE(Ab-V<-y~ThW5a_@}YRN_7!9pMU;AvTMySCZT>c1 zCl^?w@)fcFoOgsGy*(}KT0%{|7ai>iaa(-TQJUMZ>Jk%3z zI@lP0J3X(Vg<-6%KScfgneS6?UZLClnMa7J%FBt&xQ1h}Qu;1g6F4~yS^qT9>H!dk zrjR|#?C!y(@u6fPEj$1}ySCz*aPE3Pl}tFqji9m*%VQ^P)9tZd8pv@8OCXm^CVUYK zzY-*JAB1eF#CRXb;7tMenkpN4Xio3>oQ(CiD*x9$;A^VRIPK#Ojj5yj_G_R$IG(E0 z4K@%M+;aBBDOG=~rk{D2#JacZdk@I<|tt~hP!Je31|6!nx{`v zNN>_obEUwm3k!v#p95})bl$mp@lpz9lijzkW^pRsk#C->%qVN8p$Y0UJzT$Ynuz+$ zXI6TV1c>^-iI5GUovqmblgXkWd%pHuom|cQTe_@@AtN3{evgeD8a|{vlx%FNr$C*U z`{DK2kk)7zo8m_`A>zHXbvX5|u|Tqc6%&0Xorr3OxZJ2iw+?RuYfR~B+Y%CIC-FD- zAK!WqILGt*-WxylY<(_m#X5=iDo`CP5<(o3!Un}lmYZKUf@rLRiwtUxt% zniI0(yXh$r45Nt-y|W+4ows6z$=v%FU%lTX`@jmk`EeaOGQyut0@BH=fyxg!k5KD9 z>(F{*KQ%@wSN5esRcJI&gN1tnN4Yl?L+oP_W=YcS7%Jpc#FEQ`V@sHIwHac%IgF>D zxk30~3pwPApkNFLRJcXH6<+-*JQ$lvc8Z@ZtI**KHEx>WoSM4g< zWok}%Mx;5-f#C7#=2*l@?Zot6(z1~ss~z)#mBxRZXXfwUn#fFdFsL}0ntHlFo0^Le zB{rX)x%b(0dZLZf5L4O7XYb5qXA{(AE?V7^H%pc1b_&+W=P7Y^c-2Zq3Ev7|buvT~ zzMId{u;_SBtTZG&8Uz??8eFM_clPX9jjLhIkgH2u2~ij{6|J~`v^Ec&_7Qdh8qBH0 zNin`XJRXiUP#r#_U!MNrAriY#ru!;9SK0-wX`pWkuLkhXIPdiJnfmhP=9I+J%Uo@L z^V~Bn?6x)9#ksfv-i|(sZ}jH=9nITZn1?}0NmtrMp?zO1XzNI}ExlJ>Zn$d&!l7rz zP}`)j8tXKg?e`>df_b>x&lo%9Mj3fDe1?(Ts>kh;wCWEsos@5SgS`Zej ze{J=xPn>s@?k_fP5bGzeVl20A=BM;)txkEtp>Asmzn5dA?i&=zPO0%h+d%eg4Of-4 z(RUTM-UaJWfx02j&T0(RL7~+udV&===ku#FD&^@`L!`6{J>q^om?zINPYScq=vcIk zXj~au2EobLI>~CaJGQk$2_gtYI*!4y12`{wjWyw{mZQ0=?|bV8;b0%Jd{iUQ^rQ0E zPpx0Sd2;18%v+&6dVS&vmjecTEhtzbxnTPv=m}=82*GXi{*w(WvZ$I zz7cf(^P}`$oD-fFMh9mYC-05Ubw1MRQnU{uSoDtMMP>sqv6J5*IA`zlVyS8zj7pzA zH;$nrRi%MkJUx#Am9;VH?26fN7>+zRkEUynUQ2`HFPuYPx;lWNW#rg~hCzFq?DWBGWg)h?O%*aNG<>~mM}XgE zz@Vi^Uw9Io#zsUtzfn0Bw^z3+jp}t;x7(|5SFf(6IAP^1o$GRn<`^iqI%J#g`Er?z z)NsHS;BB=d*7b|lLAh~F7v_sm#MASWSEuIH%+#gHnWov4Ei9p8R$Y|u6v4*7wL9uv z@v1q{@|U!U)-6S79KWy@R|qvPqGFbx{H>^3T|Rgo6?n0GTCfFQTB-<6N=`ZdS+d?P z7WK9WWjAhk^+TQwm{)`waw8fibJIsFF+XZuQ=FsTc|)|WanvxZK`5*?=QBZ8Id=_#rmdAfMYJ?kw;f}K>!tba=?Qi;S>!#0Tm}$8)}*nC7C{I^jiq< zsG+zdmF|tsEEMionYWtn&xZMNkLHBEaAROm@Dj5!?@azGKM!-mVLEMOS%1TNQ0$Dy zodKp_$d7n7UE`LzI1EoFz2Slq6JK`h)N!V41QC715Y$rt?#BwM(;_+RZPUFwiKd}Y zUiNlezs2-W*5Tw0UeXA+S-TNS1ao}?<1kTUrG^$6)=5OG2B4W`I-_ucWRr=7k^vL26+Dn3g6`d*z^`W>C*XX} zEn_L~yXmH`60|XGz;+=07+=Q`db5gf8%iI+Fxc#N^?1EAzYbQeRpcE}8C;!@&2kiq z^ZWn-r%zy>R*3m~8CW8bB3n5xYC|ZrCyLQDy-p-`cw}=;vkv;#S%&hi4nmmI2H~<^ zn-5*9Sj5=vs1mSRdl6UwFd@@}xV0Jd7J@{tRW(9+ZCbao{>ZKCgYHRsWtHjGVV_)c zSZkSnTqrz_e)ema^u)*ovWi-Lb|Soqz%|zzy-(>;HsC;#k-C(gP#!X6UU_k(Y`X#p zQ8iGy@p{{Bjm_vHeEAMrPjA5z7(dJ`F!^ z`Y_QJK};=e-n?}f!;7lZt}cctv3uF9*zLy>-hFs|?WI)HDmF4Y2l>=o6<72r(nc1B zW=0PT_&J1r0_qUGewh}x(SeNKs@0l+@UTj!pFo^k0?3$Y%F%}6le%6$tDM?Z03533 z>^ZZiLKvX+Y<0v*y^65|U89}qS(wZc2NnGPBfyWmwSsgpj=Zvwe`yx>Qd*+o6T%@y zai+y=r@k!eKKr9RQ4b%eOd2Q@hm4gL2!0)~Er)cpY zb4a~tEcApFbh1l@$5-Q?Kj;lIQ5cLN=yqF)HIO&iGX2|0?A ze2gF_ucnH{*1P4L*qr-iXm9l!tM)59J+{)^zoS`FCqQ=eBpv!l5#;0pbzLWqFShqG!Wf&7YS$AA_w7z8BeOq!-#B!OrjSmDpl8vo+XV zX#cYrzl4*2_wWO^vq_^}0#`jW)ox`sej z`yDT$sPS`MMeKBGLbwTa!qC*ENNQW+D>O_XkF6E?usoU4-9J2l(&DkW>N7kvnJ=ug&R>qd*I7nPr zqh6kA9OwBKp9PIcU?rk@!jp->n-!7FqT~fvBg9E^?T4|diB>zRNbcDQmF@&SPkL>P zmus5r)JM;qUhfF*piOeuBZY>9$C_F)c_#Ig{kl#e5PGX~A1WJ2hZ?02C{bZ3VT{1f z@0fc69BO$sW$EfFdz#c?%uR>cl9#j2{5P-c^7^6e>Q&+gaVku!{6aHrZ^GISocPQg zj!j||_I;i1aTkjF3%}1G!1s@eM#T)`4ez{{amcC6aK?_Lwnf1j3@Hn?Mm1IV!)fMS zhfGKU|KP|Px+bC$P#jCqTz<`bL6{BpCu-m~3zUyi40ofUNukQRB9pZfDXZ_Gp3{Q1 z(&6ZwbAEf*B}+&`ZH*pDxG;daUpT>D8l%#$m1`#IWEy}n(%v6O)OYF-* zoe5^4so?Am_O}*l1;|OojX3DlS52;*Ky5i&il`9_YLg*otKHP87tNZqKE;v}DrjJ| zYcg#K1MOD5Am+R?A?QgMv88TrmxkLA7#^1^C55ONZV>8tK!=TVq3H7d`p%>md6P;V zgw+AUO%zC31Ye3ud3@$5>=r%4m+~*J!RR`g$9V)3RZ~Fs1ahgs6BprUOBV*Yi{7a7 z9)l_imFf&#z1@z9=g%dg@p2)L6NYIdZUG112WowD$04tbc&A%?x^7%=zw609+qZgQ z#1F3(>XN`}`(z<@${5m(K|j5t416p%8Mo0+rVmy)RuJjY@xo%661cX+i6H{61Om98 z*Qz9NSnMu^L2Mp1ikl}PIY!I5wKP~<_X7v-IPi3R9dZl*)LN7^;)uktCT+@|2OReH z(o65`CBO+<5}McZLaL7A0E@9$1p#Xf!#K6Nx|*YqxIl6) zf%T826ol8gu4n1EYU&eM zs4(VDJ;Du5RT)^q0>J2u6R0nRaiXC6mTS_Zw~aoz!XxY+=M!gR^l( z6!+5!=8sEZ9!X-Bs0s<#pX~9LeV} zU~?8#Rdv{?Vnmi5wQ_=2l)VU9xq;y(zB6ddySw5l)yEEH6df6?qEM`gn!$N!?PyFq z?=*z(Fd(Uy>f%)!MFoohTGd#7slgNI5y8@Wj0hfiAjC;TZKuu4b+>I4wIhcj)`4OU_K`N|%Z2h0QwHC*sO+o!(Tea@*&{$43OGDgLXptP*OFHx!It zTxoqmesWdf;BLeN=CxaK7*zAJH};cpVj zvs_0XV@N8c0glW&++4U}2n2Wrt}ou|W1)+j8_PF6*~>e9<<6cRHCKeYDOW6-vQs{n zJJm#1jBtjFya<}E=P2esWt@-m!=*+xN`hEX^ODgpaJeAS2NsC*REO=oLMcQ{c>=<` z;({SN9e(&tk*w97am6jd_YSds!4DKvxltg6dPz+#=mXa>6TAn5y!53}YbZh<{De-W zRLSsCDjH#E!1#`o#~~@#s29-cW5S}{B&9FX_4!7s zfkUj*)}&%0=piOlSxY5m0o$H>-T#W=JdUbbMj}lH5>As<{5=}l9vorp%a?k21@JA1sIwfV#Kjs z!tCZ+7?1?z927SyFh@nKC_t`oCUfc=BC^SDSMn2`w!b!`V+7La6B!^48I})LLocVD zIV-bzY)Y2~gWfj`UUGNrVua^`lhI(s0iT0g`Lh6L%i?k#2=#Jc73u|n%y5yA0(E)- zFuu-OClP$pa{(a%yHJx+Fb0Z;vi;7R5ed+wt6gvaY*%L##ckk@I8B%7bcvx5h%Y^` zK&d8+G=#vM=xdglr!$%5m{zD-z8uMRF)nO3ONF$k2v~L_TijM(1|-Z053U^KX%!L% z3c17ZoXB)GIj5$-*}D0b3yw9cB`+Hu_i4pti+~scOilI;`N$jb`mv_)*XZJLEm-Lx zl+W5Vc8LRTmn`i4^`HDzK`Tj(9Dl}oryVCq`PQ?cSln_G6{O;bJL2 zWCM0fF-nO!CzHLNXjqLKRQ1*H8C?gudBiJi*PFdm9{8~NFFAM zUAoOK>8OP3jxJ62p2bNS4GPV9-=j#xCMXVrQ)NR-r|$tqB*$GSydW5hDhg z(MkUw%JW$zQy2grrP`XQXcR$EXX>w!0!)(kDbx!r0XO+|DnWv$8n}8EF~n>tjepy-f{r zGt*7?BoF~-6}$WW*TplBzo_fCr}g!ND~i#6!}V%w?B9HFLvP)h#4Cr_Y^^(sZrr_u zk5v02DPUYLi|JwBXrX}Aa!WN7n(f#;dW_LOxi@$fbTuZ|ysnbpr40@|6>srem10a7 zN8%3R)$^sQ9B>Y+xGcwA{t9(ps{-+ba4K;K;DBO=phNCJAY$8OkU9&Wo(Y+ z2vFQ|P)xGIqRUY8?rMyT@D!*OQLJoxl(N8=&VgI-G1iEbV}gKy0nLaai#smn8wn9|zF!{o_OpY*!=AVyp9@&nN)watFt8Tz0EOQWHDx`iMVbZ!gR)1!bM@Tj|_GUa~GMVChsGj zA{8&r+N}yB+go&Riy*gy7z`{MEE3!q2|BR&U=d_VEyCZ8>k#}o#BL@E>*lTYE<*^u z!(HzXtgXI13=JJ^E=K+Zk!Q4cRuT&(0A0BNE&^COth7{=-M1+97c4idIDI7C)?l>3 zsHX(2$TeCJL$|=Sd8Eoy4G0jWYgBQkXHB1$kPH-T0v-n+tJY1wW9PkcUXwXN?zzRW z*mgYjM0dn6rolN;+ZO_7@3*yjj!6kZuoE!worDsGTC*^7n(BKG*4XO^Ce?Bx zz*L~mqP5>ZY7gLUR}33#Z;hn8kXu^SG{CZl2^o(sZf|JUMg?Lk!rhOjTo;K%hZtmr z;q^MOlaN}y4VngEXSsDV=|%>@*58`~Z+pB5IGcDzSU$sVc^mg7L5GrPMb`~fTF6o@ zgqc&Nq>Ld({BWeIJTwX+67YL7vk_saMUdO8VHK)MjfoI22OPsd130i5>~2N`4ReL! zM8ZK|2uRPg^ZLgKmasQaVYc{8%gRU5RjC0#bP&9eJnk*a)7SlD0a9c>{RF)^(K8VVIM9 zl=Z0joiR^T)p{{t_l*`h5Wji+x(M@^& z7m{#^MVUU>Jo74dH4DC=n`OzRiDzSTN`v-XCr0BBI4AA#k*2d3RrPHvj@O<;O;wa* zB)@Y~+OrBn!-qliVASD`_Q)qZuYztDQFYVBc9wmqxbQ{#ZS_U@ZO!cbJAu8Q*(CGI zOvtvPL+V&>@QV6GE63uDj5_A1_%b>nIE3EtwQHf2Iqt?I4yWV9dSB_6XOHGzVg5G= z>)NU5RVX8H?V}QrSEyJ~#yf-IL!CpkKMh7I9~t2qme$M%td4gH&=zO@t8nTcyIeW|6(fc*r~pCEBG= zsQ|xaP7--IPv;wTvz*;aRsGeeeI|B|^Pi+9Gzhk02wkoxL#Huh@r_bfwzO?BEfNSO zKM}zFOi>~Utk!{edzwqQ+KFKfTxE`o@vEMX%=pU#~&Rgk` zN5qhV8{9TRhNNlH-J00_sgMxDh)o4{#EvEu(J6<*_z?)0hO`Hv%mPH)%@vgP(apd* z2k7!aaZ5zwXVX>_u#mFk`Ha(P9w<<;qxO;;FCnhdbf!%(6$NOz2oR14&lmimn&rUb zdl+e_Lkn4%8UwwIA^qrBt7Ns_5}?us392x*lM>Q{Uztq^w_!bA5#4P%hS8uGOPy4~ ztNJg`5b?vLguiRVk0pZ+OGID}7&BThHfKXD% zyV^u7+xo5E$=cX%-!DGODA@jMb$qV(D^K(XIdK^sh@yY%Q04<~- zY=p$Qn9DQy6Zm8+ZnQC_*YWDBLUF`^E651^l|p-8fk<#=qvju;t>W=vQhl9uSG>*C z@98%{@uYQKeOC77WGk)lnj9c~zO@<)raO1Z+!x6AB)8C^I5x5DI+bg1l6n+&YimGJ zkXCQfg-Zi!gusp%tx+8}%g|?bk{!XUV2(Y)uvt>8bp3qg2*S9Nu!%ifn*KHKF0){3 z5KDZ!IYSy1Nr$Zn#bpV0Rt{_RwRy4@Ky(7}hiXIM6?bnP^5a2P^KH|Nctj>E1|oJL zBNrOm){IL<+&+?e0dtlx1t?^6uJFXDqC_sM`mktB>Pm!GD)|CuDRqzfBcTF84y{l^ zT3GliC?%MaYgGi9!x9SOm9C2oZxn<+7a{HvOCkb$CiIF)%QI6!*3)(-ppC7qB&e*? z3RyYvXp{Lh>l z*|XDUyI-}3NwGYvNf19f_w1*kfGfG#EjRBoJ=Ayt4pr70r_5NBc-AMvTqe|VLI^f4 zkZQ(jamh*5f2+|g>@l{?673`6+Yo6#{{ctX-ht|fY}k=spCq5z^mH*&c0#-iI5Ktp zMjV*2-^{K)p9W=8r*XLQ^N_7}lYx|{+}OBZN8>nQgHELhfMccdbVVpag>J7z-n{$t zcvCafq)qopPAtjb6kF5!#D{vAL)+e~X<`^#v8K&~AsT!v=7p|HbEXgyTU zwO7_V;srN?z{{WJ?>(m|EoU6n1w0MA5-^{aGbHP8S*0@cPMfBJ;fXw$Zs@?e0aE*U0WpckJ#j*-5fe;OQjbbhbIJ+A_HG*E_ z9J3|B-lL{*W3y50XIRvM&A&ve@&|t7LDWE(3InC% zcyox~?`FvS;rS>eEPSeM$||PjislujM^>P=g%~V4SzhHrAH=6gOG%=<5|68Nhs8c; zIjI3Ujmc@Icc2nc1+Fm0fGdFq>8!5f(R1?ymgD+`#v~3pnR}If86}X+oM`atb;|aS z>YC^{(Y+^nK4S%=n$ktYWk_kcw1LoDc@lEho!wG0g!p6!Dp%-ks`3mxTES;i%ct&S z1SL5BfGrJc$Syf$cA!Jl;)DF+jK}S`ek-PQo?%Go9APsQ0gmzP%XG1Z48xiOAs=Mz zvrdKRMpZ=0bPws-qJRP4a3c_IKn)niu+_dju>-g5^G+%vZt6bR>~LT7hde1<4P3HhU-G) z@La0p#2OFELO4A@Y{$Q$3rJT@6lzh4d<;u2Fa*mxQJLnhh?fM+BOTU;3}?Db zfe*?8u$UmEPOG?rS9K-`52L9NIYxpyQSd5ET51-oMuc3SC>dB%dx1N?Eu@T$zh$YW zPS*^pcA#5K3*lR|wg8KCAw)&0tV_~^)RXpRLZ=9=H^E{xU_D{T+8i;=DX1pN4Vu5- zsL06mW<{J!9C^vnbQP_Ul_OMQ*vQ564!k(q_uG3iV&FzP6u*}+Un8(d3yN(v*>CPDwI|i|o;iPhya7dunL1|=akX74D<}JAB&2CLHSp%;JK=1~QYO@S0 z1=VGqMk%8lVSVwbl`P~^xt&xWp<=@t9?kDFUtVL7azd#Fe-5coY0}~J4K7kP%Uu`x zTr6$bJkCwCg>Bn54Qt@Ce^bH~%EYZ^O2E^e^NJsn$R7ZvXGgj_ncaqaZ- zmbfN14RH=#aBxFyiB!HNX*i&-dGaDYZ^4$_uoRIZpKQxP+870z-+IyjJoJ^%ZL&4@ ziE^Ea+HB>L%!zBYSwT=V+pK>T+zGjv1Ju%7PgzWcc7RAG(adTR$Sx4&yGJ^+v9jTAXI?HEAGaBhFvMTvZ5Nf-H%DBl%4Pl7q*_x#!G%R=@1E8p|a7!g&HGD#_N zFh-Tsg)9n$<4%z)tc*r-4%C-@P!2qZfYRR>lZ(%;Gfo7S+9d23FeRqO;FlRL$ei7yOt}j_J!er z5NoaJ3zIGR^-%6^AB%iz?7!#e1y}a;H<@~!S>SQ1#?ntLtF^mA0nH)c< zRQ2~s2>5S?Z&Z~S{De3Tgy8rw8SQbsclvxEsU&`XY)%Tv2rA%~K#nya!Kp3Vv?p3qxbE={N_5{*bU zT{p@yV)Bis+^ls*vQEA7G|_Drtz?b**1D{_Y5i5#ZQtAZv>}x>vOuZh#)QM&!H6JY zT9Gjwa={KAh_KNkm|yh>ygfgRZwntIO#QtG9j=-d;{*@ZK`uDsDDc6OxvW!0 zdij2a1mzBc-umwS;_tY7$kS)iK|VC_V5=x&gxkM@YtOK=jd7tD^;kqr{;-W`P-7uF z*kD@;Nje$}_TQ|6bgqO#>WvG~XC%Ewu4)t*E@U}+i=01_ie|G|f3;KI=opKZyH#|A z&EH+eD_uVzK0)8_l97X_CM#uwpLDHaU*kcDHXe4e=WDm(CeMumFN%h!p&=qen{n=x zO1h>d@;=O<$o0jL zcf5e&f&doVo5Ji2Ygel#u_XG)x+7dFd*gbzL?c`xv25neN3_mqPL)tuBCZ+R8b{E^ z)u~Ko6$2%{OF9q*4O8U4md*-R26(h+MCpF)63g`)-d^zgqqIeqJpd1eL^Oi`UasyEKoFzE zb$K4wRxqq3j;FfR?Hovq@Ni#Ga=k6P3_8Kjqj6^3!OOeY?(=iD?uhQ@YndKG?Hmzc*(XaRY*^5<#5dcPJ%*kn611TB&?=A$@W(n3 zIffh}W=?hTX3(m^<8=)&OxQcg574M%3Vo8+-Te)q(%eJ(68lb%5HvO@?cTisQctfd zs&KekPoII-JZ(%eiq`QO141xNMPsgy9W7}qC+Z4eYmLym<3a+n z&)@PELPaP@w7r+Q6`K-Dp3%~4sVIiubrO-XN4gp8$1Sf9z-aefeD`v}U*hh(A`K;~ zef4||yR>N=@##ir!&{Zu8^YFW?D2VOtXoqV(k^_kHWHMpC$;bLe!N*~Trg-ktSa>k zA;m8dQ57A|w*#L`=e1c~uT!&|AP1=_g2l4D!gN9n=}+7t$}4-kn$Wep?ZmY z8mkdhbTv@q-A6OE@{~7~Tp#?Ob%0Gsx*@yj^yFAjCpIbC#RnS;7&P^gDrYp7n_(%o zBXTwq{7+Os4M22gbgM&4;a{(FasOZ)?tWBGtLe*7FMyR$T=g20mnYPOgj=I1sn)04 zl}+tgN9ql!J|x%IgvUnqOY{p2*w;>Wk!y+jb&XluKy9jkM*?ubID zojzSolI^9MLY-z{-#>g#0{^If4wr}B*=*D$1TY%HB=*`bqiz%I7{qHFUmM* zf*l`ySG+Nv5@o3?H>8isL8A_WaIn3K+$f~CQBDzCS3X$MvZ@ywoisk#r{}G<#6FoN z^yJsI7L-9%6=nuCSndY_2$xBxu7WCr#VG-DBr~6_sAc1_s?>KQRb?Zn$8XqsWz9ZT zGe$y330ymPRxJ9GYLzccr@ZTq)f}h|xAQ}^)>Z4418!q;%olQ)oku7ck}jodm4SJ~ z+m&CJth}(6oVyymRheRqV@Slf&1pV3v#ctVN1F^e!I zvjR6i8z}yyomGvQUP*pCSV4y~WL}y{K?LUB6i|{%Bf$6O7{uu;U_6IG+{|Whd7t!c zFL!2n_bpX%l}4D}`gVf!(bG|8Vo<^;TWxfSvJQhfjhRv!g5G9nMJxe4%nC%fUIxGG zHVi7zNP!zY(qJgZf>rw~O@9^X=GqV*Rifclv1ZR)KaR{BihNXa8JL~(B zcPt|@6d_15bG?yp1wLBjn(~~Jc{deF6xHw5_*vvWi|P5a?9Mf#3ogXgEB&cdilBOlpXz)=Sc*`oP`7EuvHD^TGmN zMAyp=5s&=Nqbe`n&!e(}HJ6E?OtgfrTwIAeJZDcU?P-ZzH(L!+A+1z(%Nm&obv`Ki z8Ib)u;?uo=NKxXpjRn^E2GfPnN3>>YU zpyIhO)0Vb~G&>OncRIC-uVZT;>a122JzM}bPN!}jfm2#<5Ij*y{D-QW6GqSm`ofqC z*DMvHg}j!;)~6IPDgV4CYA3i(H7bT)i2ddqo#RUiyN8;qx2-_2gQbwj<232efRr@| zF9-Oq8UkgmrMYI#5;c=gSMMvHX4?_qX8~2$c3~)e7oSv?nf&vZH<>nJ?wjGw>>!cIlM3Po# zp)|?6nl#ur5DI*XOx|x>YJDK_)*dl2W|CuIAWICHL;PVW@`mWYqkfG zAsp|4y4J@*lnEzp)wp*hbBD(1h8BhaLx%;jF1e}vMQa4@0xwoRjLUlHa+UW#ctcj^UEp=fDDFhc0 zLqA1Gpw=q)-SEbAe}TUeM&lwyW^!($un{x9w3#p9mQ;l#T8Y6?mV4-B{l5?3gvpB@ z0nLS)k+__VXcg(yz~Jo;aC@+u96r{S?X-KU^eY%(oGS^^ZM4P3oaagFiDIvDw*CIm z8d`F_8!es8sE7l0U=&;vKLzBGcGc|ynj2ed*S?h|V<5g;{v*YAruBo?o~qYap7C@i zx(DgabtFvPRO|#*!YATYd1sEu*}HcWEHyJTJL%g-A>N8x1v}WiZhfh&97H)FjJg%yC0zMg&Bt>td(X%|UMU$pAjoqSU_uucQqrx8PUNauNrxXF zP%@5)c9ycAdFnKp+MPnDPlc*r7ZD_KTq9?!v!gxB;W9*W6Gmf?? zcCOtj3@(fUp^>(S>lU#i|spOs#gH<0SS0r82%Nqu_eU9#G&$yBYA7uY9Z071}1voz~NWAy)t6L+LQy9cqALrD&er6U-*%+dImq`Y<3U>3m=JKMszh{I*2`r|9~)}8G)%aDhc z`+vx3)ta}n9b^TN;)g367E`boI|c<4!q{17=s6NEk*gg8i@WfyQ1sFRv@#?2df&rB zKhz?}-miGdr>FIQ2h}kJdxX$(3i4(I=0gt-}ckecA zwmeltOo$<@(m=C{RJ#&FG1p9h$iaPw2|lL+jHLsepXItX%-ARcVs(M0IYgRRGX|tni zrniSO6D9yHC66PLJdISaB=na=sTht^yQEuQ&7-pvpX?S!1;9rm(~t>P=d5~Ng##sF zq^c@di&>muiYcNvq$3Rs+d;!6?ur8O!!cp-+3S2zYWrEg;q@AZLss*2v)QVIV~_t{VWKR+rA3%F1y%=-6sspq4ID)u6mO7ErAg-d_dnJAUh?@qbr z{73%7P}EE?`OZ+eXr@S(Gt;-{u2g1rVyDbxjPWNkwRwdEh@vQzH0<8pu;S&}8L`vj zsX(8c^JOY5(M6S>)^!bKX5A{QELvJuy-oK~Gb^ZtS?FQ}uiv&auboVp-|7pTyZx@IBSUA1Oj{)4r?IWrDhwpUjJ9m|<2 z+q>KdH*ek=1>NS$8l;GZ5k+8$V~$|Vw&y{eF^T)B`?>?KT(kq>wAuz-t9z&F=yQ8t z=*?)C1coB5og%7Yb*EAk3`t4G2xf%`4U^~}Lohoy=_~tU6h?~9BOeJ>DI}A4uB}F{ z%4SgfSN!rq!*}oz6Lec;QjrJxsl=xRMS*VqgL0N&R07B3DBxOZCwipbxsb}?%vHOQm;gv+S%Fp zXp4*W99E$`UtYE0hJ&P)?sYHi_`9t+**!kG^Y>t$m@z|Rh@{CEt8DKSt$4V)@s+%2 zEiOS01W`3=-lXevW}B0uN|K&Y@_5+{Y{uz&hlm*)Q3HBurtST{?Tv-^ym621zZQ0X zZ;SmvnzZg;;pO|wm%L@`7FPIs1PB4B7@|<6ED=~Z`&|KDUMv{CseW%b(=LP#qW6Hq zB}9gT9hr3Ttlz=o{UL*q7KZAWp$1C1{*w!2M)3p8XpT#+M`e}3%}XroHY$dpqO zB+>UG3YbEJoe;sQ| z6EknL5S|?_u5}y}Y<6K0LzexPuAHWYO*vf){mIjaCXerGUq~5U=*)I5bmlmBk6W6Y zon{6b$=Z>KtNv7=>eyR7NG0Y7R!rg_est6GjDW9NuvhO<<1WCkW{r zad40KU~=$v_pIAGptrln;aJ|_neL$P<}8!yE2w4uSL^+lj98S4Su z#4%@3Y%t(%XOhD;H^|pC&v<_S(boCcPqd5CuD{~e`HS-tYre*wty}0E-}u27TX!_y zb8!bo3+w*%%A&7R6D5B}&(F@{%K#yUVs?gu#(HTK*utg?&q8~E({qZ+40&{ZJe&?M zyrL0gJ)_k*2G61_q97vo=@Ulu>nF1kAAEq0_j;O!hUrn7Ji|k`l>h?97%2!N8j6L7Q4noK$Ux+{P_Qdo zgwP|A8T*L;)}YB zvHwm}6LrJ4x7$YPOG#zX#q>HW1dPSdhraoSB(ayE)_HmUrXgKV9KL%{pRZY$Ahp1q zJ`sro0=sZHjCTzT0A17PKqOTFCRnoMB6@cBZm4Qv433NlfRWEfIu2t$hNKCoz3LFg zio)J}=<#S(IJO!TQANGtdWyRkQxyH(0hE;j9_5_%`EISrk?;L&$;2twF;#rW!&t=- zyD9sHHFb`RObxxA(;V5E>Y-WZ=FMAXdz0K_Q@k_F@FFHz)|{Lm8h zB{8?>CFf{vW>e4Y6CXI&M1RxVQRiq08IZ$VQ^&>J$0O>;t9nVkiehexS#ftOF5XTQ zWNS6ydCZg;9YT1~mxRypv;!krUP?EHvvPu85cEHy<#zLEYSJJ93l~BMd|C!Ww4me$^Bz z4a=DKPl}E#Fsi7Ns#CP@P0V;W(wxERfZ;Z8^YxrI; z7p&(_d8hQB5rO!9`TJtQ$UmKWL{Jt|R2CvYhF$zXeAvQCnr$5Aiz}=XSdon&iIY8uYp^h zm_1~t4o1|4JIJ2HUIe}0n*Pn>i_S`?#anPLAL!ngFeEVHtPlsr5co^C#_q+2DIq(S zkfuNiCBZ_WK`0CopGOE1GYl?B`{!2hniZ1Ixx@&@^H+qLa1F1yTr-VpCRSpPmOZhn zzFvpi%cbQ49E!T@m*Wdv7w~eZW|~`c*!Jw2go-Jp+uv_kzSwE1F^h*FV3Y`0ES+E+ zQ)3vDfRs^(2J1$X;%-vhH7d}c$^aiHeF9Yjd(|Yy16u^ye*C0lpK?mi`zqhGJs-;tWx<`bnb5gXBG{ z;$b~QIokPpswWjiQ^_YfN0kf}Xv3vBG)duAR`+XvAw}iI-o6;i(8p=Of+qYrp@d-p zms{l)LRG68gv!5s(G3T1ehFz?7xk2;djZb(0c-_|bqOWk}QsU;^ruhYK$HmJHp zb4{g2jQhARW=}0!H_#T^QnojpwIW1=M=6G6+H}Me(;S#sy$oRZNviOC#dps?&qwd8 z)HrC0XZ;SI%Fo@}-*2TGOEc%wR~-;?C(m%L!2;OHiSy?N287Vn2?W3mZ$Ey#{mz>9 z_L@t>@4vsR`mSf5x$E~?ykxY5XPG&C73ir__UvmXPPAL)uyu)Uw9o!`pu?Ib(MgCF_Zy&do9u3y!cWp!*y3CVKR*L2Wj+!Fv3@;C`}g~yy}wruP6WwS#ctbx;A zqPI876*;eo6xLM-bAWc3VuoQS;-Ud_pjgl zFv67&SrSis16n+Tr{y$YTr3VSL|1~r+_-!oVj#$^9!Zk!zhpaGbQWUnUtS6w$(-kn z;DLZ}!2G9R@b!N4Uj*SVALa#gDk5=$)TebfyFyQe_6sU_lvLZX>l^f&M(}ufZ%a!r zHBo_@^lR52jW|+@K;nh{7kYW{wB*z)u-TM}>miRj%m_;=QU9-EL6DGa`F{WR&ly|GQAE z@1u2JWI6JZHsFhyJut_cGP^* zF+o=%p>I4)rF!e_r{ATouT8l!>k~h=Pn_ z3g(@=TU%E*p{A0-*1?ig)Men*0bIhZ=WdpD=b z;>CYYK^fy~&AAJgaUn-R$K}6nKy!!FXr!rqde;4VHaG~!u3I9izgF3rH`q=lwhhhv z@aO}#07?-P%^K>w$k^b_?<4E!AF5gbG!6ZBC8$V4`}{cf|qHLQiMD+A*l9`*XM zU&{kV`uqA?bUjAE#0|-~F6G2jV`E}s-~PnM5e0~t1^#gq5RCJ1k+QQ zRZF8uh~=ZKd{|H+kY;W{lVYH^3`<{AH40ePa5nIL9(#^`!hsLg5m2lQ433VcuahmKt< zoXv#i+}1&{E0#mb@^70{S>3MhXx*l*gdvOCnMD38xpyfmlAG>iwL5pU5?AXw^Uknq zO9T?8L*7;2t;%A{L`F3ov>nWpOJ>3tKc(#TyrqR7JKAzV_Lv*Z@_2u!0^Qr9S-6Jk z%`d*VF-^j?rGY@aqW^jzr1b;)c5w|f`;p&Yl4loM06OI?2&5eZ;R=IJQdC6{oidIo z=_V64esk?XnnHlcN&j{tyab9u((g!aUwnL!gboFGA|qrCvcl=--o3Wir(|h*l}uW< z7CGL~Aj)xf10F{J8k=n-vMSeVni-iN=yg47kC984lHh`QUs7ItandKF+l(|P;W!C{ zZY}&J)2g0sdXS#x(UDW0OAugZPtJaD63JAPsMg6Is`^%|v(>5yvEp>#d+5r0?0gk0 zf$*^l5+Uoe0WRusGU)gX)1d(%4i$3k??Di-z9z*#KInaZyt z*0^O=jjC?mJL0Ko%QETEla|muFf>I(^>QUe$km$IDZ`1n`Zu0%LBt+0a6KG@yqa?j zz&Jujv`F#I- zsmJs!n;pn;pu77otK~0;9!QD51$2&4aUIP2*JqziF-1R1SgcJB=kn7gKI|zLlg56( zx!c7X{@+Cf5MR;-+_>q!ajg0$p~0bF1W8j@hgChq5!XNFRcv@D1P}FqY_pPs8a?ZX za*I(R%uqUs_#7e2w&u!5b?(+>!mtqBJCTd(vRw%-j*OrY9~{wxrvM7-Ux=)io-kDo z)Lk22b}3*?fe>8w(Gh7K+#YV61kx(Iw_qzWm;DbHpOCr|A6=mnoHI&G3t zv-`w2iYta^BO~tJ$fn!!Q)rD;G%w6=NNr3FR23+hqnnH|w96fz%*fMGuqiG90CP3|JS7B2ceOEJ1 zZ!!$Xo_+?@&R&nO)7yip>73rTX-0b5%K?z_t@U024Dn(ta$UEF5NTwi4x!vPFr*z? zx7k81IdfOf)t2Q2s^+kqj#&w4e~3Rt7)6*?9$6TLeRD6A*~ay{oSa;kUwhynA3Of` z+ow+V^&EWT4G`?7Jxy2^mm$@Vq^Ab9PNjNL-=~|M$2*=Y4&6kTWoAVxY5RP5c zMKLEx1g2h~btIri;P+6z(CeRjE%jPgoSyt3ob6x)6uvW&xqfx(1+oiw9h zP`A5_^bnCDEQ(^hVDg0cw(_G_S2}LA-*BvamCLUi3$}fF&pmwfY-=lb{?cu=qsI?N zpDc)1%pHsMe)j3jibqFBx!c;GnXP-V#{qOTHBL*cZfl`pacEc5MyO*a(B@L#+ zFQ*~ps(dy1syLCHblh|p67ZtKA3f;RuaG6-DY1-{%^TG&tzKh&F{8-UE!F0q{dXBz zIv=A&^D8WWHsu3qCarpidF`RsCZt%<0q>D8xK^gHXre?S^8?wn68kCWMKomyYLN)~ z3YJY{iX()~o19K=z6*iHDHK8&C=^J9Dj~)7LF^+^4b*^Wddj;I4%$d2NN{Vs1PYZP ztrk7Lf3&4#)R3O%cE@5xt#AR_TX?fJA5cvRA!dBhY782x6?Y0Cpcu}eIKmF#XfXUy zJsd`l7)scG+q8^|Rf3Ud$6pX@pAuxJgXo#UZE>6LguyVgk5Hij17F~wE!%k25a773(Z<2BF^INIXUL1B?K;i{zZub?DQ>C?nnfIj($)`~8MFksuCBAYlTr!} z8E%WQ@!D~ya*8yhQ5wqSki!)6AS8qv=QF>XOTEvCD+Zz!A`~*Qk#6%tJ;o42<}Soh zh;!Q)B?Y-)LswjT9RCU)EG-6HDC5_DkX9;2G=@NWUR^&MMMP0J6^}!% ziEsnQMv|iVzwXZzCe`csQt~7K^CqDFBd;BdN;f$h8$FHA0VntZ@{oXV60-Uz4pv|s z3X{pQC~px&)BZ^iFm6DB&6GYW5wm}ZSVhR8v;c!&ZM?kuM zpD3|=A?WBpRLQIYgLX>Fz5uM2ohA;xKn5hg?P=owl6cJ*jK$-I%mCp~F%Nc^H11V_tDJX^}iHmZ)M96`y zu7{DGqAU~!Ip$AZ6l@|ODdm^z=iCl=T?WppST(pp6-r_#22QI;HA0G_C`3i7&p!2o z3te|oUGO@|H5eNPUWS2d79y%6wXD(u;?jLfmf9YRyy|A`Qt7nNue7-(>{N~BtcWq| zoMUED^wSDD0bIZgI*88&d~r01R~)ij_fV>-gGCC~MQq;blC5hDq4-0_SlxD%IA-kU zc|rN|$;6Gc(!}I^hmHu+B7Ue4jcHI@gmK1-KCSt5Jq|6J(}Ig}EY9FJoGlVsLSmXk z{I@@K`fjLOuq3@hx@Td=YyPF#DCbqQ8huo^a8*qT9mHL4!?jQlqN8xcdgnAQTwydY;?(=Q86 z4MO21>6YA(&f#a1Mc)g;S=*@arFEj_vh^yyx*TpD@%^WeW7Z*}?**3DdH&SUw6wHx zTN9L0V)aRt!i5XJHi?MPLvGVmzEKz-d@ylf|3pG)-2O)Kkx-QqO?xV|Hc)34AD{GD zMh2~mLf?H=RIF=UvvORo}Ogg`XF8Ie55?bLjSLK(e{0}~HtoWT->IYCT~QDa#Rj02*S^me(L*PP;$3h2W zF)$960^Pt7np&vui{}m=Y8iPa!7#;JaXHzJ8yd=DQHyI)cg&1768N= zKN+cT0z@m6yQVS1UZo=VbuZHX$%%4$bM5-49cG@_D%``dW#KUD3 zj~RJXXE}603Go6?qntrbi8LrDhxqO2NUiEsWvWuRfG=|3uc^Lv1QrAS5|16NLgyB0 zQDF-J*uOK1H!B2-qW=}?rlf%n2V)?D6r)?Go9ikYp$B=#ec_Nl|Czq7wH0B33&+|_ zGM=paxHgx&!Pc;T86$)-HfwQ25FQ2f$0(-D?uRi0WH%fcZeOr1cG-RK#(6c91T7bP z=d)IwdiF1A{hOCsfGz(kNQ_X{vH$T+SqisH>tK;U85i zreFfbs@1B!bYZ z!Ym4mFk%T)k!%4o2FA5}tj#ir4T1o6!rnIH^8vgMM`1gB#dUh&lDqGJdASW?r96IV z7!j5!;Ae(eEFhgEVo__wz>UI({u7nObWrBsFkO*3kb8M+5CT7B!{qxa?PHfs$XU${ zLuii^Dvc6E1loen-4(Ge3QqjcX0wr7eO=0xs{uAUvi+cGFljtHdx0s>S#)<6uWB#c zw`^GknqerjK@x32=9^620&5W(_Cr~?BABBrFb*gnle8wr5Lyj=Ll7q(QR0h3CAo>+XjvoDeUHtB^}Uv3$i% zOKkeWK)tG4=yfZr0KtJL@5}f~*GVoh0%8US!mp#FiTrb$abJ~9FJgka5zncb^1Vk| z9$=A=#Vpr2-CLUARu+F==M5_LJHYl=pm2J-SQB{t=e4H zw~Tw!C#B#ug=48h$R$22#bU`&$ZhB2@oQ6yBOoOA&5ajKgo9#!d#TUk+ydX>nag+x zn7vpx+*;s@sl21=|EQ}P`?MzYUl6!`F{R!1>)Nc%haM*Qn(=EX-5-H1;q-^|A5IV7 z_*m*Urn1IP$aU?krS^N!P$;E=M9`^F7YR*;$8V)3`tG}26Yi^E(Vr@)rBEoWNC`Zt zv%otiR&F?5x8c;`mW9@w*8`t!JoQ;!gY}mA;<=SsHhIfQ&x#zHkp`bTY+LI?ze z;2W}?ohNfbkBovm5+vcJ2SxsFLD)_|2ZJy*bAsstqY1DJl!QqbO%@Sh6wsI05sDS% z>3&M_6j~Ig=s)qPKyn13g+c7%{Favw!t|TFcY*;_#$HQ=yA(*5*KB;(ViZdhgRnr# zluZs%HBCgvp5YM)KlOK7Iuu~u{o>(f9}W+N!yEtQbY|8KB@!RJ z)6?_LyNN_!T^%)1Kq@AW7WL_(hN+7=!RHcf^V;&U00Qzk^3Wl5Ch{pQOP04REjzrf z`0rZI=}%9fWE4G!VkQd2$V^pr+t ziV+C#HV>IU8=3G1^5UH~HTLm73Y^HGzx9+^Oz<2LP5cRw=7@9LX3F{Ytozy!m|-R7 zoWX9XJLo)DvU=6S-KogY|Js&%-@Lx|+O)Pj$DGv7*QWlY`FiG?^)3kV6&o%=;w7X! zoDF9TF>`SW`-qt~HN?IJ0PqReY&@z-8-SA+?a$09Xy-sKr=p^aAr+s&RFHlYg4XI5z|8=*-?nWCOhrfU zU8e(5I7N)tHJXi@wek+|Q0NOJiQqxY82j^4za3}GXjXaPWbqD??2BL;G;w1)0LP>h zhY~_~KzxRZ=%ZYe$fc;O+c6PEXB&HGT>Aip|I-Vy0Ph#zflv^>vN-R9G4(1UAIv3F zR@I!Ta69lc{Ounv+`OVB6&xH^l;eL&&siIFZJ`}#fTqPo30dKvL9me(X+|%q2By4mcP30ESYw^Lh63liZq7|f{&>( zTlLmfs+^W+ZvCp_??CuDTDG?el zq6EB28arDSr=gz*Hn#A#_U7+LNsCVdfw6zki=CprN>cR&l(Pzbo3q0 zZPG&oc#O7e1pZ<{dUhmixKalx6Gj}3z~(mb8mIeCAbb#BAnTg&fi<=v>;+6H&ZLc) zd4NaFwC;`bNWN`(I5`XWzdEl8llxiNDO{ zO+qjcS%0^$DJ_Qt6+2{<1G=4cl#l@f32OXT@i&LqA+LMRqqgZ^%DN4>;Ge7yRrCRMWgJxZ$!btj68tiqf0e zyLB5)_9M0^ib73v+crb&p)EAUS8Uk2d5dD%O0KNc2sEJXV!R2Ei9MU5+Q}e8m{Ur5Po(>p{s~Cp^Z{pFo-r=yTB?p^$ zmABWA4FBcYiOuZozZWJC2YQr#-kB&Z&Ks;6$Qyp-iWj^hx76fS>DT9OZ71t=%wv_n zqMqb~BptF$0JC4lA@TW9-N zk6(V2x8OgFNwU9J8AkpefhfS4In#4b`#E3430yy^uS8qfNPv;+8 z`jn3#g-#JM5jFuD{~089mh+|$TgrGj3b*wAIf2bJq^KZMRA_81jy?7Ko{)1V{&3tO zXgot*AwOxQnqv9#Qhrm~uGANH0ln`hL)})f-A~x9wCviij9aDkwe|IzRy*rwus@St z%6m$V)!?<96s-ChSn#k{fum+@H8NB0O#O4;B`n|T`|D-uX1;Vc9y`q8WS&!~&1BLm z0FtMw{DF^0v{`2Ot+Ms=dUMYRy(oSr9x9u8BGgayv zWo}~rU*D{X-#7(qRx4YVSeHEIG)*RQPn)$3=m;N~tIh45t0!1`LI-N$K`_?94%tig zpMTOPBzd;Lew8ppaKDzGd&cT6?ljca7>&8>J8v-L+9J<#tu+x96;>AL_SKS*Ju8F! z7S2gXWV1cS<(8`0_1P;*W#LuHvXv#JD|rdQK>=YUA8rZlxZSDErH1|gES&6>$407W zX~jWa<!+?hEy`Pk7=% zx9!uIY-rFiPycj&wF7J`Op18foTB;v7C*KVcpPF_Rfh{tn4XT6Wf;n~5bM|52w{?7 zybwF;L)*3TU^EwmBz6)`!FpVAn%M5(%gDrbBuRU;9`YKg+Esj3>K zU<{Q76<7?iAZz2^wr$hFM^sWD!Sx0YDf0)zab&Uedn!+Hj%QQ(@^#(2A7neP+q^V0 z_muUN?R8*JW9MgYfG;eks%T8Y$k#Uj@=SQuAgpJ z6DmwvZr!XRH8%SAnXL7hylU`sn#lKnqwCzkUq133JCaDBSs)4f!!6E>KDRhT(n@d2 zr&^wg0Ad;h`N8^FeP@Mxzqbd+ZeY7?a9DtL@hZ%&lFB*Mt_5oJI~Yw3qht2xiqisM z>5C9E;S+q<;%@LJ(RakuD?)V|XHy44q3_a_9;JwMBQZ)2kHN#;9!-P$oZ43oS!yg# ze?b%Vg$bIdokx}?jvPfU#CF8)lbs&v)9D~U1jog3Upe3_3dJms0dCZCFVbu8v;Jyp z>+S73K6z@s06Rd$zkQh>95nUK%OB0pW9If7rF>g#M6>0?Amr8{Y7NPeN3bktXHQP-0qbQasMR61a+aF`~9nSjg7bm>g@qkc4 zjlfZ=?AKzH-ibQq3Pd9}5Py4r^= zC3{+xUy{Z1Tj9{Gt3CdCwq~0Cc@6~b@M8?dyF&*VEmC&x=0}e%#sg*ZCI^AZs1xVU zH#H^fV#%mkEPds$3Y3aw+QHS(C?${6FWgQ+M13xunrQ3bzn2?Uq-rkLOw@#H@`gJT zou7A3cDlEJc@k;vHE&9$iHYM<*7YI7fnAgEX8RhO2|bhF#r}-rOJwU$ZD*<6egN7J z74PW8r}#tDMD}c~ylv5oy1P7Tzx9WO_Ny3dsq)& zLuo969@G3`hpyz#z5C^c?p$bjZey25%PCvF zW9iAmorh1Bs>+UQ+qC1yic4qpz^&{{HVMe#_?dpydf95ELaxirbG016Xy6wG*>stH z$Nl&=Tg%^PEB|tg_jtjLBiA@ei}_`S&t3U+CI{7gu<1|Y?Z=1%WxMhE9p3fafKk}^ z>ChM23Zt0huf1?IJhDcI!&MeK_2{8(kw7G{?U94eJar8HD}-QdeE1RG((2nJ@J!fK zm3S}8JH6Z4(Ghs$`5(UY(zO@Ov){k*iZ(iTD4$mc1VsrWDiIr>Tr4!74<_xB)U~TS z=88(yX*%a-ua?xNWqhq|SfF_y@NF}N=fwoL-M`7n7B&bAFMq2Z300N(+gR zq?McUOfy57DNF{O_(eP*iST`5gh)uVM7s7iZ*h0>+jWtThNLjXbl6mTF?!;;Px~t5LiC^0_#zKHNc(xx11(7E2ymw?J}A zMfsCGFc)k}v>MTYhSk-{6KGC^ZunB}xa~Gi&!-sB15$E7Y(9?GU^4zW*zu)vo-%ENsVMH9iG zi9{k3CS$p{X#xtgW9DvQt?J^W%@Ytt88~Pm;t^;&Funj!uV0iAOqjCkoG*D>0(g-< zzk%DB^)JmzM2&Tx8Mz0B-Vi9Hb0=XBZgj2(O;79eu(ZUbHG5>|c69D|JfR8k!Oo|j zUC0>nH6@X-d3MNn$2 zGw$bI&c`1g9H0ZeYYuef`=}Gg0@wWyY9d(qViP)1#fXdv?T zU~wj5v!HHD6Rhr$$@t-3*;T!YhLuqfkw3ZK-ulb2=V*c_g#41)Tf^u z#q5*yS9swWhKzb7WOL}El|1iag_aAqdqB?F!j^`HS^Y??b7nR>j}Xr3;>yP!TPcFD zWO+a9<%*ZH66<3{L;p=~Z{03wjKRk(H*OZVj_6tsON zM4eILGn=k3fq8*I31Gi6j<0%3@Ym3*Wp|%rz&-+vjgDmN9Q%)TLlIQ3Yp5iPX7pVy zR(}S7gHIIK=GLrn=N3Pa4gXk`6ymBjX?X>gc+Di<>>o<>bENhQX%+qm+sg?$n3!*o5q$AGxIKdy=5(U_Q(C(yc>O6 zxR{=I&2pP71rKi;U6ukh|10RCy}salMz0)~8;swRd<1JpRyj(G_-`3LKk@0uIVgX@ zPqEMCCN~}|-Hq0HyVm2EdHDLp?zP4JazzK&{#}A#jYkSRH=XbV$Gqx$P9G*!F*Sa`3_gCS9hb(+^bWO>cz#2QBk7L zQ%YMu_*Nm(FGUy~vG|>{>=)Vh!z(T(Oh2z+-dEGsRF3?L>`ZQA5Q84&Q9rf{mM`U7Ms63j*K$vPw}Dy0$i@q_W5X zyI$|;NHlr&%x3K^KG1%k`0oRtGhBTP?+HA@9}Db(d?bjtl-lLBwc9glGf0?xB-Y~G z1YW`BRJ(Ke>}#8}Hq_=X+>y5^4W49FY+{O~$Z*TF;PbV&pE%yp=F=vgc%wm$nYRHP zs8F(-tK7A9N-EO5Q1K*qU?pT9iZ}wNgxV0pXoK#I|AZ(UGtz-Hn%g~?;F3wWw*k<^ zH4TIn%5MwVH8Gq6w>(9Hb9V@Wf6&d;pufiFUAx>sVbAY>Pr77aAi)P8iK8h|+`8Qf zxO4)FjTH(R3*ZYoNZC$sa?1)HWRQ@!-BUVziBdQLo?I;r~=b+So9Qq$xl9b}9F?*&B^(Hd2e zF_vCFoG&;(6zvlnF6t7+7rKxiN$6-tWZKm&v$KD=+McrU{uq7x0}r-0R0w6NH=$j* zBk(dQ2;X~eeSPobo;{IbZ=v4ua^0Xl!(bSVAj22AlsSOo7&c;N!@+fT@G})|nN{Io z{-k|mO5^d2Rpk2m2u31qZGL}F^*meKg}nd7il4ouw?^{!CIoU86HF5k#+q&juVX3zH53wBXT22rp!!>`iw0heOn|Cxt9OzpQ@9GF(B`>07~ zOm6aeb=@5ZawR|4K`i#~8)c^uAfxefktw{8feEygTc=j9t)xNuLu zEr$-ZDA}DO#Pw%Hj)$%~P+U~fp#U8GRG|6;jpMl4S{(*{x;6Ap0pPGw?+(i&t<^7I zSU(q|wY=2ZjTa03V&+aJDtOF>@EUPU}2?M^^1_+Fp zTSrDkR9K+L#nf9j{Y_mq5KM~(bHn;NSF2(#D?gFHi9%t*iYF9`3lr|5ean4)uL#-asO*y^8SD)BY)NX^ox6>xfMz+h+T~n=yMf_gP;}#;xot8;XKXIie!x5m z{`+cL0=f>DQcOC%e;;dlTo`8D8%;yRelP?RpaI1|&h2gfgNYD(cJ10(MKZJQ83m^U zO4x|COmLVOo-ge)$1?jp9-f$XMztq#g5z7`=r|?N^U+#Tx zEnJok1QGLWzM3)kXk2jHL~71#+&2A=F&OH--F|N^cE|Zx*~YZK+XE$&COy?J9-b4q zCIjv>rRE;^CLEpyOYgcD?7)~lqHtFD(dWPGd=1SU^XTAR^X~HH`Y(@yXG3sU^)|-{ zCF^w|)SlQ1gt!2L=!%gMqM{dfU94pqJt6Dv%j4sh!?fP();jXUwg0|IpYe>2&OcmZ zlht>nhJy+v3ohSDhl*g={QtQj<~w$TV!QYqZrSU2z56d?V^^*u#yX@*DirYQm~>um z`})t(y0s0nh>4kX6%ux88{1EL@sID91;|i@_Tji#W&n4EmI(r*7R@Xyn zMOf7%p~S_j3I(thYFQ)<*(MT+@xqCpnReTdu;@IWA0ylxpaoL(E`L$~-(r^|yMXrg zx@+9Mo_B;%A=Ok$;Ajw zVEYP`o@%n<%I@vW&Ka{g;U(=8K29_p7YHT7@$SSM8{hphHHV#YKUFFcrNV6Tv(G6a zj!EYF+X057dLVkF<7w!5cq+tV21*aMVZaH54|(IiJL0_)1GP)%(Wl$5dy|7hW{+=- z+u4*jk`7GUNQ?CYy!`G@{B;L3B$dymaIbSStzdhq(ALN;$ zA%U9ce`^OUHIju}K1`Ij<{DQFeduUOTyu?74@D;wYsG7-);~`E{;Ff9%m9irzeuU* zr%0rJHjjL+Tk03&1N;IL%|OsG>qzEB-Z{VKd1y-f?DL7bT{@Bud#jC7qWxVtbPw*Nq?<*_DDlYz6 zhZ>PkEzoh%Xg;Z^_5MLyS*AJSO~SN=sKhe8{=WxSP-WSrb}BCj@(7}>{IYno_^g@_ ziZ*y95&falJI5FRJdVl}ihjd2pVm98=s$f$_!^u%jSwa}2#W=$w}>j|B=+>wn`*%n zsS;-NFb+iat|8hU*rcp#pmkw@eU^$`i+hFJlHxv8Cg+ywy>jKCntMlEY8RTbJlK|- z!g;mL3cq=bW5R+A#g=YRyXPN|{vkQae> ze%R#C%_NzBhHuZ|S;oo9c?q_f)a7JjZl1&kxShIWmowQ?H95si z{j}t*3b(k&lS)7yX^Cc5W~bM9hbS|dFY$&!x5a?S?!7y`tIb*!h~k&}%4f;Z%ngi? zeijr}o*1QL`PkA4X{_y8{m%Yk>v^5{P2=1ueClr#cOjic7sPQnL43&hH}p~A=3IK* zJt44VP>Gl!hK29m&wMK1T8_m}O*n|{3K=1G9|@8f!@KpM@xpi}d-jS39^)R-TQ#^q zZZW#C9NhpYLSq_YgyuCj;+=sgHW5H?aAdwp8!nfd+#R*i%lK+btmfGHqHM4dnw^}Y zk>(8yX?s_wiU*{J;$)6eQ5@$8?2VQ?o^Qv}5AT?}%{8OPgBPQ|sQcmzP9i;Kn(*g3 zosJ!(vN>1gNKVZJyxdT*{F4sHnaCZ~9c8?=fjjM?QF(aaCgbOwzyLmdZCRVqV#&%P z0*=1fxYkWaSZd{I;aN;sX6&($aMg#+9~xOge`Y3!k&*G!>NIm2B4^Z4eH=H&E#r;< zO@j$|-s&qVpzHIaF~lPD^GKXbrYkx@eho)lW=e#hQ^DJY5MwBgg0w0gMq;#Fdg?~B z>-r%0f>u{PqF^eO$F>lEMxZ3JPny_~zUmIc6)o%nrJi9vHyTND9ndY%0faD9IXtz| z%ZQQl#Dfp?CWihmeEqzaS8y31aa|6{_i(|DL-oOQ`TFegQTwl%1dzTjsrl)n0hfaG z`;&VR)O-k!OKo>e`Szat4?_siaQHxsv8{=71PHg=-=k>C6&EN4(4{=0A=kAQ z>{!L*&=uEcodQ%_q$9X`)DHf)kqVXtu}m*V5?#Fuyt+04f%EzY-^v1_H$aE5(X}L5 zAy{-7gWZee?QT6J*W+=r)X<74~iNvXw9_xSJ9iT&f?|ZgvO$`-YSA2Tp!bs zPjRN>haO}#>$&_nOQd(Vf3b*7kD;?g#k0F8F&Hw4xH8mwhoQE6o3dGLdR|m>!hoo- zK;o&&to9VX)8*$Zml9V6OsWBL=A**@Q?d+#*vzcBlh-2@H=(2N!j+I~a^)uAE{3tGg;nqC=7d~20rQe7z4c~gi;yy7nmL#gHdBw#% zp{rHU&dmL1YHCjfTs7e1?j^YFDPya%)wnT+67ViGZnYB5$dEG~nChk1L%wLlx=M4C z!rHnvSoHt6$FPG+wh8DsZ2M(n)Tjo#pkn?(eiij8CzZQQLxNY!HbCqA)TPIyIOFp5 z3OuNQFh4~b-;n3tl){lCub2Rc92^=6}y~ydf&(V<9(;gf6!v@VT*bZe=Gb+2XTDc z>CDToG(w#cf)6HZ+`z@5No1Ad{jDAL z2+oduP#aGEJj9C|{M)zqapS0yvS*E>vq942bKcqcN-9*!OW@_th75cxOtpwu8J?NU z70fBxf18L=C}D_^=_2R=!_C$hij}7j#-qEiVG{@`adF!yx=3|wFyc}dy^49o{xE-C zBJne~WDtsbq=&!%cS=1ntT}EuQyO^%7BG>zi6kHGf_cqI=}+!#SZW9CE5u9p;7>|N zE(vtIZJYq0t-Fni?VZ>qbY9r%dF3(>)(L20LjZ(CCf01pc^VihhXpP*FfDr|h6E6x zP}_l^r55copi7{OW_QfwUuP|ue-lFw3y|G@^iEP|yO^P&SZ0z0Pi-82e#ifmSC6&K zJ~s3};8giM-WDxh(-0|@Kfys^2y_Qg-hCNjkPrjrFVBTRT6{zi0`4GqKBBpMS&T@6yio&E7vp zsUbJYhCfEvc;)op$@nwqnPB~@Wn2#e7sJn05wrbQOI+%)V=lGi>V8IjNJWR;1Br(h zOcXTVbi_0RUJbaVq9^5|HLt$vH`15f^V2(}FlJT7C}C@(jS=xKhTuf6t@QZ}y;Om= zR#dgN^tHD3wX{}=6`xYD`O|F1`0z*lhrZWpG-&RUt*U|_de}at(7!dDmcDignyYF4 z(91}#Svgqz6Vv1Oti-QEiVHp&Ga#N*X{zkEodwFoFF|Wl`@2WwvFM%zxIJEC*3;0N zoCxo>0W>Jy7dp?N(?g?Nm#Mr|NY3KyWcu9yYVjaWJgs=FYd&h z&rp?O#u%nfrx4#hvmlGZpUfjW+@;UZ&SIULH)c`PrPNUL{okX>J!y#uLJ}IMk_#ep z>4Xy&WFm2K8HDqK*u+`gFoxmzUpcGk-cj}b@bH89__>Yx@LyU?>3BN)=-j%^NKntB zGv*gwP;}K(fjl1l(hDknIYrT1#(A^PWCON%?M^CL)(Mk>P$Om`mtW`dofYG5609XrT1rG}~$doi6=4 zbr8~a9a@S|&E`j$Yx{xiy~)$)2jP#_JuFM|!_JT9%uV0Wd$&E%zQA7tR9840#@uBr zn#1qGpT963x>4P#-{_FI_o;bbV#CIfx7u;Pa_x~Ldg(rMi7o|mA-lK5y*DYMuB2=4 ze2*tHI-G3busng>bzcLx;Xr$ixtPeh(-2veuLEmx6 zWmXgtMNtC5$HFw$j(bjT4cz|mh0z|u4H^*fpuw2y;X{<2IvguzGWo!Ry+dj9nGL7U zr+-p79vsbwXr|et>#s)a{{SUQHJfzUf46$5aW9l5B43+#FnJs;bPtL-bf39JnPh+T zo6S8vjg#|?45!b+hsG}_Z75n)64=4b=Qlf=(>I*Gu3>3deXT;=3$|}hbhO{^Y8PK? z>nBqB=9&UlaMz+U2}mEm)Y{om>uB|>w;?~T!3{HUt!-Po*T8y@j=e^+KUG<)`4aoI z<9&GDyT90R6OSN-MzCwVwABa70W4UcC{KIMEv6{<-w#!a#VHs#ap7YO&QQ^B#H%hv z=^6uWmRZ=x%h%4*%Ux+6f@{J4sMSpi@hP*kQ6vf_0bSony3;4%f_)7Gu0xZDn>;r z#;Ca8=$X6EoEk>pZnOO>Vc7J9J?Hj?fM>ovb5|Hj8-8W4MSn^H7J_Vloo|PwE`PBA zF9hCA=r=v!o&<5Qd7y(au(DtHPkTF;u*^Li_hzl5c! zv_vZtnr)p3|97Q%N#a0R%e{x_v1D?gx&6b2ctm7aA5aabsM$1Vx_|Q|c>HlXTC4acZoyDq| zdfJptigWd*O{=R~aYxIwxZp!md4`{aH|1laoR5w*w6xE*^PP@|pvt?waJ>I==eR|N7;;B?2)^5(^oYS&e3;rcjl_NZq4kUY zG-4A~Lqf#F=RNBXNXIal^;zXEnTlN_fl*2EtE|bgv1G$ z*EdPWt+@yOy4<($wKG+bL-`+kEU(`B{+_xKJOYQ#@A&A04e}=OP=2m|*_OYK`xdsH zJ@fd`UHKoJ{{09Z0bq=nY@Ixbe1#4+wfv9(EF_{Q8;Nzrn(^8}OOPyMTQ_l-{-Q(H zlgVjJNh{u3ZSW(0`DL=by^owdfX!jBXml@7v^6WM;o((R_?DGzEF(4&Gs?^g;$~!` zvHy4Yf*X%+tn6+g^7EC(6}=Nb3+!N6WPkE>tO%!PWx zUWL*s2|P*{751JqVn&u2@i!K}m}f{#$Tc>qxlKz-xzPTwJ}9YYe8kENN;?qRB6E8K zttAad&jdv5kfcyKcG58qYw!Q93DMbE&DmMeeli=rr(`HRMtuE^?jFf53diEQf1h&J za=gFOxiP;kxjisv2JgLmL5i*N0Qa1$tjQeoVp0$1C|xJlCxRKL^{gFlb_UM~d2bTO*JM^2RShAs%&QE4(o>GtlHLSx;FIhBiu4MScCax0E{d;;R;PR_M>SU3?kCoY!( z7Y(>c3Fr`|@S?po>>3H_4e1G48v+h+I&LS$?{1q6c~b5w|1$E+3#0laUShY=Tv~7@ z%)}+QcDLMV!2!jCn(Z1w9LmXvN0J`7VCypEp`m*J=xuA^&s|v*-7s?lH(sZUjdASQ zAu2M_82Rcv6^Az01#!M)^ide|rGR)M_cTwbuGp`{Qy+t`q5Y7LdEV*9pY&5hNNtmMec)xx_Rh@3)Vy2T$Z3j5`>AC+;C+OUDYkdye*WAL=m{h%)sx84rND z_0(CDSBb2dP(XRiERl+(2VTWa*+wa~EHk|#{<$@Eu|3jsfMA3IhNbiK(s{W%s1n%9 z_y(d#o6(rFREH@&gQygTZt3USa7M6>UrIE@OLMU28?ar108=Nn;du@Q8d9jQ7hMf3 z67qPw2EOdUQnflxsYH~6FZg<2{F>tT@kd{FMD2>Agw}*AU{^lBl`m-J^S~3|%jEOr zq`?K}3{Dl=r`>~fpc+`Jp8MM;Cxxt6>^i#K7B|K7PS_lm!Im8?{=Fk?*bl#`#+n)% z5Rf~|7v(9x+6e-(v@(PjGscDCb)gfPI(ePFV-sap1H?9LKsAclWt%odObWmkU$oit z(k0uvbyL|<6pcfQJQBeWdpj|y2sX`?N$;)Ba~0)?++2HM8GPePc(*rT_KUJLJj!aqb`gs3`w@a4;jVNiZvb zzc!_gxzoD>1PkbLiq=Ceh?_! zknK^F@mOm~bbSM*L_j|Iy;L{(r2{B+dWZmJRun+Y|1%0H-y2Y^Gtzsqj#e<-7I+s` z`OLWm1+z=qdig+{Q>|Y5tBQP*V{T)qD7;MCwLLH}4Zc77Y84YkN%S7-Kx^Htt^bK# zhkayo6;D+HsB>X{cGl5!u*ejCWT9F9o#{LIikEJgm%psJDBSc<^Fw)tDML&tku!f|r0jiZPir*$sC)dKPM7Z5zIdSF-{7_^ z+ z(cA`>*MD!J7762M(C=Ot)9+>@zh4w@Wl<`MHs zKCuyxeVwHb_W}WFt2WS#I-?_YvL7y4YWc-Mjg$*?tFRXmGr?)Z?H3Wc-WGMfuLB{3?=T#DigIkizDccTL0fk5sVB~0{RpIi z!7-}vLFsC3CCPIvG}(vP<15*BvUg~kJ_NO0ok2Zjyp(r#0E}^lr`nQgb;)v<*>EOi zIxZ6#QY_1|S+nQiEA4wMm1y%}CZmVt{-7)+U?h1Mek)?TcE&$EC5gyH?7HY-cjrY# zNS>t@eER-1>19IEvk&y(gL(or$wa~xV(S|Zc>ErZSWroi*EA7}L~i=h6z;Y6|G#oh z?2K2g(51^<6A>tlWV{q+d8~2MNh_I9hK^=WAW7His4mNYR0>hhjb2M_l2_$<*XpvR z(YWdRN~GKHfa4ggZ*hf8xBg}ah4zSSUFLD3VI{MIk^HzAQ34!ArCas3I4y^q=!jF} zP=uhC3^YyLS~!BSBxqiMg-;%rj(P2lCjq{=_Pc>C9Qwb>E1Q=T(sUqy;+G zSWPdedsH910waI~>u9t|N6Z=kI!H{d{$fmIT2Pf zmwwEvzCIAa#+CfgX>rh{)hCR7ZQk%m=K41anM+VQiJV zjm8+pX9e&Y*4mbw6_U!N4f~;R%!OmR&U2-R2XGK~5@__c@IB~U$Ylu7eYrBTn6$UY zP!(Ydw>=GMhM14WlOyJc>^ez#l_E-ex`OFgkVioWh;}X4W%=4~IJ6++f^zC(X)WlO zkuW4x-TxAI_K$w@V@T_gawgsTAN_D>=s^-IoqaRO?^Fq*k_eMqORFm>&TNoLobIp8vm)w+TA_j;HgIB1d8{+ z6%uO#@QmCL+Hw1wkB>GwyMpKx%G5RJt+z(se9Mx5yTdy9&u2r~%~^?;fWBj4pOJ*n zfjWfWH*Ig?1N#n-uo0~L7x-ow?$nQMRgN9svh}#$v04BhC9am4v_oH@Vo!-+ZVA~* zz&VaM5srGC%c&3GIJ?*d=vqu)KEJv|c_7mR5Ul4WwAxp7t^zbRqMWkT6UHo->5VEZ z`4$A;vIybYpz4kNE{h1M&7{+ceooQ`SR|C{n+?ztz&W1q*72&U46lQEV|lR~2qSnV zYD~L}cCe0pT%(=46d&#IBeOKJI=ybCU08AI$x|zkurj@F6`-e-qFDFp#&iNDv{)P* z5k_3+3$WX76VEN{*4FZ0p;VNrxc*^k=|?F`{m^X+AMc8T1g=BHHWu`QB4!w48uz0x z3u6)sQv@tlnVz;u@2#lSe0)rQ)i^11K=V3&!OuLH*tdToG2=YAH#kIL94@(Vk2HG| z?q~R-_c!P_d%$XpuwwTh+svE&z-Su^O?ApZ1|$*fLN{kCa1XlmhE1_7uu~!JKO||L z;swo(>13x6@8iWsi+%>-!H(nTbb0X>1Vc>C@SB=21Oq9NMIx6>ka9_7v*C!PT&7)I z4bu=pZy<0$k(GGxcGs~%%j_&*_Qn!?AGg;(5o>0w2FnWO z?bVHatB&euVAM$;D+xD-OVko%ktK0)k$Bkr$iy7RaWiV33iN>d>9)3vJs$nFdMln2 zgq4%fzszLOPyLVGn&gQXeU~Svcq*Q?oWF3d;#l0vEX5G{q1gO8{KH^d&fUxnOX2Wh zSY*2cHdmYfEw0~i;6$`v;947!s5N9S6^^@(CEQQy`|)MsQV-J+=nf~nyc|F)5e=Yf zE{j6S_YG}3{b!4Pa1Y!rU&gpKhrXj5eSSWvmr6V1dtp}+iJzn-e7?lqJwpQniS~B> zKX*E~Iy<=gwzuB$di%;Fl-+50iLYP3sxG6V)p1_l-~Z5qgUtq07v>nKGBJGyQEJO% z<2NUu0~4Q@WZI85Zi5ER0Srt21GD{h+=B`?9i;hjbIdgaz3YAi69!ck;Stq=&CFnh z1x0SgdNQSo3Yx_jEjijQZsv+?)4LwJjo%J%&Szv_Mm@RIevDfCmwe2wBsu?eycZ~N z!#ZKdSkKzA@uP=k8Cl%a53aEz*w;Qt^~X{dH%hM=l6m#vxjV5$AU7IJ&S*%t5?_5Eb@G-gUJ@XfizHN zR5X$9J0c%`7_#=CH;t`0$aC;ukI6LdSz=nIclPXWGWBez7Pl7*3?Vi10s~dsRr|D| zB2>2Kb9EGu+vf8`M*t!bzf4**w(v!o>XTFy`eR3i-ML`|hkhOADF`Uo06*IPtt((h zoM|&Lem~NcRAxBs%{-g5DX5$ARu*t}hreClaB*!oT>;tNp#^U4(F>+8dv%4l`B>Xc zfrTWY+v}H1DBkfxN&{Z1-NzH!g|sDC(_f1b!3EPfJmnFTmA>azWaJpW)mrBgnSc2^lmy4)2O2=eT+Vq3dqhm zlgBClz`gK4omu~ksbBFm^fiG2dwEsf6(N*RjSw=UlM&*g=dWuBtYTG+={k;wMu2Ep$bgQ% zDFliu*H~w%0R^DcX$cIMTQR0-3QaDcS$f>68`W;ztsejvMsou(-`gR9n16gB0-P~x zqb#(W;KEMnupR?`k->hmLCX!DmQ(dz7)M(W3$>IZ7GojOZC_h#x3e*U7Peb*e)TS=F-5 zk-Iu_30`b{14M2IQWLp4AVOU=4Ip5TF#W<`6J!pb15Scbj6~55T9^jA!Dhoq0SQWX zn?}%^)>(!JNuf8_YyYODxtsTBZ#|?HwJ+VNrvW{{d~tVXta%Yik}HDw!;}E0X{37DxP2QZ~KP$m2sXG9H($x1r;|p9<;vpOz^ma9(c(}(JDX* zq1u_$#Ln!0tFyzhz{z5VHFdmGU431`5`^2{yA(mwEkce{t%j|E)(**M5B*w-lNhwM z@9*xDQi?H?)Q!j`FW)jz6UJRv?Wa-Is@1hB4f}W}iZwMdgG$v=>QXigX3ngwBTMxz zqIrJaVroGgCMV@fxh;`(!-{|}^{6Ar_}b@$guT+emZYrpJw4r$?C^O~ni!o?PM5>g zYjgojh|Tx-tzL`!d06n>dIH7kS>L(r6E|+sbB_jBoH#-1yeA(RaMX_C2GgY71%dba zeL|b7BPVBtqUfaFY&Dxfe!j_Uw>H?|35DxjG;sjyu;c`~y*kvaQeYoX_1dAp?li zsOY*g7M!QgrGLRG&Eacv*MukL$I17)6g77&wB)cWK+VpIlr4ug)D%(Gx#LQ6rU9G7 z44KVWc63%nuu3dC)(H|JwPO;=A{Zqh<6}a-kx8Rv5pZqp2#CShW_pYQ>Ilj9)B-0Vos_nj8oEfLPZ^-X{GD*mO zXEki-n&-YWyT_HlR`mC=j6EayB3W6nsJ%kK#01>u#u)1$L2zl5eDhxowZE}26hX1a zRBH1ij{wi*+V&t3f{HUVDwffFA{!grNy^Zedcc z=n6ATXku0}w^Bmgnrv5cFLK!pkJ#c|cBTK5pyVf!$Er$EU=41^A1L4n-((usx?||` zkch5#qgPOHfCNnPy$@Xva7WWRaHy+SUd<|QkcnrS8}qFt;AZ-5afW7E7LFncIS2=i+0@CBv@KTj})&?K|80$l;E#B$aAb1*p2= zh{BD)irx*brfoG6d5Nk7pJ;A)e0{~-9o=h(VapHfgAjB_d;Q3+w6-U>r;Z! zV13HRFV42@+!c%2@;?%2d}o;{5EU((SL{ys!C>%v1=$jMZf$qlhV#45!HWr4;3<#E zoh*2)uo)cc`N4|(T<_+F;IJ)48Kx*(A;(cxO$_-)qw0?jmT$>>wfB#Ei*8MkUteg@ zNE5g))O9;SAyT7PXurb+!M23dad!yP)6&vtZlTnl{ZDXoW3G<6#Mg)OdG7w4=dI9Z z#)^u?$_nG#B^h3vKVdN9eNND2&mVArpw)ju~(E1N-A!quwq>xT zrhVJvr|42Rq*9^sLM9V{Y&Qk%+00IYc=))OZH-x3+jV}PSyQuJpP5!su-={v5GRTO zJaG%OkHleFE}6}L*CztF+di^mKihb;h)3_CJv`g`mjP z%s?xoUmX}0^NP@ug62m%KU}gST#GRE?{1Jwt1)aV9^+;gdzR+Q-5;fyfKW2UX0rh{ zhKe~bN%_+@owFLMo9OeBZ~z83<6}T>Da`yktUCb&@y*1S4ZHRrbuf?=@O}eCKCCJU zY?=7Iqb7c-#P8i$UdF5b6a6A66kgi8fpGgG5nQBB@g~=>f@mJNJr<*@{C6L%IJjUA zx>igL^0cu}squ$#>-}Mf+qybh6NyH|6)N6c`UQ^jaT&h6@hS4Uz$Hf)C}tA~J9Q8> z3+^Cy-hT)vfV0}n(il9b0pf=B>%SA;06~u2;4}=kJT^k%Z0R#TH@J3?MbixH?|px@ z4G>sAC;^O=rB?2}ee(8KOG}q=$5+$TZEC|#gE=lsPdrPg0b@R`2+TP3liGhD&OXl9 z3p+)fLd;M?4LG~0_LG*hmTcdDW}P2ThUZ^GM-dBY=Pk6zj8Kc1*&*04F@im>I9u`cj-h1}k=NHe#og4Y}+}XWl6Q4zVw)#&& z)>xqStRU-8usGxe?m$;yCcJ`H8T?yqa>HIO+{xMFXZUkF%sq}7!e;oH-l~cmT{&07 z)~ExFvzOoAe146&M>Bs0m_Qf^n@idWF#bGu*?)e2Z0ZGfid(|Pg10bz-`156Bf0y< zA}as;gGY?pZ7k=1;_CsEN?*JaP-$)3>&MD0YknXKT-^49P!MQHvXmhl#fP{#q2iX^ z5ZzBJaOOBcb-VdQIwb)IMcYK~=U-S)c?AY4f1Jh7cN|bW8$VG3S9=a&Ard^+W&O2} zK8wuKU-%Upa`WI~!;NPoO-0w_#$ff%7eVwjcj9h$#nE{=vEesl~@0X&|$z&ptnQ#N#ghP zD)@f9LjCz9cVnHf#cs9tl7qzk_Ev>ifFWB0Uh?cZ>g~rlsF}kYcF>BCXhEnKWmgBV za$q1gXl+@Wbnof=fkzYG=k?`=J9u}Q9&9#^|F)E2sPEt`Vd+hqDQHo`41BWBdZ*OX}yDg~hUDkG~EGD2>CvRi(> zp*v2*3KGGDxQEKN3dWAzMz(RacBoC(ktSjg^NpKk0^B{ZXm2DAjwn*cv z`mQO>lJgJEw5IQZI3ZhB%&H!!wqGkI6graRC9pnjYC+Sa zBD75^=o7FG?!Rzh|3Q{uzd#Z(X;?6TGp~IwIpxAlYx9;GRaA7&CHJtu%CM^o2RUlh*|#ifum9n|0C*|wS>A`Af% zQc+h56`}gE9xH3-^gUc|zQV39|L2H`5Oo$!3%Z7=467Id>2zIYf>-cF=G=TaeG&)O zsKf34VpliD-MenM-5t-ZH@bIi+135U$k+4-tNoOUORg~jy&_H*3PK z>ssZBbN!D9N8Pl_WdFit^_f|)xD|3Z&pIhsMn;2oG1c02!P&QK5g3meP=b}k0j;K_fE^`! zbam-JYqf7hV*~TjVv-m^Ni8J8zIFBg|(9k32u`m?swqYDc-sYx5&cYXcqOE3aKXz@k{nW$pFivMqV@Sb-&L$441V}78!loFVX%SB=Gnc`0 zqWcr2#ZE-<0%K3Hls$Ud?YJ?OO6_tc%Hn$ZdP_xRA`?fiBfTzY#mNBv)Est@i9H)U z$hKt(6GL78>UE+ZY{0TzDi}KV7oy(X)voa|@XJ!)7K^?Lea9U()CT+kry{XZFp@GX z5J7r6)J7KvpXW%2-a}008K!$zmJVC7^4H2*;9dd#LO`_Glj&Q{1qXh zh9wqdR9F1eABYi5bj=DQkkp&V`%FWE#!OLRsQS}ZBCE+?VB(Ldb!sJ%|NK-U0l}81 z)ew$i6u~MEQlug@o(^OWSM|(KwPnt^IK$Yroj;FpnmJ>2ue%GVy?A$XdedVKr%wkg zZDFl{)?f=utmS_w)20{!qfZ}Vpqe4&(J``O&R~@m0=1*4EuRX!_B`ThJ)%~pZwm>^ zI(;f=i|3-}&M`wc{mCAZdA7&oOTEHxltn(oK1A#&TA!0}|swD7^5+Yt|!gy0@h(4%kixAG5C*`&Fj5M-}{epFj; zOkVmx+_7(KIMWBOuHLsM7!FU4=wSMoZDFDF=qkO)p83|o>)#F2-7YQ1L6G|&tYZQNJ&Xw`lI(K3+|wHOp@)mL8^ZNS-yc!T zjOk4%&Vfh7Am8b?BR+sTr+l~O102ISVeL$qHYCDRG*RCk#=$ZCgEimf9|O+f!qC1< zmROw0>I)U(lQ!Q01P2frfM@_%#A?g=r@o`bHVomy|fvle^Vl%Egj5md^*ku?;P3c8h@>oHS3* zawp#VD*k@#W zSm;YHtE9&a1_Nk3>QC}Bl7g9Dn0BHF3~2{LAsve0Nh+N}g#;(+X;jUcC}c4B=_b`Xfzrf z+Kxc9t0H`v_VZQX&=vX8fl%RH=bKXLo6ft^X%<<7{6oxjFlPwV^bx=L*T@Z%)l?hM zA#CWGXHh~ipaQMu6NA@6Ko<1KeB#K0G%u(%jhZmjMwB;o!Sx=cBvnd!9QCZ{ES8g1 z&u%HUsD*9k?65WV8`_~x=u!l8Bm}M_Bc;X>TL4%@4!Fc^OwYF3?Lz9eSYJl$z;K6O zwxV-$dV(lsvDw*C`zF|-F$;zAz_LzlR_joVX<{W-0hPPN>pim{kqgWEx`e{6K0~l8 zpEejIc!z2V`gckY{MmnKlOl5-=JOV)t;wI6$%mM5c5)k-qTH5hgp5z08XgQ}EflGR zj8`03H=6a^VywxZ)z-}HD{#b9dh5cph;2qNy6a26{(hTAyLT@kfN$jdw<&z%!=g|`UWyeB<(?HGDkgmD@NMk&;Zra#*-UpQ>quZ6itp!@ zUL$73jm5!1Wnr4ZdH_5oySD+On)5qu0PZErDm_hy?3fl9!r&T7fntChpcr_bpx@+E zkvM8V@yJtoXa_cnVgw+48fb}xL14_nA?2jZY%&kDnWPp26-xDRi8WxnHb!5&8AVXq z?-l>inM!v_a!=9I|EqXv`WYx^dVYwvsEZya&4#ebL>HI@FdmLQ<5%2bMf@jO-Vordx#Ii{HZUP z6BerA&O+1aeTESlhUlghp!Wh1h72g4F+s$a5aNIQBIH34!x>`1J2aD>8YIuNXzMmz zUedI=X-V?YoTg1pOPNG~g}*Wn16XpaeNIz3;B>y8k-vK~i1ZBYKn)P?4||f=Lh08% zFUdyu_^0mO-@7(-mb`bbXAgtF{zym*CgXiVvU3t&eM~QU9U>vEr{LSwq|r!x(1L7F zylQVEVbDI}^Td|?YKC9-AAF?0IZBLVgw9l*SP`WGG@?j<@Vlme%ct7@PxnYf)o;qC z+P&-FR?>OBX+!meN|7j{lFtSD3XQu&^XCtLZic$IGcrmx|L}i#nGTsICqgDFD<-7d z&17W-2|}>;>IGU`Qlw5MGNHinWYD0&|4j+7Vt$SU7NHg-2viYRtW=LH^dYYYp}7&- z2vi$B*r`zIT?$a76&3@EoL#ec*mE?2CZ%qzyU@pE4!;|p@L1KkXD|pW?W1bgk8EYL zF1IV}(2(C1+>JgG3b?%Y=8J|!`PTg5{K0eGz-h#O0g)Xkee^@tA;HwYq%)|Lf`Joq zIXEoD1SVu=5?pp9Thaz!rU`%_X>ZR~+wJOX=UOoeoZz7`TO9^Bl6(F$&5_wzJN#rb zm8=VZw^)6=5Jv~O4d!cLT`39$CQqOK^;1GR6~pJ+`grws7Z9tRk0gunS{6^Yv~UfE zJoz(S|JkxxF>ARw1+N@3hPvnaF&WFn1*xrcHgRKo{`_&T!4+mbO|y62m?fTmdh(x7 zXS=uF7-P8%gK6p&vxj40-UA09yrt_ukAPt#0^vCss?`j_=<@<09B~X*80L0_3R{G> z_KBj2A*8gm?e1x9?dds$M|U;QQMuWjZEb^~6xAe#X7x`acGgRLajaYo4VcP%?-qbp zm-hluZ=!d?9r2o__rUT*OY8GkYfIt-cJ=$9kc-^Lp2Qww!$*5GtrxocA3%vxRk!p@ zAIcnj(f-5soxaNs6s3CoK>v}(?7cqdMU|KPDe-4SjpLurbT*|O-h{n-#j*YP-s99a zNA3mg-%9_lE%yx>LVfV*)s$3QX3-N5?=R#H)f?Qx(I;i)mGU83DGJ`TO%Bk*WBTla z4=VtD>rb6F8l@goNPJo)De<*hJxOct+TM%%xx{%;;s+-yw~qZ8kTjf~y?GPa+gkw7 z*VZoH!~U-h)ztz$RMqKnJiPnE&<)f0IMrV|>4;qI<9gaG-@uDs96mNZM;qy-{3Efq zm+a}GRGfR617@~^HCSDj5!|p-%G<(tM(G8Y{)4QnaFAM^ovQgu2PP1T&Z2<8ZX+ga zXUxpH>h`#B17X11+Q5_@P)IH>f?W;KorSLm{aF==C7f>cnO&F6C+wYH5r((L^0z?z z%h1ly4b-vI4Xlt(cv$~L)D!YJ)z{iD?Rf{`mFA!O3A}hIDEpVOUiv|t!iENACUihQ z^a$#Makvk9CK8EW;SNnbPX!jqhK35X!jvTk1^^H?YC|_+2FyYj9v!Qw2bb>W_T@{Q zFvFW_4U478B`Sexq)ocIfQgQ!LVar|8#7~Jp{Dq)zPZEth5UYS@FXqBaPGRM^0u36 ztA2{qD2s{6)5bLPvMl(Sa@k-@eJx}?leAgPVYQ;)WUweOn1Gu(`CsY+_jhswV;|#% zGW1f@U~huHA_S#Y=4f9{qJAb}-GVOW5)GI|VZZ<)24IdSX{&W+f^9IearEMX!kOx+ zTkCo#$qOCO#x0mIlgw1B(QC+ZcVNHmMA)>yc|uoF;QkrSH<^^y2+TaHe@Bp~jb}@| zrLER4wdW=zq%(sEf>2n9VAO_QwRQVqt|xoZGm$I}p-PvGet#%r{rD133sHO$YMqHN;|(C!hVJ%3$=#KM5o0 z$^dlhZX;~VmAG!THUQrA5@f$e5Xwiva%q4dUk8zTqP#uo5=B9MT7jh6HUTV7Wo!A= zAP5b}k#5O;{L=qa0*UHhoq&7qW8eb=H>F?Id5>BWgN*jCLz$iqpZ2Fru6{3iyGwAd z)Z2}0FH&iEI!dKJ_=@>;^Oe|0O0|TxP3B}q`!ozP3~?|vK*leQ^e4vDnVIg3vZC^~ zRbW)Ysurtg&`c&)NB{LIVPfh0t)MRcw%YG!!F(cx73{+by^m8c?C?K6R}@*C&+DG) zh^-ed{+RSW8zY*PePG+l!J0iO`d@Bpy3Cc8=mH8Ve#Z+;xm&}EdMwn--%fmyRi6My z6`LUDrqcb)={&!|o9-4+w?RrO5xMtT4{AKUeHc1<&XF}rP{wXJ;oY@=Nk!VjVmK`! zQ3!%DF~&F_Gl-WIq#_iwB<$L+l;JY_`ZUzxrBP_qTJNn6^uK+GH}&IvQH?e7bHO}d z-qz3lfqjhxRfhG;Y6opLqCY<~2*-URP=7E$KgS}6s>SBabF(wnEPtS;53l^PGGf=M zs%n^})tkSBsS_7N7z;i44B6&}P1b-@${Z?#R*UIyRL{$mSSLPOTKcZ@5Ba9+_utFj zVsa@HfsVLKRBh@q5w-)DNXj@~aZzlNVzU#7)H){+#nBIGakd%?nuaEVTvNpeRVXDC zQ$X>E4lXcgw2m5sIXNsyH@%KtH~b!vvsy(e8^I&vmH#XAi^Rmvoi{2CtYQ!s!^c3L z4vtiQx;K*=`X2e_jCK3A{zyS2k=w#cU}08l!-P^<5&F*cFQJP!4oZRIqfvdC7`1|< ziSVF^C-3OvJ_K*BYi~ygq`jSh2}cXu_zro)(Qa{QwPdaf`Vg(SE_RB(~vb|F2UYRsR79qAlm(-MMu*ii4^rc?a1dL$6VYE~5bT<8^Kg_tm66 zeg5Xn#TDFKS!{^jl^7PNBSb0oSGY-Iyl&3@$UsA zFH<=sSjX%hOyX2+|LK#tSKqKXsK7)smZ8?2RYkIvE{TU1D8si;@+mv?W2vey?^Dum z@T>8K|A*S+988 z0SseK>hC{>Dw=o`a}lkj-X8TTkf#G`oGRGA2`4XIimVg>?PJJI;pH!I#YzCYoxK2Y zc9%3S{MoD{D|{~eSmeqE)7R)63WPF09c>@|bl1Mk^wad>_PH(PvX_tivTh)mLHY&h zm<|eqcaIwz1C5P9j(~!?IR{K!l3Q2zife;=TH1>hfZzgM9Vt59@#6%x7N-ZgmSXkw zXV)?VFG7JSn^GgZcxy}Qyd9}J?YlipI@H(C>}QH_-_SLcq){8dP(iXRqrx-h#a+ab za9=wLw;B7+#CRTA`}pE~o-8oGnDx4MC(U#)B1CsOemj7rrvna2$cZ{3Qcu_>9}&YO z(HTSWN8o#lr=V8hFpc={Ri;ssF2STVX)XD2oAba~TU9?5Bk22(yEd#MBXXU9>oFN8 z!G(jA9wv%XUx_((v46bh@gAm8IhH^4P@3&g{Dm8VqeY9lVam_PlRQr*BnJz9tr4wf zqo1jo3!uCDoxE`hj!bkRrFfaEwu`LDlw*;=fA0%>`>z z>IH;BVM$+$kpJC}KkI(paI!kIcEksCSWy{TeR9K3c|ZU79bfbt4*rbwSnLNLz6u_L zqQz9V~b3&w?y-@F!f#}IrYpi}`n~4~(WA@6I@B;)I*T{KuyX@tcWtzL3dt<~y_-K7# zsnhAmdleOn3rWJ3zA_>FbncbHKvBM@vVyiI3D{4;eeQw7w>?;jV>45vF08~I)gm;- z2jbr=xjP2aRZ7>JJX>(bD-vydnNIswy8+v&2;@FBv|q&Y{FvVdx2w0pz9PG@l9Z+o z4ji+lkWjlaso(@S+Lxaq@WArNyRtGHz4gap!t}0A&FZ_q{$St&ps5?7DdyypgNj#C zMgf7LQ*q>*)6XElT!1Wwg4k}rP6=K(r?;0ZEaD(Dh~PGfvwl5!B@0725TUllqvI<_ zRVxC{(W{})g>Ykwv&zbzG2kg0c+uDO?4ZR~M75!#K>*BSB4QTYY+?s#5oE^7oI$W8 zGNcM{Ltj#T9QKc>8SUzgM^zl)M`k6V!L_vBQnLTp+!*a-d4f{oHCLPc{r6y2dL|_;cY@&L znl)g+4@elve?_D!^(jPr?x}Z1|toVoju1Y{FGjKlo;Uah*D|9Ca zig3nBMD8gKU0WkONE=B+x;iE6Vqh^WQ49pz-U^Etv1?l_?x_?xDRxcVS}{ZP)PhcQ z`dUHNXr}*L@iz@s{Za8RU6#cHcFu_ds0g*#c*5^U%_|}D5ba&(wyzaC^#l$S*~SP| zUv+rMD;n}UAuqLd0!29-7F$ThRTxN)!RHcmreVP#!0dr=6i;RXEkU+q#7RTX-N=D@ z+MvsPXwAI>?W#c7epdqQtj+1ZO5Ex`J=f3t+HsMguSIke)reS$aupJx7O42DhzPz~ zrRJ|hm|dWb@H1e$J_y)6;HgpT*4cYGX%dW3Be}At=ct^ZS98dBpD*7;uM_5@-@5)7 zBl|1&T#*d4k~Cl6OC*Q#}MdD(GRp86c>!_@Ozrb7GVhy<`vG`zH&w{p^{j^VV%bPCwNl}XU5lBDWw zxqKNaz3V(Al^$~5m7=ng*;ar_*z5<4)sxxTlgm!@_L63EYgq75YU)sMSgYAAR(?*U z&&`1!?PPOapUqaAQ)R&dZ(SduZu~Gso%&C7vhHYEgRFY3PG@)bRVKy!OfhYz%i8ar z5*L5pZ7jZ!uTNDLg+vGGk6D!4A|*U!D0mVI3Uqb;_d(uPm^1QLN`oW;T->iZPF^0_ z+iS^s>&>^ox}d_g?Q3g$rQ-f`FTw*PD@>oi^)?y5ZJQ}7D>DlWZ~EhPzP&wRNkF{% z4on1<$uYmE)7enFvyVg7AP0`~K4vM?assxVh0sP&$tO{F{@Y?)*@gA=s z$5e^vQ-LF$%PoNWIQjuB%BD{pM7OmfzTn%Ln%pOzi!0@**|SlaX^3j#1~b5kNUyzP zKljKJ)u9GgZNFDBtUwv`uD|hvKcks6dja(E$wrS}I-vr7nbE2(Gn-Do@R+K?c+Sr0 zEx%uS?bIo9bTlc`Ahi4bujA|w0rV^aDwBK3mX?essslu08MtI_W$n5%p?dFP5M zu8QyVVK_IJBX9Iho#waglZO))WR!s z;6Y#Via^Lw?0vyn0%b}Zz7olWBzx3CCqTBxfWr=u$u$`!Ttq}0*8GNQ#>|gEwGpd+ zHUtUxZfz>QQCvRb&>tey%qO$}GAqTbd4QLK|2_*AO#O>RsV|s=nEg>i_{ucjyADCu?=`PK zima$P&0?L---MVTJi{Eoa2#8e!(3;BDemt>~l#4bg(G1QvDW76*x~CL&bb zES4n$CF(FL3Pu$KP-8z3rce*BbixYr6Qc!%K(QZU-swV2>dl^Ls?>*2gD+hJCQmpG zo1cC8|9UF_gcgSe7w0S5=ApV1J-Jg|U7hDTvmr@{m;<`O@7*iIyQrp% z?Kx{Y!0zpiiI9nD@`hIDXt%_@y`S5sa%An|?wYnZompAK+~Gc6pEGgp?41soo{&1O zSdkG_0ABBc$0VbT7^K$EVRH(L!GjEI6N|qFTPaeR>@FoW zHE>K>;viT5cbBY>HVlVoc1Vu`Q)yAH=LyacgcAGr8$t!nVt5o0%71}f9RWRzp9Q9c zpQ^W_Ku@3vD*l1xH?t){i90(4I}$6s%>p1Df)6$qN9RN;pudfX2HYszB)IWnj7HxP z=1y7(!nUwkBZB=!i@=SJys?;{lS) zsoeyU5Oga+hJnuXSsj**oQlYpG=&s|^p|8aFi!l{fgYX82e$x2UqdAsw|K3r#rny= zJBNo`^^iQ^jq#8(cddoKG{V%95JX@Da&Ajr`(y|S`0Z2zM zbWutuyJ2bZDsYphA|!B1T}1+>OFN;6X=0RO{PBBt*~LqTF~Z;GbgFaJ!&k4aLRVdV z^`WYhFL=Jrl~?yKsJgtlqDO$At>6+ZQ>lY>}eKi?57=<2v%O(siMGyenu-bM2b^$I(}TTC6na= z>Q}CRz2dC8%6m0ak#V*nTF?+Y?rTT=LEw#D#&9D^I|lo)m_q~xl7!A0W+`8-0SWuN z>5%?S+Blf2I*KrYA21jXq8N;*Zy8L#^t4Q%paw(4Zvh;Wk_hF8UanwZK__=`xbKhf zxCh?ny1NJmMF)H&B%ro77kX2b@LZMUs!%=7QUL~PeQg##L_;o_FW+Rcm^aUCxDQeas;IPsNH=tCu z>0!bDF-p`@GYCk$|4L=+hVyhh6c;Oml~E+<92<-Jq6fer^kr!Q_1 zr*R4f*vfC>ER1C}^du02sADL3!W!IsBq1Tm z_-M4*|J_nlsdRQLcphcjfXu6=lOPi?1ptK9`J;Km)CBO)m zvatWJS3TS2o`L)$(rF;SbHNmJ@5KQ5pBO_6etV%N8M?NLg_#m^mMXCp3!>mu6F*8q z$F|kPfoUQ$VvlXDp&Y{Sti=+`FAvikI%guxm>E6N)m3Csge=)b<5aBR-Z)Fw6hxLV zY7jDMMCJI`2D;tvXpz52DK25zw3J62;nvlyt$+bVaF(IJ4jnR%;DCc@RkCJ{o8CFO zX{7p#%lOHAs za+R^o=h-UUarrS-BKDKgOkw*{<@&Gho&wqNdvbDG2mbitLMk#xg4ClgeC)Ym&jG6p z0{WJyZG$8Wj$uWwg{hAVLbq*lLL5d2neAfKvLY5^F$CB|d9;{V(L;7PFrO^|wgQ|$A7+cz!e1YbU+ zc^5Nd2!>(c!O2GBID92X5CNiqWMS4eoC-RZhlR7`?2oWU6TvND!I`#=#!O8E3QlDo zLQgK`L6RtniYg*{xPgWAC0LHUXhHzdvO7EbJwnfYV0HM;WcGUs0*~s6PX1ZEgCGeM zRs0>{2Yyhf>?-+tmd@`N@rM>Rt6As0=<~A6+Z;~|joiE&r&HZq9Pm$AM$?(@aLjm{aND*v-Ld~v;Bn;pjO=9@6B6_^ma=M&AJr=YW$>Y1ysSMx5@0TUSW3V}#I{(m6CJm*+%jy~fg-=a5fk>m~aI&qMWd*nTuMm54Ths_bV@7o2` z+AgqSx24IKKWfzYkDR?WN2kwS8=LW0_4evlYi-A&DNf zlnXnO4pJe_)CF=yoW$J?MsNgYA$S{;DoKclMJz!;ANT$TJRDntO8^m7^?|(xoonK% zRKx%(8)#N#y|dOV6`&mNIc|tQG##rwqV4cQ=O=z5GFkXAF#V&>o!CvRj|?z8wqh~Q zuwWRp3{5uWr%}J>Kwi7S%D)LAM)1S%}+ zYZ(w>0@`aq7)%>_XHci;t*`a6Cq=UH0Fh5>kNw#C+Csf|ZXPcGs6;f{`f-dk(O(2F z7J79tmgF-=N&=srJ|IQ)l$_T3|EdxZko0oq5#Hb{Y5$MiH|?N=)^Gm86nl3KEM1S5 zyk2WP$fv;S{%J23H_~0veKZ&_lDS&ri83J|p)M;RZ3=<1XT1iZOf(UL@l*n|&1h^B zMHnZv3G@#!KFfS9dS+^vNV{*q{B%&5$$ond4&G)YN>bqBiVyRu14)|PzXdZB z?zv8avn00zE>-@PhqZLz0DBCcr~<7QZszz~Ktm?3jp>>`-pMqVq-gMeEK!b9;lfd= z$>W1XmvQOx0lSzqu`k8iaKpa z`#bqMWOvm)YZ!K|3)Q#c4{xi&{f661KZCKW%D8J5R_wnU_G9owV|be3=ND9Z{PE&sLXx0GvO zg~h(*vlFvT*JlmxMR5uSV5g?p>r0<~{d@n=mr)TK<1!#s>Ahy?08lfZ?e{LrZ>IYc z%gGA(A^!=0-&1}KVEHI-U4xIAApLWs4DW#W zj?c`TuaKq9g+rz}`@fRle<$S2m%F|R-(oUz9FmJ>q!H9!Jw}_2#@|1CZI(`-y*4)M z4{V^ybJIpUkvFt@uW~qB_0#NXP&4HGF%2PWOmDWAn+)h<1; zGvtBQXQ>$NbS2|iPFN*Aw#RkFa%xW)PYNnteO3HddclGuhi*V*R11@^>L-596P8sI z1dbFo*L{>woyJHEh<`R14KXmZaznS0;8Yg@*4womKnM1PfTMVQp&@lZmZAHii^NH< z{v{vMe7y@4f6y-{G}_k~2!!ysl}M}A%bLB@JI1g1kwUr%ASIw#>7h)}$a)dQ)ZHRW z_@%%Qv#toRK6aI}M!}csLJpBut0d`?l#!$pp`4LDKI0`4_OPHTg8%!hE79A#5&z_q zSGFIh&h*8hDJWd#Elg&x_P)+F zz86a><>ccjjdE9Im%J;b`o-j7leOlt7@7#{CdK12t(J;DwWigxxy4?@8jb|?GYg7S zmX(3rSar-&q}3q3E)5E8ZZ>dy$p7(?GoPNlCg+7o!Eq7r07(7-IPz#fx8&jA-P&t0 z#gYlMAu1HCNt2qi@_~W&w2YZZJ^NBR+e*dKUpmx51*I-4BK-M}ib5oUsPU#iO+U>0 z_~mp$LCw|#I3nko zIu$n=!R8=Bg()u}XR-*;iU7%aYR9M^{W`sSG_in38|(&(^0E>eASnKw>mvwl~S3E5tm)E{+caV&qo1~VeH*=Ergaz+U#90L%t#{U$SP}J6d-4qs z9d!9MZP|I4lIf(MzY`dCeUE6*S4dz9Q}0jICR&cXrv#U)^sJ$DnEU8|B?KXl*95B9 zz0M3u0AwR~_uK9LyZg~C$_IMXooYoXh6}$vxWKiI4}Q~8!k469;IO4zeHavftCmR`6VG3 zQdtQ@u)=b|A!t8mGTqGjc4_-=s2TSZSmXhEK!}J5bbq&M)03FZMrdQvePDFb6Q-rT zsS)g=#yb_823;;*GSfj}!Nuboj7K9#r{iJJmHkIV&F7e&p0CCNhz*x*k1{uj4xG!2 z+`7%Nb?eRkhQj8yHs0xYt_fR#o@v2Jt9l7rO4F}ve|~+2&m)>Ye|h`rO)H~!IJGQg z=#o>1Dp{{P$=*I*c_E!|GCF&G~%cGQkp z;h@S}-W_zNnC)eG`W|E80;wKEZ<*Ce9l%@EidvW`j_wc`NW*K3Q&}ZJ5?Np4U^47$ z<2(U`FP05s<7f`i9u_sDE5?au6lIN~5oTrsZ->mA&CsMwRCmsG;8x&^&JPU+;i2;> z!bE8}Jv{*T31?b&^wlR`$iZCJfOijcAR>8xHDlbevJH#CmEYf~R&8fLxjao+-B4Ta z^uuuLLYEI~)TF>Q$S~pPh7g=4^ud`Y)HvA1 z>tUW?sO$+?&0NTnkK;h*j8l{w`^X9jS*m!%)4;i+_ zD=+QY(;vS1$)R%@PK(xTN6+HMhHL|tu+C*7S6r-&vRDmXGZH`V&J4M4O8!Hd7P}d- zK_G$41jzFlG)zSe0alC&;tG~j7?>lwQ?xu8T4?$4lNPbkkM{*k z9QQQGMB;onY9+B+({=Z*#2I@zpOnsqtI zJTejyEU6MrAfVZ#IxA~i4heDoU0;aIDpwG$d9&c07sy&)xWYz<*Ma@%YY!i$ys{qx4u?6Dv#f z-N)e||IWb&xy;vsY3G(3DM1T9?D;t>Xj*ec6Q2j z>W@3#rKSk`*Eel2%rs3UDgoQ1;! z>M^RAZbE3bKP}V-PKng>bJX7MK%u)N`Tgcnat=dZYwJK=r-LM9VEig_NBkHp{TfR zw>=%jNnEC4|13HSw4OCv39ET_z;py1G_iUz5+STsLdgC@7uKn)1i^W?-w3Y10qnli zKRf$EPY?Pfsn<`yQ4QrRzhF9doDks0Z5(R6YLa96?wVOFfV?RZtX7q zZ;c#{{BUfrcM*E6k@TY9)df9w;XNnL?o2ush05!{YTUQ8p`LU?kZn>m=bNf!N&e6A z@MeZq{^`KKoFHR;wpHO%YA$p0&xmKQ6RVbBd=re@f*+~>6#%)oMuDMg|1i-ETP*nv z4KA&0fAP3<0osqEsLX}zD0uZVV~evT=rkB_j-j6+iwd1!6)G$upD>Y>Cn~{_fvHjM z8o=@y7St|(@|2Su9*%vVK3QHXkmsK^?&Dx|kClPy&(E>;$Ka|f@=7v8GlO0a%@GEN zczP#Ew>sf95$tEBrvVmx0mEMiV65uOhr{N^XMqW(Xr_?1x5ax($%clpKmF%qi|YqR z-fe~ZVBh(*;|6aF{h^Y@;Q`GcwJY8&B}GR@ZQguOMK}nlZ^&qzI&kEh{FcNnZwX1%DDZ#s&;(1|QX~$WnzH5CUpO^zoFl9Us-v`LQBqB zQi0J_j+qfkWwB==;wk0nCgg+t`QR|3Ralz41K5&yPnFVPQ|H!?no(@>zGH6w`Q_|^ zNv5dnrRv{Hey@J1ZL)7*Ap7N?`K`aV!Cv6hN4~3NY_ycJK-t)FKc)ksPDw+Vf9v&z zHFGL=$r|spQR1al5Lo?-Ke*$|d-8Hh#9klf0z|hFD5)_B*oI)+c=6@?CaargjXOhYH7Xo!)|!N(YNiJZvHofRP2w zyWc_I8GM(PL~upn-J4%0Ui|t#LuY$l#{I7s#dUnCY_CNBmM4gk6#PPVe-m12L_@#n zCVwgLQHW2zZ+f0IAc!~S#aBX+wBpk6Yc^#$RgCtNHoa@@4u(ULAo+XA*-V^;7qne1 z3s)h&!;mXWv0X0>D@jE{uSNl~VVDaYAGZ)JTp>@~v4576LGuOmZ;ufA%HdHUkvIeC zWKSZ3AtI5t$6@-4llhOrYu8T9S5yoQ0c~h8qs64*l6kdae1SJjM`+181p}&r!J*2@ zY!u5oc4hPOQaVT~)d*aV&O|Z7as$Ts_Avdti2VQ|FH3(VcWgT>$Yro;W?G+ZE^Nzj zd#F}_6!CS#!yx8c=JIGzKYs*U8Dg{j!*yZR^? zR(qI_zHNN8u8V-<3Mq9@HC~>ecUzJK&xX* z(v)(r@U1g+Tqv}NTsOtKe$gWDwU!o@RfGklh|)Sg@KZm0S7f1Fz3S5M1n*%81~OK+ zrd{IW&SqfYt}aU`@JUmsqNevK4nrgXVW4Zz!+9D%Rn&%RfuMTB)j6A9N77au8m5Kr z(23|v?R0^A@2LF$-6D@*We(DD^`di6zLyVXMTKkR(l?!Vj*oIP=!?cTrSb!wIsTyh z`zLMQB|{_AB8`gVigQJQS_GUGg;*Yz&(wVB8)fElFp*UkAgCqw`9ciAOhW8q!nI3k z%Z)>PHbI2bSscv9=7n&KR~Ms>lR!jHU*_!F1RqW~7W5NA150`Y%w5|d{YIP(z4q`wtbWRZT zfUZqSsaHD<^pC^CP#-i5k57ar2^rtNe?pq8$J^yu+Fk2AVFHr3^xjhX{RlXW%zyUj zZx5pSfZlRj+tH*$q?ZybTNWPH(HIuqm{Q>WGmii6j&FkxCsh63+*o2RSyTFZLe(o2 z630pW=_QR#O}ADbMj2Fg8))WxE+D@Id$PZgbW=4i;77O+@LeRRd1zpJ!CvbY&pdS#EC&gPpafFnl zDRsYj+znP>({cNJIqHzpJnI{{e>LVD)C_fxJ#A25tO=2B?Jd`gfiL?ZHzR~_AdIpfK6=1wD(XUL^zQRwQ6gM`UU z1Tsv$gZ`dU)8-18BT_aQuvd1TEZG+D-XH*EwLom7H^i@{&BcYSub@1AAug;??f~)Y ztYdpP<{pJl5ID@@lqRPgAOjcZ%NQ>f7l9nji&iA@uevFL&IQMzSOK;DA=*L6;Q*m2 z63ihyn&VAtB#2O)Y&MqODb&Gpr`iKQ((Kej?K}&=io!WCbI{i5C5%Rg7X17`yg`g) zDAS%OmMl>`Nn^~!j~?4v6KgNT97{*JvdWuXaM`b!-mlMQo^a0$P(}ulwQ}=YXYuc+ zqr0*K@b#@6c#sM1=ocF1DDWKoXRPQXG_!HM`X2*qn1%fvpjoC7i4BR4V-zt&rrYu7 z|H!S(uFlR9a%kgE_jCN@#5XD{$-L~Yu1@%Ag5JlL_}z>chB{cgau=|X>Fnx_+Q_v- z*u&k}nq)->R2yO7Zx@KdX#W?~o?+&~Zyy3V4{vd~=f4AL^?fkcyjLySO51&zs@bjD z8GOto$(a;`scOKAJYIpD)&lKpxXqC~(0YH1 z+OD?nNwNV2L8AmK(cj81I6X50pNwsDSre_`K)MLVFboefBF;$#ZrNVd7ieVZz~ujCt2t@|mlRLuZfGq)<(@|=y%g~L6{Jo_f}`^d&u>+t zX0#!7zW3iHEsw=)^Hn1ACuVXO0!k+6v=4Jap)voBQ?J)Aw0#nr8M#!OQ@76OvTFb4 zFplbO4^s-BzYzuYr1#x3(iO4qdt`{sx8`@X2M{>Q%skq*Ffg|;z?2<3P=dU@n89WFez)EZ3)I>^y6!iPI@p`< zgr<)f_VG2pK^}+3_V!!|8W{GDh@ty;5(5cXclVu8x_y4w-Jv6Na+u}t4{Sdr`xLxo zRkNUcoP%^!pSEGPNdm>u!wfRr<-gj_SIiGeL&#mDQGy8gp|D%+-SAqnp2ezPG!EP8 zfqnamStL0D!(iFi;;6_XwVl^?@~F>DH1JUdg4jlIzw{l}!|tm5fR})HEO&2SIy8Ur zCd|>C9lm*S9!lr2Yor&LFll@7GHsZC+nm2P`z^murpMl+B$In*XI+gxS=KHpppEeeU#Bem5G6qR>F6}37*0{e5>9?)SseG8|lC`SE$ za$BUo%|N`nRN+gdWv~K!dq!=6az+*lT6I>>4fA(62yBw#%ykq3cSKgd6ZTz@>4RZd zF)18=MyEeJ061^LbyDm#^ec4M`6i-P8+Px2crOQu3UOi_k+Hb=qFgoVv$p zOeVIe0h~VX9(Upd2`1#41*c}K>wYCTgg&<20-XTlR@=F66>(MFMk&?!h`F2Pt)_qV z6__)=6AAXz)!+99d+^R3V^oL#fG}|B9_!Xc6L^*@mopI+g`RZ7w4*0ZkmD2rAMv^O zq)T6g!E9!Yjv57-S|a9m8p*5v%tpBDd;YFq^83)`hxR2%Yjp*?l24t_MbB+1NHZFr zXpTJ$(zIOut`^UYTCWVkb^$fu%XYAGEz#B%7W(!Yqr}%L5Rk;zxxO`1@3-5ve0gKz zN>l2u3e@)%Q!uPVzMaSLBtxd&W);IFJl^T0D{&rF?2VUBLIF7$MtghNM3hf5QV*MI zbcc(Q0;sKoE^qz$Uf*3+aRvsajdw8jB_KDinD6%JU{vCsEbt>1QRQD753bKRdE&(9 z`_$QSy>b?A0`@$O?V&$eiz~D=)x2o;)_z(#pLV7Jd~~@L{nowxjtCRYmfdbY?4zA7 zzQX7Pl=4u>BzcKtsY&Ou<2Xj31ik|?Uc>4z3{MXd&wh9KOQku@LuvcfidjL>l5ars zm~S3lRf1rMWadit6?P^v&U7WiB8x6~-e0NPb%~#sUR(|e~=}Ze81p~UKuz(5~=?!YtfUXxZ)!K0?6Tsv9!o|!zh z^5^vPexL2|dn2u;@66ZyfbFqeFV3nb$@ns1ur_V!FBZhVyFcWyl3NNh%I0~29i|Zr zirrFAhjz}1=O4b#vFf7)O9S9X5=-s3Vg=~?>zeR= z^@n#B!fBlD7Os4Mc-Bj~CmKfQ?C0#}^Pe#29Lzw~w}X+F>d!p2bEIIc zD|i+!0=uMawM_Nn$`v;?+nne*&%~1Al_=P|!n7_RuzQrp8y(>}fCnF|%yjNkpx9po zg0XtdnWAk9ZT^M`lTD%3zW^TS=zny*rUCrA#lG`zP@>q_QTrTgXv^6j{JNmEsJt%g1+NG1 z_t=r%<-k}B$CfC`I%IvcPl>`SI%oy;_4ITeFP%zh(y4zI+(J^3j0&U{YFw7|fMipR&N+0jX-MxZw@u2@@%58S(sYA;9F)5o3A z1ZsB`APTuVzn@+6cSBrP`X+3wXHmzuOy;+BpQ{mB1n|jgy|u0L;Jo(>?rAhNSU%9< zIK_1@d2VL9Eb4lGMnC-4kgE00-27%B-CmKjoc~oQCUAv9p~8UW3Hl`w%DA}Vqb2;Z z%p&58Y#*kRrb4f-VNVCGxt!oh?^`VFOwVOzYomavOY&Z9Xc4?`0P`ykm2$~1X5Jxg zS!e9*l|Ob05cNk7UaH3G4j#^C9m-~bahEqYFIB&6{NlihWrocKNY<9q$6ks&^m63- zoq8CUP+-4%9jaWdAN}ZwC$deQRL%b`6bpVl`hIAjW?7;>J6su;pg$Z7#Ec}a34qZlqHO^$ zhrA7ROeC1<{=qZE&^_N<(0(L4@&#$sJ|N+R6le3v*NdOr?R?Zq6(Nr7H}EkG#1>4l z`$|`kzSD;5{@%asr|M6)J@(%AEzfi{zW)~{Xt@(ZvI>DwTsl6U=-o~I*iUy4B*ri1 z8nd&Etl3-l9BH!Nyupmjx(dV)F!*D=B+Kpskg)gbG{O)Y`RTUD#KaM(04(!U=?(BX zl7w9-gY+6pB~2!t^SiMBiAueHqM}G!Rj4dl^e3;ZJh(rPox4~XbIGL_&#}lt(Y2^Y zGzVWJ01(9(i;5EEQ60toaaBFZffTrJtE;Q`ANVWF8?N?vQe9NnE2F~lEp~5 zGfK`o*S>TgNyvjdc|c1=vlfg(8)43m-%t>p%dx9FFg5P`^1@DPl2{B_Ch;QSE5%QnOf|QGm)9uj?ni8=$)vQ7W zcJ4@SZwB0NvkZW?v_)%e+ZV|!|Kfvo8)|wU9t1ZI0F|5t>5w{z#Vc1UtFBxTt!3Y* z7!vYVFzua4U*@~BE?@pQ)!#u7vrQcI`UWb^%+WLXluT_iLC*$%|G;L~FNc}yis`yJE5Nn+j92mJ(Ht&_Hehrw!B7C4Qvo!{2!6jKGiML_mioL8eJ4 zk}uWZH-H*_tU?@}#2NMz3Ud7fBxqZPeks=2%Ja$&+A5LFX6t_stb1(a%U8Z z8E#=^x)L(Fjo#OpoW$Ihg1+ec>engeVqB zsmE3`O1Rm{7XtyKt9cXXhu%+we8X#QcML7xd-{pm3hEry$F1NHBKVk2p3EPQ>#md1 z=l`%p>7Bf5p>KoV2ZuZZ7Y6U{Ix*m4RY6m7PRyIj`pnt!y60Hdt#vr&rvVFdB|tpr z0VP#3lYKF*BDjnnR!o!Aj&;o*N&IA?fh$jEB#VfED3ICt-MY&X$t*x=l|w>1QJTb!MpO!s%0W5=gj zFFbZ#ZEv488VnOY{U87vhpeK>L4B8LR|SKbtHZRh4BS--9 zN9+@TS1n%VN593_lMM{rNRo{-({S1M1(G_4&^d~Hp(*SgMKsfF!{P?F0BHGh(~*;{ z#eQtQV`ykr1jtb>HDp6=e;f%8K6aw>Xy=JzTLtm1@%>`){_$O{v{$?iS4=!EC_E`J z={L+IpHEUy`~NV$;VgbHkNaGDBQ>$x?{%h&VLIrlH+MxQShh&NU9`trOdrU|a85t4~Yebe!^I!eoNRf}^wZ6AL@|er_ zO2_b>^F9$U>G>D!t$aa8I(ngUFF$8}z!zF0XrcO?LpMv;b(-UX%F{BGxr=exmPP*` zE@>a|U=TD-Q_V%kW4?%i|2^tsQ+jp!w;9c0v79cYJ!_6j5Z_Cw=rL5sqYG^F`jcwa z4ch`=z!7ij1GJw5`tQ6r(lj-Qf+|cv%l7P*+lexkA)d)jQ>(vXeR75Mm2b>7UirkC zw)PH~JQ^Kbnbqq})DqR507ppaeG7W;deXRr-$-xW;JlF0%q zW;EcbaZsiIO_s1x=xG|Fc-TrK;)K{ef(e zI^w|ZSJFO#jk2^9irNiip$pB;LV zcY8gn+cChD3k%*J9UL6beMKVW;!hq&9nHc+-S-bx1LgS#3%l!wM~pJ(D=4M7ba zjn2H#S97juPS32U_6L#R*ymUNZY{B2jt8y zfk)oiYbW0BLJD{Ebz5tkHuQbomN4_}B;qLXje>e&zWL}l+4;&q;BRbW3B5AA19x2+ z{+)_&{5J24KAb{QI6N@0M|cTz`#;4DvbFwJKJopDMH6|^Pm6$rg9Y7Q4A4pMZp0Br zzzy*Z;y1lN%=TW#4jg&?=b)gUznN(**u+gUq`{F-Q(Ony|DHpI13$mv9&Gx^>jgR@ zo$=;py#LebRd`k??26V^*=xV|s{er!0M9lR)d0MTU}kXQl!NSm05a@B#3fb8noXRO zRjtEzKYUg;=T%HZ(DD;{OWe}AdEw_rghLy~$Z%^!VsN9FH?#ic7tFr-w~dJIOkD6- zYR5&6GfM0jsmFUh0jI%Y{=}V5kv}5;@Lrzl`hH7dPhvmeVHO(j8i42-y6Bm*5w|@K z7|prMn7A+Q?>u9RoCRDm;dm&$xfi*Qzisyzl=&&64E*tB00kuts1M~nN8>TK%nZvjv5y?Mx+o?w!8l{W>}bx4c2@TxqBNPYbqLpQ!mST zOOWe1P*;>8*phMfIb(OBvlt-#m7(CcP_!dJVWMYYEN1yz%ZYGhtRu1cqdQZFV-h(- zqqS_*e}4aE$Z90P09uYu!8*^XK$k*2QO~f&v@mqe3woQVO#!cm6^AS03UZNmftZ!Z!O{sJNU}ozGs$)OO6YtmuGsbY znW*fw70*x{)n7U-!7ArYN8W+Xh>utYYgl^3>mu(46@|gfEc|&BKeX>FH4-^f$Su5j zVD`U7o{$ahLNvOrKoOBb;s7vk1oKK$;Zsja(IbA38MrRm{~CMLELxmZn9mm!RV)`i z!`lvyaO|$lf3#xydWivZ`ruzs5nW$%xzq2$lNBH9@Eygv8M?37#6vNBM1H&nF;eKf z`wi{k6C+zm_1NJM;>haKe>)YR(guEYk^hUR5ZtDc?y*FO#uhK++pHYv&IE?2G7z09 z#S;cb97m_R&4~6{LakSC@9$Bdl0w+8%E6()-SE{T43#r3?8Ihh-%>x&89-kv09&y} z*Cfo*btbOx-aRB6heFAc$UmTjFqC&M=GGTV5EFzDi(-eH)6EQeS+Gq@eL7rIWz5U1@dc4 zUBuB0L)jJ4U8*gk{vL~{0|^=AY)OjR`U5km3Db*X50iVJ2 zqNSa3wP!)`M*7cz-o|8dhg>*T+dtqdrb);7q3B zf1D|W&%dKsa`?A22OVb%7 literal 0 HcmV?d00001 diff --git a/anms-ui/angular-port/anms-ui/src/environments/environment.development.ts b/anms-ui/angular-port/anms-ui/src/environments/environment.development.ts new file mode 100644 index 00000000..a569cab4 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/environments/environment.development.ts @@ -0,0 +1,12 @@ +export const environment = { + VUE_APP_UI_VERSION: "VUE_APP_UI_VERSION_TEMPLATE", + VUE_APP_STATUS_REFRESH_RATE: 60000, // milliseconds + SERVICE_INFO: { + names: [ + "adminer","anms-core","authnz","amp-manager", + "postgres","redis","grafana","grafana-image-renderer" + ], + normal_status: ["running","healthy"], + error_status: ["not-running","unhealthy"] + } +}; diff --git a/anms-ui/angular-port/anms-ui/src/environments/environment.ts b/anms-ui/angular-port/anms-ui/src/environments/environment.ts new file mode 100644 index 00000000..a569cab4 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/environments/environment.ts @@ -0,0 +1,12 @@ +export const environment = { + VUE_APP_UI_VERSION: "VUE_APP_UI_VERSION_TEMPLATE", + VUE_APP_STATUS_REFRESH_RATE: 60000, // milliseconds + SERVICE_INFO: { + names: [ + "adminer","anms-core","authnz","amp-manager", + "postgres","redis","grafana","grafana-image-renderer" + ], + normal_status: ["running","healthy"], + error_status: ["not-running","unhealthy"] + } +}; diff --git a/anms-ui/angular-port/anms-ui/src/favicon.png b/anms-ui/angular-port/anms-ui/src/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..39d32dbada06c04fd1ef6253a9439ef7661c829d GIT binary patch literal 777 zcmV+k1NQuhP)&$2OFH`ih~xy|6C{WOBsl@e30O*K;@n_CqE9+XW~8ySSnGJd4oDtp<*+=PH`lL@p#LXU=6x$8BM3gBZ!5ZBOWUI&4y zD8$HZVh!;5cED6Jmjn`WhmkG_>SrP!$^>XX&PxWG(vEPVsnLwwC$7T++pLh%p+;xN zijgNI?cW)OP^L@ZwM2`YtP>W>RAwzqVwAAZTF-{-fSK%tWYJLwPHXnrpQgI!bf*o9!uNvuv}lzjM(2&Ar#l27!Pb_Zc>LE~K4Gg=fPKIrykw( + + + + AMMOS ANMS Application + + + + + + + + + diff --git a/anms-ui/angular-port/anms-ui/src/main.ts b/anms-ui/angular-port/anms-ui/src/main.ts new file mode 100644 index 00000000..5df75f9c --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/main.ts @@ -0,0 +1,6 @@ +import { bootstrapApplication } from '@angular/platform-browser'; +import { appConfig } from './app/app.config'; +import { App } from './app/app'; + +bootstrapApplication(App, appConfig) + .catch((err) => console.error(err)); diff --git a/anms-ui/angular-port/anms-ui/src/material-theme.scss b/anms-ui/angular-port/anms-ui/src/material-theme.scss new file mode 100644 index 00000000..57b6b77b --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/material-theme.scss @@ -0,0 +1,38 @@ +// Include theming for Angular Material with `mat.theme()`. +// This Sass mixin will define CSS variables that are used for styling Angular Material +// components according to the Material 3 design spec. +// Learn more about theming and how to use it for your application's +// custom components at https://material.angular.dev/guide/theming +@use '@angular/material' as mat; + +html { + height: 100%; + @include mat.theme( + ( + color: ( + primary: mat.$cyan-palette, + tertiary: mat.$orange-palette, + ), + typography: Roboto, + density: 0, + ) + ); +} + +body { + // Default the application to a light color theme. This can be changed to + // `dark` to enable the dark color theme, or to `light dark` to defer to the + // user's system settings. + color-scheme: light; + + // Set a default background, font and text colors for the application using + // Angular Material's system-level CSS variables. Learn more about these + // variables at https://material.angular.dev/guide/system-variables + background-color: var(--mat-sys-surface); + color: var(--mat-sys-on-surface); + font: var(--mat-sys-body-medium); + + // Reset the user agent margin. + margin: 0; + height: 100%; +} diff --git a/anms-ui/angular-port/anms-ui/src/styles.css b/anms-ui/angular-port/anms-ui/src/styles.css new file mode 100644 index 00000000..5e2b5ad4 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/src/styles.css @@ -0,0 +1,38 @@ +/* You can add global styles to this file, and also import other style files */ +@import "bootstrap/dist/css/bootstrap.min.css"; +@import "bootstrap-icons/font/bootstrap-icons.css"; +@import '@angular/material/prebuilt-themes/cyan-orange.css'; + +@font-face { + font-family: 'Material Icons'; + font-style: normal; + font-weight: 400; + src: url(assets/fonts/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2) format('woff2'); +} + +body, html { + width: 100%; + height: 100%; + margin: 0; + padding: 0 +} + +.material-icons { + font-family: 'Material Icons', serif; + font-weight: normal; + font-style: normal; + font-size: 24px; + line-height: 1; + letter-spacing: normal; + text-transform: none; + display: inline-block; + white-space: nowrap; + word-wrap: normal; + direction: ltr; + -moz-font-feature-settings: 'liga'; + -moz-osx-font-smoothing: grayscale; +} + +.font-weight-normal { + font-weight: normal !important; +} diff --git a/anms-ui/angular-port/anms-ui/tsconfig.app.json b/anms-ui/angular-port/anms-ui/tsconfig.app.json new file mode 100644 index 00000000..264f459b --- /dev/null +++ b/anms-ui/angular-port/anms-ui/tsconfig.app.json @@ -0,0 +1,15 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/app", + "types": [] + }, + "include": [ + "src/**/*.ts" + ], + "exclude": [ + "src/**/*.spec.ts" + ] +} diff --git a/anms-ui/angular-port/anms-ui/tsconfig.json b/anms-ui/angular-port/anms-ui/tsconfig.json new file mode 100644 index 00000000..2ab74427 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/tsconfig.json @@ -0,0 +1,33 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "compileOnSave": false, + "compilerOptions": { + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "isolatedModules": true, + "experimentalDecorators": true, + "importHelpers": true, + "target": "ES2022", + "module": "preserve" + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + }, + "files": [], + "references": [ + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/anms-ui/angular-port/anms-ui/tsconfig.spec.json b/anms-ui/angular-port/anms-ui/tsconfig.spec.json new file mode 100644 index 00000000..d3837063 --- /dev/null +++ b/anms-ui/angular-port/anms-ui/tsconfig.spec.json @@ -0,0 +1,15 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/spec", + "types": [ + "vitest/globals" + ] + }, + "include": [ + "src/**/*.d.ts", + "src/**/*.spec.ts" + ] +} From 2fbc6feffe73287db493acdac920fc6675862320 Mon Sep 17 00:00:00 2001 From: Michael Malinowski <265828397+js-2026-git@users.noreply.github.com> Date: Wed, 15 Apr 2026 10:58:48 -0400 Subject: [PATCH 02/41] Update readme; get and load agent reports --- anms-ui/angular-port/anms-ui/README.md | 25 +++++++++++++++++++ anms-ui/angular-port/anms-ui/angular.json | 4 +-- .../anms-ui/src/app/features/adms/adms.ts | 1 - .../agents/agent-modal/agent-modal.html | 8 ++---- .../agents/agent-modal/agent-modal.ts | 6 ----- .../management/agents/reports/reports.css | 2 +- .../management/agents/reports/reports.html | 19 +++----------- .../management/agents/reports/reports.ts | 19 +++++++------- .../anms-ui/src/app/shared/api-adm.service.ts | 1 - .../anms-ui/src/app/shared/api.service.ts | 17 ++++++------- .../src/app/store/modules/agents.service.ts | 1 - 11 files changed, 51 insertions(+), 52 deletions(-) diff --git a/anms-ui/angular-port/anms-ui/README.md b/anms-ui/angular-port/anms-ui/README.md index e69de29b..c8f3e61f 100644 --- a/anms-ui/angular-port/anms-ui/README.md +++ b/anms-ui/angular-port/anms-ui/README.md @@ -0,0 +1,25 @@ +# ANMS-UI Angular +## Software Stack +- Angular 21 +- Bootstrap 5 + +## Development +Project source files are Located under `angular-port/anms-ui` until VUE to Angular port is complete. + +### Configuration +For local development and remote VM server use `proxy.confiig.json` +_NOTE_ `proxy.config.json` is used by default for dev-server profile builds. Change this behavior under `angular.json`. +Currently, proxy config is set to `anms-test` VM. +Environment setup is done with `src/environments` `environment.ts` for deployment and `environment.development.ts` for development + +### Install dependencies and Run +- Install all required dependency defined under `package.json` with `npm install` + - Note that npm requires `node v22.12.0` + - All modules are installed under `node_modules` directory +- Run anms-ui with `ng serve` + - Open web app in a browser `localhost:4200` + +## Build and Deploy +Build application for deployment with `ng build` +The deployment ready output `dist/anms-ui` Copy the content to deployment directory of a web app server. For example, if using tomcat, then `webapps/anms-ui`. + diff --git a/anms-ui/angular-port/anms-ui/angular.json b/anms-ui/angular-port/anms-ui/angular.json index 1a32f409..9de345af 100644 --- a/anms-ui/angular-port/anms-ui/angular.json +++ b/anms-ui/angular-port/anms-ui/angular.json @@ -31,8 +31,8 @@ "budgets": [ { "type": "initial", - "maximumWarning": "500kB", - "maximumError": "1MB" + "maximumWarning": "20MB", + "maximumError": "30MB" }, { "type": "anyComponentStyle", diff --git a/anms-ui/angular-port/anms-ui/src/app/features/adms/adms.ts b/anms-ui/angular-port/anms-ui/src/app/features/adms/adms.ts index b5865c53..d59bfffd 100644 --- a/anms-ui/angular-port/anms-ui/src/app/features/adms/adms.ts +++ b/anms-ui/angular-port/anms-ui/src/app/features/adms/adms.ts @@ -35,7 +35,6 @@ export class Adms implements AfterViewInit { } else if (!_.isNil(this.admService.uploadStatus()) && this.admService.uploadStatus() !== '') { // toastr.success(this.uploadStatus);uploadStatus - console.log(this.admService.uploadStatus()); this.admService.getAdms(); } } diff --git a/anms-ui/angular-port/anms-ui/src/app/features/management/agents/agent-modal/agent-modal.html b/anms-ui/angular-port/anms-ui/src/app/features/management/agents/agent-modal/agent-modal.html index bd0083b0..a3ad54ef 100644 --- a/anms-ui/angular-port/anms-ui/src/app/features/management/agents/agent-modal/agent-modal.html +++ b/anms-ui/angular-port/anms-ui/src/app/features/management/agents/agent-modal/agent-modal.html @@ -45,19 +45,15 @@

Agent Details

- RPTT: {{rptt}} - - + [operations]="this.agentsService.getOperations()"> } diff --git a/anms-ui/angular-port/anms-ui/src/app/features/management/agents/agent-modal/agent-modal.ts b/anms-ui/angular-port/anms-ui/src/app/features/management/agents/agent-modal/agent-modal.ts index de37f1cb..08054d4b 100644 --- a/anms-ui/angular-port/anms-ui/src/app/features/management/agents/agent-modal/agent-modal.ts +++ b/anms-ui/angular-port/anms-ui/src/app/features/management/agents/agent-modal/agent-modal.ts @@ -37,9 +37,6 @@ export class AgentModal implements OnInit { protected agentInfo: AgentInfo; - protected rptt: ReportOption[] = []; - protected operations: Operation[] = []; - constructor( private dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: AgentInfo, @@ -52,9 +49,6 @@ export class AgentModal implements OnInit { this.agentsService.setAgentId(this.agentInfo.registered_agents_id); this.agentsService.reloadAgent(); } - - this.rptt = this.agentsService.rptt(); - this.operations = this.agentsService.operations(); } close(): void { diff --git a/anms-ui/angular-port/anms-ui/src/app/features/management/agents/reports/reports.css b/anms-ui/angular-port/anms-ui/src/app/features/management/agents/reports/reports.css index ffde3ece..0454926f 100644 --- a/anms-ui/angular-port/anms-ui/src/app/features/management/agents/reports/reports.css +++ b/anms-ui/angular-port/anms-ui/src/app/features/management/agents/reports/reports.css @@ -7,6 +7,6 @@ } .scrollable-div { - max-height: 600px; + max-height: 500px; overflow-y: auto; } diff --git a/anms-ui/angular-port/anms-ui/src/app/features/management/agents/reports/reports.html b/anms-ui/angular-port/anms-ui/src/app/features/management/agents/reports/reports.html index 49c868f0..32d50584 100644 --- a/anms-ui/angular-port/anms-ui/src/app/features/management/agents/reports/reports.html +++ b/anms-ui/angular-port/anms-ui/src/app/features/management/agents/reports/reports.html @@ -15,7 +15,7 @@
Reports sent:
(change)="onReportSelect()" [ngModelOptions]="{ standalone: true }" > - + @for (rpt of rptts; track rpt) {