@rancher/shell 3.0.9-rc.6 → 3.0.10
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/styles/base/_color.scss +4 -0
- package/assets/styles/themes/_light.scss +6 -6
- package/assets/styles/themes/_modern.scss +14 -6
- package/assets/translations/en-us.yaml +2 -5
- package/components/CopyToClipboard.vue +28 -0
- package/components/CopyToClipboardText.vue +4 -0
- package/components/CruResource.vue +1 -0
- package/components/GlobalRoleBindings.vue +1 -5
- package/components/IconOrSvg.vue +61 -42
- package/components/ResourceDetail/index.vue +0 -21
- package/components/SortableTable/index.vue +2 -2
- package/components/__tests__/CruResource.test.ts +35 -1
- package/components/form/BannerSettings.vue +2 -2
- package/components/form/NotificationSettings.vue +2 -2
- package/composables/useIsNewDetailPageEnabled.test.ts +98 -0
- package/composables/useIsNewDetailPageEnabled.ts +12 -0
- package/config/product/explorer.js +11 -1
- package/config/product/manager.js +0 -1
- package/config/table-headers.js +0 -9
- package/config/types.js +0 -1
- package/detail/fleet.cattle.io.cluster.vue +1 -1
- package/dialog/FeatureFlagListDialog.vue +1 -1
- package/edit/auth/github-app-steps.vue +2 -0
- package/edit/auth/github-steps.vue +2 -0
- package/edit/catalog.cattle.io.clusterrepo.vue +1 -1
- package/edit/management.cattle.io.user.vue +60 -35
- package/edit/monitoring.coreos.com.alertmanagerconfig/__tests__/auth.spec.ts +145 -0
- package/edit/monitoring.coreos.com.alertmanagerconfig/__tests__/index.test.ts +202 -0
- package/edit/monitoring.coreos.com.alertmanagerconfig/__tests__/tls.spec.ts +226 -0
- package/edit/monitoring.coreos.com.alertmanagerconfig/auth.vue +24 -21
- package/edit/monitoring.coreos.com.alertmanagerconfig/types/__tests__/opsgenie.spec.ts +157 -0
- package/edit/monitoring.coreos.com.alertmanagerconfig/types/__tests__/pagerduty.spec.ts +132 -0
- package/edit/monitoring.coreos.com.alertmanagerconfig/types/__tests__/slack.spec.ts +108 -0
- package/edit/monitoring.coreos.com.alertmanagerconfig/types/pagerduty.vue +2 -1
- package/edit/monitoring.coreos.com.receiver/__tests__/auth.spec.ts +165 -0
- package/edit/monitoring.coreos.com.receiver/__tests__/index.test.ts +153 -0
- package/edit/monitoring.coreos.com.receiver/__tests__/tls.spec.ts +115 -0
- package/edit/monitoring.coreos.com.receiver/types/__tests__/email.spec.ts +86 -0
- package/edit/monitoring.coreos.com.receiver/types/__tests__/opsgenie.spec.ts +209 -0
- package/edit/monitoring.coreos.com.receiver/types/__tests__/pagerduty.spec.ts +105 -0
- package/edit/monitoring.coreos.com.receiver/types/__tests__/slack.spec.ts +92 -0
- package/edit/monitoring.coreos.com.receiver/types/__tests__/webhook.spec.ts +131 -0
- package/edit/provisioning.cattle.io.cluster/ingress/IngressCards.vue +14 -12
- package/edit/provisioning.cattle.io.cluster/rke2.vue +4 -5
- package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +18 -3
- package/edit/provisioning.cattle.io.cluster/tabs/Ingress.vue +100 -76
- package/edit/token.vue +29 -68
- package/list/provisioning.cattle.io.cluster.vue +2 -2
- package/models/__tests__/chart.test.ts +2 -2
- package/models/chart.js +3 -3
- package/models/token.js +0 -4
- package/package.json +8 -8
- package/pages/account/index.vue +67 -96
- package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +108 -24
- package/pages/c/_cluster/apps/charts/index.vue +1 -11
- package/pages/c/_cluster/explorer/index.vue +2 -19
- package/pages/c/_cluster/explorer/tools/index.vue +1 -1
- package/pages/c/_cluster/manager/cloudCredential/index.vue +1 -1
- package/pages/c/_cluster/uiplugins/index.vue +1 -1
- package/pkg/auto-import.js +41 -0
- package/plugins/dashboard-store/resource-class.js +2 -2
- package/plugins/steve/__tests__/steve-class.test.ts +1 -1
- package/plugins/steve/steve-class.js +3 -3
- package/plugins/steve/steve-pagination-utils.ts +2 -4
- package/rancher-components/Pill/RcCounterBadge/RcCounterBadge.vue +7 -7
- package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +5 -2
- package/rancher-components/RcIcon/types.ts +2 -2
- package/rancher-components/RcItemCard/RcItemCard.vue +8 -1
- package/rancher-components/RcSection/RcSection.test.ts +323 -0
- package/rancher-components/RcSection/RcSection.vue +252 -0
- package/rancher-components/RcSection/RcSectionActions.test.ts +212 -0
- package/rancher-components/RcSection/RcSectionActions.vue +85 -0
- package/rancher-components/RcSection/RcSectionBadges.test.ts +149 -0
- package/rancher-components/RcSection/RcSectionBadges.vue +29 -0
- package/rancher-components/RcSection/index.ts +12 -0
- package/rancher-components/RcSection/types.ts +86 -0
- package/scripts/test-plugins-build.sh +5 -4
- package/types/shell/index.d.ts +93 -108
- package/utils/style.ts +17 -0
- package/utils/svg-filter.js +4 -3
- package/utils/units.js +14 -5
- package/models/ext.cattle.io.token.js +0 -48
package/pages/account/index.vue
CHANGED
|
@@ -9,107 +9,94 @@ import { mapGetters } from 'vuex';
|
|
|
9
9
|
|
|
10
10
|
import { Banner } from '@components/Banner';
|
|
11
11
|
import ResourceTable from '@shell/components/ResourceTable';
|
|
12
|
+
import CopyToClipboardText from '@shell/components/CopyToClipboardText';
|
|
12
13
|
import TabTitle from '@shell/components/TabTitle';
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
import {
|
|
17
|
-
ACCESS_KEY, DESCRIPTION, EXPIRES, EXPIRY_STATE,
|
|
18
|
-
LAST_USED, AGE_NORMAN, SCOPE_NORMAN, NORMAN_KEY_DEPRECATION
|
|
19
|
-
} from '@shell/config/table-headers';
|
|
20
|
-
import { FilterArgs, PaginationParamFilter } from '@shell/types/store/pagination.types';
|
|
15
|
+
const API_ENDPOINT = '/v3';
|
|
21
16
|
|
|
22
17
|
export default {
|
|
23
18
|
components: {
|
|
24
|
-
BackLink, Banner, Loading, ResourceTable, Principal, TabTitle
|
|
19
|
+
CopyToClipboardText, BackLink, Banner, Loading, ResourceTable, Principal, TabTitle
|
|
25
20
|
},
|
|
26
21
|
mixins: [BackRoute],
|
|
27
22
|
async fetch() {
|
|
28
|
-
const hashedRequests = {};
|
|
29
|
-
|
|
30
23
|
this.canChangePassword = await this.calcCanChangePassword();
|
|
31
24
|
|
|
32
|
-
|
|
33
|
-
|
|
25
|
+
if (this.apiKeySchema) {
|
|
26
|
+
this.rows = await this.$store.dispatch('rancher/findAll', { type: NORMAN.TOKEN });
|
|
27
|
+
}
|
|
34
28
|
|
|
35
|
-
|
|
29
|
+
// Get all settings - the API host setting may not be set, so this avoids a 404 request if we look for the specific setting
|
|
30
|
+
const allSettings = await this.$store.dispatch('management/findAll', { type: MANAGEMENT.SETTING });
|
|
31
|
+
const apiHostSetting = allSettings.find((i) => i.id === SETTING.API_HOST);
|
|
32
|
+
const serverUrlSetting = allSettings.find((i) => i.id === SETTING.SERVER_URL);
|
|
36
33
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
34
|
+
this.apiHostSetting = apiHostSetting?.value;
|
|
35
|
+
this.serverUrlSetting = serverUrlSetting?.value;
|
|
40
36
|
|
|
41
|
-
|
|
42
|
-
this.filterByUserTokens = this.$store.getters[`management/paginationEnabled`](EXT.TOKEN);
|
|
43
|
-
|
|
44
|
-
if (this.filterByUserTokens && selfUser?.status?.userID) {
|
|
45
|
-
// Only get associated with the current user
|
|
46
|
-
const opt = { // Of type ActionFindPageArgs
|
|
47
|
-
pagination: new FilterArgs({
|
|
48
|
-
filters: PaginationParamFilter.createSingleField({
|
|
49
|
-
field: 'metadata.fields.1',
|
|
50
|
-
value: selfUser.status?.userID,
|
|
51
|
-
})
|
|
52
|
-
})
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
hashedRequests.steveTokens = this.$store.dispatch(`management/findPage`, { type: EXT.TOKEN, opt });
|
|
56
|
-
} else {
|
|
57
|
-
hashedRequests.steveTokens = this.$store.dispatch('management/findAll', { type: EXT.TOKEN });
|
|
58
|
-
}
|
|
59
|
-
}
|
|
37
|
+
const selfUser = await this.$store.dispatch('auth/getSelfUser');
|
|
60
38
|
|
|
61
39
|
if (selfUser?.canGetUser && selfUser.status?.userID) {
|
|
62
40
|
// Fetch the user info for ChangePassword (ChangePasswordDialog needs the user info for the user whose password is being changed)
|
|
63
|
-
|
|
41
|
+
this.user = await this.$store.dispatch('management/find', {
|
|
64
42
|
type: MANAGEMENT.USER,
|
|
65
43
|
id: selfUser.status?.userID
|
|
66
44
|
});
|
|
45
|
+
} else {
|
|
46
|
+
throw new Error(this.t('changePassword.errors.cannotFetchSelf'));
|
|
67
47
|
}
|
|
68
|
-
|
|
69
|
-
// Get all settings - the API host setting may not be set, so this avoids a 404 request if we look for the specific setting
|
|
70
|
-
hashedRequests.allSettings = this.$store.dispatch('management/findAll', { type: MANAGEMENT.SETTING });
|
|
71
|
-
|
|
72
|
-
const {
|
|
73
|
-
normanTokens, steveTokens, allSettings, user
|
|
74
|
-
} = await allHash(hashedRequests);
|
|
75
|
-
|
|
76
|
-
this.normanTokens = normanTokens;
|
|
77
|
-
this.steveTokens = steveTokens;
|
|
78
|
-
this.user = user;
|
|
79
|
-
|
|
80
|
-
const apiHostSetting = allSettings.find((i) => i.id === SETTING.API_HOST);
|
|
81
|
-
const serverUrlSetting = allSettings.find((i) => i.id === SETTING.SERVER_URL);
|
|
82
|
-
|
|
83
|
-
this.apiHostSetting = apiHostSetting?.value;
|
|
84
|
-
this.serverUrlSetting = serverUrlSetting?.value;
|
|
85
48
|
},
|
|
86
49
|
data() {
|
|
87
50
|
return {
|
|
88
|
-
normanTokenSchema: undefined,
|
|
89
|
-
steveTokenSchema: undefined,
|
|
90
51
|
apiHostSetting: null,
|
|
91
52
|
serverUrlSetting: null,
|
|
92
53
|
rows: null,
|
|
93
54
|
canChangePassword: false,
|
|
94
|
-
user: null
|
|
95
|
-
normanTokens: null,
|
|
96
|
-
steveTokens: null,
|
|
55
|
+
user: null
|
|
97
56
|
};
|
|
98
57
|
},
|
|
99
58
|
computed: {
|
|
100
59
|
...mapGetters({ t: 'i18n/t' }),
|
|
101
60
|
|
|
102
61
|
apiKeyheaders() {
|
|
103
|
-
return [
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
62
|
+
return this.apiKeySchema ? this.$store.getters['type-map/headersFor'](this.apiKeySchema) : [];
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
// Port of Ember code for API Url - see: https://github.com/rancher/ui/blob/8e07c492673171731f3b26af14c978bc103d1828/lib/shared/addon/endpoint/service.js#L58
|
|
66
|
+
apiUrlBase() {
|
|
67
|
+
let setting = this.apiHostSetting;
|
|
68
|
+
|
|
69
|
+
if (setting && setting.indexOf('http') !== 0) {
|
|
70
|
+
setting = `http://${ setting }`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Use Server Setting URL if the api host setting is not set
|
|
74
|
+
let url = setting || this.serverUrlSetting;
|
|
75
|
+
|
|
76
|
+
// If the URL is relative, add on the current base URL from the browser
|
|
77
|
+
if ( url.indexOf('http') !== 0 ) {
|
|
78
|
+
url = `${ window.location.origin }/${ url.replace(/^\/+/, '') }`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// URL must end in a single slash
|
|
82
|
+
url = `${ url.replace(/\/+$/, '') }/`;
|
|
83
|
+
|
|
84
|
+
return url;
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
apiUrl() {
|
|
88
|
+
const base = this.apiUrlBase;
|
|
89
|
+
const path = API_ENDPOINT.replace(/^\/+/, '');
|
|
90
|
+
|
|
91
|
+
return `${ base }${ path }`;
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
apiKeySchema() {
|
|
95
|
+
try {
|
|
96
|
+
return this.$store.getters[`rancher/schemaFor`](NORMAN.TOKEN);
|
|
97
|
+
} catch (e) {}
|
|
98
|
+
|
|
99
|
+
return null;
|
|
113
100
|
},
|
|
114
101
|
|
|
115
102
|
principal() {
|
|
@@ -123,7 +110,7 @@ export default {
|
|
|
123
110
|
return principal || {};
|
|
124
111
|
},
|
|
125
112
|
|
|
126
|
-
|
|
113
|
+
apiKeys() {
|
|
127
114
|
// Filter out tokens that are not API Keys and are not expired UI Sessions
|
|
128
115
|
const isApiKey = (key) => {
|
|
129
116
|
const labels = key.labels;
|
|
@@ -133,24 +120,7 @@ export default {
|
|
|
133
120
|
return ( !expired || !labels || !labels['ui-session'] ) && !current;
|
|
134
121
|
};
|
|
135
122
|
|
|
136
|
-
return !this.
|
|
137
|
-
},
|
|
138
|
-
|
|
139
|
-
filteredNewTokens() {
|
|
140
|
-
// Filter out tokens that are not API Keys and are not expired UI Sessions
|
|
141
|
-
const isApiKey = (key) => {
|
|
142
|
-
const labels = key.metadata?.labels;
|
|
143
|
-
const expired = key.status?.expired;
|
|
144
|
-
const current = key.status?.current;
|
|
145
|
-
|
|
146
|
-
return ( !expired || !labels || !labels['ui-session'] ) && !current;
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
return !this.steveTokens ? [] : this.steveTokens.filter(isApiKey);
|
|
150
|
-
},
|
|
151
|
-
|
|
152
|
-
apiKeys() {
|
|
153
|
-
return (this.filteredNormanTokens || []).concat(this.filteredNewTokens || []);
|
|
123
|
+
return !this.rows ? [] : this.rows.filter(isApiKey);
|
|
154
124
|
}
|
|
155
125
|
},
|
|
156
126
|
|
|
@@ -221,9 +191,16 @@ export default {
|
|
|
221
191
|
<div class="keys-header">
|
|
222
192
|
<div>
|
|
223
193
|
<h2 v-t="'accountAndKeys.apiKeys.title'" />
|
|
194
|
+
<div class="api-url">
|
|
195
|
+
<span>{{ t("accountAndKeys.apiKeys.apiEndpoint") }}</span>
|
|
196
|
+
<CopyToClipboardText
|
|
197
|
+
:aria-label="t('accountAndKeys.apiKeys.copyApiEnpoint')"
|
|
198
|
+
:text="apiUrl"
|
|
199
|
+
/>
|
|
200
|
+
</div>
|
|
224
201
|
</div>
|
|
225
202
|
<button
|
|
226
|
-
v-if="
|
|
203
|
+
v-if="apiKeySchema"
|
|
227
204
|
role="button"
|
|
228
205
|
:aria-label="t('accountAndKeys.apiKeys.add.label')"
|
|
229
206
|
class="btn role-primary add mb-20"
|
|
@@ -234,17 +211,11 @@ export default {
|
|
|
234
211
|
</button>
|
|
235
212
|
</div>
|
|
236
213
|
<div
|
|
237
|
-
v-if="
|
|
214
|
+
v-if="apiKeySchema"
|
|
238
215
|
class="keys"
|
|
239
216
|
>
|
|
240
|
-
<Banner
|
|
241
|
-
v-if="filteredNormanTokens.length"
|
|
242
|
-
color="warning"
|
|
243
|
-
class="mb-20"
|
|
244
|
-
:label="t('accountAndKeys.apiKeys.normanTokenDeprecation')"
|
|
245
|
-
/>
|
|
246
217
|
<ResourceTable
|
|
247
|
-
:schema="
|
|
218
|
+
:schema="apiKeySchema"
|
|
248
219
|
:rows="apiKeys"
|
|
249
220
|
:headers="apiKeyheaders"
|
|
250
221
|
key-field="id"
|
|
@@ -1,26 +1,78 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { reactive } from 'vue';
|
|
2
3
|
import { RcItemCardAction } from '@components/RcItemCard';
|
|
3
4
|
import { RcButton } from '@components/RcButton';
|
|
5
|
+
import { isTruncated } from '@shell/utils/style';
|
|
6
|
+
import RcIcon from '@components/RcIcon/RcIcon.vue';
|
|
7
|
+
import type { RcIconType } from '@components/RcIcon/types';
|
|
4
8
|
|
|
5
9
|
interface FooterItem {
|
|
6
|
-
icon?:
|
|
7
|
-
iconTooltip?:
|
|
10
|
+
icon?: RcIconType;
|
|
11
|
+
iconTooltip?: { key?: string; text?: string };
|
|
8
12
|
labels: string[];
|
|
9
13
|
labelTooltip?: string;
|
|
10
14
|
type?: string;
|
|
11
15
|
}
|
|
12
16
|
|
|
13
|
-
const emit = defineEmits<{(e: 'click:item', type: string, label: string): void
|
|
17
|
+
const emit = defineEmits<{(e: 'click:item', type: string, label: string): void }>();
|
|
14
18
|
|
|
15
19
|
defineProps<{
|
|
16
20
|
items: FooterItem[];
|
|
17
21
|
clickable?: boolean;
|
|
18
22
|
}>();
|
|
19
23
|
|
|
20
|
-
|
|
24
|
+
const tooltipOverrides = reactive<Record<string, string | undefined>>({});
|
|
25
|
+
const labelElements: Record<string, HTMLElement | null> = {};
|
|
26
|
+
|
|
27
|
+
function onClickItem(type: string, label: string): void {
|
|
21
28
|
emit('click:item', type, label);
|
|
22
29
|
}
|
|
23
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Creates a unique key for identifying a label element.
|
|
33
|
+
* @param itemIndex - Index of the footer item
|
|
34
|
+
* @param labelIndex - Index of the label within the footer item
|
|
35
|
+
*/
|
|
36
|
+
function createLabelKey(itemIndex: number, labelIndex: number): string {
|
|
37
|
+
return `${ itemIndex }-${ labelIndex }`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Registers a label element reference for truncation detection.
|
|
42
|
+
* @param key - Unique identifier for the label
|
|
43
|
+
* @param el - The HTML element or null
|
|
44
|
+
*/
|
|
45
|
+
function registerLabelRef(key: string, el: HTMLElement | null): void {
|
|
46
|
+
labelElements[key] = el;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Updates the tooltip content based on whether the label text is truncated.
|
|
51
|
+
* If truncated, prepends the full label text to the base tooltip.
|
|
52
|
+
* @param key - Unique identifier for the label
|
|
53
|
+
* @param label - The label text content
|
|
54
|
+
* @param baseTooltip - The original tooltip content
|
|
55
|
+
*/
|
|
56
|
+
function updateTooltipOnHover(key: string, label: string, baseTooltip?: string): void {
|
|
57
|
+
if (!baseTooltip) {
|
|
58
|
+
tooltipOverrides[key] = undefined;
|
|
59
|
+
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const element = labelElements[key];
|
|
64
|
+
|
|
65
|
+
tooltipOverrides[key] = isTruncated(element) ? `${ label }. ${ baseTooltip }` : baseTooltip;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Returns the tooltip to display for a given label.
|
|
70
|
+
* @param key - Unique identifier for the label
|
|
71
|
+
* @param fallback - Fallback tooltip if no override exists
|
|
72
|
+
*/
|
|
73
|
+
function getTooltip(key: string, fallback?: string): string | undefined {
|
|
74
|
+
return tooltipOverrides[key] ?? fallback;
|
|
75
|
+
}
|
|
24
76
|
</script>
|
|
25
77
|
|
|
26
78
|
<template>
|
|
@@ -31,43 +83,52 @@ function onClickItem(type: string, label: string) {
|
|
|
31
83
|
class="app-chart-card-footer-item"
|
|
32
84
|
data-testid="app-chart-card-footer-item"
|
|
33
85
|
>
|
|
86
|
+
<RcIcon
|
|
87
|
+
v-if="footerItem.icon"
|
|
88
|
+
v-clean-tooltip="footerItem.iconTooltip?.key ? t(footerItem.iconTooltip.key) : undefined"
|
|
89
|
+
class="app-chart-card-footer-item-icon"
|
|
90
|
+
:type="footerItem.icon"
|
|
91
|
+
/>
|
|
34
92
|
<template
|
|
35
93
|
v-for="(label, j) in footerItem.labels"
|
|
36
94
|
:key="j"
|
|
37
95
|
>
|
|
38
96
|
<rc-item-card-action
|
|
39
97
|
v-if="clickable && footerItem.type"
|
|
40
|
-
class="app-chart-card-footer-item-
|
|
98
|
+
class="app-chart-card-footer-item-action"
|
|
41
99
|
>
|
|
42
100
|
<rc-button
|
|
43
|
-
v-clean-tooltip="footerItem.labelTooltip"
|
|
101
|
+
v-clean-tooltip="getTooltip(createLabelKey(i, j), footerItem.labelTooltip)"
|
|
44
102
|
variant="ghost"
|
|
45
103
|
class="app-chart-card-footer-button secondary-text-link"
|
|
46
104
|
data-testid="app-chart-card-footer-item-text"
|
|
47
105
|
:aria-label="t('catalog.charts.appChartCard.footerItem.ariaLabel', { filter: label })"
|
|
48
106
|
@click="onClickItem(footerItem.type, label)"
|
|
107
|
+
@mouseenter="updateTooltipOnHover(createLabelKey(i, j), label, footerItem.labelTooltip)"
|
|
49
108
|
>
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
>
|
|
54
|
-
<i
|
|
55
|
-
v-clean-tooltip="t(footerItem.iconTooltip?.key)"
|
|
56
|
-
:class="['icon', 'app-chart-card-footer-item-icon', footerItem.icon]"
|
|
57
|
-
/>
|
|
58
|
-
</template>
|
|
59
|
-
{{ label }}
|
|
60
|
-
<span v-if="footerItem.labels.length > 1 && j !== footerItem.labels.length - 1">, </span>
|
|
109
|
+
<span
|
|
110
|
+
:ref="(el) => registerLabelRef(createLabelKey(i, j), el as HTMLElement)"
|
|
111
|
+
class="app-chart-card-footer-button-label"
|
|
112
|
+
>{{ label }}</span>
|
|
61
113
|
</rc-button>
|
|
114
|
+
<span
|
|
115
|
+
v-if="footerItem.labels.length > 1 && j !== footerItem.labels.length - 1"
|
|
116
|
+
class="app-chart-card-footer-item-separator"
|
|
117
|
+
>,</span>
|
|
62
118
|
</rc-item-card-action>
|
|
63
119
|
<span
|
|
64
120
|
v-else
|
|
65
|
-
|
|
121
|
+
:ref="(el) => registerLabelRef(createLabelKey(i, j), el as HTMLElement)"
|
|
122
|
+
v-clean-tooltip="getTooltip(createLabelKey(i, j), footerItem.labelTooltip)"
|
|
66
123
|
class="app-chart-card-footer-item-text"
|
|
67
124
|
data-testid="app-chart-card-footer-item-text"
|
|
125
|
+
@mouseenter="updateTooltipOnHover(createLabelKey(i, j), label, footerItem.labelTooltip)"
|
|
68
126
|
>
|
|
69
127
|
{{ label }}
|
|
70
|
-
<span
|
|
128
|
+
<span
|
|
129
|
+
v-if="footerItem.labels.length > 1 && j !== footerItem.labels.length - 1"
|
|
130
|
+
class="app-chart-card-footer-item-separator"
|
|
131
|
+
>,</span>
|
|
71
132
|
</span>
|
|
72
133
|
</template>
|
|
73
134
|
</div>
|
|
@@ -78,6 +139,7 @@ function onClickItem(type: string, label: string) {
|
|
|
78
139
|
.app-chart-card-footer {
|
|
79
140
|
display: flex;
|
|
80
141
|
flex-wrap: wrap;
|
|
142
|
+
max-width: 100%;
|
|
81
143
|
|
|
82
144
|
&-item {
|
|
83
145
|
display: flex;
|
|
@@ -85,18 +147,31 @@ function onClickItem(type: string, label: string) {
|
|
|
85
147
|
color: var(--link-text-secondary);
|
|
86
148
|
margin-top: 8px;
|
|
87
149
|
margin-right: 8px;
|
|
150
|
+
min-width: 0;
|
|
151
|
+
max-width: 100%;
|
|
152
|
+
|
|
153
|
+
&-action {
|
|
154
|
+
display: flex;
|
|
155
|
+
align-items: center;
|
|
156
|
+
min-width: 0; // Critical for truncation in flex containers
|
|
157
|
+
max-width: 100%;
|
|
158
|
+
}
|
|
88
159
|
|
|
89
160
|
&-text {
|
|
90
|
-
|
|
91
|
-
display: -webkit-box;
|
|
92
|
-
-webkit-line-clamp: 1;
|
|
93
|
-
-webkit-box-orient: vertical;
|
|
161
|
+
display: block;
|
|
94
162
|
overflow: hidden;
|
|
95
163
|
text-overflow: ellipsis;
|
|
96
|
-
|
|
164
|
+
white-space: nowrap;
|
|
165
|
+
min-width: 0;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
&-separator {
|
|
169
|
+
flex-shrink: 0;
|
|
170
|
+
margin-right: 4px;
|
|
97
171
|
}
|
|
98
172
|
|
|
99
173
|
&-icon {
|
|
174
|
+
flex-shrink: 0;
|
|
100
175
|
width: 20px;
|
|
101
176
|
height: 20px;
|
|
102
177
|
display: flex;
|
|
@@ -109,6 +184,15 @@ function onClickItem(type: string, label: string) {
|
|
|
109
184
|
|
|
110
185
|
&-button {
|
|
111
186
|
text-transform: capitalize;
|
|
187
|
+
min-width: 0;
|
|
188
|
+
max-width: 100%;
|
|
189
|
+
|
|
190
|
+
&-label {
|
|
191
|
+
display: block;
|
|
192
|
+
overflow: hidden;
|
|
193
|
+
text-overflow: ellipsis;
|
|
194
|
+
white-space: nowrap;
|
|
195
|
+
}
|
|
112
196
|
}
|
|
113
197
|
}
|
|
114
198
|
|
|
@@ -321,7 +321,7 @@ export default {
|
|
|
321
321
|
pill: chart.featured ? { label: { key: 'generic.shortFeatured' }, tooltip: { key: 'generic.featured' } } : undefined,
|
|
322
322
|
header: {
|
|
323
323
|
title: { text: chart.chartNameDisplay },
|
|
324
|
-
statuses:
|
|
324
|
+
statuses: chart.cardContent.statuses
|
|
325
325
|
},
|
|
326
326
|
subHeaderItems: chart.cardContent.subHeaderItems,
|
|
327
327
|
image: { src: chart.latestCompatibleVersion.icon, alt: { text: this.t('catalog.charts.iconAlt', { app: get(chart, 'chartNameDisplay') }) } },
|
|
@@ -553,16 +553,6 @@ export default {
|
|
|
553
553
|
}
|
|
554
554
|
},
|
|
555
555
|
|
|
556
|
-
addStatusesToCharts(chart) {
|
|
557
|
-
if (this.suseAppCollectionRepo.includes(chart.repoName)) {
|
|
558
|
-
return [{
|
|
559
|
-
icon: 'icon-notify-info', color: 'info', tooltip: { key: 'catalog.charts.isFromSuseAppCoRepository' }
|
|
560
|
-
}, ...chart.cardContent.statuses];
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
return chart.cardContent.statuses;
|
|
564
|
-
},
|
|
565
|
-
|
|
566
556
|
async closeSuseAppCollectionBanner() {
|
|
567
557
|
this.showAppCollectionBanner = false;
|
|
568
558
|
await this.$store.dispatch('prefs/set', { key: HIDE_SUSE_APP_COLLECTION_REPO_BANNER, value: true });
|
|
@@ -394,29 +394,12 @@ export default {
|
|
|
394
394
|
},
|
|
395
395
|
|
|
396
396
|
metricAggregations() {
|
|
397
|
-
let checkNodes = this.nodes;
|
|
398
|
-
|
|
399
|
-
// Special case local cluster
|
|
400
|
-
if (this.currentCluster.isLocal) {
|
|
401
|
-
const nodeNames = this.nodes.reduce((acc, n) => {
|
|
402
|
-
acc[n.id] = n;
|
|
403
|
-
|
|
404
|
-
return acc;
|
|
405
|
-
}, {});
|
|
406
|
-
|
|
407
|
-
checkNodes = this.mgmtNodes.filter((n) => {
|
|
408
|
-
const nodeName = n.metadata?.labels?.['management.cattle.io/nodename'] || n.id;
|
|
409
|
-
|
|
410
|
-
return !!nodeNames[nodeName];
|
|
411
|
-
});
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
const someNonWorkerRoles = checkNodes.some((node) => node.hasARole && !node.isWorker);
|
|
415
397
|
const metrics = this.nodeMetrics.filter((nodeMetrics) => {
|
|
416
398
|
const node = this.nodes.find((nd) => nd.id === nodeMetrics.id);
|
|
417
399
|
|
|
418
|
-
return node
|
|
400
|
+
return node;
|
|
419
401
|
});
|
|
402
|
+
|
|
420
403
|
const initialAggregation = {
|
|
421
404
|
cpu: 0,
|
|
422
405
|
memory: 0
|
|
@@ -98,7 +98,7 @@ export default {
|
|
|
98
98
|
},
|
|
99
99
|
subHeaderItems: chart.cardContent.subHeaderItems,
|
|
100
100
|
footerItems: chart.deploysOnWindows ? [{
|
|
101
|
-
icon: '
|
|
101
|
+
icon: 'tag-alt',
|
|
102
102
|
iconTooltip: { key: 'generic.tags' },
|
|
103
103
|
labels: [this.t('catalog.charts.deploysOnWindows')],
|
|
104
104
|
}] : [],
|
package/pkg/auto-import.js
CHANGED
|
@@ -11,6 +11,43 @@ function replaceAll(str, find, replace) {
|
|
|
11
11
|
return str.split(find).join(replace);
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
// Injected at the top of every generated importTypes() function.
|
|
15
|
+
// Ensures both $extension (newer Rancher) and $plugin (older Rancher) are available on
|
|
16
|
+
// Vue globalProperties, regardless of which one the host injected. This makes all
|
|
17
|
+
// extensions compatible across Rancher versions without any per-extension code changes.
|
|
18
|
+
const COMPAT_SHIM = ` if (typeof document !== 'undefined') {
|
|
19
|
+
var patchGlobalProps = function() {
|
|
20
|
+
var __vueApp = document.getElementById('app').__vue_app__;
|
|
21
|
+
|
|
22
|
+
if (!__vueApp) {
|
|
23
|
+
// no __vue_app__, vueApp.mount('#app') has not been called yet
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (__vueApp.config && __vueApp.config.globalProperties) {
|
|
28
|
+
var __gp = __vueApp.config.globalProperties;
|
|
29
|
+
if (!__gp.$extension && __gp.$plugin) { __gp.$extension = __gp.$plugin; }
|
|
30
|
+
else if (!__gp.$plugin && __gp.$extension) { __gp.$plugin = __gp.$extension; }
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Fallback to failure case
|
|
35
|
+
return false;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
if (!patchGlobalProps()) {
|
|
39
|
+
// Could not patch, keep retrying until it works
|
|
40
|
+
var __retry = setInterval(function() {
|
|
41
|
+
if (patchGlobalProps()) {
|
|
42
|
+
clearInterval(__retry);
|
|
43
|
+
}
|
|
44
|
+
}, 100);
|
|
45
|
+
|
|
46
|
+
// Fallback: clear interval after 10 seconds just in case
|
|
47
|
+
setTimeout(function() { clearInterval(__retry); }, 10000);
|
|
48
|
+
}
|
|
49
|
+
}\n`;
|
|
50
|
+
|
|
14
51
|
function registerFile(file, type, pkg, f) {
|
|
15
52
|
const importType = (f === 'models') ? 'require' : 'import';
|
|
16
53
|
const chunkName = (f === 'l10n') ? '' : `/* webpackChunkName: "${ f }" */`;
|
|
@@ -31,6 +68,8 @@ function register(file, pkg, f) {
|
|
|
31
68
|
function generateTypeImport(pkg, dir) {
|
|
32
69
|
let content = 'export function importTypes($extension) { \n';
|
|
33
70
|
|
|
71
|
+
content += COMPAT_SHIM;
|
|
72
|
+
|
|
34
73
|
// Auto-import if the folder exists
|
|
35
74
|
contextFolders.forEach((f) => {
|
|
36
75
|
const filePath = path.join(dir, f);
|
|
@@ -79,6 +118,8 @@ function generateDynamicTypeImport(pkg, dir) {
|
|
|
79
118
|
const template = fs.readFileSync(path.join(__dirname, 'import.js'), { encoding: 'utf8' });
|
|
80
119
|
let content = 'export function importTypes($extension) { \n';
|
|
81
120
|
|
|
121
|
+
content += COMPAT_SHIM;
|
|
122
|
+
|
|
82
123
|
// Auto-import if the folder exists
|
|
83
124
|
contextFolders.forEach((f) => {
|
|
84
125
|
if (fs.existsSync(path.join(dir, f))) {
|
|
@@ -1193,7 +1193,7 @@ export default class Resource {
|
|
|
1193
1193
|
* Allow to handle the response of the save request
|
|
1194
1194
|
* @param {*} res Full request response
|
|
1195
1195
|
*/
|
|
1196
|
-
processSaveResponse(res) { }
|
|
1196
|
+
processSaveResponse(res, opt = {}) { }
|
|
1197
1197
|
|
|
1198
1198
|
async _save(opt = { }) {
|
|
1199
1199
|
const forNew = !this.id;
|
|
@@ -1280,7 +1280,7 @@ export default class Resource {
|
|
|
1280
1280
|
const res = await this.$dispatch('request', { opt, type: this.type } );
|
|
1281
1281
|
|
|
1282
1282
|
// Allow to process response independently from the related models
|
|
1283
|
-
this.processSaveResponse(res);
|
|
1283
|
+
this.processSaveResponse(res, opt);
|
|
1284
1284
|
|
|
1285
1285
|
// Steve sometimes returns Table responses instead of the resource you just saved.. ignore
|
|
1286
1286
|
if ( res && res.kind !== 'Table') {
|
|
@@ -74,7 +74,7 @@ describe('class: Steve', () => {
|
|
|
74
74
|
|
|
75
75
|
steve.processSaveResponse(response);
|
|
76
76
|
|
|
77
|
-
expect(parentProcessSaveResponse).toHaveBeenCalledWith(response);
|
|
77
|
+
expect(parentProcessSaveResponse).toHaveBeenCalledWith(response, {});
|
|
78
78
|
});
|
|
79
79
|
|
|
80
80
|
describe('growl notifications', () => {
|
|
@@ -70,11 +70,11 @@ export default class SteveModel extends HybridModel {
|
|
|
70
70
|
*
|
|
71
71
|
* @param {*} res
|
|
72
72
|
*/
|
|
73
|
-
processSaveResponse(res) {
|
|
74
|
-
super.processSaveResponse(res);
|
|
73
|
+
processSaveResponse(res, opt = {}) {
|
|
74
|
+
super.processSaveResponse(res, opt);
|
|
75
75
|
|
|
76
76
|
// Conditionally show the growl for autogenerated names
|
|
77
|
-
if (res && res._status === 201 && res.metadata?.generateName && res.id) {
|
|
77
|
+
if (res && res._status === 201 && res.metadata?.generateName && res.id && !opt.suppressSuccessToast) {
|
|
78
78
|
// Split to remove the namespace if present (default/generated-xxx)
|
|
79
79
|
const nameOnly = res.id.split('/').pop();
|
|
80
80
|
|
|
@@ -15,8 +15,7 @@ import {
|
|
|
15
15
|
INGRESS,
|
|
16
16
|
WORKLOAD_TYPES,
|
|
17
17
|
HPA,
|
|
18
|
-
SECRET
|
|
19
|
-
EXT
|
|
18
|
+
SECRET
|
|
20
19
|
} from '@shell/config/types';
|
|
21
20
|
import { CAPI as CAPI_LAB_AND_ANO, CATTLE_PUBLIC_ENDPOINTS, STORAGE, UI_PROJECT_SECRET_COPY } from '@shell/config/labels-annotations';
|
|
22
21
|
import { Schema } from '@shell/plugins/steve/schema';
|
|
@@ -768,8 +767,7 @@ export const PAGINATION_SETTINGS_STORE_DEFAULTS: PaginationSettingsStores = {
|
|
|
768
767
|
{ resource: MANAGEMENT.CLUSTER, context: ['side-bar'] },
|
|
769
768
|
{ resource: CATALOG.APP, context: ['branding'] },
|
|
770
769
|
SECRET,
|
|
771
|
-
CAPI.MACHINE_SET
|
|
772
|
-
EXT.TOKEN
|
|
770
|
+
CAPI.MACHINE_SET
|
|
773
771
|
],
|
|
774
772
|
generic: false,
|
|
775
773
|
}
|