bfg-common 1.5.452 → 1.5.454

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.
@@ -2749,7 +2749,8 @@
2749
2749
  "template": "Шаблон",
2750
2750
  "convertToTemplate": "Пераўтварыць у шаблон",
2751
2751
  "newTemplate": "Новы шаблон",
2752
- "createTemplateFromVM": "Стварыць шаблон з ВМ"
2752
+ "createTemplateFromVM": "Стварыць шаблон з ВМ",
2753
+ "actionsNumObjects": "Дзеянні – {0} аб’ектаў"
2753
2754
  },
2754
2755
  "networks": {
2755
2756
  "active_ports": "Актыўныя Парты",
@@ -3252,7 +3253,8 @@
3252
3253
  "channels": "Каналы ({0})",
3253
3254
  "metricsNotFound": "Паказчыкі не знойдзены",
3254
3255
  "uploadFolder": "Тэчка загрузкі",
3255
- "uploadFiles": "Запампаваць файлы"
3256
+ "uploadFiles": "Запампаваць файлы",
3257
+ "clickForUse": "Націсніце для выкарыстання"
3256
3258
  },
3257
3259
  "vmWizard": {
3258
3260
  "selectCreationType": "Выберыце тып стварэння",
@@ -2753,7 +2753,8 @@
2753
2753
  "template": "Template",
2754
2754
  "convertToTemplate": "Convert to Template",
2755
2755
  "newTemplate": "New Template",
2756
- "createTemplateFromVM": "Create Template from VM"
2756
+ "createTemplateFromVM": "Create Template from VM",
2757
+ "actionsNumObjects": "Actions - {0} Objects"
2757
2758
  },
2758
2759
  "networks": {
2759
2760
  "active_ports": "Active ports",
@@ -3256,7 +3257,8 @@
3256
3257
  "channels": "Channels ({0})",
3257
3258
  "metricsNotFound": "Metrics not found",
3258
3259
  "uploadFolder": "Upload folder",
3259
- "uploadFiles": "Upload files"
3260
+ "uploadFiles": "Upload files",
3261
+ "clickForUse": "Click for use"
3260
3262
  },
3261
3263
  "vmWizard": {
3262
3264
  "selectCreationType": "Select a Creation Type",
@@ -2753,7 +2753,8 @@
2753
2753
  "template": "Ձևանմուշ",
2754
2754
  "convertToTemplate": "Վերափոխել ձևանմուշի",
2755
2755
  "newTemplate": "Նոր ձևանմուշ",
2756
- "createTemplateFromVM": "Ստեղծել ձևանմուշ ՎՄ-ից"
2756
+ "createTemplateFromVM": "Ստեղծել ձևանմուշ ՎՄ-ից",
2757
+ "actionsNumObjects": "Գործողություններ – {0} օբյեկտ"
2757
2758
  },
2758
2759
  "networks": {
2759
2760
  "active_ports": "Ակտիվ պորտեր",
@@ -3256,7 +3257,8 @@
3256
3257
  "channels": "Ալիքներ ({0})",
3257
3258
  "metricsNotFound": "Չափանիշները չեն գտնվել",
3258
3259
  "uploadFolder": "Վերբեռնեք թղթապանակը",
3259
- "uploadFiles": "Վերբեռնեք ֆայլեր"
3260
+ "uploadFiles": "Վերբեռնեք ֆայլեր",
3261
+ "clickForUse": "Կտտացրեք օգտագործման համար"
3260
3262
  },
3261
3263
  "vmWizard": {
3262
3264
  "selectCreationType": "Ընտրեք ստեղծման տեսակը",
@@ -2752,7 +2752,8 @@
2752
2752
  "template": "Үлгі",
2753
2753
  "convertToTemplate": "Үлгіге айналдыру",
2754
2754
  "newTemplate": "Жаңа үлгі",
2755
- "createTemplateFromVM": "ВМ негізінде үлгі жасау"
2755
+ "createTemplateFromVM": "ВМ негізінде үлгі жасау",
2756
+ "actionsNumObjects": "Әрекеттер – {0} нысан"
2756
2757
  },
2757
2758
  "networks": {
2758
2759
  "active_ports": "Белсенді порттар",
@@ -3255,7 +3256,8 @@
3255
3256
  "channels": "Арналар ({0})",
3256
3257
  "metricsNotFound": "Көрсеткіштер табылмады",
3257
3258
  "uploadFolder": "Қалтаны жүктеп салу",
3258
- "uploadFiles": "Файлдарды жүктеп салу"
3259
+ "uploadFiles": "Файлдарды жүктеп салу",
3260
+ "clickForUse": "Пайдалану үшін басыңыз"
3259
3261
  },
3260
3262
  "vmWizard": {
3261
3263
  "selectCreationType": "Жасау түрін таңдаңыз",
@@ -2752,7 +2752,8 @@
2752
2752
  "template": "Шаблон",
2753
2753
  "convertToTemplate": "Преобразовать в шаблон",
2754
2754
  "newTemplate": "Новый шаблон",
2755
- "createTemplateFromVM": "Создать шаблон из ВМ"
2755
+ "createTemplateFromVM": "Создать шаблон из ВМ",
2756
+ "actionsNumObjects": "Действия – {0} объектов"
2756
2757
  },
2757
2758
  "networks": {
2758
2759
  "active_ports": "Активные порты",
@@ -3255,7 +3256,8 @@
3255
3256
  "channels": "Каналы ({0})",
3256
3257
  "metricsNotFound": "Метрики не найдены",
3257
3258
  "uploadFolder": "Загрузить папку",
3258
- "uploadFiles": "Загрузить файлы"
3259
+ "uploadFiles": "Загрузить файлы",
3260
+ "clickForUse": "Нажмите для использования"
3259
3261
  },
