@rancher/shell 0.3.21 → 0.3.22

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.
Files changed (35) hide show
  1. package/assets/translations/en-us.yaml +4 -0
  2. package/assets/translations/zh-hans.yaml +8 -1
  3. package/cloud-credential/__tests__/azure.test.ts +53 -0
  4. package/cloud-credential/azure.vue +6 -0
  5. package/components/GrowlManager.vue +33 -30
  6. package/components/form/ResourceQuota/ProjectRow.vue +38 -15
  7. package/components/formatter/ClusterProvider.vue +9 -3
  8. package/components/formatter/__tests__/ClusterProvider.test.ts +5 -1
  9. package/components/nav/Header.vue +1 -0
  10. package/config/settings.ts +59 -2
  11. package/config/types.js +2 -0
  12. package/creators/pkg/files/.github/workflows/build-extension-catalog.yml +28 -0
  13. package/creators/pkg/files/.github/workflows/build-extension-charts.yml +26 -0
  14. package/creators/pkg/init +63 -4
  15. package/detail/provisioning.cattle.io.cluster.vue +4 -2
  16. package/edit/fleet.cattle.io.gitrepo.vue +1 -0
  17. package/edit/provisioning.cattle.io.cluster/rke2.vue +4 -4
  18. package/mixins/__tests__/chart.test.ts +40 -0
  19. package/mixins/chart.js +5 -0
  20. package/models/catalog.cattle.io.clusterrepo.js +6 -2
  21. package/models/fleet.cattle.io.cluster.js +10 -2
  22. package/package.json +1 -1
  23. package/pages/c/_cluster/gatekeeper/index.vue +10 -1
  24. package/plugins/steve/__tests__/header-warnings.spec.ts +238 -0
  25. package/plugins/steve/actions.js +4 -23
  26. package/plugins/steve/header-warnings.ts +91 -0
  27. package/promptRemove/management.cattle.io.project.vue +9 -6
  28. package/scripts/extension/parse-tag-name +30 -0
  29. package/types/shell/index.d.ts +1 -0
  30. package/utils/settings.ts +2 -17
  31. package/vue-config-helper.js +135 -0
  32. package/vue.config.js +23 -139
  33. package/yarn-error.log +200 -0
  34. package/creators/pkg/files/.github/workflows/build-container.yml +0 -64
  35. package/creators/pkg/files/.github/workflows/build-extension.yml +0 -110
@@ -0,0 +1,40 @@
1
+ import { createLocalVue } from '@vue/test-utils';
2
+ import Vuex from 'vuex';
3
+ import ChartMixin from '@shell/mixins/chart';
4
+ import { OPA_GATE_KEEPER_ID } from '@shell/pages/c/_cluster/gatekeeper/index.vue';
5
+
6
+ describe('chartMixin', () => {
7
+ const testCases = [[null, 0], [OPA_GATE_KEEPER_ID, 1], ['any_other_id', 0]];
8
+
9
+ it.each(testCases)(
10
+ 'should add OPA deprecation warning properly', (chartId, expected) => {
11
+ const localVue = createLocalVue();
12
+
13
+ localVue.use(Vuex);
14
+ localVue.mixin(ChartMixin);
15
+
16
+ const store = new Vuex.Store({
17
+ getters: {
18
+ currentCluster: () => {},
19
+ isRancher: () => true,
20
+ 'catalog/repo': () => {
21
+ return () => 'repo';
22
+ },
23
+ 'catalog/chart': () => {
24
+ return () => ({ id: chartId });
25
+ },
26
+ 'i18n/t': () => jest.fn()
27
+ }
28
+ });
29
+
30
+ const vm = localVue.extend({});
31
+ const instance = new vm({ store });
32
+
33
+ instance.$route = { query: { chart: 'chart_name' } };
34
+
35
+ const warnings = instance.warnings;
36
+
37
+ expect(warnings).toHaveLength(expected);
38
+ }
39
+ );
40
+ });
package/mixins/chart.js CHANGED
@@ -8,6 +8,7 @@ import { CATALOG as CATALOG_ANNOTATIONS } from '@shell/config/labels-annotations
8
8
  import { SHOW_PRE_RELEASE, mapPref } from '@shell/store/prefs';
