@rancher/shell 0.3.11 → 0.3.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/translations/en-us.yaml +51 -5
- package/chart/monitoring/StorageClassSelector.vue +1 -0
- package/chart/monitoring/index.vue +4 -0
- package/chart/monitoring/prometheus/index.vue +6 -3
- package/components/ActionMenu.vue +1 -1
- package/components/DetailTop.vue +0 -2
- package/components/ExplorerMembers.vue +22 -10
- package/components/ExplorerProjectsNamespaces.vue +1 -0
- package/components/GrafanaDashboard.vue +2 -2
- package/components/Inactivity.vue +1 -0
- package/components/ModalWithCard.vue +1 -0
- package/components/Tabbed/index.vue +2 -0
- package/components/Wizard.vue +4 -3
- package/components/form/KeyValue.vue +12 -7
- package/components/form/NodeAffinity.vue +29 -7
- package/components/form/PodAffinity.vue +27 -7
- package/components/form/Taints.vue +6 -0
- package/components/formatter/ExtensionCache.vue +74 -0
- package/components/nav/Header.vue +1 -0
- package/components/nav/WindowManager/ContainerShell.vue +10 -0
- package/components/nav/WindowManager/index.vue +1 -0
- package/config/product/explorer.js +1 -10
- package/config/product/monitoring.js +2 -1
- package/config/router.js +3 -3
- package/config/table-headers.js +32 -24
- package/config/uiplugins.js +11 -0
- package/config/workload.ts +1 -0
- package/core/types.ts +25 -7
- package/creators/pkg/files/.github/workflows/build-container.yml +64 -0
- package/creators/pkg/init +13 -6
- package/detail/node.vue +2 -2
- package/detail/workload/index.vue +1 -1
- package/edit/__tests__/management.cattle.io.setting.test.ts +1 -1
- package/edit/autoscaling.horizontalpodautoscaler/metric-target.vue +0 -2
- package/edit/logging.banzaicloud.io.output/__tests__/logging.banzaicloud.io.output.test.ts +43 -0
- package/edit/logging.banzaicloud.io.output/index.vue +8 -5
- package/edit/logging.banzaicloud.io.output/providers/__tests__/loki.test.ts +13 -0
- package/edit/logging.banzaicloud.io.output/providers/loki.vue +1 -0
- package/edit/monitoring.coreos.com.alertmanagerconfig/receiverConfig.vue +0 -2
- package/edit/monitoring.coreos.com.receiver/index.vue +32 -1
- package/edit/monitoring.coreos.com.receiver/types/email.vue +12 -4
- package/edit/namespace.vue +1 -0
- package/edit/provisioning.cattle.io.cluster/MachinePool.vue +36 -6
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +2 -2
- package/edit/provisioning.cattle.io.cluster/rke2.vue +58 -13
- package/middleware/authenticated.js +1 -0
- package/models/__tests__/batch.cronjob.test.ts +88 -0
- package/models/cluster/node.js +8 -0
- package/models/management.cattle.io.clusterroletemplatebinding.js +5 -1
- package/models/projectroletemplatebinding.js +9 -1
- package/models/workload.js +1 -1
- package/package.json +1 -1
- package/pages/__tests__/prefs.test.ts +96 -0
- package/pages/auth/setup.vue +13 -13
- package/pages/c/_cluster/apps/charts/chart.vue +1 -1
- package/pages/c/_cluster/apps/charts/install.vue +5 -2
- package/pages/c/_cluster/monitoring/index.vue +10 -5
- package/pages/c/_cluster/settings/performance.vue +2 -0
- package/pages/c/_cluster/uiplugins/CatalogList/CatalogLoadDialog.vue +601 -0
- package/pages/c/_cluster/uiplugins/CatalogList/index.vue +183 -0
- package/pages/c/_cluster/uiplugins/UninstallDialog.vue +50 -9
- package/pages/c/_cluster/uiplugins/index.vue +329 -224
- package/pages/fail-whale.vue +1 -1
- package/pages/home.vue +11 -0
- package/pages/prefs.vue +20 -1
- package/plugins/plugin.js +1 -1
- package/public/index.html +6 -1
- package/rancher-components/components/Card/Card.vue +1 -0
- package/rancher-components/components/Form/Radio/RadioGroup.vue +1 -0
- package/scripts/extension/bundle +20 -4
- package/scripts/extension/helm/charts/ui-plugin-server/.helmignore +23 -0
- package/scripts/extension/helm/charts/ui-plugin-server/Chart.yaml +20 -0
- package/scripts/extension/helm/charts/ui-plugin-server/templates/_helpers.tpl +52 -0
- package/scripts/extension/helm/charts/ui-plugin-server/templates/cr.yaml +12 -0
- package/scripts/extension/helm/charts/ui-plugin-server/values.yaml +6 -0
- package/scripts/extension/helm/package/Dockerfile +27 -0
- package/scripts/extension/helm/package/nginx.conf +17 -0
- package/scripts/extension/helm/scripts/package +23 -0
- package/scripts/extension/helm/scripts/patch +101 -0
- package/scripts/extension/helm/scripts/version +31 -0
- package/scripts/extension/helmpatch +3 -25
- package/scripts/extension/publish +47 -32
- package/types/shell/index.d.ts +30 -24
- package/utils/__tests__/grafana.test.ts +2 -2
- package/utils/error.js +11 -0
- package/utils/grafana.js +5 -4
- package/vue.config.js +3 -17
|
@@ -1120,6 +1120,7 @@ export default {
|
|
|
1120
1120
|
Refer to the developer docs at docs/developer/helm-chart-apps.md
|
|
1121
1121
|
for details on what values are injected and where they come from.
|
|
1122
1122
|
*/
|
|
1123
|
+
|
|
1123
1124
|
this.addGlobalValuesTo(values);
|
|
1124
1125
|
|
|
1125
1126
|
const form = JSON.parse(JSON.stringify(this.value));
|
|
@@ -1510,6 +1511,7 @@ export default {
|
|
|
1510
1511
|
<div class="step__values__controls">
|
|
1511
1512
|
<ButtonGroup
|
|
1512
1513
|
v-model="preFormYamlOption"
|
|
1514
|
+
data-testid="btn-group-options-view"
|
|
1513
1515
|
:options="formYamlOptions"
|
|
1514
1516
|
inactive-class="bg-disabled btn-sm"
|
|
1515
1517
|
active-class="bg-primary btn-sm"
|
|
@@ -1979,7 +1981,7 @@ export default {
|
|
|
1979
1981
|
display: flex;
|
|
1980
1982
|
flex-direction: column;
|
|
1981
1983
|
padding: 0;
|
|
1982
|
-
|
|
1984
|
+
overflow: auto;
|
|
1983
1985
|
}
|
|
1984
1986
|
|
|
1985
1987
|
.header {
|
|
@@ -2052,10 +2054,11 @@ export default {
|
|
|
2052
2054
|
}
|
|
2053
2055
|
|
|
2054
2056
|
.os-label {
|
|
2055
|
-
position:
|
|
2057
|
+
position: relative;
|
|
2056
2058
|
background-color: var(--warning-banner-bg);
|
|
2057
2059
|
color:var(--warning);
|
|
2058
2060
|
margin-top: 5px;
|
|
2061
|
+
top: 21px;
|
|
2059
2062
|
}
|
|
2060
2063
|
|
|
2061
2064
|
</style>
|
|
@@ -4,10 +4,10 @@ import isEmpty from 'lodash/isEmpty';
|
|
|
4
4
|
import InstallRedirect from '@shell/utils/install-redirect';
|
|
5
5
|
import AlertTable from '@shell/components/AlertTable';
|
|
6
6
|
import { NAME, CHART_NAME } from '@shell/config/product/monitoring';
|
|
7
|
-
import { ENDPOINTS, MONITORING } from '@shell/config/types';
|
|
7
|
+
import { CATALOG, ENDPOINTS, MONITORING } from '@shell/config/types';
|
|
8
8
|
import { allHash } from '@shell/utils/promise';
|
|
9
9
|
import { findBy } from '@shell/utils/array';
|
|
10
|
-
|
|
10
|
+
import { getClusterPrefix } from '@shell/utils/grafana';
|
|
11
11
|
import { Banner } from '@components/Banner';
|
|
12
12
|
import LazyImage from '@shell/components/LazyImage';
|
|
13
13
|
import SimpleBox from '@shell/components/SimpleBox';
|
|
@@ -58,7 +58,7 @@ export default {
|
|
|
58
58
|
iconSrc: grafanaSrc,
|
|
59
59
|
label: 'monitoring.overview.linkedList.grafana.label',
|
|
60
60
|
description: 'monitoring.overview.linkedList.grafana.description',
|
|
61
|
-
link:
|
|
61
|
+
link: '',
|
|
62
62
|
},
|
|
63
63
|
{
|
|
64
64
|
enabled: false,
|
|
@@ -94,10 +94,13 @@ export default {
|
|
|
94
94
|
methods: {
|
|
95
95
|
async fetchDeps() {
|
|
96
96
|
const { $store, externalLinks } = this;
|
|
97
|
+
const currentCluster = this.$store.getters['currentCluster'];
|
|
97
98
|
|
|
98
99
|
this.v1Installed = await haveV1MonitoringWorkloads($store);
|
|
99
|
-
|
|
100
|
-
|
|
100
|
+
const hash = await allHash({
|
|
101
|
+
endpoints: $store.dispatch('cluster/findAll', { type: ENDPOINTS }),
|
|
102
|
+
app: $store.dispatch(`cluster/find`, { type: CATALOG.APP, id: 'cattle-monitoring-system/rancher-monitoring' })
|
|
103
|
+
});
|
|
101
104
|
|
|
102
105
|
if (!isEmpty(hash.endpoints)) {
|
|
103
106
|
const amMatch = findBy(externalLinks, 'group', 'alertmanager');
|
|
@@ -105,6 +108,8 @@ export default {
|
|
|
105
108
|
const promeMatch = externalLinks.filter(
|
|
106
109
|
el => el.group === 'prometheus'
|
|
107
110
|
);
|
|
111
|
+
|
|
112
|
+
grafanaMatch.link = `${ getClusterPrefix(hash.app?.currentVersion || '', currentCluster.id) }/api/v1/namespaces/cattle-monitoring-system/services/http:rancher-monitoring-grafana:80/proxy/`;
|
|
108
113
|
const alertmanager = findBy(
|
|
109
114
|
hash.endpoints,
|
|
110
115
|
'id',
|
|
@@ -174,6 +174,7 @@ export default {
|
|
|
174
174
|
<div class="ml-20">
|
|
175
175
|
<LabeledInput
|
|
176
176
|
v-model="value.inactivity.threshold"
|
|
177
|
+
data-testid="inactivity-threshold"
|
|
177
178
|
:mode="mode"
|
|
178
179
|
:label="t('performance.inactivity.inputLabel')"
|
|
179
180
|
:disabled="!value.inactivity.enabled"
|
|
@@ -382,6 +383,7 @@ export default {
|
|
|
382
383
|
</template>
|
|
383
384
|
<div v-if="mode === 'edit'">
|
|
384
385
|
<AsyncButton
|
|
386
|
+
data-testid="performance__save-btn"
|
|
385
387
|
class="pull-right mt-20"
|
|
386
388
|
mode="apply"
|
|
387
389
|
:disabled="!canSave"
|
|
@@ -0,0 +1,601 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { mapGetters } from 'vuex';
|
|
3
|
+
import isEmpty from 'lodash/isEmpty';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
CATALOG, SECRET, SERVICE, UI_PLUGIN, WORKLOAD_TYPES
|
|
7
|
+
} from '@shell/config/types';
|
|
8
|
+
import { UI_PLUGIN_LABELS, UI_PLUGIN_NAMESPACE } from '@shell/config/uiplugins';
|
|
9
|
+
import { TYPES as SECRET_TYPES } from '@shell/models/secret';
|
|
10
|
+
import { allHash } from '@shell/utils/promise';
|
|
11
|
+
|
|
12
|
+
import ResourceManager from '@shell/mixins/resource-manager';
|
|
13
|
+
|
|
14
|
+
import AsyncButton from '@shell/components/AsyncButton';
|
|
15
|
+
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
|
16
|
+
import Loading from '@shell/components/Loading.vue';
|
|
17
|
+
import { Banner } from '@components/Banner';
|
|
18
|
+
import { LabeledInput } from '@components/Form/LabeledInput';
|
|
19
|
+
|
|
20
|
+
const DEFAULT_DEPLOYMENT = {
|
|
21
|
+
type: WORKLOAD_TYPES.DEPLOYMENT,
|
|
22
|
+
metadata: {
|
|
23
|
+
name: '',
|
|
24
|
+
namespace: UI_PLUGIN_NAMESPACE,
|
|
25
|
+
labels: {}
|
|
26
|
+
},
|
|
27
|
+
spec: {
|
|
28
|
+
replicas: 1,
|
|
29
|
+
selector: { matchLabels: {} },
|
|
30
|
+
template: {
|
|
31
|
+
metadata: {
|
|
32
|
+
namespace: UI_PLUGIN_NAMESPACE,
|
|
33
|
+
labels: {}
|
|
34
|
+
},
|
|
35
|
+
spec: {
|
|
36
|
+
containers: [
|
|
37
|
+
{
|
|
38
|
+
image: '',
|
|
39
|
+
imagePullPolicy: 'Always',
|
|
40
|
+
name: 'container-0'
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
imagePullSecrets: [],
|
|
44
|
+
restartPolicy: 'Always'
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const DEFAULT_SERVICE = {
|
|
51
|
+
type: SERVICE,
|
|
52
|
+
metadata: {
|
|
53
|
+
name: '',
|
|
54
|
+
namespace: UI_PLUGIN_NAMESPACE,
|
|
55
|
+
labels: { [UI_PLUGIN_LABELS.CATALOG_IMAGE]: '' }
|
|
56
|
+
},
|
|
57
|
+
spec: {
|
|
58
|
+
ports: [
|
|
59
|
+
{
|
|
60
|
+
name: '',
|
|
61
|
+
port: 8080,
|
|
62
|
+
protocol: 'TCP',
|
|
63
|
+
targetPort: 8080
|
|
64
|
+
}
|
|
65
|
+
],
|
|
66
|
+
selector: { [UI_PLUGIN_LABELS.CATALOG_IMAGE]: '' },
|
|
67
|
+
type: 'ClusterIP'
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const DEFAULT_REPO = {
|
|
72
|
+
type: CATALOG.CLUSTER_REPO,
|
|
73
|
+
metadata: {
|
|
74
|
+
name: '',
|
|
75
|
+
labels: { [UI_PLUGIN_LABELS.CATALOG_IMAGE]: '' }
|
|
76
|
+
},
|
|
77
|
+
spec: { url: null }
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const initialState = () => {
|
|
81
|
+
const deploymentValues = structuredClone(DEFAULT_DEPLOYMENT);
|
|
82
|
+
const serviceValues = structuredClone(DEFAULT_SERVICE);
|
|
83
|
+
const repoValues = structuredClone(DEFAULT_REPO);
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
deploymentValues,
|
|
87
|
+
serviceValues,
|
|
88
|
+
repoValues,
|
|
89
|
+
canModifyName: true,
|
|
90
|
+
canModifyImage: true,
|
|
91
|
+
imagePullSecrets: [],
|
|
92
|
+
imagePullNamespacedSecrets: [],
|
|
93
|
+
extensionUrl: null,
|
|
94
|
+
extensionDeployment: null,
|
|
95
|
+
extensionSvc: null,
|
|
96
|
+
extensionRepo: null,
|
|
97
|
+
extensionCrd: null
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export default {
|
|
102
|
+
components: {
|
|
103
|
+
AsyncButton, Banner, LabeledInput, Loading, LabeledSelect
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
mixins: [ResourceManager],
|
|
107
|
+
|
|
108
|
+
async fetch() {
|
|
109
|
+
const hash = {};
|
|
110
|
+
|
|
111
|
+
if ( this.$store.getters['management/canList'](WORKLOAD_TYPES.DEPLOYMENT) ) {
|
|
112
|
+
hash.deployments = this.$store.dispatch('management/findAll', { type: WORKLOAD_TYPES.DEPLOYMENT });
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if ( this.$store.getters['management/canList'](SERVICE) ) {
|
|
116
|
+
hash.services = this.$store.dispatch('management/findAll', { type: SERVICE });
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
await allHash(hash);
|
|
120
|
+
|
|
121
|
+
this.secondaryResourceData = this.secondaryResourceDataConfig();
|
|
122
|
+
this.resourceManagerFetchSecondaryResources(this.secondaryResourceData);
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
data() {
|
|
126
|
+
return {
|
|
127
|
+
...initialState(),
|
|
128
|
+
secondaryResourceData: null
|
|
129
|
+
};
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
computed: {
|
|
133
|
+
...mapGetters({ allRepos: 'catalog/repos' }),
|
|
134
|
+
|
|
135
|
+
namespacedDeployments() {
|
|
136
|
+
return this.$store.getters['management/all'](WORKLOAD_TYPES.DEPLOYMENT).filter(dep => dep.metadata.namespace === UI_PLUGIN_NAMESPACE);
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
namespacedServices() {
|
|
140
|
+
return this.$store.getters['management/all'](SERVICE).filter(svc => svc.metadata.namespace === UI_PLUGIN_NAMESPACE);
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
methods: {
|
|
145
|
+
secondaryResourceDataConfig() {
|
|
146
|
+
return {
|
|
147
|
+
namespace: UI_PLUGIN_NAMESPACE,
|
|
148
|
+
data: {
|
|
149
|
+
[SECRET]: {
|
|
150
|
+
applyTo: [
|
|
151
|
+
{
|
|
152
|
+
var: 'imagePullNamespacedSecrets',
|
|
153
|
+
parsingFunc: (data) => {
|
|
154
|
+
return data.filter(secret => (secret._type === SECRET_TYPES.DOCKER || secret._type === SECRET_TYPES.DOCKER_JSON));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
]
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
showDialog() {
|
|
164
|
+
this.$modal.show('catalogLoadDialog');
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
closeDialog(result) {
|
|
168
|
+
this.$modal.hide('catalogLoadDialog');
|
|
169
|
+
this.$emit('closed', result);
|
|
170
|
+
|
|
171
|
+
// Reset state
|
|
172
|
+
Object.assign(this.$data, initialState());
|
|
173
|
+
this.secondaryResourceData = this.secondaryResourceDataConfig();
|
|
174
|
+
this.resourceManagerFetchSecondaryResources(this.secondaryResourceData);
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
async loadImage(btnCb) {
|
|
178
|
+
try {
|
|
179
|
+
if (!isEmpty(this.deploymentValues.spec.template.spec.containers[0].image)) {
|
|
180
|
+
const image = this.deploymentValues.spec.template.spec.containers[0].image;
|
|
181
|
+
const name = this.extractImageName(image);
|
|
182
|
+
|
|
183
|
+
if (name) {
|
|
184
|
+
// Create deployment
|
|
185
|
+
await this.loadDeployment(image, name, btnCb);
|
|
186
|
+
|
|
187
|
+
if (this.extensionDeployment) {
|
|
188
|
+
// Create service
|
|
189
|
+
await this.loadService(name, btnCb);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (this.extensionSvc) {
|
|
193
|
+
// Create helm repo
|
|
194
|
+
await this.loadRepo(name, btnCb);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (this.extensionRepo) {
|
|
198
|
+
// Create uiplugin crd
|
|
199
|
+
await this.loadPlugin(name, this.extensionUrl, image);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
btnCb(true);
|
|
203
|
+
} else {
|
|
204
|
+
throw new Error('Unable to determine image name');
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
} catch (e) {
|
|
208
|
+
this.handleGrowlError(e, true);
|
|
209
|
+
btnCb(false);
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
|
|
213
|
+
async loadDeployment(image, name, btnCb) {
|
|
214
|
+
const exists = this.namespacedDeployments.find(dep => dep.spec.template.spec.containers[0].image === image);
|
|
215
|
+
|
|
216
|
+
if (!exists) {
|
|
217
|
+
// Sets deploymentValues with name, labels, and imagePullSecrets
|
|
218
|
+
const deploymentValues = this.parseDeploymentValues(name);
|
|
219
|
+
|
|
220
|
+
this.extensionDeployment = await this.$store.dispatch('management/create', deploymentValues);
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
await this.extensionDeployment.save();
|
|
224
|
+
} catch (e) {
|
|
225
|
+
this.handleGrowlError(e, true);
|
|
226
|
+
btnCb(false);
|
|
227
|
+
}
|
|
228
|
+
} else {
|
|
229
|
+
const error = {
|
|
230
|
+
_statusText: this.t('plugins.manageCatalog.imageLoad.error.exists.deployment.title'),
|
|
231
|
+
message: this.t('plugins.manageCatalog.imageLoad.error.exists.deployment.message', { image })
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
this.handleGrowlError(error);
|
|
235
|
+
btnCb(false);
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
|
|
239
|
+
async loadService(name, btnCb) {
|
|
240
|
+
const serviceName = `${ name }-svc`;
|
|
241
|
+
const exists = this.namespacedServices.find(svc => svc.metadata.name === serviceName);
|
|
242
|
+
|
|
243
|
+
if (exists) {
|
|
244
|
+
const error = {
|
|
245
|
+
_statusText: this.t('plugins.manageCatalog.imageLoad.error.exists.service.title'),
|
|
246
|
+
message: this.t('plugins.manageCatalog.imageLoad.error.exists.service.message', { svc: serviceName })
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
this.handleGrowlError(error, true);
|
|
250
|
+
btnCb(false);
|
|
251
|
+
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Sets serviceValues with name, label, and selector
|
|
256
|
+
const serviceValues = this.parseServiceValues(name, serviceName);
|
|
257
|
+
const serviceResource = await this.$store.dispatch('management/create', serviceValues);
|
|
258
|
+
|
|
259
|
+
try {
|
|
260
|
+
await serviceResource.save();
|
|
261
|
+
} catch (e) {
|
|
262
|
+
this.handleGrowlError(e, true);
|
|
263
|
+
btnCb(false);
|
|
264
|
+
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
try {
|
|
269
|
+
this.extensionSvc = await this.$store.dispatch('management/find', {
|
|
270
|
+
type: SERVICE,
|
|
271
|
+
id: `${ UI_PLUGIN_NAMESPACE }/${ serviceResource.metadata.name }`,
|
|
272
|
+
namespace: UI_PLUGIN_NAMESPACE
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
if (this.extensionSvc) {
|
|
276
|
+
this.extensionUrl = `http://${ this.extensionSvc.spec.clusterIP }:${ this.extensionSvc.spec.ports[0].port }`;
|
|
277
|
+
} else {
|
|
278
|
+
throw new Error('Error fetching extension service');
|
|
279
|
+
}
|
|
280
|
+
} catch (e) {
|
|
281
|
+
this.handleGrowlError(e, true);
|
|
282
|
+
btnCb(false);
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
|
|
286
|
+
async loadRepo(name, btnCb) {
|
|
287
|
+
const chartName = `${ name }-charts`;
|
|
288
|
+
const exists = this.allRepos.find(repo => repo.metadata.name === chartName);
|
|
289
|
+
|
|
290
|
+
if (exists) {
|
|
291
|
+
const error = {
|
|
292
|
+
_statusText: this.t('plugins.manageCatalog.imageLoad.error.exists.repo.title'),
|
|
293
|
+
message: this.t('plugins.manageCatalog.imageLoad.error.exists.repo.message', { repo: chartName })
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
this.handleGrowlError(error);
|
|
297
|
+
btnCb(false);
|
|
298
|
+
this.clean();
|
|
299
|
+
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Set repoValues with name, label, and url
|
|
304
|
+
const repoValues = this.parseRepoValues(chartName);
|
|
305
|
+
|
|
306
|
+
this.extensionRepo = await this.$store.dispatch('management/create', repoValues);
|
|
307
|
+
|
|
308
|
+
try {
|
|
309
|
+
await this.extensionRepo.save();
|
|
310
|
+
} catch (e) {
|
|
311
|
+
this.handleGrowlError(e, true);
|
|
312
|
+
btnCb(false);
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
|
|
316
|
+
async loadPlugin(name, url, image, btnCb) {
|
|
317
|
+
// Try and parse version number from the image
|
|
318
|
+
const version = this.extractImageVersion(image) || 'latest';
|
|
319
|
+
|
|
320
|
+
if (!this.extractImageVersion(image)) {
|
|
321
|
+
this.$store.dispatch('growl/warning', {
|
|
322
|
+
title: this.t('plugins.manageCatalog.imageLoad.imageVersion.title'),
|
|
323
|
+
message: this.t('plugins.manageCatalog.imageLoad.imageVersion.message', { image }),
|
|
324
|
+
timeout: 4000,
|
|
325
|
+
}, { root: true });
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
let crdName = name;
|
|
329
|
+
|
|
330
|
+
const parts = name.split('-');
|
|
331
|
+
|
|
332
|
+
if (parts.length >= 2) {
|
|
333
|
+
crdName = parts.join('-');
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
this.extensionCrd = await this.$store.dispatch('management/create', {
|
|
337
|
+
type: UI_PLUGIN,
|
|
338
|
+
metadata: {
|
|
339
|
+
name,
|
|
340
|
+
namespace: UI_PLUGIN_NAMESPACE,
|
|
341
|
+
labels: {
|
|
342
|
+
[UI_PLUGIN_LABELS.CATALOG_IMAGE]: name,
|
|
343
|
+
[UI_PLUGIN_LABELS.REPOSITORY]: this.extensionRepo.metadata.name
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
spec: {
|
|
347
|
+
plugin: {
|
|
348
|
+
name: crdName,
|
|
349
|
+
version,
|
|
350
|
+
endpoint: url,
|
|
351
|
+
noCache: false,
|
|
352
|
+
metadata: { [UI_PLUGIN_LABELS.CATALOG]: 'true' }
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
try {
|
|
358
|
+
await this.extensionCrd.save({ url: `/v1/${ UI_PLUGIN }`, method: 'POST' });
|
|
359
|
+
|
|
360
|
+
this.closeDialog();
|
|
361
|
+
this.$store.dispatch('growl/success', {
|
|
362
|
+
title: this.t('plugins.manageCatalog.imageLoad.success.title', { name }),
|
|
363
|
+
message: this.t('plugins.manageCatalog.imageLoad.success.message'),
|
|
364
|
+
timeout: 4000,
|
|
365
|
+
}, { root: true });
|
|
366
|
+
} catch (e) {
|
|
367
|
+
this.handleGrowlError(e, true);
|
|
368
|
+
btnCb(false);
|
|
369
|
+
}
|
|
370
|
+
},
|
|
371
|
+
|
|
372
|
+
parseDeploymentValues(name) {
|
|
373
|
+
let out = {};
|
|
374
|
+
|
|
375
|
+
this.$set(this.deploymentValues.metadata, 'name', name);
|
|
376
|
+
|
|
377
|
+
const addLabel = { [UI_PLUGIN_LABELS.CATALOG_IMAGE]: name };
|
|
378
|
+
const addTo = ['metadata.labels', 'spec.selector.matchLabels', 'spec.template.metadata.labels'];
|
|
379
|
+
|
|
380
|
+
// Populates workloadselector labels
|
|
381
|
+
out = this.assignLabels(this.deploymentValues, addLabel, addTo);
|
|
382
|
+
|
|
383
|
+
if (this.imagePullSecrets.length) {
|
|
384
|
+
out.spec.template.spec.imagePullSecrets = this.imagePullSecrets.map((secret) => {
|
|
385
|
+
return { name: secret };
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return out;
|
|
390
|
+
},
|
|
391
|
+
|
|
392
|
+
parseServiceValues(name, serviceName) {
|
|
393
|
+
const out = this.serviceValues;
|
|
394
|
+
|
|
395
|
+
out.metadata.name = serviceName;
|
|
396
|
+
out.metadata.labels[UI_PLUGIN_LABELS.CATALOG_IMAGE] = name;
|
|
397
|
+
out.spec.selector[UI_PLUGIN_LABELS.CATALOG_IMAGE] = name;
|
|
398
|
+
|
|
399
|
+
return out;
|
|
400
|
+
},
|
|
401
|
+
|
|
402
|
+
parseRepoValues(chartName) {
|
|
403
|
+
const out = this.repoValues;
|
|
404
|
+
|
|
405
|
+
out.metadata.name = chartName;
|
|
406
|
+
out.metadata.labels[UI_PLUGIN_LABELS.CATALOG_IMAGE] = this.deploymentValues.metadata.name;
|
|
407
|
+
out.spec.url = this.extensionUrl;
|
|
408
|
+
|
|
409
|
+
return out;
|
|
410
|
+
},
|
|
411
|
+
|
|
412
|
+
assignLabels(source, labels, args) {
|
|
413
|
+
for (let i = 0; i < args.length; i++) {
|
|
414
|
+
const path = args[i].split('.');
|
|
415
|
+
let currentObj = source;
|
|
416
|
+
|
|
417
|
+
for (let j = 0; j < path.length - 1; j++) {
|
|
418
|
+
currentObj = currentObj[path[j]];
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
currentObj[path[path.length - 1]] = labels;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return source;
|
|
425
|
+
},
|
|
426
|
+
|
|
427
|
+
extractImageVersion(image) {
|
|
428
|
+
// Returns the version number with optional pre-release identifiers
|
|
429
|
+
const regex = /:(\d+\.\d+\.\d+([-\w\d]+)*)$/;
|
|
430
|
+
const matches = regex.exec(image);
|
|
431
|
+
|
|
432
|
+
if (matches) {
|
|
433
|
+
return matches[1];
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return null;
|
|
437
|
+
},
|
|
438
|
+
|
|
439
|
+
extractImageName(image) {
|
|
440
|
+
// Returns the name within the image that prefixes the version number
|
|
441
|
+
const regex = /\/([^/:]+)(?::\d+\.\d+\.\d+(-[a-zA-Z0-9]+)?|$)/;
|
|
442
|
+
const matches = regex.exec(image);
|
|
443
|
+
|
|
444
|
+
if (matches) {
|
|
445
|
+
return matches[1];
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
return null;
|
|
449
|
+
},
|
|
450
|
+
|
|
451
|
+
clean() {
|
|
452
|
+
// Remove failed resources
|
|
453
|
+
if (this.extensionDeployment) {
|
|
454
|
+
this.extensionDeployment.remove();
|
|
455
|
+
}
|
|
456
|
+
if (this.extensionSvc) {
|
|
457
|
+
this.extensionSvc.remove();
|
|
458
|
+
}
|
|
459
|
+
if (this.extensionRepo) {
|
|
460
|
+
this.extensionRepo.remove();
|
|
461
|
+
}
|
|
462
|
+
if (this.extensionCrd) {
|
|
463
|
+
this.extensionCrd.remove();
|
|
464
|
+
}
|
|
465
|
+
},
|
|
466
|
+
|
|
467
|
+
handleGrowlError(e, clean = false) {
|
|
468
|
+
const error = e?.data || e;
|
|
469
|
+
|
|
470
|
+
this.$store.dispatch('growl/error', {
|
|
471
|
+
title: error._statusText,
|
|
472
|
+
message: error.message,
|
|
473
|
+
timeout: 5000,
|
|
474
|
+
}, { root: true });
|
|
475
|
+
|
|
476
|
+
if (clean) {
|
|
477
|
+
this.clean();
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
</script>
|
|
483
|
+
|
|
484
|
+
<template>
|
|
485
|
+
<modal
|
|
486
|
+
name="catalogLoadDialog"
|
|
487
|
+
height="auto"
|
|
488
|
+
:scrollable="true"
|
|
489
|
+
@closed="closeDialog()"
|
|
490
|
+
>
|
|
491
|
+
<Loading
|
|
492
|
+
v-if="$fetchState.loading"
|
|
493
|
+
mode="relative"
|
|
494
|
+
/>
|
|
495
|
+
<div
|
|
496
|
+
v-else
|
|
497
|
+
class="plugin-install-dialog"
|
|
498
|
+
>
|
|
499
|
+
<template>
|
|
500
|
+
<div>
|
|
501
|
+
<h4>
|
|
502
|
+
{{ t('plugins.manageCatalog.imageLoad.load') }}
|
|
503
|
+
</h4>
|
|
504
|
+
<p>
|
|
505
|
+
{{ t('plugins.manageCatalog.imageLoad.prompt') }}
|
|
506
|
+
</p>
|
|
507
|
+
|
|
508
|
+
<div class="custom mt-10">
|
|
509
|
+
<Banner
|
|
510
|
+
color="info"
|
|
511
|
+
:label="t('plugins.manageCatalog.imageLoad.banner')"
|
|
512
|
+
class="mt-10"
|
|
513
|
+
/>
|
|
514
|
+
</div>
|
|
515
|
+
|
|
516
|
+
<div class="custom mt-10">
|
|
517
|
+
<div class="fields">
|
|
518
|
+
<LabeledInput
|
|
519
|
+
v-model.trim="deploymentValues.spec.template.spec.containers[0].image"
|
|
520
|
+
label-key="plugins.manageCatalog.imageLoad.fields.image.label"
|
|
521
|
+
placeholder-key="plugins.manageCatalog.imageLoad.fields.image.placeholder"
|
|
522
|
+
/>
|
|
523
|
+
</div>
|
|
524
|
+
</div>
|
|
525
|
+
<div class="custom mt-10">
|
|
526
|
+
<div class="fields">
|
|
527
|
+
<LabeledSelect
|
|
528
|
+
v-model="imagePullSecrets"
|
|
529
|
+
:label="t('workload.container.imagePullSecrets')"
|
|
530
|
+
:multiple="true"
|
|
531
|
+
:taggable="true"
|
|
532
|
+
:options="imagePullNamespacedSecrets"
|
|
533
|
+
option-label="metadata.name"
|
|
534
|
+
:reduce="service => service.metadata.name"
|
|
535
|
+
/>
|
|
536
|
+
<Banner
|
|
537
|
+
color="warning"
|
|
538
|
+
class="mt-10"
|
|
539
|
+
>
|
|
540
|
+
<span v-clean-html="t('plugins.manageCatalog.imageLoad.fields.secrets.banner', {}, true)" />
|
|
541
|
+
</Banner>
|
|
542
|
+
</div>
|
|
543
|
+
</div>
|
|
544
|
+
</div>
|
|
545
|
+
</template>
|
|
546
|
+
|
|
547
|
+
<div class="custom mt-10">
|
|
548
|
+
<div class="fields">
|
|
549
|
+
<div class="dialog-buttons mt-20">
|
|
550
|
+
<button
|
|
551
|
+
class="btn role-secondary"
|
|
552
|
+
data-testid="image-load-ext-modal-cancel-btn"
|
|
553
|
+
@click="closeDialog()"
|
|
554
|
+
>
|
|
555
|
+
{{ t('generic.cancel') }}
|
|
556
|
+
</button>
|
|
557
|
+
<AsyncButton
|
|
558
|
+
mode="load"
|
|
559
|
+
data-testid="image-load-ext-modal-install-btn"
|
|
560
|
+
@click="loadImage"
|
|
561
|
+
/>
|
|
562
|
+
</div>
|
|
563
|
+
</div>
|
|
564
|
+
</div>
|
|
565
|
+
</div>
|
|
566
|
+
</modal>
|
|
567
|
+
</template>
|
|
568
|
+
|
|
569
|
+
<style lang="scss" scoped>
|
|
570
|
+
.plugin-install-dialog {
|
|
571
|
+
padding: 10px;
|
|
572
|
+
|
|
573
|
+
h4 {
|
|
574
|
+
font-weight: bold;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
.dialog-panel {
|
|
578
|
+
display: flex;
|
|
579
|
+
flex-direction: column;
|
|
580
|
+
min-height: 100px;
|
|
581
|
+
|
|
582
|
+
p {
|
|
583
|
+
margin-bottom: 5px;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
.dialog-info {
|
|
587
|
+
flex: 1;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
.dialog-buttons {
|
|
592
|
+
display: flex;
|
|
593
|
+
justify-content: flex-end;
|
|
594
|
+
margin-top: 10px;
|
|
595
|
+
|
|
596
|
+
> *:not(:last-child) {
|
|
597
|
+
margin-right: 10px;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
</style>
|