bfg-common 1.4.358 → 1.4.359

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