@rancher/shell 0.3.16 → 0.3.18
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/images/wechat-qr-code.jpg +0 -0
- package/assets/translations/en-us.yaml +75 -16
- package/assets/translations/zh-hans.yaml +151 -15
- package/chart/__tests__/S3.test.ts +50 -0
- package/chart/rancher-backup/S3.vue +21 -0
- package/chart/rancher-backup/index.vue +4 -0
- package/components/AsyncButton.vue +1 -1
- package/components/CommunityLinks.vue +1 -0
- package/components/FileDiff.vue +92 -85
- package/components/Inactivity.vue +10 -0
- package/components/LazyImage.vue +2 -2
- package/components/PromptRestore.vue +7 -5
- package/components/ResourceDetail/Masthead.vue +1 -1
- package/components/ResourceDetail/index.vue +8 -14
- package/components/ResourceList/index.vue +1 -1
- package/components/ResourceTable.vue +50 -2
- package/components/YamlEditor.vue +1 -0
- package/components/__tests__/PromptRestore.test.ts +72 -0
- package/components/auth/AzureWarning.vue +1 -1
- package/components/auth/RoleDetailEdit.vue +1 -0
- package/components/fleet/FleetResources.vue +3 -64
- package/components/form/FileImageSelector.vue +9 -0
- package/components/form/FileSelector.vue +2 -1
- package/components/form/MatchExpressions.vue +1 -3
- package/components/form/NameNsDescription.vue +28 -12
- package/components/form/NodeAffinity.vue +2 -2
- package/components/form/PodAffinity.vue +2 -2
- package/components/form/ResourceTabs/index.vue +8 -2
- package/components/form/Select.vue +16 -0
- package/components/form/__tests__/FileImageSelector.test.ts +42 -0
- package/components/form/__tests__/FileSelector.test.ts +76 -0
- package/components/form/__tests__/NodeAffinity.test.ts +38 -0
- package/components/form/__tests__/PodAffinity.test.ts +46 -0
- package/components/formatter/ClusterLink.vue +8 -4
- package/components/formatter/ClusterProvider.vue +3 -1
- package/components/formatter/ImageName.vue +23 -0
- package/components/formatter/PodImages.vue +7 -1
- package/components/formatter/__tests__/ClusterLink.test.ts +101 -0
- package/components/formatter/__tests__/ClusterProvider.test.ts +24 -0
- package/components/nav/Header.vue +2 -2
- package/components/nav/WindowManager/ContainerShell.vue +60 -36
- package/components/nav/WindowManager/__tests__/ContainerShell.test.ts +561 -0
- package/config/__test__/home-links.test.ts +62 -0
- package/config/home-links.js +15 -3
- package/config/labels-annotations.js +7 -2
- package/config/persistentVolume.ts +108 -0
- package/config/product/manager.js +5 -1
- package/config/router.js +0 -4
- package/config/settings.ts +4 -0
- package/config/table-headers.js +6 -5
- package/config/types.js +2 -0
- package/config/uiplugins.js +50 -5
- package/core/plugin-helpers.js +39 -15
- package/core/plugin.ts +9 -0
- package/core/plugins.js +1 -1
- package/core/types-provisioning.ts +253 -0
- package/core/types.ts +21 -3
- package/detail/autoscaling.horizontalpodautoscaler/index.vue +50 -1
- package/detail/fleet.cattle.io.gitrepo.vue +10 -2
- package/detail/node.vue +6 -6
- package/detail/pod.vue +38 -9
- package/detail/provisioning.cattle.io.cluster.vue +46 -7
- package/detail/workload/index.vue +49 -18
- package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +62 -0
- package/edit/__tests__/ui.cattle.io.navlink.test.ts +110 -0
- package/edit/auth/github.vue +1 -0
- package/edit/autoscaling.horizontalpodautoscaler/hpa-scaling-rule.vue +130 -0
- package/edit/autoscaling.horizontalpodautoscaler/index.vue +79 -0
- package/edit/fleet.cattle.io.clustergroup.vue +14 -3
- package/edit/fleet.cattle.io.gitrepo.vue +18 -1
- package/edit/namespace.vue +9 -1
- package/edit/networking.k8s.io.ingress/RulePath.vue +0 -2
- package/edit/persistentvolume/__tests__/persistentvolume.test.ts +82 -0
- package/edit/persistentvolume/index.vue +2 -1
- package/edit/persistentvolume/plugins/csi.vue +3 -1
- package/edit/persistentvolume/plugins/longhorn.vue +12 -12
- package/edit/provisioning.cattle.io.cluster/AgentConfiguration.vue +1 -30
- package/edit/provisioning.cattle.io.cluster/RegistryConfigs.vue +15 -11
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +79 -1
- package/edit/provisioning.cattle.io.cluster/index.vue +53 -1
- package/edit/provisioning.cattle.io.cluster/rke2.vue +335 -151
- package/edit/storage.k8s.io.storageclass/index.vue +1 -2
- package/edit/ui.cattle.io.navlink.vue +213 -186
- package/initialize/App.js +3 -13
- package/initialize/layouts.ts +26 -0
- package/layouts/default.vue +1 -1
- package/list/group.principal.vue +1 -1
- package/list/provisioning.cattle.io.cluster.vue +8 -1
- package/middleware/authenticated.js +101 -5
- package/mixins/brand.js +39 -3
- package/mixins/child-hook.js +2 -2
- package/mixins/create-edit-view/impl.js +4 -4
- package/models/chart.js +1 -1
- package/models/fleet.cattle.io.cluster.js +33 -4
- package/models/fleet.cattle.io.gitrepo.js +113 -38
- package/models/management.cattle.io.kontainerdriver.js +14 -0
- package/models/persistentvolume.js +2 -111
- package/models/pod.js +30 -0
- package/models/provisioning.cattle.io.cluster.js +9 -1
- package/models/rke.cattle.io.etcdsnapshot.js +10 -7
- package/package.json +2 -2
- package/pages/about.vue +8 -2
- package/pages/auth/login.vue +1 -1
- package/pages/auth/logout.vue +11 -3
- package/pages/c/_cluster/apps/charts/index.vue +5 -2
- package/pages/c/_cluster/apps/charts/install.vue +5 -0
- package/pages/c/_cluster/auth/group.principal/assign-edit.vue +1 -1
- package/pages/c/_cluster/auth/roles/index.vue +1 -1
- package/pages/c/_cluster/explorer/index.vue +2 -11
- package/pages/c/_cluster/manager/cloudCredential/_id.vue +0 -1
- package/pages/c/_cluster/manager/cloudCredential/create.vue +0 -1
- package/pages/c/_cluster/settings/brand.vue +11 -8
- package/pages/c/_cluster/uiplugins/AddExtensionRepos.vue +177 -0
- package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +19 -3
- package/pages/c/_cluster/uiplugins/RemoveUIPlugins.vue +90 -21
- package/pages/c/_cluster/uiplugins/SetupUIPlugins.vue +107 -37
- package/pages/c/_cluster/uiplugins/index.vue +160 -44
- package/pages/docs/_doc.vue +9 -3
- package/pages/home.vue +6 -6
- package/pages/support/index.vue +10 -4
- package/pkg/auto-import.js +1 -1
- package/plugins/clean-tooltip-directive.js +1 -1
- package/plugins/dashboard-store/__tests__/actions.spec.ts +165 -0
- package/plugins/dashboard-store/__tests__/getters.spec.ts +100 -0
- package/plugins/dashboard-store/__tests__/{mutations.spec.js → mutations.spec.ts} +2 -2
- package/plugins/dashboard-store/actions.js +1 -1
- package/plugins/dashboard-store/resource-class.js +39 -2
- package/plugins/plugin.js +9 -1
- package/plugins/steve/__tests__/getters.spec.ts +93 -0
- package/plugins/steve/getters.js +21 -1
- package/plugins/steve/subscribe.js +1 -3
- package/rancher-components/BadgeState/BadgeState.vue +5 -1
- package/rancher-components/Banner/Banner.test.ts +51 -1
- package/rancher-components/Banner/Banner.vue +134 -53
- package/rancher-components/Card/Card.test.ts +37 -0
- package/rancher-components/Card/Card.vue +24 -7
- package/rancher-components/Form/Checkbox/Checkbox.test.ts +20 -29
- package/rancher-components/Form/Checkbox/Checkbox.vue +45 -20
- package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +2 -8
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +22 -10
- package/rancher-components/Form/Radio/RadioButton.test.ts +31 -0
- package/rancher-components/Form/Radio/RadioButton.vue +30 -13
- package/rancher-components/Form/Radio/RadioGroup.vue +26 -7
- package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +7 -6
- package/rancher-components/Form/ToggleSwitch/ToggleSwitch.test.ts +25 -38
- package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +23 -11
- package/rancher-components/LabeledTooltip/LabeledTooltip.vue +19 -5
- package/rancher-components/StringList/StringList.test.ts +453 -49
- package/rancher-components/StringList/StringList.vue +44 -26
- package/scripts/extension/publish +2 -2
- package/scripts/typegen.sh +11 -2
- package/server/server-middleware.js +4 -12
- package/store/index.js +14 -3
- package/store/prefs.js +0 -3
- package/store/store-types.js +2 -0
- package/store/type-map.js +17 -29
- package/types/api.d.ts +1 -0
- package/types/fleet.d.ts +1 -0
- package/types/shell/index.d.ts +931 -85
- package/types/userPreferences.d.ts +1 -1
- package/utils/__mocks__/socket.js +21 -0
- package/utils/grafana.js +23 -11
- package/utils/kube.js +9 -0
- package/utils/object.js +27 -0
- package/utils/selector.js +2 -1
- package/utils/settings.ts +2 -2
- package/utils/validators/formRules/index.ts +3 -3
- package/vue.config.js +3 -2
- package/components/.DS_Store +0 -0
- package/components/__tests__/.DS_Store +0 -0
- package/creators/pkg/package-lock.json +0 -37
- package/pages/safeMode.vue +0 -17
- package/plugins/steve/urloptions.js +0 -47
- package/yarn-error.log +0 -196
|
@@ -26,6 +26,16 @@ const getPackageFromRoute = (route) => {
|
|
|
26
26
|
return arraySafe.find((m) => !!m.pkg)?.pkg;
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
+
const getResourceFromRoute = (to) => {
|
|
30
|
+
let resource = to.params?.resource;
|
|
31
|
+
|
|
32
|
+
if (!resource) {
|
|
33
|
+
resource = findMeta(to, 'resource');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return resource;
|
|
37
|
+
};
|
|
38
|
+
|
|
29
39
|
let beforeEachSetup = false;
|
|
30
40
|
|
|
31
41
|
function findMeta(route, key) {
|
|
@@ -71,9 +81,18 @@ export function getProductFromRoute(to) {
|
|
|
71
81
|
return product;
|
|
72
82
|
}
|
|
73
83
|
|
|
74
|
-
function setProduct(store, to) {
|
|
84
|
+
function setProduct(store, to, redirect) {
|
|
75
85
|
let product = getProductFromRoute(to);
|
|
76
86
|
|
|
87
|
+
// since all products are hardcoded as routes (ex: c-local-explorer), if we match the wildcard route it means that the product does not exist
|
|
88
|
+
if ((product && (!to.matched.length || (to.matched.length && to.matched[0].path === '/c/:cluster/:product'))) ||
|
|
89
|
+
// if the product grabbed from the route is not registered, then we don't have it!
|
|
90
|
+
(product && !store.getters['type-map/isProductRegistered'](product))) {
|
|
91
|
+
store.dispatch('loadingError', new Error(store.getters['i18n/t']('nav.failWhale.productNotFound', { productNotFound: product }, true)));
|
|
92
|
+
|
|
93
|
+
return () => redirect(302, '/fail-whale');
|
|
94
|
+
}
|
|
95
|
+
|
|
77
96
|
if ( !product ) {
|
|
78
97
|
product = EXPLORER;
|
|
79
98
|
}
|
|
@@ -92,6 +111,50 @@ function setProduct(store, to) {
|
|
|
92
111
|
// There might be management catalog items in it vs cluster.
|
|
93
112
|
store.commit('catalog/reset');
|
|
94
113
|
}
|
|
114
|
+
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Check that the resource is valid, if not redirect to fail whale
|
|
120
|
+
*
|
|
121
|
+
* This requires that
|
|
122
|
+
* - product is set
|
|
123
|
+
* - product's store is set and setup (so we can check schema's within it)
|
|
124
|
+
* - product's store has the schemaFor getter (extension stores might not have it)
|
|
125
|
+
* - there's a resource associated with route (meta or param)
|
|
126
|
+
*/
|
|
127
|
+
function invalidResource(store, to, redirect) {
|
|
128
|
+
const product = store.getters['currentProduct'];
|
|
129
|
+
const resource = getResourceFromRoute(to);
|
|
130
|
+
|
|
131
|
+
// In order to check a resource is valid we need these
|
|
132
|
+
if (!product || !resource) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Note - don't use the current products store... because products can override stores for resources with `typeStoreMap`
|
|
137
|
+
const inStore = store.getters['currentStore'](resource);
|
|
138
|
+
// There's a chance we're in an extension's product who's store could be anything, so confirm schemaFor exists
|
|
139
|
+
const schemaFor = store.getters[`${ inStore }/schemaFor`];
|
|
140
|
+
|
|
141
|
+
// In order to check a resource is valid we need these
|
|
142
|
+
if (!inStore || !schemaFor) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Resource is valid if a schema exists for it (standard resource, spoofed resource) or it's a virtual resource
|
|
147
|
+
const validResource = schemaFor(resource) || store.getters['type-map/isVirtual'](resource);
|
|
148
|
+
|
|
149
|
+
if (validResource) {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Unknown resource, redirect to fail whale
|
|
154
|
+
|
|
155
|
+
store.dispatch('loadingError', new Error(store.getters['i18n/t']('nav.failWhale.resourceNotFound', { resource }, true)));
|
|
156
|
+
|
|
157
|
+
return () => redirect(302, '/fail-whale');
|
|
95
158
|
}
|
|
96
159
|
|
|
97
160
|
export default async function({
|
|
@@ -281,19 +344,39 @@ export default async function({
|
|
|
281
344
|
store.dispatch('gcRouteChanged', route);
|
|
282
345
|
|
|
283
346
|
// Load stuff
|
|
347
|
+
let localCheckResource = false;
|
|
348
|
+
|
|
284
349
|
await applyProducts(store, $plugin);
|
|
350
|
+
|
|
285
351
|
// Setup a beforeEach hook once to keep track of the current product
|
|
286
352
|
if ( !beforeEachSetup ) {
|
|
287
353
|
beforeEachSetup = true;
|
|
354
|
+
// This only needs to happen when beforeEach hook hasn't run (the initial load)
|
|
355
|
+
localCheckResource = true;
|
|
288
356
|
|
|
289
357
|
store.app.router.beforeEach((to, from, next) => {
|
|
290
358
|
// NOTE - This beforeEach runs AFTER this middleware. So anything in this middleware that requires it must set it manually
|
|
291
|
-
setProduct(store, to);
|
|
359
|
+
let redirected = setProduct(store, to, redirect);
|
|
360
|
+
|
|
361
|
+
if (redirected) {
|
|
362
|
+
return redirected();
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
redirected = invalidResource(store, to, redirect);
|
|
366
|
+
|
|
367
|
+
if (redirected) {
|
|
368
|
+
return redirected();
|
|
369
|
+
}
|
|
370
|
+
|
|
292
371
|
next();
|
|
293
372
|
});
|
|
294
373
|
|
|
295
374
|
// Call it for the initial pageload
|
|
296
|
-
setProduct(store, route);
|
|
375
|
+
const redirected = setProduct(store, route, redirect);
|
|
376
|
+
|
|
377
|
+
if (redirected) {
|
|
378
|
+
return redirected();
|
|
379
|
+
}
|
|
297
380
|
|
|
298
381
|
if (process.client) {
|
|
299
382
|
store.app.router.afterEach((to, from) => {
|
|
@@ -376,7 +459,12 @@ export default async function({
|
|
|
376
459
|
// When fleet moves to it's own package this should be moved to pkg onEnter/onLeave
|
|
377
460
|
if ((oldProduct === FLEET_NAME || product === FLEET_NAME) && oldProduct !== product) {
|
|
378
461
|
// See note above for store.app.router.beforeEach, need to setProduct manually, for the moment do this in a targeted way
|
|
379
|
-
setProduct(store, route);
|
|
462
|
+
const redirected = setProduct(store, route, redirect);
|
|
463
|
+
|
|
464
|
+
if (redirected) {
|
|
465
|
+
return redirected();
|
|
466
|
+
}
|
|
467
|
+
|
|
380
468
|
store.commit('updateWorkspace', {
|
|
381
469
|
value: store.getters['prefs/get'](WORKSPACE) || DEFAULT_WORKSPACE,
|
|
382
470
|
getters: store.getters
|
|
@@ -397,6 +485,14 @@ export default async function({
|
|
|
397
485
|
})
|
|
398
486
|
]);
|
|
399
487
|
|
|
488
|
+
if (localCheckResource) {
|
|
489
|
+
const redirected = invalidResource(store, route, redirect);
|
|
490
|
+
|
|
491
|
+
if (redirected) {
|
|
492
|
+
return redirected();
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
400
496
|
if (!clusterId) {
|
|
401
497
|
clusterId = store.getters['defaultClusterId']; // This needs the cluster list, so no parallel
|
|
402
498
|
const isSingleProduct = store.getters['isSingleProduct'];
|
|
@@ -422,7 +518,7 @@ export default async function({
|
|
|
422
518
|
return redirect(302, '/home');
|
|
423
519
|
} else {
|
|
424
520
|
// Sets error 500 if lost connection to API
|
|
425
|
-
store.commit('setError', { error: e, locationError: new Error('
|
|
521
|
+
store.commit('setError', { error: e, locationError: new Error(store.getters['i18n/t']('nav.failWhale.authMiddleware')) });
|
|
426
522
|
|
|
427
523
|
return redirect(302, '/fail-whale');
|
|
428
524
|
}
|
package/mixins/brand.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { mapGetters } from 'vuex';
|
|
1
2
|
import { CATALOG, MANAGEMENT } from '@shell/config/types';
|
|
2
3
|
import { getVendor } from '@shell/config/private-label';
|
|
3
4
|
import { SETTING } from '@shell/config/settings';
|
|
@@ -5,6 +6,12 @@ import { findBy } from '@shell/utils/array';
|
|
|
5
6
|
import { createCssVars } from '@shell/utils/color';
|
|
6
7
|
import { _ALL_IF_AUTHED } from '@shell/plugins/dashboard-store/actions';
|
|
7
8
|
|
|
9
|
+
const cspAdaptorApp = ['rancher-csp-adapter', 'rancher-csp-billing-adapter'];
|
|
10
|
+
|
|
11
|
+
export const hasCspAdapter = (apps) => {
|
|
12
|
+
return apps?.find((a) => cspAdaptorApp.includes(a.metadata?.name));
|
|
13
|
+
};
|
|
14
|
+
|
|
8
15
|
export default {
|
|
9
16
|
async fetch() {
|
|
10
17
|
// For the login page, the schemas won't be loaded - we don't need the apps in this case
|
|
@@ -23,13 +30,19 @@ export default {
|
|
|
23
30
|
}
|
|
24
31
|
});
|
|
25
32
|
} catch (e) {}
|
|
33
|
+
|
|
34
|
+
// Setting this up front will remove `computed` churn, and we only care that we've initialised them
|
|
35
|
+
this.haveAppsAndSettings = !!this.apps && !!this.globalSettings;
|
|
26
36
|
},
|
|
27
37
|
|
|
28
38
|
data() {
|
|
29
|
-
return {
|
|
39
|
+
return {
|
|
40
|
+
apps: null, globalSettings: null, haveAppsAndSettings: null
|
|
41
|
+
};
|
|
30
42
|
},
|
|
31
43
|
|
|
32
44
|
computed: {
|
|
45
|
+
...mapGetters({ loggedIn: 'auth/loggedIn' }),
|
|
33
46
|
|
|
34
47
|
brand() {
|
|
35
48
|
const setting = findBy(this.globalSettings, 'id', SETTING.BRAND);
|
|
@@ -61,7 +74,23 @@ export default {
|
|
|
61
74
|
},
|
|
62
75
|
|
|
63
76
|
cspAdapter() {
|
|
64
|
-
|
|
77
|
+
if (!this.canCalcCspAdapter) {
|
|
78
|
+
// We only have a watch on cspAdapter to kick off persisting the brand setting.
|
|
79
|
+
// So we need to ensure we don't return an undefined here... which would match the undefined gave if no csp app was found...
|
|
80
|
+
// .. and wouldn't kick off the watcher
|
|
81
|
+
return '';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Note! this used to be `findBy(this.app)` however for that case we lost reactivity on the collection
|
|
85
|
+
// (computed fires before fetch, fetch happens and update apps, computed would not fire again - even with vue.set)
|
|
86
|
+
// So use `.find` in method instead
|
|
87
|
+
return hasCspAdapter(this.apps);
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
canCalcCspAdapter() {
|
|
91
|
+
// We need to take consider the loggedIn state, as the brand mixin is used in the logout page where we can be in a mixed state
|
|
92
|
+
// (things in store but user has no auth to make changes)
|
|
93
|
+
return this.loggedIn && this.haveAppsAndSettings;
|
|
65
94
|
}
|
|
66
95
|
},
|
|
67
96
|
|
|
@@ -90,7 +119,13 @@ export default {
|
|
|
90
119
|
},
|
|
91
120
|
|
|
92
121
|
cspAdapter(neu) {
|
|
122
|
+
if (!this.canCalcCspAdapter) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// The brand setting will only get updated if...
|
|
93
127
|
if (neu && !this.brand) {
|
|
128
|
+
// 1) There should be a brand... but there's no brand setting
|
|
94
129
|
const brandSetting = findBy(this.globalSettings, 'id', SETTING.BRAND);
|
|
95
130
|
|
|
96
131
|
if (brandSetting) {
|
|
@@ -109,7 +144,8 @@ export default {
|
|
|
109
144
|
} else if (!neu) {
|
|
110
145
|
const brandSetting = findBy(this.globalSettings, 'id', SETTING.BRAND);
|
|
111
146
|
|
|
112
|
-
if (brandSetting) {
|
|
147
|
+
if (brandSetting && brandSetting.value !== '') {
|
|
148
|
+
// 2) There should not be a brand... but there is a brand setting
|
|
113
149
|
brandSetting.value = '';
|
|
114
150
|
brandSetting.save();
|
|
115
151
|
}
|
package/mixins/child-hook.js
CHANGED
|
@@ -20,8 +20,8 @@ export default {
|
|
|
20
20
|
});
|
|
21
21
|
},
|
|
22
22
|
|
|
23
|
-
registerAfterHook(boundFn, name, priority) {
|
|
24
|
-
this._registerHook(AFTER_SAVE_HOOKS, boundFn, name, priority);
|
|
23
|
+
registerAfterHook(boundFn, name, priority = 99, boundFnContext) {
|
|
24
|
+
this._registerHook(AFTER_SAVE_HOOKS, boundFn, name, priority, boundFnContext);
|
|
25
25
|
},
|
|
26
26
|
|
|
27
27
|
async applyHooks(key, ...args) {
|
|
@@ -65,9 +65,9 @@ export default {
|
|
|
65
65
|
|
|
66
66
|
let name = this.$route.name;
|
|
67
67
|
|
|
68
|
-
if ( name
|
|
68
|
+
if ( name?.endsWith('-id') ) {
|
|
69
69
|
name = name.replace(/(-namespace)?-id$/, '');
|
|
70
|
-
} else if ( name
|
|
70
|
+
} else if ( name?.endsWith('-create') ) {
|
|
71
71
|
name = name.replace(/-create$/, '');
|
|
72
72
|
}
|
|
73
73
|
|
|
@@ -124,7 +124,7 @@ export default {
|
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
try {
|
|
127
|
-
await this.applyHooks(BEFORE_SAVE_HOOKS);
|
|
127
|
+
await this.applyHooks(BEFORE_SAVE_HOOKS, this.value);
|
|
128
128
|
|
|
129
129
|
// Remove the labels map if it's empty
|
|
130
130
|
if ( this.value?.metadata?.labels && Object.keys(this.value.metadata.labels || {}).length === 0 ) {
|
|
@@ -152,7 +152,7 @@ export default {
|
|
|
152
152
|
await this.$store.dispatch('cluster/findAll', { type: this.value.type, opt: { force: true } }, { root: true });
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
await this.applyHooks(AFTER_SAVE_HOOKS);
|
|
155
|
+
await this.applyHooks(AFTER_SAVE_HOOKS, this.value);
|
|
156
156
|
buttonDone && buttonDone(true);
|
|
157
157
|
|
|
158
158
|
this.done();
|
package/models/chart.js
CHANGED
|
@@ -2,7 +2,7 @@ import { compatibleVersionsFor } from '@shell/store/catalog';
|
|
|
2
2
|
import {
|
|
3
3
|
REPO_TYPE, REPO, CHART, VERSION, _FLAGGED, HIDE_SIDE_NAV
|
|
4
4
|
} from '@shell/config/query-params';
|
|
5
|
-
import { BLANK_CLUSTER } from '@shell/store';
|
|
5
|
+
import { BLANK_CLUSTER } from '@shell/store/store-types.js';
|
|
6
6
|
import SteveModel from '@shell/plugins/steve/steve-class';
|
|
7
7
|
|
|
8
8
|
export default class Chart extends SteveModel {
|
|
@@ -4,6 +4,7 @@ import { _RKE2 } from '@shell/store/prefs';
|
|
|
4
4
|
import SteveModel from '@shell/plugins/steve/steve-class';
|
|
5
5
|
import { escapeHtml } from '@shell/utils/string';
|
|
6
6
|
import { insertAt } from '@shell/utils/array';
|
|
7
|
+
import jsyaml from 'js-yaml';
|
|
7
8
|
|
|
8
9
|
export default class FleetCluster extends SteveModel {
|
|
9
10
|
get _availableActions() {
|
|
@@ -89,7 +90,7 @@ export default class FleetCluster extends SteveModel {
|
|
|
89
90
|
}
|
|
90
91
|
|
|
91
92
|
get state() {
|
|
92
|
-
if (
|
|
93
|
+
if (this.spec?.paused === true) {
|
|
93
94
|
return 'paused';
|
|
94
95
|
}
|
|
95
96
|
|
|
@@ -124,19 +125,47 @@ export default class FleetCluster extends SteveModel {
|
|
|
124
125
|
return mgmt;
|
|
125
126
|
}
|
|
126
127
|
|
|
127
|
-
get
|
|
128
|
-
const norman = this.$rootGetters['rancher/byId'](NORMAN.CLUSTER, this.metadata
|
|
128
|
+
get basicNorman() {
|
|
129
|
+
const norman = this.$rootGetters['rancher/byId'](NORMAN.CLUSTER, this.metadata?.labels?.[FLEET_LABELS.CLUSTER_NAME]);
|
|
129
130
|
|
|
130
131
|
return norman;
|
|
131
132
|
}
|
|
132
133
|
|
|
134
|
+
get norman() {
|
|
135
|
+
if (this.basicNorman) {
|
|
136
|
+
return this.basicNorman;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// If navigate to YAML view directly, norman is not loaded yet
|
|
140
|
+
return this.$dispatch('rancher/find', { type: NORMAN.CLUSTER, id: this.metadata.labels[FLEET_LABELS.CLUSTER_NAME] }, { root: true });
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async normanClone() {
|
|
144
|
+
const norman = await this.norman;
|
|
145
|
+
|
|
146
|
+
return this.$dispatch('rancher/clone', { resource: norman }, { root: true });
|
|
147
|
+
}
|
|
148
|
+
|
|
133
149
|
get groupByLabel() {
|
|
134
150
|
const name = this.metadata.namespace;
|
|
135
151
|
|
|
136
|
-
if (
|
|
152
|
+
if (name) {
|
|
137
153
|
return this.$rootGetters['i18n/t']('resourceTable.groupLabel.workspace', { name: escapeHtml(name) });
|
|
138
154
|
} else {
|
|
139
155
|
return this.$rootGetters['i18n/t']('resourceTable.groupLabel.notInAWorkspace');
|
|
140
156
|
}
|
|
141
157
|
}
|
|
158
|
+
|
|
159
|
+
async saveYaml(yaml) {
|
|
160
|
+
await this._saveYaml(yaml);
|
|
161
|
+
|
|
162
|
+
const parsed = jsyaml.load(yaml);
|
|
163
|
+
|
|
164
|
+
const norman = await this.normanClone();
|
|
165
|
+
|
|
166
|
+
norman.setLabels(parsed.metadata.labels);
|
|
167
|
+
norman.setAnnotations(parsed.metadata.annotations);
|
|
168
|
+
|
|
169
|
+
await norman.save();
|
|
170
|
+
}
|
|
142
171
|
}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { convert, matching, convertSelectorObj } from '@shell/utils/selector';
|
|
2
2
|
import jsyaml from 'js-yaml';
|
|
3
|
-
import { escapeHtml } from '@shell/utils/string';
|
|
3
|
+
import { escapeHtml, randomStr } from '@shell/utils/string';
|
|
4
4
|
import { FLEET } from '@shell/config/types';
|
|
5
5
|
import { FLEET as FLEET_ANNOTATIONS } from '@shell/config/labels-annotations';
|
|
6
6
|
import { addObject, addObjects, findBy, insertAt } from '@shell/utils/array';
|
|
7
7
|
import { set } from '@shell/utils/object';
|
|
8
8
|
import SteveModel from '@shell/plugins/steve/steve-class';
|
|
9
|
+
import { STATES_ENUM, colorForState, stateDisplay, stateSort } from '@shell/plugins/dashboard-store/resource-class';
|
|
10
|
+
import { NAME } from '@shell/config/product/explorer';
|
|
9
11
|
|
|
10
12
|
function quacksLikeAHash(str) {
|
|
11
|
-
if (
|
|
13
|
+
if (str.match(/^[a-f0-9]{40,}$/i)) {
|
|
12
14
|
return true;
|
|
13
15
|
}
|
|
14
16
|
|
|
@@ -24,12 +26,13 @@ export default class GitRepo extends SteveModel {
|
|
|
24
26
|
|
|
25
27
|
spec.repo = spec.repo || '';
|
|
26
28
|
|
|
27
|
-
if (
|
|
29
|
+
if (!spec.branch && !spec.revision) {
|
|
28
30
|
spec.branch = 'master';
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
spec.paths = spec.paths || [];
|
|
32
34
|
spec.clientSecretName = spec.clientSecretName || null;
|
|
35
|
+
spec.correctDrift = { enabled: false };
|
|
33
36
|
|
|
34
37
|
set(this, 'spec', spec);
|
|
35
38
|
set(this, 'metadata', meta);
|
|
@@ -85,7 +88,7 @@ export default class GitRepo extends SteveModel {
|
|
|
85
88
|
}
|
|
86
89
|
|
|
87
90
|
get state() {
|
|
88
|
-
if (
|
|
91
|
+
if (this.spec?.paused === true) {
|
|
89
92
|
return 'paused';
|
|
90
93
|
}
|
|
91
94
|
|
|
@@ -97,10 +100,10 @@ export default class GitRepo extends SteveModel {
|
|
|
97
100
|
const clusters = workspace?.clusters || [];
|
|
98
101
|
const groups = workspace?.clusterGroups || [];
|
|
99
102
|
|
|
100
|
-
if (
|
|
103
|
+
if (workspace?.id === 'fleet-local') {
|
|
101
104
|
const local = findBy(groups, 'id', 'fleet-local/default');
|
|
102
105
|
|
|
103
|
-
if (
|
|
106
|
+
if (local) {
|
|
104
107
|
return local.targetClusters;
|
|
105
108
|
}
|
|
106
109
|
|
|
@@ -113,30 +116,30 @@ export default class GitRepo extends SteveModel {
|
|
|
113
116
|
|
|
114
117
|
const out = [];
|
|
115
118
|
|
|
116
|
-
for (
|
|
117
|
-
if (
|
|
119
|
+
for (const tgt of this.spec.targets) {
|
|
120
|
+
if (tgt.clusterName) {
|
|
118
121
|
const cluster = findBy(clusters, 'metadata.name', tgt.clusterName);
|
|
119
122
|
|
|
120
|
-
if (
|
|
123
|
+
if (cluster) {
|
|
121
124
|
addObject(out, cluster);
|
|
122
125
|
}
|
|
123
|
-
} else if (
|
|
126
|
+
} else if (tgt.clusterGroup) {
|
|
124
127
|
const group = findBy(groups, {
|
|
125
128
|
'metadata.namespace': this.metadata.namespace,
|
|
126
129
|
'metadata.name': tgt.clusterGroup,
|
|
127
130
|
});
|
|
128
131
|
|
|
129
|
-
if (
|
|
132
|
+
if (group) {
|
|
130
133
|
addObjects(out, group.targetClusters);
|
|
131
134
|
}
|
|
132
|
-
} else if (
|
|
135
|
+
} else if (tgt.clusterGroupSelector) {
|
|
133
136
|
const expressions = convertSelectorObj(tgt.clusterGroupSelector);
|
|
134
137
|
const matchingGroups = matching(groups, expressions);
|
|
135
138
|
|
|
136
|
-
for (
|
|
139
|
+
for (const group of matchingGroups) {
|
|
137
140
|
addObjects(out, group.targetClusters);
|
|
138
141
|
}
|
|
139
|
-
} else if (
|
|
142
|
+
} else if (tgt.clusterSelector) {
|
|
140
143
|
const expressions = convertSelectorObj(tgt.clusterSelector);
|
|
141
144
|
const matchingClusters = matching(clusters, expressions);
|
|
142
145
|
|
|
@@ -150,7 +153,7 @@ export default class GitRepo extends SteveModel {
|
|
|
150
153
|
get github() {
|
|
151
154
|
const match = this.spec.repo.match(/^https?:\/\/github\.com\/(.*?)(\.git)?\/*$/);
|
|
152
155
|
|
|
153
|
-
if (
|
|
156
|
+
if (match) {
|
|
154
157
|
return match[1];
|
|
155
158
|
}
|
|
156
159
|
|
|
@@ -158,7 +161,7 @@ export default class GitRepo extends SteveModel {
|
|
|
158
161
|
}
|
|
159
162
|
|
|
160
163
|
get repoIcon() {
|
|
161
|
-
if (
|
|
164
|
+
if (this.github) {
|
|
162
165
|
return 'icon icon-github';
|
|
163
166
|
}
|
|
164
167
|
|
|
@@ -172,7 +175,7 @@ export default class GitRepo extends SteveModel {
|
|
|
172
175
|
repo = repo.replace(/^https:\/\//, '');
|
|
173
176
|
repo = repo.replace(/\/+$/, '');
|
|
174
177
|
|
|
175
|
-
if (
|
|
178
|
+
if (this.github) {
|
|
176
179
|
return this.github;
|
|
177
180
|
}
|
|
178
181
|
|
|
@@ -183,15 +186,15 @@ export default class GitRepo extends SteveModel {
|
|
|
183
186
|
const spec = this.spec;
|
|
184
187
|
const hash = this.status?.commit?.substr(0, 7);
|
|
185
188
|
|
|
186
|
-
if (
|
|
189
|
+
if (!spec || !spec.repo) {
|
|
187
190
|
return null;
|
|
188
191
|
}
|
|
189
192
|
|
|
190
|
-
if (
|
|
193
|
+
if (spec.revision && quacksLikeAHash(spec.revision)) {
|
|
191
194
|
return spec.revision.substr(0, 7);
|
|
192
|
-
} else if (
|
|
195
|
+
} else if (spec.revision) {
|
|
193
196
|
return spec.revision;
|
|
194
|
-
} else if (
|
|
197
|
+
} else if (spec.branch) {
|
|
195
198
|
return spec.branch + (hash ? ` @ ${ hash }` : '');
|
|
196
199
|
}
|
|
197
200
|
|
|
@@ -219,7 +222,7 @@ export default class GitRepo extends SteveModel {
|
|
|
219
222
|
|
|
220
223
|
advanced = jsyaml.dump(targets);
|
|
221
224
|
|
|
222
|
-
if (
|
|
225
|
+
if (advanced === '[]\n') {
|
|
223
226
|
advanced = `# - name:
|
|
224
227
|
# clusterSelector:
|
|
225
228
|
# matchLabels:
|
|
@@ -239,39 +242,39 @@ export default class GitRepo extends SteveModel {
|
|
|
239
242
|
`;
|
|
240
243
|
}
|
|
241
244
|
|
|
242
|
-
if (
|
|
245
|
+
if (this.metadata.namespace === 'fleet-local') {
|
|
243
246
|
mode = 'local';
|
|
244
|
-
} else if (
|
|
247
|
+
} else if (!targets.length) {
|
|
245
248
|
mode = 'none';
|
|
246
|
-
} else if (
|
|
249
|
+
} else if (targets.length === 1) {
|
|
247
250
|
const target = targets[0];
|
|
248
251
|
|
|
249
252
|
if (Object.keys(target).length > 1) {
|
|
250
253
|
// There are multiple properties in a single target, so use the 'advanced' mode
|
|
251
254
|
// (otherwise any existing content is nuked for what we provide)
|
|
252
255
|
mode = 'advanced';
|
|
253
|
-
} else if (
|
|
256
|
+
} else if (target.clusterGroup) {
|
|
254
257
|
clusterGroup = target.clusterGroup;
|
|
255
258
|
|
|
256
|
-
if (
|
|
259
|
+
if (!mode) {
|
|
257
260
|
mode = 'clusterGroup';
|
|
258
261
|
}
|
|
259
|
-
} else if (
|
|
262
|
+
} else if (target.clusterName) {
|
|
260
263
|
mode = 'cluster';
|
|
261
264
|
cluster = target.clusterName;
|
|
262
|
-
} else if (
|
|
263
|
-
if (
|
|
265
|
+
} else if (target.clusterSelector) {
|
|
266
|
+
if (Object.keys(target.clusterSelector).length === 0) {
|
|
264
267
|
mode = 'all';
|
|
265
268
|
} else {
|
|
266
269
|
const expressions = convert(target.clusterSelector.matchLabels, target.clusterSelector.matchExpressions);
|
|
267
270
|
|
|
268
|
-
if (
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
271
|
+
if (expressions.length === 1 &&
|
|
272
|
+
expressions[0].key === FLEET_ANNOTATIONS.CLUSTER_NAME &&
|
|
273
|
+
expressions[0].operator === 'In' &&
|
|
274
|
+
expressions[0].values.length === 1
|
|
272
275
|
) {
|
|
273
276
|
cluster = expressions[0].values[0];
|
|
274
|
-
if (
|
|
277
|
+
if (!mode) {
|
|
275
278
|
mode = 'cluster';
|
|
276
279
|
}
|
|
277
280
|
}
|
|
@@ -279,7 +282,7 @@ export default class GitRepo extends SteveModel {
|
|
|
279
282
|
}
|
|
280
283
|
}
|
|
281
284
|
|
|
282
|
-
if (
|
|
285
|
+
if (!mode) {
|
|
283
286
|
mode = 'advanced';
|
|
284
287
|
}
|
|
285
288
|
|
|
@@ -295,7 +298,7 @@ export default class GitRepo extends SteveModel {
|
|
|
295
298
|
get groupByLabel() {
|
|
296
299
|
const name = this.metadata.namespace;
|
|
297
300
|
|
|
298
|
-
if (
|
|
301
|
+
if (name) {
|
|
299
302
|
return this.$rootGetters['i18n/t']('resourceTable.groupLabel.workspace', { name: escapeHtml(name) });
|
|
300
303
|
} else {
|
|
301
304
|
return this.$rootGetters['i18n/t']('resourceTable.groupLabel.notInAWorkspace');
|
|
@@ -305,7 +308,7 @@ export default class GitRepo extends SteveModel {
|
|
|
305
308
|
get bundles() {
|
|
306
309
|
const all = this.$getters['all'](FLEET.BUNDLE);
|
|
307
310
|
|
|
308
|
-
return all.filter((bundle) => bundle.
|
|
311
|
+
return all.filter((bundle) => bundle.repoName === this.name &&
|
|
309
312
|
bundle.namespace === this.namespace &&
|
|
310
313
|
bundle.namespacedName.startsWith(`${ this.namespace }:${ this.name }`));
|
|
311
314
|
}
|
|
@@ -324,6 +327,78 @@ export default class GitRepo extends SteveModel {
|
|
|
324
327
|
return bds.filter((bd) => bd.metadata?.labels?.['fleet.cattle.io/repo-name'] === this.name);
|
|
325
328
|
}
|
|
326
329
|
|
|
330
|
+
get resourcesStatuses() {
|
|
331
|
+
const clusters = this.targetClusters || [];
|
|
332
|
+
const resources = this.status?.resources || [];
|
|
333
|
+
const conditions = this.status?.conditions || [];
|
|
334
|
+
|
|
335
|
+
const out = [];
|
|
336
|
+
|
|
337
|
+
for (const c of clusters) {
|
|
338
|
+
const clusterBundleDeploymentResources = this.bundleDeployments
|
|
339
|
+
.find((bd) => bd.metadata?.labels?.[FLEET_ANNOTATIONS.CLUSTER] === c.metadata.name)
|
|
340
|
+
?.status?.resources || [];
|
|
341
|
+
|
|
342
|
+
resources.forEach((r, i) => {
|
|
343
|
+
let namespacedName = r.name;
|
|
344
|
+
|
|
345
|
+
if (r.namespace) {
|
|
346
|
+
namespacedName = `${ r.namespace }:${ r.name }`;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
let state = r.state;
|
|
350
|
+
const perEntry = r.perClusterState?.find((x) => x.clusterId === c.id);
|
|
351
|
+
const tooMany = r.perClusterState?.length >= 10 || false;
|
|
352
|
+
|
|
353
|
+
if (perEntry) {
|
|
354
|
+
state = perEntry.state;
|
|
355
|
+
} else if (tooMany) {
|
|
356
|
+
state = STATES_ENUM.UNKNOWN;
|
|
357
|
+
} else {
|
|
358
|
+
state = STATES_ENUM.READY;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const color = colorForState(state).replace('text-', 'bg-');
|
|
362
|
+
const display = stateDisplay(state);
|
|
363
|
+
|
|
364
|
+
const detailLocation = {
|
|
365
|
+
name: `c-cluster-product-resource${ r.namespace ? '-namespace' : '' }-id`,
|
|
366
|
+
params: {
|
|
367
|
+
product: NAME,
|
|
368
|
+
cluster: c.metadata.labels[FLEET_ANNOTATIONS.CLUSTER_NAME],
|
|
369
|
+
resource: r.type,
|
|
370
|
+
namespace: r.namespace,
|
|
371
|
+
id: r.name,
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
out.push({
|
|
376
|
+
key: `${ r.id }-${ c.id }-${ r.type }-${ r.namespace }-${ r.name }`,
|
|
377
|
+
tableKey: `${ r.id }-${ c.id }-${ r.type }-${ r.namespace }-${ r.name }-${ randomStr(8) }`,
|
|
378
|
+
kind: r.kind,
|
|
379
|
+
apiVersion: r.apiVersion,
|
|
380
|
+
type: r.type,
|
|
381
|
+
id: r.id,
|
|
382
|
+
namespace: r.namespace,
|
|
383
|
+
name: r.name,
|
|
384
|
+
clusterId: c.id,
|
|
385
|
+
clusterName: c.nameDisplay,
|
|
386
|
+
state,
|
|
387
|
+
stateBackground: color,
|
|
388
|
+
stateDisplay: display,
|
|
389
|
+
stateSort: stateSort(color, display),
|
|
390
|
+
namespacedName,
|
|
391
|
+
detailLocation,
|
|
392
|
+
conditions: conditions[i],
|
|
393
|
+
bundleDeploymentStatus: clusterBundleDeploymentResources?.[i],
|
|
394
|
+
creationTimestamp: clusterBundleDeploymentResources?.[i]?.createdAt
|
|
395
|
+
});
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return out;
|
|
400
|
+
}
|
|
401
|
+
|
|
327
402
|
get clustersList() {
|
|
328
403
|
return this.$getters['all'](FLEET.CLUSTER);
|
|
329
404
|
}
|