3260
3262
  "vmWizard": {
3261
3263
  "selectCreationType": "Выбор типа создания",
@@ -2750,7 +2750,8 @@
2750
2750
  "template": "模板",
2751
2751
  "convertToTemplate": "转换为模板",
2752
2752
  "newTemplate": "新模板",
2753
- "createTemplateFromVM": "从虚拟机创建模板"
2753
+ "createTemplateFromVM": "从虚拟机创建模板",
2754
+ "actionsNumObjects": "操作 – {0} 个对象"
2754
2755
  },
2755
2756
  "networks": {
2756
2757
  "active_ports": "活动端口",
@@ -3253,7 +3254,8 @@
3253
3254
  "channels": "频道 ({0})",
3254
3255
  "metricsNotFound": "未找到指标",
3255
3256
  "uploadFolder": "上传文件夹",
3256
- "uploadFiles": "上传文件"
3257
+ "uploadFiles": "上传文件",
3258
+ "clickForUse": "点击使用"
3257
3259
  },
3258
3260
  "vmWizard": {
3259
3261
  "selectCreationType": "选择创建类型",
@@ -2906,7 +2906,8 @@
2906
2906
  "channels": "القنوات ({0})",
2907
2907
  "metricsNotFound": "لم يتم العثور على المقاييس",
2908
2908
  "uploadFolder": "تحميل مجلد",
2909
- "uploadFiles": "تحميل ملفات"
2909
+ "uploadFiles": "تحميل ملفات",
2910
+ "clickForUse": "انقر للاستخدام"
2910
2911
  },
