@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.
Files changed (245) hide show
  1. package/assets/brand/suse/dark/rancher-logo.svg +1 -148
  2. package/assets/brand/suse/favicon.png +0 -0
  3. package/assets/brand/suse/rancher-logo.svg +1 -130
  4. package/assets/images/featured/img1.jpg +0 -0
  5. package/assets/images/featured.jpg +0 -0
  6. package/assets/images/generic-plugin.svg +1 -0
  7. package/assets/styles/themes/_dark.scss +3 -0
  8. package/assets/styles/themes/_light.scss +3 -0
  9. package/assets/styles/themes/_suse.scss +1 -1
  10. package/assets/translations/en-us.yaml +219 -47
  11. package/assets/translations/zh-hans.yaml +21 -24
  12. package/components/AsyncButton.vue +17 -2
  13. package/components/ButtonDropdown.vue +4 -0
  14. package/components/Carousel.vue +291 -0
  15. package/components/CommunityLinks.vue +64 -22
  16. package/components/CruResource.vue +11 -3
  17. package/components/Dialog.vue +102 -0
  18. package/components/ExplorerMembers.vue +2 -4
  19. package/components/ExplorerProjectsNamespaces.vue +25 -9
  20. package/components/IconMessage.vue +9 -1
  21. package/components/LazyImage.vue +21 -8
  22. package/components/LocaleSelector.vue +62 -29
  23. package/components/PromptRemove.vue +2 -2
  24. package/components/ResourceList/Masthead.vue +21 -1
  25. package/components/ResourceList/ResourceLoadingIndicator.vue +0 -8
  26. package/components/ResourceList/index.vue +9 -23
  27. package/components/ResourceTable.vue +7 -2
  28. package/components/SimpleBox.vue +6 -4
  29. package/components/SortableTable/index.vue +18 -25
  30. package/components/Tabbed/Tab.vue +5 -0
  31. package/components/Tabbed/index.vue +54 -9
  32. package/components/TypeDescription.vue +10 -1
  33. package/components/auth/Principal.vue +1 -0
  34. package/components/fleet/FleetBundles.vue +8 -3
  35. package/components/fleet/FleetClusters.vue +6 -0
  36. package/components/fleet/FleetRepos.vue +7 -1
  37. package/components/fleet/FleetSummary.vue +6 -0
  38. package/components/form/Command.vue +5 -0
  39. package/components/form/EnvVars.vue +5 -0
  40. package/components/form/KeyValue.vue +80 -58
  41. package/components/form/NameNsDescription.vue +13 -5
  42. package/components/form/NodeScheduling.vue +6 -1
  43. package/components/form/PodAffinity.vue +5 -0
  44. package/components/form/ResourceTabs/index.vue +5 -1
  45. package/components/form/ServiceNameSelect.vue +5 -0
  46. package/components/form/ValueFromResource.vue +7 -1
  47. package/components/formatter/ClusterLink.vue +3 -7
  48. package/components/nav/NamespaceFilter.vue +3 -3
  49. package/components/nav/TopLevelMenu.vue +12 -29
  50. package/config/home-links.js +155 -0
  51. package/config/labels-annotations.js +2 -1
  52. package/config/private-label.js +1 -1
  53. package/config/product/explorer.js +5 -4
  54. package/config/product/legacy.js +0 -47
  55. package/config/product/manager.js +0 -2
  56. package/config/product/multi-cluster-apps.js +0 -12
  57. package/config/product/settings.js +12 -1
  58. package/config/product/uiplugins.js +17 -0
  59. package/config/settings.js +23 -2
  60. package/config/types.js +5 -1
  61. package/config/uiplugins.js +117 -0
  62. package/config/version.js +17 -0
  63. package/content/docs/en-us/getting-started.md +1 -26
  64. package/core/plugin.ts +12 -0
  65. package/core/plugins.js +38 -2
  66. package/core/types.ts +6 -0
  67. package/creators/app/{.eslintignore → files/.eslintignore} +0 -0
  68. package/creators/app/{.eslintrc.js → files/.eslintrc.js} +0 -0
  69. package/creators/app/{.vscode → files/.vscode}/settings.json +0 -0
  70. package/creators/app/{babel.config.js → files/babel.config.js} +0 -0
  71. package/creators/app/{nuxt.config.js → files/nuxt.config.js} +0 -0
  72. package/creators/app/{tsconfig.json → files/tsconfig.json} +2 -1
  73. package/creators/app/init +16 -17
  74. package/creators/app/package.json +6 -0
  75. package/creators/pkg/{babel.config.js → files/babel.config.js} +0 -0
  76. package/creators/pkg/{index.ts → files/index.ts} +0 -0
  77. package/creators/pkg/{tsconfig.json → files/tsconfig.json} +13 -12
  78. package/creators/pkg/{vue.config.js → files/vue.config.js} +0 -0
  79. package/creators/pkg/init +1 -1
  80. package/creators/update/init +54 -0
  81. package/creators/update/package.json +20 -0
  82. package/creators/update/upgrade +56 -0
  83. package/creators/update/yarn-error.log +54 -0
  84. package/detail/provisioning.cattle.io.cluster.vue +3 -3
  85. package/detail/workload/index.vue +3 -2
  86. package/dialog/DiagnosticTimingsDialog.vue +116 -0
  87. package/dialog/RotateCertificatesDialog.vue +9 -3
  88. package/edit/auth/azuread.vue +28 -9
  89. package/edit/networking.k8s.io.ingress/index.vue +2 -2
  90. package/edit/persistentvolume/index.vue +51 -13
  91. package/edit/persistentvolumeclaim.vue +31 -13
  92. package/edit/pod.vue +27 -0
  93. package/edit/provisioning.cattle.io.cluster/rke2.vue +103 -24
  94. package/edit/service.vue +7 -5
  95. package/edit/workload/__tests__/Upgrading.test.ts +1 -0
  96. package/edit/workload/index.vue +32 -10
  97. package/edit/workload/mixins/workload.js +121 -126
  98. package/edit/workload/storage/ContainerMountPaths.vue +240 -0
  99. package/edit/workload/storage/Mount.vue +1 -0
  100. package/edit/workload/storage/awsElasticBlockStore.vue +20 -1
  101. package/edit/workload/storage/azureDisk.vue +22 -2
  102. package/edit/workload/storage/azureFile.vue +20 -2
  103. package/edit/workload/storage/csi/index.vue +23 -1
  104. package/edit/workload/storage/gcePersistentDisk.vue +20 -2
  105. package/edit/workload/storage/index.vue +33 -65
  106. package/edit/workload/storage/persistentVolumeClaim/index.vue +5 -0
  107. package/edit/workload/storage/secret.vue +6 -1
  108. package/edit/workload/storage/vsphereVolume.vue +11 -1
  109. package/layouts/default.vue +14 -8
  110. package/layouts/home.vue +9 -4
  111. package/layouts/plain.vue +10 -5
  112. package/list/catalog.cattle.io.app.vue +10 -9
  113. package/list/catalog.cattle.io.clusterrepo.vue +6 -61
  114. package/list/cis.cattle.io.clusterscan.vue +12 -12
  115. package/list/fleet.cattle.io.bundle.vue +33 -28
  116. package/list/fleet.cattle.io.cluster.vue +26 -22
  117. package/list/fleet.cattle.io.clustergroup.vue +6 -0
  118. package/list/fleet.cattle.io.clusterregistrationtoken.vue +28 -24
  119. package/list/fleet.cattle.io.gitrepo.vue +25 -14
  120. package/list/helm.cattle.io.projecthelmchart.vue +52 -33
  121. package/list/logging.banzaicloud.io.clusterflow.vue +7 -12
  122. package/list/logging.banzaicloud.io.flow.vue +7 -14
  123. package/list/management.cattle.io.cluster.vue +26 -15
  124. package/list/management.cattle.io.feature.vue +13 -8
  125. package/list/management.cattle.io.setting.vue +3 -3
  126. package/list/management.cattle.io.user.vue +38 -19
  127. package/list/monitoring.coreos.com.alertmanagerconfig.vue +8 -15
  128. package/list/namespace.vue +14 -1
  129. package/list/node.vue +13 -16
  130. package/list/persistentvolume.vue +16 -9
  131. package/list/persistentvolumeclaim.vue +5 -8
  132. package/list/provisioning.cattle.io.cluster.vue +35 -9
  133. package/list/service.vue +24 -12
  134. package/list/ui.cattle.io.navlink.vue +6 -0
  135. package/list/workload.vue +2 -2
  136. package/machine-config/harvester.vue +5 -3
  137. package/middleware/authenticated.js +6 -0
  138. package/mixins/resource-fetch.js +12 -18
  139. package/mixins/resource-manager.js +126 -0
  140. package/models/catalog.cattle.io.uiplugin.js +38 -0
  141. package/models/cluster/node.js +25 -2
  142. package/models/fleet.cattle.io.bundle.js +1 -1
  143. package/models/harvesterhci.io.management.cluster.js +11 -5
  144. package/models/pod.js +15 -5
  145. package/models/provisioning.cattle.io.cluster.js +16 -6
  146. package/models/workload.js +5 -3
  147. package/models/workload.service.js +10 -0
  148. package/nuxt.config.js +70 -25
  149. package/package.json +108 -109
  150. package/pages/auth/login.vue +11 -1
  151. package/pages/auth/verify.vue +9 -0
  152. package/pages/c/_cluster/apps/charts/index.vue +46 -1
  153. package/pages/c/_cluster/apps/charts/install.vue +10 -9
  154. package/pages/c/_cluster/explorer/index.vue +72 -9
  155. package/pages/c/_cluster/explorer/tools/index.vue +12 -5
  156. package/pages/c/_cluster/mcapps/index.vue +1 -1
  157. package/pages/c/_cluster/settings/DefaultLinksEditor.vue +108 -0
  158. package/pages/c/_cluster/settings/brand.vue +0 -40
  159. package/pages/c/_cluster/settings/links.vue +152 -0
  160. package/pages/c/_cluster/settings/performance.vue +90 -7
  161. package/pages/c/_cluster/uiplugins/DeveloperInstallDialog.vue +232 -0
  162. package/pages/c/_cluster/uiplugins/InstallDialog.vue +293 -0
  163. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +300 -0
  164. package/pages/c/_cluster/uiplugins/RemoveUIPlugins.vue +125 -0
  165. package/pages/c/_cluster/uiplugins/SetupUIPlugins.vue +261 -0
  166. package/pages/c/_cluster/uiplugins/UninstallDialog.vue +122 -0
  167. package/pages/c/_cluster/uiplugins/index.vue +808 -0
  168. package/pages/diagnostic.vue +185 -101
  169. package/pages/docs/_doc.vue +3 -1
  170. package/pages/home.vue +21 -56
  171. package/pages/prefs.vue +108 -88
  172. package/pages/safeMode.vue +17 -0
  173. package/pages/support/index.vue +34 -137
  174. package/pkg/dynamic-importer.lib.js +4 -0
  175. package/plugins/dashboard-store/actions.js +19 -0
  176. package/plugins/dashboard-store/getters.js +20 -3
  177. package/plugins/dashboard-store/mutations.js +13 -7
  178. package/plugins/dashboard-store/resource-class.js +2 -2
  179. package/plugins/formatters.js +15 -0
  180. package/plugins/plugin.js +61 -6
  181. package/plugins/steve/getters.js +12 -0
  182. package/plugins/steve/mutations.js +1 -1
  183. package/plugins/steve/subscribe.js +94 -72
  184. package/plugins/steve/web-worker.steve-sub-worker.js +24 -15
  185. package/plugins/version.js +21 -0
  186. package/promptRemove/management.cattle.io.globalrole.vue +47 -0
  187. package/promptRemove/management.cattle.io.roletemplate.vue +47 -0
  188. package/promptRemove/mixin/roleDeletionCheck.js +97 -0
  189. package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +6 -7
  190. package/rancher-components/components/BadgeState/BadgeState.spec.ts +12 -0
  191. package/rancher-components/components/BadgeState/BadgeState.vue +107 -0
  192. package/rancher-components/components/BadgeState/index.ts +1 -0
  193. package/rancher-components/components/Banner/Banner.test.ts +13 -0
  194. package/rancher-components/components/Banner/Banner.vue +163 -0
  195. package/rancher-components/components/Banner/index.ts +1 -0
  196. package/rancher-components/components/Card/Card.vue +150 -0
  197. package/rancher-components/components/Card/index.ts +1 -0
  198. package/rancher-components/components/Form/Checkbox/Checkbox.test.ts +77 -0
  199. package/rancher-components/components/Form/Checkbox/Checkbox.vue +395 -0
  200. package/rancher-components/components/Form/Checkbox/index.ts +1 -0
  201. package/rancher-components/components/Form/LabeledInput/LabeledInput.test.ts +29 -0
  202. package/rancher-components/components/Form/LabeledInput/LabeledInput.vue +343 -0
  203. package/rancher-components/components/Form/LabeledInput/index.ts +1 -0
  204. package/rancher-components/components/Form/Radio/RadioButton.vue +270 -0
  205. package/rancher-components/components/Form/Radio/RadioGroup.vue +235 -0
  206. package/rancher-components/components/Form/Radio/index.ts +2 -0
  207. package/rancher-components/components/Form/TextArea/TextAreaAutoGrow.vue +168 -0
  208. package/rancher-components/components/Form/TextArea/index.ts +1 -0
  209. package/rancher-components/components/Form/ToggleSwitch/ToggleSwitch.test.ts +107 -0
  210. package/rancher-components/components/Form/ToggleSwitch/ToggleSwitch.vue +137 -0
  211. package/rancher-components/components/Form/ToggleSwitch/index.ts +1 -0
  212. package/rancher-components/components/Form/index.ts +5 -0
  213. package/rancher-components/components/LabeledTooltip/LabeledTooltip.vue +137 -0
  214. package/rancher-components/components/LabeledTooltip/index.ts +1 -0
  215. package/scripts/publish-shell.sh +40 -7
  216. package/scripts/record-deps.js +37 -0
  217. package/scripts/sync-shell-deps +37 -0
  218. package/scripts/test-plugins-build.sh +8 -5
  219. package/scripts/typegen.sh +84 -0
  220. package/store/auth.js +3 -0
  221. package/store/catalog.js +9 -8
  222. package/store/i18n.js +10 -1
  223. package/store/index.js +12 -3
  224. package/store/prefs.js +16 -0
  225. package/store/type-map.js +32 -5
  226. package/store/uiplugins.ts +15 -61
  227. package/types/shell/index.d.ts +3046 -0
  228. package/utils/__tests__/object.test.ts +0 -24
  229. package/utils/__tests__/selector.test.ts +1 -1
  230. package/utils/dynamic-importer.js +4 -0
  231. package/utils/favicon.js +8 -2
  232. package/utils/gc/gc-interval.ts +40 -0
  233. package/utils/gc/gc-root-store.js +76 -0
  234. package/utils/gc/gc-route-changed.ts +44 -0
  235. package/utils/gc/gc-types.ts +21 -0
  236. package/utils/gc/gc.ts +282 -0
  237. package/utils/grafana.js +2 -6
  238. package/utils/socket.js +41 -20
  239. package/utils/string.js +1 -7
  240. package/utils/validators/formRules/__tests__/index.test.ts +108 -0
  241. package/utils/validators/formRules/index.ts +9 -1
  242. package/config/footer.js +0 -19
  243. package/creators/pkg/nuxt.config.js +0 -6
  244. package/pages/plugins.vue +0 -387
  245. package/server/verdaccio-middleware.js +0 -56
