@rancher/shell 3.0.8-rc.6 → 3.0.8-rc.8
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/assets/brand/suse/favicon.png +0 -0
- package/assets/images/content/README.md +5 -0
- package/assets/images/content/cloud-native.svg +84 -0
- package/assets/images/content/dark/cloud-native.svg +21 -0
- package/assets/images/content/dark/shield.svg +59 -0
- package/assets/images/content/dark/suse.svg +10 -0
- package/assets/images/content/shield.svg +59 -0
- package/assets/images/content/suse.svg +10 -0
- package/assets/styles/themes/_dark.scss +1 -1
- package/assets/styles/themes/_light.scss +1 -1
- package/assets/styles/themes/_modern.scss +2 -2
- package/assets/styles/themes/_suse.scss +4 -3
- package/assets/translations/en-us.yaml +6 -0
- package/components/BannerGraphic.vue +4 -4
- package/components/Drawer/Chrome.vue +2 -6
- package/components/Drawer/ResourceDetailDrawer/ConfigTab.vue +3 -9
- package/components/Drawer/ResourceDetailDrawer/YamlTab.vue +3 -8
- package/components/Drawer/ResourceDetailDrawer/composables.ts +3 -4
- package/components/Drawer/ResourceDetailDrawer/index.vue +3 -9
- package/components/Drawer/ResourceDetailDrawer/types.ts +16 -0
- package/components/Drawer/types.ts +3 -0
- package/components/DynamicContent/DynamicContentBanner.vue +102 -0
- package/components/DynamicContent/DynamicContentCloseButton.vue +42 -0
- package/components/DynamicContent/DynamicContentIcon.vue +132 -0
- package/components/DynamicContent/DynamicContentPanel.vue +112 -0
- package/components/DynamicContent/content.ts +78 -0
- package/components/PaginatedResourceTable.vue +2 -6
- package/components/Resource/Detail/CopyToClipboard.vue +3 -0
- package/components/Resource/Detail/Metadata/KeyValueRow.vue +1 -1
- package/components/Resource/Detail/Metadata/composables.ts +9 -9
- package/components/Resource/Detail/TitleBar/composables.ts +2 -1
- package/components/Resource/Detail/composables.ts +12 -0
- package/components/nav/Header.vue +2 -3
- package/components/nav/NamespaceFilter.vue +13 -1
- package/components/nav/NotificationCenter/index.vue +2 -1
- package/components/nav/TopLevelMenu.helper.ts +16 -6
- package/components/templates/plain.vue +30 -4
- package/core/plugin-helpers.ts +2 -0
- package/edit/auth/__tests__/oidc.test.ts +26 -0
- package/edit/auth/oidc.vue +5 -1
- package/edit/provisioning.cattle.io.cluster/__tests__/Networking.test.ts +24 -1
- package/edit/provisioning.cattle.io.cluster/index.vue +7 -3
- package/edit/provisioning.cattle.io.cluster/rke2.vue +7 -5
- package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +3 -2
- package/edit/service.vue +8 -4
- package/machine-config/amazonec2.vue +7 -1
- package/machine-config/components/EC2Networking.vue +37 -21
- package/machine-config/components/__tests__/EC2Networking.test.ts +54 -0
- package/mixins/__tests__/chart.test.ts +21 -0
- package/mixins/chart.js +7 -1
- package/models/provisioning.cattle.io.cluster.js +5 -1
- package/package.json +1 -1
- package/pages/c/_cluster/manager/hostedprovider/index.vue +5 -0
- package/pages/home.vue +14 -3
- package/pkg/dynamic-importer.lib.js +4 -0
- package/plugins/dashboard-store/resource-class.js +1 -2
- package/plugins/steve/subscribe.js +17 -9
- package/plugins/subscribe-events.ts +4 -2
- package/rancher-components/RcDropdown/RcDropdownItem.vue +1 -0
- package/store/index.js +32 -13
- package/store/notifications.ts +21 -5
- package/store/type-map.js +3 -3
- package/types/notifications/index.ts +3 -0
- package/types/shell/index.d.ts +1 -0
- package/types/store/subscribe-events.types.ts +8 -1
- package/types/store/subscribe.types.ts +1 -0
- package/types/window-manager.ts +2 -0
- package/utils/__tests__/version.test.ts +19 -1
- package/utils/back-off.ts +3 -3
- package/utils/dynamic-content/__tests__/info.test.ts +15 -9
- package/utils/dynamic-content/announcement.ts +71 -41
- package/utils/dynamic-content/info.ts +1 -2
- package/utils/dynamic-content/types.d.ts +21 -1
- package/utils/pagination-wrapper.ts +12 -8
- package/utils/version.js +15 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Common close button for dynamic component banners and panels
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useStore } from 'vuex';
|
|
7
|
+
import { RcButton } from '@components/RcButton';
|
|
8
|
+
|
|
9
|
+
const props = defineProps<{id: string}>();
|
|
10
|
+
const store = useStore();
|
|
11
|
+
|
|
12
|
+
/*
|
|
13
|
+
* Marks the notification as read when the close button is clicked
|
|
14
|
+
*/
|
|
15
|
+
const markRead = () => {
|
|
16
|
+
store.dispatch('notifications/markRead', props.id);
|
|
17
|
+
};
|
|
18
|
+
</script>
|
|
19
|
+
<template>
|
|
20
|
+
<RcButton
|
|
21
|
+
ghost
|
|
22
|
+
small
|
|
23
|
+
:aria-label="t('dynamicContent.action.close')"
|
|
24
|
+
tabindex="0"
|
|
25
|
+
@click="markRead()"
|
|
26
|
+
>
|
|
27
|
+
<i class="dc-close-button icon icon-close" />
|
|
28
|
+
</RcButton>
|
|
29
|
+
</template>
|
|
30
|
+
<style lang="scss" scoped>
|
|
31
|
+
.dc-close-button {
|
|
32
|
+
opacity: 0.5;
|
|
33
|
+
cursor: pointer;
|
|
34
|
+
border: 1px solid transparent;
|
|
35
|
+
padding: 4px;
|
|
36
|
+
border-radius: 4px;
|
|
37
|
+
|
|
38
|
+
&:hover {
|
|
39
|
+
opacity: 1;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
</style>
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Icon component to render an icon from the icon data in an announcement
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useStore } from 'vuex';
|
|
7
|
+
import { computed } from 'vue';
|
|
8
|
+
|
|
9
|
+
import { AnnouncementNotificationIconData } from '@shell/utils/dynamic-content/types';
|
|
10
|
+
|
|
11
|
+
type KeyValues = {
|
|
12
|
+
[key: string]: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const ICON_FORMAT = /(@|#|>)/;
|
|
16
|
+
|
|
17
|
+
const props = defineProps<{icon: AnnouncementNotificationIconData}>();
|
|
18
|
+
const store = useStore();
|
|
19
|
+
const theme = computed(() => store.getters['prefs/theme']);
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get the correct url to use for light/dark mode
|
|
23
|
+
*/
|
|
24
|
+
const url = computed(() => {
|
|
25
|
+
const darkTheme = theme.value === 'dark';
|
|
26
|
+
|
|
27
|
+
return darkTheme ? props.icon?.dark || props.icon?.light : props.icon?.light;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const iconName = computed(() => {
|
|
31
|
+
const decodedIcon = url.value.split(ICON_FORMAT);
|
|
32
|
+
|
|
33
|
+
if (decodedIcon[0].startsWith('!')) {
|
|
34
|
+
return `icon-${ decodedIcon[0].substring(1) }`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return undefined;
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get the src value for an image tag
|
|
42
|
+
*/
|
|
43
|
+
const src = computed(() => {
|
|
44
|
+
const decodedIcon = url.value.split(ICON_FORMAT);
|
|
45
|
+
|
|
46
|
+
// If the icon name starts with ~, then it references a built-in icon/image
|
|
47
|
+
if (decodedIcon[0].startsWith('~')) {
|
|
48
|
+
const img = decodedIcon[0].substring(1);
|
|
49
|
+
const themePrefix = theme.value === 'dark' ? 'dark/' : '';
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
return require(`~shell/assets/images/content/${ themePrefix }${ img }`);
|
|
53
|
+
} catch {
|
|
54
|
+
return require(`~shell/assets/images/content/${ img }`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Regular URL, use it directly
|
|
59
|
+
return decodedIcon[0];
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Icon value can include some custom style information:
|
|
64
|
+
*
|
|
65
|
+
* '@wxh' (or @w) To change the width/height
|
|
66
|
+
* '>x' to set the padding to x px
|
|
67
|
+
* '<x' to set the margin to x px
|
|
68
|
+
* '#rrggbb' to set the color
|
|
69
|
+
*
|
|
70
|
+
*/
|
|
71
|
+
const style = computed(() => {
|
|
72
|
+
const decodedIcon = props.icon.light.split(ICON_FORMAT).slice(1);
|
|
73
|
+
const OPTIONS: { [key: string]: (v: string, result: KeyValues) => void } = {
|
|
74
|
+
'@': (v: string, result: KeyValues) => {
|
|
75
|
+
const wh = v.split('x');
|
|
76
|
+
|
|
77
|
+
result.width = `${ wh[0] }px`;
|
|
78
|
+
result.height = (wh.length === 2) ? `${ wh[1] }px` : `${ wh[0] }px`;
|
|
79
|
+
result.fontSize = result.width;
|
|
80
|
+
},
|
|
81
|
+
'#': (v: string, result: KeyValues) => {
|
|
82
|
+
result.color = `#${ v }`;
|
|
83
|
+
},
|
|
84
|
+
'>': (v: string, result: KeyValues) => {
|
|
85
|
+
result.padding = `${ v }px`;
|
|
86
|
+
},
|
|
87
|
+
'<': (v: string, result: KeyValues) => {
|
|
88
|
+
result.margin = `${ v }px`;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const pairs = Math.floor(decodedIcon.length / 2);
|
|
93
|
+
const result = {};
|
|
94
|
+
|
|
95
|
+
for (let i = 0; i < pairs; i++) {
|
|
96
|
+
const index = 2 * i;
|
|
97
|
+
|
|
98
|
+
if (OPTIONS[decodedIcon[index]]) {
|
|
99
|
+
const handler = OPTIONS[decodedIcon[index]];
|
|
100
|
+
const value = decodedIcon[index + 1];
|
|
101
|
+
|
|
102
|
+
if (handler) {
|
|
103
|
+
handler(value, result);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return result;
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
</script>
|
|
112
|
+
<template>
|
|
113
|
+
<i
|
|
114
|
+
v-if="iconName"
|
|
115
|
+
class="icon"
|
|
116
|
+
:style="style"
|
|
117
|
+
:class="iconName"
|
|
118
|
+
/>
|
|
119
|
+
<img
|
|
120
|
+
v-else
|
|
121
|
+
:style="style"
|
|
122
|
+
:src="src"
|
|
123
|
+
class="dc-icon"
|
|
124
|
+
>
|
|
125
|
+
</template>
|
|
126
|
+
|
|
127
|
+
<style lang="scss" scoped>
|
|
128
|
+
.dc-icon {
|
|
129
|
+
width: 48px;
|
|
130
|
+
height: 48px;
|
|
131
|
+
}
|
|
132
|
+
</style>
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Component top render an announcement as a side panel
|
|
4
|
+
*/
|
|
5
|
+
import DynamicContentIcon from './DynamicContentIcon.vue';
|
|
6
|
+
import DynamicContentCloseButton from './DynamicContentCloseButton.vue';
|
|
7
|
+
import Markdown from '@shell/components/Markdown.vue';
|
|
8
|
+
import { useDynamicContent, DynamicInputProps } from './content';
|
|
9
|
+
|
|
10
|
+
const props = defineProps<DynamicInputProps>();
|
|
11
|
+
const {
|
|
12
|
+
dynamicContent,
|
|
13
|
+
invokeAction,
|
|
14
|
+
primaryButtonStyle,
|
|
15
|
+
} = useDynamicContent(props, 'rhs');
|
|
16
|
+
|
|
17
|
+
</script>
|
|
18
|
+
<template>
|
|
19
|
+
<div
|
|
20
|
+
v-if="dynamicContent"
|
|
21
|
+
:compact="true"
|
|
22
|
+
:can-close="true"
|
|
23
|
+
class="dc-side-panel mt-10"
|
|
24
|
+
>
|
|
25
|
+
<div class="dc-title-block">
|
|
26
|
+
<DynamicContentIcon
|
|
27
|
+
v-if="dynamicContent.data.icon"
|
|
28
|
+
:icon="dynamicContent.data.icon"
|
|
29
|
+
:class="{'mr-10': dynamicContent.data.icon }"
|
|
30
|
+
/>
|
|
31
|
+
<div class="dc-title">
|
|
32
|
+
{{ dynamicContent.title }}
|
|
33
|
+
</div>
|
|
34
|
+
<DynamicContentCloseButton
|
|
35
|
+
:id="dynamicContent.id"
|
|
36
|
+
class="dc-close-button"
|
|
37
|
+
/>
|
|
38
|
+
</div>
|
|
39
|
+
<div class="dc-content">
|
|
40
|
+
<div class="dc-message">
|
|
41
|
+
<Markdown
|
|
42
|
+
v-if="dynamicContent.message"
|
|
43
|
+
v-model:value="dynamicContent.message"
|
|
44
|
+
/>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
<div class="dc-actions">
|
|
48
|
+
<button
|
|
49
|
+
v-if="dynamicContent.primaryAction"
|
|
50
|
+
role="button"
|
|
51
|
+
class="btn btn-sm"
|
|
52
|
+
:aria-label="t('dynamicContent.action.openPrimary')"
|
|
53
|
+
:class="primaryButtonStyle"
|
|
54
|
+
@click.stop.prevent="invokeAction(dynamicContent.primaryAction)"
|
|
55
|
+
>
|
|
56
|
+
{{ dynamicContent.primaryAction.label }}
|
|
57
|
+
</button>
|
|
58
|
+
<button
|
|
59
|
+
v-if="dynamicContent.secondaryAction"
|
|
60
|
+
role="button"
|
|
61
|
+
:aria-label="t('dynamicContent.action.openSecondary')"
|
|
62
|
+
class="btn btn-sm role-secondary"
|
|
63
|
+
@click.stop.prevent="invokeAction(dynamicContent.secondaryAction)"
|
|
64
|
+
>
|
|
65
|
+
{{ dynamicContent.secondaryAction.label }}
|
|
66
|
+
</button>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
</template>
|
|
70
|
+
|
|
71
|
+
<style lang="scss" scoped>
|
|
72
|
+
$dc-padding: 8px;
|
|
73
|
+
|
|
74
|
+
.dc-side-panel {
|
|
75
|
+
border: 1px solid var(--border);
|
|
76
|
+
display: flex;
|
|
77
|
+
flex-direction: column;
|
|
78
|
+
|
|
79
|
+
.dc-title-block {
|
|
80
|
+
display: flex;
|
|
81
|
+
flex: 1;
|
|
82
|
+
align-items: center;
|
|
83
|
+
border-bottom: 1px solid var(--border);
|
|
84
|
+
padding: 0 $dc-padding;
|
|
85
|
+
|
|
86
|
+
.dc-title {
|
|
87
|
+
flex: 1;
|
|
88
|
+
font-weight: bold;
|
|
89
|
+
font-size: 14px;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.dc-content {
|
|
94
|
+
display: flex;
|
|
95
|
+
flex-direction: column;
|
|
96
|
+
flex: 1;
|
|
97
|
+
padding: $dc-padding;
|
|
98
|
+
|
|
99
|
+
.dc-message {
|
|
100
|
+
font-size: 1em;
|
|
101
|
+
line-height: 1.3em;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.dc-actions {
|
|
106
|
+
display: flex;
|
|
107
|
+
justify-content: flex-end;
|
|
108
|
+
padding: $dc-padding;
|
|
109
|
+
gap: 10px;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
</style>
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composable to provide access to an announcement
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { computed, ComputedRef } from 'vue';
|
|
6
|
+
import { useStore } from 'vuex';
|
|
7
|
+
import { Notification, StoredNotification, NotificationAction } from '@shell/types/notifications';
|
|
8
|
+
import { useRouter } from 'vue-router';
|
|
9
|
+
|
|
10
|
+
export type Styles = { [key: string]: string };
|
|
11
|
+
|
|
12
|
+
export interface UseDynamicInput {
|
|
13
|
+
dynamicContent: ComputedRef<Notification | undefined>;
|
|
14
|
+
primaryButtonStyle: ComputedRef<string>;
|
|
15
|
+
styles: ComputedRef<Styles>;
|
|
16
|
+
invokeAction: (action: NotificationAction) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface DynamicInputProps {
|
|
20
|
+
location?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const useDynamicContent = (props: DynamicInputProps, defaultLocation: string): UseDynamicInput => {
|
|
24
|
+
const store = useStore();
|
|
25
|
+
const router = useRouter();
|
|
26
|
+
|
|
27
|
+
// Return the first un-read hidden notification for the given location
|
|
28
|
+
const dynamicContent = computed(() => {
|
|
29
|
+
const location = props.location || defaultLocation;
|
|
30
|
+
const hiddenUnreadNotificationsForLocation: Notification[] = store.getters['notifications/hidden'].filter((n: StoredNotification) => !n.read && n.data?.location === location);
|
|
31
|
+
|
|
32
|
+
return hiddenUnreadNotificationsForLocation.length > 0 ? hiddenUnreadNotificationsForLocation[0] : undefined;
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const styles = computed(() => {
|
|
36
|
+
const parts = dynamicContent?.value?.data?.style?.trim().split(',') || [];
|
|
37
|
+
const res: Styles = {};
|
|
38
|
+
|
|
39
|
+
parts.forEach((part: string) => {
|
|
40
|
+
const kv = part.split(':');
|
|
41
|
+
|
|
42
|
+
if (kv.length === 2) {
|
|
43
|
+
res[kv[0].trim()] = kv[1].trim();
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
return res;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const primaryButtonStyle = computed(() => {
|
|
51
|
+
const buttonStyle = styles.value.btn === 'link' ? 'tertiary' : styles.value.btn || 'primary';
|
|
52
|
+
|
|
53
|
+
return `role-${ buttonStyle }`;
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Invoke action (typically from either the primary or secondary buttons of a notification)
|
|
57
|
+
// This can open a URL in a new tab OR navigate to an application route
|
|
58
|
+
const invokeAction = (action: NotificationAction) => {
|
|
59
|
+
if (action.target) {
|
|
60
|
+
window.open(action.target, '_blank');
|
|
61
|
+
} else if (action.route) {
|
|
62
|
+
try {
|
|
63
|
+
router.push(action.route);
|
|
64
|
+
} catch (e) {
|
|
65
|
+
console.error('Error navigating to route for the notification action', e); // eslint-disable-line no-console
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
console.error('Notification action must either specify a "target" or a "route"'); // eslint-disable-line no-console
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
dynamicContent,
|
|
74
|
+
invokeAction,
|
|
75
|
+
primaryButtonStyle,
|
|
76
|
+
styles
|
|
77
|
+
};
|
|
78
|
+
};
|
|
@@ -116,15 +116,11 @@ export default defineComponent({
|
|
|
116
116
|
},
|
|
117
117
|
|
|
118
118
|
async fetch() {
|
|
119
|
-
const promises = [
|
|
120
|
-
this.$fetchType(this.resource, [], this.overrideInStore || this.inStore),
|
|
121
|
-
];
|
|
122
|
-
|
|
123
119
|
if (this.fetchSecondaryResources) {
|
|
124
|
-
|
|
120
|
+
await this.fetchSecondaryResources({ canPaginate: this.canPaginate });
|
|
125
121
|
}
|
|
126
122
|
|
|
127
|
-
await
|
|
123
|
+
await this.$fetchType(this.resource, [], this.overrideInStore || this.inStore);
|
|
128
124
|
},
|
|
129
125
|
|
|
130
126
|
computed: {
|
|
@@ -6,18 +6,19 @@ import { computed, toValue, Ref } from 'vue';
|
|
|
6
6
|
import {
|
|
7
7
|
useLiveDate, useNamespace, useProject, useResourceDetails, useWorkspace
|
|
8
8
|
} from '@shell/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields';
|
|
9
|
+
import { useOnShowConfiguration } from '@shell/components/Resource/Detail/composables';
|
|
9
10
|
|
|
10
11
|
export const useBasicMetadata = (resource: any) => {
|
|
11
12
|
const labels = useDefaultLabels(resource);
|
|
12
13
|
const annotations = useDefaultAnnotations(resource);
|
|
13
|
-
const
|
|
14
|
+
const onShowConfiguration = useOnShowConfiguration(resource);
|
|
14
15
|
|
|
15
16
|
return computed(() => {
|
|
16
17
|
return {
|
|
17
|
-
resource:
|
|
18
|
-
labels:
|
|
19
|
-
annotations:
|
|
20
|
-
onShowConfiguration
|
|
18
|
+
resource: toValue(resource),
|
|
19
|
+
labels: labels.value,
|
|
20
|
+
annotations: annotations.value,
|
|
21
|
+
onShowConfiguration
|
|
21
22
|
};
|
|
22
23
|
});
|
|
23
24
|
};
|
|
@@ -28,7 +29,7 @@ export const useDefaultMetadataProps = (resource: any, additionalIdentifyingInfo
|
|
|
28
29
|
|
|
29
30
|
const identifyingInformation = computed(() => [...defaultIdentifyingInformation.value, ...(additionalIdentifyingInformationValue || [])]);
|
|
30
31
|
const basicMetaData = useBasicMetadata(resource);
|
|
31
|
-
const
|
|
32
|
+
const onShowConfiguration = useOnShowConfiguration(resource);
|
|
32
33
|
|
|
33
34
|
return computed(() => {
|
|
34
35
|
return {
|
|
@@ -36,7 +37,7 @@ export const useDefaultMetadataProps = (resource: any, additionalIdentifyingInfo
|
|
|
36
37
|
identifyingInformation: identifyingInformation.value,
|
|
37
38
|
labels: basicMetaData.value.labels,
|
|
38
39
|
annotations: basicMetaData.value.annotations,
|
|
39
|
-
onShowConfiguration
|
|
40
|
+
onShowConfiguration
|
|
40
41
|
};
|
|
41
42
|
});
|
|
42
43
|
};
|
|
@@ -47,7 +48,6 @@ export const useDefaultMetadataForLegacyPagesProps = (resource: any) => {
|
|
|
47
48
|
const workspace = useWorkspace(resource);
|
|
48
49
|
const namespace = useNamespace(resource);
|
|
49
50
|
const liveDate = useLiveDate(resource);
|
|
50
|
-
const resourceValue = toValue(resource);
|
|
51
51
|
|
|
52
52
|
const identifyingInformation = computed((): IdentifyingInformationRow[] => {
|
|
53
53
|
const defaultInfo = [
|
|
@@ -71,7 +71,7 @@ export const useDefaultMetadataForLegacyPagesProps = (resource: any) => {
|
|
|
71
71
|
identifyingInformation: identifyingInformation.value,
|
|
72
72
|
labels: basicMetaData.value.labels,
|
|
73
73
|
annotations: basicMetaData.value.annotations,
|
|
74
|
-
onShowConfiguration:
|
|
74
|
+
onShowConfiguration: basicMetaData.value.onShowConfiguration
|
|
75
75
|
};
|
|
76
76
|
});
|
|
77
77
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useOnShowConfiguration } from '@shell/components/Resource/Detail/composables';
|
|
1
2
|
import { TitleBarProps } from '@shell/components/Resource/Detail/TitleBar/index.vue';
|
|
2
3
|
import { computed, Ref, toValue } from 'vue';
|
|
3
4
|
import { useRoute } from 'vue-router';
|
|
@@ -23,7 +24,7 @@ export const useDefaultTitleBarProps = (resource: any, resourceSubtype?: Ref<str
|
|
|
23
24
|
resource: resourceValue.type
|
|
24
25
|
}
|
|
25
26
|
};
|
|
26
|
-
const onShowConfiguration = resourceValue.disableResourceDetailDrawer ? undefined : (
|
|
27
|
+
const onShowConfiguration = resourceValue.disableResourceDetailDrawer ? undefined : useOnShowConfiguration(resource);
|
|
27
28
|
|
|
28
29
|
return {
|
|
29
30
|
resource: resourceValue,
|
|
@@ -2,6 +2,7 @@ import { computed, Ref, toValue } from 'vue';
|
|
|
2
2
|
import { useStore } from 'vuex';
|
|
3
3
|
import { Props as BannerProps } from '@components/Banner/Banner.vue';
|
|
4
4
|
import { useI18n } from '@shell/composables/useI18n';
|
|
5
|
+
import ResourceClass from '@shell/plugins/dashboard-store/resource-class';
|
|
5
6
|
|
|
6
7
|
export const useResourceDetailBannerProps = (resource: any): Ref<BannerProps | undefined> => {
|
|
7
8
|
const store = useStore();
|
|
@@ -43,3 +44,14 @@ export const useResourceDetailBannerProps = (resource: any): Ref<BannerProps | u
|
|
|
43
44
|
return undefined;
|
|
44
45
|
});
|
|
45
46
|
};
|
|
47
|
+
|
|
48
|
+
export const useOnShowConfiguration = (resource: any) => {
|
|
49
|
+
return (returnFocusSelector?: string) => {
|
|
50
|
+
const resourceValue = toValue(resource);
|
|
51
|
+
// Because extensions can make a copy of the resource-class it's possible that an extension will have a resource-class which predates the inclusion of showConfiguration
|
|
52
|
+
// to still the rest of shell to consume
|
|
53
|
+
const showConfiguration = resourceValue.showConfiguration ? resourceValue.showConfiguration.bind(resourceValue) : ResourceClass.prototype.showConfiguration.bind(resourceValue);
|
|
54
|
+
|
|
55
|
+
showConfiguration(returnFocusSelector);
|
|
56
|
+
};
|
|
57
|
+
};
|
|
@@ -97,7 +97,6 @@ export default {
|
|
|
97
97
|
'isSingleProduct',
|
|
98
98
|
'isRancherInHarvester',
|
|
99
99
|
'showTopLevelMenu',
|
|
100
|
-
'isMultiCluster',
|
|
101
100
|
'showWorkspaceSwitcher'
|
|
102
101
|
]),
|
|
103
102
|
|
|
@@ -424,7 +423,7 @@ export default {
|
|
|
424
423
|
data-testid="header"
|
|
425
424
|
>
|
|
426
425
|
<div>
|
|
427
|
-
<TopLevelMenu v-if="
|
|
426
|
+
<TopLevelMenu v-if="showTopLevelMenu" />
|
|
428
427
|
</div>
|
|
429
428
|
|
|
430
429
|
<div
|
|
@@ -495,7 +494,7 @@ export default {
|
|
|
495
494
|
:alt="t('branding.logos.label')"
|
|
496
495
|
/>
|
|
497
496
|
<div
|
|
498
|
-
v-if="!currentCluster"
|
|
497
|
+
v-if="!currentCluster && !$route.path.startsWith('/c/')"
|
|
499
498
|
class="simple-title"
|
|
500
499
|
>
|
|
501
500
|
<BrandImage
|
|
@@ -673,9 +673,16 @@ export default {
|
|
|
673
673
|
},
|
|
674
674
|
|
|
675
675
|
removeOption(ns, event) {
|
|
676
|
-
this.selectOption(ns);
|
|
677
676
|
event.preventDefault();
|
|
678
677
|
event.stopPropagation();
|
|
678
|
+
|
|
679
|
+
this.selectOption(ns);
|
|
680
|
+
|
|
681
|
+
if (event.type !== 'keydown' || this.value.length !== 0) {
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
this.$refs.namespaceFilterInput.focus();
|
|
679
686
|
},
|
|
680
687
|
|
|
681
688
|
defaultOption() {
|
|
@@ -782,6 +789,7 @@ export default {
|
|
|
782
789
|
ghost
|
|
783
790
|
class="ns-chip-button"
|
|
784
791
|
:data-testid="`namespaces-values-close-${j}`"
|
|
792
|
+
:aria-label="t('namespaceFilter.removeNamespace', { name: ns.label })"
|
|
785
793
|
@click="removeOption(ns, $event)"
|
|
786
794
|
@keydown.enter.space.stop="removeOption(ns, $event)"
|
|
787
795
|
@mousedown="handleValueMouseDown(ns, $event)"
|
|
@@ -951,6 +959,10 @@ export default {
|
|
|
951
959
|
display: inline-block;
|
|
952
960
|
border-radius: var(--border-radius);
|
|
953
961
|
|
|
962
|
+
&:focus, &.focused {
|
|
963
|
+
@include focus-outline;
|
|
964
|
+
}
|
|
965
|
+
|
|
954
966
|
.ns-glass {
|
|
955
967
|
top: 0;
|
|
956
968
|
bottom: 0;
|
|
@@ -13,7 +13,8 @@ import {
|
|
|
13
13
|
} from '@components/RcDropdown';
|
|
14
14
|
|
|
15
15
|
const store = useStore();
|
|
16
|
-
|
|
16
|
+
// We don't want any hidden notifications showing in the notification center (these are shown elsewhere, e.g. home page dynamic content announcements)
|
|
17
|
+
const allNotifications = computed(() => store.getters['notifications/visible']);
|
|
17
18
|
const unreadLevelClass = computed(() => {
|
|
18
19
|
return store.getters['notifications/unreadCount'] === 0 ? '' : 'unread';
|
|
19
20
|
});
|
|
@@ -28,6 +28,7 @@ interface UpdateArgs {
|
|
|
28
28
|
searchTerm: string,
|
|
29
29
|
pinnedIds: string[],
|
|
30
30
|
unPinnedMax?: number,
|
|
31
|
+
forceWatch?: boolean
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
type MgmtCluster = {
|
|
@@ -192,9 +193,12 @@ export class TopLevelMenuHelperPagination extends BaseTopLevelMenuHelper impleme
|
|
|
192
193
|
this.clustersOthersWrapper = new PaginationWrapper({
|
|
193
194
|
$store,
|
|
194
195
|
id: 'tlm-unpinned-clusters',
|
|
195
|
-
onChange: async() => {
|
|
196
|
+
onChange: async({ forceWatch }) => {
|
|
196
197
|
if (this.args) {
|
|
197
|
-
await this.update(
|
|
198
|
+
await this.update({
|
|
199
|
+
...this.args,
|
|
200
|
+
forceWatch
|
|
201
|
+
});
|
|
198
202
|
}
|
|
199
203
|
},
|
|
200
204
|
enabledFor: {
|
|
@@ -210,9 +214,12 @@ export class TopLevelMenuHelperPagination extends BaseTopLevelMenuHelper impleme
|
|
|
210
214
|
this.provClusterWrapper = new PaginationWrapper({
|
|
211
215
|
$store,
|
|
212
216
|
id: 'tlm-prov-clusters',
|
|
213
|
-
onChange: async() => {
|
|
217
|
+
onChange: async({ forceWatch }) => {
|
|
214
218
|
if (this.args) {
|
|
215
|
-
await this.update(
|
|
219
|
+
await this.update({
|
|
220
|
+
...this.args,
|
|
221
|
+
forceWatch
|
|
222
|
+
});
|
|
216
223
|
}
|
|
217
224
|
},
|
|
218
225
|
enabledFor: {
|
|
@@ -244,7 +251,7 @@ export class TopLevelMenuHelperPagination extends BaseTopLevelMenuHelper impleme
|
|
|
244
251
|
pinned: MgmtCluster[],
|
|
245
252
|
notPinned: MgmtCluster[]
|
|
246
253
|
} = await allHash(promises) as any;
|
|
247
|
-
const provClusters = await this.updateProvCluster(res.notPinned, res.pinned);
|
|
254
|
+
const provClusters = await this.updateProvCluster(res.notPinned, res.pinned, args.forceWatch || false);
|
|
248
255
|
const provClustersByMgmtId = provClusters.reduce((res: { [mgmtId: string]: ProvCluster}, provCluster: ProvCluster) => {
|
|
249
256
|
if (provCluster.mgmtClusterId) {
|
|
250
257
|
res[provCluster.mgmtClusterId] = provCluster;
|
|
@@ -340,6 +347,7 @@ export class TopLevelMenuHelperPagination extends BaseTopLevelMenuHelper impleme
|
|
|
340
347
|
}
|
|
341
348
|
|
|
342
349
|
return this.clustersPinnedWrapper.request({
|
|
350
|
+
forceWatch: args.forceWatch,
|
|
343
351
|
pagination: {
|
|
344
352
|
filters: this.constructParams({
|
|
345
353
|
pinnedIds: args.pinnedIds,
|
|
@@ -357,6 +365,7 @@ export class TopLevelMenuHelperPagination extends BaseTopLevelMenuHelper impleme
|
|
|
357
365
|
*/
|
|
358
366
|
private async updateOthers(args: UpdateArgs): Promise<MgmtCluster[]> {
|
|
359
367
|
return this.clustersOthersWrapper.request({
|
|
368
|
+
forceWatch: args.forceWatch,
|
|
360
369
|
pagination: {
|
|
361
370
|
filters: this.constructParams({
|
|
362
371
|
searchTerm: args.searchTerm,
|
|
@@ -375,8 +384,9 @@ export class TopLevelMenuHelperPagination extends BaseTopLevelMenuHelper impleme
|
|
|
375
384
|
/**
|
|
376
385
|
* Find all provisioning clusters associated with the displayed mgmt clusters
|
|
377
386
|
*/
|
|
378
|
-
private async updateProvCluster(notPinned: MgmtCluster[], pinned: MgmtCluster[]): Promise<ProvCluster[]> {
|
|
387
|
+
private async updateProvCluster(notPinned: MgmtCluster[], pinned: MgmtCluster[], forceWatch: boolean): Promise<ProvCluster[]> {
|
|
379
388
|
return this.provClusterWrapper.request({
|
|
389
|
+
forceWatch,
|
|
380
390
|
pagination: {
|
|
381
391
|
filters: [
|
|
382
392
|
PaginationParamFilter.createMultipleFields(
|