2911
2912
  "vmWizard": {
2912
2913
  "devices": "الأجهزة ({0})",
@@ -211,6 +211,7 @@
211
211
  :data-id="`${item[0].testId}-select-row`"
212
212
  class="datagrid-row-master datagrid-row-flex"
213
213
  @click.stop="selectRowById(item[0])"
214
+ @contextmenu.prevent="onShowContextMenu($event, item[0])"
214
215
  >
215
216
  <div
216
217
  v-if="props.type || $slots.toggleBlock"
@@ -601,6 +602,7 @@ const emits = defineEmits<{
601
602
  (event: 'sorting', value: [string, boolean]): void
602
603
  (event: 'change', value: any): void
603
604
  (event: 'row-detail', value: number): void
605
+ (event: 'show-context-menu', value: Event): void
604
606
  }>()
605
607
 
606
608
  const localization = computed<UI_I_Localization>(() => useLocal())
@@ -1086,6 +1088,19 @@ const exportSelected = (): void => {
1086
1088
  generateCsvAndDownload([header, ...Array.from(rows)])
1087
1089
  }
1088
1090
 
1091
+ const onShowContextMenu = (event: Event, item: UI_I_BodyItem): void => {
1092
+ const { id } = item
1093
+ let has = false
1094
+ if (props.type === 'checkbox' && Array.isArray(selectedRowLocal.value)) {
1095
+ has = selectedRowLocal.value.includes(id)
1096
+ } else {
1097
+ has = selectedRowLocal.value === id
1098
+ }
1099
+ if (!has) selectRowById(item)
1100
+
1101
+ emits('show-context-menu', event)
1102
+ }
1103
+
1089
1104
  onMounted(() => {
1090
1105
  window.addEventListener('mousemove', mousemove)
1091
1106
  window.addEventListener('mouseup', mouseup)
@@ -1,27 +1,37 @@
1
1
  <template>
2
2
  <div
3
- class="window-container"
3
+ :class="['window-container', { loading: props.loading }]"
4
4
  :style="{
5
- left: x + 'px',
6
- top: y + 'px',
7
- width: width + 'px',
8
- height: height + 'px',
5
+ left: leftLocal + 'px',
6
+ top: topLocal + 'px',
7
+ width: widthLocal + 'px',
8
+ height: heightLocal + 'px',
9
9
  }"
10
10
  >
11
11
  <div ref="headerEl" class="window-header">
12
- <div class="window-title">{{ props.title }}</div>
13
- <button class="window-close" @click="onHide">
14
- <ui-icon name="close" class="close-icon" />
15
- </button>
12
+ <slot name="header">
13
+ <div class="window-title">{{ props.title }}</div>
14
+ </slot>
15
+
16
+ <div class="flex-align-center">
17
+ <button class="window-close" @click="onCollapse">
18
+ <ui-icon
19
+ :name="collapseData.isCollapsed ? 'arrow' : 'check-line'"
20
+ class="hide-icon"
21
+ />
22
+ </button>
23
+ <button class="window-close ml-1" @click="onHide">
24
+ <ui-icon name="close" class="close-icon" />
25
+ </button>
26
+ </div>
16
27
  </div>
17
28
 
18
29
  <div class="window-content">
30
+ <div v-show="props.loading" class="absolute-center">
31
+ <ui-icon name="spinner" />
32
+ </div>
19
33
  <slot></slot>
20
- <div
21
- v-show="isShowContentBlocker"
22
- :style="{}"
23
- class="content-blocker"
24
- ></div>
34
+ <div v-show="isShowContentBlocker" class="content-blocker"></div>
25
35
  </div>
26
36
 
27
37
  <!-- Ручки для ресайза -->
@@ -62,14 +72,27 @@
62
72
 
63
73
  <script setup lang="ts">
64
74
  import { useDraggable } from '@vueuse/core'
65
-
66
- const props = defineProps<{
67
- top: number
68
- left: number
69
- width: number
70
- height: number
71
- title: string
72
- }>()
75
+ import type { UI_I_WindowCollapsedData } from '~/components/atoms/window/lib/models/interfaces'
76
+ import {
77
+ minWidth,
78
+ minHeight,
79
+ headerHeight,
80
+ windowPadding,
81
+ } from '~/components/atoms/window/lib/config/config'
82
+
83
+ const props = withDefaults(
84
+ defineProps<{
85
+ top: number
86
+ left: number
87
+ width: number
88
+ height: number
89
+ title: string
90
+ loading?: boolean
91
+ }>(),
92
+ {
93
+ loading: false,
94
+ }
95
+ )
73
96
  const emits = defineEmits<{
74
97
  (event: 'hide'): void
75
98
  }>()
@@ -77,20 +100,34 @@ const emits = defineEmits<{
77
100
  const headerEl = ref(null)
78
101
  const isShowContentBlocker = ref<boolean>(false)
79
102
 
80
- const x = ref(props.left)
81
- const y = ref(props.top)
82
- const width = ref(props.width)
83
- const height = ref(props.height)
103
+ const leftLocal = ref<number>(props.left)
104
+ const topLocal = ref<number>(props.top)
105
+ const widthLocal = ref<number>(Math.max(minWidth, props.width))
106
+ const heightLocal = ref<number>(Math.max(minHeight, props.height))
107
+
108
+ const collapseData = ref<UI_I_WindowCollapsedData>({
109
+ isCollapsed: false,
110
+ cashTop: -1,
111
+ cashLeft: -1,
112
+ cashWidth: -1,
113
+ cashHeight: -1,
114
+ })
84
115
 
85
116
  // Используем vueuse для перетаскивания
86
117
  useDraggable(headerEl, {
87
- initialValue: { x: props.left, y: props.top },
118
+ initialValue: { x: leftLocal.value, y: topLocal.value },
88
119
  onMove({ x: newX, y: newY }) {
89
- x.value = newX
90
- y.value = newY
120
+ leftLocal.value = newX
121
+ topLocal.value = newY
91
122
  },
92
123
  onStart() {
93
124
  isShowContentBlocker.value = true
125
+ if (!collapseData.value.isCollapsed) {
126
+ collapseData.value.cashTop = topLocal.value
127
+ collapseData.value.cashLeft = leftLocal.value
128
+ collapseData.value.cashWidth = widthLocal.value
129
+ collapseData.value.cashHeight = heightLocal.value
130
+ }
94
131
  },
95
132
  onEnd() {
96
133
  isShowContentBlocker.value = false
@@ -102,35 +139,48 @@ const fixSize = (): void => {
102
139
  const globalWindowWidth = window.innerWidth
103
140
  const globalWindowHeight = window.innerHeight
104
141
 
105
- width.value = Math.min(globalWindowWidth - 8 * 2, width.value)
106
- height.value = Math.min(globalWindowHeight - 8 * 2, height.value)
142
+ widthLocal.value = Math.min(
143
+ globalWindowWidth - windowPadding * 2,
144
+ widthLocal.value
145
+ )
146
+ heightLocal.value = Math.min(
147
+ globalWindowHeight - windowPadding * 2,
148
+ heightLocal.value
149
+ )
107
150
  }
108
151
  const fixPosition = (): void => {
109
152
  const globalWindowWidth = window.innerWidth
110
153
  const globalWindowHeight = window.innerHeight
111
- let newX = x.value
112
- let newY = y.value
113
- if (x.value + width.value / 2 > globalWindowWidth) {
114
- newX = globalWindowWidth - 20
115
- } else if (x.value + width.value > globalWindowWidth - 8) {
116
- newX = globalWindowWidth - width.value - 8
117
- } else if (x.value < 8) {
118
- newX = 8
154
+ let newX = leftLocal.value
155
+ let newY = topLocal.value
156
+
157
+ if (leftLocal.value + widthLocal.value > globalWindowWidth - windowPadding) {
158
+ newX = globalWindowWidth - widthLocal.value - windowPadding
159
+ } else if (leftLocal.value < windowPadding) {
160
+ newX = windowPadding
119
161
  }
120
162
 
121
- if (y.value + height.value / 2 > globalWindowHeight) {
122
- newY = globalWindowHeight - 20
123
- } else if (y.value + height.value > globalWindowHeight - 8) {
124
- newY = globalWindowHeight - height.value - 8
125
- } else if (y.value < 8) {
126
- newY = 8
163
+ if (topLocal.value + heightLocal.value / 2 > globalWindowHeight) {
164
+ newY = globalWindowHeight - headerHeight
165
+ collapseData.value.isCollapsed = true
166
+ } else if (
167
+ topLocal.value + heightLocal.value >
168
+ globalWindowHeight - windowPadding
169
+ ) {
170
+ newY = globalWindowHeight - heightLocal.value - windowPadding
171
+ collapseData.value.isCollapsed = false
172
+ } else if (topLocal.value < windowPadding) {
173
+ newY = windowPadding
174
+ collapseData.value.isCollapsed = false
175
+ } else {
176
+ collapseData.value.isCollapsed = false
127
177
  }
128
178
 
129
179
  smoothMove(newX, newY)
130
180
  }
131
181
  const smoothMove = (newX: number, newY: number, duration = 100): void => {
132
- const startX = x.value
133
- const startY = y.value
182
+ const startX = leftLocal.value
183
+ const startY = topLocal.value
134
184
 
135
185
  // Время начала анимации
136
186
  const startTime = performance.now()
@@ -144,8 +194,8 @@ const smoothMove = (newX: number, newY: number, duration = 100): void => {
144
194
  const progress = Math.min(elapsedTime / duration, 1)
145
195
 
146
196
  // Вычисляем новые координаты с помощью линейной интерполяции
147
- x.value = startX + (newX - startX) * progress
148
- y.value = startY + (newY - startY) * progress
197
+ leftLocal.value = startX + (newX - startX) * progress
198
+ topLocal.value = startY + (newY - startY) * progress
149
199
 
150
200
  // Если анимация не завершена, продолжаем
151
201
  if (progress < 1) {
@@ -157,39 +207,65 @@ const smoothMove = (newX: number, newY: number, duration = 100): void => {
157
207
  requestAnimationFrame(animate)
158
208
  }
159
209
 
210
+ const onCollapse = (): void => {
211
+ collapseData.value.isCollapsed = !collapseData.value.isCollapsed
212
+
213
+ if (collapseData.value.isCollapsed) {
214
+ collapseData.value.cashTop = topLocal.value
215
+ collapseData.value.cashLeft = leftLocal.value
216
+ collapseData.value.cashWidth = widthLocal.value
217
+ collapseData.value.cashHeight = heightLocal.value
218
+
219
+ const globalWindowWidth = window.innerWidth
220
+ const globalWindowHeight = window.innerHeight
221
+
222
+ widthLocal.value = minWidth
223
+ heightLocal.value = minHeight
224
+ leftLocal.value = globalWindowWidth - widthLocal.value - windowPadding
225
+ topLocal.value = globalWindowHeight - headerHeight
226
+ fixPosition()
227
+ } else {
228
+ leftLocal.value = collapseData.value.cashLeft
229
+ topLocal.value = collapseData.value.cashTop
230
+ widthLocal.value = collapseData.value.cashWidth
231
+ heightLocal.value = collapseData.value.cashHeight
232
+ fixPosition()
233
+ }
234
+ }
235
+
160
236
  // Функция для ресайза
161
237
  const startResize = (e: any, direction: any): void => {
162
238
  e.preventDefault()
163
239
 
164
240
  const startX = e.clientX
165
241
  const startY = e.clientY
166
- const startWidth = width.value
167
- const startHeight = height.value
168
- const startLeft = x.value
169
- const startTop = y.value
242
+ const startWidth = widthLocal.value
243
+ const startHeight = heightLocal.value
244
+ const startLeft = leftLocal.value
245
+ const startTop = topLocal.value
170
246
 
171
247
  const onMouseMove = (e: any): void => {
172
248
  const deltaX = e.clientX - startX
173
249
  const deltaY = e.clientY - startY
174
250
 
175
251
  if (direction.includes('right')) {
176
- width.value = Math.max(200, startWidth + deltaX)
252
+ widthLocal.value = Math.max(minWidth, startWidth + deltaX)
177
253
  }
178
254
  if (direction.includes('bottom')) {
179
- height.value = Math.max(150, startHeight + deltaY)
255
+ heightLocal.value = Math.max(minHeight, startHeight + deltaY)
180
256
  }
181
257
  if (direction.includes('left')) {
182
- const newWidth = Math.max(200, startWidth - deltaX)
183
- if (newWidth !== width.value) {
184
- x.value = startLeft + deltaX
185
- width.value = newWidth
258
+ const newWidth = Math.max(minWidth, startWidth - deltaX)
259
+ if (newWidth !== widthLocal.value) {
260
+ leftLocal.value = startLeft + deltaX
261
+ widthLocal.value = newWidth
186
262
  }
187
263
  }
188
264
  if (direction.includes('top')) {
189
- const newHeight = Math.max(150, startHeight - deltaY)
190
- if (newHeight !== height.value) {
191
- y.value = startTop + deltaY
192
- height.value = newHeight
265
+ const newHeight = Math.max(minHeight, startHeight - deltaY)
266
+ if (newHeight !== heightLocal.value) {
267
+ topLocal.value = startTop + deltaY
268
+ heightLocal.value = newHeight
193
269
  }
194
270
  }
195
271
  fixSize()
@@ -241,6 +317,10 @@ const onHide = (): void => {
241
317
  min-height: 200px;
242
318
  z-index: var(--z-toast);
243
319
 
320
+ &.loading {
321
+ opacity: 0.5;
322
+ }
323
+
244
324
  .window-header {
245
325
  background-color: #314351;
246
326
  color: white;
@@ -0,0 +1,4 @@
1
+ export const minWidth = 300
2
+ export const minHeight = 200
3
+ export const windowPadding = 8
4
+ export const headerHeight = 36
@@ -0,0 +1,7 @@
1
+ export interface UI_I_WindowCollapsedData {
2
+ isCollapsed: boolean
3
+ cashTop: number
4
+ cashLeft: number
5
+ cashWidth: number
6
+ cashHeight: number
7
+ }
@@ -53,7 +53,7 @@
53
53
  @submit-options="emits('submit-options', $event)"
54
54
  />
55
55
 
56
- <div v-show="!props.data" class="empty-container">
56
+ <div v-show="!props.data && !props.advancedLoading" class="empty-container">
57
57
  {{
58
58
  localization.inventoryMonitor
59
59
  .noPerformanceCurrentlySelectedMetrics
@@ -107,12 +107,12 @@
107
107
  import type { UI_I_Localization } from '~/lib/models/interfaces'
108
108
 
109
109
  const alertShow = defineModel<boolean>('alert-show')
110
- const currentDateFrom = defineModel<boolean>('current-date-from')
111
- const dateFrom = defineModel<boolean>('date-from')
112
- const currentTimeFrom = defineModel<boolean>('current-time-from')
113
- const currentDateTo = defineModel<boolean>('current-date-to')
114
- const dateTo = defineModel<boolean>('date-to')
115
- const currentTimeTo = defineModel<boolean>('current-time-to')
110
+ const currentDateFrom = defineModel<string>('current-date-from')
111
+ const dateFrom = defineModel<number>('date-from')
112
+ const currentTimeFrom = defineModel<string>('current-time-from')
113
+ const currentDateTo = defineModel<string>('current-date-to')
114
+ const dateTo = defineModel<number>('date-to')
115
+ const currentTimeTo = defineModel<string>('current-time-to')
116
116
 
117
117
  const props = defineProps<{
118
118
  titleIntervalModal: string
@@ -1,102 +1,102 @@
1
- <template>
2
- <div class="main-content-backups h-full overflow-hidden">
3
- <common-pages-backups-tools
4
- :project="props.project"
5
- :backups-count="backupsTree.length"
6
- :backups-loading="backupsLoading"
7
- :selected-node="selectedNode"
8
- :target-type="typeFromRoute"
9
- @select="onShowModal"
10
- />
11
-
12
- <atoms-loader-pre-loader
13
- v-show="backupsLoading"
14
- id="loader"
15
- :show="true"
16
- class="backups-loading w-full h-full flex items-center justify-center"
17
- test-id="backups-tree-spinner"
18
- />
19
- <div class="fill-parent w-full h-full">
20
- <div v-if="backupsTree.length" class="backup-view mt-1 flex w-full">
21
- <common-tree-view
22
- :loading="backupsLoading"
23
- :nodes="backupsTree"
24
- @select-node="onSelectNode"
25
- @show-nodes="onShowNodes"
26
- />
27
- <common-pages-backups-detail-view :detail="detailData" />
28
- </div>
29
- <div v-else class="empty-block flex justify-center items-center w-full">
30
- {{ localization.common.noBackupAvailable }}
31
- </div>
32
- </div>
33
- </div>
34
- </template>
35
-
36
- <script setup lang="ts">
37
- import type { UI_T_Project } from '~/lib/models/types'
38
- import type { UI_I_Localization } from '~/lib/models/interfaces'
39
- import type {
40
- UI_T_TargetType,
41
- UI_T_BackupActionType,
42
- } from '~/components/common/pages/backups/lib/models/types'
43
- import type {
44
- UI_I_Backup,
45
- UI_I_BackupsTreeNode,
46
- } from '~/components/common/pages/backups/lib/models/interfaces'
47
-
48
- const props = defineProps<{
49
- project: UI_T_Project
50
- backups: UI_I_Backup[]
51
- backupsTree: UI_I_BackupsTreeNode[]
52
- backupsLoading: boolean
53
- selectedNode: UI_I_BackupsTreeNode
54
- }>()
55
- const emits = defineEmits<{
56
- (event: 'select-node', value: string | number): void
57
- (event: 'show-nodes', value: string | number): void
58
- (event: 'set-action', value: UI_T_BackupActionType): void
59
- }>()
60
-
61
- const localization = computed<UI_I_Localization>(() => useLocal())
62
- const { $recursion }: any = useNuxtApp()
63
-
64
- const typeFromRoute = computed<UI_T_TargetType>(
65
- () => useRoute().params.type.toString() as UI_T_TargetType
66
- )
67
-
68
- const detailData = computed<UI_I_BackupsTreeNode | null>(() => {
69
- const node =
70
- $recursion.find(props.backupsTree, true, 'isActive', 'nodes') || null
71
- return node?.type === 'backup' ? node : null
72
- })
73
-
74
- const onSelectNode = (item: UI_I_BackupsTreeNode): void => {
75
- emits('select-node', item.id)
76
- if (item.type === 'group') {
77
- onShowNodes(item.id.toString())
78
- }
79
- }
80
- const onShowNodes = (id: string | number): void => {
81
- emits('show-nodes', id)
82
- }
83
-
84
- const onShowModal = (action: UI_T_BackupActionType): void => {
85
- emits('set-action', action)
86
- }
87
- </script>
88
-
89
- <style scoped lang="scss">
90
- .main-content-backups {
91
- padding: 10px 5px 10px 0;
92
-
93
- .fill-parent {
94
- overflow: visible;
95
-
96
- .backup-view {
97
- // TODO fix
98
- height: calc(90% - 24px);
99
- }
100
- }
101
- }
102
- </style>
1
+ <template>
2
+ <div class="main-content-backups h-full overflow-hidden">
3
+ <common-pages-backups-tools
4
+ :project="props.project"
5
+ :backups-count="backupsTree.length"
6
+ :backups-loading="backupsLoading"
7
+ :selected-node="selectedNode"
8
+ :target-type="typeFromRoute"
9
+ @select="onShowModal"
10
+ />
11
+
12
+ <atoms-loader-pre-loader
13
+ v-show="backupsLoading"
14
+ id="loader"
15
+ :show="true"
16
+ class="backups-loading w-full h-full flex items-center justify-center"
17
+ test-id="backups-tree-spinner"
18
+ />
19
+ <div class="fill-parent w-full h-full">
20
+ <div v-if="backupsTree.length" class="backup-view mt-1 flex w-full">
21
+ <common-tree-view
22
+ :loading="backupsLoading"
23
+ :nodes="backupsTree"
24
+ @select-node="onSelectNode"
25
+ @show-nodes="onShowNodes"
26
+ />
27
+ <common-pages-backups-detail-view :detail="detailData" />
28
+ </div>
29
+ <div v-else class="empty-block flex justify-center items-center w-full">
30
+ {{ localization.common.noBackupAvailable }}
31
+ </div>
32
+ </div>
33
+ </div>
34
+ </template>
35
+
36
+ <script setup lang="ts">
37
+ import type { UI_T_Project } from '~/lib/models/types'
38
+ import type { UI_I_Localization } from '~/lib/models/interfaces'
39
+ import type {
40
+ UI_T_TargetType,
41
+ UI_T_BackupActionType,
42
+ } from '~/components/common/pages/backups/lib/models/types'
43
+ import type {
44
+ UI_I_Backup,
45
+ UI_I_BackupsTreeNode,
46
+ } from '~/components/common/pages/backups/lib/models/interfaces'
47
+
48
+ const props = defineProps<{
49
+ project: UI_T_Project
50
+ backups: UI_I_Backup[]
51
+ backupsTree: UI_I_BackupsTreeNode[]
52
+ backupsLoading: boolean
53
+ selectedNode: UI_I_BackupsTreeNode | null
54
+ }>()
55
+ const emits = defineEmits<{
56
+ (event: 'select-node', value: string | number): void
57
+ (event: 'show-nodes', value: string | number): void
58
+ (event: 'set-action', value: UI_T_BackupActionType): void
59
+ }>()
60
+
61
+ const localization = computed<UI_I_Localization>(() => useLocal())
62
+ const { $recursion }: any = useNuxtApp()
63
+
64
+ const typeFromRoute = computed<UI_T_TargetType>(
65
+ () => useRoute().params.type.toString() as UI_T_TargetType
66
+ )
67
+
68
+ const detailData = computed<UI_I_BackupsTreeNode | null>(() => {
69
+ const node =
70
+ $recursion.find(props.backupsTree, true, 'isActive', 'nodes') || null
71
+ return node?.type === 'backup' ? node : null
72
+ })
73
+
74
+ const onSelectNode = (item: UI_I_BackupsTreeNode): void => {
75
+ emits('select-node', item.id)
76
+ if (item.type === 'group') {
77
+ onShowNodes(item.id.toString())
78
+ }
79
+ }
80
+ const onShowNodes = (id: string | number): void => {
81
+ emits('show-nodes', id)
82
+ }
83
+
84
+ const onShowModal = (action: UI_T_BackupActionType): void => {
85
+ emits('set-action', action)
86
+ }
87
+ </script>
88
+
89
+ <style scoped lang="scss">
90
+ .main-content-backups {
91
+ padding: 10px 5px 10px 0;
92
+
93
+ .fill-parent {
94
+ overflow: visible;
95
+
96
+ .backup-view {
97
+ // TODO fix
98
+ height: calc(90% - 24px);
99
+ }
100
+ }
101
+ }
102
+ </style>
@@ -43,7 +43,10 @@
43
43
  <script lang="ts" setup>
44
44
  import type { UI_I_WizardStep } from '~/node_modules/bfg-uikit/components/ui/wizard/lib/models/interfaces'
45
45
  import type { UI_T_Project } from '~/lib/models/types'
46
- import type { UI_I_SendTaskParams, UI_I_ModalPayload } from '~/lib/models/interfaces'
46
+ import type {
47
+ UI_I_SendTaskParams,
48
+ UI_I_ModalPayload,
49
+ } from '~/lib/models/interfaces'
47
50
  import type { UI_I_ScheduleNewTasksForm } from '~/components/common/pages/scheduledTasks/modals/lib/models/interfaces'
48
51
  import type {
49
52
  UI_I_BackupDatastoreTable,
@@ -69,7 +72,7 @@ const props = defineProps<{
69
72
  selectedVmName: string
70
73
  disks: UI_I_Pvm['disk_devices']
71
74
  disksLoading: boolean
72
- datastoreTable: UI_I_BackupDatastoreTable
75
+ datastoreTable: UI_I_BackupDatastoreTable | null
73
76
  datastoreTableLoading: boolean
74
77
  restoreDatastore: UI_I_DatastoreTableItem[]
75
78
  isRestoreDatastoreLoading: boolean
@@ -101,7 +101,7 @@ const props = defineProps<{
101
101
  selectedVmName: string
102
102
  disks: UI_I_Pvm['disk_devices']
103
103
  disksLoading: boolean
104
- datastoreTable: UI_I_BackupDatastoreTable
104
+ datastoreTable: UI_I_BackupDatastoreTable | null
105
105
  datastoreTableLoading: boolean
106
106
  }>()
107
107
 
@@ -1,59 +1,59 @@
1
- <template>
2
- <div class="datastores h-full">
3
- <common-pages-backups-modals-create-backup-datastores-table-view
4
- v-model:selected="model.backup_storage"
5
- :data-table="props.datastoreTable?.items"
6
- :total-items="props.datastoreTable?.total_items || 0"
7
- :total-pages="props.datastoreTable?.total_pages || 1"
8
- :loading="props.datastoreTableLoading"
9
- />
10
- </div>
11
- </template>
12
-
13
- <script lang="ts" setup>
14
- import type {
15
- UI_I_BackupDatastoreTable,
16
- UI_I_CreateBackupForm
17
- } from '~/components/common/pages/backups/modals/lib/models/interfaces'
18
-
19
- const props = defineProps<{
20
- datastoreTable: UI_I_BackupDatastoreTable
21
- datastoreTableLoading: boolean
22
- }>()
23
- const emits = defineEmits<{
24
- (event: 'change', value: string[]): void
25
- }>()
26
-
27
- const model = defineModel<UI_I_CreateBackupForm>({ required: true })
28
-
29
- const selectedKeys = ref<string[]>([])
30
-
31
- watch(
32
- () => props.datastoreTable,
33
- () => {
34
- selectedKeys.value = []
35
- },
36
- { deep: true, immediate: true }
37
- )
38
-
39
- watch(
40
- () => model.value.backup_storage,
41
- (newValue: string) => {
42
- const selectedNames: string[] = []
43
-
44
- props.datastoreTable?.items.forEach((item) => {
45
- if (newValue === item.id) selectedNames.push(item.name)
46
- })
47
-
48
- emits('change', selectedNames)
49
- },
50
- { deep: true, immediate: true }
51
- )
52
- </script>
53
-
54
- <style lang="scss" scoped>
55
- .backups-table-content {
56
- height: 100%;
57
- padding: 0 10px 10px 10px;
58
- }
59
- </style>
1
+ <template>
2
+ <div class="datastores h-full">
3
+ <common-pages-backups-modals-create-backup-datastores-table-view
4
+ v-model:selected="model.backup_storage"
5
+ :data-table="props.datastoreTable?.items || []"
6
+ :total-items="props.datastoreTable?.total_items || 0"
7
+ :total-pages="props.datastoreTable?.total_pages || 1"
8
+ :loading="props.datastoreTableLoading"
9
+ />
10
+ </div>
11
+ </template>
12
+
13
+ <script lang="ts" setup>
14
+ import type {
15
+ UI_I_BackupDatastoreTable,
16
+ UI_I_CreateBackupForm
17
+ } from '~/components/common/pages/backups/modals/lib/models/interfaces'
18
+
19
+ const props = defineProps<{
20
+ datastoreTable: UI_I_BackupDatastoreTable | null
21
+ datastoreTableLoading: boolean
22
+ }>()
23
+ const emits = defineEmits<{
24
+ (event: 'change', value: string[]): void
25
+ }>()
26
+
27
+ const model = defineModel<UI_I_CreateBackupForm>({ required: true })
28
+
29
+ const selectedKeys = ref<string[]>([])
30
+
31
+ watch(
32
+ () => props.datastoreTable,
33
+ () => {
34
+ selectedKeys.value = []
35
+ },
36
+ { deep: true, immediate: true }
37
+ )
38
+
39
+ watch(
40
+ () => model.value.backup_storage,
41
+ (newValue: string) => {
42
+ const selectedNames: string[] = []
43
+
44
+ props.datastoreTable?.items.forEach((item) => {
45
+ if (newValue === item.id) selectedNames.push(item.name)
46
+ })
47
+
48
+ emits('change', selectedNames)
49
+ },
50
+ { deep: true, immediate: true }
51
+ )
52
+ </script>
53
+
54
+ <style lang="scss" scoped>
55
+ .backups-table-content {
56
+ height: 100%;
57
+ padding: 0 10px 10px 10px;
58
+ }
59
+ </style>
@@ -16,90 +16,92 @@
16
16
  <atoms-the-icon2 name="drag" class="vmw-drawer__drag-icon" />
17
17
  </div>
18
18
 
19
- <div class="vmw-drawer-header">
20
- <h3>{{ localization.common.consolePanel }}</h3>
21
- <atoms-the-icon
22
- class="vmw-drawer-header__close"
23
- data-id="spice-console-drawer-toggle-icon"
24
- name="close"
25
- @click="toggleDrawer"
26
- />
27
- </div>
19
+ <div class="drawer-content">
20
+ <div class="vmw-drawer-header">
21
+ <h3>{{ localization.common.consolePanel }}</h3>
22
+ <atoms-the-icon
23
+ class="vmw-drawer-header__close"
24
+ data-id="spice-console-drawer-toggle-icon"
25
+ name="close"
26
+ @click="toggleDrawer"
27
+ />
28
+ </div>
28
29
 
29
- <div class="vmw-drawer-body">
30
- <button
31
- class="vmw-drawer-body__btn animation toggle-fullscreen"
32
- data-id="spice-console-drawer-toggle-fullscreen"
33
- @click="emits('toggle-fullscreen')"
34
- >
35
- {{ localization.common.toggleFullscreenMode }}
36
- </button>
37
- <button
38
- class="vmw-drawer-body__btn animation toggle-fullscreen"
39
- @click="emits('send-alt-command')"
40
- >
41
- {{ localization.common.sendAltCommand }}
42
- </button>
43
- <label
44
- v-development="true"
45
- class="vmw-drawer-body__btn animation relative"
46
- >
47
- {{ localization.remoteConsole.uploadFolder }}
48
- <input type="file" webkitdirectory directory multiple />
49
- </label>
50
- <label
51
- v-development="true"
52
- class="vmw-drawer-body__btn animation relative"
53
- >
54
- {{ localization.remoteConsole.uploadFiles }}
55
- <input type="file" multiple />
56
- </label>
57
-
58
- <select
59
- v-model="usbDevice"
60
- v-development="true"
61
- @mouseenter="hover = true"
62
- @mouseleave="hover = false"
63
- @change="onChangeUsbDevice"
64
- >
65
- <option
66
- v-for="item in usbDevices"
67
- :key="item.value"
68
- :value="item.value"
69
- :disabled="item.disabled"
30
+ <div class="vmw-drawer-body">
31
+ <button
32
+ class="vmw-drawer-body__btn animation toggle-fullscreen"
33
+ data-id="spice-console-drawer-toggle-fullscreen"
34
+ @click="emits('toggle-fullscreen')"
70
35
  >
71
- {{ item.label }}
72
- </option>
73
- </select>
74
-
75
- <select
76
- :value="codec"
77
- @mouseenter="hover = true"
78
- @mouseleave="hover = false"
79
- @change="onChangeCodec"
80
- >
81
- <option :value="1">MJPEG</option>
82
- <option :value="2">VP8</option>
83
- <option :value="3">H264</option>
84
- <option :value="4" disabled>VP9</option>
85
- <option :value="5">H265</option>
86
- </select>
87
-
88
- <hr />
89
- <button
90
- class="vmw-drawer-body__btn animation show-keyboard"
91
- @click="onToggleKeyboard"
92
- >
93
- {{ showOrHideKeyboard }}
94
- </button>
95
- </div>
36
+ {{ localization.common.toggleFullscreenMode }}
37
+ </button>
38
+ <button
39
+ class="vmw-drawer-body__btn animation toggle-fullscreen"
40
+ @click="emits('send-alt-command')"
41
+ >
42
+ {{ localization.common.sendAltCommand }}
43
+ </button>
44
+ <label
45
+ v-development="true"
46
+ class="vmw-drawer-body__btn animation relative"
47
+ >
48
+ {{ localization.remoteConsole.uploadFolder }}
49
+ <input type="file" webkitdirectory directory multiple />
50
+ </label>
51
+ <label
52
+ v-development="true"
53
+ class="vmw-drawer-body__btn animation relative"
54
+ >
55
+ {{ localization.remoteConsole.uploadFiles }}
56
+ <input type="file" multiple />
57
+ </label>
58
+
59
+ <select
60
+ v-model="usbDevice"
61
+ v-development="true"
62
+ @mouseenter="hover = true"
63
+ @mouseleave="hover = false"
64
+ @change="onChangeUsbDevice"
65
+ >
66
+ <option
67
+ v-for="item in usbDevices"
68
+ :key="item.value"
69
+ :value="item.value"
70
+ :disabled="item.disabled"
71
+ >
72
+ {{ item.label }}
73
+ </option>
74
+ </select>
75
+
76
+ <select
77
+ :value="codec"
78
+ @mouseenter="hover = true"
79
+ @mouseleave="hover = false"
80
+ @change="onChangeCodec"
81
+ >
82
+ <option :value="1">MJPEG</option>
83
+ <option :value="2">VP8</option>
84
+ <option :value="3">H264</option>
85
+ <option :value="4" disabled>VP9</option>
86
+ <option :value="5">H265</option>
87
+ </select>
88
+
89
+ <hr />
90
+ <button
91
+ class="vmw-drawer-body__btn animation show-keyboard"
92
+ @click="onToggleKeyboard"
93
+ >
94
+ {{ showOrHideKeyboard }}
95
+ </button>
96
+ </div>
96
97
 
97
- <div class="vmw-drawer-footer">
98
- <div class="size-info">
99
- <p class="size-info-text">View size: {{ viewSize }}</p>
100
- <p class="size-info-text">Canvas size: {{ canvasSize }}</p>
98
+ <div class="vmw-drawer-footer">
99
+ <div class="size-info">
100
+ <p class="size-info-text">View size: {{ viewSize }}</p>
101
+ <p class="size-info-text">Canvas size: {{ canvasSize }}</p>
102
+ </div>
103
+ <div id="debug-stream"></div>
101
104
  </div>
102
- <div id="debug-stream"></div>
103
105
  </div>
104
106
  <div v-if="isKeyboardShown">
105
107
  <common-spice-console-keyboard />
@@ -256,7 +258,9 @@ setDefaultDevices()
256
258
  right: -300px;
257
259
  width: 300px;
258
260
  height: 100vh;
259
- padding: 20px;
261
+ display: flex;
262
+ flex-direction: column;
263
+ padding: 20px 0 20px 20px;
260
264
  z-index: var(--z-modal);
261
265
 
262
266
  &.show {
@@ -306,6 +310,10 @@ setDefaultDevices()
306
310
  }
307
311
  }
308
312
 
313
+ .drawer-content {
314
+ overflow: auto;
315
+ padding-right: 20px;
316
+ }
309
317
  .vmw-drawer-header {
310
318
  & h3 {
311
319
  color: #fff;
@@ -1,6 +1,14 @@
1
1
  <template>
2
2
  <div ref="fullScreen" class="spice-console">
3
3
  <div id="vmw" ref="vmScreen" @scroll="onConsoleScroll"></div>
4
+ <div
5
+ v-if="isWindow"
6
+ v-show="isShowBlocker"
7
+ class="blocker"
8
+ @click="onFocus"
9
+ >
10
+ <p class="blocker-text">{{ localization.remoteConsole.clickForUse }}</p>
11
+ </div>
4
12
  <common-spice-console-drawer
5
13
  @toggle-fullscreen="toggle"
6
14
  @send-alt-command="onSendAltCommand"
@@ -11,6 +19,7 @@
11
19
  import { useFullscreen } from '@vueuse/core'
12
20
  import RFB from '@novnc/novnc/core/rfb.js'
13
21
  import SpiceConsole from '~/plugins/spice-console/spice.console'
22
+ import type { UI_I_Localization } from '~/lib/models/interfaces'
14
23
 
15
24
  const props = defineProps<{
16
25
  wsUrl: string
@@ -20,6 +29,8 @@ const props = defineProps<{
20
29
  port: number
21
30
  }>()
22
31
 
32
+ const localization = computed<UI_I_Localization>(() => useLocal())
33
+
23
34
  let rfb: any
24
35
  const vmScreen = ref<any>(null)
25
36
  const fullScreen = ref<any>(null)
@@ -100,14 +111,28 @@ const blockCtrlOrCmdCommands = (e): void => {
100
111
  // return false;
101
112
  // }
102
113
  }
114
+
115
+ const isWindow = computed<boolean>(() => useRoute().query.window === 'true')
116
+ const isShowBlocker = ref<boolean>(true)
117
+ const onFocus = (): void => {
118
+ document.getElementById('inputmanager')?.focus()
119
+ isShowBlocker.value = false
120
+ }
121
+
122
+ const inputManagerBlur = (): void => {
123
+ isShowBlocker.value = true
124
+ }
125
+
103
126
  onMounted(() => {
104
127
  connectWS()
105
128
  document.addEventListener('keydown', blockCtrlOrCmdCommands)
129
+ window.addEventListener('blur', inputManagerBlur)
106
130
  })
107
131
 
108
132
  onBeforeUnmount(() => {
109
133
  vmDisconnect()
110
134
  document.removeEventListener('keydown', blockCtrlOrCmdCommands)
135
+ window.removeEventListener('blur', inputManagerBlur)
111
136
  })
112
137
  </script>
113
138
  <style scoped lang="scss">
@@ -124,4 +149,20 @@ onBeforeUnmount(() => {
124
149
  height: 100vh;
125
150
  background-color: #000;
126
151
  }
152
+ .blocker {
153
+ position: fixed;
154
+ top: 0;
155
+ left: 0;
156
+ width: 100%;
157
+ height: 100%;
158
+ display: flex;
159
+ justify-content: center;
160
+ align-items: center;
161
+ background-color: rgba(0, 0, 0, 0.5);
162
+ z-index: 9999;
163
+
164
+ .blocker-text {
165
+ color: #e4e4e4;
166
+ }
167
+ }
127
168
  </style>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bfg-common",
3
3
  "private": false,
4
- "version": "1.5.452",
4
+ "version": "1.5.454",
5
5
  "scripts": {
6
6
  "build": "nuxt build",
7
7
  "dev": "nuxt dev --port=3002",