9
9
  import { NAME as EXPLORER } from '@shell/config/product/explorer';
10
10
  import { NAME as MANAGER } from '@shell/config/product/manager';
11
+ import { OPA_GATE_KEEPER_ID } from '@shell/pages/c/_cluster/gatekeeper/index.vue';
11
12
 
12
13
  import { formatSi, parseSi } from '@shell/utils/units';
13
14
  import { CAPI, CATALOG } from '@shell/config/types';
@@ -185,6 +186,10 @@ export default {
185
186
  }
186
187
  }
187
188
 
189
+ if (this.chart?.id === OPA_GATE_KEEPER_ID) {
190
+ warnings.unshift(this.t('gatekeeperIndex.deprecated', {}, true));
191
+ }
192
+
188
193
  return warnings;
189
194
  },
190
195
 
@@ -29,11 +29,15 @@ export default class ClusterRepo extends SteveModel {
29
29
  return out;
30
30
  }
31
31
 
32
- refresh() {
32
+ async refresh() {
33
33
  const now = (new Date()).toISOString().replace(/\.\d+Z$/, 'Z');
34
34
 
35
35
  this.spec.forceUpdate = now;
36
- this.save();
36
+ await this.save();
37
+
38
+ await this.waitForState('active', 10000, 1000);
39
+
40
+ this.$dispatch('catalog/load', { force: true, reset: true }, { root: true });
37
41
  }
38
42
 
39
43
  get isGit() {
@@ -1,4 +1,4 @@
1
- import { MANAGEMENT, NORMAN } from '@shell/config/types';
1
+ import { LOCAL_CLUSTER, MANAGEMENT, NORMAN } from '@shell/config/types';
2
2
  import { CAPI, FLEET as FLEET_LABELS } from '@shell/config/labels-annotations';
3
3
  import { _RKE2 } from '@shell/store/prefs';
4
4
  import SteveModel from '@shell/plugins/steve/steve-class';
@@ -34,7 +34,7 @@ export default class FleetCluster extends SteveModel {
34
34
  enabled: !!this.links.update
35
35
  });
36
36
 
37
- if (!this.isRke2) {
37
+ if (this.canChangeWorkspace) {
38
38
  insertAt(out, 3, {
39
39
  action: 'assignTo',
40
40
  label: 'Change workspace',
@@ -79,6 +79,14 @@ export default class FleetCluster extends SteveModel {
79
79
  return false;
80
80
  }
81
81
 
82
+ get canChangeWorkspace() {
83
+ return !this.isRke2 && !this.isLocal;
84
+ }
85
+
86
+ get isLocal() {
87
+ return this.metadata.name === LOCAL_CLUSTER || this.metadata?.labels?.[FLEET_LABELS.CLUSTER_NAME] === LOCAL_CLUSTER;
88
+ }
89
+
82
90
  get isRke2() {
83
91
  const provider = this?.metadata?.labels?.[CAPI.PROVIDER] || this?.status?.provider;
84
92
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rancher/shell",
3
- "version": "0.3.21",
3
+ "version": "0.3.22",
4
4
  "description": "Rancher Dashboard Shell",
5
5
  "repository": "https://github.com/rancherlabs/dashboard",
6
6
  "license": "Apache-2.0",
@@ -3,10 +3,16 @@ import { NAME, CHART_NAME } from '@shell/config/product/gatekeeper';
3
3
  import InstallRedirect from '@shell/utils/install-redirect';
4
4
  import ChartHeading from '@shell/components/ChartHeading';
5
5
  import SortableTable from '@shell/components/SortableTable';
6
+ import { Banner } from '@components/Banner';
6
7
  import { CONSTRAINT_VIOLATION_CONSTRAINT_LINK, CONSTRAINT_VIOLATION_COUNT, CONSTRAINT_VIOLATION_TEMPLATE_LINK } from '@shell/config/table-headers';
7
8
  import { GATEKEEPER } from '@shell/config/types';
9
+
10
+ export const OPA_GATE_KEEPER_ID = 'cluster/rancher-charts/rancher-gatekeeper';
11
+
8
12
  export default {
9
- components: { ChartHeading, SortableTable },
13
+ components: {
14
+ ChartHeading, SortableTable, Banner
15
+ },
10
16
  middleware: InstallRedirect(NAME, CHART_NAME),
11
17
  async fetch() {
12
18
  const constraints = this.constraint ? [this.constraint] : await this.$store.dispatch('cluster/findAll', { type: GATEKEEPER.SPOOFED.CONSTRAINT });
@@ -49,6 +55,9 @@ export default {
49
55
  :label="t('gatekeeperIndex.poweredBy')"
50
56
  url="https://github.com/open-policy-agent/gatekeeper"
51
57
  />
58
+ <Banner color="warning">
59
+ <span v-clean-html="t('gatekeeperIndex.deprecated', {}, true)" />
60
+ </Banner>
52
61
  <div class="spacer" />
53
62
  <div class="mb-10">
54
63
  <h2><t k="gatekeeperIndex.violations" /></h2>
@@ -0,0 +1,238 @@
1
+ import { handleKubeApiHeaderWarnings } from '@shell/plugins/steve/header-warnings';
2
+ import { DEFAULT_PERF_SETTING } from '@shell/config/settings';
3
+
4
+ describe('steve: header-warnings', () => {
5
+ // eslint-disable-next-line jest/no-hooks
6
+
7
+ function setupMocks(settings = {
8
+ separator: '299 - ',
9
+ notificationBlockList: ['299 - unknown field']
10
+ }) {
11
+ return {
12
+ dispatch: jest.fn(),
13
+ consoleWarn: jest.spyOn(console, 'warn').mockImplementation(),
14
+ consoleDebug: jest.spyOn(console, 'debug').mockImplementation(),
15
+ rootGetter: {
16
+ 'management/byId': (resource: string, id: string): { value: string} => {
17
+ return {
18
+ value: JSON.stringify({
19
+ ...DEFAULT_PERF_SETTING,
20
+ kubeAPI: { warningHeader: settings }
21
+ })
22
+ };
23
+ },
24
+ 'i18n/t': (key: string, { resourceType }: any) => {
25
+ return `${ key }_${ resourceType }`;
26
+ }
27
+ }
28
+ };
29
+ }
30
+
31
+ function createMockRes( headers = {}, data = { type: inputResourceType }) {
32
+ return {
33
+ headers, config: { url: 'unit/test' }, data
34
+ };
35
+ }
36
+
37
+ function createGrowlResponse(message: string, action = updateKey) {
38
+ return [
39
+ 'growl/warning',
40
+ {
41
+ message, timeout: 0, title: `${ action }_${ inputResourceType }`
42
+ },
43
+ { root: true }
44
+ ];
45
+ }
46
+
47
+ function createConsoleResponse(warnings: string) {
48
+ return `Validation Warnings for unit/test\n\n${ warnings }`;
49
+ }
50
+
51
+ const inputResourceType = 'abc';
52
+ const updateKey = 'growl.kubeApiHeaderWarning.titleUpdate';
53
+ const createKey = 'growl.kubeApiHeaderWarning.titleCreate';
54
+ const podSecurity = '299 - would violate PodSecurity "restricted:latest": unrestricted capabilities (container "container-0" must set securityContext.capabilities.drop=["ALL"]), runAsNonRoot != true (container "container-0" must not set securityContext.runAsNonRoot=false), seccompProfile (pod or container "container-0" must set securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost")';
55
+ const deprecated = "299 - i'm deprecated";
56
+ const validation = '299 - unknown field "spec.containers[0].__active"';
57
+
58
+ describe('no warnings', () => {
59
+ it('put, no header warning', () => {
60
+ const mocks = setupMocks();
61
+
62
+ handleKubeApiHeaderWarnings(createMockRes(),
63
+ mocks.dispatch,
64
+ mocks.rootGetter,
65
+ 'put',
66
+ true,
67
+ );
68
+
69
+ expect(mocks.dispatch).not.toHaveBeenCalled();
70
+ expect(mocks.consoleWarn).not.toHaveBeenCalled();
71
+ expect(mocks.consoleDebug).not.toHaveBeenCalled();
72
+ });
73
+
74
+ it('post, no header warning', () => {
75
+ const mocks = setupMocks();
76
+
77
+ handleKubeApiHeaderWarnings(createMockRes(),
78
+ mocks.dispatch,
79
+ mocks.rootGetter,
80
+ 'post',
81
+ true,
82
+ );
83
+
84
+ expect(mocks.dispatch).not.toHaveBeenCalled();
85
+ expect(mocks.consoleWarn).not.toHaveBeenCalled();
86
+ expect(mocks.consoleDebug).not.toHaveBeenCalled();
87
+ });
88
+
89
+ it('patch, no header warning', () => {
90
+ const mocks = setupMocks();
91
+
92
+ handleKubeApiHeaderWarnings(createMockRes(),
93
+ mocks.dispatch,
94
+ mocks.rootGetter,
95
+ 'patch',
96
+ true,
97
+ );
98
+
99
+ expect(mocks.dispatch).not.toHaveBeenCalled();
100
+ expect(mocks.consoleWarn).not.toHaveBeenCalled();
101
+ expect(mocks.consoleDebug).not.toHaveBeenCalled();
102
+ });
103
+
104
+ it('get, no header warning', () => {
105
+ const mocks = setupMocks();
106
+
107
+ handleKubeApiHeaderWarnings(createMockRes(),
108
+ mocks.dispatch,
109
+ mocks.rootGetter,
110
+ 'get',
111
+ true,
112
+ );
113
+
114
+ expect(mocks.dispatch).not.toHaveBeenCalled();
115
+ expect(mocks.consoleWarn).not.toHaveBeenCalled();
116
+ expect(mocks.consoleDebug).not.toHaveBeenCalled();
117
+ });
118
+
119
+ it('get, warnings', () => {
120
+ const mocks = setupMocks();
121
+
122
+ handleKubeApiHeaderWarnings(createMockRes( { warnings: ['erg'] }),
123
+ mocks.dispatch,
124
+ mocks.rootGetter,
125
+ 'get',
126
+ true,
127
+ );
128
+
129
+ expect(mocks.dispatch).not.toHaveBeenCalled();
130
+ expect(mocks.consoleWarn).not.toHaveBeenCalled();
131
+ expect(mocks.consoleDebug).not.toHaveBeenCalled();
132
+ });
133
+
134
+ it('blocked (via default config)', () => {
135
+ const mocks = setupMocks();
136
+
137
+ handleKubeApiHeaderWarnings(
138
+ createMockRes({ warning: validation }),
139
+ mocks.dispatch,
140
+ mocks.rootGetter,
141
+ 'put',
142
+ true
143
+ );
144
+
145
+ expect(mocks.dispatch).not.toHaveBeenCalled();
146
+ expect(mocks.consoleWarn).not.toHaveBeenCalled();
147
+ expect(mocks.consoleDebug).toHaveBeenCalledWith(createConsoleResponse(validation));
148
+ });
149
+
150
+ it('blocked (via custom config)', () => {
151
+ const mocks = setupMocks({
152
+ separator: '299 - ',
153
+ notificationBlockList: [deprecated]
154
+ });
155
+
156
+ handleKubeApiHeaderWarnings(
157
+ createMockRes({ warning: deprecated }),
158
+ mocks.dispatch,
159
+ mocks.rootGetter,
160
+ 'put',
161
+ true
162
+ );
163
+
164
+ expect(mocks.dispatch).not.toHaveBeenCalled();
165
+ expect(mocks.consoleWarn).not.toHaveBeenCalled();
166
+ expect(mocks.consoleDebug).toHaveBeenCalledWith(createConsoleResponse(deprecated));
167
+ });
168
+
169
+ it('multi blocked (via custom config)', () => {
170
+ const mocks = setupMocks({
171
+ separator: '299 - ',
172
+ notificationBlockList: [deprecated, podSecurity]
173
+ });
174
+
175
+ handleKubeApiHeaderWarnings(
176
+ createMockRes({ warning: `${ deprecated },${ podSecurity }` }),
177
+ mocks.dispatch,
178
+ mocks.rootGetter,
179
+ 'put',
180
+ true
181
+ );
182
+
183
+ expect(mocks.dispatch).not.toHaveBeenCalled();
184
+ expect(mocks.consoleWarn).not.toHaveBeenCalled();
185
+ expect(mocks.consoleDebug).toHaveBeenCalledWith(createConsoleResponse(`${ deprecated }\n${ podSecurity }`));
186
+ });
187
+ });
188
+
189
+ describe('warnings', () => {
190
+ it('deprecated', () => {
191
+ const mocks = setupMocks();
192
+
193
+ handleKubeApiHeaderWarnings(
194
+ createMockRes({ warning: deprecated }),
195
+ mocks.dispatch,
196
+ mocks.rootGetter,
197
+ 'put',
198
+ true,
199
+ );
200
+
201
+ expect(mocks.dispatch).toHaveBeenCalledWith(...createGrowlResponse(deprecated));
202
+ expect(mocks.consoleWarn).not.toHaveBeenCalled();
203
+ expect(mocks.consoleDebug).toHaveBeenCalledWith(createConsoleResponse(deprecated));
204
+ });
205
+
206
+ it('pod security', () => {
207
+ const mocks = setupMocks();
208
+
209
+ handleKubeApiHeaderWarnings(
210
+ createMockRes({ warning: podSecurity }),
211
+ mocks.dispatch,
212
+ mocks.rootGetter,
213
+ 'post',
214
+ true,
215
+ );
216
+
217
+ expect(mocks.dispatch).toHaveBeenCalledWith(...createGrowlResponse(podSecurity, createKey));
218
+ expect(mocks.consoleWarn).not.toHaveBeenCalled();
219
+ expect(mocks.consoleDebug).toHaveBeenCalledWith(createConsoleResponse(podSecurity));
220
+ });
221
+
222
+ it('deprecated & pod security', () => {
223
+ const mocks = setupMocks();
224
+
225
+ handleKubeApiHeaderWarnings(
226
+ createMockRes({ warning: `${ deprecated },${ podSecurity }` }),
227
+ mocks.dispatch,
228
+ mocks.rootGetter,
229
+ 'put',
230
+ true,
231
+ );
232
+
233
+ expect(mocks.dispatch).toHaveBeenCalledWith(...createGrowlResponse(`${ deprecated }, ${ podSecurity }` ));
234
+ expect(mocks.consoleWarn).not.toHaveBeenCalled();
235
+ expect(mocks.consoleDebug).toHaveBeenCalledWith(createConsoleResponse(`${ deprecated }\n${ podSecurity }`));
236
+ });
237
+ });
238
+ });
@@ -8,6 +8,7 @@ import isObject from 'lodash/isObject';
8
8
  import { classify } from '@shell/plugins/dashboard-store/classify';
9
9
  import { NAMESPACE } from '@shell/config/types';
10
10
  import jsyaml from 'js-yaml';
11
+ import { handleKubeApiHeaderWarnings } from '@shell/plugins/steve/header-warnings';
11
12
 
12
13
  export default {
13
14
 
@@ -85,7 +86,7 @@ export default {
85
86
 
86
87
  while (true) {
87
88
  try {
88
- const out = await makeRequest(this, opt);
89
+ const out = await makeRequest(this, opt, rootGetters);
89
90
 
90
91
  if (!opt.depaginate) {
91
92
  return out;
@@ -116,7 +117,7 @@ export default {
116
117
  }
117
118
  }
118
119
 
119
- function makeRequest(that, opt) {
120
+ function makeRequest(that, opt, rootGetters) {
120
121
  return that.$axios(opt).then((res) => {
121
122
  let out;
122
123
 
@@ -128,9 +129,7 @@ export default {
128
129
 
129
130
  finishDeferred(key, 'resolve', out);
130
131
 
131
- if (opt.method === 'post' || opt.method === 'put') {
132
- handleValidationWarnings(res);
133
- }
132
+ handleKubeApiHeaderWarnings(res, dispatch, rootGetters, opt.method);
134
133
 
135
134
  return out;
136
135
  });
@@ -196,24 +195,6 @@ export default {
196
195
 
197
196
  return Promise.reject(out);
198
197
  }
199
-
200
- function handleValidationWarnings(res) {
201
- const warnings = (res.headers?.warning || '').split(',');
202
-
203
- if (!warnings.length || !warnings[0]) {
204
- return;
205
- }
206
-
207
- const message = warnings.reduce((message, warning) => {
208
- return `${ message }\n${ warning.trim() }`;
209
- }, `Validation Warnings for ${ opt.url }\n`);
210
-
211
- if (process.env.dev) {
212
- console.warn(`${ message }\n\n`, res.data); // eslint-disable-line no-console
213
- } else {
214
- console.debug(message); // eslint-disable-line no-console
215
- }
216
- }
217
198
  },
218
199
 
219
200
  promptMove({ commit, state }, resources) {
@@ -0,0 +1,91 @@
1
+ import { PerfSettingsWarningHeaders } from '@shell/config/settings';
2
+ import { getPerformanceSetting } from '@shell/utils/settings';
3
+
4
+ interface HttpResponse {
5
+ headers?: { [key: string]: string},
6
+ data?: any,
7
+ config: {
8
+ url: string,
9
+ }
10
+ }
11
+
12
+ /**
13
+ * Cache the kube api warning header settings that will determine if they are growled or not
14
+ */
15
+ let warningHeaderSettings: PerfSettingsWarningHeaders;
16
+
17
+ /**
18
+ * Extract sanitised warnings from the warnings header string
19
+ */
20
+ function kubeApiHeaderWarnings(allWarnings: string): string[] {
21
+ // Find each warning.
22
+ // Each warning is separated by `,`... however... this can appear within the warning itself so can't `split` on it
23
+ // Instead provide a configurable way to split (default 299 - )
24
+ const warnings = allWarnings.split(warningHeaderSettings.separator) || [];
25
+
26
+ // Trim and remove effects of split
27
+ return warnings.reduce((res, warning) => {
28
+ const trimmedWarning = warning.trim();
29
+
30
+ if (!trimmedWarning) {
31
+ return res;
32
+ }
33
+
34
+ const fixedWarning = trimmedWarning.endsWith(',') ? trimmedWarning.slice(0, -1) : trimmedWarning;
35
+
36
+ // Why add the separator again? It's almost certainly `299 - ` which is important info to include
37
+ res.push(warningHeaderSettings.separator + fixedWarning);
38
+
39
+ return res;
40
+ }, [] as string[]);
41
+ }
42
+
43
+ /**
44
+ * Take action given the `warnings` in the response header of a kube api request
45
+ */
46
+ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
47
+ export function handleKubeApiHeaderWarnings(res: HttpResponse, dispatch: any, rootGetters: any, method: string, refreshCache = false): void {
48
+ const safeMethod = method?.toLowerCase(); // Some requests have this as uppercase
49
+
50
+ // Exit early if there's no warnings
51
+ if ((safeMethod !== 'post' && safeMethod !== 'put') || !res.headers?.warning) {
52
+ return;
53
+ }
54
+
55
+ // Grab the required settings
56
+ if (!warningHeaderSettings || refreshCache) {
57
+ const settings = getPerformanceSetting(rootGetters);
58
+
59
+ // Cache this, we don't need to react to changes within the same session
60
+ warningHeaderSettings = settings?.kubeAPI.warningHeader;
61
+ }
62
+
63
+ // Determine each warning
64
+ const sanitisedWarnings = kubeApiHeaderWarnings(res.headers?.warning);
65
+
66
+ if (!sanitisedWarnings.length) {
67
+ return;
68
+ }
69
+
70
+ // Shows warnings as growls
71
+ const growlWarnings = sanitisedWarnings.filter((w) => !warningHeaderSettings.notificationBlockList.find((blocked) => w.startsWith(blocked)));
72
+
73
+ if (growlWarnings.length) {
74
+ const resourceType = res.data?.type || res.data?.kind || rootGetters['i18n/t']('generic.resource', { count: 1 });
75
+
76
+ dispatch('growl/warning', {
77
+ title: method === 'put' ? rootGetters['i18n/t']('growl.kubeApiHeaderWarning.titleUpdate', { resourceType }) : rootGetters['i18n/t']('growl.kubeApiHeaderWarning.titleCreate', { resourceType }),
78
+ message: growlWarnings.join(', '),
79
+ timeout: 0,
80
+ }, { root: true });
81
+ }
82
+
83
+ // Print warnings to console
84
+ const message = `Validation Warnings for ${ res.config.url }\n\n${ sanitisedWarnings.join('\n') }`;
85
+
86
+ if (process.env.dev) {
87
+ console.warn(`${ message }\n\n`, res.data); // eslint-disable-line no-console
88
+ } else {
89
+ console.debug(message); // eslint-disable-line no-console
90
+ }
91
+ }
@@ -71,9 +71,12 @@ export default {
71
71
  names() {
72
72
  return this.filteredNamespaces.map((obj) => obj.nameDisplay).slice(0, 5);
73
73
  },
74
- // Only admins and cluster owners can see namespaces outside of projects
75
- canSeeProjectlessNamespaces() {
76
- return this.currentCluster.canUpdate;
74
+
75
+ canManageNamespaces() {
76
+ // Only admins and cluster owners can see namespaces outside of projects
77
+ // BUT cluster members can also manage projects and namespaces and may want to not delete the namespaces associated with the project
78
+ // as per https://github.com/rancher/dashboard/issues/9517 despite the namespaces cannot be seen afterwards (projectless)
79
+ return this.currentCluster.canUpdate || (this.currentProject.canDelete && this.filteredNamespaces.length && this.filteredNamespaces[0]?.canDelete);
77
80
  }
78
81
  },
79
82
  methods: {
@@ -81,7 +84,7 @@ export default {
81
84
  remove() {
82
85
  // Delete all of thre namespaces and return false - this tells the prompt remove dialog to continue and delete the project
83
86
  // Delete all namespaces if the user wouldn't be able to see them after deleting the project
84
- if (this.deleteProjectNamespaces || !this.canSeeProjectlessNamespaces) {
87
+ if (this.deleteProjectNamespaces || !this.canManageNamespaces) {
85
88
  return Promise.all(this.filteredNamespaces.map((n) => n.remove())).then(() => false);
86
89
  }
87
90
 
@@ -97,7 +100,7 @@ export default {
97
100
  <div>
98
101
  <div class="mb-10">
99
102
  {{ t('promptRemove.attemptingToRemove', { type }) }} <span class="display-name">{{ `${displayName}.` }}</span>
100
- <template v-if="!canSeeProjectlessNamespaces">
103
+ <template v-if="!canManageNamespaces">
101
104
  <span class="delete-warning"> {{ t('promptRemove.willDeleteAssociatedNamespaces') }}</span> <br>
102
105
  <div
103
106
  v-clean-html="resourceNames(names, plusMore, t)"
@@ -106,7 +109,7 @@ export default {
106
109
  </template>
107
110
  </div>
108
111
  <div
109
- v-if="filteredNamespaces.length > 0 && canSeeProjectlessNamespaces"
112
+ v-if="filteredNamespaces.length > 0 && canManageNamespaces"
110
113
  class="mt-20 remove-project-dialog"
111
114
  >
112
115
  <Checkbox
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env bash
2
+
3
+ GITHUB_RELEASE_TAG=$1
4
+ GITHUB_RUN_ID=$2
5
+ GITHUB_WORKFLOW_TYPE=$3
6
+
7
+ # Check packages for released tag name
8
+ if [[ "${GITHUB_WORKFLOW_TYPE}" == "container" ]]; then
9
+ for d in pkg/*/ ; do
10
+ pkg=$(basename $d)
11
+
12
+ PKG_VERSION=$(jq -r .version pkg/${pkg}/package.json)
13
+ PKG_NAME="${pkg}-${PKG_VERSION}"
14
+
15
+ if [[ "${GITHUB_RELEASE_TAG}" == "${PKG_NAME}" ]]; then
16
+ gh run cancel ${GITHUB_RUN_ID}
17
+ else
18
+ continue
19
+ fi
20
+ done
21
+ else
22
+ # Check base extension name for tag name
23
+ BASE_EXT=$(jq -r .name package.json)
24
+ EXT_VERSION=$(jq -r .version package.json)
25
+
26
+ if [[ "${GITHUB_RELEASE_TAG}" == "${BASE_EXT}-${EXT_VERSION}" ]]; then
27
+ echo -e "tag: ${GITHUB_RELEASE_TAG}"
28
+ gh run cancel ${GITHUB_RUN_ID}
29
+ fi
30
+ fi
@@ -2003,6 +2003,7 @@ export namespace AUTH_TYPE {
2003
2003
  const _SSH: string;
2004
2004
  const _S3: string;
2005
2005
  }
2006
+ export const LOCAL_CLUSTER: "local";
2006
2007
  }
2007
2008
 
2008
2009
  // @shell/mixins/create-edit-view/impl
package/utils/settings.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import { MANAGEMENT } from '@shell/config/types';
2
2
  import { Store } from 'vuex';
3
- import { DEFAULT_PERF_SETTING, SETTING } from '@shell/config/settings';
4
- import { GC_PREFERENCES } from '@shell/utils/gc/gc-types';
3
+ import { DEFAULT_PERF_SETTING, PerfSettings, SETTING } from '@shell/config/settings';
5
4
 
6
5
  export const fetchOrCreateSetting = async(store: Store<any>, id: string, val: string, save = true): Promise<any> => {
7
6
  let setting;
@@ -44,21 +43,7 @@ export const setSetting = async(store: Store<any>, id: string, val: string): Pro
44
43
  return setting;
45
44
  };
46
45
 
47
- export const getPerformanceSetting = (rootGetters: Record<string, (arg0: string, arg1: string) => any>): {
48
- inactivity: {
49
- enabled: boolean;
50
- threshold: number;
51
- };
52
- incrementalLoading: {
53
- enabled: boolean;
54
- threshold: number;
55
- };
56
- manualRefresh: {};
57
- disableWebsocketNotification: boolean;
58
- garbageCollection: GC_PREFERENCES;
59
- forceNsFilterV2: any;
60
- advancedWorker: {};
61
- } => {
46
+ export const getPerformanceSetting = (rootGetters: Record<string, (arg0: string, arg1: string) => any>): PerfSettings => {
62
47
  const perfSettingResource = rootGetters['management/byId'](MANAGEMENT.SETTING, SETTING.UI_PERFORMANCE);
63
48
  let perfSetting = {};
64
49