Commit 5e1115f5 authored by Alex Buijs's avatar Alex Buijs Committed by Peter Hegman

Switch AWS "easy button" icons with radio buttons

Changelog: changed
parent eee5d278
......@@ -14,7 +14,11 @@ export const EASY_BUTTONS = [
templateName:
'easybutton-amazon-linux-2-docker-manual-scaling-with-schedule-ondemandonly.cf.yml',
description: s__(
'Runners|Amazon Linux 2 Docker HA with manual scaling and optional scheduling. Non-spot. Default choice for Linux Docker executor.',
'Runners|Amazon Linux 2 Docker HA with manual scaling and optional scheduling. Non-spot.',
),
moreDetails1: s__('Runners|No spot. This is the default choice for Linux Docker executor.'),
moreDetails2: s__(
'Runners|A capacity of 1 enables warm HA through Auto Scaling group re-spawn. A capacity of 2 enables hot HA because the service is available even when a node is lost. A capacity of 3 or more enables hot HA and manual scaling of runner fleet.',
),
},
{
......@@ -26,12 +30,20 @@ export const EASY_BUTTONS = [
),
{ percentage: '100%' },
),
moreDetails1: sprintf(s__('Runners|%{percentage} spot.'), { percentage: '100%' }),
moreDetails2: s__(
'Runners|Capacity of 1 enables warm HA through Auto Scaling group re-spawn. Capacity of 2 enables hot HA because the service is available even when a node is lost. Capacity of 3 or more enables hot HA and manual scaling of runner fleet.',
),
},
{
stackName: 'win2019-shell-non-spot',
templateName: 'easybutton-windows2019-shell-manual-scaling-with-scheduling-ondemandonly.cf.yml',
description: s__(
'Runners|Windows 2019 Shell with manual scaling and optional scheduling. Non-spot. Default choice for Windows Shell executor.',
'Runners|Windows 2019 Shell with manual scaling and optional scheduling. Non-spot.',
),
moreDetails1: s__('Runners|No spot. Default choice for Windows Shell executor.'),
moreDetails2: s__(
'Runners|Capacity of 1 enables warm HA through Auto Scaling group re-spawn. Capacity of 2 enables hot HA because the service is available even when a node is lost. Capacity of 3 or more enables hot HA and manual scaling of runner fleet.',
),
},
{
......@@ -43,5 +55,9 @@ export const EASY_BUTTONS = [
),
{ percentage: '100%' },
),
moreDetails1: sprintf(s__('Runners|%{percentage} spot.'), { percentage: '100%' }),
moreDetails2: s__(
'Runners|Capacity of 1 enables warm HA through Auto Scaling group re-spawn. Capacity of 2 enables hot HA because the service is available even when a node is lost. Capacity of 3 or more enables hot HA and manual scaling of runner fleet.',
),
},
];
<script>
import { GlModal, GlSprintf, GlLink } from '@gitlab/ui';
import awsCloudFormationImageUrl from 'images/aws-cloud-formation.png';
import {
GlModal,
GlSprintf,
GlLink,
GlFormRadioGroup,
GlFormRadio,
GlAccordion,
GlAccordionItem,
} from '@gitlab/ui';
import Tracking from '~/tracking';
import { getBaseURL, objectToQuery } from '~/lib/utils/url_utility';
import { getBaseURL, objectToQuery, visitUrl } from '~/lib/utils/url_utility';
import { __, s__ } from '~/locale';
import { README_URL, CF_BASE_URL, TEMPLATES_BASE_URL, EASY_BUTTONS } from './constants';
......@@ -11,6 +18,10 @@ export default {
GlModal,
GlSprintf,
GlLink,
GlFormRadioGroup,
GlFormRadio,
GlAccordion,
GlAccordionItem,
},
mixins: [Tracking.mixin()],
props: {
......@@ -18,13 +29,16 @@ export default {
type: String,
required: true,
},
imgSrc: {
type: String,
required: false,
default: awsCloudFormationImageUrl,
},
},
data() {
return {
selected: this.$options.easyButtons[0],
};
},
methods: {
borderBottom(idx) {
return idx < this.$options.easyButtons.length - 1;
},
easyButtonUrl(easyButton) {
const params = {
templateURL: TEMPLATES_BASE_URL + easyButton.templateName,
......@@ -38,18 +52,26 @@ export default {
label: stackName,
});
},
handleModalPrimary() {
this.trackCiRunnerTemplatesClick(this.selected.stackName);
visitUrl(this.easyButtonUrl(this.selected), true);
},
},
i18n: {
title: s__('Runners|Deploy GitLab Runner in AWS'),
instructions: s__(
'Runners|For each solution, you will choose a capacity. 1 enables warm HA through Auto Scaling group re-spawn. 2 enables hot HA because the service is available even when a node is lost. 3 or more enables hot HA and manual scaling of runner fleet.',
),
dont_see_what_you_are_looking_for: s__(
"Rnners|Don't see what you are looking for? See the full list of options, including a fully customizable option, %{linkStart}here%{linkEnd}.",
'Runners|Select your preferred option here. In the next step, you can choose the capacity for your runner in the AWS CloudFormation console.',
),
note: s__(
'Runners|If you do not select an AWS VPC, the runner will deploy to the Default VPC in the AWS Region you select. Please consult with your AWS administrator to understand if there are any security risks to deploying into the Default VPC in any given region in your AWS account.',
chooseRunner: s__('Runners|Choose your preferred GitLab Runner'),
dontSeeWhatYouAreLookingFor: s__(
"Runners|Don't see what you are looking for? See the full list of options, including a fully customizable option %{linkStart}here%{linkEnd}.",
),
moreDetails: __('More Details'),
lessDetails: __('Less Details'),
},
deployButton: {
text: s__('Runners|Deploy GitLab Runner in AWS'),
attributes: [{ variant: 'confirm' }],
},
closeButton: {
text: __('Cancel'),
......@@ -63,37 +85,41 @@ export default {
<gl-modal
:modal-id="modalId"
:title="$options.i18n.title"
:action-primary="$options.deployButton"
:action-secondary="$options.closeButton"
size="sm"
@primary="handleModalPrimary"
>
<p>{{ $options.i18n.instructions }}</p>
<ul class="gl-list-style-none gl-p-0 gl-mb-0">
<li v-for="easyButton in $options.easyButtons" :key="easyButton.templateName">
<gl-link
:href="easyButtonUrl(easyButton)"
target="_blank"
class="gl-display-flex gl-font-weight-bold"
@click="trackCiRunnerTemplatesClick(easyButton.stackName)"
>
<img
:title="easyButton.stackName"
:alt="easyButton.stackName"
:src="imgSrc"
width="46"
height="46"
class="gl-mt-2 gl-mr-5 gl-mb-6"
/>
<gl-form-radio-group v-model="selected" :label="$options.i18n.chooseRunner" label-sr-only>
<gl-form-radio
v-for="(easyButton, idx) in $options.easyButtons"
:key="easyButton.templateName"
:value="easyButton"
class="gl-py-5 gl-pl-8"
:class="{ 'gl-border-b': borderBottom(idx) }"
>
<div class="gl-mt-n1 gl-pl-4 gl-pb-2 gl-font-weight-bold">
{{ easyButton.description }}
</gl-link>
</li>
</ul>
<gl-accordion :header-level="3" class="gl-pt-3">
<gl-accordion-item
:title="$options.i18n.moreDetails"
:title-visible="$options.i18n.lessDetails"
class="gl-font-weight-normal"
>
<p class="gl-pt-2">{{ easyButton.moreDetails1 }}</p>
<p class="gl-m-0">{{ easyButton.moreDetails2 }}</p>
</gl-accordion-item>
</gl-accordion>
</div>
</gl-form-radio>
</gl-form-radio-group>
<p>
<gl-sprintf :message="$options.i18n.dont_see_what_you_are_looking_for">
<gl-sprintf :message="$options.i18n.dontSeeWhatYouAreLookingFor">
<template #link="{ content }">
<gl-link :href="$options.readmeUrl" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
</p>
<p class="gl-font-sm gl-mb-0">{{ $options.i18n.note }}</p>
</gl-modal>
</template>
......@@ -21943,6 +21943,9 @@ msgstr ""
msgid "Legacy burndown chart"
msgstr ""
msgid "Less Details"
msgstr ""
msgid "Let's Encrypt does not accept emails on example.com"
msgstr ""
......@@ -24048,6 +24051,9 @@ msgstr ""
msgid "Months"
msgstr ""
msgid "More Details"
msgstr ""
msgid "More Information"
msgstr ""
......@@ -31705,9 +31711,6 @@ msgstr ""
msgid "RightSidebar|deleting the"
msgstr ""
msgid "Rnners|Don't see what you are looking for? See the full list of options, including a fully customizable option, %{linkStart}here%{linkEnd}."
msgstr ""
msgid "Roadmap"
msgstr ""
......@@ -31783,6 +31786,12 @@ msgstr ""
msgid "Runners page."
msgstr ""
msgid "Runners|%{percentage} spot."
msgstr ""
msgid "Runners|A capacity of 1 enables warm HA through Auto Scaling group re-spawn. A capacity of 2 enables hot HA because the service is available even when a node is lost. A capacity of 3 or more enables hot HA and manual scaling of runner fleet."
msgstr ""
msgid "Runners|Active"
msgstr ""
......@@ -31792,7 +31801,7 @@ msgstr ""
msgid "Runners|Amazon Linux 2 Docker HA with manual scaling and optional scheduling. %{percentage} spot."
msgstr ""
msgid "Runners|Amazon Linux 2 Docker HA with manual scaling and optional scheduling. Non-spot. Default choice for Linux Docker executor."
msgid "Runners|Amazon Linux 2 Docker HA with manual scaling and optional scheduling. Non-spot."
msgstr ""
msgid "Runners|An error has occurred fetching instructions"
......@@ -31819,9 +31828,15 @@ msgstr ""
msgid "Runners|Can run untagged jobs"
msgstr ""
msgid "Runners|Capacity of 1 enables warm HA through Auto Scaling group re-spawn. Capacity of 2 enables hot HA because the service is available even when a node is lost. Capacity of 3 or more enables hot HA and manual scaling of runner fleet."
msgstr ""
msgid "Runners|Change to project runner"
msgstr ""
msgid "Runners|Choose your preferred GitLab Runner"
msgstr ""
msgid "Runners|Command to register runner"
msgstr ""
......@@ -31849,6 +31864,9 @@ msgstr ""
msgid "Runners|Details"
msgstr ""
msgid "Runners|Don't see what you are looking for? See the full list of options, including a fully customizable option %{linkStart}here%{linkEnd}."
msgstr ""
msgid "Runners|Download and install binary"
msgstr ""
......@@ -31858,18 +31876,12 @@ msgstr ""
msgid "Runners|Enter the number of seconds. This timeout takes precedence over lower timeouts set for the project."
msgstr ""
msgid "Runners|For each solution, you will choose a capacity. 1 enables warm HA through Auto Scaling group re-spawn. 2 enables hot HA because the service is available even when a node is lost. 3 or more enables hot HA and manual scaling of runner fleet."
msgstr ""
msgid "Runners|Group"
msgstr ""
msgid "Runners|IP Address"
msgstr ""
msgid "Runners|If you do not select an AWS VPC, the runner will deploy to the Default VPC in the AWS Region you select. Please consult with your AWS administrator to understand if there are any security risks to deploying into the Default VPC in any given region in your AWS account."
msgstr ""
msgid "Runners|Install a runner"
msgstr ""
......@@ -31912,6 +31924,12 @@ msgstr ""
msgid "Runners|No recent contact from this runner; last contact was %{timeAgo}"
msgstr ""
msgid "Runners|No spot. Default choice for Windows Shell executor."
msgstr ""
msgid "Runners|No spot. This is the default choice for Linux Docker executor."
msgstr ""
msgid "Runners|Not accepting jobs"
msgstr ""
......@@ -32011,6 +32029,9 @@ msgstr ""
msgid "Runners|Runs untagged jobs"
msgstr ""
msgid "Runners|Select your preferred option here. In the next step, you can choose the capacity for your runner in the AWS CloudFormation console."
msgstr ""
msgid "Runners|Shared runners are available to every project in a GitLab instance. If you want a runner to build only specific projects, restrict the project in the table below. After you restrict a runner to a project, you cannot change it back to a shared runner."
msgstr ""
......@@ -32092,7 +32113,7 @@ msgstr ""
msgid "Runners|Windows 2019 Shell with manual scaling and optional scheduling. %{percentage} spot."
msgstr ""
msgid "Runners|Windows 2019 Shell with manual scaling and optional scheduling. Non-spot. Default choice for Windows Shell executor."
msgid "Runners|Windows 2019 Shell with manual scaling and optional scheduling. Non-spot."
msgstr ""
msgid "Runners|You are about to change this instance runner to a project runner. This operation is not reversible. Are you sure you want to continue?"
......
......@@ -2,6 +2,7 @@
exports[`RunnerAwsDeploymentsModal renders the modal 1`] = `
<gl-modal-stub
actionprimary="[object Object]"
actionsecondary="[object Object]"
dismisslabel="Close"
modalclass=""
......@@ -11,100 +12,161 @@ exports[`RunnerAwsDeploymentsModal renders the modal 1`] = `
titletag="h4"
>
<p>
For each solution, you will choose a capacity. 1 enables warm HA through Auto Scaling group re-spawn. 2 enables hot HA because the service is available even when a node is lost. 3 or more enables hot HA and manual scaling of runner fleet.
Select your preferred option here. In the next step, you can choose the capacity for your runner in the AWS CloudFormation console.
</p>
<ul
class="gl-list-style-none gl-p-0 gl-mb-0"
<gl-form-radio-group-stub
checked="[object Object]"
disabledfield="disabled"
htmlfield="html"
label="Choose your preferred GitLab Runner"
label-sr-only=""
options=""
textfield="text"
valuefield="value"
>
<li>
<gl-link-stub
class="gl-display-flex gl-font-weight-bold"
href="https://us-west-2.console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/create/review?templateURL=https%3A%2F%2Fgl-public-templates.s3.amazonaws.com%2Fcfn%2Fexperimental%2Feasybutton-amazon-linux-2-docker-manual-scaling-with-schedule-ondemandonly.cf.yml&stackName=linux-docker-nonspot&param_3GITLABRunnerInstanceURL=http%3A%2F%2Ftest.host"
target="_blank"
<gl-form-radio-stub
class="gl-py-5 gl-pl-8 gl-border-b"
value="[object Object]"
>
<div
class="gl-mt-n1 gl-pl-4 gl-pb-2 gl-font-weight-bold"
>
<img
alt="linux-docker-nonspot"
class="gl-mt-2 gl-mr-5 gl-mb-6"
height="46"
src="/assets/aws-cloud-formation.png"
title="linux-docker-nonspot"
width="46"
/>
Amazon Linux 2 Docker HA with manual scaling and optional scheduling. Non-spot. Default choice for Linux Docker executor.
</gl-link-stub>
</li>
<li>
<gl-link-stub
class="gl-display-flex gl-font-weight-bold"
href="https://us-west-2.console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/create/review?templateURL=https%3A%2F%2Fgl-public-templates.s3.amazonaws.com%2Fcfn%2Fexperimental%2Feasybutton-amazon-linux-2-docker-manual-scaling-with-schedule-spotonly.cf.yml&stackName=linux-docker-spotonly&param_3GITLABRunnerInstanceURL=http%3A%2F%2Ftest.host"
target="_blank"
Amazon Linux 2 Docker HA with manual scaling and optional scheduling. Non-spot.
<gl-accordion-stub
class="gl-pt-3"
headerlevel="3"
>
<gl-accordion-item-stub
class="gl-font-weight-normal"
title="More Details"
title-visible="Less Details"
>
<p
class="gl-pt-2"
>
No spot. This is the default choice for Linux Docker executor.
</p>
<p
class="gl-m-0"
>
A capacity of 1 enables warm HA through Auto Scaling group re-spawn. A capacity of 2 enables hot HA because the service is available even when a node is lost. A capacity of 3 or more enables hot HA and manual scaling of runner fleet.
</p>
</gl-accordion-item-stub>
</gl-accordion-stub>
</div>
</gl-form-radio-stub>
<gl-form-radio-stub
class="gl-py-5 gl-pl-8 gl-border-b"
value="[object Object]"
>
<div
class="gl-mt-n1 gl-pl-4 gl-pb-2 gl-font-weight-bold"
>
<img
alt="linux-docker-spotonly"
class="gl-mt-2 gl-mr-5 gl-mb-6"
height="46"
src="/assets/aws-cloud-formation.png"
title="linux-docker-spotonly"
width="46"
/>
Amazon Linux 2 Docker HA with manual scaling and optional scheduling. 100% spot.
</gl-link-stub>
</li>
<li>
<gl-link-stub
class="gl-display-flex gl-font-weight-bold"
href="https://us-west-2.console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/create/review?templateURL=https%3A%2F%2Fgl-public-templates.s3.amazonaws.com%2Fcfn%2Fexperimental%2Feasybutton-windows2019-shell-manual-scaling-with-scheduling-ondemandonly.cf.yml&stackName=win2019-shell-non-spot&param_3GITLABRunnerInstanceURL=http%3A%2F%2Ftest.host"
target="_blank"
<gl-accordion-stub
class="gl-pt-3"
headerlevel="3"
>
<gl-accordion-item-stub
class="gl-font-weight-normal"
title="More Details"
title-visible="Less Details"
>
<p
class="gl-pt-2"
>
100% spot.
</p>
<p
class="gl-m-0"
>
Capacity of 1 enables warm HA through Auto Scaling group re-spawn. Capacity of 2 enables hot HA because the service is available even when a node is lost. Capacity of 3 or more enables hot HA and manual scaling of runner fleet.
</p>
</gl-accordion-item-stub>
</gl-accordion-stub>
</div>
</gl-form-radio-stub>
<gl-form-radio-stub
class="gl-py-5 gl-pl-8 gl-border-b"
value="[object Object]"
>
<div
class="gl-mt-n1 gl-pl-4 gl-pb-2 gl-font-weight-bold"
>
<img
alt="win2019-shell-non-spot"
class="gl-mt-2 gl-mr-5 gl-mb-6"
height="46"
src="/assets/aws-cloud-formation.png"
title="win2019-shell-non-spot"
width="46"
/>
Windows 2019 Shell with manual scaling and optional scheduling. Non-spot. Default choice for Windows Shell executor.
</gl-link-stub>
</li>
<li>
<gl-link-stub
class="gl-display-flex gl-font-weight-bold"
href="https://us-west-2.console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/create/review?templateURL=https%3A%2F%2Fgl-public-templates.s3.amazonaws.com%2Fcfn%2Fexperimental%2Feasybutton-windows2019-shell-manual-scaling-with-scheduling-spotonly.cf.yml&stackName=win2019-shell-spot&param_3GITLABRunnerInstanceURL=http%3A%2F%2Ftest.host"
target="_blank"
Windows 2019 Shell with manual scaling and optional scheduling. Non-spot.
<gl-accordion-stub
class="gl-pt-3"
headerlevel="3"
>
<gl-accordion-item-stub
class="gl-font-weight-normal"
title="More Details"
title-visible="Less Details"
>
<p
class="gl-pt-2"
>
No spot. Default choice for Windows Shell executor.
</p>
<p
class="gl-m-0"
>
Capacity of 1 enables warm HA through Auto Scaling group re-spawn. Capacity of 2 enables hot HA because the service is available even when a node is lost. Capacity of 3 or more enables hot HA and manual scaling of runner fleet.
</p>
</gl-accordion-item-stub>
</gl-accordion-stub>
</div>
</gl-form-radio-stub>
<gl-form-radio-stub
class="gl-py-5 gl-pl-8"
value="[object Object]"
>
<div
class="gl-mt-n1 gl-pl-4 gl-pb-2 gl-font-weight-bold"
>
<img
alt="win2019-shell-spot"
class="gl-mt-2 gl-mr-5 gl-mb-6"
height="46"
src="/assets/aws-cloud-formation.png"
title="win2019-shell-spot"
width="46"
/>
Windows 2019 Shell with manual scaling and optional scheduling. 100% spot.
</gl-link-stub>
</li>
</ul>
<gl-accordion-stub
class="gl-pt-3"
headerlevel="3"
>
<gl-accordion-item-stub
class="gl-font-weight-normal"
title="More Details"
title-visible="Less Details"
>
<p
class="gl-pt-2"
>
100% spot.
</p>
<p
class="gl-m-0"
>
Capacity of 1 enables warm HA through Auto Scaling group re-spawn. Capacity of 2 enables hot HA because the service is available even when a node is lost. Capacity of 3 or more enables hot HA and manual scaling of runner fleet.
</p>
</gl-accordion-item-stub>
</gl-accordion-stub>
</div>
</gl-form-radio-stub>
</gl-form-radio-group-stub>
<p>
<gl-sprintf-stub
message="Don't see what you are looking for? See the full list of options, including a fully customizable option, %{linkStart}here%{linkEnd}."
message="Don't see what you are looking for? See the full list of options, including a fully customizable option %{linkStart}here%{linkEnd}."
/>
</p>
<p
class="gl-font-sm gl-mb-0"
>
If you do not select an AWS VPC, the runner will deploy to the Default VPC in the AWS Region you select. Please consult with your AWS administrator to understand if there are any security risks to deploying into the Default VPC in any given region in your AWS account.
</p>
</gl-modal-stub>
`;
import { GlLink } from '@gitlab/ui';
import { GlModal, GlFormRadio } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { getBaseURL } from '~/lib/utils/url_utility';
import { getBaseURL, visitUrl } from '~/lib/utils/url_utility';
import { mockTracking } from 'helpers/tracking_helper';
import {
CF_BASE_URL,
......@@ -9,17 +9,21 @@ import {
} from '~/vue_shared/components/runner_aws_deployments/constants';
import RunnerAwsDeploymentsModal from '~/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal.vue';
jest.mock('~/lib/utils/url_utility', () => ({
...jest.requireActual('~/lib/utils/url_utility'),
visitUrl: jest.fn(),
}));
describe('RunnerAwsDeploymentsModal', () => {
let wrapper;
let trackingSpy;
const findEasyButtons = () => wrapper.findAllComponents(GlLink);
const findModal = () => wrapper.findComponent(GlModal);
const findEasyButtons = () => wrapper.findAllComponents(GlFormRadio);
const createComponent = () => {
wrapper = shallowMount(RunnerAwsDeploymentsModal, {
propsData: {
modalId: 'runner-aws-deployments-modal',
imgSrc: '/assets/aws-cloud-formation.png',
},
});
};
......@@ -41,31 +45,25 @@ describe('RunnerAwsDeploymentsModal', () => {
});
describe('first easy button', () => {
const findFirstButton = () => findEasyButtons().at(0);
it('should contain the correct description', () => {
expect(findFirstButton().text()).toBe(EASY_BUTTONS[0].description);
expect(findEasyButtons().at(0).text()).toContain(EASY_BUTTONS[0].description);
});
it('should contain the correct link', () => {
const link = findFirstButton().attributes('href');
const templateUrl = encodeURIComponent(TEMPLATES_BASE_URL + EASY_BUTTONS[0].templateName);
const { stackName } = EASY_BUTTONS[0];
const instanceUrl = encodeURIComponent(getBaseURL());
const url = `${CF_BASE_URL}templateURL=${templateUrl}&stackName=${stackName}&param_3GITLABRunnerInstanceURL=${instanceUrl}`;
findModal().vm.$emit('primary');
expect(link.startsWith(CF_BASE_URL)).toBe(true);
expect(
link.includes(
`templateURL=${encodeURIComponent(TEMPLATES_BASE_URL + EASY_BUTTONS[0].templateName)}`,
),
).toBe(true);
expect(link.includes(`stackName=${EASY_BUTTONS[0].stackName}`)).toBe(true);
expect(
link.includes(`param_3GITLABRunnerInstanceURL=${encodeURIComponent(getBaseURL())}`),
).toBe(true);
expect(visitUrl).toHaveBeenCalledWith(url, true);
});
it('should track an event when clicked', () => {
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
findFirstButton().vm.$emit('click');
findModal().vm.$emit('primary');
expect(trackingSpy).toHaveBeenCalledTimes(1);
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'template_clicked', {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment