@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.
- package/assets/translations/en-us.yaml +4 -0
- package/assets/translations/zh-hans.yaml +8 -1
- package/cloud-credential/__tests__/azure.test.ts +53 -0
- package/cloud-credential/azure.vue +6 -0
- package/components/GrowlManager.vue +33 -30
- package/components/form/ResourceQuota/ProjectRow.vue +38 -15
- package/components/formatter/ClusterProvider.vue +9 -3
- package/components/formatter/__tests__/ClusterProvider.test.ts +5 -1
- package/components/nav/Header.vue +1 -0
- package/config/settings.ts +59 -2
- package/config/types.js +2 -0
- package/creators/pkg/files/.github/workflows/build-extension-catalog.yml +28 -0
- package/creators/pkg/files/.github/workflows/build-extension-charts.yml +26 -0
- package/creators/pkg/init +63 -4
- package/detail/provisioning.cattle.io.cluster.vue +4 -2
- package/edit/fleet.cattle.io.gitrepo.vue +1 -0
- package/edit/provisioning.cattle.io.cluster/rke2.vue +4 -4
- package/mixins/__tests__/chart.test.ts +40 -0
- package/mixins/chart.js +5 -0
- package/models/catalog.cattle.io.clusterrepo.js +6 -2
- package/models/fleet.cattle.io.cluster.js +10 -2
- package/package.json +1 -1
- package/pages/c/_cluster/gatekeeper/index.vue +10 -1
- package/plugins/steve/__tests__/header-warnings.spec.ts +238 -0
- package/plugins/steve/actions.js +4 -23
- package/plugins/steve/header-warnings.ts +91 -0
- package/promptRemove/management.cattle.io.project.vue +9 -6
- package/scripts/extension/parse-tag-name +30 -0
- package/types/shell/index.d.ts +1 -0
- package/utils/settings.ts +2 -17
- package/vue-config-helper.js +135 -0
- package/vue.config.js +23 -139
- package/yarn-error.log +200 -0
- package/creators/pkg/files/.github/workflows/build-container.yml +0 -64
- 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 (
|
|
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
|
@@ -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: {
|
|
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
|
+
});
|
package/plugins/steve/actions.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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.
|
|
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="!
|
|
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 &&
|
|
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
|
package/types/shell/index.d.ts
CHANGED
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
|
|