Skip to content

Commit db527df

Browse files
Wtkui 305 targetsvc selection (#79)
* WKTUI-305 Add support for querying target services and ports * retrieve ports for existing route * preload existing target ports for drop down * update message * doc update * get target service details cleanup Co-authored-by: Robert Patrick <[email protected]>
1 parent b8ba0a3 commit db527df

File tree

7 files changed

+117
-47
lines changed

7 files changed

+117
-47
lines changed

documentation/staging/content/navigate/kubernetes/k8s-ingress-controller.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ When editing a route:
8686
- Use the `Name` field to set the route name.
8787
- Use the `Virtual Host` and `Path Expression` fields to define the matching rules that determine which requests match this route.
8888
- All requests in the defined rules are routed to the service specified by the `Target Service` field that resides in the namespace specified by the read-only
89-
`Target Service Namespace` field and the port specified by the `Target Port` field.
89+
`Target Service Namespace` field and the port specified by the `Target Port` field. Once the domain has been deployed, you can also select the values from the drop down list.
9090
- Specify the `Transport Option` for the ingress route:
9191
* Select `Plain HTTP` for unencrypted traffic from the client through the ingress controller to the target service.
9292
* Select `SSL terminate at ingress controller` for SSL

electron/app/js/kubectlUtils.js

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -538,13 +538,12 @@ async function createOrReplaceTLSSecret(kubectlExe, namespace, secret, key, cert
538538
return createOrReplaceSecret(kubectlExe, namespace, secret, createArgs, errorKeys, options);
539539
}
540540

541-
async function getServiceDetails(kubectlExe, ingressNamespace, serviceName, options) {
542-
let getArgs = [];
543-
if (serviceName === '') {
544-
getArgs = [ 'get', 'services', '-n', ingressNamespace, '--output=json' ];
545-
} else {
546-
getArgs = [ 'get', 'services', serviceName, '-n', ingressNamespace, '--output=json' ];
541+
async function getServiceDetails(kubectlExe, namespace, serviceName, options) {
542+
const getArgs = [ 'get', 'services' ];
543+
if (serviceName) {
544+
getArgs.push(serviceName);
547545
}
546+
getArgs.push('-n', namespace, '--output=json');
548547
const httpsProxyUrl = getHttpsProxyUrl();
549548
const bypassProxyHosts = getBypassProxyHosts();
550549

@@ -554,13 +553,13 @@ async function getServiceDetails(kubectlExe, ingressNamespace, serviceName, opti
554553
};
555554

556555
return new Promise(resolve => {
557-
executeFileCommand(kubectlExe, getArgs, env).then((serviceDetails) => {
558-
results.serviceDetails = JSON.parse(serviceDetails);
556+
executeFileCommand(kubectlExe, getArgs, env).then((serviceDetailsJson) => {
557+
results.serviceDetails = JSON.parse(serviceDetailsJson);
559558
resolve(results);
560559
}).catch(err => {
561560
results.isSuccess = false;
562561
results.reason = i18n.t('kubectl-get-service-details-error-message',
563-
{namespace: ingressNamespace, error: getErrorMessage(err) });
562+
{namespace: namespace, error: getErrorMessage(err) });
564563
resolve(results);
565564
});
566565
});

electron/app/locales/en/webui.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,9 @@
678678
"ingress-design-ingress-route-traefik-mw-label": "Traefik Middleware",
679679
"ingress-design-ingress-route-traefik-mw-help": "Customize Traefik Middlewares Object",
680680

681+
"ingress-design-ingress-routes-getting-target-service-title" : "Retrieving existing domain service details",
682+
"ingress-design-ingress-routes-getting-target-service-error-message": "Failed to get existing domain service details from Kubernetes namespace {{namespace}}: {{error}}.",
683+
681684
"ingress-design-ingress-route-name-field-validation-error": "Route {{routeName}}",
682685
"ingress-design-ingress-route-field-validation-error": "Route: {{routeName}}, Field: {{fieldName}}",
683686
"ingress-design-ingress-route-field-tls-config-error": "The route '{{routeName}}' has the '{{fieldName}}' set to '{{tlsOption}}' but the '{{specifyTlsSecretFieldName}}' field is disabled.",

webui/src/js/viewModels/ingress-design-view-impl.js

Lines changed: 48 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66

77
define(['utils/i18n', 'accUtils', 'knockout', 'ojs/ojarraydataprovider',
88
'ojs/ojbufferingdataprovider', 'models/wkt-project', 'utils/dialog-helper',
9-
'utils/ingress-routes-updater', 'utils/view-helper', 'ojs/ojtreeview',
9+
'utils/ingress-routes-updater', 'utils/view-helper', 'utils/k8s-helper', 'ojs/ojtreeview',
1010
'ojs/ojformlayout', 'ojs/ojinputtext', 'ojs/ojcollapsible', 'ojs/ojselectsingle', 'ojs/ojswitch', 'ojs/ojtable',
1111
'ojs/ojcheckboxset'
1212
],
1313
function(i18n, accUtils, ko, ArrayDataProvider, BufferingDataProvider, project, dialogHelper,
14-
ingressRouteUpdater, viewHelper) {
14+
ingressRouteUpdater, viewHelper, k8sHelper) {
1515

1616
function IngressDesignViewModel() {
1717

@@ -209,31 +209,58 @@ function(i18n, accUtils, ko, ArrayDataProvider, BufferingDataProvider, project,
209209
window.api.ipc.invoke('show-error-message', title, message);
210210
};
211211

212-
this.handleEditRoute = (event, context) => {
212+
async function getTargetServiceDetails (project) {
213+
214+
const kubectlExe = k8sHelper.getKubectlExe();
215+
const kubectlOptions = k8sHelper.getKubectlOptions();
216+
const results = await window.api.ipc.invoke('k8s-get-service-details',
217+
kubectlExe, project.k8sDomain.kubernetesNamespace.value, '', kubectlOptions);
218+
let serviceLists = {};
219+
if (results.isSuccess) {
220+
for (const item of results.serviceDetails.items) {
221+
serviceLists[item.metadata.name] = { ports: item.spec.ports};
222+
}
223+
} else {
224+
const errTitle = i18n.t('ingress-design-ingress-routes-getting-target-service-title');
225+
const errMessage = i18n.t('ingress-design-ingress-routes-getting-target-service-error-message', {
226+
error: results.reason,
227+
namespace: namespace
228+
});
229+
await window.api.ipc.invoke('show-error-message', errTitle, errMessage);
230+
return Promise.resolve(false);
231+
}
232+
return Promise.resolve( { serviceList: serviceLists});
233+
};
234+
235+
this.handleEditRoute = async (event, context) => {
213236
// using context.item.data directly was causing problems
214237
// when project data was reloaded with matching UIDs.
215238
const index = context.item.index;
216239
let route = this.routes.observable()[index];
217-
const options = {route: route};
218-
219-
dialogHelper.promptDialog('route-edit-dialog', options)
220-
.then(result => {
221-
222-
// no result indicates operation was cancelled
223-
if (result) {
224-
let changed = false;
225-
project.ingress.ingressRouteKeys.forEach(key => {
226-
if ((key !== 'uid') && result.hasOwnProperty(key)) {
227-
route[key] = result[key];
228-
changed = true;
240+
getTargetServiceDetails(this.project).then( svc => {
241+
const options = {route: route, serviceList: svc.serviceList};
242+
dialogHelper.promptDialog('route-edit-dialog', options)
243+
.then(result => {
244+
245+
// no result indicates operation was cancelled
246+
if (result) {
247+
let changed = false;
248+
project.ingress.ingressRouteKeys.forEach(key => {
249+
if ((key !== 'uid') && result.hasOwnProperty(key)) {
250+
route[key] = result[key];
251+
changed = true;
252+
}
253+
});
254+
255+
if (changed) {
256+
this.routes.observable.replace(route, route);
229257
}
230-
});
231-
232-
if (changed) {
233-
this.routes.observable.replace(route, route);
234258
}
235-
}
236-
});
259+
});
260+
}
261+
);
262+
263+
237264
};
238265

239266
this.handleCancel = () => {

webui/src/js/viewModels/ingress-design-view.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@
55
*/
66

77
define(['viewModels/ingress-design-view-impl','utils/i18n', 'accUtils', 'knockout', 'ojs/ojarraydataprovider',
8-
'ojs/ojbufferingdataprovider', 'models/wkt-project', 'utils/dialog-helper', 'utils/view-helper',
8+
'ojs/ojbufferingdataprovider', 'models/wkt-project', 'utils/dialog-helper', 'utils/view-helper', 'utils/k8s-helper',
99
'ojs/ojtreeview', 'ojs/ojformlayout', 'ojs/ojinputtext',
1010
'ojs/ojcollapsible', 'ojs/ojselectsingle', 'ojs/ojswitch', 'ojs/ojtable', 'ojs/ojcheckboxset'
1111
],
1212
function(IngressDesignViewModel, i18n, accUtils, ko, ArrayDataProvider, BufferingDataProvider, project, dialogHelper,
13-
viewHelper) {
13+
viewHelper, k8sHelper) {
1414

1515
return function() {
16-
return new IngressDesignViewModel(i18n, accUtils, ko, ArrayDataProvider, BufferingDataProvider, project,
17-
dialogHelper, viewHelper);
16+
return new IngressDesignViewModel(i18n, accUtils, ko, ArrayDataProvider, BufferingDataProvider, project,
17+
dialogHelper, viewHelper, k8sHelper);
1818
};
1919
});

webui/src/js/viewModels/route-edit-dialog.js

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
*/
66
'use strict';
77

8-
define(['accUtils', 'knockout', 'utils/i18n', 'models/wkt-project', 'utils/view-helper', 'ojs/ojarraydataprovider',
8+
define(['accUtils', 'knockout', 'utils/i18n', 'models/wkt-project', 'utils/view-helper', 'ojs/ojarraydataprovider',
99
'ojs/ojbufferingdataprovider', 'utils/observable-properties', 'ojs/ojconverter-number', 'ojs/ojinputtext',
10-
'ojs/ojlabel', 'ojs/ojbutton', 'ojs/ojdialog', 'ojs/ojformlayout', 'ojs/ojvalidationgroup'],
10+
'ojs/ojlabel', 'ojs/ojbutton', 'ojs/ojdialog', 'ojs/ojformlayout', 'ojs/ojvalidationgroup', 'ojs/ojselectcombobox'],
1111
function(accUtils, ko, i18n, project, viewHelper, ArrayDataProvider, BufferingDataProvider, props, ojConverterNumber) {
1212
function RouteEditDialogModel(args) {
1313
const DIALOG_SELECTOR = '#routeEditDialog';
@@ -38,9 +38,37 @@ function(accUtils, ko, i18n, project, viewHelper, ArrayDataProvider, BufferingDa
3838

3939
this.project = project;
4040
this.route = args.route;
41+
this.serviceList = args.serviceList;
42+
43+
this.buildTargetSvcNames = () => {
44+
let options = [];
45+
for(var name in this.serviceList){
46+
options.push( { id : name, value: name, text: name});
47+
}
48+
return options;
49+
};
50+
51+
this.buildTargetSvcPorts = (svcName) => {
52+
let options = [];
53+
if (this.serviceList[svcName]) {
54+
for (const port of this.serviceList[svcName].ports) {
55+
options.push( { id : port.port, value: port.port, text: port.port});
56+
}
57+
}
58+
59+
return options;
60+
};
61+
4162
this.askIfConsoleSvc = ko.observable(this.route.isConsoleService);
63+
this.targetSvcNames = this.buildTargetSvcNames();
64+
this.targetSvcPorts = ko.observableArray([] );
65+
66+
if (this.route.targetService) {
67+
this.targetSvcPorts = this.buildTargetSvcPorts(this.route.targetService);
68+
}
4269

4370
this.savedAnnotations = args.route.annotations || {};
71+
this.targetServicePorts = new ArrayDataProvider(this.targetSvcPorts, {keyAttributes: 'id'});
4472

4573
this.tlsOptions = [
4674
{ id: 'plain', value: 'plain', text: this.labelMapper('route-tlsoption-plain') },
@@ -156,6 +184,14 @@ function(accUtils, ko, i18n, project, viewHelper, ArrayDataProvider, BufferingDa
156184
}
157185
};
158186

187+
this.targetSvcNameChanged = (event) => {
188+
this.targetSvcPorts.removeAll();
189+
const list = this.buildTargetSvcPorts(event.detail.value);
190+
for (const item of list) {
191+
this.targetSvcPorts.push(item);
192+
}
193+
};
194+
159195
this.okInput = () => {
160196
let tracker = document.getElementById('ingressTracker');
161197
if (tracker.valid !== 'valid') {

webui/src/js/views/route-edit-dialog.html

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,22 @@
3333
readonly="true"
3434
help.instruction="[[labelMapper('route-targetservicenamespace-help')]]">
3535
</oj-input-text>
36-
<oj-input-text label-hint="[[labelMapper('route-targetservice-label')]]"
37-
help.instruction="[[labelMapper('route-targetservice-help')]]"
38-
value="{{targetService.observable}}"
39-
validators="[[project.ingress.validators.k8sNameValidator]]">
40-
</oj-input-text>
41-
<oj-input-number label-hint="[[labelMapper('route-targetport-label')]]"
42-
converter="[[portNumberConverter]]"
43-
value="{{targetPort.observable}}"
36+
37+
<oj-combobox-one id="targetsvc" value="{{targetService.observable}}"
38+
label-hint="[[labelMapper('route-targetservice-label')]]"
39+
help.instruction="[[labelMapper('route-targetservice-help')]]"
40+
validators="[[project.ingress.validators.k8sNameValidator]]"
41+
options="[[targetSvcNames]]"
42+
on-value-changed = "[[targetSvcNameChanged]]">
43+
</oj-combobox-one>
44+
45+
<oj-combobox-one id="targetport" value="{{targetPort.observable}}"
46+
label-hint="[[labelMapper('route-targetport-label')]]"
47+
validators="[[project.ingress.validators.targetPortValidator]]"
4448
help.instruction="[[labelMapper('route-targetport-help')]]"
45-
validators="[[project.ingress.validators.targetPortValidator]]">
46-
</oj-input-number>
49+
options="[[targetServicePorts]]"
50+
>
51+
</oj-combobox-one>
4752

4853
<oj-radioset id="tlsOptionId"
4954
label-hint="[[labelMapper('route-tls-label')]]"

0 commit comments

Comments
 (0)