@rancher/shell 0.3.29 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.DS_Store +0 -0
- package/assets/translations/en-us.yaml +1 -1
- package/assets/translations/zh-hans.yaml +1 -1
- package/components/CopyCode.vue +6 -2
- package/components/CopyToClipboard.vue +2 -1
- package/components/CopyToClipboardText.vue +14 -9
- package/components/EtcdInfoBanner.vue +4 -4
- package/components/Markdown.vue +16 -12
- package/components/ResourceDetail/Masthead.vue +9 -6
- package/components/StatusTable.vue +5 -1
- package/components/__tests__/CopyCode.test.ts +5 -4
- package/components/fleet/FleetBundles.vue +5 -11
- package/components/fleet/FleetSummary.vue +3 -3
- package/components/fleet/__tests__/FleetSummary.test.ts +316 -0
- package/components/form/Password.vue +3 -1
- package/components/nav/Header.vue +1 -1
- package/config/home-links.js +1 -1
- package/core/plugin-helpers.js +3 -5
- package/creators/app/files/.gitlab-ci.yml +14 -0
- package/creators/app/init +19 -0
- package/edit/monitoring.coreos.com.prometheusrule/AlertingRule.vue +12 -3
- package/edit/monitoring.coreos.com.prometheusrule/GroupRules.vue +2 -1
- package/edit/provisioning.cattle.io.cluster/__tests__/CustomCommand.tests.ts +3 -1
- package/edit/workload/Upgrading.vue +3 -2
- package/edit/workload/storage/persistentVolumeClaim/persistentvolumeclaim.vue +2 -1
- package/initialize/index.js +24 -5
- package/models/__tests__/management.cattle.io.cluster.test.ts +4 -0
- package/models/management.cattle.io.cluster.js +7 -3
- package/models/provisioning.cattle.io.cluster.js +19 -1
- package/package.json +2 -2
- package/pages/c/_cluster/apps/charts/index.vue +64 -43
- package/plugins/clean-html-directive.js +1 -19
- package/plugins/clean-html.js +53 -0
- package/plugins/clean-tooltip-directive.js +1 -1
- package/plugins/index.js +11 -0
- package/scripts/.DS_Store +0 -0
- package/scripts/extension/bundle +19 -7
- package/scripts/extension/helm/scripts/package +11 -3
- package/scripts/extension/publish +20 -9
- package/scripts/verdaccio.log +205 -0
- package/store/index.js +3 -4
- package/types/shell/index.d.ts +6 -0
- package/utils/clipboard.js +5 -0
- package/plugins/vue-clipboard2.js +0 -4
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { mapGetters } from 'vuex';
|
|
3
3
|
import { LabeledInput } from '@components/Form/LabeledInput';
|
|
4
4
|
import { CHARSET, randomStr } from '@shell/utils/string';
|
|
5
|
+
import { copyTextToClipboard } from '@shell/utils/clipboard';
|
|
5
6
|
|
|
6
7
|
export default {
|
|
7
8
|
components: { LabeledInput },
|
|
@@ -75,6 +76,7 @@ export default {
|
|
|
75
76
|
}
|
|
76
77
|
},
|
|
77
78
|
methods: {
|
|
79
|
+
copyTextToClipboard,
|
|
78
80
|
generatePassword() {
|
|
79
81
|
this.password = randomStr(16, CHARSET.ALPHA_NUM);
|
|
80
82
|
},
|
|
@@ -109,7 +111,7 @@ export default {
|
|
|
109
111
|
>
|
|
110
112
|
<a
|
|
111
113
|
href="#"
|
|
112
|
-
@click.prevent.stop="
|
|
114
|
+
@click.prevent.stop="copyTextToClipboard(password)"
|
|
113
115
|
>{{ t('action.copy') }}</a>
|
|
114
116
|
</div>
|
|
115
117
|
<div
|
|
@@ -311,7 +311,7 @@ export default {
|
|
|
311
311
|
product: this.currentProduct.name,
|
|
312
312
|
cluster: this.currentCluster,
|
|
313
313
|
};
|
|
314
|
-
const enabled = action.enabled ? action.enabled.apply(this, [
|
|
314
|
+
const enabled = action.enabled ? action.enabled.apply(this, [this.ctx]) : true;
|
|
315
315
|
|
|
316
316
|
if (fn && enabled) {
|
|
317
317
|
fn.apply(this, [opts, [], { $route: this.$route }]);
|
package/config/home-links.js
CHANGED
package/core/plugin-helpers.js
CHANGED
|
@@ -7,13 +7,11 @@ import {
|
|
|
7
7
|
import { getProductFromRoute } from '@shell/middleware/authenticated';
|
|
8
8
|
import { isEqual } from '@shell/utils/object';
|
|
9
9
|
|
|
10
|
-
function checkRouteProduct(
|
|
11
|
-
const product = getProductFromRoute(
|
|
12
|
-
name, params, query
|
|
13
|
-
});
|
|
10
|
+
function checkRouteProduct($route, locationConfigParam) {
|
|
11
|
+
const product = getProductFromRoute($route);
|
|
14
12
|
|
|
15
13
|
// alias for the homepage
|
|
16
|
-
if (locationConfigParam === 'home' && name === 'home') {
|
|
14
|
+
if (locationConfigParam === 'home' && $route.name === 'home') {
|
|
17
15
|
return true;
|
|
18
16
|
} else if (locationConfigParam === product) {
|
|
19
17
|
return true;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
image: registry.suse.com/bci/bci-base:latest
|
|
2
|
+
|
|
3
|
+
stages:
|
|
4
|
+
- check_version
|
|
5
|
+
- build_catalog
|
|
6
|
+
|
|
7
|
+
variables:
|
|
8
|
+
REGISTRY: $CI_REGISTRY
|
|
9
|
+
REGISTRY_USER: $CI_REGISTRY_USER
|
|
10
|
+
REGISTRY_PASSWORD: $CI_REGISTRY_PASSWORD
|
|
11
|
+
IMAGE_NAMESPACE: $CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME
|
|
12
|
+
|
|
13
|
+
include:
|
|
14
|
+
- remote: 'https://raw.githubusercontent.com/rancher/dashboard/master/.gitlab/workflows/build-extension-catalog.gitlab-ci.yml'
|
package/creators/app/init
CHANGED
|
@@ -34,6 +34,25 @@ if (args.length === 3) {
|
|
|
34
34
|
fs.ensureDirSync(appFolder);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
let addGitlabWorkflow = false;
|
|
38
|
+
|
|
39
|
+
// Check for Gitlab integration option
|
|
40
|
+
if ( args.length > 3 ) {
|
|
41
|
+
for (let i = 3; i < args.length; i++) {
|
|
42
|
+
switch (args[i]) {
|
|
43
|
+
case '-l':
|
|
44
|
+
addGitlabWorkflow = true;
|
|
45
|
+
break;
|
|
46
|
+
default:
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if ( addGitlabWorkflow ) {
|
|
53
|
+
files.push('.gitlab-ci.yml');
|
|
54
|
+
}
|
|
55
|
+
|
|
37
56
|
// Check that there is a package file
|
|
38
57
|
|
|
39
58
|
let setName = false;
|
|
@@ -47,9 +47,18 @@ export default {
|
|
|
47
47
|
selectedSeverityLabel: null,
|
|
48
48
|
ignoredAnnotations: IGNORED_ANNOTATIONS,
|
|
49
49
|
severityOptions: [
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
{
|
|
51
|
+
label: this.t('prometheusRule.alertingRules.labels.severity.choices.critical'),
|
|
52
|
+
value: 'critical'
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
label: this.t('prometheusRule.alertingRules.labels.severity.choices.warning'),
|
|
56
|
+
value: 'warning'
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
label: this.t('prometheusRule.alertingRules.labels.severity.choices.none'),
|
|
60
|
+
value: 'none'
|
|
61
|
+
},
|
|
53
62
|
],
|
|
54
63
|
};
|
|
55
64
|
},
|
|
@@ -6,6 +6,7 @@ import { _VIEW } from '@shell/config/query-params';
|
|
|
6
6
|
import ArrayListGrouped from '@shell/components/form/ArrayListGrouped';
|
|
7
7
|
import AlertingRule from './AlertingRule';
|
|
8
8
|
import RecordingRule from './RecordingRule';
|
|
9
|
+
import { clone } from '@shell/utils/object';
|
|
9
10
|
|
|
10
11
|
export default {
|
|
11
12
|
components: {
|
|
@@ -105,7 +106,7 @@ export default {
|
|
|
105
106
|
});
|
|
106
107
|
break;
|
|
107
108
|
case 'alert':
|
|
108
|
-
value.push(this.defaultAlert);
|
|
109
|
+
value.push(clone(this.defaultAlert));
|
|
109
110
|
break;
|
|
110
111
|
default:
|
|
111
112
|
break;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { mount } from '@vue/test-utils';
|
|
2
2
|
import CustomCommand from '@shell/edit/provisioning.cattle.io.cluster/CustomCommand.vue';
|
|
3
|
-
|
|
3
|
+
jest.mock('@shell/utils/clipboard', () => {
|
|
4
|
+
return { copyTextToClipboard: jest.fn(() => Promise.resolve({})) };
|
|
5
|
+
});
|
|
4
6
|
describe('component: CustomCommand', () => {
|
|
5
7
|
const token = 'MY_TOKEN';
|
|
6
8
|
const ip = 'MY_IP';
|
|
@@ -40,12 +40,13 @@ export default {
|
|
|
40
40
|
data() {
|
|
41
41
|
const {
|
|
42
42
|
strategy:strategyObj = {},
|
|
43
|
+
updateStrategy: updateStrategyObj = {},
|
|
43
44
|
minReadySeconds = 0,
|
|
44
45
|
progressDeadlineSeconds = 600,
|
|
45
46
|
revisionHistoryLimit = 10,
|
|
46
47
|
podManagementPolicy = 'OrderedReady'
|
|
47
48
|
} = this.value;
|
|
48
|
-
const strategy = strategyObj.type || 'RollingUpdate';
|
|
49
|
+
const strategy = strategyObj.type || updateStrategyObj.type || 'RollingUpdate';
|
|
49
50
|
let maxSurge = '25';
|
|
50
51
|
let maxUnavailable = '25';
|
|
51
52
|
let surgeUnits = '%';
|
|
@@ -97,7 +98,7 @@ export default {
|
|
|
97
98
|
case WORKLOAD_TYPES.DAEMON_SET:
|
|
98
99
|
case WORKLOAD_TYPES.STATEFUL_SET:
|
|
99
100
|
return {
|
|
100
|
-
options: ['RollingUpdate', '
|
|
101
|
+
options: ['RollingUpdate', 'OnDelete'],
|
|
101
102
|
labels: [this.t('workload.upgrading.strategies.labels.rollingUpdate'), this.t('workload.upgrading.strategies.labels.delete')]
|
|
102
103
|
};
|
|
103
104
|
default:
|
|
@@ -78,7 +78,8 @@ export default {
|
|
|
78
78
|
* Required to initialize with default SC on creation
|
|
79
79
|
*/
|
|
80
80
|
defaultStorageClassName() {
|
|
81
|
-
return this.storageClasses.find((sc) => sc.metadata?.annotations?.['storageclass.beta.kubernetes.io/is-default-class']
|
|
81
|
+
return this.storageClasses.find((sc) => sc.metadata?.annotations?.['storageclass.beta.kubernetes.io/is-default-class'] === 'true' ||
|
|
82
|
+
sc.metadata?.annotations?.['storageclass.kubernetes.io/is-default-class'] === 'true')?.metadata.name ;
|
|
82
83
|
},
|
|
83
84
|
|
|
84
85
|
availablePVs() {
|
package/initialize/index.js
CHANGED
|
@@ -13,7 +13,7 @@ import { setContext, getLocation, getRouteData, normalizeError } from '../utils/
|
|
|
13
13
|
import { createStore } from '../config/store.js';
|
|
14
14
|
|
|
15
15
|
/* Plugins */
|
|
16
|
-
|
|
16
|
+
import { loadDirectives } from '@shell/plugins';
|
|
17
17
|
import '../plugins/portal-vue.js';
|
|
18
18
|
import cookieUniversalNuxt from '../utils/cookie-universal-nuxt.js';
|
|
19
19
|
import axios from '../utils/axios.js';
|
|
@@ -21,11 +21,7 @@ import plugins from '../core/plugins.js';
|
|
|
21
21
|
import pluginsLoader from '../core/plugins-loader.js';
|
|
22
22
|
import axiosShell from '../plugins/axios';
|
|
23
23
|
import '../plugins/tooltip';
|
|
24
|
-
import '../plugins/clean-tooltip-directive';
|
|
25
|
-
import '../plugins/vue-clipboard2';
|
|
26
24
|
import '../plugins/v-select';
|
|
27
|
-
import '../plugins/directives';
|
|
28
|
-
import '../plugins/clean-html-directive';
|
|
29
25
|
import '../plugins/transitions';
|
|
30
26
|
import '../plugins/vue-js-modal';
|
|
31
27
|
import '../plugins/js-yaml';
|
|
@@ -47,6 +43,29 @@ import '../plugins/formatters';
|
|
|
47
43
|
import version from '../plugins/version';
|
|
48
44
|
import steveCreateWorker from '../plugins/steve-create-worker';
|
|
49
45
|
|
|
46
|
+
// Prevent extensions from overriding existing directives
|
|
47
|
+
// Hook into Vue.directive and keep track of the directive names that have been added
|
|
48
|
+
// and prevent an existing directive from being overwritten
|
|
49
|
+
const directiveNames = {};
|
|
50
|
+
const vueDirective = Vue.directive;
|
|
51
|
+
|
|
52
|
+
Vue.directive = function(name) {
|
|
53
|
+
if (directiveNames[name]) {
|
|
54
|
+
console.log(`Can not override directive: ${ name }`); // eslint-disable-line no-console
|
|
55
|
+
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
directiveNames[name] = true;
|
|
60
|
+
|
|
61
|
+
vueDirective.apply(Vue, arguments);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// Load the directives from the plugins - we do this with a function so we know
|
|
65
|
+
// these are initialized here, after the code above which keeps track of them and
|
|
66
|
+
// prevents over-writes
|
|
67
|
+
loadDirectives();
|
|
68
|
+
|
|
50
69
|
// Component: <ClientOnly>
|
|
51
70
|
Vue.component(ClientOnly.name, ClientOnly);
|
|
52
71
|
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import MgmtCluster from '@shell/models/management.cattle.io.cluster';
|
|
2
2
|
|
|
3
|
+
jest.mock('@shell/utils/clipboard', () => {
|
|
4
|
+
return { copyTextToClipboard: jest.fn(() => Promise.resolve({})) };
|
|
5
|
+
});
|
|
6
|
+
|
|
3
7
|
describe('class MgmtCluster', () => {
|
|
4
8
|
describe('provisioner', () => {
|
|
5
9
|
const testCases = [
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import Vue from 'vue';
|
|
2
1
|
import { CATALOG, CLUSTER_BADGE } from '@shell/config/labels-annotations';
|
|
3
2
|
import { NODE, FLEET, MANAGEMENT, CAPI } from '@shell/config/types';
|
|
4
3
|
import { insertAt, addObject, removeObject } from '@shell/utils/array';
|
|
@@ -15,6 +14,7 @@ import HybridModel from '@shell/plugins/steve/hybrid-class';
|
|
|
15
14
|
import { LINUX, WINDOWS } from '@shell/store/catalog';
|
|
16
15
|
import { KONTAINER_TO_DRIVER } from './management.cattle.io.kontainerdriver';
|
|
17
16
|
import { PINNED_CLUSTERS } from '@shell/store/prefs';
|
|
17
|
+
import { copyTextToClipboard } from '@shell/utils/clipboard';
|
|
18
18
|
|
|
19
19
|
// See translation file cluster.providers for list of providers
|
|
20
20
|
// If the logo is not named with the provider name, add an override here
|
|
@@ -408,9 +408,13 @@ export default class MgmtCluster extends HybridModel {
|
|
|
408
408
|
}
|
|
409
409
|
|
|
410
410
|
async copyKubeConfig() {
|
|
411
|
-
|
|
411
|
+
try {
|
|
412
|
+
const config = await this.generateKubeConfig();
|
|
412
413
|
|
|
413
|
-
|
|
414
|
+
if (config) {
|
|
415
|
+
await copyTextToClipboard(config);
|
|
416
|
+
}
|
|
417
|
+
} catch {}
|
|
414
418
|
}
|
|
415
419
|
|
|
416
420
|
async fetchNodeMetrics() {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
-
CAPI, MANAGEMENT, NORMAN, SNAPSHOT, HCI
|
|
2
|
+
CAPI, MANAGEMENT, NAMESPACE, NORMAN, SNAPSHOT, HCI, LOCAL_CLUSTER
|
|
3
3
|
} from '@shell/config/types';
|
|
4
4
|
import SteveModel from '@shell/plugins/steve/steve-class';
|
|
5
5
|
import { findBy } from '@shell/utils/array';
|
|
@@ -883,4 +883,22 @@ export default class ProvCluster extends SteveModel {
|
|
|
883
883
|
get hasError() {
|
|
884
884
|
return this.status?.conditions?.some((condition) => condition.error === true);
|
|
885
885
|
}
|
|
886
|
+
|
|
887
|
+
get namespaceLocation() {
|
|
888
|
+
const localCluster = this.$rootGetters['management/byId'](MANAGEMENT.CLUSTER, LOCAL_CLUSTER);
|
|
889
|
+
|
|
890
|
+
if (localCluster) {
|
|
891
|
+
return {
|
|
892
|
+
name: 'c-cluster-product-resource-id',
|
|
893
|
+
params: {
|
|
894
|
+
cluster: localCluster.id,
|
|
895
|
+
product: this.$rootGetters['productId'],
|
|
896
|
+
resource: NAMESPACE,
|
|
897
|
+
id: this.namespace
|
|
898
|
+
}
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
return null;
|
|
903
|
+
}
|
|
886
904
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rancher/shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Rancher Dashboard Shell",
|
|
5
5
|
"repository": "https://github.com/rancherlabs/dashboard",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
"url-parse": "1.5.10",
|
|
121
121
|
"v-tooltip": "2.0.3",
|
|
122
122
|
"vue": "2.7.14",
|
|
123
|
-
"
|
|
123
|
+
"clipboard-polyfill": "4.0.1",
|
|
124
124
|
"vue-codemirror": "4.0.6",
|
|
125
125
|
"vue-js-modal": "1.3.35",
|
|
126
126
|
"vue-resize": "0.4.5",
|
|
@@ -138,6 +138,11 @@ export default {
|
|
|
138
138
|
return reducedRepos;
|
|
139
139
|
},
|
|
140
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Filter allll charts by invalid entries (deprecated, hidden and ui plugin).
|
|
143
|
+
*
|
|
144
|
+
* This does not include any user provided filters (like selected repos, categories and text query)
|
|
145
|
+
*/
|
|
141
146
|
enabledCharts() {
|
|
142
147
|
return (this.allCharts || []).filter((c) => {
|
|
143
148
|
if ( c.deprecated && !this.showDeprecated ) {
|
|
@@ -148,10 +153,6 @@ export default {
|
|
|
148
153
|
return false;
|
|
149
154
|
}
|
|
150
155
|
|
|
151
|
-
if ( this.hideRepos.includes(c.repoKey) ) {
|
|
152
|
-
return false;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
156
|
if (isUIPlugin(c)) {
|
|
156
157
|
return false;
|
|
157
158
|
}
|
|
@@ -160,26 +161,28 @@ export default {
|
|
|
160
161
|
});
|
|
161
162
|
},
|
|
162
163
|
|
|
164
|
+
/**
|
|
165
|
+
* Filter enabled charts allll filters. These are what the user will see in the list
|
|
166
|
+
*/
|
|
163
167
|
filteredCharts() {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
clusterProvider,
|
|
169
|
-
category: this.category,
|
|
170
|
-
searchQuery: this.searchQuery,
|
|
171
|
-
showDeprecated: this.showDeprecated,
|
|
172
|
-
showHidden: this.showHidden,
|
|
173
|
-
hideRepos: this.hideRepos,
|
|
174
|
-
hideTypes: [CATALOG._CLUSTER_TPL],
|
|
175
|
-
showPrerelease: this.$store.getters['prefs/get'](SHOW_PRE_RELEASE),
|
|
168
|
+
return this.filterCharts({
|
|
169
|
+
category: this.category,
|
|
170
|
+
searchQuery: this.searchQuery,
|
|
171
|
+
hideRepos: this.hideRepos
|
|
176
172
|
});
|
|
177
173
|
},
|
|
178
174
|
|
|
179
|
-
|
|
180
|
-
|
|
175
|
+
/**
|
|
176
|
+
* Filter valid charts (alll filters minus user provided ones) by whether they are featured or not
|
|
177
|
+
*
|
|
178
|
+
* This will power the carousel
|
|
179
|
+
*/
|
|
180
|
+
featuredCharts() {
|
|
181
|
+
const filteredCharts = this.filterCharts({});
|
|
182
|
+
|
|
183
|
+
// debugger;
|
|
181
184
|
|
|
182
|
-
const featuredCharts =
|
|
185
|
+
const featuredCharts = filteredCharts.filter((value) => value.featured).sort((a, b) => a.featured - b.featured);
|
|
183
186
|
|
|
184
187
|
return featuredCharts.slice(0, 5);
|
|
185
188
|
},
|
|
@@ -187,7 +190,13 @@ export default {
|
|
|
187
190
|
categories() {
|
|
188
191
|
const map = {};
|
|
189
192
|
|
|
190
|
-
|
|
193
|
+
// Filter charts by everything except itself
|
|
194
|
+
const charts = this.filterCharts({
|
|
195
|
+
searchQuery: this.searchQuery,
|
|
196
|
+
hideRepos: this.hideRepos
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
for ( const chart of charts ) {
|
|
191
200
|
for ( const c of chart.categories ) {
|
|
192
201
|
if ( !map[c] ) {
|
|
193
202
|
const labelKey = `catalog.charts.categories.${ lcFirst(c) }`;
|
|
@@ -208,14 +217,14 @@ export default {
|
|
|
208
217
|
out.unshift({
|
|
209
218
|
label: this.t('catalog.charts.categories.all'),
|
|
210
219
|
value: '',
|
|
211
|
-
count:
|
|
220
|
+
count: charts.length
|
|
212
221
|
});
|
|
213
222
|
|
|
214
|
-
return out;
|
|
223
|
+
return sortBy(out, ['label']);
|
|
215
224
|
},
|
|
216
225
|
|
|
217
226
|
showCarousel() {
|
|
218
|
-
return this.chartMode === 'featured' && this.
|
|
227
|
+
return this.chartMode === 'featured' && this.featuredCharts.length;
|
|
219
228
|
}
|
|
220
229
|
|
|
221
230
|
},
|
|
@@ -334,6 +343,22 @@ export default {
|
|
|
334
343
|
btnCb(false);
|
|
335
344
|
}
|
|
336
345
|
},
|
|
346
|
+
|
|
347
|
+
filterCharts({ category, searchQuery, hideRepos }) {
|
|
348
|
+
const enabledCharts = (this.enabledCharts || []);
|
|
349
|
+
const clusterProvider = this.currentCluster.status.provider || 'other';
|
|
350
|
+
|
|
351
|
+
return filterAndArrangeCharts(enabledCharts, {
|
|
352
|
+
clusterProvider,
|
|
353
|
+
category,
|
|
354
|
+
searchQuery,
|
|
355
|
+
showDeprecated: this.showDeprecated,
|
|
356
|
+
showHidden: this.showHidden,
|
|
357
|
+
hideRepos,
|
|
358
|
+
hideTypes: [CATALOG._CLUSTER_TPL],
|
|
359
|
+
showPrerelease: this.$store.getters['prefs/get'](SHOW_PRE_RELEASE),
|
|
360
|
+
});
|
|
361
|
+
}
|
|
337
362
|
},
|
|
338
363
|
};
|
|
339
364
|
</script>
|
|
@@ -351,7 +376,7 @@ export default {
|
|
|
351
376
|
</h1>
|
|
352
377
|
</div>
|
|
353
378
|
<div
|
|
354
|
-
v-if="
|
|
379
|
+
v-if="featuredCharts.length > 0"
|
|
355
380
|
class="actions-container"
|
|
356
381
|
>
|
|
357
382
|
<ButtonGroup
|
|
@@ -363,7 +388,7 @@ export default {
|
|
|
363
388
|
<div v-if="showCarousel">
|
|
364
389
|
<h3>{{ t('catalog.charts.featuredCharts') }}</h3>
|
|
365
390
|
<Carousel
|
|
366
|
-
:sliders="
|
|
391
|
+
:sliders="featuredCharts"
|
|
367
392
|
@clicked="(row) => selectChart(row)"
|
|
368
393
|
/>
|
|
369
394
|
</div>
|
|
@@ -514,22 +539,21 @@ export default {
|
|
|
514
539
|
}
|
|
515
540
|
}
|
|
516
541
|
}
|
|
517
|
-
|
|
542
|
+
}
|
|
518
543
|
|
|
519
544
|
.checkbox-select {
|
|
520
|
-
|
|
545
|
+
.vs__search {
|
|
521
546
|
position: absolute;
|
|
522
547
|
right: 0
|
|
523
548
|
}
|
|
524
549
|
|
|
525
|
-
|
|
550
|
+
.vs__selected-options {
|
|
526
551
|
overflow: hidden;
|
|
527
552
|
white-space: nowrap;
|
|
528
553
|
text-overflow: ellipsis;
|
|
529
554
|
display: inline-block;
|
|
530
555
|
line-height: 2.4rem;
|
|
531
556
|
}
|
|
532
|
-
|
|
533
557
|
}
|
|
534
558
|
|
|
535
559
|
.checkbox-outer-container.in-select {
|
|
@@ -537,7 +561,7 @@ export default {
|
|
|
537
561
|
padding: 7px 0 6px 13px;
|
|
538
562
|
width: calc(100% + 10px);
|
|
539
563
|
|
|
540
|
-
::v-deep.checkbox-label {
|
|
564
|
+
::v-deep .checkbox-label {
|
|
541
565
|
display: flex;
|
|
542
566
|
align-items: center;
|
|
543
567
|
|
|
@@ -552,7 +576,7 @@ export default {
|
|
|
552
576
|
}
|
|
553
577
|
}
|
|
554
578
|
|
|
555
|
-
&:hover ::v-deep.checkbox-label {
|
|
579
|
+
&:hover ::v-deep .checkbox-label {
|
|
556
580
|
color: var(--body-text);
|
|
557
581
|
}
|
|
558
582
|
|
|
@@ -560,7 +584,7 @@ export default {
|
|
|
560
584
|
&:hover {
|
|
561
585
|
background: var(--app-rancher-accent);
|
|
562
586
|
}
|
|
563
|
-
&:hover ::v-deep.checkbox-label {
|
|
587
|
+
&:hover ::v-deep .checkbox-label {
|
|
564
588
|
color: var(--app-rancher-accent-text);
|
|
565
589
|
}
|
|
566
590
|
& i {
|
|
@@ -572,7 +596,7 @@ export default {
|
|
|
572
596
|
&:hover {
|
|
573
597
|
background: var(--app-partner-accent);
|
|
574
598
|
}
|
|
575
|
-
&:hover ::v-deep.checkbox-label {
|
|
599
|
+
&:hover ::v-deep .checkbox-label {
|
|
576
600
|
color: var(--app-partner-accent-text);
|
|
577
601
|
}
|
|
578
602
|
& i {
|
|
@@ -584,7 +608,7 @@ export default {
|
|
|
584
608
|
&:hover {
|
|
585
609
|
background: var(--app-color1-accent);
|
|
586
610
|
}
|
|
587
|
-
&:hover ::v-deep.checkbox-label {
|
|
611
|
+
&:hover ::v-deep .checkbox-label {
|
|
588
612
|
color: var(--app-color1-accent-text);
|
|
589
613
|
}
|
|
590
614
|
& i {
|
|
@@ -595,10 +619,10 @@ export default {
|
|
|
595
619
|
&:hover {
|
|
596
620
|
background: var(--app-color2-accent);
|
|
597
621
|
}
|
|
598
|
-
&:hover ::v-deep.checkbox-label {
|
|
622
|
+
&:hover ::v-deep .checkbox-label {
|
|
599
623
|
color: var(--app-color2-accent-text);
|
|
600
624
|
}
|
|
601
|
-
|
|
625
|
+
& i {
|
|
602
626
|
color: var(--app-color2-accent)
|
|
603
627
|
}
|
|
604
628
|
}
|
|
@@ -606,7 +630,7 @@ export default {
|
|
|
606
630
|
&:hover {
|
|
607
631
|
background: var(--app-color3-accent);
|
|
608
632
|
}
|
|
609
|
-
&:hover ::v-deep.checkbox-label {
|
|
633
|
+
&:hover ::v-deep .checkbox-label {
|
|
610
634
|
color: var(--app-color3-accent-text);
|
|
611
635
|
}
|
|
612
636
|
& i {
|
|
@@ -628,7 +652,7 @@ export default {
|
|
|
628
652
|
&:hover {
|
|
629
653
|
background: var(--app-color5-accent);
|
|
630
654
|
}
|
|
631
|
-
&:hover ::v-deep.checkbox-label {
|
|
655
|
+
&:hover ::v-deep .checkbox-label {
|
|
632
656
|
color: var(--app-color5-accent-text);
|
|
633
657
|
}
|
|
634
658
|
& i {
|
|
@@ -639,7 +663,7 @@ export default {
|
|
|
639
663
|
&:hover {
|
|
640
664
|
background: var(--app-color6-accent);
|
|
641
665
|
}
|
|
642
|
-
&:hover ::v-deep.checkbox-label {
|
|
666
|
+
&:hover ::v-deep .checkbox-label {
|
|
643
667
|
color: var(--app-color6-accent-text);
|
|
644
668
|
}
|
|
645
669
|
& i {
|
|
@@ -650,7 +674,7 @@ export default {
|
|
|
650
674
|
&:hover {
|
|
651
675
|
background: var(--app-color7-accent);
|
|
652
676
|
}
|
|
653
|
-
&:hover ::v-deep.checkbox-label {
|
|
677
|
+
&:hover ::v-deep .checkbox-label {
|
|
654
678
|
color: var(--app-color7-accent-text);
|
|
655
679
|
}
|
|
656
680
|
& i {
|
|
@@ -661,9 +685,6 @@ export default {
|
|
|
661
685
|
&:hover {
|
|
662
686
|
background: var(--app-color8-accent);
|
|
663
687
|
}
|
|
664
|
-
&:hover ::v-deep.checkbox-label {
|
|
665
|
-
color: var(--app-color8-accent-text);
|
|
666
|
-
}
|
|
667
688
|
& i {
|
|
668
689
|
color: var(--app-color8-accent)
|
|
669
690
|
}
|
|
@@ -1,23 +1,5 @@
|
|
|
1
1
|
import Vue from 'vue';
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
const ALLOWED_TAGS = [
|
|
5
|
-
'code',
|
|
6
|
-
'li',
|
|
7
|
-
'a',
|
|
8
|
-
'p',
|
|
9
|
-
'b',
|
|
10
|
-
'br',
|
|
11
|
-
'ul',
|
|
12
|
-
'pre',
|
|
13
|
-
'span',
|
|
14
|
-
'div',
|
|
15
|
-
'i',
|
|
16
|
-
'em',
|
|
17
|
-
'strong',
|
|
18
|
-
];
|
|
19
|
-
|
|
20
|
-
export const purifyHTML = (value) => DOMPurify.sanitize(value, { ALLOWED_TAGS });
|
|
2
|
+
import { purifyHTML } from './clean-html';
|
|
21
3
|
|
|
22
4
|
export const cleanHtmlDirective = {
|
|
23
5
|
inserted(el, binding) {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import DOMPurify from 'dompurify';
|
|
2
|
+
import { uniq } from '@shell/utils/array';
|
|
3
|
+
|
|
4
|
+
const ALLOWED_TAGS = [
|
|
5
|
+
'code',
|
|
6
|
+
'li',
|
|
7
|
+
'a',
|
|
8
|
+
'p',
|
|
9
|
+
'b',
|
|
10
|
+
'br',
|
|
11
|
+
'ul',
|
|
12
|
+
'pre',
|
|
13
|
+
'span',
|
|
14
|
+
'div',
|
|
15
|
+
'i',
|
|
16
|
+
'em',
|
|
17
|
+
'strong',
|
|
18
|
+
'h1',
|
|
19
|
+
'h2',
|
|
20
|
+
'h3',
|
|
21
|
+
'h4',
|
|
22
|
+
'h5',
|
|
23
|
+
'h6',
|
|
24
|
+
'table',
|
|
25
|
+
'thead',
|
|
26
|
+
'tr',
|
|
27
|
+
'th',
|
|
28
|
+
'tbody',
|
|
29
|
+
'td',
|
|
30
|
+
'blockquote'
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
// Allow 'A' tags to keep the target=_blank attribute if they have it
|
|
34
|
+
DOMPurify.addHook('uponSanitizeAttribute', (node, data) => {
|
|
35
|
+
if (node.tagName === 'A' && data.attrName === 'target' && data.attrValue === '_blank') {
|
|
36
|
+
data.forceKeepAttr = true;
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Ensure if an 'A' tag has target=_blank that we add noopener, noreferrer and nofollow to the 'rel' attribute
|
|
41
|
+
DOMPurify.addHook('afterSanitizeAttributes', (node) => {
|
|
42
|
+
if (node.tagName === 'A' && node?.target === '_blank') {
|
|
43
|
+
const rel = ['noopener', 'noreferrer', 'nofollow'];
|
|
44
|
+
const existingRel = node.rel?.length ? node.rel.split(' ') : [];
|
|
45
|
+
const combined = uniq([...rel, ...existingRel]);
|
|
46
|
+
|
|
47
|
+
node.setAttribute('rel', combined.join(' '));
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
export const purifyHTML = (value, options = { ALLOWED_TAGS }) => {
|
|
52
|
+
return DOMPurify.sanitize(value, options);
|
|
53
|
+
};
|