bfg-common 1.5.417 → 1.5.419

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.
Files changed (61) hide show
  1. package/components/common/modals/confirmation/ConfirmationNew.vue +1 -1
  2. package/components/common/pages/backups/Backups.vue +102 -0
  3. package/components/common/pages/backups/DetailView.vue +52 -0
  4. package/components/common/pages/backups/lib/models/interfaces.ts +24 -0
  5. package/components/common/pages/backups/lib/models/types.ts +7 -0
  6. package/components/common/pages/backups/modals/Modals.vue +229 -0
  7. package/components/common/pages/backups/modals/createBackup/CreateBackup.vue +371 -0
  8. package/components/common/pages/backups/modals/createBackup/configuration/Configuration.vue +29 -0
  9. package/components/common/pages/backups/modals/createBackup/configuration/backupWindow/BackupWindow.vue +26 -0
  10. package/components/common/pages/backups/modals/createBackup/configuration/maxBandwidth/MaxBandwidth.vue +65 -0
  11. package/components/common/pages/backups/modals/createBackup/configuration/maxBandwidth/lib/config/options.ts +6 -0
  12. package/components/common/pages/backups/modals/createBackup/configuration/maxBandwidth/lib/models/types.ts +1 -0
  13. package/components/common/pages/backups/modals/createBackup/configuration/strategy/Strategy.vue +36 -0
  14. package/components/common/pages/backups/modals/createBackup/datastores/Datastores.vue +58 -0
  15. package/components/common/pages/backups/modals/createBackup/datastores/tableView/TableView.vue +94 -0
  16. package/components/common/pages/backups/modals/createBackup/datastores/tableView/lib/config/keys.ts +14 -0
  17. package/components/common/pages/backups/modals/createBackup/datastores/tableView/lib/config/table.ts +117 -0
  18. package/components/common/pages/backups/modals/createBackup/datastores/tableView/lib/models/types.ts +10 -0
  19. package/components/common/pages/backups/modals/createBackup/disks/Disks.vue +28 -0
  20. package/components/common/pages/backups/modals/createBackup/disks/tableView/TableView.vue +105 -0
  21. package/components/common/pages/backups/modals/createBackup/disks/tableView/lib/config/keys.ts +12 -0
  22. package/components/common/pages/backups/modals/createBackup/disks/tableView/lib/config/table.ts +117 -0
  23. package/components/common/pages/backups/modals/createBackup/disks/tableView/lib/models/interfaces.ts +4 -0
  24. package/components/common/pages/backups/modals/createBackup/disks/tableView/lib/models/types.ts +10 -0
  25. package/components/common/pages/backups/modals/createBackup/general/General.vue +48 -0
  26. package/components/common/pages/backups/modals/createBackup/lib/config/createBackup.ts +14 -0
  27. package/components/common/pages/backups/modals/createBackup/lib/config/steps.ts +117 -0
  28. package/components/common/pages/backups/modals/createBackup/lib/config/strategyOptions.ts +12 -0
  29. package/components/common/pages/backups/modals/createBackup/lib/models/interfaces.ts +8 -0
  30. package/components/common/pages/backups/modals/createBackup/readyToComplete/ReadyToComplete.vue +15 -0
  31. package/components/common/pages/backups/modals/lib/config/createBackup.ts +16 -0
  32. package/components/common/pages/backups/modals/lib/config/restore.ts +115 -0
  33. package/components/common/pages/backups/modals/lib/models/interfaces.ts +164 -0
  34. package/components/common/pages/backups/modals/restore/Restore.vue +388 -0
  35. package/components/common/pages/backups/modals/restore/disks/Disks.vue +28 -0
  36. package/components/common/pages/backups/modals/restore/disks/tableView/TableView.vue +101 -0
  37. package/components/common/pages/backups/modals/restore/disks/tableView/lib/config/keys.ts +14 -0
  38. package/components/common/pages/backups/modals/restore/disks/tableView/lib/config/table.ts +114 -0
  39. package/components/common/pages/backups/modals/restore/disks/tableView/lib/models/interfaces.ts +4 -0
  40. package/components/common/pages/backups/modals/restore/disks/tableView/lib/models/types.ts +10 -0
  41. package/components/common/pages/backups/modals/restore/lib/config/steps.ts +92 -0
  42. package/components/common/pages/backups/modals/restore/name/Name.vue +159 -0
  43. package/components/common/pages/backups/modals/restore/networks/Networks.vue +67 -0
  44. package/components/common/pages/backups/modals/restore/networks/table/Table.vue +216 -0
  45. package/components/common/pages/backups/modals/restore/networks/table/adapterType/AdapterType.vue +32 -0
  46. package/components/common/pages/backups/modals/restore/networks/table/adapterType/lib/config/options.ts +8 -0
  47. package/components/common/pages/backups/modals/restore/networks/table/lib/config/networkTable.ts +82 -0
  48. package/components/common/pages/backups/modals/restore/networks/table/lib/config/tableKeys.ts +10 -0
  49. package/components/common/pages/backups/modals/restore/networks/table/lib/models/types.ts +6 -0
  50. package/components/common/pages/backups/modals/restore/types/Types.vue +61 -0
  51. package/components/common/pages/backups/modals/restore/types/lib/config/descriptions.ts +7 -0
  52. package/components/common/pages/backups/modals/restore/types/lib/config/typeOptions.ts +24 -0
  53. package/components/common/pages/backups/tools/Tools.vue +75 -0
  54. package/components/common/pages/backups/tools/lib/config/tabs.ts +36 -0
  55. package/components/common/pages/scheduledTasks/lib/models/interfaces.ts +35 -0
  56. package/components/common/pages/scheduledTasks/lib/models/types.ts +16 -0
  57. package/components/common/pages/scheduledTasks/lib/utils/utils.ts +84 -0
  58. package/components/common/treeView/TreeView.vue +52 -0
  59. package/lib/models/interfaces.ts +1 -1
  60. package/package.json +1 -1
  61. /package/components/common/{backup/actions → pages/backups/modals}/deleteBackup/DeleteBackup.vue +0 -0