@@ -0,0 +1,808 @@
1
+ <script>
2
+ import Vue from 'vue';
3
+ import { mapGetters } from 'vuex';
4
+ import { mapPref, PLUGIN_DEVELOPER } from '@shell/store/prefs';
5
+ import { sortBy } from '@shell/utils/sort';
6
+ import { allHash } from '@shell/utils/promise';
7
+ import { CATALOG, UI_PLUGIN, SERVICE } from '@shell/config/types';
8
+ import { CATALOG as CATALOG_ANNOTATIONS } from '@shell/config/labels-annotations';
9
+
10
+ import ActionMenu from '@shell/components/ActionMenu';
11
+ import Tabbed from '@shell/components/Tabbed/index.vue';
12
+ import Tab from '@shell/components/Tabbed/Tab.vue';
13
+ import IconMessage from '@shell/components/IconMessage.vue';
14
+ import LazyImage from '@shell/components/LazyImage';
15
+ import UninstallDialog from './UninstallDialog.vue';
16
+ import InstallDialog from './InstallDialog.vue';
17
+ import DeveloperInstallDialog from './DeveloperInstallDialog.vue';
18
+ import PluginInfoPanel from './PluginInfoPanel.vue';
19
+ import SetupUIPlugins from './SetupUIPlugins';
20
+ import RemoveUIPlugins from './RemoveUIPlugins';
21
+ import { isUIPlugin, uiPluginHasAnnotation, isSupportedChartVersion, UI_PLUGIN_NAMESPACE } from '@shell/config/uiplugins';
22
+
23
+ const MAX_DESCRIPTION_LENGTH = 200;
24
+
25
+ export default {
26
+ components: {
27
+ ActionMenu,
28
+ DeveloperInstallDialog,
29
+ IconMessage,
30
+ InstallDialog,
31
+ LazyImage,
32
+ PluginInfoPanel,
33
+ Tab,
34
+ Tabbed,
35
+ UninstallDialog,
36
+ SetupUIPlugins,
37
+ RemoveUIPlugins,
38
+ },
39
+
40
+ data() {
41
+ return {
42
+ view: '',
43
+ charts: [],
44
+ installing: {},
45
+ errors: {},
46
+ plugins: [], // The installed plugins
47
+ helmOps: [], // Helm operations
48
+ loading: true,
49
+ menuTargetElement: null,
50
+ menuTargetEvent: null,
51
+ menuOpen: false,
52
+ hasService: false,
53
+ defaultIcon: require('~shell/assets/images/generic-plugin.svg'),
54
+ reloadRequired: false,
55
+ };
56
+ },
57
+
58
+ layout: 'plain',
59
+
60
+ async fetch() {
61
+ const hash = {};
62
+
63
+ const isSetup = await this.updateInstallStatus();
64
+
65
+ if (isSetup) {
66
+ if (this.$store.getters['management/schemaFor'](UI_PLUGIN)) {
67
+ hash.plugins = this.$store.dispatch('management/findAll', { type: UI_PLUGIN });
68
+ }
69
+ }
70
+
71
+ hash.load = await this.$store.dispatch('catalog/load');
72
+
73
+ if (this.$store.getters['management/schemaFor'](CATALOG.OPERATION)) {
74
+ hash.helmOps = await this.$store.dispatch('management/findAll', { type: CATALOG.OPERATION });
75
+ }
76
+
77
+ const res = await allHash(hash);
78
+
79
+ this.plugins = res.plugins || [];
80
+ this.helmOps = res.helmOps || [];
81
+
82
+ const c = this.$store.getters['catalog/rawCharts'];
83
+
84
+ this.charts = Object.values(c);
85
+
86
+ // If there are no plugins installed, default to the catalog view
87
+ if (this.plugins.length === 0) {
88
+ this.$refs.tabs?.select('available');
89
+ }
90
+
91
+ this.loading = false;
92
+ },
93
+
94
+ computed: {
95
+ pluginDeveloper: mapPref(PLUGIN_DEVELOPER),
96
+
97
+ ...mapGetters({ uiplugins: 'uiplugins/plugins' }),
98
+ ...mapGetters({ uiErrors: 'uiplugins/errors' }),
99
+
100
+ menuActions() {
101
+ const menuActions = [];
102
+
103
+ // Only show Developer Load action if the user has this enabled in preferences
104
+ if (this.pluginDeveloper) {
105
+ menuActions.push({
106
+ action: 'devLoad',
107
+ label: this.t('plugins.developer.label'),
108
+ enabled: true
109
+ });
110
+ menuActions.push( { divider: true });
111
+ }
112
+
113
+ if (this.hasService) {
114
+ menuActions.push({
115
+ action: 'removePluginSupport',
116
+ label: this.t('plugins.setup.remove.label'),
117
+ enabled: true
118
+ });
119
+ }
120
+
121
+ return menuActions;
122
+ },
123
+
124
+ list() {
125
+ const all = this.available;
126
+
127
+ switch (this.view) {
128
+ case 'installed':
129
+ return all.filter(p => !!p.installed || !!p.installing);
130
+ case 'updates':
131
+ return this.updates;
132
+ case 'available':
133
+ return all.filter(p => !p.installed);
134
+ default:
135
+ return all;
136
+ }
137
+ },
138
+
139
+ hasMenuActions() {
140
+ return this.menuActions?.length > 0;
141
+ },
142
+
143
+ // Message to display when the tab view is empty (depends on the tab)
144
+ emptyMessage() {
145
+ return this.t(`plugins.empty.${ this.view }`);
146
+ },
147
+
148
+ updates() {
149
+ return this.available.filter(plugin => !!plugin.upgrade);
150
+ },
151
+
152
+ available() {
153
+ let all = this.charts.filter(c => isUIPlugin(c));
154
+
155
+ // Filter out hidden charts
156
+ all = all.filter(c => !uiPluginHasAnnotation(c, CATALOG_ANNOTATIONS.HIDDEN, 'true'));
157
+
158
+ all = all.map((chart) => {
159
+ const item = {
160
+ name: chart.chartNameDisplay,
161
+ description: chart.chartDescription,
162
+ id: chart.id,
163
+ versions: [],
164
+ displayVersion: chart.versions?.length > 0 ? chart.versions[0].version : '',
165
+ installed: false,
166
+ builtin: false,
167
+ experimental: uiPluginHasAnnotation(chart, CATALOG_ANNOTATIONS.EXPERIMENTAL, 'true'),
168
+ certified: uiPluginHasAnnotation(chart, CATALOG_ANNOTATIONS.CERTIFIED, CATALOG_ANNOTATIONS._RANCHER),
169
+ };
170
+
171
+ this.latest = chart.versions[0];
172
+ item.versions = [...chart.versions];
173
+ item.chart = chart;
174
+
175
+ // Filter the versions, leaving only those that are compatible with this Rancher
176
+ item.versions = item.versions.filter(version => isSupportedChartVersion(version));
177
+
178
+ if (this.latest) {
179
+ item.icon = chart.icon || this.latest.annotations['catalog.cattle.io/ui-icon'];
180
+ }
181
+
182
+ if (this.installing[item.name]) {
183
+ item.installing = this.installing[item.name];
184
+ }
185
+
186
+ return item;
187
+ });
188
+
189
+ // Remove charts with no installable versions
190
+ all = all.filter(c => c.versions.length > 0);
191
+
192
+ // Check that all of the loaded plugins are represented
193
+ this.uiplugins.forEach((p) => {
194
+ const chart = all.find(c => c.name === p.name);
195
+
196
+ if (!chart) {
197
+ // A pluign is loaded, but there is no chart, so add an item so that it shows up
198
+ const item = {
199
+ name: p.name,
200
+ description: p.metadata?.description,
201
+ id: p.id,
202
+ versions: [],
203
+ displayVersion: p.metadata?.version || '-',
204
+ installed: true,
205
+ builtin: !!p.builtin,
206
+ };
207
+
208
+ all.push(item);
209
+ }
210
+ });
211
+
212
+ // Go through the CRs for the plugins and wire them into the catalog
213
+ this.plugins.forEach((p) => {
214
+ const chart = all.find(c => c.name === p.name);
215
+
216
+ if (chart) {
217
+ chart.installed = true;
218
+ chart.uiplugin = p;
219
+ chart.displayVersion = p.version;
220
+
221
+ // Can't do this here
222
+ chart.installing = this.installing[chart.name];
223
+
224
+ // Check for upgrade
225
+ if (chart.versions.length && p.version !== chart.versions[0].version) {
226
+ chart.upgrade = chart.versions[0].version;
227
+ }
228
+ } else {
229
+ // No chart, so add a card for the plugin based on its Custom resource being present
230
+ const item = {
231
+ name: p.name,
232
+ description: p.description || '-',
233
+ id: `${ p.name }-${ p.version }`,
234
+ versions: [],
235
+ displayVersion: p.version || '-',
236
+ installed: true,
237
+ installing: false,
238
+ builtin: false,
239
+ uiplugin: p,
240
+ };
241
+
242
+ all.push(item);
243
+ }
244
+ });
245
+
246
+ // Merge in the plugin load errors
247
+ Object.keys(this.uiErrors).forEach((e) => {
248
+ const chart = all.find(c => c.name === e);
249
+
250
+ if (chart) {
251
+ const error = this.uiErrors[e];
252
+
253
+ if (error && typeof error === 'string') {
254
+ chart.error = this.t(this.uiErrors[e]);
255
+ } else {
256
+ chart.error = false;
257
+ }
258
+ }
259
+ });
260
+
261
+ // Merge in the plugin load errors from help ops
262
+ Object.keys(this.errors).forEach((e) => {
263
+ const chart = all.find(c => c.name === e);
264
+
265
+ if (chart) {
266
+ chart.helmError = !!this.errors[e];
267
+ }
268
+ });
269
+
270
+ // Clamp the lengths of the descriptions
271
+ all.forEach((plugin) => {
272
+ if (plugin.description && plugin.description.length > MAX_DESCRIPTION_LENGTH) {
273
+ plugin.description = `${ plugin.description.substr(0, MAX_DESCRIPTION_LENGTH) } ...`;
274
+ }
275
+ });
276
+
277
+ // Sort by name
278
+ return sortBy(all, 'name', false);
279
+ },
280
+ },
281
+
282
+ watch: {
283
+ helmOps(neu) {
284
+ // Get Helm operations for UI plugins and order by date
285
+ let pluginOps = neu.filter((op) => {
286
+ return op.namespace === UI_PLUGIN_NAMESPACE;
287
+ });
288
+
289
+ pluginOps = sortBy(pluginOps, 'metadata.creationTimestamp', true);
290
+
291
+ // Go through the installed plugins
292
+ (this.available || []).forEach((plugin) => {
293
+ const op = pluginOps.find(o => o.status?.releaseName === plugin.name);
294
+
295
+ if (op) {
296
+ const active = op.metadata.state?.transitioning;
297
+ const error = op.metadata.state?.error;
298
+
299
+ Vue.set(this.errors, plugin.name, error);
300
+
301
+ if (active) {
302
+ // Can use the status directly, apart from upgrade, which maps to install
303
+ const status = op.status.action === 'upgrade' ? 'install' : op.status.action;
304
+
305
+ this.updatePluginInstallStatus(plugin.name, status);
306
+ } else if (op.status.action === 'uninstall') {
307
+ // Uninstall has finished
308
+ this.updatePluginInstallStatus(plugin.name, false);
309
+ } else if (error) {
310
+ this.updatePluginInstallStatus(plugin.name, false);
311
+ }
312
+ } else {
313
+ this.updatePluginInstallStatus(plugin.name, false);
314
+ }
315
+ });
316
+ },
317
+
318
+ plugins(neu, old) {
319
+ const installed = this.$store.getters['uiplugins/plugins'];
320
+
321
+ let changes = 0;
322
+
323
+ // Did the user remove an extension
324
+ if (neu?.length < installed.length) {
325
+ changes++;
326
+ }
327
+
328
+ neu.forEach((plugin) => {
329
+ const existing = installed.find(p => !p.removed && p.name === plugin.name && p.version === plugin.version);
330
+
331
+ if (!existing && plugin.isCached) {
332
+ changes++;
333
+
334
+ this.updatePluginInstallStatus(plugin.name, false);
335
+ }
336
+ });
337
+
338
+ if (changes > 0 ) {
339
+ Vue.set(this, 'reloadRequired', true);
340
+ }
341
+ },
342
+ },
343
+
344
+ // Forget the types when we leave the page
345
+ beforeDestroy() {
346
+ this.$store.dispatch('management/forgetType', UI_PLUGIN);
347
+ this.$store.dispatch('management/forgetType', CATALOG.OPERATION);
348
+ this.$store.dispatch('management/forgetType', CATALOG.APP);
349
+ this.$store.dispatch('management/forgetType', CATALOG.CLUSTER_REPO);
350
+ },
351
+
352
+ methods: {
353
+ async updateInstallStatus() {
354
+ let hasService;
355
+
356
+ try {
357
+ const service = await this.$store.dispatch('management/find', {
358
+ type: SERVICE,
359
+ id: `${ UI_PLUGIN_NAMESPACE }/ui-plugin-operator`,
360
+ opt: { force: true },
361
+ });
362
+
363
+ hasService = !!service;
364
+ } catch (e) {
365
+ hasService = false;
366
+ }
367
+
368
+ Vue.set(this, 'hasService', hasService);
369
+
370
+ return hasService;
371
+ },
372
+
373
+ filterChanged(f) {
374
+ this.view = f.selectedName;
375
+ },
376
+
377
+ removePluginSupport() {
378
+ this.$refs.removeUIPlugins.showDialog();
379
+ },
380
+
381
+ // Developer Load is in the action menu
382
+ showDeveloperLoaddDialog() {
383
+ this.$refs.developerInstallDialog.showDialog();
384
+ },
385
+
386
+ showInstallDialog(plugin, mode, ev) {
387
+ ev.target?.blur();
388
+ ev.preventDefault();
389
+ ev.stopPropagation();
390
+
391
+ this.$refs.installDialog.showDialog(plugin, mode);
392
+ },
393
+
394
+ showUninstallDialog(plugin, ev) {
395
+ ev.target?.blur();
396
+ ev.preventDefault();
397
+ ev.stopPropagation();
398
+
399
+ this.$refs.uninstallDialog.showDialog(plugin);
400
+ },
401
+
402
+ didUninstall(plugin) {
403
+ if (plugin) {
404
+ this.updatePluginInstallStatus(plugin.name, 'uninstall');
405
+
406
+ // Clear the load error, if there was one
407
+ this.$store.dispatch('uiplugins/setError', { name: plugin.name, error: false });
408
+ }
409
+ },
410
+
411
+ didInstall(plugin) {
412
+ if (plugin) {
413
+ // Change the view to installed if we started installing a plugin
414
+ this.$refs.tabs?.select('installed');
415
+
416
+ // Clear the load error, if there was one previously
417
+ this.$store.dispatch('uiplugins/setError', { name: plugin.name, error: false });
418
+ }
419
+ },
420
+
421
+ showPluginDetail(plugin) {
422
+ this.$refs.infoPanel.show(plugin);
423
+ },
424
+
425
+ updatePluginInstallStatus(name, status) {
426
+ // console.log(`UPDATING PLUGIN STATUS: ${ name } ${ status }`);
427
+ Vue.set(this.installing, name, status);
428
+ },
429
+
430
+ setMenu(event) {
431
+ this.menuOpen = !!event;
432
+
433
+ if (event) {
434
+ this.menuTargetElement = this.$refs.actions;
435
+ this.menuTargetEvent = event;
436
+ } else {
437
+ this.menuTargetElement = undefined;
438
+ this.menuTargetEvent = undefined;
439
+ }
440
+ },
441
+
442
+ reload() {
443
+ this.$router.go();
444
+ }
445
+ }
446
+ };
447
+ </script>
448
+
449
+ <template>
450
+ <div class="plugins">
451
+ <div class="plugin-header">
452
+ <h2>{{ t('plugins.title') }}</h2>
453
+ <div v-if="reloadRequired" class="plugin-reload-banner mr-20">
454
+ <i class="icon icon-checkmark mr-10" />
455
+ <span>
456
+ {{ t('plugins.reload') }}
457
+ </span>
458
+ <button class="ml-10 btn btn-sm role-primary" @click="reload()">
459
+ {{ t('generic.reload') }}
460
+ </button>
461
+ </div>
462
+ <button
463
+ v-if="hasService && hasMenuActions"
464
+ ref="actions"
465
+ aria-haspopup="true"
466
+ type="button"
467
+ class="btn actions role-secondary"
468
+ @click="setMenu"
469
+ >
470
+ <i class="icon icon-actions" />
471
+ </button>
472
+ <ActionMenu
473
+ v-if="hasService && hasMenuActions"
474
+ :custom-actions="menuActions"
475
+ :open="menuOpen"
476
+ :use-custom-target-element="true"
477
+ :custom-target-element="menuTargetElement"
478
+ :custom-target-event="menuTargetEvent"
479
+ @close="setMenu(false)"
480
+ @devLoad="showDeveloperLoaddDialog"
481
+ @removePluginSupport="removePluginSupport"
482
+ />
483
+ </div>
484
+
485
+ <PluginInfoPanel ref="infoPanel" />
486
+
487
+ <div v-if="!hasService">
488
+ <div v-if="loading" class="data-loading">
489
+ <i class="icon-spin icon icon-spinner" />
490
+ <t k="generic.loading" :raw="true" />
491
+ </div>
492
+ <SetupUIPlugins v-else class="setup-message" @done="updateInstallStatus" />
493
+ </div>
494
+ <div v-else>
495
+ <Tabbed ref="tabs" :tabs-only="true" @changed="filterChanged">
496
+ <Tab name="installed" label-key="plugins.tabs.installed" :weight="20" />
497
+ <Tab name="available" label-key="plugins.tabs.available" :weight="19" />
498
+ <Tab name="updates" label-key="plugins.tabs.updates" :weight="18" :badge="updates.length" />
499
+ <Tab name="all" label-key="plugins.tabs.all" :weight="17" />
500
+ </Tabbed>
501
+ <div v-if="loading" class="data-loading">
502
+ <i class="icon-spin icon icon-spinner" />
503
+ <t k="generic.loading" :raw="true" />
504
+ </div>
505
+ <div v-else class="plugin-list" :class="{'v-margin': !list.length}">
506
+ <IconMessage
507
+ v-if="list.length === 0"
508
+ :vertical="true"
509
+ :subtle="true"
510
+ icon="icon-extension"
511
+ :message="emptyMessage"
512
+ />
513
+ <template v-else>
514
+ <div v-for="plugin in list" :key="plugin.name" class="plugin" @click="showPluginDetail(plugin)">
515
+ <div class="plugin-icon">
516
+ <LazyImage
517
+ v-if="plugin.icon"
518
+ :initial-src="defaultIcon"
519
+ :error-src="defaultIcon"
520
+ :src="plugin.icon"
521
+ class="icon plugin-icon-img"
522
+ />
523
+ <img
524
+ v-else
525
+ :src="defaultIcon"
526
+ class="icon plugin-icon-img"
527
+ />
528
+ </div>
529
+ <div class="plugin-metadata">
530
+ <div class="plugin-name">
531
+ {{ plugin.name }}
532
+ </div>
533
+ <div>{{ plugin.description }}</div>
534
+ <div v-if="plugin.builtin" class="plugin-builtin">
535
+ {{ t('plugins.labels.builtin') }}
536
+ </div>
537
+ <div class="plugin-version">
538
+ <span v-if="plugin.installing === 'uninstall'" class="plugin-installing">
539
+ -
540
+ </span>
541
+ <span v-else>
542
+ <span>{{ plugin.displayVersion }}</span>
543
+ <span v-if="plugin.upgrade" v-tooltip="t('plugins.upgradeAvailable')"> -> {{ plugin.upgrade }}</span>
544
+ </span>
545
+ </div>
546
+ <div class="plugin-badges">
547
+ <div v-if="!plugin.certified" v-tooltip="t('plugins.descriptions.third-party')">
548
+ {{ t('plugins.labels.third-party') }}
549
+ </div>
550
+ <div v-if="plugin.experimental" v-tooltip="t('plugins.descriptions.experimental')">
551
+ {{ t('plugins.labels.experimental') }}
552
+ </div>
553
+ </div>
554
+ <div class="plugin-spacer" />
555
+ <div class="plugin-actions">
556
+ <template v-if="plugin.error">
557
+ <div v-tooltip="plugin.error" class="plugin-error">
558
+ <i class="icon icon-warning" />
559
+ </div>
560
+ </template>
561
+ <div v-if="plugin.helmError" v-tooltip="t('plugins.helmError')" class="plugin-error">
562
+ <i class="icon icon-warning" />
563
+ </div>
564
+
565
+ <div class="plugin-spacer" />
566
+
567
+ <div v-if="plugin.installing" class="plugin-installing">
568
+ <i class="version-busy icon icon-spin icon-spinner" />
569
+ <div v-if="plugin.installing ==='install'">
570
+ {{ t('plugins.labels.installing') }}
571
+ </div>
572
+ <div v-else>
573
+ {{ t('plugins.labels.uninstalling') }}
574
+ </div>
575
+ </div>
576
+ <div v-else-if="plugin.installed" class="plugin-buttons">
577
+ <button v-if="!plugin.builtin" class="btn role-secondary" @click="showUninstallDialog(plugin, $event)">
578
+ {{ t('plugins.uninstall.label') }}
579
+ </button>
580
+ <button v-if="plugin.upgrade" class="btn role-secondary" @click="showInstallDialog(plugin, 'update', $event)">
581
+ {{ t('plugins.update.label') }}
582
+ </button>
583
+ <button v-if="!plugin.upgrade && plugin.versions.length > 1" class="btn role-secondary" @click="showInstallDialog(plugin, 'rollback', $event)">
584
+ {{ t('plugins.rollback.label') }}
585
+ </button>
586
+ </div>
587
+ <div v-else class="plugin-buttons">
588
+ <button class="btn role-secondary" @click="showInstallDialog(plugin, 'install', $event)">
589
+ {{ t('plugins.install.label') }}
590
+ </button>
591
+ </div>
592
+ </div>
593
+ </div>
594
+ </div>
595
+ </template>
596
+ </div>
597
+ </div>
598
+
599
+ <InstallDialog ref="installDialog" @closed="didInstall" @update="updatePluginInstallStatus" />
600
+ <UninstallDialog ref="uninstallDialog" @closed="didUninstall" @update="updatePluginInstallStatus" />
601
+ <DeveloperInstallDialog ref="developerInstallDialog" @closed="didInstall" />
602
+ <RemoveUIPlugins ref="removeUIPlugins" @done="updateInstallStatus" />
603
+ </div>
604
+ </template>
605
+
606
+ <style lang="scss" scoped>
607
+
608
+ .setup-message {
609
+ margin-top: 100px;
610
+ }
611
+
612
+ .data-loading {
613
+ align-items: center;
614
+ display: flex;
615
+ justify-content: center;
616
+
617
+ > I {
618
+ margin-right: 5px;
619
+ }
620
+ }
621
+
622
+ .plugin-reload-banner {
623
+ align-items: center;
624
+ background-color: var(--success-banner-bg);
625
+ display: flex;
626
+ padding: 0 10px;
627
+ border-radius: 5px;
628
+ min-height: 36px;
629
+
630
+ > i {
631
+ color: var(--success);
632
+ font-size: 20px;
633
+ font-weight: bold;
634
+ }
635
+
636
+ > button {
637
+ line-height: 30px;
638
+ min-height: 30px;
639
+ }
640
+ }
641
+
642
+ .plugin-complete {
643
+ font-size: 18px;
644
+ }
645
+
646
+ .plugin-list {
647
+ display: flex;
648
+ flex-wrap: wrap;
649
+
650
+ > .plugin:not(:last-child) {
651
+ margin-right: 20px;
652
+ }
653
+
654
+ &.v-margin {
655
+ margin-top: 40px;
656
+ }
657
+ }
658
+ .plugins {
659
+ display: inherit;
660
+ }
661
+
662
+ .plugin-header {
663
+ display: flex;
664
+ align-items: center;
665
+ margin-bottom: 10px;
666
+
667
+ > h2 {
668
+ flex: 1;
669
+ margin-bottom: 0;
670
+ }
671
+
672
+ .btn.actions {
673
+ line-height: 36px;
674
+ min-height: 36px;
675
+ padding: 0 10px;
676
+ }
677
+ }
678
+
679
+ .plugin {
680
+ display: flex;
681
+ border: 1px solid var(--border);
682
+ padding: 10px;
683
+ width: calc(33% - 20px);
684
+ max-width: 540px;
685
+ margin-bottom: 20px;
686
+ cursor: pointer;
687
+
688
+ .plugin-icon {
689
+ font-size: 40px;
690
+ margin-right:10px;
691
+ color: #888;
692
+
693
+ .plugin-icon-img {
694
+ height: 40px;
695
+ width: 40px;
696
+ }
697
+ }
698
+
699
+ .plugin-spacer {
700
+ flex: 1;
701
+ }
702
+
703
+ .plugin-metadata {
704
+ display: flex;
705
+ flex: 1;
706
+ flex-direction: column;
707
+
708
+ .plugin-buttons {
709
+ > button:not(:first-child) {
710
+ margin-left: 5px;
711
+ }
712
+ }
713
+ }
714
+
715
+ .plugin-builtin {
716
+ color: var(--primary);
717
+ display: block;
718
+ padding: 2px 0;
719
+ text-transform: uppercase;
720
+ }
721
+
722
+ .plugin-name {
723
+ font-size: 16px;
724
+ font-weight: bold;
725
+ margin-bottom: 5px;
726
+ }
727
+
728
+ .plugin-badges {
729
+ display: flex;
730
+
731
+ > div {
732
+ border: 1px solid var(--border);
733
+ border-radius: 4px;
734
+ padding: 2px 8px;
735
+ margin-right: 10px;
736
+ font-size: 12px;
737
+ }
738
+ }
739
+
740
+ .plugin-version {
741
+ align-items: center;
742
+ display: inline-flex;
743
+ font-size: 12px;
744
+ border-radius: 4px;
745
+ margin: 5px 0;
746
+
747
+ i.icon-spinner {
748
+ padding-right: 5px;
749
+ font-size: 16px;
750
+ height: 16px;
751
+ width: 16px;
752
+ }
753
+ }
754
+
755
+ .plugin-installing {
756
+ align-items: center;
757
+ display: flex;
758
+
759
+ > div {
760
+ font-size: 14px;
761
+ margin-left: 5px;
762
+ }
763
+ }
764
+
765
+ .plugin-actions {
766
+ align-items:center;
767
+ display: flex;
768
+
769
+ $error-icon-size: 22px;
770
+
771
+ .plugin-error {
772
+ display: inline;
773
+ cursor: help;
774
+
775
+ > i {
776
+ color: var(--error);
777
+ height: $error-icon-size;
778
+ font-size: $error-icon-size;
779
+ width: $error-icon-size;
780
+ }
781
+ }
782
+
783
+ .btn {
784
+ line-height: 20px;
785
+ min-height: 20px;
786
+ padding: 0 5px;
787
+ }
788
+ }
789
+ }
790
+
791
+ @media screen and (max-width: 1200px) {
792
+ .plugin-list {
793
+ .plugin {
794
+ width: calc(50% - 20px);
795
+ }
796
+ }
797
+ }
798
+
799
+ @media screen and (max-width: 960px) {
800
+ .plugin-list {
801
+ .plugin {
802
+ margin-right: 0 !important;
803
+ width: 100%;
804
+ }
805
+ }
806
+ }
807
+
808
+ </style>