@rancher/shell 0.3.1 → 0.3.3
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 +6 -1
- 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/InputWithSelect.vue +1 -0
- package/components/form/ResourceTabs/index.vue +23 -0
- package/components/nav/Header.vue +69 -5
- package/config/harvester-manager-types.js +1 -0
- 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/fleet.cattle.io.gitrepo.vue +12 -3
- 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/import.vue +1 -1
- package/edit/provisioning.cattle.io.cluster/rke2.vue +21 -18
- package/package.json +2 -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 +17 -3
- 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 +18 -4
- 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/prefs.js +19 -12
- package/store/type-map.js +26 -0
- package/types/shell/index.d.ts +1 -0
|
@@ -3,15 +3,16 @@ import { mapGetters } from 'vuex';
|
|
|
3
3
|
import $ from 'jquery';
|
|
4
4
|
import { AUTO, CENTER, fitOnScreen } from '@shell/utils/position';
|
|
5
5
|
import { isAlternate } from '@shell/utils/platform';
|
|
6
|
+
import IconOrSvg from '@shell/components/IconOrSvg';
|
|
6
7
|
|
|
7
8
|
const HIDDEN = 'hide';
|
|
8
9
|
const CALC = 'calculate';
|
|
9
10
|
const SHOW = 'show';
|
|
10
11
|
|
|
11
12
|
export default {
|
|
12
|
-
name:
|
|
13
|
-
|
|
14
|
-
props:
|
|
13
|
+
name: 'ActionMenu',
|
|
14
|
+
components: { IconOrSvg },
|
|
15
|
+
props: {
|
|
15
16
|
customActions: {
|
|
16
17
|
// Custom actions can be used if you need the action
|
|
17
18
|
// menu to work for something that is not a Kubernetes
|
|
@@ -194,7 +195,23 @@ export default {
|
|
|
194
195
|
return;
|
|
195
196
|
}
|
|
196
197
|
|
|
197
|
-
|
|
198
|
+
// this will come from extensions...
|
|
199
|
+
if (action.invoke) {
|
|
200
|
+
const fn = action.invoke;
|
|
201
|
+
|
|
202
|
+
if (fn && action.enabled) {
|
|
203
|
+
const resources = this.$store.getters['action-menu/resources'];
|
|
204
|
+
const opts = {
|
|
205
|
+
event,
|
|
206
|
+
action,
|
|
207
|
+
isAlt: isAlternate(event)
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
if (resources.length === 1) {
|
|
211
|
+
fn.apply(this, [opts, resources]);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
} else if (this.useCustomTargetElement) {
|
|
198
215
|
// If the state of this component is controlled
|
|
199
216
|
// by props instead of Vuex, we assume you wouldn't want
|
|
200
217
|
// the mutation to have a dependency on Vuex either.
|
|
@@ -247,12 +264,16 @@ export default {
|
|
|
247
264
|
:data-testid="componentTestid + '-' + i + '-item'"
|
|
248
265
|
@click="execute(opt, $event)"
|
|
249
266
|
>
|
|
250
|
-
<
|
|
251
|
-
v-if="opt.icon"
|
|
252
|
-
:
|
|
267
|
+
<IconOrSvg
|
|
268
|
+
v-if="opt.icon || opt.svg"
|
|
269
|
+
:icon="opt.icon"
|
|
270
|
+
:src="opt.svg"
|
|
271
|
+
class="icon"
|
|
272
|
+
color="header"
|
|
253
273
|
/>
|
|
254
274
|
<span v-html="opt.label" />
|
|
255
275
|
</li>
|
|
276
|
+
|
|
256
277
|
<li
|
|
257
278
|
v-if="!hasOptions(menuOptions)"
|
|
258
279
|
class="no-actions"
|
package/components/DetailTop.vue
CHANGED
|
@@ -3,11 +3,15 @@ import Tag from '@shell/components/Tag';
|
|
|
3
3
|
import isEmpty from 'lodash/isEmpty';
|
|
4
4
|
import DetailText from '@shell/components/DetailText';
|
|
5
5
|
import { _VIEW } from '@shell/config/query-params';
|
|
6
|
+
import { ExtensionPoint, PanelLocation } from '@shell/core/types';
|
|
7
|
+
import ExtensionPanel from '@shell/components/ExtensionPanel';
|
|
6
8
|
|
|
7
9
|
export const SEPARATOR = { separator: true };
|
|
8
10
|
|
|
9
11
|
export default {
|
|
10
|
-
components: {
|
|
12
|
+
components: {
|
|
13
|
+
DetailText, Tag, ExtensionPanel
|
|
14
|
+
},
|
|
11
15
|
|
|
12
16
|
props: {
|
|
13
17
|
value: {
|
|
@@ -49,6 +53,8 @@ export default {
|
|
|
49
53
|
|
|
50
54
|
data() {
|
|
51
55
|
return {
|
|
56
|
+
extensionType: ExtensionPoint.PANEL,
|
|
57
|
+
extensionLocation: PanelLocation.DETAIL_TOP,
|
|
52
58
|
annotationsVisible: false,
|
|
53
59
|
showAllLabels: false,
|
|
54
60
|
view: _VIEW
|
|
@@ -287,6 +293,13 @@ export default {
|
|
|
287
293
|
/>
|
|
288
294
|
</div>
|
|
289
295
|
</div>
|
|
296
|
+
|
|
297
|
+
<!-- Extensions area -->
|
|
298
|
+
<ExtensionPanel
|
|
299
|
+
:resource="value"
|
|
300
|
+
:type="extensionType"
|
|
301
|
+
:location="extensionLocation"
|
|
302
|
+
/>
|
|
290
303
|
</div>
|
|
291
304
|
</template>
|
|
292
305
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { getApplicableExtensionEnhancements } from '@shell/core/plugin-helpers';
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
name: 'ExtensionPanel',
|
|
6
|
+
props: {
|
|
7
|
+
resource: {
|
|
8
|
+
type: Object,
|
|
9
|
+
default: () => {
|
|
10
|
+
return {};
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
type: {
|
|
14
|
+
type: String,
|
|
15
|
+
default: ''
|
|
16
|
+
},
|
|
17
|
+
location: {
|
|
18
|
+
type: String,
|
|
19
|
+
default: ''
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
data() {
|
|
23
|
+
return { extensionData: getApplicableExtensionEnhancements(this, this.type, this.location, this.$route) };
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<template>
|
|
29
|
+
<div
|
|
30
|
+
v-if="extensionData.length"
|
|
31
|
+
>
|
|
32
|
+
<div
|
|
33
|
+
v-for="item, i in extensionData"
|
|
34
|
+
:key="`extensionData${location}${i}`"
|
|
35
|
+
>
|
|
36
|
+
<component
|
|
37
|
+
:is="item.component"
|
|
38
|
+
:resource="resource"
|
|
39
|
+
/>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
</template>
|
package/components/IconOrSvg.vue
CHANGED
|
@@ -63,8 +63,34 @@ export default {
|
|
|
63
63
|
|
|
64
64
|
methods: {
|
|
65
65
|
setColor() {
|
|
66
|
-
const
|
|
67
|
-
|
|
66
|
+
const currTheme = this.$store.getters['prefs/theme'];
|
|
67
|
+
let uiColor, hoverColor;
|
|
68
|
+
|
|
69
|
+
// grab css vars values based on the actual stylesheets, depending on the theme applied
|
|
70
|
+
// use for loops to minimize computation
|
|
71
|
+
for (let i = 0; i < Object.keys(document.styleSheets).length; i++) {
|
|
72
|
+
let found = false;
|
|
73
|
+
const stylesheet = document.styleSheets[i];
|
|
74
|
+
|
|
75
|
+
if (stylesheet && stylesheet.cssRules) {
|
|
76
|
+
for (let x = 0; x < Object.keys(stylesheet.cssRules).length; x++) {
|
|
77
|
+
const cssRules = stylesheet.cssRules[x];
|
|
78
|
+
const selectorText = currTheme === 'light' ? 'body, .theme-light' : '.theme-dark';
|
|
79
|
+
|
|
80
|
+
if (cssRules.selectorText && cssRules.selectorText === selectorText) {
|
|
81
|
+
uiColor = mapStandardColors(cssRules.style.getPropertyValue(colors[this.color].color).trim());
|
|
82
|
+
hoverColor = mapStandardColors(cssRules.style.getPropertyValue(colors[this.color].hover).trim());
|
|
83
|
+
found = true;
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (found) {
|
|
89
|
+
break;
|
|
90
|
+
} else {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
68
94
|
|
|
69
95
|
const uiColorRGB = colorToRgb(uiColor);
|
|
70
96
|
const hoverColorRGB = colorToRgb(hoverColor);
|
|
@@ -105,6 +131,9 @@ export default {
|
|
|
105
131
|
button:hover > img.${ className } {
|
|
106
132
|
${ hoverFilter };
|
|
107
133
|
}
|
|
134
|
+
li:hover > img.${ className } {
|
|
135
|
+
${ hoverFilter };
|
|
136
|
+
}
|
|
108
137
|
a.option:hover > img.${ className } {
|
|
109
138
|
${ hoverFilter };
|
|
110
139
|
} `;
|
|
@@ -10,6 +10,8 @@ import { HIDE_SENSITIVE } from '@shell/store/prefs';
|
|
|
10
10
|
import {
|
|
11
11
|
AS, _DETAIL, _CONFIG, _YAML, MODE, _CREATE, _EDIT, _VIEW, _UNFLAG, _GRAPH
|
|
12
12
|
} from '@shell/config/query-params';
|
|
13
|
+
import { ExtensionPoint, PanelLocation } from '@shell/core/types';
|
|
14
|
+
import ExtensionPanel from '@shell/components/ExtensionPanel';
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
17
|
* Resource Detail Masthead component.
|
|
@@ -21,7 +23,7 @@ export default {
|
|
|
21
23
|
name: 'MastheadResourceDetail',
|
|
22
24
|
|
|
23
25
|
components: {
|
|
24
|
-
BadgeState, Banner, ButtonGroup
|
|
26
|
+
BadgeState, Banner, ButtonGroup, ExtensionPanel
|
|
25
27
|
},
|
|
26
28
|
props: {
|
|
27
29
|
value: {
|
|
@@ -83,7 +85,11 @@ export default {
|
|
|
83
85
|
},
|
|
84
86
|
|
|
85
87
|
data() {
|
|
86
|
-
return {
|
|
88
|
+
return {
|
|
89
|
+
DETAIL_VIEW: _DETAIL,
|
|
90
|
+
extensionType: ExtensionPoint.PANEL,
|
|
91
|
+
extensionLocation: PanelLocation.DETAILS_MASTHEAD,
|
|
92
|
+
};
|
|
87
93
|
},
|
|
88
94
|
|
|
89
95
|
computed: {
|
|
@@ -451,7 +457,7 @@ export default {
|
|
|
451
457
|
</div>
|
|
452
458
|
</div>
|
|
453
459
|
<slot name="right">
|
|
454
|
-
<div class="actions-container">
|
|
460
|
+
<div class="actions-container align-start">
|
|
455
461
|
<div class="actions">
|
|
456
462
|
<button
|
|
457
463
|
v-if="detailsAction && currentView === DETAIL_VIEW && isView"
|
|
@@ -493,6 +499,13 @@ export default {
|
|
|
493
499
|
</slot>
|
|
494
500
|
</header>
|
|
495
501
|
|
|
502
|
+
<!-- Extension area -->
|
|
503
|
+
<ExtensionPanel
|
|
504
|
+
:resource="value"
|
|
505
|
+
:type="extensionType"
|
|
506
|
+
:location="extensionLocation"
|
|
507
|
+
/>
|
|
508
|
+
|
|
496
509
|
<Banner
|
|
497
510
|
v-if="banner && isView && !parent.hideBanner"
|
|
498
511
|
class="state-banner mb-10"
|
|
@@ -6,6 +6,8 @@ import ResourceLoadingIndicator from './ResourceLoadingIndicator';
|
|
|
6
6
|
import ResourceFetch from '@shell/mixins/resource-fetch';
|
|
7
7
|
import IconMessage from '@shell/components/IconMessage.vue';
|
|
8
8
|
import { ResourceListComponentName } from './resource-list.config';
|
|
9
|
+
import { PanelLocation, ExtensionPoint } from '@shell/core/types';
|
|
10
|
+
import ExtensionPanel from '@shell/components/ExtensionPanel';
|
|
9
11
|
|
|
10
12
|
export default {
|
|
11
13
|
name: ResourceListComponentName,
|
|
@@ -15,7 +17,8 @@ export default {
|
|
|
15
17
|
ResourceTable,
|
|
16
18
|
Masthead,
|
|
17
19
|
ResourceLoadingIndicator,
|
|
18
|
-
IconMessage
|
|
20
|
+
IconMessage,
|
|
21
|
+
ExtensionPanel
|
|
19
22
|
},
|
|
20
23
|
mixins: [ResourceFetch],
|
|
21
24
|
|
|
@@ -96,6 +99,8 @@ export default {
|
|
|
96
99
|
hasListComponent,
|
|
97
100
|
showMasthead: showMasthead === undefined ? true : showMasthead,
|
|
98
101
|
resource,
|
|
102
|
+
extensionType: ExtensionPoint.PANEL,
|
|
103
|
+
extensionLocation: PanelLocation.RESOURCE_LIST,
|
|
99
104
|
loadResources: [resource], // List of resources that will be loaded, this could be many (`Workloads`)
|
|
100
105
|
hasFetch: false,
|
|
101
106
|
// manual refresh
|
|
@@ -127,7 +132,8 @@ export default {
|
|
|
127
132
|
|
|
128
133
|
showIncrementalLoadingIndicator() {
|
|
129
134
|
return this.perfConfig?.incrementalLoading?.enabled;
|
|
130
|
-
}
|
|
135
|
+
},
|
|
136
|
+
|
|
131
137
|
},
|
|
132
138
|
|
|
133
139
|
watch: {
|
|
@@ -191,6 +197,13 @@ export default {
|
|
|
191
197
|
<slot name="extraActions" />
|
|
192
198
|
</template>
|
|
193
199
|
</Masthead>
|
|
200
|
+
<!-- Extensions area -->
|
|
201
|
+
<ExtensionPanel
|
|
202
|
+
:resource="{}"
|
|
203
|
+
:type="extensionType"
|
|
204
|
+
:location="extensionLocation"
|
|
205
|
+
/>
|
|
206
|
+
|
|
194
207
|
<div v-if="hasListComponent">
|
|
195
208
|
<component
|
|
196
209
|
:is="listComponent"
|
|
@@ -348,7 +348,7 @@ export default {
|
|
|
348
348
|
}
|
|
349
349
|
}
|
|
350
350
|
.table-options-container {
|
|
351
|
-
width:
|
|
351
|
+
width: 350px;
|
|
352
352
|
border: 1px solid var(--primary);
|
|
353
353
|
background-color: var(--body-bg);
|
|
354
354
|
padding: 20px;
|
|
@@ -369,11 +369,10 @@ export default {
|
|
|
369
369
|
list-style: none;
|
|
370
370
|
margin: 0;
|
|
371
371
|
padding: 0;
|
|
372
|
-
|
|
373
|
-
|
|
372
|
+
max-height: 200px;
|
|
373
|
+
overflow-y: auto;
|
|
374
374
|
|
|
375
375
|
li {
|
|
376
|
-
flex: 1 1 136px;
|
|
377
376
|
margin: 0;
|
|
378
377
|
padding: 0;
|
|
379
378
|
|
|
@@ -486,12 +485,10 @@ export default {
|
|
|
486
485
|
}
|
|
487
486
|
</style>
|
|
488
487
|
<style lang="scss">
|
|
488
|
+
.table-options-checkbox .checkbox-custom {
|
|
489
|
+
min-width: 14px;
|
|
490
|
+
}
|
|
489
491
|
.table-options-checkbox .checkbox-label {
|
|
490
492
|
color: var(--body-text);
|
|
491
|
-
text-overflow: ellipsis;
|
|
492
|
-
width: 100px;
|
|
493
|
-
display: inline-block;
|
|
494
|
-
white-space: nowrap;
|
|
495
|
-
overflow: hidden;
|
|
496
493
|
}
|
|
497
494
|
</style>
|
|
@@ -504,7 +504,7 @@ export default {
|
|
|
504
504
|
},
|
|
505
505
|
|
|
506
506
|
applyTableAction(action, args, event) {
|
|
507
|
-
const opts = { alt: event && isAlternate(event) };
|
|
507
|
+
const opts = { alt: event && isAlternate(event), event };
|
|
508
508
|
|
|
509
509
|
// Go through the table selection and filter out those actions that can't run the chosen action
|
|
510
510
|
const executableSelection = this.selectedRows.filter((row) => {
|
|
@@ -513,7 +513,7 @@ export default {
|
|
|
513
513
|
return matchingResourceAction?.enabled;
|
|
514
514
|
});
|
|
515
515
|
|
|
516
|
-
_execute(executableSelection, action, args, opts);
|
|
516
|
+
_execute(executableSelection, action, args, opts, this);
|
|
517
517
|
|
|
518
518
|
this.actionOfInterest = null;
|
|
519
519
|
},
|
|
@@ -575,8 +575,20 @@ function _filter(map, disableAll = false) {
|
|
|
575
575
|
return out;
|
|
576
576
|
}
|
|
577
577
|
|
|
578
|
-
function _execute(resources, action, args, opts = {}) {
|
|
578
|
+
function _execute(resources, action, args, opts = {}, ctx) {
|
|
579
579
|
args = args || [];
|
|
580
|
+
|
|
581
|
+
// New pattern for extensions - always call invoke
|
|
582
|
+
if (action.invoke) {
|
|
583
|
+
const actionOpts = {
|
|
584
|
+
action,
|
|
585
|
+
event: opts.event,
|
|
586
|
+
isAlt: !!opts.alt,
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
return action.invoke.apply(ctx, [actionOpts, resources || [], args]);
|
|
590
|
+
}
|
|
591
|
+
|
|
580
592
|
if ( resources.length > 1 && action.bulkAction && !opts.alt ) {
|
|
581
593
|
const fn = resources[0][action.bulkAction];
|
|
582
594
|
|
|
@@ -4,6 +4,7 @@ import { LabeledInput } from '@components/Form/LabeledInput';
|
|
|
4
4
|
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
|
5
5
|
import Select from '@shell/components/form/Select';
|
|
6
6
|
export default {
|
|
7
|
+
name: 'InputWithSelect',
|
|
7
8
|
components: {
|
|
8
9
|
LabeledInput,
|
|
9
10
|
LabeledSelect,
|
|
@@ -11,6 +11,8 @@ import { EVENT } from '@shell/config/types';
|
|
|
11
11
|
import SortableTable from '@shell/components/SortableTable';
|
|
12
12
|
import { _VIEW } from '@shell/config/query-params';
|
|
13
13
|
import RelatedResources from '@shell/components/RelatedResources';
|
|
14
|
+
import { ExtensionPoint, TabLocation } from '@shell/core/types';
|
|
15
|
+
import { getApplicableExtensionEnhancements } from '@shell/core/plugin-helpers';
|
|
14
16
|
|
|
15
17
|
export default {
|
|
16
18
|
|
|
@@ -69,6 +71,7 @@ export default {
|
|
|
69
71
|
allEvents: [],
|
|
70
72
|
selectedTab: this.defaultTab,
|
|
71
73
|
didLoadEvents: false,
|
|
74
|
+
extensionTabs: getApplicableExtensionEnhancements(this, ExtensionPoint.TAB, TabLocation.RESOURCE_DETAIL, this.$route),
|
|
72
75
|
};
|
|
73
76
|
},
|
|
74
77
|
|
|
@@ -221,5 +224,25 @@ export default {
|
|
|
221
224
|
direction="to"
|
|
222
225
|
/>
|
|
223
226
|
</Tab>
|
|
227
|
+
|
|
228
|
+
<!-- Extension tabs -->
|
|
229
|
+
<Tab
|
|
230
|
+
v-for="tab, i in extensionTabs"
|
|
231
|
+
:key="`${tab.name}${i}`"
|
|
232
|
+
:name="tab.name"
|
|
233
|
+
:label="tab.label"
|
|
234
|
+
:label-key="tab.labelKey"
|
|
235
|
+
:weight="tab.weight"
|
|
236
|
+
:tooltip="tab.tooltip"
|
|
237
|
+
:show-header="tab.showHeader"
|
|
238
|
+
:display-alert-icon="tab.displayAlertIcon"
|
|
239
|
+
:error="tab.error"
|
|
240
|
+
:badge="tab.badge"
|
|
241
|
+
>
|
|
242
|
+
<component
|
|
243
|
+
:is="tab.component"
|
|
244
|
+
:resource="value"
|
|
245
|
+
/>
|
|
246
|
+
</Tab>
|
|
224
247
|
</Tabbed>
|
|
225
248
|
</template>
|
|
@@ -3,7 +3,7 @@ import { mapGetters } from 'vuex';
|
|
|
3
3
|
import debounce from 'lodash/debounce';
|
|
4
4
|
import { NORMAN, STEVE } from '@shell/config/types';
|
|
5
5
|
import { ucFirst } from '@shell/utils/string';
|
|
6
|
-
import { isMac } from '@shell/utils/platform';
|
|
6
|
+
import { isAlternate, isMac } from '@shell/utils/platform';
|
|
7
7
|
import Import from '@shell/components/Import';
|
|
8
8
|
import BrandImage from '@shell/components/BrandImage';
|
|
9
9
|
import { getProduct } from '@shell/config/private-label';
|
|
@@ -15,6 +15,9 @@ import WorkspaceSwitcher from './WorkspaceSwitcher';
|
|
|
15
15
|
import TopLevelMenu from './TopLevelMenu';
|
|
16
16
|
import Jump from './Jump';
|
|
17
17
|
import { allHash } from '@shell/utils/promise';
|
|
18
|
+
import { ActionLocation, ExtensionPoint } from '@shell/core/types';
|
|
19
|
+
import { getApplicableExtensionEnhancements } from '@shell/core/plugin-helpers';
|
|
20
|
+
import IconOrSvg from '@shell/components/IconOrSvg';
|
|
18
21
|
|
|
19
22
|
const PAGE_HEADER_ACTION = 'page-action';
|
|
20
23
|
|
|
@@ -29,6 +32,7 @@ export default {
|
|
|
29
32
|
BrandImage,
|
|
30
33
|
ClusterBadge,
|
|
31
34
|
ClusterProviderIcon,
|
|
35
|
+
IconOrSvg
|
|
32
36
|
},
|
|
33
37
|
|
|
34
38
|
props: {
|
|
@@ -43,13 +47,15 @@ export default {
|
|
|
43
47
|
const shellShortcut = '(Ctrl+`)';
|
|
44
48
|
|
|
45
49
|
return {
|
|
46
|
-
show:
|
|
47
|
-
showTooltip:
|
|
48
|
-
kubeConfigCopying:
|
|
50
|
+
show: false,
|
|
51
|
+
showTooltip: false,
|
|
52
|
+
kubeConfigCopying: false,
|
|
49
53
|
searchShortcut,
|
|
50
54
|
shellShortcut,
|
|
51
55
|
LOGGED_OUT,
|
|
52
|
-
navHeaderRight:
|
|
56
|
+
navHeaderRight: null,
|
|
57
|
+
extensionHeaderActions: getApplicableExtensionEnhancements(this, ExtensionPoint.ACTION, ActionLocation.HEADER, this.$route),
|
|
58
|
+
ctx: this
|
|
53
59
|
};
|
|
54
60
|
},
|
|
55
61
|
|
|
@@ -170,6 +176,12 @@ export default {
|
|
|
170
176
|
if (nue && old && nue.id !== old.id) {
|
|
171
177
|
this.checkClusterName();
|
|
172
178
|
}
|
|
179
|
+
},
|
|
180
|
+
// since the Header is a "persistent component" we need to update it at every route change...
|
|
181
|
+
$route(nue) {
|
|
182
|
+
if (nue) {
|
|
183
|
+
this.extensionHeaderActions = getApplicableExtensionEnhancements(this, ExtensionPoint.ACTION, ActionLocation.HEADER, nue);
|
|
184
|
+
}
|
|
173
185
|
}
|
|
174
186
|
},
|
|
175
187
|
|
|
@@ -288,6 +300,33 @@ export default {
|
|
|
288
300
|
button.classList.remove('header-btn-active');
|
|
289
301
|
}
|
|
290
302
|
});
|
|
303
|
+
},
|
|
304
|
+
|
|
305
|
+
handleExtensionAction(action, event) {
|
|
306
|
+
const fn = action.invoke;
|
|
307
|
+
const opts = {
|
|
308
|
+
event,
|
|
309
|
+
action,
|
|
310
|
+
isAlt: isAlternate(event),
|
|
311
|
+
product: this.currentProduct.name,
|
|
312
|
+
cluster: this.currentCluster,
|
|
313
|
+
};
|
|
314
|
+
const enabled = action.enabled ? action.enabled.apply(this, [opts]) : true;
|
|
315
|
+
|
|
316
|
+
if (fn && enabled) {
|
|
317
|
+
fn.apply(this, [opts, []]);
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
|
|
321
|
+
handleExtensionTooltip(action) {
|
|
322
|
+
if (action.tooltipKey || action.tooltip) {
|
|
323
|
+
const tooltip = action.tooltipKey ? this.t(action.tooltipKey) : action.tooltip;
|
|
324
|
+
const shortcut = action.shortcutLabel ? action.shortcutLabel() : '';
|
|
325
|
+
|
|
326
|
+
return `${ tooltip } ${ shortcut }`;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return null;
|
|
291
330
|
}
|
|
292
331
|
}
|
|
293
332
|
};
|
|
@@ -502,6 +541,31 @@ export default {
|
|
|
502
541
|
</modal>
|
|
503
542
|
</div>
|
|
504
543
|
|
|
544
|
+
<!-- Extension header actions -->
|
|
545
|
+
<div
|
|
546
|
+
v-if="extensionHeaderActions.length"
|
|
547
|
+
class="header-buttons"
|
|
548
|
+
>
|
|
549
|
+
<button
|
|
550
|
+
v-for="action, i in extensionHeaderActions"
|
|
551
|
+
:key="`${action.label}${i}`"
|
|
552
|
+
v-tooltip="handleExtensionTooltip(action)"
|
|
553
|
+
v-shortkey="action.shortcutKey"
|
|
554
|
+
:disabled="action.enabled ? !action.enabled(ctx) : false"
|
|
555
|
+
type="button"
|
|
556
|
+
class="btn header-btn role-tertiary"
|
|
557
|
+
@shortkey="handleExtensionAction(action, $event)"
|
|
558
|
+
@click="handleExtensionAction(action, $event)"
|
|
559
|
+
>
|
|
560
|
+
<IconOrSvg
|
|
561
|
+
class="icon icon-lg"
|
|
562
|
+
:icon="action.icon"
|
|
563
|
+
:src="action.svg"
|
|
564
|
+
color="header"
|
|
565
|
+
/>
|
|
566
|
+
</button>
|
|
567
|
+
</div>
|
|
568
|
+
|
|
505
569
|
<div
|
|
506
570
|
v-if="showPageActions"
|
|
507
571
|
id="page-actions"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const NAME = 'harvesterManager';
|
package/config/product/backup.js
CHANGED
package/config/query-params.js
CHANGED
|
@@ -30,6 +30,7 @@ export const MODE = 'mode';
|
|
|
30
30
|
export const _CREATE = 'create';
|
|
31
31
|
export const _VIEW = 'view';
|
|
32
32
|
export const _EDIT = 'edit';
|
|
33
|
+
export const _LIST = 'list';
|
|
33
34
|
export const _CLONE = 'clone';
|
|
34
35
|
export const _STAGE = 'stage';
|
|
35
36
|
export const _IMPORT = 'import';
|
package/config/uiplugins.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import semver from 'semver';
|
|
2
2
|
|
|
3
3
|
// Version of the plugin API supported
|
|
4
|
-
export const UI_PLUGIN_API_VERSION = '1.
|
|
4
|
+
export const UI_PLUGIN_API_VERSION = '1.1.0';
|
|
5
5
|
export const UI_PLUGIN_HOST_APP = 'rancher-manager';
|
|
6
6
|
|
|
7
7
|
export const UI_PLUGIN_BASE_URL = '/api/v1/namespaces/cattle-ui-plugin-system/services/http:ui-plugin-operator:80/proxy';
|
|
@@ -32,8 +32,8 @@ export const UI_PLUGINS_REPO_BRANCH = 'main';
|
|
|
32
32
|
// Chart annotations
|
|
33
33
|
export const UI_PLUGIN_CHART_ANNOTATIONS = {
|
|
34
34
|
RANCHER_VERSION: 'catalog.cattle.io/rancher-version',
|
|
35
|
-
EXTENSIONS_VERSION: 'catalog.cattle.io/ui-
|
|
36
|
-
EXTENSIONS_HOST: 'catalog.cattle.io/ui-
|
|
35
|
+
EXTENSIONS_VERSION: 'catalog.cattle.io/ui-extensions-version',
|
|
36
|
+
EXTENSIONS_HOST: 'catalog.cattle.io/ui-extensions-host',
|
|
37
37
|
DISPLAY_NAME: 'catalog.cattle.io/display-name',
|
|
38
38
|
};
|
|
39
39
|
|