@@ -0,0 +1,101 @@
1
+ <template>
2
+ <div class="data-table-view">
3
+ <div class="data-table-view__inner">
4
+ <atoms-table-data-grid
5
+ v-model:selected-row="selectedRowsLocal"
6
+ v-model:column-keys="columnKeys"
7
+ class="data-table"
8
+ type="checkbox"
9
+ test-id="disks-table"
10
+ :head-items="headItems"
11
+ :body-items="bodyItems"
12
+ :total-items="props.totalItems"
13
+ :total-pages="props.totalPages"
14
+ :items-per-page="itemsPerPage"
15
+ hide-pagination
16
+ server-off
17
+ />
18
+ </div>
19
+ </div>
20
+ </template>
21
+
22
+ <script lang="ts" setup>
23
+ import type {
24
+ UI_I_HeadItem,
25
+ UI_I_ColumnKey,
26
+ UI_I_BodyItem,
27
+ } from '~/components/atoms/table/dataGrid/lib/models/interfaces'
28
+ import type { UI_I_Localization } from '~/lib/models/interfaces'
29
+ import type { UI_I_Pvm } from '~/lib/models/store/vm/interfaces'
30
+ import { itemsPerPage } from '~/components/atoms/table/dataGrid/lib/config/itemsPerPage'
31
+ // import type { I_DiskDevice } from '~/store/modules/backupManager/modules/backups/lib/models/interfaces'
32
+ // import type { I_Pvm } from '~/store/modules/inventory/modules/backup/lib/models/interfaces'
33
+ import * as table from '~/components/common/pages/backups/modals/createBackup/disks/tableView/lib/config/table'
34
+
35
+ const props = defineProps<{
36
+ dataTable: I_DiskDevice[]
37
+ loading: boolean
38
+ totalItems: number
39
+ totalPages: number
40
+ }>()
41
+ const localization = computed<UI_I_Localization>(() => useLocal())
42
+
43
+ const selectedRows = defineModel<UI_I_Pvm['disk_devices']>('selected', {
44
+ required: true,
45
+ })
46
+ // const selectedRowsOld: I_Pvm['disk_devices'] = useDeepCopy(selectedRows.value)
47
+
48
+ const selectedRowsLocal = ref<string[]>(
49
+ selectedRows.value?.map((item) => item.source) || []
50
+ )
51
+ watch(
52
+ selectedRowsLocal,
53
+ (newValue) => {
54
+ // TODO понять почему тогда было так
55
+ // selectedRows.value = selectedRowsOld.filter((item) =>
56
+ // newValue.includes(item.source)
57
+ // )
58
+ selectedRows.value = props.dataTable.filter((item) =>
59
+ newValue.includes(item.source)
60
+ )
61
+ },
62
+ { deep: true }
63
+ )
64
+
65
+ const columnKeys = ref<UI_I_ColumnKey[]>(table.columnKeys(localization.value))
66
+
67
+ const headItems = computed<UI_I_HeadItem[]>(() =>
68
+ table.headItems(localization.value)
69
+ )
70
+
71
+ const bodyItems = computed<UI_I_BodyItem[][]>(() => {
72
+ let result: UI_I_BodyItem[][] = []
73
+ if (props.dataTable.length) {
74
+ result = table.bodyItems(props.dataTable)
75
+ }
76
+
77
+ return result
78
+ })
79
+ </script>
80
+
81
+ <style lang="scss" scoped>
82
+ .data-table-view {
83
+ overflow: hidden;
84
+ height: inherit;
85
+ margin-bottom: 10px;
86
+ margin-right: 10px;
87
+
88
+ &__inner {
89
+ height: inherit;
90
+ }
91
+ :deep(.data-table) {
92
+ height: inherit;
93
+ .datagrid-outer-wrapper {
94
+ height: inherit;
95
+ }
96
+ }
97
+ }
98
+ .vm-icon {
99
+ margin-right: 5px;
100
+ }
101
+ </style>
@@ -0,0 +1,14 @@
1
+ import type {
2
+ UI_T_DiskTableKeysTuple
3
+ } from "~/components/common/pages/backups/modals/restore/disks/tableView/lib/models/types";
4
+
5
+ export const tableKeys: UI_T_DiskTableKeysTuple = [
6
+ 'source',
7
+ 'device_type',
8
+ 'bus',
9
+ 'target',
10
+ 'capacity_mb',
11
+ 'used_mb',
12
+ 'free_mb',
13
+ 'volume_path',
14
+ ]
@@ -0,0 +1,114 @@
1
+ import type {
2
+ UI_I_HeadItem,
3
+ UI_I_ColumnKey,
4
+ UI_I_BodyItem,
5
+ } from '~/components/atoms/table/dataGrid/lib/models/interfaces'
6
+ import type { UI_I_Localization } from '~/lib/models/interfaces'
7
+ import {
8
+ constructHeadItem,
9
+ constructColumnKey,
10
+ } from '~/components/atoms/table/dataGrid/lib/utils/constructDataTable'
11
+ import { tableKeys } from '~/components/common/pages/backups/modals/createBackup/disks/tableView/lib/config/keys'
12
+
13
+ const getItems = (
14
+ localization: UI_I_Localization
15
+ ): [string, boolean, string, string][] => {
16
+ return [
17
+ [localization.common.source, true, '180px', tableKeys[0]],
18
+ [localization.common.deviceType, true, '180px', tableKeys[1]],
19
+ [localization.common.bus, true, '180px', tableKeys[2]],
20
+ [localization.common.target, true, '180px', tableKeys[3]],
21
+ [localization.common.capacity, true, '180px', tableKeys[4]],
22
+ [localization.common.used, true, '180px', tableKeys[5]],
23
+ [localization.common.free, true, '180px', tableKeys[6]],
24
+ [localization.common.volumePath, true, '180px', tableKeys[7]],
25
+ ]
26
+ }
27
+ export const columnKeys = (
28
+ localization: UI_I_Localization
29
+ ): UI_I_ColumnKey[] => {
30
+ const result: UI_I_ColumnKey[] = []
31
+ getItems(localization).forEach((item, i) => {
32
+ result.push(
33
+ constructColumnKey(`col${i}`, item[0], item[1], `show-column-${item[3]}`)
34
+ )
35
+ })
36
+ return result
37
+ }
38
+ export const headItems = (localization: UI_I_Localization): UI_I_HeadItem[] => {
39
+ const result: UI_I_HeadItem[] = []
40
+ getItems(localization).forEach((item, i) => {
41
+ result.push(
42
+ constructHeadItem(
43
+ `col${i}`,
44
+ item[0],
45
+ item[3],
46
+ true,
47
+ item[2],
48
+ undefined,
49
+ item[3]
50
+ )
51
+ )
52
+ })
53
+ return result
54
+ }
55
+
56
+ export const bodyItems = (data: I_DiskDevice[]): UI_I_BodyItem[][] => {
57
+ const { $binary }: any = useNuxtApp()
58
+ const lang = useLocalStorage('lang') === 'ru_RU' ? 'ru' : 'en'
59
+
60
+ const bodyItems: UI_I_BodyItem[][] = []
61
+ data.forEach((disk: I_DiskDevice) => {
62
+ bodyItems.push([
63
+ {
64
+ key: 'col0',
65
+ text: disk[tableKeys[0]],
66
+ id: disk.source,
67
+ data: {
68
+ location: disk.source,
69
+ target: disk.target,
70
+ },
71
+ },
72
+ {
73
+ key: 'col1',
74
+ text: disk[tableKeys[1]],
75
+ id: disk.source,
76
+ },
77
+ {
78
+ key: 'col2',
79
+ text: disk[tableKeys[2]],
80
+ id: disk.source,
81
+ },
82
+ {
83
+ key: 'col3',
84
+ text: disk[tableKeys[3]],
85
+ id: disk.source,
86
+ },
87
+ {
88
+ key: 'col4',
89
+ text: $binary.round(disk[tableKeys[4]], false, lang),
90
+ id: disk.source,
91
+ },
92
+ {
93
+ key: 'col5',
94
+ text: $binary.round(
95
+ disk[tableKeys[4]] - disk[tableKeys[6]],
96
+ false,
97
+ lang
98
+ ),
99
+ id: disk.source,
100
+ },
101
+ {
102
+ key: 'col6',
103
+ text: $binary.round(disk[tableKeys[6]], false, lang),
104
+ id: disk.source,
105
+ },
106
+ {
107
+ key: 'col7',
108
+ text: disk[tableKeys[7]],
109
+ id: disk.source,
110
+ },
111
+ ])
112
+ })
113
+ return bodyItems
114
+ }
@@ -0,0 +1,4 @@
1
+ export interface UI_I_DiskData {
2
+ location: string
3
+ target: string
4
+ }
@@ -0,0 +1,10 @@
1
+ export type UI_T_DiskTableKeysTuple = [
2
+ 'source',
3
+ 'device_type',
4
+ 'bus',
5
+ 'target',
6
+ 'capacity_mb',
7
+ 'used_mb',
8
+ 'free_mb',
9
+ 'volume_path',
10
+ ]
@@ -0,0 +1,92 @@
1
+ import type { UI_I_Localization } from '~/lib/models/interfaces'
2
+ import type { UI_I_WizardStep } from '~/components/atoms/wizard/lib/models/interfaces'
3
+ import { UI_E_WIZARD_STATUS } from '~/components/atoms/wizard/lib/models/enums'
4
+
5
+ export const stepsFunc = (
6
+ localization: UI_I_Localization
7
+ ): UI_I_WizardStep[] => {
8
+ return [
9
+ {
10
+ id: 0,
11
+ stepName: '',
12
+ title: localization.common.selectAType,
13
+ subTitle: '',
14
+ status: UI_E_WIZARD_STATUS.SELECTED,
15
+ isValid: true,
16
+ fields: {},
17
+ testId: 'backup-restore-type',
18
+ },
19
+ {
20
+ id: 1,
21
+ stepName: '',
22
+ title: localization.common.selectName,
23
+ subTitle: '',
24
+ status: UI_E_WIZARD_STATUS.INACTIVE,
25
+ isValid: true,
26
+ testId: 'backup-restore-select-name',
27
+ fields: {
28
+ name: {
29
+ field: '',
30
+ alert: '',
31
+ },
32
+ },
33
+ },
34
+ {
35
+ id: 2,
36
+ stepName: '',
37
+ title: localization.common.selectStorage,
38
+ subTitle: '',
39
+ status: UI_E_WIZARD_STATUS.INACTIVE,
40
+ isValid: true,
41
+ testId: 'backup-restore-select-storage',
42
+ fields: {
43
+ storage: {
44
+ field: '',
45
+ alert: '',
46
+ },
47
+ },
48
+ },
49
+ {
50
+ id: 3,
51
+ stepName: '',
52
+ title: localization.common.selectDisks,
53
+ subTitle: localization.common.selectDisksInvolvedBackup,
54
+ status: UI_E_WIZARD_STATUS.INACTIVE,
55
+ isValid: true,
56
+ fields: {
57
+ disk_devices: {
58
+ field: '',
59
+ alert: '',
60
+ },
61
+ },
62
+ testId: 'backup-restore-select--disks',
63
+ },
64
+ {
65
+ id: 4,
66
+ stepName: '',
67
+ title: localization.common.selectNetwork,
68
+ subTitle: '',
69
+ status: UI_E_WIZARD_STATUS.INACTIVE,
70
+ isValid: true,
71
+ testId: 'backup-restore-select-network',
72
+ fields: {},
73
+ },
74
+ {
75
+ id: 5,
76
+ stepName: '',
77
+ title: localization.common.readyComplete,
78
+ subTitle: localization.common.clickFinishRestore,
79
+ status: UI_E_WIZARD_STATUS.INACTIVE,
80
+ isValid: true,
81
+ testId: 'backup-restore-ready-complete',
82
+ fields: {},
83
+ },
84
+ ]
85
+ }
86
+
87
+ export const stepsSchemeInitial = [
88
+ // [0, 1, 5], // disk only
89
+ [0, 5], // disk only
90
+ [0, 5], // existing
91
+ [0, 1, 2, 3, 4, 5], // as new
92
+ ]
@@ -0,0 +1,159 @@
1
+ <template>
2
+ <div class="select-name">
3
+ <atoms-alert
4
+ v-show="errors.length"
5
+ :items="errors"
6
+ status="alert-danger"
7
+ test-id="name-alert"
8
+ @remove="onRemoveValidationErrors"
9
+ />
10
+
11
+ <form
12
+ id="virtual-machine-form"
13
+ class="flex items-center pt-5"
14
+ @submit.prevent
15
+ >
16
+ <label for="virtual-machine-name"
17
+ >{{ localization.common.virtualMachineName }}:</label
18
+ >
19
+ <input
20
+ id="virtual-machine-name"
21
+ v-model.trim="model.pvm.name"
22
+ data-id="virtual-machine-name-input"
23
+ type="text"
24
+ maxlength="54"
25
+ />
26
+ </form>
27
+ </div>
28
+ </template>
29
+
30
+ <script setup lang="ts">
31
+ import type { UI_I_Localization } from '~/lib/models/interfaces'
32
+ // import type { I_RestoreForm } from '~/components/common/pages/backups/modals/lib/models/interfaces'
33
+
34
+ const model = defineModel<I_RestoreForm>({ required: true })
35
+
36
+ const props = defineProps<{
37
+ show: boolean
38
+ nameFormSubmit: null | Function
39
+ }>()
40
+ const emits = defineEmits<{
41
+ (event: 'check-name', value: [string, (error: any) => void]): void
42
+ }>()
43
+
44
+ const localization = computed<UI_I_Localization>(() => useLocal())
45
+
46
+ watch(
47
+ () => props.nameFormSubmit,
48
+ (newValue) => {
49
+ newValue && submit(newValue)
50
+ }
51
+ )
52
+
53
+ const submit = async (cb: Function): Promise<void> => {
54
+ const name = model.value.pvm.name
55
+
56
+ if (name !== '') {
57
+ const isNameValid = await checkNameIsValid(name)
58
+ if (!isNameValid) {
59
+ // @ts-ignore
60
+ cb(false)
61
+ return
62
+ }
63
+ }
64
+
65
+ onRemoveValidationErrors()
66
+ // @ts-ignore
67
+ cb(true)
68
+ }
69
+ const checkNameIsValid = async (name: string): Promise<boolean> => {
70
+ return new Promise((resolve) => {
71
+ emits('check-name', [
72
+ name,
73
+ (error): void => {
74
+ const status = error?.statusCode || 200
75
+ switch (status) {
76
+ case 405: // Invalid kind
77
+ showValidationErrors([
78
+ localization.value.common.kindValidationDescription,
79
+ ])
80
+ resolve(false)
81
+ break
82
+ case 406: // Invalid name
83
+ showValidationErrors([
84
+ localization.value.common.vmNameValidationDescription,
85
+ ])
86
+ resolve(false)
87
+ break
88
+ case 409: // Name exist
89
+ showValidationErrors([
90
+ localization.value.common.vmNameExistInSelectedLocation,
91
+ ])
92
+ resolve(false)
93
+ break
94
+ }
95
+
96
+ resolve(true)
97
+ },
98
+ ])
99
+ })
100
+ }
101
+
102
+ const errors = ref<string[]>([])
103
+ const showValidationErrors = (data: string[]): void => {
104
+ errors.value = data
105
+ }
106
+ const onRemoveValidationErrors = (): void => {
107
+ errors.value = []
108
+ }
109
+
110
+ watch(
111
+ () => props.show,
112
+ (newValue) => {
113
+ if (!newValue) return
114
+
115
+ const input = document.getElementById('virtual-machine-name')
116
+ if (!input) return
117
+
118
+ setTimeout(() => {
119
+ input.focus()
120
+ }, 0)
121
+ }
122
+ )
123
+ </script>
124
+
125
+ <style scoped lang="scss">
126
+ .select-name {
127
+ form {
128
+ label {
129
+ width: 216px;
130
+ }
131
+ input {
132
+ width: 345px;
133
+ }
134
+ }
135
+
136
+ .tree-view-wrap {
137
+ position: relative;
138
+ border: 1px solid #000;
139
+ padding: 5px;
140
+ max-height: 300px;
141
+ min-height: 200px;
142
+ overflow: auto;
143
+ margin-bottom: 10px;
144
+ }
145
+ }
146
+ .content-signpost {
147
+ .icon-show-help {
148
+ cursor: pointer;
149
+ }
150
+
151
+ .help-title {
152
+ font-size: 22px;
153
+ margin-bottom: 24px;
154
+ }
155
+ .signpost {
156
+ max-width: 360px;
157
+ }
158
+ }
159
+ </style>
@@ -0,0 +1,67 @@
1
+ <template>
2
+ <div class="select-network h-full flex flex-col">
3
+ <atoms-alert
4
+ v-if="props.alertMessages.length"
5
+ :items="props.alertMessages"
6
+ status="alert-danger"
7
+ test-id="import-vms-select-networks-errors"
8
+ @remove="onHideAlert"
9
+ />
10
+
11
+ <common-pages-backups-modals-restore-networks-table
12
+ v-model="model"
13
+ :networks-table="model.pvm.network_devices"
14
+ :source-networks="props.networks"
15
+ :loading="networkTableLoading"
16
+ @hide-alert="onHideAlert"
17
+ />
18
+
19
+ <common-wizards-vm-common-validation-compatibility
20
+ :loading="!model.pvm.network_devices.length"
21
+ status="Success"
22
+ text="compatibilityChecksSucceeded"
23
+ />
24
+ </div>
25
+ </template>
26
+
27
+ <script lang="ts" setup>
28
+ import type { UI_I_NetworkTableItem } from '~/lib/models/store/network/interfaces'
29
+ // import type { I_RestoreForm } from '~/components/common/pages/backups/modals/lib/models/interfaces'
30
+
31
+ const model = defineModel<I_RestoreForm>({ required: true })
32
+ const props = defineProps<{
33
+ alertMessages: string[]
34
+ networks: UI_I_NetworkTableItem[]
35
+ }>()
36
+ const emits = defineEmits<{
37
+ (event: 'get-networks', value: string): void
38
+ (event: 'hide-alert', value: number): void
39
+ }>()
40
+
41
+ const networkTableLoading = ref<boolean>(false)
42
+ const getNetworks = async (): Promise<void> => {
43
+ networkTableLoading.value = true
44
+ emits('get-networks', model.value.pvm.host_id)
45
+ networkTableLoading.value = false
46
+ }
47
+ getNetworks()
48
+
49
+ const onHideAlert = (): void => {
50
+ emits('hide-alert', 4)
51
+ }
52
+ </script>
53
+
54
+ <style lang="scss" scoped>
55
+ .select-network {
56
+ margin-top: 10px;
57
+ &__text {
58
+ font-size: 12px;
59
+ }
60
+ &__matcher-details {
61
+ margin-top: 10px;
62
+ }
63
+ .vm-migrate-advanced-view-button {
64
+ text-align: end;
65
+ }
66
+ }
67
+ </style>