@rancher/shell 0.1.3 → 0.1.21
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/brand/suse/dark/rancher-logo.svg +1 -148
- package/assets/brand/suse/favicon.png +0 -0
- package/assets/brand/suse/rancher-logo.svg +1 -130
- package/assets/images/featured/img1.jpg +0 -0
- package/assets/images/featured.jpg +0 -0
- package/assets/images/generic-plugin.svg +1 -0
- package/assets/styles/themes/_dark.scss +3 -0
- package/assets/styles/themes/_light.scss +3 -0
- package/assets/styles/themes/_suse.scss +1 -1
- package/assets/translations/en-us.yaml +219 -47
- package/assets/translations/zh-hans.yaml +21 -24
- package/components/AsyncButton.vue +17 -2
- package/components/ButtonDropdown.vue +4 -0
- package/components/Carousel.vue +291 -0
- package/components/CommunityLinks.vue +64 -22
- package/components/CruResource.vue +11 -3
- package/components/Dialog.vue +102 -0
- package/components/ExplorerMembers.vue +2 -4
- package/components/ExplorerProjectsNamespaces.vue +25 -9
- package/components/IconMessage.vue +9 -1
- package/components/LazyImage.vue +21 -8
- package/components/LocaleSelector.vue +62 -29
- package/components/PromptRemove.vue +2 -2
- package/components/ResourceList/Masthead.vue +21 -1
- package/components/ResourceList/ResourceLoadingIndicator.vue +0 -8
- package/components/ResourceList/index.vue +9 -23
- package/components/ResourceTable.vue +7 -2
- package/components/SimpleBox.vue +6 -4
- package/components/SortableTable/index.vue +18 -25
- package/components/Tabbed/Tab.vue +5 -0
- package/components/Tabbed/index.vue +54 -9
- package/components/TypeDescription.vue +10 -1
- package/components/auth/Principal.vue +1 -0
- package/components/fleet/FleetBundles.vue +8 -3
- package/components/fleet/FleetClusters.vue +6 -0
- package/components/fleet/FleetRepos.vue +7 -1
- package/components/fleet/FleetSummary.vue +6 -0
- package/components/form/Command.vue +5 -0
- package/components/form/EnvVars.vue +5 -0
- package/components/form/KeyValue.vue +80 -58
- package/components/form/NameNsDescription.vue +13 -5
- package/components/form/NodeScheduling.vue +6 -1
- package/components/form/PodAffinity.vue +5 -0
- package/components/form/ResourceTabs/index.vue +5 -1
- package/components/form/ServiceNameSelect.vue +5 -0
- package/components/form/ValueFromResource.vue +7 -1
- package/components/formatter/ClusterLink.vue +3 -7
- package/components/nav/NamespaceFilter.vue +3 -3
- package/components/nav/TopLevelMenu.vue +12 -29
- package/config/home-links.js +155 -0
- package/config/labels-annotations.js +2 -1
- package/config/private-label.js +1 -1
- package/config/product/explorer.js +5 -4
- package/config/product/legacy.js +0 -47
- package/config/product/manager.js +0 -2
- package/config/product/multi-cluster-apps.js +0 -12
- package/config/product/settings.js +12 -1
- package/config/product/uiplugins.js +17 -0
- package/config/settings.js +23 -2
- package/config/types.js +5 -1
- package/config/uiplugins.js +117 -0
- package/config/version.js +17 -0
- package/content/docs/en-us/getting-started.md +1 -26
- package/core/plugin.ts +12 -0
- package/core/plugins.js +38 -2
- package/core/types.ts +6 -0
- package/creators/app/{.eslintignore → files/.eslintignore} +0 -0
- package/creators/app/{.eslintrc.js → files/.eslintrc.js} +0 -0
- package/creators/app/{.vscode → files/.vscode}/settings.json +0 -0
- package/creators/app/{babel.config.js → files/babel.config.js} +0 -0
- package/creators/app/{nuxt.config.js → files/nuxt.config.js} +0 -0
- package/creators/app/{tsconfig.json → files/tsconfig.json} +2 -1
- package/creators/app/init +16 -17
- package/creators/app/package.json +6 -0
- package/creators/pkg/{babel.config.js → files/babel.config.js} +0 -0
- package/creators/pkg/{index.ts → files/index.ts} +0 -0
- package/creators/pkg/{tsconfig.json → files/tsconfig.json} +13 -12
- package/creators/pkg/{vue.config.js → files/vue.config.js} +0 -0
- package/creators/pkg/init +1 -1
- package/creators/update/init +54 -0
- package/creators/update/package.json +20 -0
- package/creators/update/upgrade +56 -0
- package/creators/update/yarn-error.log +54 -0
- package/detail/provisioning.cattle.io.cluster.vue +3 -3
- package/detail/workload/index.vue +3 -2
- package/dialog/DiagnosticTimingsDialog.vue +116 -0
- package/dialog/RotateCertificatesDialog.vue +9 -3
- package/edit/auth/azuread.vue +28 -9
- package/edit/networking.k8s.io.ingress/index.vue +2 -2
- package/edit/persistentvolume/index.vue +51 -13
- package/edit/persistentvolumeclaim.vue +31 -13
- package/edit/pod.vue +27 -0
- package/edit/provisioning.cattle.io.cluster/rke2.vue +103 -24
- package/edit/service.vue +7 -5
- package/edit/workload/__tests__/Upgrading.test.ts +1 -0
- package/edit/workload/index.vue +32 -10
- package/edit/workload/mixins/workload.js +121 -126
- package/edit/workload/storage/ContainerMountPaths.vue +240 -0
- package/edit/workload/storage/Mount.vue +1 -0
- package/edit/workload/storage/awsElasticBlockStore.vue +20 -1
- package/edit/workload/storage/azureDisk.vue +22 -2
- package/edit/workload/storage/azureFile.vue +20 -2
- package/edit/workload/storage/csi/index.vue +23 -1
- package/edit/workload/storage/gcePersistentDisk.vue +20 -2
- package/edit/workload/storage/index.vue +33 -65
- package/edit/workload/storage/persistentVolumeClaim/index.vue +5 -0
- package/edit/workload/storage/secret.vue +6 -1
- package/edit/workload/storage/vsphereVolume.vue +11 -1
- package/layouts/default.vue +14 -8
- package/layouts/home.vue +9 -4
- package/layouts/plain.vue +10 -5
- package/list/catalog.cattle.io.app.vue +10 -9
- package/list/catalog.cattle.io.clusterrepo.vue +6 -61
- package/list/cis.cattle.io.clusterscan.vue +12 -12
- package/list/fleet.cattle.io.bundle.vue +33 -28
- package/list/fleet.cattle.io.cluster.vue +26 -22
- package/list/fleet.cattle.io.clustergroup.vue +6 -0
- package/list/fleet.cattle.io.clusterregistrationtoken.vue +28 -24
- package/list/fleet.cattle.io.gitrepo.vue +25 -14
- package/list/helm.cattle.io.projecthelmchart.vue +52 -33
- package/list/logging.banzaicloud.io.clusterflow.vue +7 -12
- package/list/logging.banzaicloud.io.flow.vue +7 -14
- package/list/management.cattle.io.cluster.vue +26 -15
- package/list/management.cattle.io.feature.vue +13 -8
- package/list/management.cattle.io.setting.vue +3 -3
- package/list/management.cattle.io.user.vue +38 -19
- package/list/monitoring.coreos.com.alertmanagerconfig.vue +8 -15
- package/list/namespace.vue +14 -1
- package/list/node.vue +13 -16
- package/list/persistentvolume.vue +16 -9
- package/list/persistentvolumeclaim.vue +5 -8
- package/list/provisioning.cattle.io.cluster.vue +35 -9
- package/list/service.vue +24 -12
- package/list/ui.cattle.io.navlink.vue +6 -0
- package/list/workload.vue +2 -2
- package/machine-config/harvester.vue +5 -3
- package/middleware/authenticated.js +6 -0
- package/mixins/resource-fetch.js +12 -18
- package/mixins/resource-manager.js +126 -0
- package/models/catalog.cattle.io.uiplugin.js +38 -0
- package/models/cluster/node.js +25 -2
- package/models/fleet.cattle.io.bundle.js +1 -1
- package/models/harvesterhci.io.management.cluster.js +11 -5
- package/models/pod.js +15 -5
- package/models/provisioning.cattle.io.cluster.js +16 -6
- package/models/workload.js +5 -3
- package/models/workload.service.js +10 -0
- package/nuxt.config.js +70 -25
- package/package.json +108 -109
- package/pages/auth/login.vue +11 -1
- package/pages/auth/verify.vue +9 -0
- package/pages/c/_cluster/apps/charts/index.vue +46 -1
- package/pages/c/_cluster/apps/charts/install.vue +10 -9
- package/pages/c/_cluster/explorer/index.vue +72 -9
- package/pages/c/_cluster/explorer/tools/index.vue +12 -5
- package/pages/c/_cluster/mcapps/index.vue +1 -1
- package/pages/c/_cluster/settings/DefaultLinksEditor.vue +108 -0
- package/pages/c/_cluster/settings/brand.vue +0 -40
- package/pages/c/_cluster/settings/links.vue +152 -0
- package/pages/c/_cluster/settings/performance.vue +90 -7
- package/pages/c/_cluster/uiplugins/DeveloperInstallDialog.vue +232 -0
- package/pages/c/_cluster/uiplugins/InstallDialog.vue +293 -0
- package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +300 -0
- package/pages/c/_cluster/uiplugins/RemoveUIPlugins.vue +125 -0
- package/pages/c/_cluster/uiplugins/SetupUIPlugins.vue +261 -0
- package/pages/c/_cluster/uiplugins/UninstallDialog.vue +122 -0
- package/pages/c/_cluster/uiplugins/index.vue +808 -0
- package/pages/diagnostic.vue +185 -101
- package/pages/docs/_doc.vue +3 -1
- package/pages/home.vue +21 -56
- package/pages/prefs.vue +108 -88
- package/pages/safeMode.vue +17 -0
- package/pages/support/index.vue +34 -137
- package/pkg/dynamic-importer.lib.js +4 -0
- package/plugins/dashboard-store/actions.js +19 -0
- package/plugins/dashboard-store/getters.js +20 -3
- package/plugins/dashboard-store/mutations.js +13 -7
- package/plugins/dashboard-store/resource-class.js +2 -2
- package/plugins/formatters.js +15 -0
- package/plugins/plugin.js +61 -6
- package/plugins/steve/getters.js +12 -0
- package/plugins/steve/mutations.js +1 -1
- package/plugins/steve/subscribe.js +94 -72
- package/plugins/steve/web-worker.steve-sub-worker.js +24 -15
- package/plugins/version.js +21 -0
- package/promptRemove/management.cattle.io.globalrole.vue +47 -0
- package/promptRemove/management.cattle.io.roletemplate.vue +47 -0
- package/promptRemove/mixin/roleDeletionCheck.js +97 -0
- package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +6 -7
- package/rancher-components/components/BadgeState/BadgeState.spec.ts +12 -0
- package/rancher-components/components/BadgeState/BadgeState.vue +107 -0
- package/rancher-components/components/BadgeState/index.ts +1 -0
- package/rancher-components/components/Banner/Banner.test.ts +13 -0
- package/rancher-components/components/Banner/Banner.vue +163 -0
- package/rancher-components/components/Banner/index.ts +1 -0
- package/rancher-components/components/Card/Card.vue +150 -0
- package/rancher-components/components/Card/index.ts +1 -0
- package/rancher-components/components/Form/Checkbox/Checkbox.test.ts +77 -0
- package/rancher-components/components/Form/Checkbox/Checkbox.vue +395 -0
- package/rancher-components/components/Form/Checkbox/index.ts +1 -0
- package/rancher-components/components/Form/LabeledInput/LabeledInput.test.ts +29 -0
- package/rancher-components/components/Form/LabeledInput/LabeledInput.vue +343 -0
- package/rancher-components/components/Form/LabeledInput/index.ts +1 -0
- package/rancher-components/components/Form/Radio/RadioButton.vue +270 -0
- package/rancher-components/components/Form/Radio/RadioGroup.vue +235 -0
- package/rancher-components/components/Form/Radio/index.ts +2 -0
- package/rancher-components/components/Form/TextArea/TextAreaAutoGrow.vue +168 -0
- package/rancher-components/components/Form/TextArea/index.ts +1 -0
- package/rancher-components/components/Form/ToggleSwitch/ToggleSwitch.test.ts +107 -0
- package/rancher-components/components/Form/ToggleSwitch/ToggleSwitch.vue +137 -0
- package/rancher-components/components/Form/ToggleSwitch/index.ts +1 -0
- package/rancher-components/components/Form/index.ts +5 -0
- package/rancher-components/components/LabeledTooltip/LabeledTooltip.vue +137 -0
- package/rancher-components/components/LabeledTooltip/index.ts +1 -0
- package/scripts/publish-shell.sh +40 -7
- package/scripts/record-deps.js +37 -0
- package/scripts/sync-shell-deps +37 -0
- package/scripts/test-plugins-build.sh +8 -5
- package/scripts/typegen.sh +84 -0
- package/store/auth.js +3 -0
- package/store/catalog.js +9 -8
- package/store/i18n.js +10 -1
- package/store/index.js +12 -3
- package/store/prefs.js +16 -0
- package/store/type-map.js +32 -5
- package/store/uiplugins.ts +15 -61
- package/types/shell/index.d.ts +3046 -0
- package/utils/__tests__/object.test.ts +0 -24
- package/utils/__tests__/selector.test.ts +1 -1
- package/utils/dynamic-importer.js +4 -0
- package/utils/favicon.js +8 -2
- package/utils/gc/gc-interval.ts +40 -0
- package/utils/gc/gc-root-store.js +76 -0
- package/utils/gc/gc-route-changed.ts +44 -0
- package/utils/gc/gc-types.ts +21 -0
- package/utils/gc/gc.ts +282 -0
- package/utils/grafana.js +2 -6
- package/utils/socket.js +41 -20
- package/utils/string.js +1 -7
- package/utils/validators/formRules/__tests__/index.test.ts +108 -0
- package/utils/validators/formRules/index.ts +9 -1
- package/config/footer.js +0 -19
- package/creators/pkg/nuxt.config.js +0 -6
- package/pages/plugins.vue +0 -387
- package/server/verdaccio-middleware.js +0 -56
|
@@ -8,17 +8,35 @@ import { MODE, _IMPORT } from '@shell/config/query-params';
|
|
|
8
8
|
import { filterOnlyKubernetesClusters, filterHiddenLocalCluster } from '@shell/utils/cluster';
|
|
9
9
|
import { mapFeature, HARVESTER as HARVESTER_FEATURE } from '@shell/store/features';
|
|
10
10
|
import { NAME as EXPLORER } from '@shell/config/product/explorer';
|
|
11
|
+
import ResourceFetch from '@shell/mixins/resource-fetch';
|
|
11
12
|
|
|
12
13
|
export default {
|
|
13
14
|
components: {
|
|
14
15
|
Banner, ResourceTable, Masthead
|
|
15
16
|
},
|
|
17
|
+
mixins: [ResourceFetch],
|
|
18
|
+
props: {
|
|
19
|
+
loadResources: {
|
|
20
|
+
type: Array,
|
|
21
|
+
default: () => []
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
loadIndeterminate: {
|
|
25
|
+
type: Boolean,
|
|
26
|
+
default: false
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
incrementalLoadingIndicator: {
|
|
30
|
+
type: Boolean,
|
|
31
|
+
default: false
|
|
32
|
+
},
|
|
33
|
+
},
|
|
16
34
|
|
|
17
35
|
async fetch() {
|
|
18
36
|
const hash = {
|
|
37
|
+
rancherClusters: this.$fetchType(CAPI.RANCHER_CLUSTER),
|
|
19
38
|
normanClusters: this.$store.dispatch('rancher/findAll', { type: NORMAN.CLUSTER }),
|
|
20
39
|
mgmtClusters: this.$store.dispatch('management/findAll', { type: MANAGEMENT.CLUSTER }),
|
|
21
|
-
rancherClusters: this.$store.dispatch('management/findAll', { type: CAPI.RANCHER_CLUSTER }),
|
|
22
40
|
};
|
|
23
41
|
|
|
24
42
|
if ( this.$store.getters['management/canList'](SNAPSHOT) ) {
|
|
@@ -50,7 +68,6 @@ export default {
|
|
|
50
68
|
const res = await allHash(hash);
|
|
51
69
|
|
|
52
70
|
this.mgmtClusters = res.mgmtClusters;
|
|
53
|
-
this.rancherClusters = res.rancherClusters;
|
|
54
71
|
},
|
|
55
72
|
|
|
56
73
|
data() {
|
|
@@ -58,19 +75,18 @@ export default {
|
|
|
58
75
|
resource: CAPI.RANCHER_CLUSTER,
|
|
59
76
|
schema: this.$store.getters['management/schemaFor'](CAPI.RANCHER_CLUSTER),
|
|
60
77
|
mgmtClusters: [],
|
|
61
|
-
rancherClusters: [],
|
|
62
78
|
};
|
|
63
79
|
},
|
|
64
80
|
|
|
65
81
|
computed: {
|
|
66
|
-
|
|
82
|
+
filteredRows() {
|
|
67
83
|
// If Harvester feature is enabled, hide Harvester Clusters
|
|
68
84
|
if (this.harvesterEnabled) {
|
|
69
|
-
return filterHiddenLocalCluster(filterOnlyKubernetesClusters(this.
|
|
85
|
+
return filterHiddenLocalCluster(filterOnlyKubernetesClusters(this.rows), this.$store);
|
|
70
86
|
}
|
|
71
87
|
|
|
72
88
|
// Otherwise, show Harvester clusters - these will be shown with a warning
|
|
73
|
-
return filterHiddenLocalCluster(this.
|
|
89
|
+
return filterHiddenLocalCluster(this.rows, this.$store);
|
|
74
90
|
},
|
|
75
91
|
|
|
76
92
|
hiddenHarvesterCount() {
|
|
@@ -82,7 +98,7 @@ export default {
|
|
|
82
98
|
return 0;
|
|
83
99
|
}
|
|
84
100
|
|
|
85
|
-
return this.
|
|
101
|
+
return this.rows.length - filterOnlyKubernetesClusters(this.rows).length;
|
|
86
102
|
},
|
|
87
103
|
|
|
88
104
|
createLocation() {
|
|
@@ -115,6 +131,13 @@ export default {
|
|
|
115
131
|
harvesterEnabled: mapFeature(HARVESTER_FEATURE),
|
|
116
132
|
},
|
|
117
133
|
|
|
134
|
+
$loadingResources() {
|
|
135
|
+
return {
|
|
136
|
+
loadResources: [CAPI.RANCHER_CLUSTER],
|
|
137
|
+
loadIndeterminate: true, // results are filtered so we wouldn't get the correct count on indicator...
|
|
138
|
+
};
|
|
139
|
+
},
|
|
140
|
+
|
|
118
141
|
mounted() {
|
|
119
142
|
window.c = this;
|
|
120
143
|
},
|
|
@@ -130,6 +153,9 @@ export default {
|
|
|
130
153
|
:resource="resource"
|
|
131
154
|
:create-location="createLocation"
|
|
132
155
|
component-testid="cluster-manager-list"
|
|
156
|
+
:show-incremental-loading-indicator="incrementalLoadingIndicator"
|
|
157
|
+
:load-resources="loadResources"
|
|
158
|
+
:load-indeterminate="loadIndeterminate"
|
|
133
159
|
>
|
|
134
160
|
<template v-if="canImport" slot="extraActions">
|
|
135
161
|
<n-link
|
|
@@ -142,14 +168,14 @@ export default {
|
|
|
142
168
|
</template>
|
|
143
169
|
</Masthead>
|
|
144
170
|
|
|
145
|
-
<ResourceTable :schema="schema" :rows="
|
|
171
|
+
<ResourceTable :schema="schema" :rows="filteredRows" :namespaced="false" :loading="loading">
|
|
146
172
|
<template #cell:summary="{row}">
|
|
147
173
|
<span v-if="!row.stateParts.length">{{ row.nodes.length }}</span>
|
|
148
174
|
</template>
|
|
149
175
|
<template #cell:explorer="{row}">
|
|
150
176
|
<span v-if="row.mgmt && row.mgmt.isHarvester"></span>
|
|
151
177
|
<n-link
|
|
152
|
-
v-else-if="row.mgmt && row.mgmt.isReady"
|
|
178
|
+
v-else-if="row.mgmt && row.mgmt.isReady && !row.hasError"
|
|
153
179
|
data-testid="cluster-manager-list-explore-management"
|
|
154
180
|
class="btn btn-sm role-secondary"
|
|
155
181
|
:to="{name: 'c-cluster', params: {cluster: row.mgmt.id}}"
|
package/list/service.vue
CHANGED
|
@@ -1,12 +1,25 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import ResourceTable from '@shell/components/ResourceTable';
|
|
3
|
-
import Loading from '@shell/components/Loading';
|
|
4
3
|
import { NODE } from '@shell/config/types';
|
|
5
4
|
import { allHash } from '@shell/utils/promise';
|
|
5
|
+
import ResourceFetch from '@shell/mixins/resource-fetch';
|
|
6
6
|
|
|
7
7
|
export default {
|
|
8
8
|
name: 'ListService',
|
|
9
|
-
components: {
|
|
9
|
+
components: { ResourceTable },
|
|
10
|
+
mixins: [ResourceFetch],
|
|
11
|
+
props: {
|
|
12
|
+
resource: {
|
|
13
|
+
type: String,
|
|
14
|
+
required: true,
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
schema: {
|
|
18
|
+
type: Object,
|
|
19
|
+
required: true,
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
|
|
10
23
|
// fetch nodes before loading this page, as they may be referenced in the Target table column
|
|
11
24
|
async fetch() {
|
|
12
25
|
const store = this.$store;
|
|
@@ -21,23 +34,22 @@ export default {
|
|
|
21
34
|
}
|
|
22
35
|
} catch {}
|
|
23
36
|
|
|
24
|
-
const hash = { rows:
|
|
37
|
+
const hash = { rows: this.$fetchType(this.resource) };
|
|
25
38
|
|
|
26
39
|
if (hasNodes) {
|
|
27
40
|
hash.nodes = store.dispatch(`${ inStore }/findAll`, { type: NODE });
|
|
28
41
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
this.rows = res.rows;
|
|
32
|
-
},
|
|
33
|
-
|
|
34
|
-
data() {
|
|
35
|
-
return { rows: [] };
|
|
42
|
+
await allHash(hash);
|
|
36
43
|
}
|
|
37
44
|
};
|
|
38
45
|
</script>
|
|
39
46
|
|
|
40
47
|
<template>
|
|
41
|
-
<
|
|
42
|
-
|
|
48
|
+
<ResourceTable
|
|
49
|
+
:schema="schema"
|
|
50
|
+
:rows="rows"
|
|
51
|
+
:headers="$attrs.headers"
|
|
52
|
+
:group-by="$attrs.groupBy"
|
|
53
|
+
:loading="loading"
|
|
54
|
+
/>
|
|
43
55
|
</template>
|
|
@@ -16,6 +16,11 @@ export default {
|
|
|
16
16
|
type: Array,
|
|
17
17
|
required: true,
|
|
18
18
|
},
|
|
19
|
+
|
|
20
|
+
loading: {
|
|
21
|
+
type: Boolean,
|
|
22
|
+
required: false,
|
|
23
|
+
},
|
|
19
24
|
},
|
|
20
25
|
|
|
21
26
|
computed: { ...mapGetters(['clusterId']) }
|
|
@@ -26,6 +31,7 @@ export default {
|
|
|
26
31
|
<ResourceTable
|
|
27
32
|
:schema="schema"
|
|
28
33
|
:rows="rows"
|
|
34
|
+
:loading="loading"
|
|
29
35
|
>
|
|
30
36
|
<template #cell:to="{row}">
|
|
31
37
|
<template v-if="row.spec && row.spec.toService">
|
package/list/workload.vue
CHANGED
|
@@ -76,7 +76,7 @@ export default {
|
|
|
76
76
|
return schema;
|
|
77
77
|
},
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
filteredRows() {
|
|
80
80
|
const out = [];
|
|
81
81
|
|
|
82
82
|
for ( const typeRows of this.resources ) {
|
|
@@ -151,5 +151,5 @@ export default {
|
|
|
151
151
|
</script>
|
|
152
152
|
|
|
153
153
|
<template>
|
|
154
|
-
<ResourceTable :loading="$fetchState.pending" :schema="schema" :rows="
|
|
154
|
+
<ResourceTable :loading="$fetchState.pending" :schema="schema" :rows="filteredRows" :overflow-y="true" />
|
|
155
155
|
</template>
|
|
@@ -170,8 +170,10 @@ export default {
|
|
|
170
170
|
};
|
|
171
171
|
});
|
|
172
172
|
|
|
173
|
-
(res.namespaces.value.data || []).forEach((namespace) => {
|
|
174
|
-
|
|
173
|
+
(res.namespaces.value.data || []).forEach(async(namespace) => {
|
|
174
|
+
const proxyNamespace = await this.$store.dispatch('cluster/create', namespace);
|
|
175
|
+
|
|
176
|
+
if (!proxyNamespace.isSystem && namespace.links.update) {
|
|
175
177
|
const value = namespace.metadata.name;
|
|
176
178
|
const label = namespace.metadata.name;
|
|
177
179
|
|
|
@@ -519,7 +521,7 @@ export default {
|
|
|
519
521
|
:options="imageOptions"
|
|
520
522
|
:required="true"
|
|
521
523
|
:searchable="true"
|
|
522
|
-
:disabled="
|
|
524
|
+
:disabled="disabled"
|
|
523
525
|
label-key="cluster.credential.harvester.image"
|
|
524
526
|
:placeholder="t('cluster.harvester.machinePool.image.placeholder')"
|
|
525
527
|
@on-open="onOpen"
|
|
@@ -234,6 +234,8 @@ export default async function({
|
|
|
234
234
|
}
|
|
235
235
|
}
|
|
236
236
|
}
|
|
237
|
+
|
|
238
|
+
store.dispatch('gcStartIntervals');
|
|
237
239
|
}
|
|
238
240
|
|
|
239
241
|
if (!process.server) {
|
|
@@ -245,6 +247,10 @@ export default async function({
|
|
|
245
247
|
window.location.href = backTo;
|
|
246
248
|
}
|
|
247
249
|
}
|
|
250
|
+
|
|
251
|
+
// GC should be notified of route change before any find/get request is made that might be used for that page
|
|
252
|
+
store.dispatch('gcRouteChanged', route);
|
|
253
|
+
|
|
248
254
|
// Load stuff
|
|
249
255
|
await applyProducts(store, $plugin);
|
|
250
256
|
// Setup a beforeEach hook once to keep track of the current product
|
package/mixins/resource-fetch.js
CHANGED
|
@@ -1,26 +1,10 @@
|
|
|
1
1
|
import { mapGetters } from 'vuex';
|
|
2
|
-
import {
|
|
3
|
-
COUNT, MANAGEMENT, POD, WORKLOAD_TYPES, WORKLOAD, SECRET
|
|
4
|
-
} from '@shell/config/types';
|
|
2
|
+
import { COUNT, MANAGEMENT } from '@shell/config/types';
|
|
5
3
|
import { SETTING, DEFAULT_PERF_SETTING } from '@shell/config/settings';
|
|
6
4
|
|
|
7
5
|
// Number of pages to fetch when loading incrementally
|
|
8
6
|
const PAGES = 4;
|
|
9
7
|
|
|
10
|
-
// restrict advanced features of manual refresh and incremental loading to these resource types
|
|
11
|
-
export const TYPES_RESTRICTED = [
|
|
12
|
-
SECRET,
|
|
13
|
-
POD,
|
|
14
|
-
WORKLOAD_TYPES.DEPLOYMENT,
|
|
15
|
-
WORKLOAD_TYPES.CRON_JOB,
|
|
16
|
-
WORKLOAD_TYPES.DAEMON_SET,
|
|
17
|
-
WORKLOAD_TYPES.JOB,
|
|
18
|
-
WORKLOAD_TYPES.STATEFUL_SET,
|
|
19
|
-
WORKLOAD_TYPES.REPLICA_SET,
|
|
20
|
-
WORKLOAD_TYPES.REPLICATION_CONTROLLER,
|
|
21
|
-
WORKLOAD
|
|
22
|
-
];
|
|
23
|
-
|
|
24
8
|
export default {
|
|
25
9
|
data() {
|
|
26
10
|
// fetching the settings related to manual refresh from global settings
|
|
@@ -66,7 +50,17 @@ export default {
|
|
|
66
50
|
}
|
|
67
51
|
},
|
|
68
52
|
|
|
69
|
-
computed: {
|
|
53
|
+
computed: {
|
|
54
|
+
...mapGetters({ refreshFlag: 'resource-fetch/refreshFlag' }),
|
|
55
|
+
rows() {
|
|
56
|
+
const inStore = this.$store.getters['currentStore'](this.resource);
|
|
57
|
+
|
|
58
|
+
return this.$store.getters[`${ inStore }/all`](this.resource);
|
|
59
|
+
},
|
|
60
|
+
loading() {
|
|
61
|
+
return this.rows.length ? false : this.$fetchState.pending;
|
|
62
|
+
},
|
|
63
|
+
},
|
|
70
64
|
watch: {
|
|
71
65
|
refreshFlag(neu) {
|
|
72
66
|
// this is where the data assignment will trigger the update of the list view...
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { mapGetters } from 'vuex';
|
|
2
|
+
import { allHashSettled } from '@shell/utils/promise';
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
computed: { ...mapGetters(['currentCluster']) },
|
|
6
|
+
data() {
|
|
7
|
+
return { isLoadingSecondaryResources: false };
|
|
8
|
+
},
|
|
9
|
+
methods: {
|
|
10
|
+
/**
|
|
11
|
+
*
|
|
12
|
+
* Function resourceManagerFetchSecondaryResources
|
|
13
|
+
* This method is used to fetch what is called "secondary resources", which can be defined as resources that are needed to populate
|
|
14
|
+
* the page/component itself (ex: used as options on a Select) but don't need to be put into Vuex store or watched to get constant updates.
|
|
15
|
+
* This method allows to fetch resources for a given namespace to reduce the amount of results instead of needing to fetch all and filtering afterwards.
|
|
16
|
+
*
|
|
17
|
+
*
|
|
18
|
+
* @param {String} resourceData.namespace - Namespace identifier
|
|
19
|
+
* @param {Object} resourceData.data - Object containing info about the data needed to be fetched and how it should be parsed. Note: The KEY NEEDS to be the resource TYPE!
|
|
20
|
+
* @param {Array} resourceData.data[TYPE].applyTo - The array of operations needed to be performed for the specific data TYPE
|
|
21
|
+
* @param {String} resourceData.data[TYPE].applyTo[x].var - The 'this' property name that should be populated with the data fetched
|
|
22
|
+
* @param {Boolean} resourceData.data[TYPE].applyTo[x].classify - Whether the data fetched should have a model applied to it
|
|
23
|
+
* @param {Function} resourceData.data[TYPE].applyTo[x].parsingFunc - Optional parsing function if the fetched data needs to be parsed
|
|
24
|
+
* @param {Boolean} onlyNamespaced - Only fetch namespaced resources
|
|
25
|
+
*/
|
|
26
|
+
async resourceManagerFetchSecondaryResources(resourceData, onlyNamespaced = false) {
|
|
27
|
+
const requests = {};
|
|
28
|
+
const namespace = resourceData.namespace;
|
|
29
|
+
|
|
30
|
+
// Only fetch types if the user is allowed to...
|
|
31
|
+
Object.keys(resourceData.data).forEach((type) => {
|
|
32
|
+
const schema = this.$store.getters['cluster/schemaFor'](type);
|
|
33
|
+
|
|
34
|
+
if (schema) {
|
|
35
|
+
let url = schema.links.collection;
|
|
36
|
+
|
|
37
|
+
if (schema?.attributes?.namespaced && namespace) {
|
|
38
|
+
const parts = url.split('/');
|
|
39
|
+
|
|
40
|
+
parts.splice(parts.length - 2, 0, `api`);
|
|
41
|
+
parts.splice(parts.length - 1, 0, `namespaces/${ namespace }`);
|
|
42
|
+
url = parts.join('/');
|
|
43
|
+
} else if (onlyNamespaced) {
|
|
44
|
+
// Type isn't namespaced and we've been requested to only fetch namespaced types
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
requests[type] = this.$store.dispatch('cluster/request', { url });
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
if (Object.keys(requests).length) {
|
|
53
|
+
// this is the flag/variable that we need to apply to all places that rely on this data. Ex: LabeledSelect
|
|
54
|
+
this.isLoadingSecondaryResources = true;
|
|
55
|
+
const hash = await allHashSettled(requests);
|
|
56
|
+
const types = Object.keys(hash);
|
|
57
|
+
|
|
58
|
+
for (let i = 0; i < types.length; i++) {
|
|
59
|
+
const type = types[i];
|
|
60
|
+
const status = hash[type].status;
|
|
61
|
+
// if it's namespaced, we get the data on 'items' prop, for non-namespaced it's 'data' prop...
|
|
62
|
+
const requestData = hash[type].value.items || hash[type].value.data || hash[type].value;
|
|
63
|
+
const schema = this.$store.getters['cluster/schemaFor'](type);
|
|
64
|
+
|
|
65
|
+
if (status === 'fulfilled' && resourceData.data[type] && resourceData.data[type].applyTo?.length) {
|
|
66
|
+
for (let y = 0; y < resourceData.data[type].applyTo.length; y++) {
|
|
67
|
+
const apply = resourceData.data[type].applyTo[y];
|
|
68
|
+
let resources = requestData;
|
|
69
|
+
|
|
70
|
+
if (schema?.attributes?.namespaced) {
|
|
71
|
+
// The resources returned when requesting namespaced types do not contain id, type and links properties.
|
|
72
|
+
// This isn't perfect, or universally applicable, but will work for the current set of use cases
|
|
73
|
+
// To make this more generic
|
|
74
|
+
// - id param = this.$store.getters['cluster/keyFieldForType'](type)
|
|
75
|
+
// - id value = new dashboard-store getter, overwritten by steve store getter
|
|
76
|
+
requestData.forEach((item) => {
|
|
77
|
+
item.type = type;
|
|
78
|
+
item.id = `${ item.metadata.namespace }/${ item.metadata.name }`;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (apply.classify) {
|
|
83
|
+
resources = await this.$store.dispatch('cluster/createMany', requestData);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (apply.parsingFunc) {
|
|
87
|
+
this[apply.var] = apply.parsingFunc(resources);
|
|
88
|
+
} else {
|
|
89
|
+
this[apply.var] = resources;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
} else if (status === 'rejected') {
|
|
93
|
+
console.error(`Resource Manager - secondary data request for type ${ type } has failed`, status.error); // eslint-disable-line no-console
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
this.isLoadingSecondaryResources = false;
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Clear the cached secondary resources
|
|
103
|
+
*
|
|
104
|
+
* @param {*} resourceData See resourceManagerFetchSecondaryResources
|
|
105
|
+
* @param {*} onlyNamespaced Clear only namespaced resources
|
|
106
|
+
*/
|
|
107
|
+
resourceManagerClearSecondaryResources(resourceData, onlyNamespaced = false) {
|
|
108
|
+
Object.keys(resourceData.data).forEach((type) => {
|
|
109
|
+
const schema = this.$store.getters['cluster/schemaFor'](type);
|
|
110
|
+
|
|
111
|
+
if (schema) {
|
|
112
|
+
if (!schema?.attributes?.namespaced && onlyNamespaced) {
|
|
113
|
+
// resource isn't namespaced and we're only interested in namespaced resources
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
for (let y = 0; y < resourceData.data[type].applyTo.length; y++) {
|
|
118
|
+
const apply = resourceData.data[type].applyTo[y];
|
|
119
|
+
|
|
120
|
+
this[apply.var] = [];
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import SteveModel from '@shell/plugins/steve/steve-class';
|
|
2
|
+
|
|
3
|
+
const CACHED_STATUS = 'cached';
|
|
4
|
+
|
|
5
|
+
export default class UIPlugin extends SteveModel {
|
|
6
|
+
get name() {
|
|
7
|
+
return this.spec?.plugin?.name;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
get description() {
|
|
11
|
+
return this.spec?.plugin?.description;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
get version() {
|
|
15
|
+
return this.spec?.plugin?.version;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
get willBeCached() {
|
|
19
|
+
return this.spec?.plugin?.noCache === false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Has the plugin been cached?
|
|
23
|
+
get isCached() {
|
|
24
|
+
return !this.willBeCached || (this.willBeCached && this.status?.cacheState === CACHED_STATUS);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get pluginMetadata() {
|
|
28
|
+
return this.spec?.plugin?.metadata || {};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get isDeveloper() {
|
|
32
|
+
return this.pluginMetadata?.developer === 'true';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get plugin() {
|
|
36
|
+
return this.spec?.plugin || {};
|
|
37
|
+
}
|
|
38
|
+
}
|
package/models/cluster/node.js
CHANGED
|
@@ -153,6 +153,14 @@ export default class ClusterNode extends SteveModel {
|
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
get cpuUsage() {
|
|
156
|
+
/*
|
|
157
|
+
With EKS nodes that have been migrated from norman,
|
|
158
|
+
cpu/memory usage is by the annotation `management.cattle.io/pod-requests`
|
|
159
|
+
*/
|
|
160
|
+
if ( this.isFromNorman && this.provider === 'eks' ) {
|
|
161
|
+
return parseSi(this.podRequests.cpu || '0');
|
|
162
|
+
}
|
|
163
|
+
|
|
156
164
|
return parseSi(this.$rootGetters['cluster/byId'](METRIC.NODE, this.id)?.usage?.cpu || '0');
|
|
157
165
|
}
|
|
158
166
|
|
|
@@ -165,6 +173,10 @@ export default class ClusterNode extends SteveModel {
|
|
|
165
173
|
}
|
|
166
174
|
|
|
167
175
|
get ramUsage() {
|
|
176
|
+
if ( this.isFromNorman && this.provider === 'eks' ) {
|
|
177
|
+
return parseSi(this.podRequests.memory || '0');
|
|
178
|
+
}
|
|
179
|
+
|
|
168
180
|
return parseSi(this.$rootGetters['cluster/byId'](METRIC.NODE, this.id)?.usage?.memory || '0');
|
|
169
181
|
}
|
|
170
182
|
|
|
@@ -192,6 +204,10 @@ export default class ClusterNode extends SteveModel {
|
|
|
192
204
|
return this.pods.length;
|
|
193
205
|
}
|
|
194
206
|
|
|
207
|
+
get podRequests() {
|
|
208
|
+
return JSON.parse(this.metadata.annotations['management.cattle.io/pod-requests'] || '{}');
|
|
209
|
+
}
|
|
210
|
+
|
|
195
211
|
get isPidPressureOk() {
|
|
196
212
|
return this.isCondition('PIDPressure', 'False');
|
|
197
213
|
}
|
|
@@ -368,14 +384,13 @@ export default class ClusterNode extends SteveModel {
|
|
|
368
384
|
}
|
|
369
385
|
|
|
370
386
|
get canDelete() {
|
|
371
|
-
const provider = this.$rootGetters['currentCluster'].provisioner.toLowerCase();
|
|
372
387
|
const cloudProviders = [
|
|
373
388
|
'aks', 'azureaks', 'azurekubernetesservice',
|
|
374
389
|
'eks', 'amazoneks',
|
|
375
390
|
'gke', 'googlegke'
|
|
376
391
|
];
|
|
377
392
|
|
|
378
|
-
return !cloudProviders.includes(provider);
|
|
393
|
+
return !cloudProviders.includes(this.provider);
|
|
379
394
|
}
|
|
380
395
|
|
|
381
396
|
// You need to preload CAPI.MACHINEs to use this
|
|
@@ -389,6 +404,14 @@ export default class ClusterNode extends SteveModel {
|
|
|
389
404
|
|
|
390
405
|
return null;
|
|
391
406
|
}
|
|
407
|
+
|
|
408
|
+
get isFromNorman() {
|
|
409
|
+
return (this.$rootGetters['currentCluster'].metadata.labels || {})['cattle.io/creator'] === 'norman';
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
get provider() {
|
|
413
|
+
return this.$rootGetters['currentCluster'].provisioner.toLowerCase();
|
|
414
|
+
}
|
|
392
415
|
}
|
|
393
416
|
|
|
394
417
|
function calculatePercentage(allocatable, capacity) {
|
|
@@ -79,11 +79,11 @@ export default class HciCluster extends ProvCluster {
|
|
|
79
79
|
const pkgName = `${ HARVESTER_NAME }-1.0.3`;
|
|
80
80
|
|
|
81
81
|
if (uiOfflinePreferred === 'true') {
|
|
82
|
-
// Embedded (aka give me the
|
|
83
|
-
const embeddedPath =
|
|
82
|
+
// Embedded (aka give me the embedded plugin that was in the last rancher release)
|
|
83
|
+
const embeddedPath = `${ pkgName }/${ pkgName }.umd.min.js`;
|
|
84
84
|
|
|
85
85
|
return {
|
|
86
|
-
pkgUrl: process.env.dev ? `${ process.env.api }/${ embeddedPath }` : embeddedPath,
|
|
86
|
+
pkgUrl: process.env.dev ? `${ process.env.api }/dashboard/${ embeddedPath }` : embeddedPath,
|
|
87
87
|
pkgName
|
|
88
88
|
};
|
|
89
89
|
}
|
|
@@ -111,15 +111,21 @@ export default class HciCluster extends ProvCluster {
|
|
|
111
111
|
* Determine the harvester plugin's package name and url for clusters that provide the plugin
|
|
112
112
|
*/
|
|
113
113
|
_supportedClusterPkgDetails(uiInfo, clusterId) {
|
|
114
|
-
|
|
114
|
+
let pkgName = `${ HARVESTER_NAME }-${ uiInfo['ui-plugin-bundled-version'] }`;
|
|
115
115
|
const fileName = `${ pkgName }.umd.min.js`;
|
|
116
116
|
let pkgUrl;
|
|
117
117
|
|
|
118
118
|
if (uiInfo['ui-source'] === 'bundled' ) { // offline bundled
|
|
119
|
-
pkgUrl =
|
|
119
|
+
pkgUrl = `/k8s/clusters/${ clusterId }/v1/harvester/plugin-assets/${ fileName }`;
|
|
120
120
|
} else if (uiInfo['ui-source'] === 'external') {
|
|
121
121
|
if (uiInfo['ui-plugin-index']) {
|
|
122
122
|
pkgUrl = uiInfo['ui-plugin-index'];
|
|
123
|
+
|
|
124
|
+
// When using an external address, the pkgName should also be get from the url
|
|
125
|
+
const names = pkgUrl.split('/');
|
|
126
|
+
const jsName = names[names.length - 1];
|
|
127
|
+
|
|
128
|
+
pkgName = jsName?.split('.umd.min.js')[0];
|
|
123
129
|
} else {
|
|
124
130
|
throw new Error('Harvester cluster requested the plugin at `ui-plugin-index` is used, however did not provide a value for it');
|
|
125
131
|
}
|
package/models/pod.js
CHANGED
|
@@ -182,19 +182,29 @@ export default class Pod extends WorkloadService {
|
|
|
182
182
|
}
|
|
183
183
|
|
|
184
184
|
save() {
|
|
185
|
+
const prev = { ...this };
|
|
186
|
+
|
|
185
187
|
const { metadata, spec } = this.spec.template;
|
|
186
188
|
|
|
187
189
|
this.spec = {
|
|
188
190
|
...this.spec,
|
|
189
|
-
metadata: {
|
|
190
|
-
...this.metadata,
|
|
191
|
-
...metadata
|
|
192
|
-
},
|
|
193
191
|
...spec
|
|
194
192
|
};
|
|
195
193
|
|
|
194
|
+
this.metadata = {
|
|
195
|
+
...this.metadata,
|
|
196
|
+
...metadata
|
|
197
|
+
};
|
|
198
|
+
|
|
196
199
|
delete this.spec.template;
|
|
197
200
|
|
|
198
|
-
|
|
201
|
+
// IF there is an error POD world model get overwritten
|
|
202
|
+
// For the workloads this need be reset back
|
|
203
|
+
return this._save(...arguments).catch((e) => {
|
|
204
|
+
this.spec = prev.spec;
|
|
205
|
+
this.metadata = prev.metadata;
|
|
206
|
+
|
|
207
|
+
return Promise.reject(e);
|
|
208
|
+
});
|
|
199
209
|
}
|
|
200
210
|
}
|
|
@@ -197,12 +197,10 @@ export default class ProvCluster extends SteveModel {
|
|
|
197
197
|
return super.canEditYaml;
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
-
get
|
|
201
|
-
|
|
202
|
-
}
|
|
200
|
+
get isHostedKubernetesProvider() {
|
|
201
|
+
const providers = ['AKS', 'EKS', 'GKE'];
|
|
203
202
|
|
|
204
|
-
|
|
205
|
-
return this.provisioner === 'EKS';
|
|
203
|
+
return providers.includes(this.provisioner);
|
|
206
204
|
}
|
|
207
205
|
|
|
208
206
|
get isImported() {
|
|
@@ -230,13 +228,17 @@ export default class ProvCluster extends SteveModel {
|
|
|
230
228
|
get isImportedK3s() {
|
|
231
229
|
// As of Rancher v2.6.7, this returns false for imported K3s clusters,
|
|
232
230
|
// in which this.provisioner is `k3s`.
|
|
233
|
-
return this.isImported && this.
|
|
231
|
+
return this.isImported && this.isK3s;
|
|
234
232
|
}
|
|
235
233
|
|
|
236
234
|
get isImportedRke2() {
|
|
237
235
|
return this.isImported && this.mgmt?.status?.provider?.startsWith('rke2');
|
|
238
236
|
}
|
|
239
237
|
|
|
238
|
+
get isK3s() {
|
|
239
|
+
return this.mgmt?.status?.provider === 'k3s';
|
|
240
|
+
}
|
|
241
|
+
|
|
240
242
|
get isRke2() {
|
|
241
243
|
return !!this.spec?.rkeConfig;
|
|
242
244
|
}
|
|
@@ -677,6 +679,10 @@ export default class ProvCluster extends SteveModel {
|
|
|
677
679
|
}
|
|
678
680
|
|
|
679
681
|
get supportsWindows() {
|
|
682
|
+
if (this.isK3s || this.isImportedK3s) {
|
|
683
|
+
return false;
|
|
684
|
+
}
|
|
685
|
+
|
|
680
686
|
if ( this.isRke1 ) {
|
|
681
687
|
return this.mgmt?.spec?.windowsPreferedCluster || false;
|
|
682
688
|
}
|
|
@@ -764,4 +770,8 @@ export default class ProvCluster extends SteveModel {
|
|
|
764
770
|
await this.$dispatch('ws.resource.remove', { data: this });
|
|
765
771
|
}
|
|
766
772
|
}
|
|
773
|
+
|
|
774
|
+
get hasError() {
|
|
775
|
+
return this.status?.conditions?.some(condition => condition.error === true);
|
|
776
|
+
}
|
|
767
777
|
}
|