Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-05-22 21:08:22 +00:00
parent 1cf95147ea
commit 1547279331
24 changed files with 95 additions and 49 deletions

View file

@ -13,20 +13,18 @@ export default {
...mapState('monitoringDashboard', ['variables']), ...mapState('monitoringDashboard', ['variables']),
}, },
methods: { methods: {
...mapActions('monitoringDashboard', ['fetchDashboardData', 'updateVariableValues']), ...mapActions('monitoringDashboard', ['updateVariablesAndFetchData']),
refreshDashboard(variable, value) { refreshDashboard(variable, value) {
if (this.variables[variable].value !== value) { if (this.variables[variable].value !== value) {
const changedVariable = { key: variable, value }; const changedVariable = { key: variable, value };
// update the Vuex store // update the Vuex store
this.updateVariableValues(changedVariable); this.updateVariablesAndFetchData(changedVariable);
// the below calls can ideally be moved out of the // the below calls can ideally be moved out of the
// component and into the actions and let the // component and into the actions and let the
// mutation respond directly. // mutation respond directly.
// This can be further investigate in // This can be further investigate in
// https://gitlab.com/gitlab-org/gitlab/-/issues/217713 // https://gitlab.com/gitlab-org/gitlab/-/issues/217713
setCustomVariablesFromUrl(this.variables); setCustomVariablesFromUrl(this.variables);
// fetch data
this.fetchDashboardData();
} }
}, },
variableComponent(type) { variableComponent(type) {

View file

@ -415,8 +415,10 @@ export const duplicateSystemDashboard = ({ state }, payload) => {
// Variables manipulation // Variables manipulation
export const updateVariableValues = ({ commit }, updatedVariable) => { export const updateVariablesAndFetchData = ({ commit, dispatch }, updatedVariable) => {
commit(types.UPDATE_VARIABLE_VALUES, updatedVariable); commit(types.UPDATE_VARIABLES, updatedVariable);
return dispatch('fetchDashboardData');
}; };
// prevent babel-plugin-rewire from generating an invalid default during karma tests // prevent babel-plugin-rewire from generating an invalid default during karma tests

View file

@ -3,7 +3,7 @@ export const REQUEST_METRICS_DASHBOARD = 'REQUEST_METRICS_DASHBOARD';
export const RECEIVE_METRICS_DASHBOARD_SUCCESS = 'RECEIVE_METRICS_DASHBOARD_SUCCESS'; export const RECEIVE_METRICS_DASHBOARD_SUCCESS = 'RECEIVE_METRICS_DASHBOARD_SUCCESS';
export const RECEIVE_METRICS_DASHBOARD_FAILURE = 'RECEIVE_METRICS_DASHBOARD_FAILURE'; export const RECEIVE_METRICS_DASHBOARD_FAILURE = 'RECEIVE_METRICS_DASHBOARD_FAILURE';
export const SET_VARIABLES = 'SET_VARIABLES'; export const SET_VARIABLES = 'SET_VARIABLES';
export const UPDATE_VARIABLE_VALUES = 'UPDATE_VARIABLE_VALUES'; export const UPDATE_VARIABLES = 'UPDATE_VARIABLES';
export const REQUEST_DASHBOARD_STARRING = 'REQUEST_DASHBOARD_STARRING'; export const REQUEST_DASHBOARD_STARRING = 'REQUEST_DASHBOARD_STARRING';
export const RECEIVE_DASHBOARD_STARRING_SUCCESS = 'RECEIVE_DASHBOARD_STARRING_SUCCESS'; export const RECEIVE_DASHBOARD_STARRING_SUCCESS = 'RECEIVE_DASHBOARD_STARRING_SUCCESS';

View file

@ -191,7 +191,7 @@ export default {
[types.SET_VARIABLES](state, variables) { [types.SET_VARIABLES](state, variables) {
state.variables = variables; state.variables = variables;
}, },
[types.UPDATE_VARIABLE_VALUES](state, updatedVariable) { [types.UPDATE_VARIABLES](state, updatedVariable) {
Object.assign(state.variables[updatedVariable.key], { Object.assign(state.variables[updatedVariable.key], {
...state.variables[updatedVariable.key], ...state.variables[updatedVariable.key],
value: updatedVariable.value, value: updatedVariable.value,

View file

@ -30,7 +30,7 @@
ingress_help_path: help_page_path('user/project/clusters/index.md', anchor: 'getting-the-external-endpoint'), ingress_help_path: help_page_path('user/project/clusters/index.md', anchor: 'getting-the-external-endpoint'),
ingress_dns_help_path: help_page_path('user/clusters/applications.md', anchor: 'pointing-your-dns-at-the-external-endpoint'), ingress_dns_help_path: help_page_path('user/clusters/applications.md', anchor: 'pointing-your-dns-at-the-external-endpoint'),
ingress_mod_security_help_path: help_page_path('user/clusters/applications.md', anchor: 'web-application-firewall-modsecurity'), ingress_mod_security_help_path: help_page_path('user/clusters/applications.md', anchor: 'web-application-firewall-modsecurity'),
environments_help_path: help_page_path('ci/environments', anchor: 'defining-environments'), environments_help_path: help_page_path('ci/environments/index.md', anchor: 'defining-environments'),
clusters_help_path: help_page_path('user/project/clusters/index.md', anchor: 'deploying-to-a-kubernetes-cluster'), clusters_help_path: help_page_path('user/project/clusters/index.md', anchor: 'deploying-to-a-kubernetes-cluster'),
deploy_boards_help_path: help_page_path('user/project/deploy_boards.html', anchor: 'enabling-deploy-boards'), deploy_boards_help_path: help_page_path('user/project/deploy_boards.html', anchor: 'enabling-deploy-boards'),
cloud_run_help_path: help_page_path('user/project/clusters/add_remove_clusters.md', anchor: 'cloud-run-for-anthos'), cloud_run_help_path: help_page_path('user/project/clusters/add_remove_clusters.md', anchor: 'cloud-run-for-anthos'),

View file

@ -6,4 +6,4 @@
This Route Map is invalid: This Route Map is invalid:
= viewer.validation_message = viewer.validation_message
= link_to 'Learn more', help_page_path('ci/environments', anchor: 'going-from-source-files-to-public-pages') = link_to 'Learn more', help_page_path('ci/environments/index.md', anchor: 'going-from-source-files-to-public-pages')

View file

@ -1,4 +1,4 @@
= icon('spinner spin fw') = icon('spinner spin fw')
Validating Route Map… Validating Route Map…
= link_to 'Learn more', help_page_path('ci/environments', anchor: 'going-from-source-files-to-public-pages') = link_to 'Learn more', help_page_path('ci/environments/index.md', anchor: 'going-from-source-files-to-public-pages')

View file

@ -3,7 +3,7 @@
%h4.gl-mt-0 %h4.gl-mt-0
= _("Environments") = _("Environments")
%p %p
- link_to_read_more = link_to(_("Read more about environments"), help_page_path("ci/environments")) - link_to_read_more = link_to(_("Read more about environments"), help_page_path("ci/environments/index.md"))
= _("Environments allow you to track deployments of your application %{link_to_read_more}.").html_safe % { link_to_read_more: link_to_read_more } = _("Environments allow you to track deployments of your application %{link_to_read_more}.").html_safe % { link_to_read_more: link_to_read_more }
= form_for [@project.namespace.becomes(Namespace), @project, @environment], html: { class: 'col-lg-9' } do |f| = form_for [@project.namespace.becomes(Namespace), @project, @environment], html: { class: 'col-lg-9' } do |f|

View file

@ -11,4 +11,4 @@
%p.state-description %p.state-description
= s_('Metrics|Check out the CI/CD documentation on deploying to an environment') = s_('Metrics|Check out the CI/CD documentation on deploying to an environment')
.text-center .text-center
= link_to s_("Environments|Learn about environments"), help_page_path('ci/environments'), class: 'btn btn-success' = link_to s_("Environments|Learn about environments"), help_page_path('ci/environments/index.md'), class: 'btn btn-success'

View file

@ -4,5 +4,5 @@
"can-read-environment" => can?(current_user, :read_environment, @project).to_s, "can-read-environment" => can?(current_user, :read_environment, @project).to_s,
"can-create-environment" => can?(current_user, :create_environment, @project).to_s, "can-create-environment" => can?(current_user, :create_environment, @project).to_s,
"new-environment-path" => new_project_environment_path(@project), "new-environment-path" => new_project_environment_path(@project),
"help-page-path" => help_page_path("ci/environments"), "help-page-path" => help_page_path("ci/environments/index.md"),
"deploy-boards-help-path" => help_page_path("user/project/deploy_boards", anchor: "enabling-deploy-boards") } } "deploy-boards-help-path" => help_page_path("user/project/deploy_boards", anchor: "enabling-deploy-boards") } }

View file

@ -23,7 +23,7 @@
emphasis_end: '</strong>'.html_safe, emphasis_end: '</strong>'.html_safe,
ci_config_link_start: '<a href="https://docs.gitlab.com/ee/ci/yaml/" target="_blank" rel="noopener noreferrer">'.html_safe, ci_config_link_start: '<a href="https://docs.gitlab.com/ee/ci/yaml/" target="_blank" rel="noopener noreferrer">'.html_safe,
ci_config_link_end: '</a>'.html_safe } ci_config_link_end: '</a>'.html_safe }
%a{ href: 'https://docs.gitlab.com/ee/ci/environments.html#stopping-an-environment', %a{ href: 'https://docs.gitlab.com/ee/ci/environments/index.html#stopping-an-environment',
target: '_blank', target: '_blank',
rel: 'noopener noreferrer' } rel: 'noopener noreferrer' }
= s_('Environments|Learn more about stopping environments') = s_('Environments|Learn more about stopping environments')

View file

@ -11,4 +11,4 @@
%p.state-description.text-center %p.state-description.text-center
= s_('Logs|To see the logs, deploy your code to an environment.') = s_('Logs|To see the logs, deploy your code to an environment.')
.text-center .text-center
= link_to s_('Environments|Learn about environments'), help_page_path('ci/environments'), class: 'btn btn-success' = link_to s_('Environments|Learn about environments'), help_page_path('ci/environments/index.md'), class: 'btn btn-success'

View file

@ -0,0 +1,5 @@
---
title: Fix Geo replication for design thumbnails
merge_request: 32703
author:
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Extend "Remember me" token after each login
merge_request: 32730
author:
type: other

View file

@ -43,10 +43,7 @@ if !Rails.env.test? && Gitlab::Metrics.prometheus_metrics_enabled?
defined?(::Prometheus::Client.reinitialize_on_pid_change) && Prometheus::Client.reinitialize_on_pid_change defined?(::Prometheus::Client.reinitialize_on_pid_change) && Prometheus::Client.reinitialize_on_pid_change
Gitlab::Metrics::Samplers::RubySampler.initialize_instance(Settings.monitoring.ruby_sampler_interval).start Gitlab::Metrics::Samplers::RubySampler.initialize_instance(Settings.monitoring.ruby_sampler_interval).start
if Gitlab::Utils.to_boolean(ENV['ENABLE_DATABASE_CONNECTION_POOL_METRICS'])
Gitlab::Metrics::Samplers::DatabaseSampler.initialize_instance(Gitlab::Metrics::Samplers::DatabaseSampler::SAMPLING_INTERVAL_SECONDS).start Gitlab::Metrics::Samplers::DatabaseSampler.initialize_instance(Gitlab::Metrics::Samplers::DatabaseSampler::SAMPLING_INTERVAL_SECONDS).start
end
if Gitlab.ee? && Gitlab::Runtime.sidekiq? if Gitlab.ee? && Gitlab::Runtime.sidekiq?
Gitlab::Metrics::Samplers::GlobalSearchSampler.instance(Settings.monitoring.global_search_sampler_interval).start Gitlab::Metrics::Samplers::GlobalSearchSampler.instance(Settings.monitoring.global_search_sampler_interval).start

View file

@ -102,7 +102,7 @@ Devise.setup do |config|
# config.remember_across_browsers = true # config.remember_across_browsers = true
# If true, extends the user's remember period when remembered via cookie. # If true, extends the user's remember period when remembered via cookie.
# config.extend_remember_period = false config.extend_remember_period = true
# Options to be passed to the created cookie. For instance, you can set # Options to be passed to the created cookie. For instance, you can set
# secure: true in order to force SSL only cookies. # secure: true in order to force SSL only cookies.

View file

@ -178,6 +178,30 @@ The following metrics are available:
|:--------------------------------- |:--------- |:------------------------------------------------------------- |:-------------------------------------- | |:--------------------------------- |:--------- |:------------------------------------------------------------- |:-------------------------------------- |
| `db_load_balancing_hosts` | Gauge | [12.3](https://gitlab.com/gitlab-org/gitlab/-/issues/13630) | Current number of load balancing hosts | | `db_load_balancing_hosts` | Gauge | [12.3](https://gitlab.com/gitlab-org/gitlab/-/issues/13630) | Current number of load balancing hosts |
## Connection pool metrics
These metrics record the status of the database [connection pools](https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/ConnectionPool.html).
They all have these labels:
1. `class` - the Ruby class being recorded.
1. `ActiveRecord::Base` is the main database connection.
1. `Geo::TrackingBase` is the connection to the Geo tracking database, if
enabled.
1. `Gitlab::Database::LoadBalancing::Host` is a connection used by [database
load balancing](../../database_load_balancing.md), if enabled.
1. `host` - the host name used to connect to the database.
1. `port` - the port used to connect to the database.
| Metric | Type | Since | Description |
|:----------------------------------------------|:------|:------|:--------------------------------------------------|
| `gitlab_database_connection_pool_size` | Gauge | 13.0 | Total connection pool capacity |
| `gitlab_database_connection_pool_connections` | Gauge | 13.0 | Current connections in the pool |
| `gitlab_database_connection_pool_busy` | Gauge | 13.0 | Connections in use where the owner is still alive |
| `gitlab_database_connection_pool_dead` | Gauge | 13.0 | Connections in use where the owner is not alive |
| `gitlab_database_connection_pool_idle` | Gauge | 13.0 | Connections not in use |
| `gitlab_database_connection_pool_waiting` | Gauge | 13.0 | Threads currently waiting on this queue |
## Ruby metrics ## Ruby metrics
Some basic Ruby runtime metrics are available: Some basic Ruby runtime metrics are available:

View file

@ -2,6 +2,8 @@
module Gitlab module Gitlab
class GitAccessWiki < GitAccess class GitAccessWiki < GitAccess
prepend_if_ee('EE::Gitlab::GitAccessWiki') # rubocop: disable Cop/InjectEnterpriseEditionModule
ERROR_MESSAGES = { ERROR_MESSAGES = {
read_only: "You can't push code to a read-only GitLab instance.", read_only: "You can't push code to a read-only GitLab instance.",
write_to_wiki: "You are not allowed to write to this project's wiki." write_to_wiki: "You are not allowed to write to this project's wiki."
@ -31,8 +33,10 @@ module Gitlab
ERROR_MESSAGES[:read_only] ERROR_MESSAGES[:read_only]
end end
def container private
project.wiki
def repository
project.wiki.repository
end end
end end
end end

View file

@ -10065,6 +10065,12 @@ msgstr ""
msgid "GeoNodes|secondary nodes" msgid "GeoNodes|secondary nodes"
msgstr "" msgstr ""
msgid "Geo|%{label} can't be blank"
msgstr ""
msgid "Geo|%{label} should be between 1-999"
msgstr ""
msgid "Geo|%{name} is scheduled for forced re-download" msgid "Geo|%{name} is scheduled for forced re-download"
msgstr "" msgstr ""
@ -10128,6 +10134,12 @@ msgstr ""
msgid "Geo|Next sync scheduled at" msgid "Geo|Next sync scheduled at"
msgstr "" msgstr ""
msgid "Geo|Node name can't be blank"
msgstr ""
msgid "Geo|Node name should be between 1 and 255 characters"
msgstr ""
msgid "Geo|Not synced yet" msgid "Geo|Not synced yet"
msgstr "" msgstr ""
@ -10212,6 +10224,12 @@ msgstr ""
msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed." msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
msgstr "" msgstr ""
msgid "Geo|URL can't be blank"
msgstr ""
msgid "Geo|URL must be a valid url (ex: https://gitlab.com)"
msgstr ""
msgid "Geo|Unknown state" msgid "Geo|Unknown state"
msgstr "" msgstr ""
@ -14047,9 +14065,6 @@ msgstr ""
msgid "Name has already been taken" msgid "Name has already been taken"
msgstr "" msgstr ""
msgid "Name must be between 1 and 255 characters"
msgstr ""
msgid "Name new label" msgid "Name new label"
msgstr "" msgstr ""
@ -23107,9 +23122,6 @@ msgstr ""
msgid "URL is required" msgid "URL is required"
msgstr "" msgstr ""
msgid "URL must be a valid url (ex: https://gitlab.com)"
msgstr ""
msgid "URL must start with %{codeStart}http://%{codeEnd}, %{codeStart}https://%{codeEnd}, or %{codeStart}ftp://%{codeEnd}" msgid "URL must start with %{codeStart}http://%{codeEnd}, %{codeStart}https://%{codeEnd}, or %{codeStart}ftp://%{codeEnd}"
msgstr "" msgstr ""

View file

@ -41,7 +41,7 @@
"@babel/preset-env": "^7.8.4", "@babel/preset-env": "^7.8.4",
"@gitlab/at.js": "1.5.5", "@gitlab/at.js": "1.5.5",
"@gitlab/svgs": "1.128.0", "@gitlab/svgs": "1.128.0",
"@gitlab/ui": "^14.14.2", "@gitlab/ui": "14.14.2",
"@gitlab/visual-review-tools": "1.6.1", "@gitlab/visual-review-tools": "1.6.1",
"@rails/actioncable": "^6.0.3", "@rails/actioncable": "^6.0.3",
"@sentry/browser": "^5.10.2", "@sentry/browser": "^5.10.2",

View file

@ -57,8 +57,7 @@ describe('Metrics dashboard/variables section component', () => {
}); });
describe('when changing the variable inputs', () => { describe('when changing the variable inputs', () => {
const fetchDashboardData = jest.fn(); const updateVariablesAndFetchData = jest.fn();
const updateVariableValues = jest.fn();
beforeEach(() => { beforeEach(() => {
store = new Vuex.Store({ store = new Vuex.Store({
@ -70,8 +69,7 @@ describe('Metrics dashboard/variables section component', () => {
variables: sampleVariables, variables: sampleVariables,
}, },
actions: { actions: {
fetchDashboardData, updateVariablesAndFetchData,
updateVariableValues,
}, },
}, },
}, },
@ -86,13 +84,12 @@ describe('Metrics dashboard/variables section component', () => {
firstInput.vm.$emit('onUpdate', 'label1', 'test'); firstInput.vm.$emit('onUpdate', 'label1', 'test');
return wrapper.vm.$nextTick(() => { return wrapper.vm.$nextTick(() => {
expect(updateVariableValues).toHaveBeenCalled(); expect(updateVariablesAndFetchData).toHaveBeenCalled();
expect(mergeUrlParams).toHaveBeenCalledWith( expect(mergeUrlParams).toHaveBeenCalledWith(
convertVariablesForURL(sampleVariables), convertVariablesForURL(sampleVariables),
window.location.href, window.location.href,
); );
expect(updateHistory).toHaveBeenCalled(); expect(updateHistory).toHaveBeenCalled();
expect(fetchDashboardData).toHaveBeenCalled();
}); });
}); });
@ -102,13 +99,12 @@ describe('Metrics dashboard/variables section component', () => {
firstInput.vm.$emit('onUpdate', 'label1', 'test'); firstInput.vm.$emit('onUpdate', 'label1', 'test');
return wrapper.vm.$nextTick(() => { return wrapper.vm.$nextTick(() => {
expect(updateVariableValues).toHaveBeenCalled(); expect(updateVariablesAndFetchData).toHaveBeenCalled();
expect(mergeUrlParams).toHaveBeenCalledWith( expect(mergeUrlParams).toHaveBeenCalledWith(
convertVariablesForURL(sampleVariables), convertVariablesForURL(sampleVariables),
window.location.href, window.location.href,
); );
expect(updateHistory).toHaveBeenCalled(); expect(updateHistory).toHaveBeenCalled();
expect(fetchDashboardData).toHaveBeenCalled();
}); });
}); });
@ -117,10 +113,9 @@ describe('Metrics dashboard/variables section component', () => {
firstInput.vm.$emit('onUpdate', 'label1', 'Simple text'); firstInput.vm.$emit('onUpdate', 'label1', 'Simple text');
expect(updateVariableValues).not.toHaveBeenCalled(); expect(updateVariablesAndFetchData).not.toHaveBeenCalled();
expect(mergeUrlParams).not.toHaveBeenCalled(); expect(mergeUrlParams).not.toHaveBeenCalled();
expect(updateHistory).not.toHaveBeenCalled(); expect(updateHistory).not.toHaveBeenCalled();
expect(fetchDashboardData).not.toHaveBeenCalled();
}); });
}); });
}); });

View file

@ -26,7 +26,7 @@ import {
clearExpandedPanel, clearExpandedPanel,
setGettingStartedEmptyState, setGettingStartedEmptyState,
duplicateSystemDashboard, duplicateSystemDashboard,
updateVariableValues, updateVariablesAndFetchData,
} from '~/monitoring/stores/actions'; } from '~/monitoring/stores/actions';
import { import {
gqClient, gqClient,
@ -417,19 +417,23 @@ describe('Monitoring store actions', () => {
}); });
}); });
describe('updateVariableValues', () => { describe('updateVariablesAndFetchData', () => {
it('should commit UPDATE_VARIABLE_VALUES mutation', done => { it('should commit UPDATE_VARIABLES mutation and fetch data', done => {
testAction( testAction(
updateVariableValues, updateVariablesAndFetchData,
{ pod: 'POD' }, { pod: 'POD' },
state, state,
[ [
{ {
type: types.UPDATE_VARIABLE_VALUES, type: types.UPDATE_VARIABLES,
payload: { pod: 'POD' }, payload: { pod: 'POD' },
}, },
], ],
[], [
{
type: 'fetchDashboardData',
},
],
done, done,
); );
}); });

View file

@ -418,14 +418,14 @@ describe('Monitoring mutations', () => {
}); });
}); });
describe('UPDATE_VARIABLE_VALUES', () => { describe('UPDATE_VARIABLES', () => {
afterEach(() => { afterEach(() => {
mutations[types.SET_VARIABLES](stateCopy, {}); mutations[types.SET_VARIABLES](stateCopy, {});
}); });
it('updates only the value of the variable in variables', () => { it('updates only the value of the variable in variables', () => {
mutations[types.SET_VARIABLES](stateCopy, { environment: { value: 'prod', type: 'text' } }); mutations[types.SET_VARIABLES](stateCopy, { environment: { value: 'prod', type: 'text' } });
mutations[types.UPDATE_VARIABLE_VALUES](stateCopy, { key: 'environment', value: 'new prod' }); mutations[types.UPDATE_VARIABLES](stateCopy, { key: 'environment', value: 'new prod' });
expect(stateCopy.variables).toEqual({ environment: { value: 'new prod', type: 'text' } }); expect(stateCopy.variables).toEqual({ environment: { value: 'new prod', type: 'text' } });
}); });

View file

@ -787,7 +787,7 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.128.0.tgz#c510050d5646d73b52e684248a186dbd1f55cbb0" resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.128.0.tgz#c510050d5646d73b52e684248a186dbd1f55cbb0"
integrity sha512-RqgF6k2xPptbz58RB1nNgeo6gy3l1u7+1rxXvALzIAsazmrAw708NYCT3PALg2RoyH0G/fpUa6yPQ0HbR+OtEg== integrity sha512-RqgF6k2xPptbz58RB1nNgeo6gy3l1u7+1rxXvALzIAsazmrAw708NYCT3PALg2RoyH0G/fpUa6yPQ0HbR+OtEg==
"@gitlab/ui@^14.14.2": "@gitlab/ui@14.14.2":
version "14.14.2" version "14.14.2"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-14.14.2.tgz#7cc81d90d5b5394345d6781ff02e974e24b97387" resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-14.14.2.tgz#7cc81d90d5b5394345d6781ff02e974e24b97387"
integrity sha512-Fq7fGjhofnN64xckTuuuX4EE23ZXcndwCfFBFrCTCbDfrDSa0l0xkmkrvYCSrNNTp6CyL5Ec/LWgGcnGCPWaFw== integrity sha512-Fq7fGjhofnN64xckTuuuX4EE23ZXcndwCfFBFrCTCbDfrDSa0l0xkmkrvYCSrNNTp6CyL5Ec/LWgGcnGCPWaFw==