@rancher/shell 0.3.1 → 0.3.2
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/global/_gauges.scss +1 -1
- package/assets/styles/global/_layout.scss +4 -0
- package/assets/styles/themes/_dark.scss +1 -0
- package/assets/translations/en-us.yaml +4 -0
- package/assets/translations/zh-hans.yaml +175 -44
- package/components/ActionMenu.vue +28 -7
- package/components/DetailTop.vue +14 -1
- package/components/ExtensionPanel.vue +42 -0
- package/components/IconOrSvg.vue +31 -2
- package/components/ResourceDetail/Masthead.vue +16 -3
- package/components/ResourceList/index.vue +15 -2
- package/components/ResourceTable.vue +3 -1
- package/components/SortableTable/THead.vue +6 -9
- package/components/SortableTable/filtering.js +1 -1
- package/components/SortableTable/selection.js +15 -3
- package/components/Tabbed/Tab.vue +1 -1
- package/components/form/ResourceTabs/index.vue +23 -0
- package/components/nav/Header.vue +69 -5
- package/config/product/backup.js +1 -1
- package/config/query-params.js +1 -0
- package/config/uiplugins.js +3 -3
- package/core/plugin-helpers.js +171 -0
- package/core/plugin.ts +61 -1
- package/core/plugins.js +33 -0
- package/core/types.ts +128 -2
- package/edit/catalog.cattle.io.clusterrepo.vue +3 -0
- package/edit/monitoring.coreos.com.alertmanagerconfig/receiverConfig.vue +14 -2
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +3 -0
- package/edit/provisioning.cattle.io.cluster/rke2.vue +21 -18
- package/package.json +1 -1
- package/pages/c/_cluster/apps/charts/index.vue +17 -0
- package/pages/c/_cluster/explorer/index.vue +39 -0
- package/pages/c/_cluster/uiplugins/DeveloperInstallDialog.vue +2 -0
- package/pages/c/_cluster/uiplugins/InstallDialog.vue +3 -0
- package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +8 -1
- package/pages/c/_cluster/uiplugins/RemoveUIPlugins.vue +2 -0
- package/pages/c/_cluster/uiplugins/SetupUIPlugins.vue +1 -0
- package/pages/c/_cluster/uiplugins/UninstallDialog.vue +2 -0
- package/pages/c/_cluster/uiplugins/index.vue +14 -1
- package/plugins/dashboard-store/resource-class.js +16 -1
- package/rancher-components/components/Banner/Banner.vue +1 -0
- package/store/action-menu.js +4 -3
- package/store/type-map.js +26 -0
- package/types/shell/index.d.ts +1 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { ActionLocation, CardLocation, ExtensionPoint } from '@shell/core/types';
|
|
2
|
+
import { isMac } from '@shell/utils/platform';
|
|
3
|
+
import { ucFirst, randomStr } from '@shell/utils/string';
|
|
4
|
+
import { _EDIT, _CONFIG, _DETAIL, _LIST } from '@shell/config/query-params';
|
|
5
|
+
import { getProductFromRoute } from '@shell/middleware/authenticated';
|
|
6
|
+
|
|
7
|
+
function checkRouteProduct({ name, params, query }, locationConfigParam) {
|
|
8
|
+
const product = getProductFromRoute({
|
|
9
|
+
name, params, query
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
// alias for the homepage
|
|
13
|
+
if (locationConfigParam === 'home' && name === 'home') {
|
|
14
|
+
return true;
|
|
15
|
+
} else if (locationConfigParam === product) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function checkRouteMode({ name, query }, locationConfigParam) {
|
|
23
|
+
if (locationConfigParam === _EDIT && query.mode && query.mode === _EDIT) {
|
|
24
|
+
return true;
|
|
25
|
+
} else if (locationConfigParam === _CONFIG && query.as && query.as === _CONFIG) {
|
|
26
|
+
return true;
|
|
27
|
+
} else if (locationConfigParam === _DETAIL && name.includes('-id')) {
|
|
28
|
+
return true;
|
|
29
|
+
// alias to target all list views
|
|
30
|
+
} else if (locationConfigParam === _LIST && !name.includes('-id') && name.includes('-resource')) {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function checkExtensionRouteBinding($route, locationConfig) {
|
|
38
|
+
// if no configuration is passed, consider it as global
|
|
39
|
+
if (!Object.keys(locationConfig).length) {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const { params } = $route;
|
|
44
|
+
|
|
45
|
+
// "params" to be checked based on the locationConfig
|
|
46
|
+
const paramsToCheck = [
|
|
47
|
+
'product',
|
|
48
|
+
'resource',
|
|
49
|
+
'namespace',
|
|
50
|
+
'cluster',
|
|
51
|
+
'id',
|
|
52
|
+
'mode'
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
let res = false;
|
|
56
|
+
|
|
57
|
+
for (let i = 0; i < paramsToCheck.length; i++) {
|
|
58
|
+
const param = paramsToCheck[i];
|
|
59
|
+
|
|
60
|
+
if (locationConfig[param]) {
|
|
61
|
+
for (let x = 0; x < locationConfig[param].length; x++) {
|
|
62
|
+
const locationConfigParam = locationConfig[param][x];
|
|
63
|
+
|
|
64
|
+
if (locationConfigParam) {
|
|
65
|
+
// handle "product" in a separate way...
|
|
66
|
+
if (param === 'product') {
|
|
67
|
+
res = checkRouteProduct($route, locationConfigParam);
|
|
68
|
+
// also handle "mode" in a separate way because it mainly depends on query params
|
|
69
|
+
} else if (param === 'mode') {
|
|
70
|
+
res = checkRouteMode($route, locationConfigParam);
|
|
71
|
+
} else if (locationConfigParam === params[param]) {
|
|
72
|
+
res = true;
|
|
73
|
+
} else {
|
|
74
|
+
res = false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// If a single location config param is good then this is an param (aka ['pods', 'configmap'] = pods or configmaps)
|
|
79
|
+
if (res) {
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// If a single param (set of location config params) is bad then this is not an acceptable location
|
|
85
|
+
if (!res) {
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return res;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function getApplicableExtensionEnhancements(pluginCtx, actionType, uiArea, currRoute, translationCtx = pluginCtx) {
|
|
95
|
+
const extensionEnhancements = [];
|
|
96
|
+
|
|
97
|
+
const actions = pluginCtx.$plugin.getUIConfig(actionType, uiArea);
|
|
98
|
+
|
|
99
|
+
actions.forEach((action, i) => {
|
|
100
|
+
if (checkExtensionRouteBinding(currRoute, action.locationConfig)) {
|
|
101
|
+
// ADD CARD PLUGIN UI ENHANCEMENT
|
|
102
|
+
if (actionType === ExtensionPoint.CARD) {
|
|
103
|
+
// intercept to apply translation
|
|
104
|
+
if (uiArea === CardLocation.CLUSTER_DASHBOARD_CARD && action.labelKey) {
|
|
105
|
+
actions[i].label = translationCtx.t(action.labelKey);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ADD ACTION PLUGIN UI ENHANCEMENT
|
|
109
|
+
} else if (actionType === ExtensionPoint.ACTION) {
|
|
110
|
+
// TABLE ACTION
|
|
111
|
+
if (uiArea === ActionLocation.TABLE) {
|
|
112
|
+
// intercept to apply translation
|
|
113
|
+
if (action.labelKey) {
|
|
114
|
+
actions[i].label = translationCtx.t(action.labelKey);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// sets the enabled flag to true if omitted on the config
|
|
118
|
+
if (!Object.keys(action).includes('enabled')) {
|
|
119
|
+
actions[i].enabled = true;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// bulkable flag
|
|
123
|
+
actions[i].bulkable = actions[i].multiple || actions[i].bulkable;
|
|
124
|
+
|
|
125
|
+
// populate action identifier to prevent errors
|
|
126
|
+
if (!actions[i].action) {
|
|
127
|
+
actions[i].action = `custom-table-action-${ randomStr(10).toLowerCase() }`;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// extract simplified shortcut definition on plugin - HEADER ACTION
|
|
132
|
+
if (uiArea === ActionLocation.HEADER && action.shortcut) {
|
|
133
|
+
// if it's a string, then assume CTRL for windows and META for mac
|
|
134
|
+
if (typeof action.shortcut === 'string') {
|
|
135
|
+
actions[i].shortcutLabel = () => {
|
|
136
|
+
return isMac ? `(\u2318-${ action.shortcut.toUpperCase() })` : `(Ctrl-${ action.shortcut.toUpperCase() })`;
|
|
137
|
+
};
|
|
138
|
+
actions[i].shortcutKey = { windows: ['ctrl', action.shortcut], mac: ['meta', action.shortcut] };
|
|
139
|
+
// correct check for an Object type in JS... handle the object passed
|
|
140
|
+
} else if (typeof action.shortcut === 'object' && !Array.isArray(action.shortcut) && action.shortcut !== null) {
|
|
141
|
+
actions[i].shortcutKey = action.shortcut;
|
|
142
|
+
const keyboardCombo = isMac ? actions[i].shortcut.mac : actions[i].shortcut.windows ? actions[i].shortcut.windows : [];
|
|
143
|
+
let scLabel = '';
|
|
144
|
+
|
|
145
|
+
keyboardCombo.forEach((key, i) => {
|
|
146
|
+
if (i < keyboardCombo.length - 1) {
|
|
147
|
+
if (key === 'meta') {
|
|
148
|
+
key = '\u2318';
|
|
149
|
+
} else {
|
|
150
|
+
key = ucFirst(key);
|
|
151
|
+
}
|
|
152
|
+
scLabel += `${ key }`;
|
|
153
|
+
scLabel += '-';
|
|
154
|
+
} else {
|
|
155
|
+
scLabel += `${ key.toUpperCase() }`;
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
actions[i].shortcutLabel = () => {
|
|
160
|
+
return `(${ scLabel })`;
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
extensionEnhancements.push(actions[i]);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
return extensionEnhancements;
|
|
171
|
+
}
|
package/core/plugin.ts
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import { RouteConfig } from 'vue-router';
|
|
2
2
|
import { DSL as STORE_DSL } from '@shell/store/type-map';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
CoreStoreInit,
|
|
5
|
+
Action,
|
|
6
|
+
Tab,
|
|
7
|
+
Card,
|
|
8
|
+
Panel,
|
|
9
|
+
TableColumn,
|
|
10
|
+
IPlugin,
|
|
11
|
+
LocationConfig,
|
|
12
|
+
ExtensionPoint,
|
|
13
|
+
} from './types';
|
|
4
14
|
import coreStore, { coreStoreModule, coreStoreState } from '@shell/plugins/dashboard-store';
|
|
5
15
|
import {
|
|
6
16
|
PluginRouteConfig, RegisterStore, UnregisterStore, CoreStoreSpecifics, CoreStoreConfig, OnNavToPackage, OnNavAwayFromPackage, OnLogOut
|
|
@@ -20,6 +30,8 @@ export class Plugin implements IPlugin {
|
|
|
20
30
|
public onLeave: OnNavAwayFromPackage = () => Promise.resolve();
|
|
21
31
|
public _onLogOut: OnLogOut = () => Promise.resolve();
|
|
22
32
|
|
|
33
|
+
public uiConfig: { [key: string]: any } = {};
|
|
34
|
+
|
|
23
35
|
// Plugin metadata (plugin package.json)
|
|
24
36
|
public _metadata: any = {};
|
|
25
37
|
|
|
@@ -34,6 +46,11 @@ export class Plugin implements IPlugin {
|
|
|
34
46
|
constructor(id: string) {
|
|
35
47
|
this.id = id;
|
|
36
48
|
this.name = id;
|
|
49
|
+
|
|
50
|
+
// Initialize uiConfig for all of the possible enum values
|
|
51
|
+
Object.values(ExtensionPoint).forEach((v) => {
|
|
52
|
+
this.uiConfig[v] = {};
|
|
53
|
+
});
|
|
37
54
|
}
|
|
38
55
|
|
|
39
56
|
get metadata() {
|
|
@@ -108,6 +125,49 @@ export class Plugin implements IPlugin {
|
|
|
108
125
|
this.routes.push({ parent, route });
|
|
109
126
|
}
|
|
110
127
|
|
|
128
|
+
private _addUIConfig(type: string, where: string, when: LocationConfig | string, config: any) {
|
|
129
|
+
// For convenience 'when' can be a string to indicate a resource, so convert it to the LocationConfig format
|
|
130
|
+
const locationConfig = (typeof when === 'string') ? { resource: when } : when;
|
|
131
|
+
|
|
132
|
+
this.uiConfig[type][where] = this.uiConfig[type][where] || [];
|
|
133
|
+
this.uiConfig[type][where].push({ ...config, locationConfig });
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Adds an action/button to the UI
|
|
138
|
+
*/
|
|
139
|
+
addAction(where: string, when: LocationConfig | string, action: Action): void {
|
|
140
|
+
this._addUIConfig(ExtensionPoint.ACTION, where, when, action);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Adds a tab to the UI
|
|
145
|
+
*/
|
|
146
|
+
addTab(where: string, when: LocationConfig | string, tab: Tab): void {
|
|
147
|
+
this._addUIConfig(ExtensionPoint.TAB, where, when, tab);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Adds a panel/component to the UI
|
|
152
|
+
*/
|
|
153
|
+
addPanel(where: string, when: LocationConfig | string, panel: Panel): void {
|
|
154
|
+
this._addUIConfig(ExtensionPoint.PANEL, where, when, panel);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Adds a card to the to the UI
|
|
159
|
+
*/
|
|
160
|
+
addCard( where: string, when: LocationConfig | string, card: Card): void {
|
|
161
|
+
this._addUIConfig(ExtensionPoint.CARD, where, when, card);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Adds a new column to a table on the UI
|
|
166
|
+
*/
|
|
167
|
+
addTableColumn(where: string, when: LocationConfig | string, column: TableColumn): void {
|
|
168
|
+
this._addUIConfig(ExtensionPoint.TABLE_COL, where, when, column);
|
|
169
|
+
}
|
|
170
|
+
|
|
111
171
|
setHomePage(component: any) {
|
|
112
172
|
this.addRoute({
|
|
113
173
|
name: 'home',
|
package/core/plugins.js
CHANGED
|
@@ -3,6 +3,7 @@ import { clearModelCache } from '@shell/plugins/dashboard-store/model-loader';
|
|
|
3
3
|
import { Plugin } from './plugin';
|
|
4
4
|
import { PluginRoutes } from './plugin-routes';
|
|
5
5
|
import { UI_PLUGIN_BASE_URL } from '@shell/config/uiplugins';
|
|
6
|
+
import { ExtensionPoint } from './types';
|
|
6
7
|
|
|
7
8
|
const MODEL_TYPE = 'models';
|
|
8
9
|
|
|
@@ -21,6 +22,12 @@ export default function({
|
|
|
21
22
|
|
|
22
23
|
const pluginRoutes = new PluginRoutes(app.router);
|
|
23
24
|
|
|
25
|
+
const uiConfig = {};
|
|
26
|
+
|
|
27
|
+
for (const ep in ExtensionPoint) {
|
|
28
|
+
uiConfig[ExtensionPoint[ep]] = {};
|
|
29
|
+
}
|
|
30
|
+
|
|
24
31
|
inject('plugin', {
|
|
25
32
|
// Plugins should not use these - but we will pass them in for now as a 2nd argument
|
|
26
33
|
// in case there are use cases not covered that require direct access - we may remove access later
|
|
@@ -251,6 +258,18 @@ export default function({
|
|
|
251
258
|
});
|
|
252
259
|
});
|
|
253
260
|
|
|
261
|
+
// UI Configuration - copy UI config from a plugin into the global uiConfig object
|
|
262
|
+
Object.keys(plugin.uiConfig).forEach((actionType) => {
|
|
263
|
+
Object.keys(plugin.uiConfig[actionType]).forEach((actionLocation) => {
|
|
264
|
+
plugin.uiConfig[actionType][actionLocation].forEach((action) => {
|
|
265
|
+
if (!uiConfig[actionType][actionLocation]) {
|
|
266
|
+
uiConfig[actionType][actionLocation] = [];
|
|
267
|
+
}
|
|
268
|
+
uiConfig[actionType][actionLocation].push(action);
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
|
|
254
273
|
// l10n
|
|
255
274
|
Object.keys(plugin.l10n).forEach((name) => {
|
|
256
275
|
plugin.l10n[name].forEach((fn) => {
|
|
@@ -334,6 +353,20 @@ export default function({
|
|
|
334
353
|
return validators[name];
|
|
335
354
|
},
|
|
336
355
|
|
|
356
|
+
/**
|
|
357
|
+
* Return the UI configuration for the given type and location
|
|
358
|
+
*/
|
|
359
|
+
getUIConfig(type, uiArea) {
|
|
360
|
+
return uiConfig[type][uiArea] || [];
|
|
361
|
+
},
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Returns all UI Configuration (useful for debugging)
|
|
365
|
+
*/
|
|
366
|
+
getAllUIConfig() {
|
|
367
|
+
return uiConfig;
|
|
368
|
+
},
|
|
369
|
+
|
|
337
370
|
// Timestamp that a UI package was last loaded
|
|
338
371
|
// Typically used to invalidate caches (e.g. i18n) when new plugins are loaded
|
|
339
372
|
get lastLoad() {
|
package/core/types.ts
CHANGED
|
@@ -31,10 +31,111 @@ export type OnEnterLeavePackageConfig = {
|
|
|
31
31
|
isExt: string,
|
|
32
32
|
oldIsExt: string
|
|
33
33
|
}
|
|
34
|
+
|
|
34
35
|
export type OnNavToPackage = (store: any, config: OnEnterLeavePackageConfig) => Promise<void>;
|
|
35
36
|
export type OnNavAwayFromPackage = (store: any, config: OnEnterLeavePackageConfig) => Promise<void>;
|
|
36
37
|
export type OnLogOut = (store: any) => Promise<void>;
|
|
37
38
|
|
|
39
|
+
/** Enum regarding the extensionable areas/places of the UI */
|
|
40
|
+
export enum ExtensionPoint {
|
|
41
|
+
ACTION = 'Action', // eslint-disable-line no-unused-vars
|
|
42
|
+
TAB = 'Tab', // eslint-disable-line no-unused-vars
|
|
43
|
+
PANEL = 'Panel', // eslint-disable-line no-unused-vars
|
|
44
|
+
CARD = 'Card', // eslint-disable-line no-unused-vars
|
|
45
|
+
TABLE_COL = 'TableColumn', // eslint-disable-line no-unused-vars
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Enum regarding action locations that are extensionable in the UI */
|
|
49
|
+
export enum ActionLocation {
|
|
50
|
+
HEADER = 'header-action', // eslint-disable-line no-unused-vars
|
|
51
|
+
TABLE = 'table-action', // eslint-disable-line no-unused-vars
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Enum regarding panel locations that are extensionable in the UI */
|
|
55
|
+
export enum PanelLocation {
|
|
56
|
+
DETAILS_MASTHEAD = 'details-masthead', // eslint-disable-line no-unused-vars
|
|
57
|
+
DETAIL_TOP = 'detail-top', // eslint-disable-line no-unused-vars
|
|
58
|
+
RESOURCE_LIST = 'resource-list', // eslint-disable-line no-unused-vars
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Enum regarding tab locations that are extensionable in the UI */
|
|
62
|
+
export enum TabLocation {
|
|
63
|
+
RESOURCE_DETAIL = 'tab', // eslint-disable-line no-unused-vars
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Enum regarding card locations that are extensionable in the UI */
|
|
67
|
+
export enum CardLocation {
|
|
68
|
+
CLUSTER_DASHBOARD_CARD = 'cluster-dashboard-card', // eslint-disable-line no-unused-vars
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Enum regarding table col locations that are extensionable in the UI */
|
|
72
|
+
export enum TableColumnLocation {
|
|
73
|
+
RESOURCE = 'resource-list', // eslint-disable-line no-unused-vars
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Definition of the shortcut object (keyboard shortcuts) */
|
|
77
|
+
export type ShortCutKey = {
|
|
78
|
+
windows?: string[];
|
|
79
|
+
mac?: string[];
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/** Definition of the action options (table actions) */
|
|
83
|
+
export type ActionOpts = {
|
|
84
|
+
event: any;
|
|
85
|
+
isAlt: boolean;
|
|
86
|
+
action: any;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/** Definition of an extension action (options that can be passed when setting an extension action) */
|
|
90
|
+
export type Action = {
|
|
91
|
+
label?: string;
|
|
92
|
+
labelKey?: string;
|
|
93
|
+
tooltipKey?: string;
|
|
94
|
+
tooltip?: string;
|
|
95
|
+
shortcut?: string | ShortCutKey;
|
|
96
|
+
svg?: Function;
|
|
97
|
+
icon?: string;
|
|
98
|
+
multiple?: boolean;
|
|
99
|
+
enabled?: (ctx: any) => boolean;
|
|
100
|
+
invoke: (opts: ActionOpts, resources: any[]) => void | boolean | Promise<boolean>;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/** Definition of a panel (options that can be passed when defining an extension panel enhancement) */
|
|
104
|
+
export type Panel = {
|
|
105
|
+
component: Function;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/** Definition of a card (options that can be passed when defining an extension card enhancement) */
|
|
109
|
+
export type Card = {
|
|
110
|
+
label?: string;
|
|
111
|
+
labelKey?: string;
|
|
112
|
+
component: Function;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export type TableColumn = any;
|
|
116
|
+
|
|
117
|
+
/** Definition of a tab (options that can be passed when defining an extension tab enhancement) */
|
|
118
|
+
export type Tab = {
|
|
119
|
+
name: string;
|
|
120
|
+
label?: string;
|
|
121
|
+
labelKey?: string;
|
|
122
|
+
tooltipKey?: string;
|
|
123
|
+
tooltip?: string;
|
|
124
|
+
showHeader?: boolean;
|
|
125
|
+
weight?: number;
|
|
126
|
+
component: Function;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
/** Definition of the locationConfig object (used in extensions) */
|
|
130
|
+
export type LocationConfig = {
|
|
131
|
+
product?: string[],
|
|
132
|
+
resource?: string[],
|
|
133
|
+
namespace?: string[],
|
|
134
|
+
cluster?: string[],
|
|
135
|
+
id?: string[],
|
|
136
|
+
mode?: string[]
|
|
137
|
+
};
|
|
138
|
+
|
|
38
139
|
/**
|
|
39
140
|
* Interface for a Dashboard plugin
|
|
40
141
|
*/
|
|
@@ -63,7 +164,7 @@ export interface IPlugin {
|
|
|
63
164
|
validators: {[key: string]: Function};
|
|
64
165
|
|
|
65
166
|
/**
|
|
66
|
-
* Add a module
|
|
167
|
+
* Add a module containing localisations for a specific locale
|
|
67
168
|
*/
|
|
68
169
|
addL10n(locale: string, fn: Function): void;
|
|
69
170
|
|
|
@@ -73,6 +174,31 @@ export interface IPlugin {
|
|
|
73
174
|
addRoute(route: RouteConfig): void;
|
|
74
175
|
addRoute(parent: string, route: RouteConfig): void;
|
|
75
176
|
|
|
177
|
+
/**
|
|
178
|
+
* Adds an action/button to the UI
|
|
179
|
+
*/
|
|
180
|
+
addAction(where: ActionLocation | string, when: LocationConfig | string, action: Action): void;
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Adds a tab to the UI (ResourceTabs component)
|
|
184
|
+
*/
|
|
185
|
+
addTab(where: TabLocation | string, when: LocationConfig | string, action: Tab): void;
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Adds a panel/component to the UI
|
|
189
|
+
*/
|
|
190
|
+
addPanel(where: PanelLocation | string, when: LocationConfig | string, action: Panel): void;
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Adds a card to the UI
|
|
194
|
+
*/
|
|
195
|
+
addCard(where: CardLocation | string, when: LocationConfig | string, action: Card): void;
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Adds a new column to the SortableTable component
|
|
199
|
+
*/
|
|
200
|
+
addTableColumn(where: TableColumnLocation | string, when: LocationConfig | string, action: TableColumn): void;
|
|
201
|
+
|
|
76
202
|
/**
|
|
77
203
|
* Set the component to use for the landing home page
|
|
78
204
|
* @param component Home page component
|
|
@@ -116,7 +242,7 @@ export interface IPlugin {
|
|
|
116
242
|
onLogOut?: OnLogOut
|
|
117
243
|
): void;
|
|
118
244
|
|
|
119
|
-
|
|
245
|
+
/**
|
|
120
246
|
* Register 'something' that can be dynamically loaded - e.g. model, edit, create, list, i18n
|
|
121
247
|
* @param {String} type type of thing to register, e.g. 'edit'
|
|
122
248
|
* @param {String} name unique name of 'something'
|
|
@@ -59,6 +59,7 @@ export default {
|
|
|
59
59
|
:options="[false, true]"
|
|
60
60
|
:labels="[t('catalog.repo.target.http'), t('catalog.repo.target.git')]"
|
|
61
61
|
:mode="mode"
|
|
62
|
+
data-testid="clusterrepo-radio-input"
|
|
62
63
|
/>
|
|
63
64
|
</div>
|
|
64
65
|
</div>
|
|
@@ -74,6 +75,7 @@ export default {
|
|
|
74
75
|
:label="t('catalog.repo.gitRepo.label')"
|
|
75
76
|
:placeholder="t('catalog.repo.gitRepo.placeholder', null, true)"
|
|
76
77
|
:mode="mode"
|
|
78
|
+
data-testid="clusterrepo-git-repo-input"
|
|
77
79
|
/>
|
|
78
80
|
</div>
|
|
79
81
|
<div class="col span-6">
|
|
@@ -83,6 +85,7 @@ export default {
|
|
|
83
85
|
:label="t('catalog.repo.gitBranch.label')"
|
|
84
86
|
:placeholder="t('catalog.repo.gitBranch.placeholder', null, true)"
|
|
85
87
|
:mode="mode"
|
|
88
|
+
data-testid="clusterrepo-git-branch-input"
|
|
86
89
|
/>
|
|
87
90
|
</div>
|
|
88
91
|
</div>
|
|
@@ -154,7 +154,7 @@ export default {
|
|
|
154
154
|
view: _VIEW,
|
|
155
155
|
yamlError: '',
|
|
156
156
|
fvFormRuleSets: [
|
|
157
|
-
{ path: 'name', rules: ['required'] }
|
|
157
|
+
{ path: 'name', rules: ['required', 'duplicateName'] }
|
|
158
158
|
],
|
|
159
159
|
fvReportedValidationPaths: ['value']
|
|
160
160
|
};
|
|
@@ -183,7 +183,19 @@ export default {
|
|
|
183
183
|
receiverNameDisabled() {
|
|
184
184
|
return this.$route.query.mode === _VIEW;
|
|
185
185
|
},
|
|
186
|
-
|
|
186
|
+
fvExtraRules() {
|
|
187
|
+
return {
|
|
188
|
+
duplicateName: () => {
|
|
189
|
+
const receiversArray = this.alertmanagerConfigResource.spec.receivers;
|
|
190
|
+
const receiverNamesArray = receiversArray.map(R => R.name);
|
|
191
|
+
const receiversSet = new Set(receiverNamesArray);
|
|
192
|
+
|
|
193
|
+
if (receiversArray.length !== receiversSet.size) {
|
|
194
|
+
return this.$store.getters['i18n/t']('monitoring.alerting.validation.duplicatedReceiverName', { name: this.value.name });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
}
|
|
187
199
|
},
|
|
188
200
|
|
|
189
201
|
watch: {
|
|
@@ -6,6 +6,9 @@ describe('component: rke2', () => {
|
|
|
6
6
|
'v1.25.0+rke2r1',
|
|
7
7
|
'v1.24.0+rke2r1',
|
|
8
8
|
'v1.23.0+rke2r1',
|
|
9
|
+
'v1.25.0+k3s1',
|
|
10
|
+
'v1.24.0+k3s1',
|
|
11
|
+
'v1.23.0+k3s1',
|
|
9
12
|
])('should display PSA option', () => {
|
|
10
13
|
const label = 'whatever';
|
|
11
14
|
const option = { label, value: label };
|
|
@@ -314,6 +314,7 @@ export default {
|
|
|
314
314
|
harvesterVersionRange: {},
|
|
315
315
|
lastDefaultPodSecurityPolicyTemplateName,
|
|
316
316
|
previousKubernetesVersion,
|
|
317
|
+
harvesterVersion: ''
|
|
317
318
|
};
|
|
318
319
|
},
|
|
319
320
|
|
|
@@ -356,11 +357,10 @@ export default {
|
|
|
356
357
|
*/
|
|
357
358
|
needsPSA() {
|
|
358
359
|
const release = this.value?.spec?.kubernetesVersion || '';
|
|
359
|
-
const isRKE2 = release.includes('rke2');
|
|
360
360
|
const version = release.match(/\d+/g);
|
|
361
361
|
const isRequiredVersion = version?.length ? +version[0] > 1 || +version[1] >= 23 : false;
|
|
362
362
|
|
|
363
|
-
return
|
|
363
|
+
return isRequiredVersion;
|
|
364
364
|
},
|
|
365
365
|
|
|
366
366
|
/**
|
|
@@ -903,6 +903,7 @@ export default {
|
|
|
903
903
|
},
|
|
904
904
|
|
|
905
905
|
isHarvesterIncompatible() {
|
|
906
|
+
const CompareVersion = '<v1.2';
|
|
906
907
|
let ccmRke2Version = (this.chartVersions['harvester-cloud-provider'] || {})['version'];
|
|
907
908
|
let csiRke2Version = (this.chartVersions['harvester-csi-driver'] || {})['version'];
|
|
908
909
|
|
|
@@ -918,8 +919,12 @@ export default {
|
|
|
918
919
|
}
|
|
919
920
|
|
|
920
921
|
if (ccmVersion && csiVersion) {
|
|
921
|
-
if (semver.satisfies(
|
|
922
|
-
|
|
922
|
+
if (semver.satisfies(this.harvesterVersion, CompareVersion) || !(this.harvesterVersion || '').startsWith('v')) {
|
|
923
|
+
// When harveste version is less than `CompareVersion`, compatibility is not determined,
|
|
924
|
+
// At the same time, version numbers like this will not be checked: master-14bbee2c-head
|
|
925
|
+
return false;
|
|
926
|
+
} else if (semver.satisfies(ccmRke2Version, ccmVersion) &&
|
|
927
|
+
semver.satisfies(csiRke2Version, csiVersion)) {
|
|
923
928
|
return false;
|
|
924
929
|
} else {
|
|
925
930
|
return true;
|
|
@@ -1752,7 +1757,10 @@ export default {
|
|
|
1752
1757
|
const res = await this.$store.dispatch('cluster/request', { url: `${ url }/${ HCI.SETTING }s` });
|
|
1753
1758
|
|
|
1754
1759
|
const version = (res?.data || []).find(s => s.id === 'harvester-csi-ccm-versions');
|
|
1760
|
+
// get harvester server-version
|
|
1761
|
+
const serverVersion = (res?.data || []).find(s => s.id === 'server-version');
|
|
1755
1762
|
|
|
1763
|
+
this.harvesterVersion = serverVersion.value;
|
|
1756
1764
|
if (version) {
|
|
1757
1765
|
this.harvesterVersionRange = JSON.parse(version.value || version.default || '{}');
|
|
1758
1766
|
} else {
|
|
@@ -1791,32 +1799,27 @@ export default {
|
|
|
1791
1799
|
const major = parseInt(version?.[0] || 0);
|
|
1792
1800
|
const minor = parseInt(version?.[1] || 0);
|
|
1793
1801
|
|
|
1794
|
-
//
|
|
1795
|
-
if (
|
|
1802
|
+
// Reset PSA if not RKE2
|
|
1803
|
+
if (!value.includes('rke2')) {
|
|
1796
1804
|
set(this.value.spec, 'defaultPodSecurityPolicyTemplateName', '');
|
|
1797
1805
|
} else {
|
|
1798
|
-
|
|
1799
|
-
const major = parseInt(previous?.[0] || 0);
|
|
1800
|
-
const minor = parseInt(previous?.[1] || 0);
|
|
1801
|
-
|
|
1806
|
+
// Reset PSP if it's legacy due k8s version 1.25+
|
|
1802
1807
|
if (major === 1 && minor >= 25) {
|
|
1803
|
-
|
|
1808
|
+
set(this.value.spec, 'defaultPodSecurityPolicyTemplateName', '');
|
|
1809
|
+
} else {
|
|
1804
1810
|
set(this.value.spec, 'defaultPodSecurityPolicyTemplateName', this.lastDefaultPodSecurityPolicyTemplateName);
|
|
1805
1811
|
}
|
|
1806
|
-
}
|
|
1807
1812
|
|
|
1808
|
-
|
|
1809
|
-
|
|
1813
|
+
this.previousKubernetesVersion = value;
|
|
1814
|
+
}
|
|
1810
1815
|
}
|
|
1811
1816
|
},
|
|
1812
1817
|
|
|
1813
1818
|
/**
|
|
1814
|
-
*
|
|
1819
|
+
* Keep last PSP value
|
|
1815
1820
|
*/
|
|
1816
1821
|
handlePspChange(value) {
|
|
1817
|
-
|
|
1818
|
-
this.lastDefaultPodSecurityPolicyTemplateName = value;
|
|
1819
|
-
}
|
|
1822
|
+
this.lastDefaultPodSecurityPolicyTemplateName = value;
|
|
1820
1823
|
},
|
|
1821
1824
|
|
|
1822
1825
|
},
|
package/package.json
CHANGED
|
@@ -21,6 +21,7 @@ import { CATALOG } from '@shell/config/labels-annotations';
|
|
|
21
21
|
import { isUIPlugin } from '@shell/config/uiplugins';
|
|
22
22
|
|
|
23
23
|
export default {
|
|
24
|
+
name: 'Charts',
|
|
24
25
|
components: {
|
|
25
26
|
AsyncButton,
|
|
26
27
|
Banner,
|
|
@@ -53,6 +54,7 @@ export default {
|
|
|
53
54
|
searchQuery: null,
|
|
54
55
|
showDeprecated: null,
|
|
55
56
|
showHidden: null,
|
|
57
|
+
isPspLegacy: false,
|
|
56
58
|
chartOptions: [
|
|
57
59
|
{
|
|
58
60
|
label: 'Browse',
|
|
@@ -239,6 +241,14 @@ export default {
|
|
|
239
241
|
}
|
|
240
242
|
},
|
|
241
243
|
|
|
244
|
+
created() {
|
|
245
|
+
const release = this.currentCluster?.status?.version.gitVersion || '';
|
|
246
|
+
const isRKE2 = release.includes('rke2');
|
|
247
|
+
const version = release.match(/\d+/g);
|
|
248
|
+
|
|
249
|
+
this.isPspLegacy = version?.length ? isRKE2 && (+version[0] === 1 && +version[1] < 25) : false;
|
|
250
|
+
},
|
|
251
|
+
|
|
242
252
|
methods: {
|
|
243
253
|
colorForChart(chart) {
|
|
244
254
|
const repos = this.repoOptions;
|
|
@@ -363,6 +373,13 @@ export default {
|
|
|
363
373
|
@clicked="(row) => selectChart(row)"
|
|
364
374
|
/>
|
|
365
375
|
</div>
|
|
376
|
+
|
|
377
|
+
<Banner
|
|
378
|
+
v-if="isPspLegacy"
|
|
379
|
+
color="warning"
|
|
380
|
+
:label="t('catalog.chart.banner.legacy')"
|
|
381
|
+
/>
|
|
382
|
+
|
|
366
383
|
<TypeDescription resource="chart" />
|
|
367
384
|
<div class="left-right-split">
|
|
368
385
|
<Select
|