@rancher/shell 0.1.3 → 0.1.4

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