bfg-common 1.4.881 → 1.4.882

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,664 +1,667 @@
1
- <template>
2
- <div>
3
- <ui-portlet
4
- class="attributes"
5
- test-id="attributes-portlet"
6
- :title="portletTitle"
7
- :dragged="props.dragged"
8
- :dragged-any="props.draggedAny"
9
- :is-open="props.isOpen"
10
- :portlet-id="props.portletId"
11
- :is-loading="props.isLoading"
12
- @toggle="onTogglePortlet"
13
- >
14
- <template #portletBody="{ isLoadingBody }">
15
- <ui-data-table
16
- test-id="attributes-portlet-table"
17
- :data="data"
18
- :options="attributeTableHeaderOptions"
19
- server-off
20
- :default-layout="false"
21
- size="sm"
22
- :loading="isLoadingBody"
23
- :skeleton="skeletonData"
24
- >
25
- <template #notFound>
26
- <div
27
- v-if="!allRowsCount"
28
- class="add-new-attribute-button-container"
29
- >
30
- <ui-button
31
- test-id="add-new-attribute-button"
32
- variant="text"
33
- is-without-height
34
- is-without-sizes
35
- :disabled="hasEditRow"
36
- @click="onAddNew"
37
- >
38
- {{ localization.zabbix.addNew }}...
39
- </ui-button>
40
- </div>
41
- </template>
42
- <!-- :loading="loading"-->
43
- <template #icon="{ item }">
44
- <div v-if="item.data.type === 'options'" class="actions-container">
45
- <span
46
- :id="`actions-attributes-${item.data.row}`"
47
- :class="[
48
- 'icon-container',
49
- {
50
- selected: isShow[`action-attributes-${item.data.row}`],
51
- },
52
- ]"
53
- @click="onShowActions(item.data.row)"
54
- >
55
- <ui-icon :name="item.data.icon" width="16" height="16" />
56
- </span>
57
- <ui-popup-window
58
- v-model="isShow[`action-attributes-${item.data.row}`]"
59
- :elem-id="`actions-attributes-${item.data.row}`"
60
- width="fit-content"
61
- left
62
- bottom
63
- >
64
- <div class="actions-dropdown-container">
65
- <div
66
- :class="['action', { disabled: hasEditRow }]"
67
- data-id="edit-attribute-action"
68
- @click="onEditAttribute(item.data.row)"
69
- >
70
- <!-- @click="onShowDetails(item.data.row)"-->
71
- <ui-icon name="edit" width="16" height="16" />
72
- <span>{{ localization.common.edit }}</span>
73
- </div>
74
- <div
75
- class="action-remove"
76
- data-id="delete-attribute-action"
77
- @click="onShowRemoveDialog(item.data.row)"
78
- >
79
- <ui-icon name="delete" width="16" height="16" />
80
- <span>{{ localization.common.delete }}</span>
81
- </div>
82
- </div>
83
- </ui-popup-window>
84
- </div>
85
- <template v-if="item.data.type === 'attribute'">
86
- <input
87
- v-model="editData.attribute"
88
- data-id="attribute-name-input"
89
- :class="[
90
- 'table-input',
91
- {
92
- 'table-input-error':
93
- validationData.attribute && isShowValidation,
94
- },
95
- ]"
96
- :placeholder="localization.common[item.data.type]"
97
- type="text"
98
- @click.prevent.stop
99
- />
100
- </template>
101
-
102
- <template v-else-if="item.data.type === 'value'">
103
- <input
104
- v-model="editData.value"
105
- :class="[
106
- 'table-input',
107
- {
108
- 'table-input-error':
109
- validationData.value && isShowValidation,
110
- },
111
- ]"
112
- :placeholder="localization.common[item.data.type]"
113
- type="text"
114
- data-id="attribute-value-input"
115
- @click.prevent.stop
116
- />
117
- </template>
118
- <template v-else-if="item.data.type === 'type'">
119
- <div class="category-select">
120
- <ui-select
121
- v-model="editData.type"
122
- :items="selectItems"
123
- :placeholder="localization.common.chooseCategory"
124
- width="165px"
125
- select-width="100%"
126
- :show-text="true"
127
- :error="validationData.type && isShowValidation"
128
- :disabled="isEditRow"
129
- size="sm"
130
- test-id="attribute-type-select"
131
- />
132
- </div>
133
- </template>
134
- <template v-else-if="item.data.type === 'actions'">
135
- <div class="edit-actions-container">
136
- <span
137
- class="hide-row"
138
- data-id="hide-create-attribute-row"
139
- @click.prevent.stop="onHideEditRow"
140
- >
141
- <ui-icon name="close" width="16" height="16" />
142
- </span>
143
- <span
144
- class="create-row"
145
- data-id="create-attribute-accept"
146
- @click.prevent.stop="onAddAttribute"
147
- >
148
- <ui-icon name="status-check" width="16" height="16" />
149
- </span>
150
- </div>
151
- </template>
152
- </template>
153
- </ui-data-table>
154
- </template>
155
- <template #portletFooter="{ isLoadingFooter }">
156
- <div class="footer">
157
- <ui-button
158
- v-if="!isLoadingFooter"
159
- test-id="add-new-attribute-button"
160
- variant="text"
161
- is-without-height
162
- is-without-sizes
163
- :disabled="hasEditRow"
164
- @click="onAddNew"
165
- >
166
- {{ localization.zabbix.addNew }}...
167
- </ui-button>
168
- <ui-portlet-skeleton-footer v-else />
169
- </div>
170
- </template>
171
- </ui-portlet>
172
-
173
- <ui-popup
174
- v-if="isShowRemoveDialog"
175
- test-id="remove-attribute-dialog"
176
- icon-name="info-status"
177
- :texts="removeAttributeDialogTexts"
178
- :message="removeAttributeMessage"
179
- :title="localization.zabbix.deleteConfirmation"
180
- @hide="onHideRemoveDialog"
181
- @submit="onRemoveAttribute"
182
- />
183
- </div>
184
- </template>
185
-
186
- <script setup lang="ts">
187
- import type {
188
- UI_I_DataTable,
189
- UI_I_DataTableHeader,
190
- UI_I_DataTableBody,
191
- } from '~/node_modules/bfg-uikit/components/ui/dataTable/models/interfaces'
192
- import type { UI_I_Dropdown } from '~/node_modules/bfg-uikit/components/ui/dropdown/models/interfaces'
193
- import type { UI_I_ModalTexts } from '~/node_modules/bfg-uikit/components/ui/modal/models/interfaces'
194
- import type {
195
- UI_I_Localization,
196
- UI_I_ArbitraryObject,
197
- } from '~/lib/models/interfaces'
198
- import type { UI_I_NewAttributeData } from '~/components/common/portlets/customAttributes/lib/models/interfaces'
199
- import {
200
- attributeTableHeaderOptions,
201
- attributeTableHeaderDataFunc,
202
- createRow,
203
- makeNewAttributeCreateActionRowFunc,
204
- availableTypesFunc,
205
- skeletonData,
206
- } from '~/components/common/portlets/customAttributes/lib/config/config'
207
-
208
- const props = defineProps<{
209
- portletId: string
210
- dragged?: boolean
211
- isOpen?: boolean
212
- draggedAny?: boolean
213
- selectedItemName: string
214
- type: UI_I_Dropdown
215
- bodyItems: UI_I_DataTableBody[]
216
- isLoading: boolean
217
- }>()
218
-
219
- const localization = computed<UI_I_Localization>(() => useLocal())
220
-
221
- const portletTitle = computed<string>(() => {
222
- const count = allRowsCount.value
223
- ? ` (${isEditRow.value ? allRowsCount.value + 1 : allRowsCount.value})`
224
- : ''
225
-
226
- return localization.value.common.customAttributes + count
227
- })
228
-
229
- const isShow = ref<UI_I_ArbitraryObject<boolean>>({})
230
-
231
- const onShowActions = (id: number): void => {
232
- isShow.value[`action-attributes-${id}`] =
233
- !isShow.value[`action-attributes-${id}`]
234
- }
235
-
236
- const attributesHeadItems = computed<UI_I_DataTableHeader[]>(() =>
237
- attributeTableHeaderDataFunc(localization.value)
238
- )
239
-
240
- const attributesBodyItems = ref<UI_I_DataTableBody[]>([])
241
-
242
- const allRowsCount = computed<number>(
243
- () =>
244
- attributesBodyItems.value.filter(
245
- (row: UI_I_DataTableBody) => !row.actionRow
246
- ).length
247
- )
248
-
249
- const hasEditRow = computed<boolean>(
250
- () => !!attributesBodyItems.value.find((row) => row.actionRow)
251
- )
252
-
253
- const selectedType = computed<UI_I_Dropdown>(() => ({
254
- text: props.type.text,
255
- value: props.type.value,
256
- selected: true,
257
- }))
258
-
259
- const editData = ref<UI_I_NewAttributeData>({
260
- attribute: '',
261
- value: '',
262
- type: selectedType.value.text,
263
- })
264
-
265
- const resetFields = (): void => {
266
- editData.value = {
267
- attribute: '',
268
- value: '',
269
- type: selectedType.value.text,
270
- }
271
- }
272
-
273
- const isShowValidation = ref<boolean>(false)
274
- const validationData = computed<{
275
- attribute: boolean
276
- value: boolean
277
- type: boolean
278
- }>(() => ({
279
- attribute: !editData.value.attribute.trim(),
280
- value: !editData.value.value.trim(),
281
- type: !editData.value.type.trim(),
282
- }))
283
-
284
- watch(
285
- validationData,
286
- (newValue: { attribute: boolean; value: boolean; type: boolean }) => {
287
- !newValue.attribute &&
288
- !newValue.value &&
289
- !newValue.type &&
290
- (isShowValidation.value = false)
291
- },
292
- { deep: true }
293
- )
294
-
295
- const onAddAttribute = (): void => {
296
- if (
297
- validationData.value.attribute ||
298
- validationData.value.value ||
299
- validationData.value.type
300
- ) {
301
- isShowValidation.value = true
302
- return
303
- }
304
-
305
- if (isEditRow.value && editRowData.value) {
306
- const editedRowData = createRow(editRowData.value.row, editData.value)
307
-
308
- attributesBodyItems.value = attributesBodyItems.value.map((row) =>
309
- row.row === editRowData.value?.row ? editedRowData : row
310
- )
311
-
312
- emits('edit-attribute', editedRowData)
313
- isShow.value[`action-attributes-${editRowData.value?.row}`] = false
314
- editRowData.value = undefined
315
- isEditRow.value = false
316
- } else {
317
- const withoutEditRowData = attributesBodyItems.value.filter(
318
- (row) => !row.actionRow
319
- )
320
-
321
- const rowsIds = withoutEditRowData.map((row) => row.row).sort()
322
- let lastId = rowsIds[rowsIds.length - 1]
323
- lastId = lastId === undefined ? -1 : lastId
324
-
325
- const addedRowData = createRow(lastId + 1, editData.value)
326
-
327
- attributesBodyItems.value.push(addedRowData)
328
- emits('add-attribute', addedRowData)
329
- attributesBodyItems.value = attributesBodyItems.value.filter(
330
- (row) => !row.actionRow
331
- )
332
- }
333
-
334
- resetFields()
335
- }
336
-
337
- const onHideEditRow = (): void => {
338
- if (isEditRow.value && editRowData.value) {
339
- attributesBodyItems.value = attributesBodyItems.value.map((row) =>
340
- row.row === editRowData.value?.row ? editRowData.value : row
341
- )
342
- isShow.value[`action-attributes-${editRowData.value?.row}`] = false
343
- editRowData.value = undefined
344
- isEditRow.value = false
345
- } else {
346
- attributesBodyItems.value = attributesBodyItems.value.filter(
347
- (row) => !row.actionRow
348
- )
349
- }
350
- resetFields()
351
- }
352
-
353
- watch(
354
- () => props.bodyItems,
355
- (newValue: UI_I_DataTableBody[]) => {
356
- attributesBodyItems.value = newValue
357
- },
358
- { deep: true, immediate: true }
359
- )
360
-
361
- const data = computed<UI_I_DataTable>(() => ({
362
- id: 'attributes',
363
- selectedRows: [],
364
- isAllSelected: false,
365
- title: '',
366
- subTitle: '',
367
- header: attributesHeadItems.value,
368
- body: attributesBodyItems.value,
369
- }))
370
-
371
- const selectItems = ref<UI_I_Dropdown[]>(
372
- useDeepCopy(availableTypesFunc(selectedType.value))
373
- )
374
-
375
- const onAddNew = (): void => {
376
- const rowsIds = attributesBodyItems.value.map((row) => row.row).sort()
377
- let lastId = rowsIds[rowsIds.length - 1]
378
- lastId = lastId === undefined ? -1 : lastId
379
-
380
- attributesBodyItems.value.push(
381
- makeNewAttributeCreateActionRowFunc(lastId + 1)
382
- )
383
- }
384
-
385
- const removeAttributeDialogTexts = computed<UI_I_ModalTexts>(() => ({
386
- button1: localization.value.common.cancel,
387
- button2: localization.value.common.delete,
388
- }))
389
-
390
- const isShowRemoveDialog = ref<boolean>(false)
391
-
392
- const onHideRemoveDialog = (): void => {
393
- isShowRemoveDialog.value = false
394
- }
395
-
396
- const attributeIdForRemove = ref<number>(0)
397
- const attributeNameForRemove = computed<string>(
398
- () =>
399
- (attributesBodyItems.value.find(
400
- (row: UI_I_DataTableBody) => row.row === attributeIdForRemove.value
401
- )?.data[0]?.text || '') as string
402
- )
403
- const removeAttributeMessage = computed<string>(() =>
404
- localization.value.common.removeAttributeDialog
405
- .replace?.('{attributeName}', attributeNameForRemove.value)
406
- .replace('{selectedItemName}', props.selectedItemName)
407
- )
408
- const onShowRemoveDialog = (id: number): void => {
409
- attributeIdForRemove.value = id
410
- isShowRemoveDialog.value = true
411
- }
412
- const onRemoveAttribute = (): void => {
413
- onHideRemoveDialog()
414
-
415
- const removeItem = attributesBodyItems.value.find(
416
- (row) => row.row === attributeIdForRemove.value
417
- )
418
- if (!removeItem) return
419
-
420
- emits('remove-attribute', removeItem)
421
- attributesBodyItems.value = attributesBodyItems.value.filter(
422
- (row) => row.row !== attributeIdForRemove.value
423
- )
424
- }
425
-
426
- const isEditRow = ref<boolean>(false)
427
- const editRowData = ref<UI_I_DataTableBody | undefined>(undefined)
428
- const onEditAttribute = (id: number): void => {
429
- editRowData.value = useDeepCopy(
430
- attributesBodyItems.value.find((row) => row.row === id)
431
- )
432
-
433
- if (!editRowData.value) return
434
- isEditRow.value = true
435
-
436
- editData.value = {
437
- attribute: editRowData.value.data[0].text,
438
- value: editRowData.value.data[1].text,
439
- type: editRowData.value.data[2].text,
440
- }
441
-
442
- attributesBodyItems.value = attributesBodyItems.value.map((row) =>
443
- row.row === id ? makeNewAttributeCreateActionRowFunc(id) : row
444
- )
445
- }
446
-
447
- const emits = defineEmits<{
448
- (event: 'toggle-portlet', id: string): void
449
- (event: 'remove-attribute', value: UI_I_DataTableBody): void
450
- (event: 'add-attribute', value: UI_I_DataTableBody): void
451
- (event: 'edit-attribute', value: UI_I_DataTableBody): void
452
- }>()
453
-
454
- const onTogglePortlet = (id: string): void => {
455
- emits('toggle-portlet', id)
456
- }
457
- </script>
458
-
459
- <style>
460
- :root {
461
- --separator-color: #e9ebed;
462
- --input-border: #d3d6da;
463
- --portlet-input-color: #4d5d69;
464
- --input-caret-color: #000000;
465
- --input-bg-common: #ffffff;
466
- --hide-row-icon: #4d5d69;
467
- --hide-row-icon-hover: #213444;
468
- --create-row-icon: #008fd6;
469
- --create-row-icon-hover: #0081c1;
470
- }
471
- :root.dark-theme {
472
- --separator-color: #e9ebed1f;
473
- --input-border: #e9ebed3d;
474
- --portlet-input-color: #d3d6da;
475
- --input-caret-color: #ffffff;
476
- --input-bg-common: transparent;
477
- --hide-row-icon: #e9eaec;
478
- --hide-row-icon-hover: #ffffff;
479
- --create-row-icon: #2ba2de;
480
- --create-row-icon-hover: #008fd6;
481
- }
482
- </style>
483
-
484
- <style scoped lang="scss">
485
- .icon-container {
486
- width: 16px;
487
- height: 16px;
488
- display: block;
489
- cursor: pointer;
490
- color: var(--table-actions-icon);
491
-
492
- &:hover,
493
- &.selected {
494
- color: var(--table-actions-icon-hover);
495
- }
496
- }
497
-
498
- .action-remove {
499
- display: flex;
500
- align-items: center;
501
- column-gap: 8px;
502
-
503
- height: 32px;
504
- border-radius: 4px;
505
- padding: 8px;
506
- color: var(--table-actions-remove);
507
- cursor: pointer;
508
-
509
- font-size: 13px;
510
- font-weight: 500;
511
- line-height: 15.73px;
512
-
513
- &:hover {
514
- background: var(--table-actions-bg-hover);
515
- }
516
- }
517
-
518
- .action {
519
- display: flex;
520
- align-items: center;
521
- column-gap: 8px;
522
-
523
- height: 32px;
524
- border-radius: 4px;
525
- padding: 8px;
526
- color: var(--table-actions-view);
527
- margin-bottom: 8px;
528
- cursor: pointer;
529
-
530
- font-size: 13px;
531
- font-weight: 500;
532
- line-height: 15.73px;
533
-
534
- &.disabled {
535
- user-select: none;
536
- pointer-events: none;
537
- opacity: 0.3;
538
- cursor: not-allowed;
539
- }
540
-
541
- &:hover {
542
- background: var(--table-actions-bg-hover);
543
- }
544
- }
545
-
546
- .footer {
547
- padding: 0 16px 8px;
548
-
549
- :deep(button.ui-btn) {
550
- line-height: 16px;
551
- margin-bottom: 11px;
552
- margin-left: 6px;
553
- width: fit-content;
554
- }
555
- }
556
-
557
- .table-input {
558
- height: 28px;
559
- background-color: var(--input-bg-common);
560
- border: 1px solid var(--input-border);
561
- font-size: 12px;
562
- font-weight: 500;
563
- line-height: 14.52px;
564
- border-radius: 6px;
565
- width: 100%;
566
- padding: 6px 8px;
567
- caret-color: var(--input-caret-color);
568
- color: var(--portlet-input-color);
569
-
570
- &::placeholder {
571
- color: #9da6ad;
572
- }
573
-
574
- &:focus,
575
- &:focus-visible {
576
- border: 1px solid #008fd6;
577
- outline: none;
578
- }
579
-
580
- &.table-input-error {
581
- border-color: #ea3223;
582
- }
583
- }
584
-
585
- .category-select {
586
- width: 100%;
587
-
588
- :deep(.ui-select-toggle-button) {
589
- height: 28px;
590
- background-color: var(--input-bg-common);
591
-
592
- &:disabled {
593
- background: var(--select-bg-disabled);
594
- color: var(--select-color-disabled);
595
- cursor: default;
596
-
597
- .ui-arrow-icon {
598
- color: var(--select-color-disabled);
599
- }
600
-
601
- :deep(.content-icon) {
602
- opacity: 40%;
603
- }
604
- }
605
- }
606
-
607
- :deep(.ui-placeholder) {
608
- font-size: 12px;
609
- }
610
-
611
- :deep(.ui-dropdown) {
612
- max-height: 250px;
613
- overflow-y: auto;
614
-
615
- .ui-dropdown-menu-item {
616
- height: 28px;
617
- margin-bottom: 4px;
618
- .ui-item-text {
619
- font-size: 12px;
620
- }
621
- }
622
- }
623
- }
624
-
625
- .hide-row {
626
- cursor: pointer;
627
- line-height: 16px;
628
- color: var(--hide-row-icon);
629
-
630
- &:hover {
631
- color: var(--hide-row-icon-hover);
632
- }
633
- }
634
-
635
- .create-row {
636
- cursor: pointer;
637
- line-height: 16px;
638
- color: var(--create-row-icon);
639
-
640
- &:hover {
641
- color: var(--create-row-icon-hover);
642
- }
643
- }
644
-
645
- .add-new-attribute-button-container {
646
- margin-top: 12px;
647
- }
648
-
649
- .actions-container {
650
- display: flex;
651
- justify-content: flex-end;
652
- width: 100%;
653
- }
654
-
655
- .actions-dropdown-container {
656
- padding: 8px;
657
- }
658
-
659
- .edit-actions-container {
660
- display: flex;
661
- align-items: center;
662
- column-gap: 12px;
663
- }
664
- </style>
1
+ <template>
2
+ <div>
3
+ <ui-portlet
4
+ class="attributes"
5
+ test-id="attributes-portlet"
6
+ :title="portletTitle"
7
+ :dragged="props.dragged"
8
+ :dragged-any="props.draggedAny"
9
+ :is-open="props.isOpen"
10
+ :portlet-id="props.portletId"
11
+ :is-loading="props.isLoading"
12
+ @toggle="onTogglePortlet"
13
+ >
14
+ <template #portletBody="{ isLoadingBody }">
15
+ <ui-data-table
16
+ test-id="attributes-portlet-table"
17
+ :data="data"
18
+ :options="attributeTableHeaderOptions"
19
+ server-off
20
+ :default-layout="false"
21
+ size="sm"
22
+ :loading="isLoadingBody"
23
+ :skeleton="skeletonData"
24
+ >
25
+ <template #notFound>
26
+ <div
27
+ v-if="!allRowsCount"
28
+ class="add-new-attribute-button-container"
29
+ >
30
+ <ui-button
31
+ v-development="true"
32
+ test-id="add-new-attribute-button"
33
+ variant="text"
34
+ is-without-height
35
+ is-without-sizes
36
+ :disabled="hasEditRow"
37
+ @click="onAddNew"
38
+ >
39
+ {{ localization.zabbix.addNew }}...
40
+ </ui-button>
41
+ </div>
42
+ </template>
43
+ <!-- :loading="loading"-->
44
+ <template #icon="{ item }">
45
+ <div v-if="item.data.type === 'options'" class="actions-container">
46
+ <span
47
+ v-development="true"
48
+ :id="`actions-attributes-${item.data.row}`"
49
+ :class="[
50
+ 'icon-container',
51
+ {
52
+ selected: isShow[`action-attributes-${item.data.row}`],
53
+ },
54
+ ]"
55
+ @click="onShowActions(item.data.row)"
56
+ >
57
+ <ui-icon :name="item.data.icon" width="16" height="16" />
58
+ </span>
59
+ <ui-popup-window
60
+ v-model="isShow[`action-attributes-${item.data.row}`]"
61
+ :elem-id="`actions-attributes-${item.data.row}`"
62
+ width="fit-content"
63
+ left
64
+ bottom
65
+ >
66
+ <div class="actions-dropdown-container">
67
+ <div
68
+ :class="['action', { disabled: hasEditRow }]"
69
+ data-id="edit-attribute-action"
70
+ @click="onEditAttribute(item.data.row)"
71
+ >
72
+ <!-- @click="onShowDetails(item.data.row)"-->
73
+ <ui-icon name="edit" width="16" height="16" />
74
+ <span>{{ localization.common.edit }}</span>
75
+ </div>
76
+ <div
77
+ class="action-remove"
78
+ data-id="delete-attribute-action"
79
+ @click="onShowRemoveDialog(item.data.row)"
80
+ >
81
+ <ui-icon name="delete" width="16" height="16" />
82
+ <span>{{ localization.common.delete }}</span>
83
+ </div>
84
+ </div>
85
+ </ui-popup-window>
86
+ </div>
87
+ <template v-if="item.data.type === 'attribute'">
88
+ <input
89
+ v-model="editData.attribute"
90
+ data-id="attribute-name-input"
91
+ :class="[
92
+ 'table-input',
93
+ {
94
+ 'table-input-error':
95
+ validationData.attribute && isShowValidation,
96
+ },
97
+ ]"
98
+ :placeholder="localization.common[item.data.type]"
99
+ type="text"
100
+ @click.prevent.stop
101
+ />
102
+ </template>
103
+
104
+ <template v-else-if="item.data.type === 'value'">
105
+ <input
106
+ v-model="editData.value"
107
+ :class="[
108
+ 'table-input',
109
+ {
110
+ 'table-input-error':
111
+ validationData.value && isShowValidation,
112
+ },
113
+ ]"
114
+ :placeholder="localization.common[item.data.type]"
115
+ type="text"
116
+ data-id="attribute-value-input"
117
+ @click.prevent.stop
118
+ />
119
+ </template>
120
+ <template v-else-if="item.data.type === 'type'">
121
+ <div class="category-select">
122
+ <ui-select
123
+ v-model="editData.type"
124
+ :items="selectItems"
125
+ :placeholder="localization.common.chooseCategory"
126
+ width="165px"
127
+ select-width="100%"
128
+ :show-text="true"
129
+ :error="validationData.type && isShowValidation"
130
+ :disabled="isEditRow"
131
+ size="sm"
132
+ test-id="attribute-type-select"
133
+ />
134
+ </div>
135
+ </template>
136
+ <template v-else-if="item.data.type === 'actions'">
137
+ <div class="edit-actions-container">
138
+ <span
139
+ class="hide-row"
140
+ data-id="hide-create-attribute-row"
141
+ @click.prevent.stop="onHideEditRow"
142
+ >
143
+ <ui-icon name="close" width="16" height="16" />
144
+ </span>
145
+ <span
146
+ class="create-row"
147
+ data-id="create-attribute-accept"
148
+ @click.prevent.stop="onAddAttribute"
149
+ >
150
+ <ui-icon name="status-check" width="16" height="16" />
151
+ </span>
152
+ </div>
153
+ </template>
154
+ </template>
155
+ </ui-data-table>
156
+ </template>
157
+ <template #portletFooter="{ isLoadingFooter }">
158
+ <div class="footer">
159
+ <ui-button
160
+ v-if="!isLoadingFooter"
161
+ v-development="true"
162
+ test-id="add-new-attribute-button"
163
+ variant="text"
164
+ is-without-height
165
+ is-without-sizes
166
+ :disabled="hasEditRow"
167
+ @click="onAddNew"
168
+ >
169
+ {{ localization.zabbix.addNew }}...
170
+ </ui-button>
171
+ <ui-portlet-skeleton-footer v-else />
172
+ </div>
173
+ </template>
174
+ </ui-portlet>
175
+
176
+ <ui-popup
177
+ v-if="isShowRemoveDialog"
178
+ test-id="remove-attribute-dialog"
179
+ icon-name="info-status"
180
+ :texts="removeAttributeDialogTexts"
181
+ :message="removeAttributeMessage"
182
+ :title="localization.zabbix.deleteConfirmation"
183
+ @hide="onHideRemoveDialog"
184
+ @submit="onRemoveAttribute"
185
+ />
186
+ </div>
187
+ </template>
188
+
189
+ <script setup lang="ts">
190
+ import type {
191
+ UI_I_DataTable,
192
+ UI_I_DataTableHeader,
193
+ UI_I_DataTableBody,
194
+ } from '~/node_modules/bfg-uikit/components/ui/dataTable/models/interfaces'
195
+ import type { UI_I_Dropdown } from '~/node_modules/bfg-uikit/components/ui/dropdown/models/interfaces'
196
+ import type { UI_I_ModalTexts } from '~/node_modules/bfg-uikit/components/ui/modal/models/interfaces'
197
+ import type {
198
+ UI_I_Localization,
199
+ UI_I_ArbitraryObject,
200
+ } from '~/lib/models/interfaces'
201
+ import type { UI_I_NewAttributeData } from '~/components/common/portlets/customAttributes/lib/models/interfaces'
202
+ import {
203
+ attributeTableHeaderOptions,
204
+ attributeTableHeaderDataFunc,
205
+ createRow,
206
+ makeNewAttributeCreateActionRowFunc,
207
+ availableTypesFunc,
208
+ skeletonData,
209
+ } from '~/components/common/portlets/customAttributes/lib/config/config'
210
+
211
+ const props = defineProps<{
212
+ portletId: string
213
+ dragged?: boolean
214
+ isOpen?: boolean
215
+ draggedAny?: boolean
216
+ selectedItemName: string
217
+ type: UI_I_Dropdown
218
+ bodyItems: UI_I_DataTableBody[]
219
+ isLoading: boolean
220
+ }>()
221
+
222
+ const localization = computed<UI_I_Localization>(() => useLocal())
223
+
224
+ const portletTitle = computed<string>(() => {
225
+ const count = allRowsCount.value
226
+ ? ` (${isEditRow.value ? allRowsCount.value + 1 : allRowsCount.value})`
227
+ : ''
228
+
229
+ return localization.value.common.customAttributes + count
230
+ })
231
+
232
+ const isShow = ref<UI_I_ArbitraryObject<boolean>>({})
233
+
234
+ const onShowActions = (id: number): void => {
235
+ isShow.value[`action-attributes-${id}`] =
236
+ !isShow.value[`action-attributes-${id}`]
237
+ }
238
+
239
+ const attributesHeadItems = computed<UI_I_DataTableHeader[]>(() =>
240
+ attributeTableHeaderDataFunc(localization.value)
241
+ )
242
+
243
+ const attributesBodyItems = ref<UI_I_DataTableBody[]>([])
244
+
245
+ const allRowsCount = computed<number>(
246
+ () =>
247
+ attributesBodyItems.value.filter(
248
+ (row: UI_I_DataTableBody) => !row.actionRow
249
+ ).length
250
+ )
251
+
252
+ const hasEditRow = computed<boolean>(
253
+ () => !!attributesBodyItems.value.find((row) => row.actionRow)
254
+ )
255
+
256
+ const selectedType = computed<UI_I_Dropdown>(() => ({
257
+ text: props.type.text,
258
+ value: props.type.value,
259
+ selected: true,
260
+ }))
261
+
262
+ const editData = ref<UI_I_NewAttributeData>({
263
+ attribute: '',
264
+ value: '',
265
+ type: selectedType.value.text,
266
+ })
267
+
268
+ const resetFields = (): void => {
269
+ editData.value = {
270
+ attribute: '',
271
+ value: '',
272
+ type: selectedType.value.text,
273
+ }
274
+ }
275
+
276
+ const isShowValidation = ref<boolean>(false)
277
+ const validationData = computed<{
278
+ attribute: boolean
279
+ value: boolean
280
+ type: boolean
281
+ }>(() => ({
282
+ attribute: !editData.value.attribute.trim(),
283
+ value: !editData.value.value.trim(),
284
+ type: !editData.value.type.trim(),
285
+ }))
286
+
287
+ watch(
288
+ validationData,
289
+ (newValue: { attribute: boolean; value: boolean; type: boolean }) => {
290
+ !newValue.attribute &&
291
+ !newValue.value &&
292
+ !newValue.type &&
293
+ (isShowValidation.value = false)
294
+ },
295
+ { deep: true }
296
+ )
297
+
298
+ const onAddAttribute = (): void => {
299
+ if (
300
+ validationData.value.attribute ||
301
+ validationData.value.value ||
302
+ validationData.value.type
303
+ ) {
304
+ isShowValidation.value = true
305
+ return
306
+ }
307
+
308
+ if (isEditRow.value && editRowData.value) {
309
+ const editedRowData = createRow(editRowData.value.row, editData.value)
310
+
311
+ attributesBodyItems.value = attributesBodyItems.value.map((row) =>
312
+ row.row === editRowData.value?.row ? editedRowData : row
313
+ )
314
+
315
+ emits('edit-attribute', editedRowData)
316
+ isShow.value[`action-attributes-${editRowData.value?.row}`] = false
317
+ editRowData.value = undefined
318
+ isEditRow.value = false
319
+ } else {
320
+ const withoutEditRowData = attributesBodyItems.value.filter(
321
+ (row) => !row.actionRow
322
+ )
323
+
324
+ const rowsIds = withoutEditRowData.map((row) => row.row).sort()
325
+ let lastId = rowsIds[rowsIds.length - 1]
326
+ lastId = lastId === undefined ? -1 : lastId
327
+
328
+ const addedRowData = createRow(lastId + 1, editData.value)
329
+
330
+ attributesBodyItems.value.push(addedRowData)
331
+ emits('add-attribute', addedRowData)
332
+ attributesBodyItems.value = attributesBodyItems.value.filter(
333
+ (row) => !row.actionRow
334
+ )
335
+ }
336
+
337
+ resetFields()
338
+ }
339
+
340
+ const onHideEditRow = (): void => {
341
+ if (isEditRow.value && editRowData.value) {
342
+ attributesBodyItems.value = attributesBodyItems.value.map((row) =>
343
+ row.row === editRowData.value?.row ? editRowData.value : row
344
+ )
345
+ isShow.value[`action-attributes-${editRowData.value?.row}`] = false
346
+ editRowData.value = undefined
347
+ isEditRow.value = false
348
+ } else {
349
+ attributesBodyItems.value = attributesBodyItems.value.filter(
350
+ (row) => !row.actionRow
351
+ )
352
+ }
353
+ resetFields()
354
+ }
355
+
356
+ watch(
357
+ () => props.bodyItems,
358
+ (newValue: UI_I_DataTableBody[]) => {
359
+ attributesBodyItems.value = newValue
360
+ },
361
+ { deep: true, immediate: true }
362
+ )
363
+
364
+ const data = computed<UI_I_DataTable>(() => ({
365
+ id: 'attributes',
366
+ selectedRows: [],
367
+ isAllSelected: false,
368
+ title: '',
369
+ subTitle: '',
370
+ header: attributesHeadItems.value,
371
+ body: attributesBodyItems.value,
372
+ }))
373
+
374
+ const selectItems = ref<UI_I_Dropdown[]>(
375
+ useDeepCopy(availableTypesFunc(selectedType.value))
376
+ )
377
+
378
+ const onAddNew = (): void => {
379
+ const rowsIds = attributesBodyItems.value.map((row) => row.row).sort()
380
+ let lastId = rowsIds[rowsIds.length - 1]
381
+ lastId = lastId === undefined ? -1 : lastId
382
+
383
+ attributesBodyItems.value.push(
384
+ makeNewAttributeCreateActionRowFunc(lastId + 1)
385
+ )
386
+ }
387
+
388
+ const removeAttributeDialogTexts = computed<UI_I_ModalTexts>(() => ({
389
+ button1: localization.value.common.cancel,
390
+ button2: localization.value.common.delete,
391
+ }))
392
+
393
+ const isShowRemoveDialog = ref<boolean>(false)
394
+
395
+ const onHideRemoveDialog = (): void => {
396
+ isShowRemoveDialog.value = false
397
+ }
398
+
399
+ const attributeIdForRemove = ref<number>(0)
400
+ const attributeNameForRemove = computed<string>(
401
+ () =>
402
+ (attributesBodyItems.value.find(
403
+ (row: UI_I_DataTableBody) => row.row === attributeIdForRemove.value
404
+ )?.data[0]?.text || '') as string
405
+ )
406
+ const removeAttributeMessage = computed<string>(() =>
407
+ localization.value.common.removeAttributeDialog
408
+ .replace?.('{attributeName}', attributeNameForRemove.value)
409
+ .replace('{selectedItemName}', props.selectedItemName)
410
+ )
411
+ const onShowRemoveDialog = (id: number): void => {
412
+ attributeIdForRemove.value = id
413
+ isShowRemoveDialog.value = true
414
+ }
415
+ const onRemoveAttribute = (): void => {
416
+ onHideRemoveDialog()
417
+
418
+ const removeItem = attributesBodyItems.value.find(
419
+ (row) => row.row === attributeIdForRemove.value
420
+ )
421
+ if (!removeItem) return
422
+
423
+ emits('remove-attribute', removeItem)
424
+ attributesBodyItems.value = attributesBodyItems.value.filter(
425
+ (row) => row.row !== attributeIdForRemove.value
426
+ )
427
+ }
428
+
429
+ const isEditRow = ref<boolean>(false)
430
+ const editRowData = ref<UI_I_DataTableBody | undefined>(undefined)
431
+ const onEditAttribute = (id: number): void => {
432
+ editRowData.value = useDeepCopy(
433
+ attributesBodyItems.value.find((row) => row.row === id)
434
+ )
435
+
436
+ if (!editRowData.value) return
437
+ isEditRow.value = true
438
+
439
+ editData.value = {
440
+ attribute: editRowData.value.data[0].text,
441
+ value: editRowData.value.data[1].text,
442
+ type: editRowData.value.data[2].text,
443
+ }
444
+
445
+ attributesBodyItems.value = attributesBodyItems.value.map((row) =>
446
+ row.row === id ? makeNewAttributeCreateActionRowFunc(id) : row
447
+ )
448
+ }
449
+
450
+ const emits = defineEmits<{
451
+ (event: 'toggle-portlet', id: string): void
452
+ (event: 'remove-attribute', value: UI_I_DataTableBody): void
453
+ (event: 'add-attribute', value: UI_I_DataTableBody): void
454
+ (event: 'edit-attribute', value: UI_I_DataTableBody): void
455
+ }>()
456
+
457
+ const onTogglePortlet = (id: string): void => {
458
+ emits('toggle-portlet', id)
459
+ }
460
+ </script>
461
+
462
+ <style>
463
+ :root {
464
+ --separator-color: #e9ebed;
465
+ --input-border: #d3d6da;
466
+ --portlet-input-color: #4d5d69;
467
+ --input-caret-color: #000000;
468
+ --input-bg-common: #ffffff;
469
+ --hide-row-icon: #4d5d69;
470
+ --hide-row-icon-hover: #213444;
471
+ --create-row-icon: #008fd6;
472
+ --create-row-icon-hover: #0081c1;
473
+ }
474
+ :root.dark-theme {
475
+ --separator-color: #e9ebed1f;
476
+ --input-border: #e9ebed3d;
477
+ --portlet-input-color: #d3d6da;
478
+ --input-caret-color: #ffffff;
479
+ --input-bg-common: transparent;
480
+ --hide-row-icon: #e9eaec;
481
+ --hide-row-icon-hover: #ffffff;
482
+ --create-row-icon: #2ba2de;
483
+ --create-row-icon-hover: #008fd6;
484
+ }
485
+ </style>
486
+
487
+ <style scoped lang="scss">
488
+ .icon-container {
489
+ width: 16px;
490
+ height: 16px;
491
+ display: block;
492
+ cursor: pointer;
493
+ color: var(--table-actions-icon);
494
+
495
+ &:hover,
496
+ &.selected {
497
+ color: var(--table-actions-icon-hover);
498
+ }
499
+ }
500
+
501
+ .action-remove {
502
+ display: flex;
503
+ align-items: center;
504
+ column-gap: 8px;
505
+
506
+ height: 32px;
507
+ border-radius: 4px;
508
+ padding: 8px;
509
+ color: var(--table-actions-remove);
510
+ cursor: pointer;
511
+
512
+ font-size: 13px;
513
+ font-weight: 500;
514
+ line-height: 15.73px;
515
+
516
+ &:hover {
517
+ background: var(--table-actions-bg-hover);
518
+ }
519
+ }
520
+
521
+ .action {
522
+ display: flex;
523
+ align-items: center;
524
+ column-gap: 8px;
525
+
526
+ height: 32px;
527
+ border-radius: 4px;
528
+ padding: 8px;
529
+ color: var(--table-actions-view);
530
+ margin-bottom: 8px;
531
+ cursor: pointer;
532
+
533
+ font-size: 13px;
534
+ font-weight: 500;
535
+ line-height: 15.73px;
536
+
537
+ &.disabled {
538
+ user-select: none;
539
+ pointer-events: none;
540
+ opacity: 0.3;
541
+ cursor: not-allowed;
542
+ }
543
+
544
+ &:hover {
545
+ background: var(--table-actions-bg-hover);
546
+ }
547
+ }
548
+
549
+ .footer {
550
+ padding: 0 16px 8px;
551
+
552
+ :deep(button.ui-btn) {
553
+ line-height: 16px;
554
+ margin-bottom: 11px;
555
+ margin-left: 6px;
556
+ width: fit-content;
557
+ }
558
+ }
559
+
560
+ .table-input {
561
+ height: 28px;
562
+ background-color: var(--input-bg-common);
563
+ border: 1px solid var(--input-border);
564
+ font-size: 12px;
565
+ font-weight: 500;
566
+ line-height: 14.52px;
567
+ border-radius: 6px;
568
+ width: 100%;
569
+ padding: 6px 8px;
570
+ caret-color: var(--input-caret-color);
571
+ color: var(--portlet-input-color);
572
+
573
+ &::placeholder {
574
+ color: #9da6ad;
575
+ }
576
+
577
+ &:focus,
578
+ &:focus-visible {
579
+ border: 1px solid #008fd6;
580
+ outline: none;
581
+ }
582
+
583
+ &.table-input-error {
584
+ border-color: #ea3223;
585
+ }
586
+ }
587
+
588
+ .category-select {
589
+ width: 100%;
590
+
591
+ :deep(.ui-select-toggle-button) {
592
+ height: 28px;
593
+ background-color: var(--input-bg-common);
594
+
595
+ &:disabled {
596
+ background: var(--select-bg-disabled);
597
+ color: var(--select-color-disabled);
598
+ cursor: default;
599
+
600
+ .ui-arrow-icon {
601
+ color: var(--select-color-disabled);
602
+ }
603
+
604
+ :deep(.content-icon) {
605
+ opacity: 40%;
606
+ }
607
+ }
608
+ }
609
+
610
+ :deep(.ui-placeholder) {
611
+ font-size: 12px;
612
+ }
613
+
614
+ :deep(.ui-dropdown) {
615
+ max-height: 250px;
616
+ overflow-y: auto;
617
+
618
+ .ui-dropdown-menu-item {
619
+ height: 28px;
620
+ margin-bottom: 4px;
621
+ .ui-item-text {
622
+ font-size: 12px;
623
+ }
624
+ }
625
+ }
626
+ }
627
+
628
+ .hide-row {
629
+ cursor: pointer;
630
+ line-height: 16px;
631
+ color: var(--hide-row-icon);
632
+
633
+ &:hover {
634
+ color: var(--hide-row-icon-hover);
635
+ }
636
+ }
637
+
638
+ .create-row {
639
+ cursor: pointer;
640
+ line-height: 16px;
641
+ color: var(--create-row-icon);
642
+
643
+ &:hover {
644
+ color: var(--create-row-icon-hover);
645
+ }
646
+ }
647
+
648
+ .add-new-attribute-button-container {
649
+ margin-top: 12px;
650
+ }
651
+
652
+ .actions-container {
653
+ display: flex;
654
+ justify-content: flex-end;
655
+ width: 100%;
656
+ }
657
+
658
+ .actions-dropdown-container {
659
+ padding: 8px;
660
+ }
661
+
662
+ .edit-actions-container {
663
+ display: flex;
664
+ align-items: center;
665
+ column-gap: 12px;
666
+ }
667
+ </style>