diff --git a/app/kubernetes/views/resource-pools/create/createResourcePool.html b/app/kubernetes/views/resource-pools/create/createResourcePool.html index dbe1365e9..ed4248de2 100644 --- a/app/kubernetes/views/resource-pools/create/createResourcePool.html +++ b/app/kubernetes/views/resource-pools/create/createResourcePool.html @@ -93,17 +93,18 @@
- - + >
-
+
- - + visible-tooltip="true" + >
diff --git a/app/kubernetes/views/resource-pools/create/createResourcePoolController.js b/app/kubernetes/views/resource-pools/create/createResourcePoolController.js index ae139bd3a..6384f4e03 100644 --- a/app/kubernetes/views/resource-pools/create/createResourcePoolController.js +++ b/app/kubernetes/views/resource-pools/create/createResourcePoolController.js @@ -33,6 +33,8 @@ class KubernetesCreateResourcePoolController { this.onToggleResourceQuota = this.onToggleResourceQuota.bind(this); this.onChangeIngressControllerAvailability = this.onChangeIngressControllerAvailability.bind(this); this.onRegistriesChange = this.onRegistriesChange.bind(this); + this.handleMemoryLimitChange = this.handleMemoryLimitChange.bind(this); + this.handleCpuLimitChange = this.handleCpuLimitChange.bind(this); } /* #endregion */ @@ -101,6 +103,18 @@ class KubernetesCreateResourcePoolController { } } + handleMemoryLimitChange(memoryLimit) { + return this.$async(async () => { + this.formValues.MemoryLimit = memoryLimit; + }); + } + + handleCpuLimitChange(cpuLimit) { + return this.$async(async () => { + this.formValues.CpuLimit = cpuLimit; + }); + } + /* #region CREATE NAMESPACE */ createResourcePool() { return this.$async(async () => { diff --git a/app/kubernetes/views/resource-pools/edit/resourcePool.html b/app/kubernetes/views/resource-pools/edit/resourcePool.html index 40c149173..dac67d021 100644 --- a/app/kubernetes/views/resource-pools/edit/resourcePool.html +++ b/app/kubernetes/views/resource-pools/edit/resourcePool.html @@ -80,15 +80,18 @@
- + value="ctrl.formValues.MemoryLimit" + on-change="(ctrl.handleMemoryLimitChange)" + visible-tooltip="true" + data-cy="k8sNamespaceEdit-memoryLimitSlider" + >
-
+
@@ -117,14 +121,16 @@
- + value="ctrl.formValues.CpuLimit" + on-change="(ctrl.handleCpuLimitChange)" + data-cy="k8sNamespaceEdit-cpuLimitSlider" + visible-tooltip="true" + >
diff --git a/app/kubernetes/views/resource-pools/edit/resourcePoolController.js b/app/kubernetes/views/resource-pools/edit/resourcePoolController.js index 331082811..39d85d287 100644 --- a/app/kubernetes/views/resource-pools/edit/resourcePoolController.js +++ b/app/kubernetes/views/resource-pools/edit/resourcePoolController.js @@ -69,6 +69,8 @@ class KubernetesResourcePoolController { this.onToggleStorageQuota = this.onToggleStorageQuota.bind(this); this.onChangeIngressControllerAvailability = this.onChangeIngressControllerAvailability.bind(this); this.onRegistriesChange = this.onRegistriesChange.bind(this); + this.handleMemoryLimitChange = this.handleMemoryLimitChange.bind(this); + this.handleCpuLimitChange = this.handleCpuLimitChange.bind(this); } /* #endregion */ @@ -122,6 +124,18 @@ class KubernetesResourcePoolController { } } + handleMemoryLimitChange(memoryLimit) { + return this.$async(async () => { + this.formValues.MemoryLimit = memoryLimit; + }); + } + + handleCpuLimitChange(cpuLimit) { + return this.$async(async () => { + this.formValues.CpuLimit = cpuLimit; + }); + } + showEditor() { this.state.showEditorTab = true; this.selectTab(2); diff --git a/app/portainer/react/components/index.ts b/app/portainer/react/components/index.ts index 14eeff6f0..2a2ee45dc 100644 --- a/app/portainer/react/components/index.ts +++ b/app/portainer/react/components/index.ts @@ -33,6 +33,7 @@ import { FallbackImage } from '@@/FallbackImage'; import { BadgeIcon } from '@@/BadgeIcon'; import { TeamsSelector } from '@@/TeamsSelector'; import { PortainerSelect } from '@@/form-components/PortainerSelect'; +import { Slider } from '@@/form-components/Slider'; import { fileUploadField } from './file-upload-field'; import { switchField } from './switch-field'; @@ -184,6 +185,18 @@ export const componentsModule = angular 'isClearable', ]) ) + .component( + 'porSlider', + r2a(Slider, [ + 'min', + 'max', + 'step', + 'value', + 'onChange', + 'visibleTooltip', + 'dataCy', + ]) + ) .component( 'porAccessManagementUsersSelector', r2a(PorAccessManagementUsersSelector, ['onChange', 'options', 'value']) diff --git a/app/react/components/form-components/Slider/Slider.module.css b/app/react/components/form-components/Slider/Slider.module.css index db5aa38e5..6cd2af8d5 100644 --- a/app/react/components/form-components/Slider/Slider.module.css +++ b/app/react/components/form-components/Slider/Slider.module.css @@ -7,26 +7,50 @@ } .slider :global .rc-slider-handle { - width: 32px; - height: 32px; - margin-top: -14px; + @apply border-blue-8 border-2; + width: 24px; + height: 24px; + margin-top: -8px; border-radius: 16px; cursor: pointer; - background-color: #0db9f0; + background-color: #ffffff; +} + +.slider :global .rc-slider-track { + @apply bg-blue-8; } .slider :global .rc-slider-handle:after { position: absolute; - top: 10px; - left: 10px; - width: 8px; - height: 8px; + top: 8px; + left: 8px; + width: 9px; + height: 9px; background: #ffffff; - border-radius: 4px; + border-radius: 5px; content: ''; } -.slider :global .rc-slider-mark-text, -.slider :global .rc-slider-tooltip-inner { - font-family: Inter, serif; +.slider :global .rc-slider-mark-text { + font-size: 14px; + color: var(--text-body-color); +} + +.slider :global .rc-slider-tooltip-arrow { + bottom: 2px; + border-top-color: var(--bg-tooltip-color); +} + +.slider :global .rc-slider-tooltip-placement-top { + padding: 6px 0px; +} + +.slider :global .rc-slider-tooltip-inner { + font-size: 14px; + color: var(--text-tooltip-color); + height: fit-content; + background-color: var(--bg-tooltip-color); + box-shadow: 0 2px 4px 0 rgb(34 36 38 / 12%), 0 2px 10px 0 rgb(34 36 38 / 15%); + padding: 8px 12px; + text-align: center; } diff --git a/app/react/components/form-components/Slider/Slider.stories.tsx b/app/react/components/form-components/Slider/Slider.stories.tsx index 76f14457a..1cd7da5bc 100644 --- a/app/react/components/form-components/Slider/Slider.stories.tsx +++ b/app/react/components/form-components/Slider/Slider.stories.tsx @@ -8,7 +8,14 @@ export default { title: 'Components/Form/Slider', } as Meta; -function Template({ value, min, max, step }: JSX.IntrinsicAttributes & Props) { +function Template({ + value, + min, + max, + step, + dataCy, + visibleTooltip, +}: JSX.IntrinsicAttributes & Props) { const [sliderValue, setSliderValue] = useState(min); useEffect(() => { @@ -22,6 +29,8 @@ function Template({ value, min, max, step }: JSX.IntrinsicAttributes & Props) { step={step} value={sliderValue} onChange={setSliderValue} + dataCy={dataCy} + visibleTooltip={visibleTooltip} /> ); } @@ -32,4 +41,6 @@ Primary.args = { max: 100, step: 1, value: 5, + visibleTooltip: true, + dataCy: 'someView-coolSlider', }; diff --git a/app/react/components/form-components/Slider/Slider.test.tsx b/app/react/components/form-components/Slider/Slider.test.tsx index 403881753..cd5d375a6 100644 --- a/app/react/components/form-components/Slider/Slider.test.tsx +++ b/app/react/components/form-components/Slider/Slider.test.tsx @@ -8,9 +8,19 @@ function renderDefault({ step = 1, value = min, onChange = () => {}, + dataCy = 'someView-coolSlider', + visibleTooltip = true, }: Partial = {}) { return render( - + ); } diff --git a/app/react/components/form-components/Slider/Slider.tsx b/app/react/components/form-components/Slider/Slider.tsx index e60c1fb57..a07d55409 100644 --- a/app/react/components/form-components/Slider/Slider.tsx +++ b/app/react/components/form-components/Slider/Slider.tsx @@ -9,13 +9,25 @@ export interface Props { step: number; value: number; onChange: (value: number) => void; + // true if you want to always show the tooltip + dataCy: string; + visibleTooltip?: boolean; } -export function Slider({ min, max, step, value, onChange }: Props) { +export function Slider({ + min, + max, + step, + value, + onChange, + dataCy, + visibleTooltip: visible, +}: Props) { const SliderWithTooltip = RcSlider.createSliderWithTooltip(RcSlider); + // if the tooltip is always visible, hide the marks when tooltip value gets close to the edges const marks = { - [min]: translateMinValue(min), - [max]: max.toString(), + [min]: visible && value / max < 0.1 ? '' : translateMinValue(min), + [max]: visible && value / max > 0.9 ? '' : max.toString(), }; return ( @@ -29,6 +41,11 @@ export function Slider({ min, max, step, value, onChange }: Props) { defaultValue={value} onAfterChange={onChange} className={styles.slider} + tipProps={{ visible }} + railStyle={{ height: 8 }} + trackStyle={{ height: 8 }} + dotStyle={{ visibility: 'hidden' }} + data-cy={dataCy} />
); diff --git a/app/react/components/form-components/Slider/index.ts b/app/react/components/form-components/Slider/index.ts new file mode 100644 index 000000000..f84acd84c --- /dev/null +++ b/app/react/components/form-components/Slider/index.ts @@ -0,0 +1 @@ +export { Slider } from './Slider';