@xen-orchestra/web-core 0.35.1 → 0.36.0
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.
- package/lib/components/console/VtsRemoteConsole.vue +1 -1
- package/lib/components/copy-button/VtsCopyButton.vue +1 -1
- package/lib/components/layout/VtsLayoutSidebar.vue +1 -1
- package/lib/components/quick-info-card/VtsQuickInfoCard.vue +1 -1
- package/lib/components/relative-time/VtsRelativeTime.vue +2 -3
- package/lib/components/select/VtsSelect.vue +1 -1
- package/lib/components/state-hero/VtsStateHero.vue +20 -10
- package/lib/components/table/VtsRow.vue +26 -0
- package/lib/components/table/VtsTable.vue +99 -42
- package/lib/components/table/cells/VtsCollapsedListCell.vue +59 -0
- package/lib/components/table/cells/VtsHeaderCell.vue +31 -0
- package/lib/components/table/cells/VtsLinkCell.vue +33 -0
- package/lib/components/table/cells/VtsNumberCell.vue +21 -0
- package/lib/components/{size-progress-cell/VtsSizeProgressCell.vue → table/cells/VtsProgressBarCell.vue} +8 -5
- package/lib/components/table/cells/VtsStatusCell.vue +69 -0
- package/lib/components/table/cells/VtsTagCell.vue +24 -0
- package/lib/components/table/cells/VtsTextCell.vue +32 -0
- package/lib/components/table/cells/VtsTruncatedTextCell.vue +64 -0
- package/lib/components/task/VtsQuickTaskButton.vue +1 -1
- package/lib/components/tree/VtsTreeLine.vue +1 -1
- package/lib/components/ui/alert/UiAlert.vue +1 -1
- package/lib/components/ui/button/UiButton.vue +4 -2
- package/lib/components/ui/button-icon/UiButtonIcon.vue +12 -11
- package/lib/components/ui/column-header/UiColumnHeader.vue +22 -0
- package/lib/components/ui/info/UiInfo.vue +5 -1
- package/lib/components/ui/input/UiInput.vue +3 -2
- package/lib/components/ui/link/UiLink.vue +5 -0
- package/lib/components/ui/log-entry-viewer/UiLogEntryViewer.vue +1 -1
- package/lib/components/ui/query-search-bar/UiQuerySearchBar.vue +2 -2
- package/lib/components/ui/table-cell/UiTableCell.vue +41 -0
- package/lib/components/ui/table-pagination/PaginationButton.vue +1 -1
- package/lib/components/ui/toaster/UiToaster.vue +1 -1
- package/lib/components/ui/top-bottom-table/UiTopBottomTable.vue +6 -2
- package/lib/composables/pagination.composable.ts +16 -1
- package/lib/composables/relative-time.composable.ts +8 -61
- package/lib/composables/table-state.composable.ts +56 -0
- package/lib/composables/tree-filter.composable.ts +2 -2
- package/lib/icons/fa-icons.ts +2 -0
- package/lib/icons/object-icons.ts +1 -1
- package/lib/locales/en.json +23 -0
- package/lib/locales/fr.json +23 -0
- package/lib/packages/form-select/use-form-select-controller.ts +1 -0
- package/lib/packages/table/README.md +53 -308
- package/lib/packages/table/define-column.ts +7 -0
- package/lib/packages/table/define-columns.ts +104 -50
- package/lib/packages/table/index.ts +3 -11
- package/lib/packages/table/types.ts +10 -0
- package/lib/{composables/tree.composable.md → packages/tree/README.md} +28 -23
- package/lib/{composables → packages}/tree/branch-definition.ts +9 -4
- package/lib/{composables → packages}/tree/branch.ts +15 -20
- package/lib/{composables → packages}/tree/build-nodes.ts +5 -5
- package/lib/{composables → packages}/tree/define-branch.ts +8 -4
- package/lib/{composables → packages}/tree/define-leaf.ts +8 -3
- package/lib/{composables → packages}/tree/define-tree.ts +10 -5
- package/lib/{composables → packages}/tree/leaf-definition.ts +1 -1
- package/lib/{composables → packages}/tree/leaf.ts +3 -3
- package/lib/{composables → packages}/tree/tree-node-base.ts +18 -3
- package/lib/{composables → packages}/tree/tree-node-definition-base.ts +4 -2
- package/lib/{composables → packages}/tree/types.ts +11 -9
- package/lib/{composables/tree.composable.ts → packages/tree/use-tree.ts} +24 -11
- package/lib/tables/column-definitions/address-column.ts +4 -0
- package/lib/tables/column-definitions/button-column.ts +35 -0
- package/lib/tables/column-definitions/button-icon-column.ts +30 -0
- package/lib/tables/column-definitions/collapsed-list-column.ts +12 -0
- package/lib/tables/column-definitions/date-column.ts +34 -0
- package/lib/tables/column-definitions/info-column.ts +12 -0
- package/lib/tables/column-definitions/input-column.ts +32 -0
- package/lib/tables/column-definitions/link-column.ts +14 -0
- package/lib/tables/column-definitions/literal-column.ts +9 -0
- package/lib/tables/column-definitions/number-column.ts +10 -0
- package/lib/tables/column-definitions/percent-column.ts +15 -0
- package/lib/tables/column-definitions/progress-bar-column.ts +10 -0
- package/lib/tables/column-definitions/select-column.ts +12 -0
- package/lib/tables/column-definitions/select-item-column.ts +8 -0
- package/lib/tables/column-definitions/status-column.ts +16 -0
- package/lib/tables/column-definitions/tag-column.ts +11 -0
- package/lib/tables/column-definitions/text-column.ts +11 -0
- package/lib/tables/column-definitions/truncated-text-column.ts +10 -0
- package/lib/tables/column-sets/backup-issue-columns.ts +15 -0
- package/lib/tables/column-sets/backup-job-columns.ts +23 -0
- package/lib/tables/column-sets/backup-job-schedule-columns.ts +21 -0
- package/lib/tables/column-sets/backup-log-columns.ts +19 -0
- package/lib/tables/column-sets/host-columns.ts +19 -0
- package/lib/tables/column-sets/network-columns.ts +22 -0
- package/lib/tables/column-sets/new-vm-network-columns.ts +24 -0
- package/lib/tables/column-sets/new-vm-sr-columns.ts +33 -0
- package/lib/tables/column-sets/patch-columns.ts +13 -0
- package/lib/tables/column-sets/pif-columns.ts +23 -0
- package/lib/tables/column-sets/server-columns.ts +18 -0
- package/lib/tables/column-sets/sr-columns.ts +20 -0
- package/lib/tables/column-sets/vdi-columns.ts +21 -0
- package/lib/tables/column-sets/vif-columns.ts +23 -0
- package/lib/tables/column-sets/vm-columns.ts +21 -0
- package/lib/tables/helpers/render-body-cell.ts +4 -0
- package/lib/tables/helpers/render-head-cell.ts +6 -0
- package/lib/tables/helpers/render-loading-cell.ts +5 -0
- package/lib/tables/types.ts +7 -0
- package/lib/utils/size.util.ts +5 -9
- package/package.json +1 -1
- package/lib/components/data-table/VtsDataTable.vue +0 -70
- package/lib/components/table/ColumnTitle.vue +0 -152
- package/lib/packages/table/apply-extensions.ts +0 -26
- package/lib/packages/table/define-renderer/define-table-cell-renderer.ts +0 -27
- package/lib/packages/table/define-renderer/define-table-renderer.ts +0 -47
- package/lib/packages/table/define-renderer/define-table-row-renderer.ts +0 -29
- package/lib/packages/table/define-renderer/define-table-section-renderer.ts +0 -29
- package/lib/packages/table/define-table/define-multi-source-table.ts +0 -39
- package/lib/packages/table/define-table/define-table.ts +0 -13
- package/lib/packages/table/define-table/define-typed-table.ts +0 -18
- package/lib/packages/table/transform-sources.ts +0 -13
- package/lib/packages/table/types/extensions.ts +0 -16
- package/lib/packages/table/types/index.ts +0 -47
- package/lib/packages/table/types/table-cell.ts +0 -18
- package/lib/packages/table/types/table-row.ts +0 -20
- package/lib/packages/table/types/table-section.ts +0 -19
- package/lib/packages/table/types/table.ts +0 -28
- package/lib/types/button.type.ts +0 -3
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Branch } from '@core/
|
|
2
|
-
import type { Identifiable, Labeled, TreeContext, TreeNodeId, TreeNodeOptions } from '@core/
|
|
1
|
+
import type { Branch } from '@core/packages/tree/branch'
|
|
2
|
+
import type { Identifiable, Labeled, TreeContext, TreeNodeId, TreeNodeOptions } from '@core/packages/tree/types'
|
|
3
3
|
|
|
4
4
|
export abstract class TreeNodeBase<TData extends object = any, TDiscriminator = any> {
|
|
5
5
|
abstract readonly isBranch: boolean
|
|
@@ -7,6 +7,7 @@ export abstract class TreeNodeBase<TData extends object = any, TDiscriminator =
|
|
|
7
7
|
abstract isExcluded: boolean
|
|
8
8
|
abstract statuses: Record<string, boolean>
|
|
9
9
|
|
|
10
|
+
readonly treeId: string
|
|
10
11
|
readonly data: TData
|
|
11
12
|
readonly depth: number
|
|
12
13
|
readonly parent: Branch | undefined
|
|
@@ -14,12 +15,14 @@ export abstract class TreeNodeBase<TData extends object = any, TDiscriminator =
|
|
|
14
15
|
readonly options: TreeNodeOptions<TData, TDiscriminator>
|
|
15
16
|
|
|
16
17
|
constructor(
|
|
18
|
+
treeId: string,
|
|
17
19
|
data: TData,
|
|
18
20
|
parent: Branch | undefined,
|
|
19
21
|
context: TreeContext,
|
|
20
22
|
depth: number,
|
|
21
23
|
options?: TreeNodeOptions<TData, TDiscriminator>
|
|
22
24
|
) {
|
|
25
|
+
this.treeId = treeId
|
|
23
26
|
this.data = data
|
|
24
27
|
this.parent = parent
|
|
25
28
|
this.context = context
|
|
@@ -27,7 +30,7 @@ export abstract class TreeNodeBase<TData extends object = any, TDiscriminator =
|
|
|
27
30
|
this.options = options ?? ({} as TreeNodeOptions<TData, TDiscriminator>)
|
|
28
31
|
}
|
|
29
32
|
|
|
30
|
-
get
|
|
33
|
+
get dataId(): TreeNodeId {
|
|
31
34
|
if (this.options.getId === undefined) {
|
|
32
35
|
return (this.data as Identifiable).id
|
|
33
36
|
}
|
|
@@ -39,6 +42,18 @@ export abstract class TreeNodeBase<TData extends object = any, TDiscriminator =
|
|
|
39
42
|
return this.data[this.options.getId as keyof TData] as TreeNodeId
|
|
40
43
|
}
|
|
41
44
|
|
|
45
|
+
get id(): TreeNodeId {
|
|
46
|
+
const parts = [this.dataId, this.treeId]
|
|
47
|
+
let currentParent = this.parent
|
|
48
|
+
|
|
49
|
+
while (currentParent) {
|
|
50
|
+
parts.push(currentParent.treeId)
|
|
51
|
+
currentParent = currentParent.parent
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return parts.reverse().join('.')
|
|
55
|
+
}
|
|
56
|
+
|
|
42
57
|
get label() {
|
|
43
58
|
if (this.options.getLabel === undefined) {
|
|
44
59
|
return (this.data as Labeled).label
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import type { TreeNodeOptions } from '@core/
|
|
1
|
+
import type { TreeNodeOptions } from '@core/packages/tree/types'
|
|
2
2
|
|
|
3
3
|
export abstract class TreeNodeDefinitionBase<TData extends object, TDiscriminator> {
|
|
4
4
|
abstract readonly isBranch: boolean
|
|
5
|
+
readonly treeId: string
|
|
5
6
|
data: TData
|
|
6
7
|
options: TreeNodeOptions<TData, TDiscriminator>
|
|
7
8
|
|
|
8
|
-
constructor(data: TData, options: TreeNodeOptions<TData, TDiscriminator>) {
|
|
9
|
+
constructor(treeId: string, data: TData, options: TreeNodeOptions<TData, TDiscriminator>) {
|
|
10
|
+
this.treeId = treeId
|
|
9
11
|
this.data = data
|
|
10
12
|
this.options = options
|
|
11
13
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import type { Branch } from '@core/
|
|
2
|
-
import type { BranchDefinition } from '@core/
|
|
3
|
-
import type { Leaf } from '@core/
|
|
4
|
-
import type { LeafDefinition } from '@core/
|
|
5
|
-
import type { TreeNodeBase } from '@core/
|
|
6
|
-
import { useTree } from '@core/
|
|
1
|
+
import type { Branch } from '@core/packages/tree/branch'
|
|
2
|
+
import type { BranchDefinition } from '@core/packages/tree/branch-definition'
|
|
3
|
+
import type { Leaf } from '@core/packages/tree/leaf'
|
|
4
|
+
import type { LeafDefinition } from '@core/packages/tree/leaf-definition'
|
|
5
|
+
import type { TreeNodeBase } from '@core/packages/tree/tree-node-base'
|
|
6
|
+
import { useTree } from '@core/packages/tree/use-tree'
|
|
7
|
+
import type { Ref } from 'vue'
|
|
7
8
|
|
|
8
9
|
export type TreeNodeId = string | number
|
|
9
10
|
|
|
@@ -62,13 +63,14 @@ export type ChildTreeDefinitionGetter<TData extends object, TChildDefinition ext
|
|
|
62
63
|
export type TreeContext = {
|
|
63
64
|
allowMultiSelect: boolean
|
|
64
65
|
selectedIds: Set<TreeNodeId>
|
|
65
|
-
|
|
66
|
+
collapsedIds: Set<TreeNodeId>
|
|
66
67
|
activeId: TreeNodeId | undefined
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
export type UseTreeOptions = {
|
|
70
71
|
allowMultiSelect?: boolean
|
|
71
|
-
|
|
72
|
+
collapse?: boolean
|
|
73
|
+
collapsedIds?: Ref<Set<TreeNodeId>>
|
|
72
74
|
selectedLabel?:
|
|
73
75
|
| ((nodes: TreeNode[]) => string)
|
|
74
76
|
| {
|
|
@@ -88,5 +90,5 @@ export type LeafStatuses = {
|
|
|
88
90
|
export type BranchStatuses = LeafStatuses & {
|
|
89
91
|
'selected-partial': boolean
|
|
90
92
|
'selected-full': boolean
|
|
91
|
-
|
|
93
|
+
collapsed: boolean
|
|
92
94
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { buildNodes } from '@core/
|
|
1
|
+
import { buildNodes } from '@core/packages/tree/build-nodes'
|
|
2
2
|
import type {
|
|
3
3
|
DefinitionToTreeNode,
|
|
4
4
|
TreeContext,
|
|
@@ -6,7 +6,7 @@ import type {
|
|
|
6
6
|
TreeNodeDefinition,
|
|
7
7
|
TreeNodeId,
|
|
8
8
|
UseTreeOptions,
|
|
9
|
-
} from '@core/
|
|
9
|
+
} from '@core/packages/tree/types'
|
|
10
10
|
import { computed, type MaybeRefOrGetter, reactive, ref, toValue } from 'vue'
|
|
11
11
|
|
|
12
12
|
export function useTree<
|
|
@@ -14,21 +14,21 @@ export function useTree<
|
|
|
14
14
|
TTreeNode extends DefinitionToTreeNode<TDefinition> = DefinitionToTreeNode<TDefinition>,
|
|
15
15
|
>(definitions: MaybeRefOrGetter<TDefinition[]>, options: UseTreeOptions = {}) {
|
|
16
16
|
const selectedIds = ref(new Set<TreeNodeId>())
|
|
17
|
-
const
|
|
17
|
+
const collapsedIds = options.collapsedIds ?? ref(new Set<TreeNodeId>())
|
|
18
18
|
const activeId = ref<TreeNodeId>()
|
|
19
19
|
|
|
20
20
|
const context = reactive({
|
|
21
21
|
allowMultiSelect: options.allowMultiSelect ?? false,
|
|
22
22
|
selectedIds,
|
|
23
|
-
|
|
23
|
+
collapsedIds,
|
|
24
24
|
activeId,
|
|
25
|
-
})
|
|
25
|
+
}) satisfies TreeContext
|
|
26
26
|
|
|
27
27
|
const nodes = computed(() => {
|
|
28
28
|
const nodes = buildNodes<TDefinition, TTreeNode>(toValue(definitions), context)
|
|
29
29
|
|
|
30
|
-
if (options.
|
|
31
|
-
nodes.forEach(node => node.isBranch && node.
|
|
30
|
+
if (options.collapse) {
|
|
31
|
+
nodes.forEach(node => node.isBranch && node.toggleCollapse(true, true))
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
return nodes
|
|
@@ -52,11 +52,24 @@ export function useTree<
|
|
|
52
52
|
|
|
53
53
|
const visibleNodes = computed(() => nodes.value.filter(node => !node.isExcluded))
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
function getNode(id: TreeNodeId | undefined): TreeNode | undefined {
|
|
56
|
+
if (id === undefined) {
|
|
57
|
+
return undefined
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return nodesMap.value.get(id)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const selectedNodes = computed(() =>
|
|
64
|
+
Array.from(selectedIds.value.values())
|
|
65
|
+
.map(id => getNode(id))
|
|
66
|
+
.filter(node => node !== undefined)
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
const expandedIds = computed(() => Array.from(nodesMap.value.keys()).filter(id => !collapsedIds.value.has(id)))
|
|
70
|
+
|
|
71
|
+
const expandedNodes = computed(() => expandedIds.value.map(id => getNode(id)).filter(node => node !== undefined))
|
|
57
72
|
|
|
58
|
-
const selectedNodes = computed(() => getNodes(Array.from(selectedIds.value.values())))
|
|
59
|
-
const expandedNodes = computed(() => getNodes(Array.from(expandedIds.value.values())))
|
|
60
73
|
const activeNode = computed(() => getNode(activeId.value))
|
|
61
74
|
|
|
62
75
|
const selectedLabel = computed(() => {
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { useCollapsedListColumn } from '@core/tables/column-definitions/collapsed-list-column.ts'
|
|
2
|
+
import type { HeaderConfig } from '@core/tables/types.ts'
|
|
3
|
+
|
|
4
|
+
export const useAddressColumn = (config?: HeaderConfig) => useCollapsedListColumn({ headerIcon: 'fa:at', ...config })
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import UiButton, {
|
|
2
|
+
type ButtonAccent,
|
|
3
|
+
type ButtonProps,
|
|
4
|
+
type ButtonVariant,
|
|
5
|
+
type ButtonSize,
|
|
6
|
+
} from '@core/components/ui/button/UiButton.vue'
|
|
7
|
+
import { defineColumn } from '@core/packages/table/define-column'
|
|
8
|
+
import { renderBodyCell } from '@core/tables/helpers/render-body-cell'
|
|
9
|
+
import { renderHeadCell } from '@core/tables/helpers/render-head-cell'
|
|
10
|
+
import type { HeaderConfig } from '@core/tables/types.ts'
|
|
11
|
+
import { h, toValue, type MaybeRefOrGetter } from 'vue'
|
|
12
|
+
|
|
13
|
+
type ButtonConfig = {
|
|
14
|
+
buttonAccent?: MaybeRefOrGetter<ButtonAccent>
|
|
15
|
+
buttonVariant?: MaybeRefOrGetter<ButtonVariant>
|
|
16
|
+
buttonSize?: MaybeRefOrGetter<ButtonSize>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const useButtonColumn = defineColumn((config?: HeaderConfig & ButtonConfig) => ({
|
|
20
|
+
renderHead: () => renderHeadCell(config?.headerIcon, config?.headerLabel),
|
|
21
|
+
renderBody: (label: string, onClick: () => void, props?: Partial<ButtonProps>) =>
|
|
22
|
+
renderBodyCell(() =>
|
|
23
|
+
h(
|
|
24
|
+
UiButton,
|
|
25
|
+
{
|
|
26
|
+
accent: toValue(config?.buttonAccent) ?? 'brand',
|
|
27
|
+
variant: toValue(config?.buttonVariant) ?? 'primary',
|
|
28
|
+
size: toValue(config?.buttonSize) ?? 'medium',
|
|
29
|
+
...props,
|
|
30
|
+
onClick,
|
|
31
|
+
},
|
|
32
|
+
() => label
|
|
33
|
+
)
|
|
34
|
+
),
|
|
35
|
+
}))
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import UiButtonIcon, {
|
|
2
|
+
type ButtonIconAccent,
|
|
3
|
+
type ButtonIconSize,
|
|
4
|
+
} from '@core/components/ui/button-icon/UiButtonIcon.vue'
|
|
5
|
+
import UiTableCell from '@core/components/ui/table-cell/UiTableCell.vue'
|
|
6
|
+
import type { IconName } from '@core/icons'
|
|
7
|
+
import { defineColumn } from '@core/packages/table/define-column'
|
|
8
|
+
import { renderHeadCell } from '@core/tables/helpers/render-head-cell'
|
|
9
|
+
import type { HeaderConfig } from '@core/tables/types.ts'
|
|
10
|
+
import { h, toValue, type MaybeRefOrGetter } from 'vue'
|
|
11
|
+
|
|
12
|
+
type ButtonIconConfig = {
|
|
13
|
+
buttonIcon: MaybeRefOrGetter<IconName>
|
|
14
|
+
buttonSize?: MaybeRefOrGetter<ButtonIconSize>
|
|
15
|
+
buttonAccent?: MaybeRefOrGetter<ButtonIconAccent>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const useButtonIconColumn = defineColumn((config: HeaderConfig & ButtonIconConfig) => ({
|
|
19
|
+
renderHead: () => renderHeadCell(config.headerIcon, config.headerLabel),
|
|
20
|
+
renderBody: (onClick: () => void) =>
|
|
21
|
+
h(UiTableCell, { align: 'center', style: 'width: 6rem' }, () =>
|
|
22
|
+
h(UiButtonIcon, {
|
|
23
|
+
icon: toValue(config.buttonIcon),
|
|
24
|
+
accent: toValue(config.buttonAccent) ?? 'brand',
|
|
25
|
+
size: toValue(config.buttonSize) ?? 'small',
|
|
26
|
+
targetScale: 1.5,
|
|
27
|
+
onClick,
|
|
28
|
+
})
|
|
29
|
+
),
|
|
30
|
+
}))
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import VtsCollapsedListCell from '@core/components/table/cells/VtsCollapsedListCell.vue'
|
|
2
|
+
import { defineColumn } from '@core/packages/table/define-column.ts'
|
|
3
|
+
import { renderHeadCell } from '@core/tables/helpers/render-head-cell.ts'
|
|
4
|
+
import type { HeaderConfig } from '@core/tables/types.ts'
|
|
5
|
+
import type { MaybeArray } from '@core/types/utility.type.ts'
|
|
6
|
+
import { toArray } from '@core/utils/to-array.utils.ts'
|
|
7
|
+
import { h } from 'vue'
|
|
8
|
+
|
|
9
|
+
export const useCollapsedListColumn = defineColumn((config?: HeaderConfig) => ({
|
|
10
|
+
renderHead: () => renderHeadCell(config?.headerIcon ?? 'fa:square-caret-down', config?.headerLabel),
|
|
11
|
+
renderBody: (items: MaybeArray<string>) => h(VtsCollapsedListCell, { items: toArray(items) }),
|
|
12
|
+
}))
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import VtsRelativeTime from '@core/components/relative-time/VtsRelativeTime.vue'
|
|
2
|
+
import { defineColumn } from '@core/packages/table/define-column'
|
|
3
|
+
import { renderBodyCell } from '@core/tables/helpers/render-body-cell'
|
|
4
|
+
import { renderHeadCell } from '@core/tables/helpers/render-head-cell'
|
|
5
|
+
import type { HeaderConfig } from '@core/tables/types.ts'
|
|
6
|
+
import type { DateLike } from '@vueuse/shared'
|
|
7
|
+
import { h } from 'vue'
|
|
8
|
+
import { useI18n } from 'vue-i18n'
|
|
9
|
+
|
|
10
|
+
type DateConfig = Pick<Intl.DateTimeFormatOptions, 'dateStyle' | 'timeStyle'>
|
|
11
|
+
|
|
12
|
+
export const useDateColumn = defineColumn((config?: HeaderConfig & DateConfig) => {
|
|
13
|
+
const { d } = useI18n()
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
renderHead: () => renderHeadCell(config?.headerIcon ?? 'fa:calendar', config?.headerLabel),
|
|
17
|
+
renderBody: (date?: DateLike, options?: { relative?: boolean }) => {
|
|
18
|
+
return renderBodyCell(() => {
|
|
19
|
+
if (date === undefined) {
|
|
20
|
+
return undefined
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (options?.relative) {
|
|
24
|
+
return h(VtsRelativeTime, { date })
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return d(date, {
|
|
28
|
+
dateStyle: config?.dateStyle,
|
|
29
|
+
timeStyle: config?.timeStyle,
|
|
30
|
+
})
|
|
31
|
+
})
|
|
32
|
+
},
|
|
33
|
+
}
|
|
34
|
+
})
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { InfoAccent } from '@core/components/ui/info/UiInfo.vue'
|
|
2
|
+
import UiInfo from '@core/components/ui/info/UiInfo.vue'
|
|
3
|
+
import { defineColumn } from '@core/packages/table/define-column'
|
|
4
|
+
import { renderBodyCell } from '@core/tables/helpers/render-body-cell'
|
|
5
|
+
import { renderHeadCell } from '@core/tables/helpers/render-head-cell'
|
|
6
|
+
import type { HeaderConfig } from '@core/tables/types.ts'
|
|
7
|
+
import { h } from 'vue'
|
|
8
|
+
|
|
9
|
+
export const useInfoColumn = defineColumn((config?: HeaderConfig) => ({
|
|
10
|
+
renderHead: () => renderHeadCell(config?.headerIcon ?? 'fa:square-caret-down', config?.headerLabel),
|
|
11
|
+
renderBody: (label: string, accent: InfoAccent) => renderBodyCell(() => h(UiInfo, { accent }, () => label)),
|
|
12
|
+
}))
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import UiInput, { type InputType } from '@core/components/ui/input/UiInput.vue'
|
|
2
|
+
import { defineColumn } from '@core/packages/table/define-column'
|
|
3
|
+
import { renderBodyCell } from '@core/tables/helpers/render-body-cell'
|
|
4
|
+
import { renderHeadCell } from '@core/tables/helpers/render-head-cell'
|
|
5
|
+
import type { HeaderConfig } from '@core/tables/types.ts'
|
|
6
|
+
import { h, toValue, type MaybeRefOrGetter, type Ref } from 'vue'
|
|
7
|
+
|
|
8
|
+
type InputConfig = {
|
|
9
|
+
placeholder?: MaybeRefOrGetter<string | undefined>
|
|
10
|
+
type?: MaybeRefOrGetter<InputType | undefined>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const useInputColumn = defineColumn((config?: HeaderConfig & InputConfig) => ({
|
|
14
|
+
renderHead: () =>
|
|
15
|
+
renderHeadCell(
|
|
16
|
+
config?.headerIcon ?? (config?.type === 'number' ? 'fa:hashtag' : 'fa:align-left'),
|
|
17
|
+
config?.headerLabel
|
|
18
|
+
),
|
|
19
|
+
renderBody: (model: Ref<string | number>, inputProps?: { disabled?: boolean }) =>
|
|
20
|
+
renderBodyCell(() =>
|
|
21
|
+
h(UiInput, {
|
|
22
|
+
accent: 'brand',
|
|
23
|
+
placeholder: toValue(config?.placeholder),
|
|
24
|
+
type: toValue(config?.type),
|
|
25
|
+
...inputProps,
|
|
26
|
+
modelValue: toValue(model),
|
|
27
|
+
'onUpdate:modelValue': (value: string | number) => {
|
|
28
|
+
model.value = value
|
|
29
|
+
},
|
|
30
|
+
})
|
|
31
|
+
),
|
|
32
|
+
}))
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import VtsLinkCell, { type VtsLinkCellProps } from '@core/components/table/cells/VtsLinkCell.vue'
|
|
2
|
+
import { defineColumn } from '@core/packages/table/define-column'
|
|
3
|
+
import { renderHeadCell } from '@core/tables/helpers/render-head-cell'
|
|
4
|
+
import type { HeaderConfig } from '@core/tables/types.ts'
|
|
5
|
+
import { h } from 'vue'
|
|
6
|
+
|
|
7
|
+
export const useLinkColumn = defineColumn((config?: HeaderConfig) => ({
|
|
8
|
+
renderHead: () => renderHeadCell(config?.headerIcon ?? 'fa:a', config?.headerLabel),
|
|
9
|
+
renderBody: (link: { label: string } & VtsLinkCellProps) => {
|
|
10
|
+
const { label, ...linkCellProps } = link
|
|
11
|
+
|
|
12
|
+
return h(VtsLinkCell, linkCellProps, () => label)
|
|
13
|
+
},
|
|
14
|
+
}))
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { defineColumn } from '@core/packages/table/define-column'
|
|
2
|
+
import { renderBodyCell } from '@core/tables/helpers/render-body-cell'
|
|
3
|
+
import { renderHeadCell } from '@core/tables/helpers/render-head-cell'
|
|
4
|
+
import type { HeaderConfig } from '@core/tables/types.ts'
|
|
5
|
+
|
|
6
|
+
export const useLiteralColumn = defineColumn((config?: HeaderConfig) => ({
|
|
7
|
+
renderHead: () => renderHeadCell(config?.headerIcon ?? 'fa:square-caret-down', config?.headerLabel),
|
|
8
|
+
renderBody: (value: any) => renderBodyCell(() => value),
|
|
9
|
+
}))
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import VtsNumberCell from '@core/components/table/cells/VtsNumberCell.vue'
|
|
2
|
+
import { defineColumn } from '@core/packages/table/define-column'
|
|
3
|
+
import { renderHeadCell } from '@core/tables/helpers/render-head-cell'
|
|
4
|
+
import type { HeaderConfig } from '@core/tables/types.ts'
|
|
5
|
+
import { h } from 'vue'
|
|
6
|
+
|
|
7
|
+
export const useNumberColumn = defineColumn((headerConfig?: HeaderConfig) => ({
|
|
8
|
+
renderHead: () => renderHeadCell(headerConfig?.headerIcon ?? 'fa:hashtag', headerConfig?.headerLabel),
|
|
9
|
+
renderBody: (number?: number | string, unit?: string) => h(VtsNumberCell, { unit }, () => number),
|
|
10
|
+
}))
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import VtsNumberCell from '@core/components/table/cells/VtsNumberCell.vue'
|
|
2
|
+
import { defineColumn } from '@core/packages/table/define-column'
|
|
3
|
+
import { renderHeadCell } from '@core/tables/helpers/render-head-cell'
|
|
4
|
+
import type { HeaderConfig } from '@core/tables/types.ts'
|
|
5
|
+
import { h } from 'vue'
|
|
6
|
+
import { useI18n } from 'vue-i18n'
|
|
7
|
+
|
|
8
|
+
export const usePercentColumn = defineColumn((headerConfig?: HeaderConfig) => {
|
|
9
|
+
const { n } = useI18n()
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
renderHead: () => renderHeadCell(headerConfig?.headerIcon ?? 'fa:hashtag', headerConfig?.headerLabel),
|
|
13
|
+
renderBody: (number: number) => h(VtsNumberCell, () => n(number, 'percent')),
|
|
14
|
+
}
|
|
15
|
+
})
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import VtsProgressBarCell from '@core/components/table/cells/VtsProgressBarCell.vue'
|
|
2
|
+
import { defineColumn } from '@core/packages/table/define-column'
|
|
3
|
+
import { renderHeadCell } from '@core/tables/helpers/render-head-cell'
|
|
4
|
+
import type { HeaderConfig } from '@core/tables/types.ts'
|
|
5
|
+
import { h } from 'vue'
|
|
6
|
+
|
|
7
|
+
export const useProgressBarColumn = defineColumn((config?: HeaderConfig) => ({
|
|
8
|
+
renderHead: () => renderHeadCell(config?.headerIcon ?? 'fa:hashtag', config?.headerLabel),
|
|
9
|
+
renderBody: (current: number, total: number) => h(VtsProgressBarCell, { current, total }),
|
|
10
|
+
}))
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import VtsSelect from '@core/components/select/VtsSelect.vue'
|
|
2
|
+
import type { FormSelectId } from '@core/packages/form-select/types.ts'
|
|
3
|
+
import { defineColumn } from '@core/packages/table/define-column.ts'
|
|
4
|
+
import { renderBodyCell } from '@core/tables/helpers/render-body-cell'
|
|
5
|
+
import { renderHeadCell } from '@core/tables/helpers/render-head-cell'
|
|
6
|
+
import type { HeaderConfig } from '@core/tables/types.ts'
|
|
7
|
+
import { h } from 'vue'
|
|
8
|
+
|
|
9
|
+
export const useSelectColumn = defineColumn((config?: HeaderConfig) => ({
|
|
10
|
+
renderHead: () => renderHeadCell(config?.headerIcon, config?.headerLabel),
|
|
11
|
+
renderBody: (id: FormSelectId) => renderBodyCell(() => h(VtsSelect, { accent: 'brand', id })),
|
|
12
|
+
}))
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { useButtonIconColumn } from '@core/tables/column-definitions/button-icon-column.ts'
|
|
2
|
+
import type { HeaderConfig } from '@core/tables/types.ts'
|
|
3
|
+
|
|
4
|
+
export const useSelectItemColumn = (config?: HeaderConfig) =>
|
|
5
|
+
useButtonIconColumn({
|
|
6
|
+
buttonIcon: 'fa:eye',
|
|
7
|
+
...config,
|
|
8
|
+
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import VtsStatusCell, { type StatusCellProps } from '@core/components/table/cells/VtsStatusCell.vue'
|
|
2
|
+
import { defineColumn } from '@core/packages/table/define-column'
|
|
3
|
+
import { renderHeadCell } from '@core/tables/helpers/render-head-cell'
|
|
4
|
+
import type { HeaderConfig } from '@core/tables/types.ts'
|
|
5
|
+
import { h } from 'vue'
|
|
6
|
+
import { useI18n } from 'vue-i18n'
|
|
7
|
+
|
|
8
|
+
export const useStatusColumn = defineColumn((config?: HeaderConfig & Omit<StatusCellProps, 'status'>) => {
|
|
9
|
+
const { t } = useI18n()
|
|
10
|
+
const { headerIcon, headerLabel, ...statusCellProps } = config ?? {}
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
renderHead: () => renderHeadCell(headerIcon ?? 'fa:square-caret-down', headerLabel ?? t('status')),
|
|
14
|
+
renderBody: (status: StatusCellProps['status']) => h(VtsStatusCell, { status, ...statusCellProps }),
|
|
15
|
+
}
|
|
16
|
+
})
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import VtsTagCell from '@core/components/table/cells/VtsTagCell.vue'
|
|
2
|
+
import { defineColumn } from '@core/packages/table/define-column'
|
|
3
|
+
import { renderHeadCell } from '@core/tables/helpers/render-head-cell'
|
|
4
|
+
import type { HeaderConfig } from '@core/tables/types.ts'
|
|
5
|
+
import type { MaybeArray } from '@core/types/utility.type'
|
|
6
|
+
import { h } from 'vue'
|
|
7
|
+
|
|
8
|
+
export const useTagColumn = defineColumn((config?: HeaderConfig) => ({
|
|
9
|
+
renderHead: () => renderHeadCell(config?.headerIcon ?? 'fa:square-caret-down', config?.headerLabel),
|
|
10
|
+
renderBody: (tag: MaybeArray<string>) => h(VtsTagCell, { tag }),
|
|
11
|
+
}))
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { TextCellProps } from '@core/components/table/cells/VtsTextCell.vue'
|
|
2
|
+
import VtsTextCell from '@core/components/table/cells/VtsTextCell.vue'
|
|
3
|
+
import { defineColumn } from '@core/packages/table/define-column.ts'
|
|
4
|
+
import { renderHeadCell } from '@core/tables/helpers/render-head-cell.ts'
|
|
5
|
+
import type { HeaderConfig } from '@core/tables/types.ts'
|
|
6
|
+
import { h } from 'vue'
|
|
7
|
+
|
|
8
|
+
export const useTextColumn = defineColumn((config?: HeaderConfig) => ({
|
|
9
|
+
renderHead: () => renderHeadCell(config?.headerIcon, config?.headerLabel),
|
|
10
|
+
renderBody: (content: string, props?: TextCellProps) => h(VtsTextCell, props, () => content),
|
|
11
|
+
}))
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import VtsTruncatedTextCell from '@core/components/table/cells/VtsTruncatedTextCell.vue'
|
|
2
|
+
import { defineColumn } from '@core/packages/table/define-column.ts'
|
|
3
|
+
import { renderHeadCell } from '@core/tables/helpers/render-head-cell.ts'
|
|
4
|
+
import type { HeaderConfig } from '@core/tables/types.ts'
|
|
5
|
+
import { h } from 'vue'
|
|
6
|
+
|
|
7
|
+
export const useTruncatedTextColumn = defineColumn((config?: HeaderConfig & { limit?: number }) => ({
|
|
8
|
+
renderHead: () => renderHeadCell(config?.headerIcon ?? 'fa:align-left', config?.headerLabel),
|
|
9
|
+
renderBody: (content: string) => h(VtsTruncatedTextCell, { content, limit: config?.limit }),
|
|
10
|
+
}))
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { defineColumns } from '@core/packages/table/define-columns.ts'
|
|
2
|
+
import { useLinkColumn } from '@core/tables/column-definitions/link-column'
|
|
3
|
+
import { useStatusColumn } from '@core/tables/column-definitions/status-column.ts'
|
|
4
|
+
import { useI18n } from 'vue-i18n'
|
|
5
|
+
|
|
6
|
+
export const useBackupIssueColumns = defineColumns(() => {
|
|
7
|
+
const { t } = useI18n()
|
|
8
|
+
|
|
9
|
+
return {
|
|
10
|
+
job: useLinkColumn({ headerIcon: 'fa:floppy-disk', headerLabel: () => t('job-name') }),
|
|
11
|
+
lastRun: useStatusColumn({ headerIcon: 'fa:square-caret-down', headerLabel: () => t('last') }),
|
|
12
|
+
secondLastRun: useStatusColumn({ headerIcon: 'fa:square-caret-down', headerLabel: () => t('2nd-last') }),
|
|
13
|
+
thirdLastRun: useStatusColumn({ headerIcon: 'fa:square-caret-down', headerLabel: () => t('3rd-last') }),
|
|
14
|
+
}
|
|
15
|
+
})
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { defineColumns } from '@core/packages/table/define-columns.ts'
|
|
2
|
+
import { useCollapsedListColumn } from '@core/tables/column-definitions/collapsed-list-column.ts'
|
|
3
|
+
import { useLinkColumn } from '@core/tables/column-definitions/link-column'
|
|
4
|
+
import { useNumberColumn } from '@core/tables/column-definitions/number-column.ts'
|
|
5
|
+
import { useSelectItemColumn } from '@core/tables/column-definitions/select-item-column.ts'
|
|
6
|
+
import { useStatusColumn } from '@core/tables/column-definitions/status-column.ts'
|
|
7
|
+
import { useI18n } from 'vue-i18n'
|
|
8
|
+
|
|
9
|
+
export const useBackupJobColumns = defineColumns(() => {
|
|
10
|
+
const { t } = useI18n()
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
job: useLinkColumn({ headerLabel: t('job-name') }),
|
|
14
|
+
mode: useCollapsedListColumn({ headerLabel: t('mode') }),
|
|
15
|
+
lastRuns: useStatusColumn({
|
|
16
|
+
headerLabel: t('last-n-runs', { n: 3 }),
|
|
17
|
+
iconOnly: true,
|
|
18
|
+
progressiveSize: true,
|
|
19
|
+
}),
|
|
20
|
+
schedules: useNumberColumn({ headerLabel: t('total-schedules') }),
|
|
21
|
+
selectItem: useSelectItemColumn(),
|
|
22
|
+
}
|
|
23
|
+
})
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { defineColumns } from '@core/packages/table/define-columns.ts'
|
|
2
|
+
import { useLinkColumn } from '@core/tables/column-definitions/link-column'
|
|
3
|
+
import { useStatusColumn } from '@core/tables/column-definitions/status-column.ts'
|
|
4
|
+
import { useTextColumn } from '@core/tables/column-definitions/text-column.ts'
|
|
5
|
+
import { useI18n } from 'vue-i18n'
|
|
6
|
+
|
|
7
|
+
export const useBackupJobScheduleColumns = defineColumns(() => {
|
|
8
|
+
const { t } = useI18n()
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
schedule: useLinkColumn({ headerLabel: () => t('schedule') }),
|
|
12
|
+
id: useTextColumn({ headerLabel: () => t('id'), headerIcon: 'fa:hashtag' }),
|
|
13
|
+
status: useStatusColumn(),
|
|
14
|
+
cronPattern: useTextColumn({ headerLabel: () => t('cron-pattern'), headerIcon: 'fa:clock' }),
|
|
15
|
+
lastThreeRuns: useStatusColumn({
|
|
16
|
+
headerLabel: () => t('last-n-runs', { n: 3 }),
|
|
17
|
+
iconOnly: true,
|
|
18
|
+
progressiveSize: true,
|
|
19
|
+
}),
|
|
20
|
+
}
|
|
21
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { defineColumns } from '@core/packages/table/define-columns.ts'
|
|
2
|
+
import { useDateColumn } from '@core/tables/column-definitions/date-column.ts'
|
|
3
|
+
import { useNumberColumn } from '@core/tables/column-definitions/number-column.ts'
|
|
4
|
+
import { useSelectItemColumn } from '@core/tables/column-definitions/select-item-column'
|
|
5
|
+
import { useStatusColumn } from '@core/tables/column-definitions/status-column.ts'
|
|
6
|
+
import { useI18n } from 'vue-i18n'
|
|
7
|
+
|
|
8
|
+
export const useBackupLogsColumns = defineColumns(() => {
|
|
9
|
+
const { t } = useI18n()
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
startDate: useDateColumn({ headerLabel: () => t('start-date'), dateStyle: 'short', timeStyle: 'medium' }),
|
|
13
|
+
endDate: useDateColumn({ headerLabel: () => t('end-date'), dateStyle: 'short', timeStyle: 'medium' }),
|
|
14
|
+
duration: useNumberColumn({ headerIcon: 'fa:time', headerLabel: () => t('duration') }),
|
|
15
|
+
status: useStatusColumn(),
|
|
16
|
+
transferSize: useNumberColumn({ headerLabel: () => t('transfer-size') }),
|
|
17
|
+
selectItem: useSelectItemColumn(),
|
|
18
|
+
}
|
|
19
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { defineColumns } from '@core/packages/table/define-columns.ts'
|
|
2
|
+
import { useAddressColumn } from '@core/tables/column-definitions/address-column.ts'
|
|
3
|
+
import { useLinkColumn } from '@core/tables/column-definitions/link-column'
|
|
4
|
+
import { useSelectItemColumn } from '@core/tables/column-definitions/select-item-column'
|
|
5
|
+
import { useTagColumn } from '@core/tables/column-definitions/tag-column.ts'
|
|
6
|
+
import { useTruncatedTextColumn } from '@core/tables/column-definitions/truncated-text-column'
|
|
7
|
+
import { useI18n } from 'vue-i18n'
|
|
8
|
+
|
|
9
|
+
export const useHostColumns = defineColumns(() => {
|
|
10
|
+
const { t } = useI18n()
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
host: useLinkColumn({ headerLabel: () => t('host') }),
|
|
14
|
+
description: useTruncatedTextColumn({ headerLabel: () => t('description') }),
|
|
15
|
+
ipAddresses: useAddressColumn({ headerLabel: () => t('ip-addresses') }),
|
|
16
|
+
tags: useTagColumn({ headerLabel: () => t('tags') }),
|
|
17
|
+
selectItem: useSelectItemColumn(),
|
|
18
|
+
}
|
|
19
|
+
})
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { defineColumns } from '@core/packages/table/define-columns.ts'
|
|
2
|
+
import { useLinkColumn } from '@core/tables/column-definitions/link-column'
|
|
3
|
+
import { useNumberColumn } from '@core/tables/column-definitions/number-column.ts'
|
|
4
|
+
import { useSelectItemColumn } from '@core/tables/column-definitions/select-item-column'
|
|
5
|
+
import { useStatusColumn } from '@core/tables/column-definitions/status-column.ts'
|
|
6
|
+
import { useTextColumn } from '@core/tables/column-definitions/text-column.ts'
|
|
7
|
+
import { useTruncatedTextColumn } from '@core/tables/column-definitions/truncated-text-column'
|
|
8
|
+
import { useI18n } from 'vue-i18n'
|
|
9
|
+
|
|
10
|
+
export const useNetworkColumns = defineColumns(() => {
|
|
11
|
+
const { t } = useI18n()
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
network: useLinkColumn({ headerLabel: () => t('name') }),
|
|
15
|
+
description: useTruncatedTextColumn({ headerLabel: () => t('description') }),
|
|
16
|
+
status: useStatusColumn({ headerLabel: () => t('status') }),
|
|
17
|
+
vlan: useNumberColumn({ headerLabel: () => t('vlan') }),
|
|
18
|
+
mtu: useNumberColumn({ headerLabel: () => t('mtu') }),
|
|
19
|
+
defaultLockingMode: useTextColumn({ headerLabel: () => t('default-locking-mode') }),
|
|
20
|
+
selectItem: useSelectItemColumn(),
|
|
21
|
+
}
|
|
22
|
+
})
|