From aa38fb68a7473aec1c341e759a0d9e622d4c346a Mon Sep 17 00:00:00 2001
From: Aleh Zasypkin
Date: Mon, 6 Jan 2020 11:43:15 +0100
Subject: [PATCH 1/3] Migrate config deprecations and `ShieldUser`
functionality to the New Platform (#53768)
---
.../framework/kibana_framework_adapter.ts | 8 ++-
.../components/pipeline_edit/pipeline_edit.js | 4 +-
x-pack/legacy/plugins/security/index.js | 18 +----
.../legacy/plugins/security/public/lib/api.ts | 6 +-
.../security/public/services/shield_user.js | 33 ---------
.../security/public/views/account/account.js | 26 +++----
.../account_management_page.test.tsx | 71 ++++++++++++++++---
.../components/account_management_page.tsx | 44 +++++++-----
.../views/management/edit_role/index.js | 7 +-
.../components/edit_user_page.test.tsx | 71 ++++++++++++-------
.../edit_user/components/edit_user_page.tsx | 6 +-
.../views/management/edit_user/edit_user.js | 9 ++-
.../public/views/management/management.js | 15 ++--
.../views/management/users_grid/users.js | 1 -
.../overwritten_session.tsx | 52 +++++++-------
.../authentication/authentication_service.ts | 31 ++++++++
.../public/authentication/index.mock.ts | 13 ++++
.../security/public/authentication/index.ts | 7 ++
x-pack/plugins/security/public/index.ts | 3 +
x-pack/plugins/security/public/mocks.ts | 19 +++++
.../nav_control/nav_control_service.test.ts | 12 +++-
.../nav_control/nav_control_service.tsx | 21 +++---
x-pack/plugins/security/public/plugin.ts | 9 ++-
.../plugins/security/public/session/index.ts | 2 +-
.../public/session/session_timeout.tsx | 2 +-
x-pack/plugins/security/server/config.ts | 15 +---
x-pack/plugins/security/server/index.ts | 27 +++++--
x-pack/plugins/security/server/plugin.ts | 7 +-
28 files changed, 326 insertions(+), 213 deletions(-)
delete mode 100644 x-pack/legacy/plugins/security/public/services/shield_user.js
create mode 100644 x-pack/plugins/security/public/authentication/authentication_service.ts
create mode 100644 x-pack/plugins/security/public/authentication/index.mock.ts
create mode 100644 x-pack/plugins/security/public/authentication/index.ts
create mode 100644 x-pack/plugins/security/public/mocks.ts
diff --git a/x-pack/legacy/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/legacy/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts
index 79ffe58d419bd..b2cfd826e6207 100644
--- a/x-pack/legacy/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts
+++ b/x-pack/legacy/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts
@@ -11,6 +11,8 @@ import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { UIRoutes } from 'ui/routes';
import { isLeft } from 'fp-ts/lib/Either';
+import { npSetup } from 'ui/new_platform';
+import { SecurityPluginSetup } from '../../../../../../../plugins/security/public';
import { BufferedKibanaServiceCall, KibanaAdapterServiceRefs, KibanaUIConfig } from '../../types';
import {
FrameworkAdapter,
@@ -58,7 +60,7 @@ export class KibanaFrameworkAdapter implements FrameworkAdapter {
};
public async waitUntilFrameworkReady(): Promise {
- const $injector = await this.onKibanaReady();
+ await this.onKibanaReady();
const xpackInfo: any = this.xpackInfoService;
let xpackInfoUnpacked: FrameworkInfo;
@@ -95,8 +97,10 @@ export class KibanaFrameworkAdapter implements FrameworkAdapter {
}
this.xpackInfo = xpackInfoUnpacked;
+ const securitySetup = ((npSetup.plugins as unknown) as { security?: SecurityPluginSetup })
+ .security;
try {
- this.shieldUser = await $injector.get('ShieldUser').getCurrent().$promise;
+ this.shieldUser = (await securitySetup?.authc.getCurrentUser()) || null;
const assertUser = RuntimeFrameworkUser.decode(this.shieldUser);
if (isLeft(assertUser)) {
diff --git a/x-pack/legacy/plugins/logstash/public/sections/pipeline_edit/components/pipeline_edit/pipeline_edit.js b/x-pack/legacy/plugins/logstash/public/sections/pipeline_edit/components/pipeline_edit/pipeline_edit.js
index aa7f88a62397c..83446278fdeca 100755
--- a/x-pack/legacy/plugins/logstash/public/sections/pipeline_edit/components/pipeline_edit/pipeline_edit.js
+++ b/x-pack/legacy/plugins/logstash/public/sections/pipeline_edit/components/pipeline_edit/pipeline_edit.js
@@ -8,6 +8,7 @@ import React from 'react';
import { render } from 'react-dom';
import { isEmpty } from 'lodash';
import { uiModules } from 'ui/modules';
+import { npSetup } from 'ui/new_platform';
import { toastNotifications } from 'ui/notify';
import { I18nContext } from 'ui/i18n';
import { PipelineEditor } from '../../../../components/pipeline_editor';
@@ -21,7 +22,6 @@ app.directive('pipelineEdit', function($injector) {
const pipelineService = $injector.get('pipelineService');
const licenseService = $injector.get('logstashLicenseService');
const kbnUrl = $injector.get('kbnUrl');
- const shieldUser = $injector.get('ShieldUser');
const $route = $injector.get('$route');
return {
@@ -32,7 +32,7 @@ app.directive('pipelineEdit', function($injector) {
scope.$evalAsync(kbnUrl.change(`/management/logstash/pipelines/${id}/edit`));
const userResource = logstashSecurity.isSecurityEnabled()
- ? await shieldUser.getCurrent().$promise
+ ? await npSetup.plugins.security.authc.getCurrentUser()
: null;
render(
diff --git a/x-pack/legacy/plugins/security/index.js b/x-pack/legacy/plugins/security/index.js
index 6ee8b5f8b2b10..bc403b803b8df 100644
--- a/x-pack/legacy/plugins/security/index.js
+++ b/x-pack/legacy/plugins/security/index.js
@@ -28,17 +28,10 @@ export const security = kibana =>
enabled: Joi.boolean().default(true),
cookieName: HANDLED_IN_NEW_PLATFORM,
encryptionKey: HANDLED_IN_NEW_PLATFORM,
- session: Joi.object({
- idleTimeout: HANDLED_IN_NEW_PLATFORM,
- lifespan: HANDLED_IN_NEW_PLATFORM,
- }).default(),
+ session: HANDLED_IN_NEW_PLATFORM,
secureCookies: HANDLED_IN_NEW_PLATFORM,
loginAssistanceMessage: HANDLED_IN_NEW_PLATFORM,
- authorization: Joi.object({
- legacyFallback: Joi.object({
- enabled: Joi.boolean().default(true), // deprecated
- }).default(),
- }).default(),
+ authorization: HANDLED_IN_NEW_PLATFORM,
audit: Joi.object({
enabled: Joi.boolean().default(false),
}).default(),
@@ -46,13 +39,6 @@ export const security = kibana =>
}).default();
},
- deprecations: function({ rename, unused }) {
- return [
- unused('authorization.legacyFallback.enabled'),
- rename('sessionTimeout', 'session.idleTimeout'),
- ];
- },
-
uiExports: {
chromeNavControls: [],
managementSections: ['plugins/security/views/management'],
diff --git a/x-pack/legacy/plugins/security/public/lib/api.ts b/x-pack/legacy/plugins/security/public/lib/api.ts
index ffa08ca44f376..c5c6994bf4be3 100644
--- a/x-pack/legacy/plugins/security/public/lib/api.ts
+++ b/x-pack/legacy/plugins/security/public/lib/api.ts
@@ -5,16 +5,12 @@
*/
import { kfetch } from 'ui/kfetch';
-import { AuthenticatedUser, Role, User, EditUser } from '../../common/model';
+import { Role, User, EditUser } from '../../common/model';
const usersUrl = '/internal/security/users';
const rolesUrl = '/api/security/role';
export class UserAPIClient {
- public async getCurrentUser(): Promise {
- return await kfetch({ pathname: `/internal/security/me` });
- }
-
public async getUsers(): Promise {
return await kfetch({ pathname: usersUrl });
}
diff --git a/x-pack/legacy/plugins/security/public/services/shield_user.js b/x-pack/legacy/plugins/security/public/services/shield_user.js
deleted file mode 100644
index 14a79f267ca75..0000000000000
--- a/x-pack/legacy/plugins/security/public/services/shield_user.js
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import 'angular-resource';
-import angular from 'angular';
-import { uiModules } from 'ui/modules';
-
-const module = uiModules.get('security', ['ngResource']);
-module.service('ShieldUser', ($resource, chrome) => {
- const baseUrl = chrome.addBasePath('/internal/security/users/:username');
- const ShieldUser = $resource(
- baseUrl,
- {
- username: '@username',
- },
- {
- changePassword: {
- method: 'POST',
- url: `${baseUrl}/password`,
- transformRequest: ({ password, newPassword }) => angular.toJson({ password, newPassword }),
- },
- getCurrent: {
- method: 'GET',
- url: chrome.addBasePath('/internal/security/me'),
- },
- }
- );
-
- return ShieldUser;
-});
diff --git a/x-pack/legacy/plugins/security/public/views/account/account.js b/x-pack/legacy/plugins/security/public/views/account/account.js
index db971bd97eab7..70a7b8dce727e 100644
--- a/x-pack/legacy/plugins/security/public/views/account/account.js
+++ b/x-pack/legacy/plugins/security/public/views/account/account.js
@@ -6,22 +6,13 @@
import routes from 'ui/routes';
import template from './account.html';
-import '../../services/shield_user';
import { i18n } from '@kbn/i18n';
import { I18nContext } from 'ui/i18n';
+import { npSetup } from 'ui/new_platform';
import { AccountManagementPage } from './components';
import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
-const renderReact = (elem, user) => {
- render(
-
-
- ,
- elem
- );
-};
-
routes.when('/account', {
template,
k7Breadcrumbs: () => [
@@ -31,13 +22,8 @@ routes.when('/account', {
}),
},
],
- resolve: {
- user(ShieldUser) {
- return ShieldUser.getCurrent().$promise;
- },
- },
controllerAs: 'accountController',
- controller($scope, $route) {
+ controller($scope) {
$scope.$on('$destroy', () => {
const elem = document.getElementById('userProfileReactRoot');
if (elem) {
@@ -45,8 +31,12 @@ routes.when('/account', {
}
});
$scope.$$postDigest(() => {
- const elem = document.getElementById('userProfileReactRoot');
- renderReact(elem, $route.current.locals.user);
+ render(
+
+
+ ,
+ document.getElementById('userProfileReactRoot')
+ );
});
},
});
diff --git a/x-pack/legacy/plugins/security/public/views/account/components/account_management_page.test.tsx b/x-pack/legacy/plugins/security/public/views/account/components/account_management_page.test.tsx
index 176b05f455439..366842e58e9e4 100644
--- a/x-pack/legacy/plugins/security/public/views/account/components/account_management_page.test.tsx
+++ b/x-pack/legacy/plugins/security/public/views/account/components/account_management_page.test.tsx
@@ -4,8 +4,11 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
-import { mountWithIntl } from 'test_utils/enzyme_helpers';
+import { act } from '@testing-library/react';
+import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers';
+import { securityMock } from '../../../../../../../plugins/security/public/mocks';
import { AccountManagementPage } from './account_management_page';
+import { AuthenticatedUser } from '../../../../common/model';
jest.mock('ui/kfetch');
@@ -32,10 +35,24 @@ const createUser = ({ withFullName = true, withEmail = true, realm = 'native' }:
};
};
+function getSecuritySetupMock({ currentUser }: { currentUser: AuthenticatedUser }) {
+ const securitySetupMock = securityMock.createSetup();
+ securitySetupMock.authc.getCurrentUser.mockResolvedValue(currentUser);
+ return securitySetupMock;
+}
+
describe('', () => {
- it(`displays users full name, username, and email address`, () => {
+ it(`displays users full name, username, and email address`, async () => {
const user = createUser();
- const wrapper = mountWithIntl();
+ const wrapper = mountWithIntl(
+
+ );
+
+ await act(async () => {
+ await nextTick();
+ wrapper.update();
+ });
+
expect(wrapper.find('EuiText[data-test-subj="userDisplayName"]').text()).toEqual(
user.full_name
);
@@ -43,28 +60,60 @@ describe('', () => {
expect(wrapper.find('[data-test-subj="email"]').text()).toEqual(user.email);
});
- it(`displays username when full_name is not provided`, () => {
+ it(`displays username when full_name is not provided`, async () => {
const user = createUser({ withFullName: false });
- const wrapper = mountWithIntl();
+ const wrapper = mountWithIntl(
+
+ );
+
+ await act(async () => {
+ await nextTick();
+ wrapper.update();
+ });
+
expect(wrapper.find('EuiText[data-test-subj="userDisplayName"]').text()).toEqual(user.username);
});
- it(`displays a placeholder when no email address is provided`, () => {
+ it(`displays a placeholder when no email address is provided`, async () => {
const user = createUser({ withEmail: false });
- const wrapper = mountWithIntl();
+ const wrapper = mountWithIntl(
+
+ );
+
+ await act(async () => {
+ await nextTick();
+ wrapper.update();
+ });
+
expect(wrapper.find('[data-test-subj="email"]').text()).toEqual('no email address');
});
- it(`displays change password form for users in the native realm`, () => {
+ it(`displays change password form for users in the native realm`, async () => {
const user = createUser();
- const wrapper = mountWithIntl();
+ const wrapper = mountWithIntl(
+
+ );
+
+ await act(async () => {
+ await nextTick();
+ wrapper.update();
+ });
+
expect(wrapper.find('EuiFieldText[data-test-subj="currentPassword"]')).toHaveLength(1);
expect(wrapper.find('EuiFieldText[data-test-subj="newPassword"]')).toHaveLength(1);
});
- it(`does not display change password form for users in the saml realm`, () => {
+ it(`does not display change password form for users in the saml realm`, async () => {
const user = createUser({ realm: 'saml' });
- const wrapper = mountWithIntl();
+ const wrapper = mountWithIntl(
+
+ );
+
+ await act(async () => {
+ await nextTick();
+ wrapper.update();
+ });
+
expect(wrapper.find('EuiFieldText[data-test-subj="currentPassword"]')).toHaveLength(0);
expect(wrapper.find('EuiFieldText[data-test-subj="newPassword"]')).toHaveLength(0);
});
diff --git a/x-pack/legacy/plugins/security/public/views/account/components/account_management_page.tsx b/x-pack/legacy/plugins/security/public/views/account/components/account_management_page.tsx
index 2ed057ad73a12..6abee73e0b353 100644
--- a/x-pack/legacy/plugins/security/public/views/account/components/account_management_page.tsx
+++ b/x-pack/legacy/plugins/security/public/views/account/components/account_management_page.tsx
@@ -4,29 +4,41 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiPage, EuiPageBody, EuiPanel, EuiSpacer, EuiText } from '@elastic/eui';
-import React from 'react';
+import React, { useEffect, useState } from 'react';
+import { SecurityPluginSetup } from '../../../../../../../plugins/security/public';
import { getUserDisplayName, AuthenticatedUser } from '../../../../common/model';
import { ChangePassword } from './change_password';
import { PersonalInfo } from './personal_info';
interface Props {
- user: AuthenticatedUser;
+ securitySetup: SecurityPluginSetup;
}
-export const AccountManagementPage: React.FC = props => (
-
-
-
-
- {getUserDisplayName(props.user)}
-
+export const AccountManagementPage = (props: Props) => {
+ const [currentUser, setCurrentUser] = useState(null);
+ useEffect(() => {
+ props.securitySetup.authc.getCurrentUser().then(setCurrentUser);
+ }, [props]);
-
+ if (!currentUser) {
+ return null;
+ }
-
+ return (
+
+
+
+
+ {getUserDisplayName(currentUser)}
+
-
-
-
-
-);
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/index.js b/x-pack/legacy/plugins/security/public/views/management/edit_role/index.js
index 09c612526918f..27c9beb4ba828 100644
--- a/x-pack/legacy/plugins/security/public/views/management/edit_role/index.js
+++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/index.js
@@ -11,10 +11,10 @@ import { kfetch } from 'ui/kfetch';
import { fatalError, toastNotifications } from 'ui/notify';
import { npStart } from 'ui/new_platform';
import template from 'plugins/security/views/management/edit_role/edit_role.html';
-import 'plugins/security/services/shield_user';
import 'plugins/security/services/shield_role';
import 'plugins/security/services/shield_indices';
import { xpackInfo } from 'plugins/xpack_main/services/xpack_info';
+import { UserAPIClient } from '../../../lib/api';
import { ROLES_PATH, CLONE_ROLES_PATH, EDIT_ROLES_PATH } from '../management_urls';
import { getEditRoleBreadcrumbs, getCreateRoleBreadcrumbs } from '../breadcrumbs';
@@ -69,9 +69,8 @@ const routeDefinition = action => ({
return role.then(res => res.toJSON());
},
- users(ShieldUser) {
- // $promise is used here because the result is an ngResource, not a promise itself
- return ShieldUser.query().$promise.then(users => _.map(users, 'username'));
+ users() {
+ return new UserAPIClient().getUsers().then(users => _.map(users, 'username'));
},
indexPatterns() {
return npStart.plugins.data.indexPatterns.getTitles();
diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_user/components/edit_user_page.test.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_user/components/edit_user_page.test.tsx
index 5c71d0da3954a..639646ce48e22 100644
--- a/x-pack/legacy/plugins/security/public/views/management/edit_user/components/edit_user_page.test.tsx
+++ b/x-pack/legacy/plugins/security/public/views/management/edit_user/components/edit_user_page.test.tsx
@@ -4,38 +4,42 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { mountWithIntl } from 'test_utils/enzyme_helpers';
+import { act } from '@testing-library/react';
+import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers';
import { EditUserPage } from './edit_user_page';
import React from 'react';
+import { securityMock } from '../../../../../../../../plugins/security/public/mocks';
import { UserAPIClient } from '../../../../lib/api';
import { User, Role } from '../../../../../common/model';
import { ReactWrapper } from 'enzyme';
+import { mockAuthenticatedUser } from '../../../../../../../../plugins/security/common/model/authenticated_user.mock';
jest.mock('ui/kfetch');
-const buildClient = () => {
- const apiClient = new UserAPIClient();
+const createUser = (username: string) => {
+ const user: User = {
+ username,
+ full_name: 'my full name',
+ email: 'foo@bar.com',
+ roles: ['idk', 'something'],
+ enabled: true,
+ };
- const createUser = (username: string) => {
- const user: User = {
- username,
- full_name: 'my full name',
- email: 'foo@bar.com',
- roles: ['idk', 'something'],
- enabled: true,
+ if (username === 'reserved_user') {
+ user.metadata = {
+ _reserved: true,
};
+ }
- if (username === 'reserved_user') {
- user.metadata = {
- _reserved: true,
- };
- }
+ return user;
+};
- return Promise.resolve(user);
- };
+const buildClient = () => {
+ const apiClient = new UserAPIClient();
- apiClient.getUser = jest.fn().mockImplementation(createUser);
- apiClient.getCurrentUser = jest.fn().mockImplementation(() => createUser('current_user'));
+ apiClient.getUser = jest
+ .fn()
+ .mockImplementation(async (username: string) => createUser(username));
apiClient.getRoles = jest.fn().mockImplementation(() => {
return Promise.resolve([
@@ -63,6 +67,14 @@ const buildClient = () => {
return apiClient;
};
+function buildSecuritySetup() {
+ const securitySetupMock = securityMock.createSetup();
+ securitySetupMock.authc.getCurrentUser.mockResolvedValue(
+ mockAuthenticatedUser(createUser('current_user'))
+ );
+ return securitySetupMock;
+}
+
function expectSaveButton(wrapper: ReactWrapper) {
expect(wrapper.find('EuiButton[data-test-subj="userFormSaveButton"]')).toHaveLength(1);
}
@@ -74,10 +86,12 @@ function expectMissingSaveButton(wrapper: ReactWrapper) {
describe('EditUserPage', () => {
it('allows reserved users to be viewed', async () => {
const apiClient = buildClient();
+ const securitySetup = buildSecuritySetup();
const wrapper = mountWithIntl(
path}
intl={null as any}
/>
@@ -86,17 +100,19 @@ describe('EditUserPage', () => {
await waitForRender(wrapper);
expect(apiClient.getUser).toBeCalledTimes(1);
- expect(apiClient.getCurrentUser).toBeCalledTimes(1);
+ expect(securitySetup.authc.getCurrentUser).toBeCalledTimes(1);
expectMissingSaveButton(wrapper);
});
it('allows new users to be created', async () => {
const apiClient = buildClient();
+ const securitySetup = buildSecuritySetup();
const wrapper = mountWithIntl(
path}
intl={null as any}
/>
@@ -105,17 +121,19 @@ describe('EditUserPage', () => {
await waitForRender(wrapper);
expect(apiClient.getUser).toBeCalledTimes(0);
- expect(apiClient.getCurrentUser).toBeCalledTimes(0);
+ expect(securitySetup.authc.getCurrentUser).toBeCalledTimes(0);
expectSaveButton(wrapper);
});
it('allows existing users to be edited', async () => {
const apiClient = buildClient();
+ const securitySetup = buildSecuritySetup();
const wrapper = mountWithIntl(
path}
intl={null as any}
/>
@@ -124,16 +142,15 @@ describe('EditUserPage', () => {
await waitForRender(wrapper);
expect(apiClient.getUser).toBeCalledTimes(1);
- expect(apiClient.getCurrentUser).toBeCalledTimes(1);
+ expect(securitySetup.authc.getCurrentUser).toBeCalledTimes(1);
expectSaveButton(wrapper);
});
});
async function waitForRender(wrapper: ReactWrapper) {
- await Promise.resolve();
- await Promise.resolve();
- await Promise.resolve();
- await Promise.resolve();
- wrapper.update();
+ await act(async () => {
+ await nextTick();
+ wrapper.update();
+ });
}
diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_user/components/edit_user_page.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_user/components/edit_user_page.tsx
index 91f5f048adc6d..bbffe07485f8d 100644
--- a/x-pack/legacy/plugins/security/public/views/management/edit_user/components/edit_user_page.tsx
+++ b/x-pack/legacy/plugins/security/public/views/management/edit_user/components/edit_user_page.tsx
@@ -28,6 +28,7 @@ import {
} from '@elastic/eui';
import { toastNotifications } from 'ui/notify';
import { FormattedMessage, injectI18n, InjectedIntl } from '@kbn/i18n/react';
+import { SecurityPluginSetup } from '../../../../../../../../plugins/security/public';
import { UserValidator, UserValidationResult } from '../../../../lib/validate_user';
import { User, EditUser, Role } from '../../../../../common/model';
import { USERS_PATH } from '../../../../views/management/management_urls';
@@ -40,6 +41,7 @@ interface Props {
intl: InjectedIntl;
changeUrl: (path: string) => void;
apiClient: UserAPIClient;
+ securitySetup: SecurityPluginSetup;
}
interface State {
@@ -82,7 +84,7 @@ class EditUserPageUI extends Component {
}
public async componentDidMount() {
- const { username, apiClient } = this.props;
+ const { username, apiClient, securitySetup } = this.props;
let { user, currentUser } = this.state;
if (username) {
try {
@@ -91,7 +93,7 @@ class EditUserPageUI extends Component {
password: '',
confirmPassword: '',
};
- currentUser = await apiClient.getCurrentUser();
+ currentUser = await securitySetup.authc.getCurrentUser();
} catch (err) {
toastNotifications.addDanger({
title: this.props.intl.formatMessage({
diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_user/edit_user.js b/x-pack/legacy/plugins/security/public/views/management/edit_user/edit_user.js
index bd9d6f2b1ca35..ab218022c6ee6 100644
--- a/x-pack/legacy/plugins/security/public/views/management/edit_user/edit_user.js
+++ b/x-pack/legacy/plugins/security/public/views/management/edit_user/edit_user.js
@@ -7,7 +7,6 @@ import routes from 'ui/routes';
import template from 'plugins/security/views/management/edit_user/edit_user.html';
import 'angular-resource';
import 'ui/angular_ui_select';
-import 'plugins/security/services/shield_user';
import 'plugins/security/services/shield_role';
import { EDIT_USERS_PATH } from '../management_urls';
import { EditUserPage } from './components';
@@ -15,12 +14,18 @@ import { UserAPIClient } from '../../../lib/api';
import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import { I18nContext } from 'ui/i18n';
+import { npSetup } from 'ui/new_platform';
import { getEditUserBreadcrumbs, getCreateUserBreadcrumbs } from '../breadcrumbs';
const renderReact = (elem, changeUrl, username) => {
render(
-
+
,
elem
);
diff --git a/x-pack/legacy/plugins/security/public/views/management/management.js b/x-pack/legacy/plugins/security/public/views/management/management.js
index db2175e91c5de..59da63abbb83f 100644
--- a/x-pack/legacy/plugins/security/public/views/management/management.js
+++ b/x-pack/legacy/plugins/security/public/views/management/management.js
@@ -13,10 +13,10 @@ import 'plugins/security/views/management/edit_user/edit_user';
import 'plugins/security/views/management/edit_role/index';
import routes from 'ui/routes';
import { xpackInfo } from 'plugins/xpack_main/services/xpack_info';
-import '../../services/shield_user';
import { ROLES_PATH, USERS_PATH, API_KEYS_PATH } from './management_urls';
import { management } from 'ui/management';
+import { npSetup } from 'ui/new_platform';
import { i18n } from '@kbn/i18n';
import { toastNotifications } from 'ui/notify';
@@ -36,7 +36,7 @@ routes
})
.defaults(/\/management/, {
resolve: {
- securityManagementSection: function(ShieldUser) {
+ securityManagementSection: function() {
const showSecurityLinks = xpackInfo.get('features.security.showLinks');
function deregisterSecurity() {
@@ -93,12 +93,11 @@ routes
if (!showSecurityLinks) {
deregisterSecurity();
} else {
- // getCurrent will reject if there is no authenticated user, so we prevent them from seeing the security
- // management screens
- //
- // $promise is used here because the result is an ngResource, not a promise itself
- return ShieldUser.getCurrent()
- .$promise.then(ensureSecurityRegistered)
+ // getCurrentUser will reject if there is no authenticated user, so we prevent them from
+ // seeing the security management screens.
+ return npSetup.plugins.security.authc
+ .getCurrentUser()
+ .then(ensureSecurityRegistered)
.catch(deregisterSecurity);
}
},
diff --git a/x-pack/legacy/plugins/security/public/views/management/users_grid/users.js b/x-pack/legacy/plugins/security/public/views/management/users_grid/users.js
index a7115f449ebfd..8d4e0526251d7 100644
--- a/x-pack/legacy/plugins/security/public/views/management/users_grid/users.js
+++ b/x-pack/legacy/plugins/security/public/views/management/users_grid/users.js
@@ -8,7 +8,6 @@ import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import routes from 'ui/routes';
import template from 'plugins/security/views/management/users_grid/users.html';
-import 'plugins/security/services/shield_user';
import { SECURITY_PATH, USERS_PATH } from '../management_urls';
import { UsersListPage } from './components';
import { UserAPIClient } from '../../../lib/api';
diff --git a/x-pack/legacy/plugins/security/public/views/overwritten_session/overwritten_session.tsx b/x-pack/legacy/plugins/security/public/views/overwritten_session/overwritten_session.tsx
index 76088443212b2..fb39c517e1c2c 100644
--- a/x-pack/legacy/plugins/security/public/views/overwritten_session/overwritten_session.tsx
+++ b/x-pack/legacy/plugins/security/public/views/overwritten_session/overwritten_session.tsx
@@ -10,36 +10,40 @@ import React from 'react';
import { render } from 'react-dom';
import chrome from 'ui/chrome';
import { I18nContext } from 'ui/i18n';
+import { npSetup } from 'ui/new_platform';
+import { SecurityPluginSetup } from '../../../../../../plugins/security/public';
import { AuthenticatedUser } from '../../../common/model';
import { AuthenticationStatePage } from '../../components/authentication_state_page';
chrome
.setVisible(false)
.setRootTemplate('')
- .setRootController('overwritten_session', ($scope: any, ShieldUser: any) => {
+ .setRootController('overwritten_session', ($scope: any) => {
$scope.$$postDigest(() => {
- ShieldUser.getCurrent().$promise.then((user: AuthenticatedUser) => {
- const overwrittenSessionPage = (
-
-
- }
- >
-
-
-
-
-
- );
- render(overwrittenSessionPage, document.getElementById('reactOverwrittenSessionRoot'));
- });
+ ((npSetup.plugins as unknown) as { security: SecurityPluginSetup }).security.authc
+ .getCurrentUser()
+ .then((user: AuthenticatedUser) => {
+ const overwrittenSessionPage = (
+
+
+ }
+ >
+
+
+
+
+
+ );
+ render(overwrittenSessionPage, document.getElementById('reactOverwrittenSessionRoot'));
+ });
});
});
diff --git a/x-pack/plugins/security/public/authentication/authentication_service.ts b/x-pack/plugins/security/public/authentication/authentication_service.ts
new file mode 100644
index 0000000000000..23c45c88e563a
--- /dev/null
+++ b/x-pack/plugins/security/public/authentication/authentication_service.ts
@@ -0,0 +1,31 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { HttpSetup } from 'src/core/public';
+import { AuthenticatedUser } from '../../common/model';
+
+interface SetupParams {
+ http: HttpSetup;
+}
+
+export interface AuthenticationServiceSetup {
+ /**
+ * Returns currently authenticated user and throws if current user isn't authenticated.
+ */
+ getCurrentUser: () => Promise;
+}
+
+export class AuthenticationService {
+ public setup({ http }: SetupParams): AuthenticationServiceSetup {
+ return {
+ async getCurrentUser() {
+ return (await http.get('/internal/security/me', {
+ headers: { 'kbn-system-api': true },
+ })) as AuthenticatedUser;
+ },
+ };
+ }
+}
diff --git a/x-pack/plugins/security/public/authentication/index.mock.ts b/x-pack/plugins/security/public/authentication/index.mock.ts
new file mode 100644
index 0000000000000..c8d77a5b62c6f
--- /dev/null
+++ b/x-pack/plugins/security/public/authentication/index.mock.ts
@@ -0,0 +1,13 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { AuthenticationServiceSetup } from './authentication_service';
+
+export const authenticationMock = {
+ createSetup: (): jest.Mocked => ({
+ getCurrentUser: jest.fn(),
+ }),
+};
diff --git a/x-pack/plugins/security/public/authentication/index.ts b/x-pack/plugins/security/public/authentication/index.ts
new file mode 100644
index 0000000000000..a55f4d7bb95b3
--- /dev/null
+++ b/x-pack/plugins/security/public/authentication/index.ts
@@ -0,0 +1,7 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export { AuthenticationService, AuthenticationServiceSetup } from './authentication_service';
diff --git a/x-pack/plugins/security/public/index.ts b/x-pack/plugins/security/public/index.ts
index dc34fcbbe7d1e..336ec37d76a1b 100644
--- a/x-pack/plugins/security/public/index.ts
+++ b/x-pack/plugins/security/public/index.ts
@@ -6,7 +6,10 @@
import { PluginInitializer } from 'src/core/public';
import { SecurityPlugin, SecurityPluginSetup, SecurityPluginStart } from './plugin';
+
+export { SecurityPluginSetup, SecurityPluginStart };
export { SessionInfo } from './types';
+export { AuthenticatedUser } from '../common/model';
export const plugin: PluginInitializer = () =>
new SecurityPlugin();
diff --git a/x-pack/plugins/security/public/mocks.ts b/x-pack/plugins/security/public/mocks.ts
new file mode 100644
index 0000000000000..3c0c59d10abd1
--- /dev/null
+++ b/x-pack/plugins/security/public/mocks.ts
@@ -0,0 +1,19 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { authenticationMock } from './authentication/index.mock';
+import { createSessionTimeoutMock } from './session/session_timeout.mock';
+
+function createSetupMock() {
+ return {
+ authc: authenticationMock.createSetup(),
+ sessionTimeout: createSessionTimeoutMock(),
+ };
+}
+
+export const securityMock = {
+ createSetup: createSetupMock,
+};
diff --git a/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts b/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts
index 3879d611d46eb..a9a89ee05f561 100644
--- a/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts
+++ b/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts
@@ -10,6 +10,8 @@ import { ILicense } from '../../../licensing/public';
import { SecurityNavControlService } from '.';
import { SecurityLicenseService } from '../../common/licensing';
import { nextTick } from 'test_utils/enzyme_helpers';
+import { securityMock } from '../mocks';
+import { mockAuthenticatedUser } from '../../common/model/authenticated_user.mock';
const validLicense = {
isAvailable: true,
@@ -29,13 +31,17 @@ describe('SecurityNavControlService', () => {
const license$ = new BehaviorSubject(validLicense);
const navControlService = new SecurityNavControlService();
+ const mockSecuritySetup = securityMock.createSetup();
+ mockSecuritySetup.authc.getCurrentUser.mockResolvedValue(
+ mockAuthenticatedUser({ username: 'some-user', full_name: undefined })
+ );
navControlService.setup({
securityLicense: new SecurityLicenseService().setup({ license$ }).license,
+ authc: mockSecuritySetup.authc,
});
const coreStart = coreMock.createStart();
coreStart.chrome.navControls.registerRight = jest.fn();
- coreStart.http.get.mockResolvedValue({ username: 'some-user' });
navControlService.start({ core: coreStart });
expect(coreStart.chrome.navControls.registerRight).toHaveBeenCalledTimes(1);
@@ -93,6 +99,7 @@ describe('SecurityNavControlService', () => {
const navControlService = new SecurityNavControlService();
navControlService.setup({
securityLicense: new SecurityLicenseService().setup({ license$ }).license,
+ authc: securityMock.createSetup().authc,
});
const coreStart = coreMock.createStart();
@@ -111,6 +118,7 @@ describe('SecurityNavControlService', () => {
const navControlService = new SecurityNavControlService();
navControlService.setup({
securityLicense: new SecurityLicenseService().setup({ license$ }).license,
+ authc: securityMock.createSetup().authc,
});
const coreStart = coreMock.createStart();
@@ -126,6 +134,7 @@ describe('SecurityNavControlService', () => {
const navControlService = new SecurityNavControlService();
navControlService.setup({
securityLicense: new SecurityLicenseService().setup({ license$ }).license,
+ authc: securityMock.createSetup().authc,
});
const coreStart = coreMock.createStart();
@@ -146,6 +155,7 @@ describe('SecurityNavControlService', () => {
const navControlService = new SecurityNavControlService();
navControlService.setup({
securityLicense: new SecurityLicenseService().setup({ license$ }).license,
+ authc: securityMock.createSetup().authc,
});
const coreStart = coreMock.createStart();
diff --git a/x-pack/plugins/security/public/nav_control/nav_control_service.tsx b/x-pack/plugins/security/public/nav_control/nav_control_service.tsx
index aeeb84219c937..153e7112dc95b 100644
--- a/x-pack/plugins/security/public/nav_control/nav_control_service.tsx
+++ b/x-pack/plugins/security/public/nav_control/nav_control_service.tsx
@@ -9,11 +9,12 @@ import { CoreStart } from 'src/core/public';
import ReactDOM from 'react-dom';
import React from 'react';
import { SecurityLicense } from '../../common/licensing';
-import { AuthenticatedUser } from '../../common/model';
import { SecurityNavControl } from './nav_control_component';
+import { AuthenticationServiceSetup } from '../authentication';
interface SetupDeps {
securityLicense: SecurityLicense;
+ authc: AuthenticationServiceSetup;
}
interface StartDeps {
@@ -22,13 +23,15 @@ interface StartDeps {
export class SecurityNavControlService {
private securityLicense!: SecurityLicense;
+ private authc!: AuthenticationServiceSetup;
private navControlRegistered!: boolean;
private securityFeaturesSubscription?: Subscription;
- public setup({ securityLicense }: SetupDeps) {
+ public setup({ securityLicense, authc }: SetupDeps) {
this.securityLicense = securityLicense;
+ this.authc = authc;
}
public start({ core }: StartDeps) {
@@ -38,14 +41,8 @@ export class SecurityNavControlService {
const shouldRegisterNavControl =
!isAnonymousPath && showLinks && !this.navControlRegistered;
-
if (shouldRegisterNavControl) {
- const user = core.http.get('/internal/security/me', {
- headers: {
- 'kbn-system-api': true,
- },
- }) as Promise;
- this.registerSecurityNavControl(core, user);
+ this.registerSecurityNavControl(core);
}
}
);
@@ -60,16 +57,16 @@ export class SecurityNavControlService {
}
private registerSecurityNavControl(
- core: Pick,
- user: Promise
+ core: Pick
) {
+ const currentUserPromise = this.authc.getCurrentUser();
core.chrome.navControls.registerRight({
order: 2000,
mount: (el: HTMLElement) => {
const I18nContext = core.i18n.Context;
const props = {
- user,
+ user: currentUserPromise,
editProfileUrl: core.http.basePath.prepend('/app/kibana#/account'),
logoutUrl: core.http.basePath.prepend(`/logout`),
};
diff --git a/x-pack/plugins/security/public/plugin.ts b/x-pack/plugins/security/public/plugin.ts
index 0f10f9d89f25a..50e0b838c750f 100644
--- a/x-pack/plugins/security/public/plugin.ts
+++ b/x-pack/plugins/security/public/plugin.ts
@@ -9,18 +9,20 @@ import { LicensingPluginSetup } from '../../licensing/public';
import {
SessionExpired,
SessionTimeout,
+ ISessionTimeout,
SessionTimeoutHttpInterceptor,
UnauthorizedResponseHttpInterceptor,
} from './session';
import { SecurityLicenseService } from '../common/licensing';
import { SecurityNavControlService } from './nav_control';
+import { AuthenticationService } from './authentication';
export interface PluginSetupDependencies {
licensing: LicensingPluginSetup;
}
export class SecurityPlugin implements Plugin {
- private sessionTimeout!: SessionTimeout;
+ private sessionTimeout!: ISessionTimeout;
private navControlService!: SecurityNavControlService;
@@ -43,12 +45,15 @@ export class SecurityPlugin implements Plugin;
private sessionInfo?: SessionInfo;
private fetchTimer?: number;
diff --git a/x-pack/plugins/security/server/config.ts b/x-pack/plugins/security/server/config.ts
index b3f96497b0538..4f1c25702ae97 100644
--- a/x-pack/plugins/security/server/config.ts
+++ b/x-pack/plugins/security/server/config.ts
@@ -8,7 +8,6 @@ import crypto from 'crypto';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { schema, Type, TypeOf } from '@kbn/config-schema';
-import { duration } from 'moment';
import { PluginInitializerContext } from '../../../../src/core/server';
export type ConfigType = ReturnType extends Observable
@@ -35,7 +34,6 @@ export const ConfigSchema = schema.object(
schema.maybe(schema.string({ minLength: 32 })),
schema.string({ minLength: 32, defaultValue: 'a'.repeat(32) })
),
- sessionTimeout: schema.maybe(schema.nullable(schema.number())), // DEPRECATED
session: schema.object({
idleTimeout: schema.nullable(schema.duration()),
lifespan: schema.nullable(schema.duration()),
@@ -88,22 +86,11 @@ export function createConfig$(context: PluginInitializerContext, isTLSEnabled: b
secureCookies = true;
}
- // "sessionTimeout" is deprecated and replaced with "session.idleTimeout"
- // however, NP does not yet have a mechanism to automatically rename deprecated keys
- // for the time being, we'll do it manually:
- const deprecatedSessionTimeout =
- typeof config.sessionTimeout === 'number' ? duration(config.sessionTimeout) : null;
- const val = {
+ return {
...config,
encryptionKey,
secureCookies,
- session: {
- ...config.session,
- idleTimeout: config.session.idleTimeout || deprecatedSessionTimeout,
- },
};
- delete val.sessionTimeout; // DEPRECATED
- return val;
})
);
}
diff --git a/x-pack/plugins/security/server/index.ts b/x-pack/plugins/security/server/index.ts
index e189b71345ffc..33f554be5caa3 100644
--- a/x-pack/plugins/security/server/index.ts
+++ b/x-pack/plugins/security/server/index.ts
@@ -4,9 +4,15 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { PluginInitializerContext } from '../../../../src/core/server';
+import { TypeOf } from '@kbn/config-schema';
+import {
+ PluginConfigDescriptor,
+ PluginInitializer,
+ PluginInitializerContext,
+ RecursiveReadonly,
+} from '../../../../src/core/server';
import { ConfigSchema } from './config';
-import { Plugin } from './plugin';
+import { Plugin, PluginSetupContract, PluginSetupDependencies } from './plugin';
// These exports are part of public Security plugin contract, any change in signature of exported
// functions or removal of exports should be considered as a breaking change.
@@ -17,8 +23,17 @@ export {
InvalidateAPIKeyParams,
InvalidateAPIKeyResult,
} from './authentication';
-export { PluginSetupContract } from './plugin';
+export { PluginSetupContract };
-export const config = { schema: ConfigSchema };
-export const plugin = (initializerContext: PluginInitializerContext) =>
- new Plugin(initializerContext);
+export const config: PluginConfigDescriptor> = {
+ schema: ConfigSchema,
+ deprecations: ({ rename, unused }) => [
+ rename('sessionTimeout', 'session.idleTimeout'),
+ unused('authorization.legacyFallback.enabled'),
+ ],
+};
+export const plugin: PluginInitializer<
+ RecursiveReadonly,
+ void,
+ PluginSetupDependencies
+> = (initializerContext: PluginInitializerContext) => new Plugin(initializerContext);
diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts
index cdd2a024310bb..9c4b01f94ef4d 100644
--- a/x-pack/plugins/security/server/plugin.ts
+++ b/x-pack/plugins/security/server/plugin.ts
@@ -110,10 +110,7 @@ export class Plugin {
this.logger = this.initializerContext.logger.get();
}
- public async setup(
- core: CoreSetup,
- { features, licensing }: PluginSetupDependencies
- ): Promise> {
+ public async setup(core: CoreSetup, { features, licensing }: PluginSetupDependencies) {
const [config, legacyConfig] = await combineLatest([
createConfig$(this.initializerContext, core.http.isTlsEnabled),
this.initializerContext.config.legacy.globalConfig$,
@@ -169,7 +166,7 @@ export class Plugin {
csp: core.http.csp,
});
- return deepFreeze({
+ return deepFreeze({
authc,
authz: {
From 5b2a188c4362211798df998e990e2047575ee309 Mon Sep 17 00:00:00 2001
From: Maja Grubic
Date: Mon, 6 Jan 2020 10:55:15 +0000
Subject: [PATCH 2/3] [Dashboard] Empty screen redesign (#53681)
* Edit screen redesign
* Edit screen redesign
* Redesign view screen
* Redesign view screen
* Fixing type failure, and functional test
* Updating failing functional tests
* update dashboard empty styles
* i18n fix
* Updating failing snapshot
Co-authored-by: Ryan Keairns
Co-authored-by: Elastic Machine
---
.../dashboard_empty_screen.test.tsx.snap | 512 ++++++++++--------
.../__tests__/dashboard_empty_screen.test.tsx | 5 +
.../public/dashboard/_dashboard_app.scss | 28 +-
.../np_ready/dashboard_app_controller.tsx | 4 +-
.../np_ready/dashboard_empty_screen.tsx | 80 +--
.../dashboard_empty_screen_constants.tsx | 60 +-
.../home/assets/welcome_graphic_dark_2x.png | Bin 0 -> 53603 bytes
.../home/assets/welcome_graphic_light_2x.png | Bin 0 -> 53122 bytes
.../apps/dashboard/empty_dashboard.js | 8 +-
.../translations/translations/ja-JP.json | 3 -
.../translations/translations/zh-CN.json | 3 -
.../feature_controls/dashboard_security.ts | 2 +-
.../feature_controls/dashboard_spaces.ts | 2 +-
13 files changed, 402 insertions(+), 305 deletions(-)
create mode 100644 src/legacy/core_plugins/kibana/public/home/assets/welcome_graphic_dark_2x.png
create mode 100644 src/legacy/core_plugins/kibana/public/home/assets/welcome_graphic_light_2x.png
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/__snapshots__/dashboard_empty_screen.test.tsx.snap b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/__snapshots__/dashboard_empty_screen.test.tsx.snap
index d48e34b2e4837..f611ec978b6b3 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/__snapshots__/dashboard_empty_screen.test.tsx.snap
+++ b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/__snapshots__/dashboard_empty_screen.test.tsx.snap
@@ -2,6 +2,31 @@
exports[`DashboardEmptyScreen renders correctly with visualize paragraph 1`] = `
-
-
-
-
+
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
- This dashboard is empty. Let’s fill it up!
-
-
-
-
-
-
-
-
-
- Click the
-
-
-
- button in the menu bar above to add a visualization to the dashboard.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ Add an existing
+
+
+
+
+
+ or new object to this dashboard
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -373,6 +361,31 @@ exports[`DashboardEmptyScreen renders correctly with visualize paragraph 1`] = `
exports[`DashboardEmptyScreen renders correctly without visualize paragraph 1`] = `
@@ -581,59 +630,48 @@ exports[`DashboardEmptyScreen renders correctly without visualize paragraph 1`]
className="euiPageBody"
>
-
-
-
-
-
-
-
-
+
+
-
This dashboard is empty. Let’s fill it up!
-
+
-
-
-
- Click the
-
+
-
-
- button in the menu bar above to start working on your new dashboard.
-
-
-
+
+ Click
+
+
+
+
+
+
+
+
+
+ in the menu bar above to start adding panels.
+
+
+
+
+
+
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/dashboard_empty_screen.test.tsx b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/dashboard_empty_screen.test.tsx
index 1c450879ee553..381ced2efd8e3 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/dashboard_empty_screen.test.tsx
+++ b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/dashboard_empty_screen.test.tsx
@@ -24,11 +24,16 @@ import {
} from '../np_ready/dashboard_empty_screen';
// @ts-ignore
import { findTestSubject } from '@elastic/eui/lib/test';
+import { coreMock } from '../../../../../../core/public/mocks';
describe('DashboardEmptyScreen', () => {
+ const setupMock = coreMock.createSetup();
+
const defaultProps = {
showLinkToVisualize: true,
onLinkClick: jest.fn(),
+ uiSettings: setupMock.uiSettings,
+ http: setupMock.http,
};
function mountComponent(props?: DashboardEmptyScreenProps) {
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss b/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss
index d9eadf6c0e37d..03a8a07d6b17d 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss
+++ b/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss
@@ -6,5 +6,31 @@
.dshStartScreen {
text-align: center;
- padding: $euiSizeS;
+}
+
+.dshStartScreen__pageContent {
+ padding: $euiSizeXXL;
+}
+
+.dshStartScreen__panelDesc {
+ max-width: 260px;
+ margin: 0 auto;
+}
+
+.dshEmptyWidget {
+ border: $euiBorderThin;
+ border-style: dashed;
+ border-radius: $euiBorderRadius;
+ padding: $euiSizeXXL * 2;
+ max-width: 400px;
+ margin-left: $euiSizeS;
+ text-align: center;
+}
+
+.dshEmptyWidget {
+ border: 2px dashed $euiColorLightShade;
+ padding: 4 * $euiSize;
+ max-width: 20em;
+ margin-left: 10px;
+ text-align: center;
}
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx
index 08637174c8cec..8fcc7e4c26321 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx
+++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx
@@ -123,7 +123,7 @@ export class DashboardAppController {
timefilter: { timefilter },
},
},
- core: { notifications, overlays, chrome, injectedMetadata, uiSettings, savedObjects },
+ core: { notifications, overlays, chrome, injectedMetadata, uiSettings, savedObjects, http },
}: DashboardAppControllerDependencies) {
new FilterStateManager(globalState, getAppState, filterManager);
const queryFilter = filterManager;
@@ -197,6 +197,8 @@ export class DashboardAppController {
const emptyScreenProps: DashboardEmptyScreenProps = {
onLinkClick: shouldShowEditHelp ? $scope.showAddPanel : $scope.enterEditMode,
showLinkToVisualize: shouldShowEditHelp,
+ uiSettings,
+ http,
};
if (shouldShowEditHelp) {
emptyScreenProps.onVisualizeClick = addVisualization;
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_empty_screen.tsx b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_empty_screen.tsx
index 2fc78d64d0a0c..ae5319c560ab9 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_empty_screen.tsx
+++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_empty_screen.tsx
@@ -19,94 +19,110 @@
import React from 'react';
import { I18nProvider } from '@kbn/i18n/react';
import {
- EuiIcon,
EuiLink,
EuiSpacer,
EuiPageContent,
EuiPageBody,
EuiPage,
+ EuiImage,
EuiText,
EuiButton,
} from '@elastic/eui';
+import { IUiSettingsClient, HttpStart } from 'kibana/public';
import * as constants from './dashboard_empty_screen_constants';
export interface DashboardEmptyScreenProps {
showLinkToVisualize: boolean;
onLinkClick: () => void;
onVisualizeClick?: () => void;
+ uiSettings: IUiSettingsClient;
+ http: HttpStart;
}
export function DashboardEmptyScreen({
showLinkToVisualize,
onLinkClick,
onVisualizeClick,
+ uiSettings,
+ http,
}: DashboardEmptyScreenProps) {
+ const IS_DARK_THEME = uiSettings.get('theme:darkMode');
+ const emptyStateGraphicURL = IS_DARK_THEME
+ ? '/plugins/kibana/home/assets/welcome_graphic_dark_2x.png'
+ : '/plugins/kibana/home/assets/welcome_graphic_light_2x.png';
const linkToVisualizeParagraph = (
{constants.createNewVisualizationButton}
);
const paragraph = (
- description1: string,
+ description1: string | null,
description2: string,
linkText: string,
ariaLabel: string,
dataTestSubj?: string
) => {
return (
-
+
{description1}
+ {description1 && }
{linkText}
+
{description2}
);
};
- const addVisualizationParagraph = (
-
- {paragraph(
- constants.addVisualizationDescription1,
- constants.addVisualizationDescription2,
- constants.addVisualizationLinkText,
- constants.addVisualizationLinkAriaLabel,
- 'emptyDashboardAddPanelButton'
- )}
-
- {linkToVisualizeParagraph}
-
- );
const enterEditModeParagraph = paragraph(
constants.howToStartWorkingOnNewDashboardDescription1,
constants.howToStartWorkingOnNewDashboardDescription2,
constants.howToStartWorkingOnNewDashboardEditLinkText,
constants.howToStartWorkingOnNewDashboardEditLinkAriaLabel
);
- return (
-
-
-
-
-
-
-
- {constants.fillDashboardTitle}
-
-
- {showLinkToVisualize ? addVisualizationParagraph : enterEditModeParagraph}
-
-
-
-
+ const enterViewModeParagraph = paragraph(
+ null,
+ constants.addNewVisualizationDescription,
+ constants.addExistingVisualizationLinkText,
+ constants.addExistingVisualizationLinkAriaLabel
+ );
+ const viewMode = (
+
+
+
+
+
+ {constants.fillDashboardTitle}
+
+
+ {enterEditModeParagraph}
+
+
+
+ );
+ const editMode = (
+
+ {enterViewModeParagraph}
+
+ {linkToVisualizeParagraph}
+
);
+ return {showLinkToVisualize ? editMode : viewMode};
}
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_empty_screen_constants.tsx b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_empty_screen_constants.tsx
index 03004f6270fef..513e6cb685a7a 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_empty_screen_constants.tsx
+++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_empty_screen_constants.tsx
@@ -19,40 +19,20 @@
import { i18n } from '@kbn/i18n';
-export const addVisualizationDescription1: string = i18n.translate(
- 'kbn.dashboard.addVisualizationDescription1',
- {
- defaultMessage: 'Click the ',
- }
-);
-export const addVisualizationDescription2: string = i18n.translate(
- 'kbn.dashboard.addVisualizationDescription2',
- {
- defaultMessage: ' button in the menu bar above to add a visualization to the dashboard.',
- }
-);
-export const addVisualizationLinkText: string = i18n.translate(
- 'kbn.dashboard.addVisualizationLinkText',
- {
- defaultMessage: 'Add',
- }
-);
-export const addVisualizationLinkAriaLabel: string = i18n.translate(
- 'kbn.dashboard.addVisualizationLinkAriaLabel',
- {
- defaultMessage: 'Add visualization',
- }
-);
+/** VIEW MODE CONSTANTS **/
+export const fillDashboardTitle: string = i18n.translate('kbn.dashboard.fillDashboardTitle', {
+ defaultMessage: 'This dashboard is empty. Let\u2019s fill it up!',
+});
export const howToStartWorkingOnNewDashboardDescription1: string = i18n.translate(
'kbn.dashboard.howToStartWorkingOnNewDashboardDescription1',
{
- defaultMessage: 'Click the ',
+ defaultMessage: 'Click',
}
);
export const howToStartWorkingOnNewDashboardDescription2: string = i18n.translate(
'kbn.dashboard.howToStartWorkingOnNewDashboardDescription2',
{
- defaultMessage: ' button in the menu bar above to start working on your new dashboard.',
+ defaultMessage: 'in the menu bar above to start adding panels.',
}
);
export const howToStartWorkingOnNewDashboardEditLinkText: string = i18n.translate(
@@ -67,13 +47,23 @@ export const howToStartWorkingOnNewDashboardEditLinkAriaLabel: string = i18n.tra
defaultMessage: 'Edit dashboard',
}
);
-export const fillDashboardTitle: string = i18n.translate('kbn.dashboard.fillDashboardTitle', {
- defaultMessage: 'This dashboard is empty. Let\u2019s fill it up!',
-});
-export const visualizeAppLinkTest: string = i18n.translate(
- 'kbn.dashboard.visitVisualizeAppLinkText',
+/** EDIT MODE CONSTANTS **/
+export const addExistingVisualizationLinkText: string = i18n.translate(
+ 'kbn.dashboard.addExistingVisualizationLinkText',
+ {
+ defaultMessage: 'Add an existing',
+ }
+);
+export const addExistingVisualizationLinkAriaLabel: string = i18n.translate(
+ 'kbn.dashboard.addVisualizationLinkAriaLabel',
+ {
+ defaultMessage: 'Add an existing visualization',
+ }
+);
+export const addNewVisualizationDescription: string = i18n.translate(
+ 'kbn.dashboard.addNewVisualizationText',
{
- defaultMessage: 'visit the Visualize app',
+ defaultMessage: 'or new object to this dashboard',
}
);
export const createNewVisualizationButton: string = i18n.translate(
@@ -82,3 +72,9 @@ export const createNewVisualizationButton: string = i18n.translate(
defaultMessage: 'Create new',
}
);
+export const createNewVisualizationButtonAriaLabel: string = i18n.translate(
+ 'kbn.dashboard.createNewVisualizationButtonAriaLabel',
+ {
+ defaultMessage: 'Create new visualization button',
+ }
+);
diff --git a/src/legacy/core_plugins/kibana/public/home/assets/welcome_graphic_dark_2x.png b/src/legacy/core_plugins/kibana/public/home/assets/welcome_graphic_dark_2x.png
new file mode 100644
index 0000000000000000000000000000000000000000..8f551c54bd5527bcd9f3c6848ec550b4324f474b
GIT binary patch
literal 53603
zcmY&z<)mwvzX)?4q7
ztR#2tojY>o%-Q?wiBM6J!9XQJefRDihMcUV+PinKGw_!t5X>n`tl2y|w
zNJ-EMp}SPW@zlN4j?XD
z4gQPuwu^%8l|h&hx10Y*`E8zP8ze5d*MS%U5Jv`vaA6^E0ss5SA_}7jy#Rd(O+^&<
zUm0Ky7H{T%w>7-`|86@GD(8`k^fK~<-X?(pEh=~zcq|SFh>u()9Qeo}pYZLsdrqPN
zL(o~6;jsT-U2(ls&8!LA2kBzSK=Ym4(0r-nt+({I4#;9whW~wk^_LS*3jFwe34g(F
zf`8A$-&*8{Z|taDm7~f#xuE~QHIWWWkcn~~)l-)${4EbY9D5&sOH@q_42kO!huP$R
zXs(@-B#b@sBpv3m9=<7LB}Z#1gxqu%$`6YN;nf*YBc-9r&`Iv0IKc>J-y(3&@#8SW
zFCeo~LMsRiAixxt{TASCq<`fh#IK7zy%+UUZT7|AZLwVN-*CXeGR=g
zZV8di!FzXwr~TkT$-U7vU+aNYMarwZOXv&ZgnQ`3p$!mwb!$DBT3r>TQvT&mnMUp<
z11-EI`mb@Ki+s(ygA0nF6mcTN3bGQFhmW%~-2BwIFCOC~69ogfM)(~X%%eZ|BAJa-
z6HQsS>&^Zd0Of}q6yRSN2C|U=X#^6l8BmI3%KB{@#u*GYX7!g?_}i5%n^pI7mB|~x
zoF_(}_sN{5Zg=Z}K{nJB?gReyTK_jGQuJ}=3XK}0Mp&t+N*xLll{@i9azAd(0xQf4$J84nAg
zH8^O0H~<4jl{cPgTag1#wxtmSw|YFzDDAE<*g8J|UhM
zNp=O!w<(=p$rC3AbmRp>1G--4&eRwQo?fJzFq;4TihVQ9o;nNyXMEe=V>)_Tmqdio
z%4z14i6#FgDS72-U2KfTK}1^@rIxaCL@>JQKE1RHs}-B0-G*dNH=MD@*ImmX|D!7P
z^!7IXFLyQl9K*alct~8l2|M=TMg!VLOs54->fg7dgvMSm-8u!vDxyy6zPo=yz+~$u
z%R%T=4Bz6$#xSP)D@@x|=RZ>hAY%qCKuFoTP0Ge=Y<9}W_t?Fht1C7Q%sG&NvwL@ayP0+|YN$wEr&tkTpt`FjDC?`Hf0QP3XgW`!CIp
z1egO$t38BKvTHS!#MVUUb|hrmob!$QH0I&K%39Me2sGl}s0g>9
zJ?LAmc(aQQgV8xgC!fZs@VL;*QZPw3D|2!`QasL}h7C}63`QX}_A
z)l!$y^j_fUmy}0G+{f(T5Y05zc%$xCog2Zt3u#Mlp5xFsa#Ut=<*P~h&LCKC(j9tx
z!K3X6T#lK8;RmlRE5W$G_MC1rET7ux_*SgZ`d~?FF8Q;R+h>o(8(F0CF;5DkW7Z=vE4m@OtaSqehZYKZvZ@t<
z6<*%VxVS3L3wjlyA$i3%6ogyA{R{k
zNaE$r6K*V1&&bT1=+k;IkJrqrGlDqj9*MuT548!QtN`N-X%rL2S3Hm(0E7v3A>MuY
zR}$SW*GbOZ_&{=T7@-ZprD%7!Br&j(pJ)R}pM|!7X~3@>p==7!eB3fnh$gN
zF`37#$F96zH5i5gB)bw2nvY%M^0{^A*+xbxoeK*`)fpG(#;I+5Ft0VYw-`3#UY3k%
z?bm+C#{zv7Q7oQZO4ZyvL3Ta|3cNA%od9mQK+;t9>N0~7?Fy18ZNb0_k$mw?!*X8dAFA0Z^93MK4HQ(cq?>$T{j^UQj35{%)faKGU{K$}Aql@7>$xDk(Y
z?8A%MEo3Z1O8RNmyvd#)&;ITsyxyd>#@Lao@O-W
z(7)@hK!2u>y@M>+Q`q%Bjgb*Fb`@D3SO3@L_oFNoisL&3c4VfNFVmtE#vMZI_A
z(c0>klG*PadwnZGzk`6*)zTl!P;Z|JwWI+u-Y*Lb*7AjU%v%|Whl5_DM{e<1JExz1
zn6>&`xJ>^;xoV4dLkV2neSDsr^p(eVF
zwEUq$FZP4zW9HXQ?-{RPSmoP=Ufp>eS!Cd55J3#k!t$XaT6yX9sH3Tlwz~T_cPrml
zZr5uC*@B$8ibX4A^8*1Ve&P4J`d1XJGtns*=ZQ&FRranLiTdvqXnD>~Hb+A=vt4L{
zrNs=^p`I4o8)CBWyy}b$q-BK8N?c;G()9VNwt(c`n!eMBzEjtff(C~m^R!nVvBIxA
z3Ne+!B|&TClZ-(4J?)jY={d
z>?^8C#u0)2qQv6Fs_qEe1cBL!{z2$PSR}m}_skR}yllqR8Te(Bh6FRLvx|-%ZYf{+
z`+3_{hVEMuhgJMMU_=_qQ8CzVM<(uVKGB7s6G+TTw%opM|1rXT+9J^Qv3Op^5^3Fx
zIXWnvrw_`M!<#lw0sruI62{^EDEL0>R+5u(8ZdkswImk+SbWyQLbl`%*M(1m*|Jd>
zM+;))LMmI>(}ZlglSiL>z%UIzPfA%GlH_*>Yubq;M+BM3?ec#7>=pA@Fbnq{3yig{
z6V((E8Rw`bt54HrGAU}w$xN2QMTGT7uJ2vaZ;jznl1c!cb0)22h*z=AzBA`(%PK&`
zE8oZo$>=M}af2BZCNM-2hJ~zOCMm$^%%g$~h@CjW>aA&sKbZ0f>}v%+xi(xBsQ=*x
zgPj%Clt7BteDRzu#(vi14DVJHfy6k&3QE>GH3DuNw48Ia5bsT*0t-l?K8;cnu~#WP
zA24TUS{O7kAr{||Z4z?;DfL@Sok6W2y)!+nskHwByN2(#q!hBb)CR=2R-&zVKhXt)
zHu=-k4~E6v_w+U$SG)}d;r7ZU+ixJcf<^)*?<}q^7Z(Tky)hxWT3(w;9ch-A2qcHC
zX{UW_m`+Qcw5#+635~G+iKmV0p*|b!nar_B3i^2Dqbla#Q|CxI;6Uv^CBH4oA&MfVe7e_*w3HK70t4F6YGs^RaDIuQDv57+{KpvHWs@wUekmA-V@uy
zBM-4`1~6v*QGw^g4CCe#j~oY;t-xr2R$o}@$B2CK6lJgc(bIaIt%gxs5g@JJf6XW)
zQ}$W3yyNX3k1i5VY^gQEUV2=Mqzd~%;t_jypHCjFxvNw@twui>ytRR9;x$=`nQqcg
z2Q1M3Ukf^Fb~V4Vm#qVCB)`}WkY?oNd6^B8*3B{NP%g!)Hk0RIvx=?DAvWM7?*)#4
zmRGJa5v$snx>%XlTPaL6djiGpZX22m?
z%zA=9HRy@N^~IAw&Kok-k!h|(ql`x>QmZN(=;lvknw^C%3
z9#ztd9;y&aCP?&nB!ucI1uhXWUGP{=J
zyKJBXZamMkgs5)Zv=4u7EXp>El!yv;pG{2-Y!VuuY@0VF#QYA!faB5V<-!2lX=JM>
zSHcE)xmGVrwlo@4c#{*-G4UT!44B4~l=}%0)_S5x)kTfe`LQ{@O;i7-`tXs;nf`!`9iQpBd-Nz16~lEr(i>!Om0yE)%~lfG*V{CHMO
zxAW4$wxZ&N8EK?1Xr?=`BNnm@x8KvqUW>`POWYVrqY&3V;8WhS#I;{S%Y;T)#rtNo
z)7l7yN$!1g@X}H`QZ2Y|^*96;Q076k7Zu726&HXU#}EhDD@Z*OsnTE@sWY$2>j0})
zJpFmOnwAy$a2g8eM!i0hnxPb#w~VV*9oE?&frO2IK!rGZniA3edNQriFo*oDRw#=7
zqQr1s1c6(}te=!rB|>i36j1Jv1i(Qc9
z%wfRb(n$(5I_EqKQq0|k`&ET$q~K>T+2d9*`Zn8>ZQhE=lXrV{_hQI(g2lkLo^vLZ
zx?LPHK*f=F5->sr%$Ret1ixEl(;oC3C>nJ2brTko-p|X*hQUMfgIgwb6iXRBR9%)_
zHgSER5N3}Egekdg(@1QrZ$lL#I(!f8@-u(~wJi#UU$hOV(fJ>6c#*{t2U
zG%xFU1^JJ^*t_-~?m-DBlFzH6oFz>DPSfNv2hZ}=JpRg2p<^|<$a1u%I1iVaXwCjn
zv}}LA3AKk^;8g#mwYA*AN<+uTn~_lH4i7&s*nLmSKSmUpmt;n3e&D-Xut!wuQKPpL
zc*rH!?}*6!*zNOFL7xKwPkEaf{GF%H0#uuwI7jdyS37b$;fp8m_We#j!v?tkhPPsN
zq@IKAN%YLBT?fKD6{<)`w-lg
zxnTE+6%U)yb(xKxR~WBWEk=B_v}k)EV0*t6Q9|9`0JbfC{^4{LPp_7Afi!KdYNXGl
zb&4GJGquJobaLcMHI9-@z?nDWq#no%q3Qz)slAQFRR
zeO-p)=;Ecf!SAoW`pPj8abm&$5wv44E^w5$eHO}s>+lyuHEzdk9$H`63rx|)Vth|_
zUVRM~7e63+Ezl_B@Gz;JVJHU1wkC$veZ|@DaJFW-P++FZu+hPKD4x5U3@oVbzK~(a8;$uN3p{psYPi;=sFAU&
zI|R2a!7BmpRHc=M863n*$gxDRWeq-?Tsx&>?5c`g&Tqn5uoqD%05Rl1o1uGhMdPo1
zsID;R)A!_CemnNY>^BwU(J51M#k0N=kseVf--sgfcmUiQ{2&ZI+VH)ZEZ(C$+YsLlxm*iQ`?K23pza7%#U
zGn4$Xc6^iKkcdIcjv>I`s?tEyx{M$E1bI5!noqZmAbvu}-PfaIgfA;ED`=!4OnQK&
zXsxnyPmy2`%8jegK?09Y{8?D|(-8{NbM02|IemIEa}p-U=C)sCwPT{<#6tf~hWcE&
zwqQ2Lp9y(wA#8h@g(O%?@?se&X}V4wRtMyPHfve~)KMUtuK3`+WIi>-eeAuJW+SN<
z?1Qka6yw0GEVxAb0VJ=1U`S2cdEAQ&!i-f@Vh
z)NjXI$(LwTP(&S`d-;a@R!-*R-aJ3;|2XJKcA7&T>p6To1=9Kt^1A&-TD?vov#unQ
z0F?jWJJbiobpibgiI<`$JPU)+b0MdkM!_GYZ%hChMJ7)*%jz}VwZ!VGZrm4An
z@cHEly!=AMjCAo+fF6^TE85MdbfZHzN&zYMo{K4p_~
zGawJ5doX7d0t|~c(N_@(fETx|_wBp$<+ZJ6Vr13?v$C<{t0KI`!$QR^$LcM5kc9kl
z5_Zvnl{8JjfJSxzB(%mv^y4z=CdO{GgE*~OYQesU@;OaBYNM0=t@S5z-z8QU2gU|M
zyC1`KCX|~Jw9={bWT2#)dDMlQw
zmVidd?yt&V$U%xc?e-Chb^`3NKt@&em+CVE113SrA>m5EkiOPrOaU~yg|c=
z*La{&%ypfXE3Jazux_TRvv(#dqvqW;qydgw4K2Pi>@xU*j0X3SO7-c6JTfiGs-AQE
zYTq|}FRmeE9feh%h`4O8-3hW<#_}|el#kN_!mQ$9FsR2sy7tj_Uk=}aDBWtD=&|2(
z+|$!!Q3$@{zeWQQnVW0XMnkkz+@%ZkLfw`ehLLfDHp@P>1W26TgJ&y4Etjd7l0@UKDKZIJ?NCBm_!8GkP}Z5N^FTxih@EF1A%2)=yc*#HJ+I
zUl{Z1VR`}0uNt}hLY>>?dh09sUA5$r%So`B4G@de6;d^ni#)<*sgw&?&Ew#Q&G$%K
zE>@cxrpWb5gStc}n!6rS`a0-F%`BX(E?MVlsNv<$9R%@>h@cqD`(;$w%?}pkOt&?T
z-{*i4AWQ{(e$5N`P&V%y^6mB6T5JyV{{d$cd*#7#^!2bvBVWz4ut*cd?EsxHqMwVoMhdR3F_y0uf05<|C*gT&KYiTZ>pa
zsWFqN<>Rzhv*SCwAIdGH>>edG?T)NVWPQ!#&Y3~Ulo>MZJX}Ls4-M`HA?UbO|59wY8$J~u)b|_7Pb+IW2okFe&m9?iaN?*WL*0(#gT)B}Y~i!}CMJQqHTh|QAOL`unWuO#hn
zpp;l5ZDY~;H+bXX#10p9=%hbnkWv1a1X0h>znwOl-*t8yEG8yyPmGU!{maAg)M^ab
znV{Lv72_x3_yZl_Il9Z`nmt|l4(jZR8cM*uX<4!HVh?F+xT+Le&Y0nN7iMC+XUzFpcpELF5IN^_Dy
zo)JWg-P(_tk!@`;51WUjLyAhAN5Nxm9L=+LkUk4G!
za%L{}l>e&*!1B$xxWM5U`A7Z{dORag^(#_$|BTq}V6&%+X{5F+n>5?f<|3fR(qoz{
z`8Z8hanD^n;gz=*Qz5ZPyk$e{`j|Rf8vk8^tc2VdNRHkL#sMFf8$X3TmGz++vzx=`
zJ&pb7-g1j=TEK4I2j=mDiJy0^Lk3#!PK(|jwV{ANM)Y&@7V7gLtyG=00V@l)@W#zM
zs16Iu)pWjsk5Ua2nB2AEW$?Wp?7pep2ui;7RlOWMWBncoJ71cN!7d=&PUsvi
z-^SDEdD!rmByhE>^YdBQj8J*L0isqYJPbpk2Wo`sIOw_oV{
z<@tDN5rT-Y1Y9vO!b+&^-V=i`z*Z|9AHfJ)HejocpE$F^fN7sge#X*T)uXdu6+a5|
z4mr<8McYYQ(Gahx&Vj@&sn2_42Ab*?^@@386Z6vl)hI=pSI}v4t=#yd8$)^
z41@f8tz32TpRmDJ2#UeD5m%&Y9fYJ$A0`GW@rnAeXY}8Ib^BBWgis@4coIfi
z@xRPQXJgm@V0H^i`>Qa@tcgAQNUV7vSvN-pj^s|mFc~MpFkdw7>@n|SxhC7g3jIj-
z{q%+kSBu3XLMA;zv_C!Q)V{j7q;2bHV!%^oec-9RYek?=S@V?tBnRI;N2?LVZ~OM6%Q$d&RCJBHeqv~d4RIPN
zzi5hg6k&vUVw$cJ?A7=jpPk8bfa1UZy=>vs5cD}ATxMLS%MCfyw1W+a@{XY2teR;X
zsj6BOBu&PgLpM2E9(U{Sn$cN{{2|EBK}{1LE7z2A8tJJ;XY@$U_ej`HtA6;D9P;J&
zkewkED&!Rn!3jatAI5m`mqg#0gsg{Yw%9Qit^phx_7;5AUfcOF&J)dW-~Gg*Y`My2
zQ1>osDnMjaY>pW6fk{+0drPJXB;&H)dsSg?Hrj={)_43v-r__xZRRn{X}R(5U|dKl
zu1x>52sjC>*&kTCc(rmX7G&7ivq)9r
zp2Q76%K}kb_5_cq#c#Ji;NR+&yYJ}VcfWu}V}7|+WqxoNJ5FMg|9!fBcd&vS0Sm&$
z`HzzBKJtRHI|MNkI)lULQHZ`7P;!&a5m=J=dPW3>%|*M;kWKw_pjBKeCa8@ftkIw-
zN%0M*}6I|JbXuHktv)o@+=sAkm
zdk`SejZD{s{o=)RVXD=?qfftd7YQ-exWGHTOOwM@{fxHeHXKiKCY_WGO=_9tga$WA
zdAJ1=#xzKAv>zYxpY`BLsq=o`63<|&)Ay-B$pXtwLih&ulr$!yrADnyLA
zKKJ*Ke7*PP${J~2*n_tPM$B7^De0TrjqC2GZKhoQQ8wNzGJFeVgK$bpXufK?%EqTN
z15*x}nCwH2oc@&v`W&c6S>c%)ak1l?_&cnS7~1$y(VeOQe8olhP@C+DY*8Us?eGe0
zP>^ikk^9|m{Lv}H@^dz1c)_{?!)bLvfmsu_Nw788YMpY^NdGeTWvyZ
zh4&;uoVz}AH)lqUUz&$>zoI6H&Lx_0M^Lw5i~cd<6cxl;TXBcd=TN>&I18=yY@?QJ
z)$lu|cg>9nQTN+mv_YDagExUq=EU!GwG|($N>Ay~)dP{?&Ws})ri*vu)O(oKz_F^h
z8^&=xMXg#0L+F_G7;bZkpv3p}YOx*(`~6l+#<>+t!P%04^*)OG1jF#Jw@xqv=G8Z`
z0v4}}^6teNVKDNjBn;sfz9_(12^ht&tbU(QzvI<2UjW9Q@16;k^OFJR{j>oU8euI9
za4!MxCBr+oV23TG<1fkdhd;tFAqh@Ha}#?LO&)<|{CR(#i3er)_o3NVPAIFUdtgDU
z3f1_nQD;wI_r*7Tc+c_bjK_4jdLZWH9fYfC0$!|m>p8xxHN?M8C%Mahkm77Y2sHcv
zu9(uMBj`H~WaMU?1k`)%A>D^(%aHA7KzOqsjMQ^uFO-*mzJ;kdx+v5sSsicwPaJ6;6SaRfCaix+W*OK+*uN;*w#?k&LFFh2D)sg4E&nsnzSVKExDu6bWA=lor{4erd%}CffJX9veEvh>z%e
z6(y7beF3zWIp8Kg1J(GN8#JE=YQ>J5spsTqOArt!tOfKt;wJ0Y6rQI>)r+%{`NH%i
zvP7`!rVxtUB+>p!7e#W$3@{a%g5$XG4^zMdE~=$r5DBSD!_CFJYy4@uV4x&p7H5)8
z_Zq}OqC|q%jyB7oHh^fktmV1Toz&!Wrk2o*Zg{|uKfTpc)QSvMa=6jQvw%GKH@j_V
z!L7YgLA*KoJ3;DS9?yf(IIm1J7`rogIPb6fis*^ZwD##G8TZa+N`6T8@3cJd`%eGl
zGbXZLBqBh^b=STk!3270qW5pxC$JB79J22M`e2i+@hew~K-7$$7{gRuaWadfFQbnj
ztRE>$;0~<{!aI)Ad_*3clb?cJEZACJ^nVfFlQgKO%#Gy|59o2*iJF6~_gD3gxXTje~Dwu!J(Ly8NBTGZlgONxy
zO#~ZYu&c`t;Y9keAPygN2-nA*JQr=i^7`OiqF}FSz#^ZF8?mIXO3nkx#`hIXcTxix
zGN01PbTfxOGOp*@kkVIo@sXGy*t7-&2l4_#h3HJ_MX>|1Xnem*hK^}KH{+Cw>VT^>
zV7Tmo=vJAjiK_WW=!d4i^F^i3ozwxqt{OJqy`a0sPvt9SgN8Y$71ZS8ji#9&zGRsh
z!A%V}HLX~)dZZwGY~q29GpDhp%zH_kt2znpSFD)Kwq5eW*IsHSv|pus)P
zgPq+2fDN!AiV3O)EYt|($Uz-@nYSlK5HbjoD)D-JZlR1!2!~-@gZEj$rJFUtkE$0=
z96Lx$CY;H3UnQ6HPvl=S$`zg%xO)Jymp;+D|4+qlzl{Wzz~18$4S8jl@dUSAU-zUi
zaUU(ez1~WLogHQxq)V`9A3J+|)?&vznO@GrN9g+}Zh*u7+29G@tZwiC#_-M2lRkh2
z>Yq`~4n}Hl*z7$ut-#XAT#t$By2^t6TJPe+xG*}3P{>dRPKvmx*9fwRMY5DaO-8G!
zB>oJN5u(9jCmFARxP+$&k^mL=1^wbp{te_YtC|@BOVm$$JBMOwcbjpW$GI0-%QjavW36iE9l&kL-VG`
zKq!NV&dnI3a<~|R9snOxmwkqgjiAg`gkb3lW2J^Thc8BW`+$g0V<{ZP2Cfm(0ci9b
z?+~DX_12jx$Hhh#%JJ6w<|u$M4kdBRJ
zAA)zDlK3t+F5-{&Ub2G;2&J4p+ox3)+2LB{kH***GVs2J?dmPwXp??x)7%~A9=gPu
zqCd|V5V10F>$sfU()jH4f{RLOKx1D|ib4+>5u^`6r~QwM&ClBYjH}OnhE}20xJ)o!
zt`oR2#9D02)!ood#MA@!Pjucj2Z6wZ&71++w_*(X!Onc8x_zuN7r9geCR#qrsRlBk?dM2X7S7
z+xIgTuI*T9AMIFZQQ@gh4!W-WvHY_{vpA1FuciLkd!t-f0wC7X0^PY%OOzx;%beXc
z)d^XBKJWUinOZSsMvDSzp1QP#a7z}fnc3ey2Cpu&E>Hv=I>X`7
zg*5VM?BDWBiBwwue7waCt1;_$Nfi@(#n7Tx^fTF8U5S=Hjq>~ZomQb@8Ta2<|ChyQ
z*ZRHzW~0>{`2cVg^9hEq+EwQV$^PN(s43#9jNF5s23XvVKC*wSg?QH(nvQbke*AE&
zqAO!&mOsK-`7yr_1v~6ho1G`x)cE~Z2^BN1bAj`=R0I3)%%1xo9`Orc@2N*z0^3-9
zj19Pjz@xv|?-%vrmir+3ID=OGR7W`z2jvyzHVfQD2C2%Hz<@
zT?M&~NV(vnoJ_ehBQ4M`@W0FommwT1EHwe_yS~MHq_cPUm2_o4`0T9_7*}nFWi1)0
zCkYZ)2$KVOxv%X5$Jj%Fk{R+ErEznm;Q-sFi<#GxVtuXwZy5uwDic+t
zQ>$on-WUnrL*L*Gf+^{eZ`&jdL@Yn=b&Kn14fATAoHUgEEhBU~c)II1+;xR-+O=tp
z+sLw;_%7yZOEsj>EpAKfKWk4-8cz`VGnw^KuCN0SMX-TS<$~D5o$pAWgI1NLE_l`g
zK>L&CNl{reO2c%O>Z5{%Tb=1yfOR-;P%|H~tXt1>Qxa6zL9J81|44jh(@08a!z(AMH@THTu)#e`;lO260S
z`_@RfQtPo-(q^mGycK8N7sdyY=^&HjW}I(2o-b?SB|!m(T}1i_k-T?>@8w=dC)GC3
z0HGq*I;8|zhw)Vfzjp0Bml;zUPdaD&4q}>fOldQ|5v0K^qwTfMTZ@Uyj{nEUMm-BR
z;zXAfqKbeqI%4D;A{}%Ar2{L#%D($|r0xBmE=p6*KO4VW?L%E3A7FxnhuS6S2ySA=
zpbeLJz~PsBn*Zr#8PzECj&KO`D{x##`9c*565J!&*$sPKayWgR(2x{S?I(yUI`zqS
zWi7H{*W`0V0Slbt_9>+^T-fDmTBK)u%ZFv(I(H`GgcWko%$6wYIjPl)ub|#|uhoCQ
zsgOiIzLVm-#cwc7P~zQB7Zcgu^+Qjy`61L|vWaZCXr7z%mGAVseES%A!~6jcQ+8T|
z>GCr-k+`(>9*fgnBWH(
zlY47LA9wdXkd=LO*q+=RXnINKVl4s~Dp{`D;~K-kr*90Ehr`o*2|f
z;yQn4@SguFB9VWMMYYDuXWfAj415-@KixNPO4ILhK~hOv#J_m%}%+(9;kcl?pE
zJbeNF8!2M~1erxca_Tvce&HgQHqF#TS}s;AgsyD7sf9z>?roZ0lHlG(ne@A;6bYaVKzJw^E1c+^&d$tmuV%9pzJ6cI
z)|B#^sBcPxz8T%$$?3dhrh$9oPvg%cwHhvzIaOxEEf6maQ@-iHb?%TLg*IJGUk*K9
zU%(-7Mf`UrNqJO>;{F_ipGC!Yw{TIH^iJAto;V~(DJj^BC~xpUy4q*a0*sZ&WLO23
zzaAtk>->kuPPvr$w@=f$O5@M8=O5
zUo%#a98u3dvBPURo0>6D^?Z4Ul03i5=kgf91AgXINTOwI19kUU`qFk8WzLV^rS%
z%=x>HJGFoD3%%Bw6j75CA?*0QEWLWt8KtBXBhC^>i<0ND`{E5zquC|uR-HW0AI2^=
zQ*Ima=5R*Vm`!2-7aa$?;A_4gb~lxa3HdL*!p`c2JiK?V1B2BEXO~%-@AhE3^;;}|
zBC<*roccsroc~m+t>CRXW#9VIa@n~2prbB00qOBPEb58D;+oc#@FO{l!WE|uh4mic
z{d$iMf9nQ4j#L~#w+^Sid4m)t3SZ(GpreW9IQp<5o>Q6=K&9%$@uxpctYT3dpZ!^g
zF^zGLYDeXQEFF8*po&%rK0NUQ%|3Oy-6G{YQyO0d3R&7I@KxppFyl!+?DXxRI)$<9
z+YO-N1JYny9-I~xteci{klY1P86Ef+?JbZI|P70!jN?1jvKGt5F?ei5L
z{E|QGnikhWs~-EsbP+p3O-=+0jJO?+p3_{YtY#R~vw^&xl&4U!u2zXGhmA&J?$kr#p`w%{O*)w8NVu
z{vDQ(8$Y4#c~M{2n)pzxskOhbM#n)Ske)i
z5Vo0@*a;GBl)oTJMPgcFJG`QyTd7`aRRfhpiiSPTBbk?oOKIMz_ziQDT<7oM$UPJc
z)SLv1ak;G_mg28eVM62z`pS1fy~_|y>d%EAFMn`LO8Kp|RsGayuS*>yJfYEQYGRR;
ze?6N#ph=ImgL)rF7ek1JMc%V?k1!jVlgHv_zzM(QOCnO`uktI5=WPh@mhsC=*aI1l
z|Kmfzvf&cRUU!m06!j()>NHH`pd63$?EgNqBK;G-b>N}5`;etPPT0M)Dv~SjR;djU
zz8j48y-u1!r0&N%k;kG0)=
zMcCW(=V&nG$TV7!5w+#;Wxci*fYRs>U&*bm9u0+!bx3huYtj$Tl5~WxnHt(0kiQ{2
zWaP^mm>JjTr`R7{uc>aXF_&rDLU6xmYHEJj^FF>05;imAt92B<^)(DO|6=&kw!60X
zmF8H#xtH(k&I$45f$bRL(Mef#QI@l82Waj%HYq6`TLS(AsFRPSri&lS)!y|8mT1}@hoi{RQ?e@=36NL96PPiF$xk>Zr5pDT^q4+(ni{7Ij8duwwB>p8((-am*DJb
zEUi!4We$l8`!A+b{@91P_fC95ss1B|hCe3}ydw3kX2b=V6*5+(^%@Gvm4#@5gs7;B
z_zV#{zpC6Ea$c>YUW8V85Gom2I0$h~ce3d-I_7#0pQ$eXiAUj#qd~tHiHlwpfvj13
znb{d&Kl~0gxrRs4S!)d*K93?>59d5g^iHAQ&OVB2J##yfrSFH7qvxp(M*fik-sYZ0J+0*fxFpX^h)qWH%EwT+o`qbjDYS5IYB~Y}MPZ
z7(95rqbVTXuNrIDn%}es9_-E(gXq#fJii<(5X)(p5;@%dA=S0HMRe@>T^PIJUA&Re
z4%G+8C`Td_pV8j^X02sBW;W-3t5ySajW>J4nK$loHZ+vYsV$B?E7APtW4QFQ^nKj-@
ziG6opY%by_NYoK~9XmFzKS?bq%*lHbpl86rw|kdz!z$L7{HXF8&WA+tW|^0n&Z{hR
zI~0c>eqhzEfQAJeEPC}MS=kEjt*?}AC|6bB*3L0K>OI(rvrbT5E_)_S
z&T!bE*tIcWx=I_K&Cz2a%`CefWawyN;PjZ^2nUH|oVe3FmXz;A;pBlS9lHz|!a`(U
z=F#%YSP}7$tzqf-%9wXIkJu5muWE-fGbFmvf_KvhA|dCwEMwj$S~QDsQ(S-wKZibj
zuO#nOLOwx8wVvZ3J5=PCre9r!qqmO=*2AQtc2KVFDl#2JSCaGTgENI4|M}OG6U>H8
z)QI)n4?91oh)i~(W`B-~cZZWAwL}AfTf%$(LEFrboHq}5M}a0oL}%bJ=|_hSU&)RL
z@Bh^Tuu%7x#T!=M8vSLZ9Nslqz(fyXfATRB9L;)k1UcENrRq;(9^kMXVmIF*#6PKQFn9cEh7>@kZP>Xjil4n_+OVkve?^{n|k
zm?aukzSYe`FRNNO5MlM)sLNo8gZ}uWCYtI0q3W%pqW-?`VFLs~VnA9tC8fKE5-I7H
zZbq6RMY_AYyGvS;9%1MX>FylzdHMdV?^?g--&r$vZk~J2KKtzJWD;8zwV*1G+~k*z
zU-@~xwd97olt_M=yLLs!^T$W*%BuAnhj+iOiS8OZdeX}iZP02hHFXcYC1fRM7pUIO
z5UblhOj0vzOZKPlyO5_=RORd!6QtJa+y!`7@riVRdbqi%M_`F0m6g`#mYWmEwrtsz
zl>#=Z<+64c`tzd4pK!kCb5h=T8M)SNCyO9*VX3&HUIy9SMXn974Qk}kL+WPP&A+@1o*-M
zlKBm5e96DR{l72C$y*Ym06&!&Q2gf-1Qa>^_qPXX3^^znTFh5pneo$4$)gW0_Wx-w
z{E#ag_d*#Q1=#90IlD$hdaP_ou+3tV+SyPoX4ieiE(V|_prn+MCG`)(iZI6{4dM!e
z2YcA#Tdmd`kWe;3tP;=?&=pg6IT{?V>jCB8XcyQjtjX~3fne}PZKe;)zi~+1A|qdX
zd!{SRjIaCVod2#`6ySUxyc0Yn{%?BT0QwT%FAU5*;hT<#?;%3eR0Mi58(oj!uIM(P
z%XirSYXePq)cYsog%=$kUa21IjKbWBl(XTWId2o31Q<(!)%b3NHdiLmYkk_}rG(Ed
zG=W}O1WC3w3^{k-*aG-JCtynm{2g%rh#4I$lh@Hu^BxBEv7Tm4ja8veo7e@(6@6B)
zGxIXG-uDdI(X&@|Y^gBi)%K!XKu6no{;HJ3XPp9=a9N7D<5Z$p&xd|f0ZObtQH{>;
zHO^=e^J_(V2C%`x#&$0Udwqi+oqdK3DA+?L>+u{M9E`PE7OYCP^39s8$)-+Dl0Z#u
ztMf*{1wPPl@3pM22!416Cqo{)-eyw1+tGzQ7HWNFN1vb^h>>P(r*0^X3k9ew#pOvof;DBK70V&b>ENC39+
zOZmRwqj{ENej&56o(H76r(9i&KkH{61|pZYc|w48#-pHx|(pg?kb;_EG_r
zi073)U)yNeg)3vn=XTcdvrgGiMKBrh
zkUs#!)2hPnOFupR%6>DIOD(rfa3nga>g&{eb6GutQQ2r>?AXd^Zm+DZZE(O>X#ziZ
zJ}2x+adUAFBwG1b4g6!K|I$bR$3y=m**3iWI?9eR>$xpyHsWYOf!jMpYzj))!I@%3vWEw|hm&fE4pK!UBS^J;V4}-Y<5tZxtYz8DZ)?AG68hht%;p=5;
z(X-#83~tNNwu3og>N|_27uWNP?TZOCfA?;`~Xuo&Q2I~mU_M1ncq`C
z+`sHHuqgBfZ2&5*reA*l^RCPAajiu(*6pbSMF1b3qs2UUiBKmZB~3g=bdtyP`|g@J
zZtJ`8c8Jgst_gh=0a)+%ku*S~pc6sgv!(u~!j%@hujkJ|k?Em6{*>=4w}kTd?$Je`
z>Ny1Bq7HUe(sZkCq1Q*kXPlyUPL6xt#}E$^zUWX&t8X-dK+IId{-(}}!ch|K2
zLi&cmqEcLR_l+Dz*aq85UTcP_WlocNUAy)#z}&Y0MXWe&nPj9mdA;wo)&J(K>6`oG
zMjK@5YWRDlV{{Aa1MXP7YFnBC{u(0w>8e>_!f^5@Kwk$EKetsVU6r&TVG({(1Ut?+
zt1Z>Pad13f!%ydasxhs8TffW^7EcGs>58+Ki7hdE^%!8To967B8H{8pWa909xj<9w
z|1O+yoRW$>t15yu{?p+V+FFQ*vT^?rPO5EZU0I&Mmal@>_Fd1U|Mqg+S#P!cuBear
zN45urJt;-{4CN2xUkY*;Z50xZr=#6FqX>rs^f@0DA8U;q2v+3#p&GS-bmc)E>nj-
zG=H=;&0}hVZrs>&>J=?SL~uiTZ;pMg$lVStvGsYoCg(@ro?waG7;-i0F>TAqHh)3>
zHx^YfBv^mQ45zhL=M0dlj}Au)?+F}Zj|k(540RR{=6fVI
zXrdz`cp8~2NHW1wMy-Ftx4Wo(5rMvjm-lX7cg=%>$G*X~rs1C%l601jR;il@UOvq=
z!_a*%16yIMLZ0iU^fd4O+nE^R6BOVt1rbVeXEHwXsPfarr!4QbWzFzg9YEkc>?bA^>@vwu#Q@I7N&Fq&8N
zgA^j`x}z)iTOZV1TAH@4Wo#OXjf33q74ST)WG5B;EipemM0ZUYo-#MS&U5+B*9eOX;Akqkas$yO!~|IuYfK
z?{t87TsLGOB8JZ6l^)NVqA^?}L=pK)%?+4H7dtyzOJ+KQt#tot{{b1P*PABC0`G=<
z{oEh>pVC8PNl@+e@LKLYCS-83XYIA%7~{RR0^S
zNY+k`1A5q5xR&DC6O6TF#rk{Hc5H3cW=R+kwjz4o#$va7qYtt9@u_}W;h!1+U}x7h
z#uzF{fT7>iMqjKP+r}^0&Ub=n7(tOie@ely}utW
zN*48!H-Y^>?R@_2iK!H%kqs+!bRw`FwI%tO{?9Zlwdwq1cW0_}r@+tT4Dk3*VW
zWQfP7E3fmJj#!km7@vPBWy*qAA%Znc8Hv3lHtKBE%(k+w^`j?wv`?#Wj4bz8hW1OH
zAS6jm98ZDsU>R+oEseGPk?beOjXMZe(ZVZ9byOnL8wB7J$1vI+p92mcNB-24dW3=*
z&6rpOt?%EixY^8=$qw{3z=YT&gRArs&ic|4*gEPiKOYP@ghocmA2fM_uSlz!qT3(c
zT=MsIC`B4Ihe*ml>lj*llKfDC?rlP~N18=zXr}_rTLXJ7t73lPM(A;o~Yyg#dNeZ1B%|O!B)Z39Ox_WNZCny>5rR
zlAoz4vp!I@e%+jEYJNo`L;s>x&we~!5%s_H0V<{L{fXTKss0-+UaE89YG&Ep>&L>k
zU01!9KtX1Q0E7&CoG=DQibmhowA(#zq0y0ofcT{iYvrb(2tGi*`+`deOg@Xx#g6k*
zLZlZ@JM@Eyr
z!hilPmk|isBN|BmuR7KzMe)BS3t3ISju`B5mU@;yU*X{5de*n)u?(Jzj3{ghaB;QG
zF6i9-6~$%>@2QspYTsT+A4%rBqU7uax&vwot}q}MYmFm3
zI7-fHgFZ@sZ+Ycg)aD(gv@#&jU>(gMB10k3ZEU!t%o)If;fqC|UT<--fa}ffLO3WF
zB7gw&?b>2}y2bVBxbgkVocoZh(buu?L5
zRnpMEHx~xfeG99<<{aW*FJZRmX-LbsjRX(z8FD0uIW2GVomd1Nn#Miqi%n(z1UPkU
zVFUeA8@s^&g0qVNFooY2LV(QmdV4t)6fdVYKw#!#r7GPw-j}BWKd+$aTt^W&7culu
zN6UV!^mBI$<6Yq>B|hw1H2T9!wz((`+kXZ5qC=S-_|4w;!RY0-d{8)`sUwO{mBp3?
zP+WJ71hYS&_p^f#+;6D=6%6D03}ymBFAuVcHAS0Bj8_u}9u}Tu#5pF1ugg@39k%CP
z+k6*8i^6-KGtF1qc}Zb?xJ$232d@}sfB5-fwUYAGSE64TnHOgs*R1ak&`T(q?3BUB
z3s7zyW(Du<<$z^ci1BoTzVPs3q^=Jx{&Y!1;YZCkL}J?ad5fDUd(YC;4G}0>EvjCF
zMKNEcPx2}xQlYWqX!Z1papmNt1+QCB$>Pr)*W2u1i~_p=?>DV>$PG924=5$lZU}cE
zzP)&^mJ|D=$E<;ldy^TdjQQcH*sKd@CnuFZ4f1UsKFgWa)^^Qpd{MMdG&
zx6{8HN*54w_>0A73O5gciay#48=9ux^+$nX&IUP1=Doak+U`3*@bCVf-seRyn%O7nMbEFF
zT@OikwMS2+Y}SGl?q`MVvON)v)fl}lOx4Yrr?#46HpCz4nc12k!bGMz{M8cn7E!$9
z%6qSOax(>AJb$nUA;dw@&tRR`o|#wmK%^S*hMm6)g1z4AMxGKg7TN%su%tjrw}HlE
z*qI<*YI%wnD*GUMbANoNs6*!i-GKOu?O%)AcJdv27h>xfV;(@`=k!XMBCBv_i1YTz2`0{1
zO@+|A@ZKY~WLQOOd=cVj?t`N(*TgjP-A*~gHXWVxu8((K*TGqBC>Tq|o3Dp{_Zk)d
z3@`RYc;Bt{h*~ztEO@UT%0$-=S{e9epH9jlH4<^LY`gMe{C$m31vA^w#^gy8)9MY8
zZgVBkyy3`b*Ej4jmp<0{cY=Un7+n@`K;@_*FLI{~u%+j@|L7#7o!O*vR!KUqhdLJV
zN>WRZqAENvQw7gf6R963bRfD=9YAP;cv@ccN3KVwWaeoG1$uzoUPjixsLTLN&pI$V
zX_pq2(d|&9ku#el#}=W42}i(9Au_*;f~`ez4_buB4bgm3r4;Ms&t_3mcn>xccqD&R
z3nO@%zRy@-Mss%MPz6QSeESPhdS%VM|4=^EBzd}}OLfZA^ELY(F3#0q?Ck7C5mih?
zk=2$!*L_j5>{J^S<%Jq*58w(!t$b~*FXmB(1EYDgf;ztd2k})-s~%%QWJ=SR5o3+(
zPjq39q@>su7?pd@ybg1&68NKrIZ*h^s+sUdmh)-q4eRec|0W_j(2~^Yr`z9%SAny^
z4@x(|VI@cxBlof!m*J53h5796DTS!Wr~wqrLmL|`-`W})IJ`N|QWllS9Mb})=nRnC
z43{>lugc+js=0xZ%Tf;4Q73q7r2B9)4qZm;#BFc83))Et(D
zcm*meMi-f0og~zLWv~=sPK4F8sLu)MeOZ{zA`O;dY;X86H&y=Jq4n!|YiJM&%~v5l
z*0626K~4-sv<~O!Hw&S>Z%2Ll&f7?Yn#|B-nDKx2W3mNF&zNy6J;(zRCBlE@(bN0R
z-y!AcAhRoSfXUBt_J7cZG!a@r#I`>h+z
z_g(eO#KL*`TIhr9T3uALE46=XxDpctK@|l7QC(OOxqNY=d1SdHnKQ>`Ap}W0kr=jX
zvm0AL6X!r(QHw#^W9)Bs=wW0)h-p$;n3Gu
zd3BBy@^2BCq|5n|pa%JZRotKeT$PR5H$Bzp0$hC9b$hye*k&ll909DIdB$%jNzu7b
ztP5LALycwyYM7~x@T=PLCn(BPQwyLmI!R8wSq69^GbA(BguYxzc5EU2>l^|iMpyEu
z?VKm*wUK3`(b+;S9+ndC8FKdjVvnSN-E*rd{Q)pIMznMHHIWwk6S#7?HCm->8<
zl@eo6Ys3wbpedHYF^~D^TK`SdN4I*_0uNR~C;7z$s+Mso%m`C$lRH{4~Ns4NW=U>@#H=I7mH@I0elD02218(29A|_6`F8
zcnNQ;3|9_{p1L)2b=k*pOZQKhNAPmwG(tbbmVBbkSmZWtAGz_do++6)xAiZ(-14Ym
zG$DSe^TDz(f9&r<0Bj$KBd({rL9c|y3+uMyS@FXAs5510XfjXlj|T1)b-IbKu$T#=
z=?j=?wGH)`>a0zj((Blp=@4FuSL6b%9m@^Y%3A4H)C5zy!bu^^VzmU2ucI8Yi9|VGhv891@gR?0+DH9az8z7)Tv+%l>r0wH`
zkap}fl4F8=6I@--ATjBWeS1T3&>RF-tyB4<=6WtGp4roCe%;e3d%YJ&CeL^1%#n6|
zWMt!Nn%LZ{XvCVPUyStB6e6@ybQj?TfXvn_uhnc&PVV#aR7{sHK4|g(%U=c%ig-fj
zW;ylV6Cw0|g=~+7MufIYHWDRT3DKEz(4pvfkCl8Xmg&IaTMfqVn!~=rWRCV@CVFz=
zBAZVg7ffX{IF5eekLX44tEyufF9={RJEkLu{Hp+((9Ww7#wz0ROAiZrLgBtXU9(@<)lQTyc4Fqgzi3r3
zuAQYm+uMhb|3i_nQMMtr188(;isql*E$y8Z5fy#!!3fcF&)`1SzL)dw^H90s$TxyN
z!yd98BqUNwJzOpSen~th>`KM<2SWo{;Eh@WMA)YqdO6+;AuJENiN|EqzX<~Fz7${q
zAS3z!+cq@dAXWH-WibwaCttg04BzwE`$@nn7d;B^M)xb@bU$01z4!dfFvC_jN2A4``!|%sXig?EPhM8270lSboHW^BfaImUkMtB2
zejpAAA7ScSm|s^M%(?x%$qxpte0S)oMz*C0fTJ+=b)t$qC$A3i8ui#!c`|y*Hqm8e
zth4fSl~*Ax=qKRC`o35;>q1*$j=sT9+Kt&fmfU;TYThiyUxb
zJU_<`V&(tj$mv%EGL^L{Y(rDM3UivwBxB)QB5yu|R$vF}QA`-VFmw`Kc30Q@+@5wh
z|B{dS#C63t~B(So76uy#BE%i|D&`
zgI(><$ZnL_x>e0&k{7jy;U|XW^#>;Yd%;D4`IwuIL{HeA2W+QMlZBF#fc6JFwl~Wv
zxDv8=?R81X?(K06etwMZ8$4&9852JF=kPq6H`sM~;&(5sCqZ6!(vT0n$!{C`iaJ)I
zETnmJu$d^%pU^7yVWHDfi}N6*tsK%p36nRt9dB%2{s1#LEuXSsl4m(6z5W^YS2L0=
zMJY8M8!j@@`R!h)ec0OO#Cs&Ny;FSo_}KG#KgVBxDLpyL=)0Mm(swqZRaIGu>z(44
z5`flipU-+$Sg-L9YP?1#;0khG+wO9eMBAN@o;Mm4v=Y|E*e6EKB4j9v8>FD8$VmMVB1yP8ypFi>&Rzp5LQ?
zNm@2MyW#nm*T%G9WKGat#c21d?9;&-rM=Wk42pDom#c2ACYYmgbl&1w{7e(MVxUW+Id}+>=c`NK}
zEfG)ky>#Im%zgwG$z%}d?8Tv~4wa@t>ES*g0c5Ff-;A}_DeK!wUuxgKk|0qlO@>rK
zq_+Jw(@Bcvm)O1vRnf%RpO(&`G?U@J%X@*MZt!<(c_aDM
zamCb0uG978-lDg~0FfAI+Q4G19w>|>6nw=v6q2F38eW}N=J%YTF+WgrjRbl
zH)K8Zba*3wqGr@`YVAVKYovcGTF$(}iJ0~8BEf%~u2;1a$?q1kRN`&GbSu6pTry>$
zYDJd*fy&LbYX%9`rfik9`)9#+!WqRRX;6zA9b*A{#f<-yP8beKr)bC}aj8teST(xcF
z(@ON~7_tp>!6)Jy?!}D4?xw@+GiF!I$^HwL08YnNvSBNwlCb+MjkP6L<|)4kj?tXb
z(n)>h7#}LSG-Heg%03D)h`aHnD1_GKMr=buO`;*skwjgDs|nqYTq=O9Y*+m6T~Z7U
zXhwvx$sy}1@&%o!$mN+=fBy`IvVIVS^u=PHC~6^~y2*1TV0}(lPe*bUOMWffYy^~Z
z{fY)EgNuwRGO{U74=LGv*8o!`y7b3N;8n5E7Vn5KjxkH>m2h7)hHMW>)$eUdgY>Iv
zH_pL#N;7gBgnU>xQMjWgY;Lp9e#o8*>3RJf_2K84U-r!-_r8qG^jsZ?dSz1hWY5pk
zT(}l@Il=Y8wN3AesRW&*@9rQ}>Uf2~YXm!qk%5~;#|!qd2uEVJEa#O?c)EEQCEdGj
z&j@L$O4s~VY%&`)XZDsFJcp(7`(5K&=EC9bq#R6YOa13c9bT$Qx;35`#KRu03e>j9
zpXev8ubzE%8=%a&MWm2QYGQT0xOX3*!iJO12|cX4k3j72e;mJW8e3%^hkznYgn>v<
z%6Bg>+#K1t!$t|c|Jg~1p_Dxuc&695mIb2NqGx_iL1`*;(pJrq()aTR+Hwk^C|3_v
z9G|WHivV^cHlH3RVwd8aGc<9VaEsFk;mMa608&tL$_aKFagVVP?w>7&K_aXnDIF5p
zKRTPZJ=#f?p?!~_)EF1k!dS32JdAp3#YJM!rU##2;1zKOPDv7)
zNoPm*X7-~aZmg}{D*1Y_#I(2dGodl~P3P%{G=*_Xu4T_JUvP6Z&3rzcXZk0=%&2qx
zo1y{I$tMks>&ta_K>t_sz8QFf;-X3=zAAUhNQ*J1#E5asM`|u)#Z&^PG@w8xPFhQ1
zu{+5RRYX%ezY(SzuRG>cPq)v4x}c&Szn0
zjG`WRx1$NUx^iO!kcn-f<)I9jN#<2ZB%&hfwD?31cH!Um^izTMIr3oVVE@&^gze|x
z34PP3^Z&xnT+Kg@c4cHCvwl+pVJ5(558jB#Fa%Z0jDCNyx>A)*9P1^X`*?$}k-RL=
zg^q~-wQBMn#-4Ql;vR>gxXb@4V&)M_1QbQvi>iR+<$T7pVTbA)82mMzgxnvU}G2vCvpO)dw^
z{tpDey3$%>@YB2&4ldsCo2z9>2lrJ=FQ~p;MG2DCfOD1ou2E&s(yYY(wO6V5o_Qne
zb5dPIL?LZ#$lzM!W#!CRhUEgQp2VzrtOok)>Bbq&JDCqeNZ;LT^BS$+uicKKT0ul-
z)n5BNlsla@_72BXzM%^#nUEDgo_?d2@!T;dyf9C@uZr(?x8GZ=H_AY;pza~&UgzH@
zm@Ku9F#jz0qGVGd5Hf-P=3N`x202FiMNIaevB_ugCBL}ER82_jFB{ba-{>u`H&u$=
zO>IU49>HkQGsAb;C&Ij=olRhECU&XLHYv65mCKw|m>f4DSj
zaj=`xB8Z7#Ka!cpbIfw@J2&x3#9veGhxW%iP*a?WO=)?pe8v967XTNq@EVh%_E7&z
z742buJex!)BSm~CA00IFtwtp2w}{G8Q~4Cy{fzt46cQ@PZtafwsjfEfh8KIb>ATt2
zKiE%KkEa>K|A;;tnj|vam*IaW#gSuOxo23j%Oz791wVr;p`yo5DLKl94&7HMHm|Ba!KGf
zp-0T~ynoku`9fXK?peT%ntb4`uL^Xzj4*jf`iHT1quv+jO$FtMQR*n4;CTuW`-^w#
zHX-U8O8K=l2Ipj8PO{c4eY5EcOrhWziZlPeE{ham3^r5mwyTCiEhW@yFcv?|s@r5l
zE1LB1vohNaiQaanzq}KCbt)u6BoYJXb|o1kPP={s66WsEjav3`8+NUE19mT+>^eQb
zH?(t;@+~tsq0+a_zh%Z0zi}tQ4-O`c^qjCmyDiz#hE=l1~Aw(+t08ZWgem3kCIn?j%qG1{&1+&ZRRu&kgbkxix
z)uZD`X#MlWaWPkufxl=XuEC0#x7LKdQ?`j_q4f~bp$qrC*sJbvXoi7m>wQ$$7xC8LXq3A
z8CGC5B#d^MijlpVCPhEOY}K=RZ=VX6G`QDEP(1AhS6QkQS*>)sLq(`YXh~^U-4-a9
zeiQ7Qu#GHSKfq(h${;CgG2OMtX4Mfc!!c`O3(A|_a}c)ojLsp({oDqzW*^^rI%U0$
zwMJPRId0(9$U(?ZuAK$K2H{b-FkzbvbbU;@vF%1zg3!Vv?;Mc6!T4`|+kKkZwTwIR
z&tG;j1UEA75~aT1-=n)#T;1YG4Cqi}i20m3!CS_dco!4H<70O^Uayw7bnVqq###;9
zMG;pXWDw-6{moU;P3%jv9t-g@4KKf*im~Ks&)xiUN4mXefju~q
z^1U0{)M%&|&kf|FxVO3^iLS5MvYlpVMYX~%b&{49Y1AdamC_cQzKAv#6V=J3nq10|
zBGB_Pso=dy$}afAUv6nBe<~eXH|@NBMrSDsrSA9CL)ARf?(?R-(vh1F_*!#vXu(Ae
zvXfr&){+-psK%+9(~=_ck24ao2z{g$1(ndWUq6#Zq+w5c_dh)Q36fhi)zER?QZz>xL{|gC7mN=~lql^3|!YV;u%>OAl%1Nts?|WRzl-&j%quxg^|be!dMM
zI!v}wIR#`BXl<*uSuZRzG^oidu@lJ|QtAaTcNKl;_22m_*#;SnlUysk(b
zV*MW5YnJPi1D*Oh+4ka{KgA7Fh4v$=L~GH!3Sbu;`P@wAANJXVk4Jv|Y-|`ouXz=(
zNQoF*Yo`>QSEl8Po~f)!l+o3CEspH$K%%60!(KwFdR`_HDP3TNh{t984eDRe5`+
z+t^$E;WmuzvWsfC(URX5lL~w6aa8K5L3P)b3H>7D>UmN=ZO$CW$r2dc>J!_$1f0o7
z)oo4MGiJolg6qLhp^hxr-Q0t*E=}HXJZ2J|9MpvK-rm|iG>I0IIBkWQN?VVsXI^o+
z9ty{)#2@oMFDhEFb!PKMl;?3_BD_+s7VC@iF#}koPpa7+R7ToqHfZ{}+202jFgWuA
zQ>4?D=FN2Gt&uD7^T(ln_}hfMQuODM@3v?S$tsR$o)8AB
zL_UkHJJtG={2s6BZZ;4IhT;&rzCy<$4)(y-guA^>7^ngJ&~i%zG;Gj8tRayB4fi1o
z9Z+w9T<5fAE*8=N2n)@nZK$g~kK><#O4wAbYy%zMGe%=t`n(w;
zKOue}mSikIBc-D9hug|y+@cWm%Do3`EAuYtS!ejwhQq8tWpEtVowdf!+W^b${3<
z(uMpZg^!X+LCYlYLXN}$j?Z1G{1H|*kqfyGEc3`E@|^S*JUJ4;x`#>Va)|z&0Yh?}
z3TzrqnD`dU&Jkx*X(dAmJ}O&%&q}46H>TTc7tvGqhiy$8d^_7K#~Zn5YIjD3NknXh
zKh|MnQlH+*&UyUmyKH?s8|bhk1$&>~SUZW5%J4A>p4rMz!v9@XAVh0l*vF___`-L(
zNZ4&7@AQvqOBd}FRcU-qZ*$K1Hm;vAKR?-~(2DDJ=YX)u+I&izaS;f_0{RMCs}lmv
zVPL#g(2{1`&YPL(tNNj9-{!=hu1}R~dvM6qaFU2WiWwMQxQ7`!FS6F5@!2VfW02k*
z_mZf+%-c^?IWkh!Yp*aq3w6~94=%0jcAKXa@S_0o7H2+|sej$KWqcnX{O#kMhWTu-
zQy=vkq&YsVJRY*XecJfoEa(bxP~|mWoGnE5-Aalaj{SlkTPZS16(L0t{w*=#HK-P+
zGzmI&s~FG#j<ybrB!Ah4@(-Ew|)2e}R7vIE}VhXI%7t
z5<68f$?5fkH3>vqk9XsEF!WSb&(LEP%FFKK!V>m6IcN$-&5CT?!ctBRW^i@6rt4VJ
zw#Ef9A17SD6&@tYrxHUFjj5^NL=I!|Y(MgkYgK6+AxYBtxQg8$5wXE`cN}QMM;w&v
z4bJ&4;2IE5lXXgzFOGe$#>MD$x_0!@{v~C=Br&G;
zsD7rF3}1WYU&$)7{ZOlUrhHmIx)sl*Ju!l5oAMw~Nw|48c
z8~1Rdlos$h=*dWE{#4~Bmq)TGW$BiuF|0MrOkOdZ9)$`aj
z0(culnA`A%9tL{O{3QP|xBxovGy<-n6*f5->XkxF__W!lZXvd}Ow_smB4_4{DMCtV
z3YpYCiLsez_r@f2vM0fVMwrMF{L6~F?_8uxr`|@z-E~n{N|GhML;e0Y#+mG**pJs)
z2gw~9X<;eTvnO+X3Qre!>8#W!t5sn0w##c|siU#?VXNG=Me|^mP%1-eRx$URG8G)h
zFKXUI(lUH_sS!3qEzWY~jw6=@&Er`fPx3Uk7pW6&!p2LoY-Bd+86<6JbW|>k#pS_@Y7&p(yNgvm-rbWknvSwPq
zSLf!Id7%}Cfv3$TcO1ebWNWL#%#@~!pSK)$RjYU!j#zX0GDI&Y)-3MBLd5=TnE-4d5WI1T)V;bos!uU^A4|rEOEVqBlg!PRMzA9
z--9!j{)vo0CG!sqzz`k%NBBdsn}u8LChuz+b2U=e4c<^4iZT
zoZ!dBYex3ws7Cb;WxWe|mLvgw|8|m&;UjK9^#<_4NfOj7(@d9}8_aUZ?E35jzk^)|
zS$C&cMZ+kQnyeNM_F`w|(nqNr+NJw6)7#a#q{cuXf&S2o)3
zQPFSG4$3Y+SF|fmIQqs+X61WCQ>)XQX$PFWiUm{VYtzVg7p?K-H2J$cwrys-kZr6<
zHS+PjxEa>o@h3cdhebF>WJYh(6)m-Oo_TTg`m-stXdc-*L6+*L?AS{QsKRI+nUQ_c
z#wkmQ;;L<(GMjBzunPg1BXQ8|XD@~;C(d+T{QDgLpG!a356#W#^3B4vl6NlV2Cc3U
zPU}aw)cLHQ{9Q|FV8{OKd+P(wxS|ij+;}9J!@*!*vlfo0;kiKgOw%lz0bja&Zb-OC
zIbjmR@#Wy@_iTC?o_JNuSluUtAyN&va?{&kr6!wpV(518uULS?2$n#{N+xuxRJI^o
zSH|`;!DoHZ;dzH5f0rp)XK|N%6r1WH%O$uvmx_2HLeI1n^!NJcMm25P&y4R~&v@iXCW
zINCW&c>f<$qoD5odLo={X34YZM;oiY+Z|8ikA2?q4LgXW
z%D0^|bu&$!LuM`tOIsMS5x7bQ&hQAOwlC(>-e)a=&M!hw>=hI?$KQcBiU9Equxai0
z1}$*HyqWcUU_0Hg3SGO98B`ys{2a+Cfw9ibH^Ic~Tvw?#%r}x@;Q~XhK8C>|15(eW
z77OhP{rH8M%Y5|ohj6opr2H-m)xK~IFTcT9+jUit_Bl*c}txU-V0}9k+W5)6HyTl|sO)W42
zHgGje!0ML?MqS$2zN1reJd(52&fd}GLpsZTcR#dcGT>X%3sGmZ2)rSko8k~YY2eXf
zxzie`kOO_NexH^z#Zk+{`ACbVng_7!1Kz?Ph3K;rUub#!={hve{$m=u)2A8d@W3xZ`~wtxtc
zncPeW6>J;6l{dCFPO0J=`3>fH*fg-c9G2Htv}Nvg^`{x%yD8*Y*A-suA*&Po7Bx%M
zlkWO4D|xZ8C)WQ@u}Vl5!!KE6@>H1O1ZxF%reu$HUSFNJ#%#0QJPrn-srTT^JhlJh0yG`BbBA%sfVP65cY>gG
zSHnR^hOFclrzFU?t*xFYAKz#BpOA&@F3!aF_pWcCxl(>&d~KMF?6;|mE93s%-PJuJ
zh7YIPtHqxJ^+l=(a&1QUeWsRlg5cXAqmi|dhhN9-%S-2HiL*2Sp}jmpfmasiCzX~R
zTG04>YYglF-u5!q)Tk-IoQlf0GUmZmqN={(Pm{uD@IjN^{JTQUfm_*0-DLu!ySTQa
zkxfz9eydPw{}vl;pJ!MyiGG)Qt$3i
zK}vf9MH5w7hV^aYfRdPAu^`gDL1K#1B$Kq_H21;;m)kmODc(dlqJ7d
z!(zKBJ2aHwj-vqKt#4B=&5+Ius8qb2tb6rW1?vqNAbtBY`d_bXLAZT2-fig3Y)@2f
zUp?FIvh3vEe?}m7S_l?X_YiX3S<4ud|t6}aAn^^bUfcH+|i(8eU7
zIob-l;OMb7PRi2ahv+9?1g5`Y^x4j$ov^teirsI8egcY(8v~95#@^KKQaD%seY;9;
zMcQN}i?dFbX9GaUrdPKc;E`d8yd{Uq#!YTSJHy(-C}F4u=+9NX8F3_o4ZV1HMb}%=
zzD-S7tDcOF{gPrRA?&C4t7optl;<-1BiN@-I~ojkBP)ujM~n4v@ILk3$MAP%BwUb1
zHXS{gjEc#!EIuDDf7u#4XU7lGN=|w?s_pTDVKoK1@7dE6IzcnfYt6N1K$h|5lnTm8
zYTi_|`6x-wzvc4T@cPK)F|iRs6QMPtd{@PpOh$?T>#M*EtQ8hA@ivIhHZAb&gcP)k
zePZ{1ngDpg5d2*$>i0a{YT4m)Scq5FhO#_+-WG58$z;MC3q48jqRe-pHw03{=Lqre
zXICV(w*EiOK~T-6=^A6q`14gQ{3IPxVyVM6!+i`!Fh8(
z-}U{-&m?E>J!fXknl-Z%36GmYUD-_4+;6h(X?vNT)l}5?AUIh*Nm6wmdYkh9wz*8t
z>tY3{nca&9s#7>oJf5jDLE8h@P=W|kkw@Q;#`v7
z*I0tp%|*e%+duyo2&m2J<2JJ8q5)TQy{d3)2NnAZ`aGELBlb+Q%2T;8N+D7Q^U@~S~yhchA@LIkX8_GV@6DMZ0E)Nxy!Q=6Kj%1ICN2oU_@Fx
zF*w)=&u)Jwh}KK|^uovsKRpejxWfL2d}wdjF66WPy-k0A&fm7Cv^L1X_uGF9IiIBv
z?Bmd-9eJ7AD xhtIZ+&RIyAB%*jh@=n_1h51u#EAo
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 |