@xen-orchestra/web-core 0.49.0 → 0.51.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/delete-button/VtsDeleteButton.vue +27 -0
- package/lib/components/menu/MenuTrigger.vue +1 -0
- package/lib/components/menu/VtsActionsMenu.vue +53 -0
- package/lib/components/modal/VtsErrorModal.vue +36 -0
- package/lib/components/modal/VtsModalList.vue +1 -1
- package/lib/components/status/VtsStatus.vue +4 -0
- package/lib/components/table/cells/VtsActionCell.vue +44 -0
- package/lib/components/table/cells/VtsDoubleLinkCell.vue +53 -0
- package/lib/composables/default-tab.composable.md +17 -11
- package/lib/composables/default-tab.composable.ts +10 -4
- package/lib/icons/fa-icons.ts +2 -0
- package/lib/icons/object-icons.ts +2 -0
- package/lib/locales/cs.json +75 -35
- package/lib/locales/da.json +3 -3
- package/lib/locales/de.json +4 -4
- package/lib/locales/en.json +61 -3
- package/lib/locales/es.json +279 -196
- package/lib/locales/fa.json +4 -4
- package/lib/locales/fi.json +1 -1
- package/lib/locales/fr.json +60 -2
- package/lib/locales/it.json +5 -4
- package/lib/locales/ko.json +3 -1
- package/lib/locales/nb-NO.json +2 -2
- package/lib/locales/nl.json +22 -5
- package/lib/locales/pl.json +2 -2
- package/lib/locales/pt-BR.json +6 -6
- package/lib/locales/pt.json +4 -4
- package/lib/locales/ru.json +60 -13
- package/lib/locales/sk.json +509 -18
- package/lib/locales/sv.json +4 -4
- package/lib/locales/uk.json +1 -1
- package/lib/locales/zh-Hans.json +31 -4
- package/lib/packages/job/define-job.ts +1 -1
- package/lib/packages/table/define-columns.ts +4 -3
- package/lib/tables/column-definitions/action-column.ts +21 -0
- package/lib/tables/column-definitions/button-icon-column.ts +2 -2
- package/lib/tables/column-definitions/double-link-column.ts +14 -0
- package/lib/tables/column-definitions/text-column.ts +1 -1
- package/lib/tables/column-sets/host-columns.ts +1 -1
- package/lib/tables/column-sets/network-columns.ts +3 -1
- package/lib/tables/column-sets/snapshot-columns.ts +2 -2
- package/lib/tables/column-sets/traffic-rules-columns.ts +29 -0
- package/lib/tables/column-sets/vdi-columns.ts +2 -2
- package/lib/tables/column-sets/vif-columns.ts +2 -2
- package/package.json +3 -3
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<UiButton
|
|
3
|
+
v-tooltip="tooltip"
|
|
4
|
+
size="medium"
|
|
5
|
+
variant="tertiary"
|
|
6
|
+
accent="danger"
|
|
7
|
+
left-icon="action:delete"
|
|
8
|
+
:disabled
|
|
9
|
+
:busy
|
|
10
|
+
>
|
|
11
|
+
{{ t('action:delete') }}
|
|
12
|
+
</UiButton>
|
|
13
|
+
</template>
|
|
14
|
+
|
|
15
|
+
<script setup lang="ts">
|
|
16
|
+
import UiButton from '@core/components/ui/button/UiButton.vue'
|
|
17
|
+
import { vTooltip } from '@core/directives/tooltip.directive'
|
|
18
|
+
import { useI18n } from 'vue-i18n'
|
|
19
|
+
|
|
20
|
+
defineProps<{
|
|
21
|
+
busy?: boolean
|
|
22
|
+
disabled?: boolean
|
|
23
|
+
tooltip?: string | false
|
|
24
|
+
}>()
|
|
25
|
+
|
|
26
|
+
const { t } = useI18n()
|
|
27
|
+
</script>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<MenuList placement="bottom-end">
|
|
3
|
+
<template #trigger="{ open }">
|
|
4
|
+
<UiButtonIcon
|
|
5
|
+
v-tooltip="{
|
|
6
|
+
placement: 'top',
|
|
7
|
+
content: t('quick-actions'),
|
|
8
|
+
}"
|
|
9
|
+
icon="action:more-actions"
|
|
10
|
+
accent="brand"
|
|
11
|
+
:size
|
|
12
|
+
@click="open($event)"
|
|
13
|
+
/>
|
|
14
|
+
</template>
|
|
15
|
+
<MenuItem
|
|
16
|
+
v-for="(action, index) of actions"
|
|
17
|
+
:key="index"
|
|
18
|
+
:icon="action.icon"
|
|
19
|
+
:disabled="action.disabled"
|
|
20
|
+
:busy="action.busy"
|
|
21
|
+
:on-click="action.onClick"
|
|
22
|
+
>
|
|
23
|
+
{{ action.label }}
|
|
24
|
+
<i v-if="action.hint">{{ action.hint }}</i>
|
|
25
|
+
</MenuItem>
|
|
26
|
+
</MenuList>
|
|
27
|
+
</template>
|
|
28
|
+
|
|
29
|
+
<script setup lang="ts">
|
|
30
|
+
import MenuItem from '@core/components/menu/MenuItem.vue'
|
|
31
|
+
import MenuList from '@core/components/menu/MenuList.vue'
|
|
32
|
+
import type { ButtonIconSize } from '@core/components/ui/button-icon/UiButtonIcon.vue'
|
|
33
|
+
import UiButtonIcon from '@core/components/ui/button-icon/UiButtonIcon.vue'
|
|
34
|
+
import { vTooltip } from '@core/directives/tooltip.directive.ts'
|
|
35
|
+
import type { IconName } from '@core/icons'
|
|
36
|
+
import { useI18n } from 'vue-i18n'
|
|
37
|
+
|
|
38
|
+
const { size = 'small', actions = [] } = defineProps<{
|
|
39
|
+
size?: ButtonIconSize
|
|
40
|
+
actions?: ActionItem[]
|
|
41
|
+
}>()
|
|
42
|
+
|
|
43
|
+
const { t } = useI18n()
|
|
44
|
+
|
|
45
|
+
export type ActionItem = {
|
|
46
|
+
label: string
|
|
47
|
+
hint?: string
|
|
48
|
+
icon?: IconName
|
|
49
|
+
onClick: () => any
|
|
50
|
+
disabled?: boolean
|
|
51
|
+
busy?: boolean
|
|
52
|
+
}
|
|
53
|
+
</script>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<VtsModal accent="danger" icon="status:danger-picto" dismissible>
|
|
3
|
+
<template #title>
|
|
4
|
+
{{ title }}
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<template #content>
|
|
8
|
+
<slot name="content">
|
|
9
|
+
{{ error }}
|
|
10
|
+
</slot>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<template #buttons>
|
|
14
|
+
<VtsModalConfirmButton>
|
|
15
|
+
{{ t('action:close') }}
|
|
16
|
+
</VtsModalConfirmButton>
|
|
17
|
+
</template>
|
|
18
|
+
</VtsModal>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<script lang="ts" setup>
|
|
22
|
+
import VtsModal from '@core/components/modal/VtsModal.vue'
|
|
23
|
+
import VtsModalConfirmButton from '@core/components/modal/VtsModalConfirmButton.vue'
|
|
24
|
+
import { useI18n } from 'vue-i18n'
|
|
25
|
+
|
|
26
|
+
defineProps<{
|
|
27
|
+
title: string
|
|
28
|
+
error?: string
|
|
29
|
+
}>()
|
|
30
|
+
|
|
31
|
+
defineSlots<{
|
|
32
|
+
content?(): any
|
|
33
|
+
}>()
|
|
34
|
+
|
|
35
|
+
const { t } = useI18n()
|
|
36
|
+
</script>
|
|
@@ -25,6 +25,8 @@ export type Status =
|
|
|
25
25
|
| 'pending'
|
|
26
26
|
| 'enabled'
|
|
27
27
|
| 'disabled'
|
|
28
|
+
| 'allow'
|
|
29
|
+
| 'drop'
|
|
28
30
|
| true
|
|
29
31
|
| false
|
|
30
32
|
|
|
@@ -52,6 +54,8 @@ const currentStatus = useMapper<Status, { text: string; accent: InfoAccent }>(
|
|
|
52
54
|
['pending', { text: t('in-progress'), accent: 'info' }],
|
|
53
55
|
['enabled', { text: t('enabled'), accent: 'success' }],
|
|
54
56
|
['disabled', { text: t('disabled'), accent: 'muted' }],
|
|
57
|
+
['allow', { text: t('allow'), accent: 'success' }],
|
|
58
|
+
['drop', { text: t('drop'), accent: 'danger' }],
|
|
55
59
|
[true, { text: t('enabled'), accent: 'success' }],
|
|
56
60
|
[false, { text: t('disabled'), accent: 'muted' }],
|
|
57
61
|
],
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<UiTableCell align="center">
|
|
3
|
+
<div class="vts-action-cell">
|
|
4
|
+
<UiButtonIcon
|
|
5
|
+
:icon="buttonIcon"
|
|
6
|
+
:accent="buttonAccent"
|
|
7
|
+
:size="buttonSize"
|
|
8
|
+
:target-scale="1.5"
|
|
9
|
+
@click="emit('click')"
|
|
10
|
+
/>
|
|
11
|
+
<VtsActionsMenu v-if="actions.length" :actions />
|
|
12
|
+
</div>
|
|
13
|
+
</UiTableCell>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<script setup lang="ts">
|
|
17
|
+
import VtsActionsMenu, { type ActionItem } from '@core/components/menu/VtsActionsMenu.vue'
|
|
18
|
+
import type { ButtonIconAccent, ButtonIconSize } from '@core/components/ui/button-icon/UiButtonIcon.vue'
|
|
19
|
+
import UiButtonIcon from '@core/components/ui/button-icon/UiButtonIcon.vue'
|
|
20
|
+
import UiTableCell from '@core/components/ui/table-cell/UiTableCell.vue'
|
|
21
|
+
import type { IconName } from '@core/icons'
|
|
22
|
+
|
|
23
|
+
const {
|
|
24
|
+
buttonAccent = 'brand',
|
|
25
|
+
buttonSize = 'small',
|
|
26
|
+
actions = [],
|
|
27
|
+
} = defineProps<{
|
|
28
|
+
buttonIcon: IconName
|
|
29
|
+
buttonAccent?: ButtonIconAccent
|
|
30
|
+
buttonSize?: ButtonIconSize
|
|
31
|
+
actions?: ActionItem[]
|
|
32
|
+
}>()
|
|
33
|
+
|
|
34
|
+
const emit = defineEmits<{
|
|
35
|
+
click: []
|
|
36
|
+
}>()
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<style scoped lang="postcss">
|
|
40
|
+
.vts-action-cell {
|
|
41
|
+
display: flex;
|
|
42
|
+
gap: 0.8rem;
|
|
43
|
+
}
|
|
44
|
+
</style>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<UiTableCell>
|
|
3
|
+
<div class="vts-double-link-cell">
|
|
4
|
+
<UiLink size="medium" :icon :to :href :target class="link">
|
|
5
|
+
<slot />
|
|
6
|
+
</UiLink>
|
|
7
|
+
<UiLink
|
|
8
|
+
v-if="suffix"
|
|
9
|
+
size="medium"
|
|
10
|
+
:icon="suffix.icon"
|
|
11
|
+
:to="suffix.to"
|
|
12
|
+
:href="suffix.href"
|
|
13
|
+
:target="suffix.target"
|
|
14
|
+
class="link"
|
|
15
|
+
>
|
|
16
|
+
{{ suffix.label }}
|
|
17
|
+
</UiLink>
|
|
18
|
+
</div>
|
|
19
|
+
</UiTableCell>
|
|
20
|
+
</template>
|
|
21
|
+
|
|
22
|
+
<script setup lang="ts">
|
|
23
|
+
import UiLink from '@core/components/ui/link/UiLink.vue'
|
|
24
|
+
import UiTableCell from '@core/components/ui/table-cell/UiTableCell.vue'
|
|
25
|
+
import type { LinkOptions } from '@core/composables/link-component.composable.ts'
|
|
26
|
+
import type { IconName } from '@core/icons'
|
|
27
|
+
|
|
28
|
+
export type VtsDoubleLinkCellProps = LinkOptions & {
|
|
29
|
+
icon?: IconName
|
|
30
|
+
suffix?: LinkOptions & {
|
|
31
|
+
label: string
|
|
32
|
+
icon?: IconName
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
defineProps<VtsDoubleLinkCellProps>()
|
|
37
|
+
|
|
38
|
+
defineSlots<{
|
|
39
|
+
default(): any
|
|
40
|
+
}>()
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
<style scoped lang="postcss">
|
|
44
|
+
.vts-double-link-cell {
|
|
45
|
+
display: flex;
|
|
46
|
+
align-items: center;
|
|
47
|
+
gap: 0.8rem;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.link {
|
|
51
|
+
line-height: 1.5;
|
|
52
|
+
}
|
|
53
|
+
</style>
|
|
@@ -7,7 +7,8 @@ This composable manages automatic navigation to default tabs and remembers the l
|
|
|
7
7
|
The composable watches route changes and:
|
|
8
8
|
|
|
9
9
|
- Automatically redirects to the remembered or default tab when navigating to the base dispatcher route
|
|
10
|
-
- Stores the
|
|
10
|
+
- Stores the last visited tab when switching between objects of the same type (e.g., VM → VM)
|
|
11
|
+
- Resets to default tab when navigating to a different object type or any other page
|
|
11
12
|
- Uses the naming convention `{dispatcherRouteName}/{tabName}` for tab route names
|
|
12
13
|
|
|
13
14
|
## Parameters
|
|
@@ -21,22 +22,27 @@ The composable watches route changes and:
|
|
|
21
22
|
|
|
22
23
|
```typescript
|
|
23
24
|
// In the dispatcher page component (e.g., `pages/pool/[id].vue`)
|
|
24
|
-
useDefaultTab('pool/[id]', 'dashboard')
|
|
25
|
+
useDefaultTab('/pool/[id]', 'dashboard')
|
|
25
26
|
|
|
26
|
-
// User
|
|
27
|
+
// User navigates to /pool/123
|
|
27
28
|
// → Automatic redirect to /pool/123/dashboard (default tab)
|
|
28
29
|
|
|
29
|
-
// User
|
|
30
|
-
// → '
|
|
30
|
+
// User navigates to /pool/123/system
|
|
31
|
+
// → 'system' is stored as the last visited tab
|
|
31
32
|
|
|
32
|
-
//
|
|
33
|
-
// → Automatic redirect to /pool/456/
|
|
33
|
+
// User navigates to /pool/456 (same object type)
|
|
34
|
+
// → Automatic redirect to /pool/456/system (tab remembered)
|
|
35
|
+
|
|
36
|
+
// User navigates to /vm/789 (different object type)
|
|
37
|
+
// → Automatic redirect to /vm/789/dashboard (Tab memory is reset)
|
|
38
|
+
|
|
39
|
+
// User navigates back to /pool/456
|
|
40
|
+
// → Automatic redirect to /pool/456/dashboard (default tab)
|
|
34
41
|
```
|
|
35
42
|
|
|
36
43
|
## Storage
|
|
37
44
|
|
|
38
|
-
Tab
|
|
45
|
+
Tab memory is stored in localStorage using two different keys:
|
|
39
46
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
```
|
|
47
|
+
- `tab-memory.dispatcher` — the dispatcher route name of the last visited object type
|
|
48
|
+
- `tab-memory.last` — the last visited tab name
|
|
@@ -1,17 +1,22 @@
|
|
|
1
|
-
import { useLocalStorage } from '@vueuse/core'
|
|
2
1
|
import { watch } from 'vue'
|
|
3
2
|
import { type RouteLocationRaw, type RouteRecordName, useRoute, useRouter } from 'vue-router'
|
|
4
3
|
|
|
4
|
+
const TAB_MEMORY_DISPATCHER = 'tab-memory.dispatcher'
|
|
5
|
+
const TAB_MEMORY_LAST = 'tab-memory.last'
|
|
6
|
+
|
|
5
7
|
export function useDefaultTab(dispatcherRouteName: RouteRecordName & string, defaultTab: string) {
|
|
6
|
-
const storage = useLocalStorage('default-tabs', new Map<string, string>())
|
|
7
8
|
const router = useRouter()
|
|
8
9
|
const route = useRoute()
|
|
9
10
|
|
|
11
|
+
// TODO: Delete after 2 to 3 months (once all users have cleared their local storage)
|
|
12
|
+
localStorage.removeItem('default-tabs')
|
|
13
|
+
|
|
10
14
|
watch(
|
|
11
15
|
() => route.name as string,
|
|
12
16
|
name => {
|
|
13
17
|
if (name === dispatcherRouteName) {
|
|
14
|
-
const
|
|
18
|
+
const isSameDispatcher = localStorage.getItem(TAB_MEMORY_DISPATCHER) === dispatcherRouteName
|
|
19
|
+
const tabName = (isSameDispatcher ? localStorage.getItem(TAB_MEMORY_LAST) : null) ?? defaultTab
|
|
15
20
|
void router.replace({ name: `${dispatcherRouteName}/${tabName}` } as RouteLocationRaw)
|
|
16
21
|
} else if (!name.startsWith(dispatcherRouteName)) {
|
|
17
22
|
return
|
|
@@ -19,7 +24,8 @@ export function useDefaultTab(dispatcherRouteName: RouteRecordName & string, def
|
|
|
19
24
|
|
|
20
25
|
const tab = name.slice(dispatcherRouteName.length).split('/')[1] ?? defaultTab
|
|
21
26
|
|
|
22
|
-
|
|
27
|
+
localStorage.setItem(TAB_MEMORY_DISPATCHER, dispatcherRouteName)
|
|
28
|
+
localStorage.setItem(TAB_MEMORY_LAST, tab)
|
|
23
29
|
},
|
|
24
30
|
{ immediate: true }
|
|
25
31
|
)
|
package/lib/icons/fa-icons.ts
CHANGED
|
@@ -99,6 +99,7 @@ import {
|
|
|
99
99
|
faRoute,
|
|
100
100
|
faSatellite,
|
|
101
101
|
faServer,
|
|
102
|
+
faShieldHalved,
|
|
102
103
|
faSliders,
|
|
103
104
|
faSpinner,
|
|
104
105
|
faSquare,
|
|
@@ -226,6 +227,7 @@ export const faIcons = defineIconPack({
|
|
|
226
227
|
time: { icon: faClock },
|
|
227
228
|
'thumb-tack': { icon: faThumbTack },
|
|
228
229
|
'thumb-tack-slash': { icon: faThumbTackSlash },
|
|
230
|
+
'traffic-rule': { icon: faShieldHalved },
|
|
229
231
|
trash: { icon: faTrash },
|
|
230
232
|
'triangle-exclamation': { icon: faTriangleExclamation },
|
|
231
233
|
'up-right-and-down-left-from-center': { icon: faUpRightAndDownLeftFromCenter },
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
faClock,
|
|
13
13
|
faDatabase,
|
|
14
14
|
faDesktop,
|
|
15
|
+
faEthernet,
|
|
15
16
|
faHdd,
|
|
16
17
|
faNetworkWired,
|
|
17
18
|
faPlay,
|
|
@@ -209,6 +210,7 @@ export const objectIcons = defineIconPack({
|
|
|
209
210
|
'vdi:disabled': [constructIcon(faHdd), ...constructCircleStatus('disabled')],
|
|
210
211
|
'vdi:warning': [constructIcon(faHdd), ...constructCircleStatus('warning-circle')],
|
|
211
212
|
'vdi:detached': [constructIcon(faHdd), ...constructCircleStatus('danger-circle')],
|
|
213
|
+
vif: constructIcon(faEthernet),
|
|
212
214
|
network: constructIcon(faNetworkWired),
|
|
213
215
|
'network:unknown': [
|
|
214
216
|
{
|