Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
80816914ff
commit
e4476c4a18
|
@ -80,10 +80,10 @@ export default {
|
|||
|
||||
<template>
|
||||
<div class="ide-commit-list-container">
|
||||
<header class="multi-file-commit-panel-header d-flex mb-0">
|
||||
<div class="d-flex align-items-center flex-fill">
|
||||
<header class="multi-file-commit-panel-header gl-display-flex gl-mb-0">
|
||||
<div class="gl-display-flex gl-align-items-center flex-fill">
|
||||
<strong> {{ titleText }} </strong>
|
||||
<div class="d-flex ml-auto">
|
||||
<div class="gl-display-flex gl-ml-auto">
|
||||
<gl-button
|
||||
v-if="!stagedList"
|
||||
v-gl-tooltip
|
||||
|
@ -114,7 +114,7 @@ export default {
|
|||
/>
|
||||
</li>
|
||||
</ul>
|
||||
<p v-else class="multi-file-commit-list form-text text-muted text-center">
|
||||
<p v-else class="multi-file-commit-list form-text gl-text-gray-600 gl-text-center">
|
||||
{{ emptyStateText }}
|
||||
</p>
|
||||
<gl-modal
|
||||
|
|
|
@ -75,25 +75,25 @@ export default {
|
|||
<span class="gl-font-weight-bold gl-w-15 gl-pt-2" data-testid="assignees-title">{{
|
||||
__('Assignee(s)')
|
||||
}}</span>
|
||||
<!-- TODO: Remove this div when https://gitlab.com/gitlab-org/gitlab-ui/-/merge_requests/2872 is merged -->
|
||||
<div
|
||||
v-if="assigneeListEmpty && !isEditing"
|
||||
class="add-assignees gl-min-w-fit-content gl-absolute gl-display-flex gl-align-items-center gl-text-gray-300 gl-pr-4 gl-top-2 gl-z-index-0"
|
||||
data-testid="empty-state"
|
||||
>
|
||||
<gl-icon name="profile" />
|
||||
<span class="gl-ml-2">{{ __('Add assignees') }}</span>
|
||||
</div>
|
||||
<gl-token-selector
|
||||
ref="tokenSelector"
|
||||
v-model="localAssignees"
|
||||
hide-dropdown-with-no-items
|
||||
:container-class="containerClass"
|
||||
class="gl-w-full gl-border gl-border-white gl-hover-border-gray-200 gl-rounded-base gl-z-index-1 gl-bg-transparent!"
|
||||
class="gl-w-full gl-border gl-border-white gl-hover-border-gray-200 gl-rounded-base"
|
||||
@token-remove="focusTokenSelector"
|
||||
@focus="isEditing = true"
|
||||
@blur="setAssignees"
|
||||
>
|
||||
<template #empty-placeholder>
|
||||
<div
|
||||
class="add-assignees gl-min-w-fit-content gl-display-flex gl-align-items-center gl-text-gray-300 gl-pr-4 gl-top-2"
|
||||
data-testid="empty-state"
|
||||
>
|
||||
<gl-icon name="profile" />
|
||||
<span class="gl-ml-2">{{ __('Add assignees') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #token-content="{ token }">
|
||||
<gl-link
|
||||
:href="token.webUrl"
|
||||
|
@ -109,10 +109,3 @@ export default {
|
|||
</gl-token-selector>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
/* TODO: Remove style block when https://gitlab.com/gitlab-org/gitlab-ui/-/merge_requests/2872 is merged */
|
||||
.work-item-assignees .add-assignees {
|
||||
left: 7.5rem;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -17,12 +17,7 @@ module Projects
|
|||
private
|
||||
|
||||
def packages_and_registries_settings_enabled!
|
||||
render_404 unless can_destroy_container_registry_image?(project)
|
||||
end
|
||||
|
||||
def can_destroy_container_registry_image?(project)
|
||||
Gitlab.config.registry.enabled &&
|
||||
can?(current_user, :destroy_container_image, project)
|
||||
render_404 unless can?(current_user, :view_package_registry_project_settings, project)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,7 +7,7 @@ module Types
|
|||
class JobType < BaseObject
|
||||
graphql_name 'CiJob'
|
||||
|
||||
connection_type_class(Types::CountableConnectionType)
|
||||
connection_type_class(Types::LimitedCountableConnectionType)
|
||||
|
||||
expose_permissions Types::PermissionTypes::Ci::Job
|
||||
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Types
|
||||
# rubocop: disable Graphql/AuthorizeTypes
|
||||
class LimitedCountableConnectionType < GraphQL::Types::Relay::BaseConnection
|
||||
COUNT_LIMIT = 1000
|
||||
COUNT_DESCRIPTION = "Limited count of collection. Returns limit + 1 for counts greater than the limit."
|
||||
|
||||
field :count, GraphQL::Types::Int, null: false, description: COUNT_DESCRIPTION do
|
||||
argument :limit, GraphQL::Types::Int,
|
||||
required: false, default_value: COUNT_LIMIT,
|
||||
validates: { numericality: { greater_than: 0, less_than_or_equal_to: COUNT_LIMIT } },
|
||||
description: "Limit value to be applied to the count query. Default is 1000."
|
||||
end
|
||||
|
||||
def count(limit:)
|
||||
relation = object.items
|
||||
|
||||
if relation.respond_to?(:page)
|
||||
relation.page.total_count_with_limit(:all, limit: limit)
|
||||
else
|
||||
[relation.size, limit.next].min
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -203,6 +203,10 @@ class ProjectPolicy < BasePolicy
|
|||
Feature.disabled?(:runner_registration_control) || Gitlab::CurrentSettings.valid_runner_registrars.include?('project')
|
||||
end
|
||||
|
||||
condition :registry_enabled do
|
||||
Gitlab.config.registry.enabled
|
||||
end
|
||||
|
||||
# `:read_project` may be prevented in EE, but `:read_project_for_iids` should
|
||||
# not.
|
||||
rule { guest | admin }.enable :read_project_for_iids
|
||||
|
@ -760,6 +764,10 @@ class ProjectPolicy < BasePolicy
|
|||
enable :import_project_members_from_another_project
|
||||
end
|
||||
|
||||
rule { registry_enabled & can?(:admin_container_image) }.policy do
|
||||
enable :view_package_registry_project_settings
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def user_is_user?
|
||||
|
|
|
@ -8,7 +8,7 @@ module TwoFactor
|
|||
|
||||
result = disable_two_factor
|
||||
|
||||
notification_service.disabled_two_factor(user) if result[:status] == :success
|
||||
notify_on_success(user) if result[:status] == :success
|
||||
|
||||
result
|
||||
end
|
||||
|
@ -20,5 +20,11 @@ module TwoFactor
|
|||
user.disable_two_factor!
|
||||
end
|
||||
end
|
||||
|
||||
def notify_on_success(user)
|
||||
notification_service.disabled_two_factor(user)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
TwoFactor::DestroyService.prepend_mod_with('TwoFactor::DestroyService')
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
= message.target_path
|
||||
%td
|
||||
= message.broadcast_type.capitalize
|
||||
%td.gl-white-space-nowrap
|
||||
%td.gl-white-space-nowrap<
|
||||
= link_to sprite_icon('pencil', css_class: 'gl-icon'), edit_admin_broadcast_message_path(message), title: _('Edit'), class: 'btn btn-icon gl-button'
|
||||
= link_to sprite_icon('remove', css_class: 'gl-icon'), admin_broadcast_message_path(message), method: :delete, remote: true, title: _('Remove'), class: 'js-remove-tr btn btn-icon gl-button btn-danger ml-2'
|
||||
= link_to sprite_icon('remove', css_class: 'gl-icon'), admin_broadcast_message_path(message), method: :delete, remote: true, title: _('Remove'), class: 'js-remove-tr btn btn-icon gl-button btn-danger gl-ml-3'
|
||||
= paginate @broadcast_messages, theme: 'gitlab'
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: omit_epic_subscribed
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/86016
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/360663
|
||||
milestone: '15.0'
|
||||
type: development
|
||||
group: group::product planning
|
||||
default_enabled: true
|
|
@ -166,6 +166,18 @@ mutation {
|
|||
}
|
||||
```
|
||||
|
||||
### Delete with the API
|
||||
|
||||
Group owners can remove a HTTP header using the GraphQL `auditEventsStreamingHeadersDestroy` mutation.
|
||||
|
||||
```graphql
|
||||
mutation {
|
||||
auditEventsStreamingHeadersDestroy(input: { headerId: "gid://gitlab/AuditEvents::ExternalAuditEventDestination/24601" }) {
|
||||
errors
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The header is created if the returned `errors` object is empty.
|
||||
|
||||
## Verify event authenticity
|
||||
|
|
|
@ -234,6 +234,7 @@ The following user actions are recorded:
|
|||
- Administrator added or removed ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/323905) in GitLab 14.1)
|
||||
- Removed SSH key ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/220127) in GitLab 14.1)
|
||||
- Added or removed GPG key ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/220127) in GitLab 14.1)
|
||||
- A user's two-factor authentication was disabled ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/238177) in GitLab 15.1)
|
||||
|
||||
Instance events can also be accessed via the [Instance Audit Events API](../api/audit_events.md#instance-audit-events).
|
||||
|
||||
|
|
|
@ -738,6 +738,24 @@ Input type: `AuditEventsStreamingHeadersCreateInput`
|
|||
| <a id="mutationauditeventsstreamingheaderscreateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
| <a id="mutationauditeventsstreamingheaderscreateheader"></a>`header` | [`AuditEventStreamingHeader`](#auditeventstreamingheader) | Created header. |
|
||||
|
||||
### `Mutation.auditEventsStreamingHeadersDestroy`
|
||||
|
||||
Input type: `AuditEventsStreamingHeadersDestroyInput`
|
||||
|
||||
#### Arguments
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationauditeventsstreamingheadersdestroyclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationauditeventsstreamingheadersdestroyheaderid"></a>`headerId` | [`AuditEventsStreamingHeaderID!`](#auditeventsstreamingheaderid) | Header to delete. |
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationauditeventsstreamingheadersdestroyclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationauditeventsstreamingheadersdestroyerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
|
||||
### `Mutation.awardEmojiAdd`
|
||||
|
||||
Input type: `AwardEmojiAddInput`
|
||||
|
@ -6033,11 +6051,24 @@ The connection type for [`CiJob`](#cijob).
|
|||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="cijobconnectioncount"></a>`count` | [`Int!`](#int) | Total count of collection. |
|
||||
| <a id="cijobconnectionedges"></a>`edges` | [`[CiJobEdge]`](#cijobedge) | A list of edges. |
|
||||
| <a id="cijobconnectionnodes"></a>`nodes` | [`[CiJob]`](#cijob) | A list of nodes. |
|
||||
| <a id="cijobconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
|
||||
|
||||
##### Fields with arguments
|
||||
|
||||
###### `CiJobConnection.count`
|
||||
|
||||
Limited count of collection. Returns limit + 1 for counts greater than the limit.
|
||||
|
||||
Returns [`Int!`](#int).
|
||||
|
||||
####### Arguments
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="cijobconnectioncountlimit"></a>`limit` | [`Int`](#int) | Limit value to be applied to the count query. Default is 1000. |
|
||||
|
||||
#### `CiJobEdge`
|
||||
|
||||
The edge type for [`CiJob`](#cijob).
|
||||
|
@ -20079,6 +20110,12 @@ A `AuditEventsExternalAuditEventDestinationID` is a global ID. It is encoded as
|
|||
|
||||
An example `AuditEventsExternalAuditEventDestinationID` is: `"gid://gitlab/AuditEvents::ExternalAuditEventDestination/1"`.
|
||||
|
||||
### `AuditEventsStreamingHeaderID`
|
||||
|
||||
A `AuditEventsStreamingHeaderID` is a global ID. It is encoded as a string.
|
||||
|
||||
An example `AuditEventsStreamingHeaderID` is: `"gid://gitlab/AuditEvents::Streaming::Header/1"`.
|
||||
|
||||
### `AwardableID`
|
||||
|
||||
A `AwardableID` is a global ID. It is encoded as a string.
|
||||
|
|
|
@ -242,6 +242,10 @@ Change a file in the project and see if it's reflected in the demo application o
|
|||
|
||||
Congratulations! You successfully set up continuous deployment to ECS.
|
||||
|
||||
NOTE:
|
||||
ECS deploy jobs wait for the rollout to complete before exiting. To disable this behavior,
|
||||
set `CI_AWS_ECS_WAIT_FOR_ROLLOUT_COMPLETE_DISABLED` to a non-empty value.
|
||||
|
||||
## Further reading
|
||||
|
||||
- If you're interested in more of the continuous deployments to clouds, see [cloud deployments](../index.md).
|
||||
|
|
|
@ -124,6 +124,10 @@ Finally, your AWS ECS service is updated with the new revision of the
|
|||
task definition, making the cluster pull the newest version of your
|
||||
application.
|
||||
|
||||
NOTE:
|
||||
ECS deploy jobs wait for the rollout to complete before exiting. To disable this behavior,
|
||||
set `CI_AWS_ECS_WAIT_FOR_ROLLOUT_COMPLETE_DISABLED` to a non-empty value.
|
||||
|
||||
WARNING:
|
||||
The [`AWS/Deploy-ECS.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/AWS/Deploy-ECS.gitlab-ci.yml)
|
||||
template includes two templates: [`Jobs/Build.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml)
|
||||
|
|
|
@ -118,6 +118,25 @@ create or update pipelines until their email address is confirmed.
|
|||
You can [change](../../../security/password_length_limits.md#modify-minimum-password-length-using-gitlab-ui)
|
||||
the minimum number of characters a user must have in their password using the GitLab UI.
|
||||
|
||||
### Password complexity requirements **(PREMIUM SELF)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/354965) in GitLab 15.1.
|
||||
|
||||
By default, the only requirement for user passwords is [minimum password length](#minimum-password-length-limit).
|
||||
You can add additional complexity requirements. Changes to password complexity requirements apply to new passwords:
|
||||
|
||||
- For new users that sign up.
|
||||
- For existing users that reset their password.
|
||||
|
||||
Existing passwords are unaffected. To change password complexity requirements:
|
||||
|
||||
1. On the top bar, select **Menu > Admin**.
|
||||
1. On the left sidebar, select **Settings > General**.
|
||||
1. Expand **Sign-up restrictions**.
|
||||
1. Under **Minimum password length (number of characters)**, select additional password complexity requirements. You can require numbers, uppercase letters, lowercase letters,
|
||||
and symbols.
|
||||
1. Select **Save changes**.
|
||||
|
||||
## Allow or deny sign ups using specific email domains
|
||||
|
||||
You can specify an inclusive or exclusive list of email domains which can be used for user sign up.
|
||||
|
|
|
@ -104,8 +104,7 @@ module Sidebars
|
|||
end
|
||||
|
||||
def packages_and_registries_menu_item
|
||||
if !Gitlab.config.registry.enabled ||
|
||||
!can?(context.current_user, :destroy_container_image, context.project)
|
||||
unless can?(context.current_user, :view_package_registry_project_settings, context.project)
|
||||
return ::Sidebars::NilMenuItem.new(item_id: :packages_and_registries)
|
||||
end
|
||||
|
||||
|
|
|
@ -60,32 +60,11 @@ describe('WorkItemAssignees component', () => {
|
|||
expect(findAssigneeLinks().at(0).attributes('data-user-id')).toBe('1');
|
||||
});
|
||||
|
||||
describe('when there are no assignees', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({ assignees: [] });
|
||||
});
|
||||
|
||||
it('should render empty state placeholder', () => {
|
||||
expect(findEmptyState().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('should hide empty state placeholder on focusing token selector', async () => {
|
||||
findTokenSelector().vm.$emit('focus');
|
||||
await nextTick();
|
||||
|
||||
expect(findEmptyState().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when there are assignees', () => {
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
it('should not render empty state placeholder', () => {
|
||||
expect(findEmptyState().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('should focus token selector on token removal', async () => {
|
||||
findTokenSelector().vm.$emit('token-remove', mockAssignees[0].id);
|
||||
await nextTick();
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Types::LimitedCountableConnectionType do
|
||||
it 'has the expected fields' do
|
||||
expected_fields = %i[count page_info]
|
||||
|
||||
expect(described_class).to have_graphql_fields(*expected_fields)
|
||||
end
|
||||
end
|
|
@ -1369,6 +1369,68 @@ RSpec.describe ProjectPolicy do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'view_package_registry_project_settings' do
|
||||
context 'with registry enabled' do
|
||||
before do
|
||||
stub_config(registry: { enabled: true })
|
||||
end
|
||||
|
||||
context 'with an admin user' do
|
||||
let(:current_user) { admin }
|
||||
|
||||
context 'when admin mode enabled', :enable_admin_mode do
|
||||
it { is_expected.to be_allowed(:view_package_registry_project_settings) }
|
||||
end
|
||||
|
||||
context 'when admin mode disabled' do
|
||||
it { is_expected.to be_disallowed(:view_package_registry_project_settings) }
|
||||
end
|
||||
end
|
||||
|
||||
%i[owner maintainer].each do |role|
|
||||
context "with #{role}" do
|
||||
let(:current_user) { public_send(role) }
|
||||
|
||||
it { is_expected.to be_allowed(:view_package_registry_project_settings) }
|
||||
end
|
||||
end
|
||||
|
||||
%i[developer reporter guest non_member anonymous].each do |role|
|
||||
context "with #{role}" do
|
||||
let(:current_user) { public_send(role) }
|
||||
|
||||
it { is_expected.to be_disallowed(:view_package_registry_project_settings) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with registry disabled' do
|
||||
before do
|
||||
stub_config(registry: { enabled: false })
|
||||
end
|
||||
|
||||
context 'with admin user' do
|
||||
let(:current_user) { admin }
|
||||
|
||||
context 'when admin mode enabled', :enable_admin_mode do
|
||||
it { is_expected.to be_disallowed(:view_package_registry_project_settings) }
|
||||
end
|
||||
|
||||
context 'when admin mode disabled' do
|
||||
it { is_expected.to be_disallowed(:view_package_registry_project_settings) }
|
||||
end
|
||||
end
|
||||
|
||||
%i[owner maintainer developer reporter guest non_member anonymous].each do |role|
|
||||
context "with #{role}" do
|
||||
let(:current_user) { public_send(role) }
|
||||
|
||||
it { is_expected.to be_disallowed(:view_package_registry_project_settings) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'read_feature_flag' do
|
||||
subject { described_class.new(current_user, project) }
|
||||
|
||||
|
|
|
@ -258,4 +258,81 @@ RSpec.describe 'Query.project.pipeline' do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.jobs.count' do
|
||||
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
|
||||
let_it_be(:successful_job) { create(:ci_build, :success, pipeline: pipeline) }
|
||||
let_it_be(:pending_job) { create(:ci_build, :pending, pipeline: pipeline) }
|
||||
let_it_be(:failed_job) { create(:ci_build, :failed, pipeline: pipeline) }
|
||||
|
||||
let(:query) do
|
||||
%(
|
||||
query {
|
||||
project(fullPath: "#{project.full_path}") {
|
||||
pipeline(iid: "#{pipeline.iid}") {
|
||||
jobs {
|
||||
count
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
before do
|
||||
post_graphql(query, current_user: user)
|
||||
end
|
||||
|
||||
it 'returns the number of jobs' do
|
||||
expect(graphql_data_at(:project, :pipeline, :jobs, :count)).to eq(3)
|
||||
end
|
||||
|
||||
context 'with limit value' do
|
||||
let(:limit) { 1 }
|
||||
|
||||
let(:query) do
|
||||
%(
|
||||
query {
|
||||
project(fullPath: "#{project.full_path}") {
|
||||
pipeline(iid: "#{pipeline.iid}") {
|
||||
jobs {
|
||||
count(limit: #{limit})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns a limited number of jobs' do
|
||||
expect(graphql_data_at(:project, :pipeline, :jobs, :count)).to eq(2)
|
||||
end
|
||||
|
||||
context 'with invalid value' do
|
||||
let(:limit) { 1500 }
|
||||
|
||||
it 'returns a validation error' do
|
||||
expect(graphql_errors).to include(a_hash_including('message' => 'limit must be less than or equal to 1000'))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with jobs filter' do
|
||||
let(:query) do
|
||||
%(
|
||||
query {
|
||||
project(fullPath: "#{project.full_path}") {
|
||||
jobs(statuses: FAILED) {
|
||||
count
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns the number of failed jobs' do
|
||||
expect(graphql_data_at(:project, :jobs, :count)).to eq(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue