diff --git a/Jenkinsfile b/Jenkinsfile
index 251fc9589..d9bbaef88 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -1,9 +1,12 @@
/*
- * Copyright (c) 2021, 2022, Oracle and/or its affiliates.
+ * Copyright (c) 2021, 2023, Oracle and/or its affiliates.
* Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
*/
pipeline {
agent { label 'ol8' }
+ options {
+ timeout(time: 480, unit: 'MINUTES')
+ }
environment {
WKTUI_PROXY = "${env.ORACLE_HTTP_PROXY}"
ELECTRON_GET_USE_PROXY = "true"
@@ -54,6 +57,9 @@ pipeline {
parallel {
stage('Linux Build') {
agent { label 'ol8' }
+ options {
+ timeout(time: 300, unit: 'MINUTES')
+ }
environment {
linux_node_dir_name = "node-v${node_version}-linux-x64"
linux_node_installer = "${linux_node_dir_name}.tar.gz"
@@ -194,6 +200,9 @@ pipeline {
}
stage('MacOS Build') {
agent { label('macosx') }
+ options {
+ timeout(time: 300, unit: 'MINUTES')
+ }
environment {
mac_node_dir_name = "node-v${node_version}-darwin-x64"
mac_node_installer = "node-v${node_version}-darwin-x64.tar.gz"
@@ -301,6 +310,9 @@ pipeline {
}
stage('Windows Build') {
agent { label 'windows'}
+ options {
+ timeout(time: 300, unit: 'MINUTES')
+ }
environment {
windows_node_dir_name = "node-v${node_version}-win-x64"
windows_node_installer = "node-v${node_version}-win-x64.zip"
diff --git a/electron/app/js/ipcRendererPreload.js b/electron/app/js/ipcRendererPreload.js
index 75b750c2a..f02f36895 100644
--- a/electron/app/js/ipcRendererPreload.js
+++ b/electron/app/js/ipcRendererPreload.js
@@ -180,6 +180,7 @@ contextBridge.exposeInMainWorld(
'do-push-image',
'kubectl-get-current-context',
'kubectl-set-current-context',
+ 'kubectl-get-contexts',
'validate-kubectl-exe',
'kubectl-verify-connection',
'validate-helm-exe',
diff --git a/electron/app/js/kubectlUtils.js b/electron/app/js/kubectlUtils.js
index 5346514d5..d93c221ba 100644
--- a/electron/app/js/kubectlUtils.js
+++ b/electron/app/js/kubectlUtils.js
@@ -1,6 +1,6 @@
/**
* @license
- * Copyright (c) 2021, 2022, Oracle and/or its affiliates.
+ * Copyright (c) 2021, 2023, Oracle and/or its affiliates.
* Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
*/
'use strict';
@@ -90,6 +90,30 @@ async function setCurrentContext(kubectlExe, context, options) {
});
}
+async function getContexts(kubectlExe, options) {
+ const args = [ 'config', 'get-contexts', '--output=name' ];
+ const httpsProxyUrl = getHttpsProxyUrl();
+ const bypassProxyHosts = getBypassProxyHosts();
+ const env = getKubectlEnvironment(options, httpsProxyUrl, bypassProxyHosts);
+ const results = {
+ isSuccess: true
+ };
+
+ return new Promise(resolve => {
+ executeFileCommand(kubectlExe, args, env).then(currentContext => {
+ results['contexts'] = currentContext.trim().split('\n');
+ getLogger().debug('getContexts result = %s', results['contexts']);
+ resolve(results);
+ }).catch(err => {
+ // kubectl config get-contexts returns an error if the config file doesn't exist...
+ results.isSuccess = false;
+ results.reason =
+ i18n.t('kubectl-get-contexts-error-message',{ error: getErrorMessage(err) });
+ resolve(results);
+ });
+ });
+}
+
async function getK8sConfigView(kubectlExe, options) {
const args = [ 'config', 'view', '-o', 'json'];
const httpsProxyUrl = getHttpsProxyUrl();
@@ -1018,6 +1042,78 @@ async function doCreateSecret(kubectlExe, createArgs, env, namespace, secret, re
});
}
+async function getVerrazzanoIngressExternalAddress(kubectlExe, options) {
+ const gatewayService = 'istio-ingressgateway';
+ const gatewayNamespace = 'istio-system';
+
+ const args = [ 'get', 'service', gatewayService, '-n', gatewayNamespace, '-o', 'json'];
+ const httpsProxyUrl = getHttpsProxyUrl();
+ const bypassProxyHosts = getBypassProxyHosts();
+ const env = getKubectlEnvironment(options, httpsProxyUrl, bypassProxyHosts);
+ const results = {
+ isSuccess: true
+ };
+
+ return new Promise(resolve => {
+ executeFileCommand(kubectlExe, args, env).then(serviceJson => {
+ const service = JSON.parse(serviceJson);
+ const ingressArray = service.status?.loadBalancer?.ingress;
+
+ if (Array.isArray(ingressArray) && ingressArray.length > 0) {
+ results.externalAddress = ingressArray[0].ip;
+ }
+ if (!results.externalAddress) {
+ results.reason = i18n.t('kubectl-get-vz-ingress-external-address-not-found',
+ { gatewayService, gatewayNamespace });
+ }
+ resolve(results);
+ }).catch(err => {
+ results.isSuccess = false;
+ results.reason =
+ i18n.t('kubectl-get-vz-ingress-external-address-error-message',
+ { gatewayService, gatewayNamespace, error: getErrorMessage(err) });
+ resolve(results);
+ });
+ });
+}
+
+async function getVerrazzanoApplicationHostnames(kubectlExe, applicationName, applicationNamespace, options) {
+ const gatewayType = 'gateways.networking.istio.io';
+ const appGatewayName = `${applicationNamespace}-${applicationName}-gw`;
+
+ const args = [ 'get', gatewayType, appGatewayName, '-n', applicationNamespace, '-o', 'json'];
+ const httpsProxyUrl = getHttpsProxyUrl();
+ const bypassProxyHosts = getBypassProxyHosts();
+ const env = getKubectlEnvironment(options, httpsProxyUrl, bypassProxyHosts);
+ const results = {
+ isSuccess: true
+ };
+
+ return new Promise(resolve => {
+ executeFileCommand(kubectlExe, args, env).then(gatewayJson => {
+ const gateway = JSON.parse(gatewayJson);
+ const serversArray = gateway.spec?.servers;
+ if (Array.isArray(serversArray) && serversArray.length > 0) {
+ const hostsArray = serversArray[0].hosts;
+ if (Array.isArray(hostsArray) && hostsArray.length > 0) {
+ results.hostnames = hostsArray;
+ }
+ }
+ if (results.hostnames) {
+ results.reason = i18n.t('kubectl-get-vz-app-hostnames-not-found',
+ { appName: applicationName, appNamespace: applicationNamespace });
+ }
+ resolve(results);
+ }).catch(err => {
+ results.isSuccess = false;
+ results.reason =
+ i18n.t('kubectl-get-vz-app-hostnames-error-message',
+ { appName: applicationName, appNamespace: applicationNamespace, error: getErrorMessage(err) });
+ resolve(results);
+ });
+ });
+}
+
function maskPasswordInCommand(err) {
// How about other cases?
return err.replace(/--docker-password=[^\s]+/, '--docker-password=*****');
@@ -1075,6 +1171,7 @@ module.exports = {
createOrReplacePullSecret,
createOrReplaceTLSSecret,
createServiceAccountIfNotExists,
+ getContexts,
getCurrentContext,
getOperatorVersion,
isOperatorAlreadyInstalled,
@@ -1086,6 +1183,8 @@ module.exports = {
getIngresses,
getK8sConfigView,
getK8sClusterInfo,
+ getVerrazzanoApplicationHostnames,
+ getVerrazzanoIngressExternalAddress,
getWkoDomainStatus,
getApplicationStatus,
getOperatorStatus,
diff --git a/electron/app/js/vzUtils.js b/electron/app/js/vzUtils.js
index a32eb264a..8455f792a 100644
--- a/electron/app/js/vzUtils.js
+++ b/electron/app/js/vzUtils.js
@@ -1,6 +1,6 @@
/**
* @license
- * Copyright (c) 2022, Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023, Oracle and/or its affiliates.
* Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
*/
const kubectlUtils = require('./kubectlUtils');
diff --git a/electron/app/locales/en/electron.json b/electron/app/locales/en/electron.json
index 6e4987157..67279e05e 100644
--- a/electron/app/locales/en/electron.json
+++ b/electron/app/locales/en/electron.json
@@ -281,6 +281,7 @@
"kubectl-current-context-error-message": "Failed to get the current kubectl cluster context: {{error}}",
"kubectl-use-context-error-message": "Failed to change the current kubectl cluster context to {{context}}: {{error}}",
+ "kubectl-get-contexts-error-message": "Failed to get the available kubectl contexts: {{error}}",
"kubectl-not-specified-error-message": "Kubernetes client path was not provided",
"kubectl-not-exists-error-message": "Kubernetes client {{filePath}} does not exist",
"kubectl-exists-failed-error-message": "Unable to verify Kubernetes client {{filePath}} exists: {{error}}",
@@ -322,6 +323,8 @@
"kubectl-get-vz-application-status-error-message": "Unable to get Verrazzano application status: {{error}}",
"kubectl-get-operator-version-not-found-error-message": "Failed to find the operator version from its log entries",
"kubectl-verify-vz-components-deployed-error-message": "Unable to find one or more components in namespace {{namespace}}: {{missingComponents}}",
+ "kubectl-get-vz-ingress-external-address-not-found": "Unable to find the External IP Address in the Istio Gateway service {{gatewayService}} in Kubernetes namespace {{gatewayNamespace}}",
+ "kubectl-get-vz-ingress-external-address-error-message": "Failed to find the External IP Address in the Istio Gateway service {{gatewayService}} in Kubernetes namespace {{gatewayNamespace}}: {{error}}",
"helm-not-specified-error-message": "Helm executable path was not provided",
"helm-not-exists-error-message": "Helm executable {{filePath}} does not exist",
diff --git a/electron/app/locales/en/webui.json b/electron/app/locales/en/webui.json
index 24268f727..f6681d46c 100644
--- a/electron/app/locales/en/webui.json
+++ b/electron/app/locales/en/webui.json
@@ -39,6 +39,23 @@
"kubectl-form-name": "Kubernetes Client Configuration",
"kubectl-title": "Configure the Kubernetes Client",
+ "kubectl-instructions-heading": "Instructions",
+ "kubectl-executables-heading": "Client Executables",
+ "kubectl-wko-connectivity-heading": "Kubernetes Cluster Connectivity Configuration",
+ "kubectl-vz-connectivity-heading": "Verrazzano Admin Cluster Connectivity Configuration",
+ "kubectl-vz-managed-clusters-heading": "Verrazzano Managed Clusters Connectivity Configuration",
+ "kubectl-vz-managed-clusters-table-aria-label": "Verrazzano Managed Clusters Connectivity Table",
+ "kubectl-vz-managed-clusters-add-row-tooltip": "Add a new Verrazzano Managed Cluster Connectivity row",
+ "kubectl-vz-managed-clusters-delete-row-tooltip": "Delete the Verrazzano Managed Cluster Connectivity row",
+ "kubectl-vz-managed-cluster-name-heading": "Verrazzano Managed Cluster Name",
+ "kubectl-vz-managed-cluster-name-help": "Name of the Verrazzano Managed Cluster.",
+ "kubectl-vz-managed-cluster-kubeconfig-heading": "Kubernetes Client Config File(s)",
+ "kubectl-vz-managed-cluster-kubeconfig-help": "The Kubernetes client configuration file(s) to use for connecting to the Verrazzano Managed Cluster.",
+ "kubectl-vz-managed-cluster-kubecontext-heading": "Kubectl Config Context to Use",
+ "kubectl-vz-managed-cluster-kubecontext-help": "The Kubernetes client configuration context name to use for connecting to the Verrazzano Managed Cluster.",
+ "kubectl-vz-managed-cluster-choose-kubecontext-tooltip": "Choose the Kubernetes cluster context for the Verrazzano Managed Cluster from the available contexts in the Kubernetes Client Config File",
+ "kubectl-vz-managed-cluster-get-context-tooltip": "Get the current Kubernetes cluster context for the Verrazzano Managed Cluster from the Kubernetes Client Config File",
+
"kubectl-k8s-flavor-label": "Kubernetes Cluster Type",
"kubectl-k8s-flavor-help": "The Kubernetes cluster provider type where the Oracle Fusion Middleware domain will be deployed.",
"kubectl-exe-file-path-label": "Kubectl Executable to Use",
@@ -48,6 +65,8 @@
"kubectl-config-file-help": "The Kubernetes client configuration file(s) to use. Leave this empty to use the default location.",
"kubectl-config-file-tooltip": "Choose the Kubernetes Client Config File(s)",
"kubectl-config-context-label": "Kubectl Config Context to Use",
+ "kubectl-config-wko-choose-context-tooltip": "Choose the Kubernetes cluster context from the available contexts in the Kubernetes Client Config File",
+ "kubectl-config-vz-choose-admin-context-tooltip": "Choose the Kubernetes cluster context for the Verrazzano Admin Cluster from the available contexts in the Kubernetes Client Config File",
"kubectl-config-context-tooltip": "Get the current Kubernetes cluster context from the Kubernetes Client Config File",
"kubectl-config-context-help": "The Kubernetes config file context name for the cluster to which you want to connect. The default is to use whatever the current context is, if any.",
"kubectl-helm-exe-file-path-label": "Helm Executable to Use",
@@ -1126,6 +1145,11 @@
"kubectl-get-current-context-error-title": "Kubernetes Client Failed",
"kubectl-get-current-context-error-message": "kubectl failed to get the current Kubernetes cluster context: {{error}}.",
+ "kubectl-get-contexts-error-title": "Kubernetes Client Failed",
+ "kubectl-get-contexts-error-message": "kubectl failed to get the available Kubernetes cluster contexts: {{error}}.",
+ "kubectl-choose-context-dialog-title": "Kubernetes Client Choose Cluster Context",
+ "kubectl-choose-context-name-label": "Cluster Context Name",
+ "kubectl-choose-context-name-help": "Select the Kubernetes cluster context to use.",
"wko-get-install-version-aborted-error-title": "Getting Installed WebLogic Kubernetes Operator Version Aborted",
"wko-get-install-version-kubectl-exe-invalid-error-message": "Unable to get the installed WebLogic Kubernetes Operator version because the Kubernetes client executable is invalid: {{error}}.",
diff --git a/electron/app/main.js b/electron/app/main.js
index a7f49188d..0da5eef22 100644
--- a/electron/app/main.js
+++ b/electron/app/main.js
@@ -755,6 +755,10 @@ class Main {
return kubectlUtils.setCurrentContext(kubectlExe, context, kubectlOptions);
});
+ ipcMain.handle('kubectl-get-contexts', async (event, kubectlExe, kubectlOptions) => {
+ return kubectlUtils.getContexts(kubectlExe, kubectlOptions);
+ });
+
ipcMain.handle('validate-kubectl-exe', async (event, kubectlExe) => {
return kubectlUtils.validateKubectlExe(kubectlExe);
});
diff --git a/electron/package-lock.json b/electron/package-lock.json
index b72f97be4..6afb4e339 100644
--- a/electron/package-lock.json
+++ b/electron/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "wktui",
- "version": "1.4.2",
+ "version": "1.5.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "wktui",
- "version": "1.4.2",
+ "version": "1.5.0",
"license": "UPL-1.0",
"dependencies": {
"electron-updater": "^5.3.0",
@@ -29,7 +29,7 @@
"@electron/notarize": "^1.2.3",
"chai": "^4.3.7",
"chai-as-promised": "^7.1.1",
- "electron": "^22.1.0",
+ "electron": "^22.2.0",
"electron-builder": "^23.6.0",
"eslint": "^8.33.0",
"mocha": "^10.2.0",
@@ -2298,9 +2298,9 @@
}
},
"node_modules/electron": {
- "version": "22.1.0",
- "resolved": "https://registry.npmjs.org/electron/-/electron-22.1.0.tgz",
- "integrity": "sha512-wz5s4N6V7zyKm4YQmXJImFoxO1Doai32ShYm0FzOLPBMwLMdQBV+REY+j1opRx0KJ9xJEIdjYgcA8OSw6vx3pA==",
+ "version": "22.2.0",
+ "resolved": "https://registry.npmjs.org/electron/-/electron-22.2.0.tgz",
+ "integrity": "sha512-puRZSF2vWJ4pz3oetL5Td8LcuivTWz3MoAk/gjImHSN1B/2VJNEQlw1jGdkte+ppid2craOswE2lmCOZ7SwF1g==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
@@ -8132,9 +8132,9 @@
}
},
"electron": {
- "version": "22.1.0",
- "resolved": "https://registry.npmjs.org/electron/-/electron-22.1.0.tgz",
- "integrity": "sha512-wz5s4N6V7zyKm4YQmXJImFoxO1Doai32ShYm0FzOLPBMwLMdQBV+REY+j1opRx0KJ9xJEIdjYgcA8OSw6vx3pA==",
+ "version": "22.2.0",
+ "resolved": "https://registry.npmjs.org/electron/-/electron-22.2.0.tgz",
+ "integrity": "sha512-puRZSF2vWJ4pz3oetL5Td8LcuivTWz3MoAk/gjImHSN1B/2VJNEQlw1jGdkte+ppid2craOswE2lmCOZ7SwF1g==",
"dev": true,
"requires": {
"@electron/get": "^2.0.0",
diff --git a/electron/package.json b/electron/package.json
index 7fef52e9f..089304f11 100644
--- a/electron/package.json
+++ b/electron/package.json
@@ -1,7 +1,7 @@
{
"name": "wktui",
"productName": "WebLogic Kubernetes Toolkit UI",
- "version": "1.4.2",
+ "version": "1.5.0",
"description": "WebLogic Kubernetes Toolkit UI",
"copyright": "Copyright (c) 2021, 2023, Oracle and/or its affiliates.",
"homepage": "https://github.com/oracle/weblogic-toolkit-ui",
@@ -51,7 +51,7 @@
"@electron/notarize": "^1.2.3",
"chai": "^4.3.7",
"chai-as-promised": "^7.1.1",
- "electron": "^22.1.0",
+ "electron": "^22.2.0",
"electron-builder": "^23.6.0",
"eslint": "^8.33.0",
"mocha": "^10.2.0",
diff --git a/webui/src/css/app.css b/webui/src/css/app.css
index 51938c18f..e9ad5a706 100644
--- a/webui/src/css/app.css
+++ b/webui/src/css/app.css
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2022, Oracle and/or its affiliates.
+ * Copyright (c) 2021, 2023, Oracle and/or its affiliates.
* Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
*/
.sendOffScreen {
@@ -257,7 +257,7 @@
border: 1px solid var(--oj-collection-border-color);
}
-.wkt-paths-table, .wkt-domain-node-selectors-table {
+.wkt-paths-table, .wkt-domain-node-selectors-table, .wkt-vz-managed-clusters-table {
border: 1px solid var(--oj-collection-border-color);
width: 100%;
}
diff --git a/webui/src/js/models/kubectl-definition.js b/webui/src/js/models/kubectl-definition.js
index d9d4827a8..51a9546a2 100644
--- a/webui/src/js/models/kubectl-definition.js
+++ b/webui/src/js/models/kubectl-definition.js
@@ -1,6 +1,6 @@
/**
* @license
- * Copyright (c) 2021, 2022, Oracle and/or its affiliates.
+ * Copyright (c) 2021, 2023, Oracle and/or its affiliates.
* Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
*/
'use strict';
@@ -22,6 +22,7 @@ define(['utils/observable-properties'],
executableFilePath: props.createProperty(window.api.k8s.getKubectlFilePath()),
kubeConfigContextToUse: props.createProperty(),
helmExecutableFilePath: props.createProperty(window.api.k8s.getHelmFilePath()),
+ vzManagedClusters: props.createListProperty(['uid', 'name', 'kubeConfig', 'KubeContext']).persistByKey('uid'),
readFrom: function(json) {
props.createGroup(name, this).readFrom(json);
diff --git a/webui/src/js/utils/k8s-helper.js b/webui/src/js/utils/k8s-helper.js
index 544ac62b8..77d5401fc 100644
--- a/webui/src/js/utils/k8s-helper.js
+++ b/webui/src/js/utils/k8s-helper.js
@@ -1,6 +1,6 @@
/**
* @license
- * Copyright (c) 2021, 2022, Oracle and/or its affiliates.
+ * Copyright (c) 2021, 2023, Oracle and/or its affiliates.
* Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
*/
'use strict';
@@ -13,11 +13,11 @@ function(WktActionsBase, project, wktConsole, i18n, projectIo, dialogHelper, val
super();
}
- async startVerifyClusterConnectivity() {
- await this.executeAction(this.callVerifyClusterConnectivity);
+ async startVerifyClusterConnectivity(kubeConfig, kubeContext) {
+ await this.executeAction(this.callVerifyClusterConnectivity, kubeConfig, kubeContext);
}
- async callVerifyClusterConnectivity(options) {
+ async callVerifyClusterConnectivity(kubeConfig, kubeContext, options) {
if (!options) {
options = {};
}
@@ -56,8 +56,8 @@ function(WktActionsBase, project, wktConsole, i18n, projectIo, dialogHelper, val
busyDialogMessage = i18n.t('flow-kubectl-use-context-in-progress');
dialogHelper.updateBusyDialog(busyDialogMessage, 2/totalSteps);
- const kubectlContext = this.project.kubectl.kubeConfigContextToUse.value;
- const kubectlOptions = this.getKubectlOptions();
+ const kubectlContext = kubeContext || this.project.kubectl.kubeConfigContextToUse.value;
+ const kubectlOptions = this.getKubectlOptions(kubeConfig);
if (!options.skipKubectlSetContext) {
if (! await this.useKubectlContext(kubectlExe, kubectlOptions, kubectlContext, errTitle, errPrefix)) {
return Promise.resolve(false);
diff --git a/webui/src/js/utils/view-helper.js b/webui/src/js/utils/view-helper.js
index 9099a9ee3..6f728418a 100644
--- a/webui/src/js/utils/view-helper.js
+++ b/webui/src/js/utils/view-helper.js
@@ -1,6 +1,6 @@
/**
* @license
- * Copyright (c) 2021, Oracle and/or its affiliates.
+ * Copyright (c) 2021, 2023, Oracle and/or its affiliates.
* Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
*/
'use strict';
@@ -12,6 +12,7 @@ define(['ojs/ojcontext'],
// width of button column in WKT tables.
// ideally this could be specified as 'auto', but oj-table will not set below 100px.
this.BUTTON_COLUMN_WIDTH = '55px';
+ this.TEXT_BUTTON_COLUMN_WIDTH = '175px';
const thisHelper = this;
diff --git a/webui/src/js/utils/vz-application-status-checker.js b/webui/src/js/utils/vz-application-status-checker.js
index 3e96b6de9..71df2edf1 100644
--- a/webui/src/js/utils/vz-application-status-checker.js
+++ b/webui/src/js/utils/vz-application-status-checker.js
@@ -1,15 +1,15 @@
/**
* @license
- * Copyright (c) 2022, Oracle and/or its affiliates.
+ * Copyright (c) 2022, 2023, Oracle and/or its affiliates.
* Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
*/
'use strict';
define(['utils/vz-actions-base', 'models/wkt-project', 'models/wkt-console', 'utils/i18n', 'utils/project-io',
'utils/dialog-helper', 'utils/k8s-domain-resource-generator', 'utils/k8s-domain-configmap-generator',
- 'utils/validation-helper', 'utils/helm-helper', 'utils/wkt-logger'],
+ 'utils/validation-helper', 'utils/wkt-logger', 'utils/helm-helper'],
function (VzActionsBase, project, wktConsole, i18n, projectIo, dialogHelper, K8sDomainResourceGenerator,
- K8sDomainConfigMapGenerator, validationHelper) {
+ K8sDomainConfigMapGenerator, validationHelper, wktLogger) {
class VerrazzanoApplicationStatusChecker extends VzActionsBase {
constructor() {
super();
@@ -86,6 +86,7 @@ function (VzActionsBase, project, wktConsole, i18n, projectIo, dialogHelper, K8s
const applicationStatusResult = await window.api.ipc.invoke('vz-get-application-status', kubectlExe,
this.project.vzApplication.applicationName.value, this.project.k8sDomain.uid.value,
this.project.k8sDomain.kubernetesNamespace.value, kubectlOptions);
+ wktLogger.debug('applicationStatusResult = %s', JSON.stringify(applicationStatusResult, null, 2));
if (!applicationStatusResult.isSuccess) {
const errMessage = i18n.t('vz-application-status-checker-get-status-failed-error-message',
{error: applicationStatusResult.reason});
diff --git a/webui/src/js/utils/wkt-actions-base.js b/webui/src/js/utils/wkt-actions-base.js
index 3e0463d2d..7ff3b1574 100644
--- a/webui/src/js/utils/wkt-actions-base.js
+++ b/webui/src/js/utils/wkt-actions-base.js
@@ -1,6 +1,6 @@
/**
* @license
- * Copyright (c) 2021, 2022, Oracle and/or its affiliates.
+ * Copyright (c) 2021, 2023, Oracle and/or its affiliates.
* Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
*/
'use strict';
@@ -22,7 +22,7 @@ function(project, wktConsole, i18n, projectIo, dialogHelper,
// Execute the specified async action method if no other action is in progress.
// If another action is in progress, log a message and cancel the new action.
- async executeAction(asyncMethod) {
+ async executeAction(asyncMethod, ...args) {
const description = asyncMethod.name;
if (actionInProgress) {
wktLogger.info('Cancelling action ' + description + ' because ' + actionInProgress + ' is in progress');
@@ -30,7 +30,7 @@ function(project, wktConsole, i18n, projectIo, dialogHelper,
wktLogger.debug('starting action ' + description);
actionInProgress = description;
try {
- await asyncMethod.call(this);
+ await asyncMethod.call(this, ...args);
} finally {
wktLogger.debug('action ' + description + ' complete');
actionInProgress = null;
@@ -54,9 +54,9 @@ function(project, wktConsole, i18n, projectIo, dialogHelper,
};
}
- getKubectlOptions() {
+ getKubectlOptions(kubeConfig = undefined) {
return {
- kubeConfig: this.project.kubectl.kubeConfig.value,
+ kubeConfig: kubeConfig || this.project.kubectl.kubeConfig.value,
extraPathDirectories: this.getExtraPathDirectoriesArray(this.project.settings.extraPathDirectories.value),
extraEnvironmentVariables: this.getExtraEnvironmentVariablesObject(this.project.settings.extraEnvironmentVariables.value)
};
diff --git a/webui/src/js/viewModels/choose-kubectl-context-dialog.js b/webui/src/js/viewModels/choose-kubectl-context-dialog.js
new file mode 100644
index 000000000..834934a16
--- /dev/null
+++ b/webui/src/js/viewModels/choose-kubectl-context-dialog.js
@@ -0,0 +1,52 @@
+/**
+ * @license
+ * Copyright (c) 2023, Oracle and/or its affiliates.
+ * Licensed under The Universal Permissive License (UPL), Version 1.0 as shown at https://oss.oracle.com/licenses/upl/
+ */
+'use strict';
+
+define(['accUtils', 'knockout', 'utils/i18n', 'utils/observable-properties', 'utils/validation-helper',
+ 'ojs/ojarraydataprovider', 'utils/wkt-logger', 'ojs/ojselectcombobox', 'ojs/ojinputtext', 'ojs/ojlabel',
+ 'ojs/ojbutton', 'ojs/ojdialog', 'ojs/ojformlayout', 'ojs/ojvalidationgroup'],
+function(accUtils, ko, i18n, props, validationHelper, ArrayDataProvider) {
+ function ChooseKubectlContextDialogModel(args) {
+ const DIALOG_SELECTOR = '#chooseKubectlContextDialog';
+
+ this.i18n = i18n;
+ this.availableKubectlContextNames = args.availableKubectlContextNames;
+ this.selectedKubectlContextName = ko.observable(args.selectedKubectlContextName);
+ this.availableKubectlContextNamesDP =
+ new ArrayDataProvider(this.availableKubectlContextNames, { keyAttributes: 'name' });
+
+ this.connected = () => {
+ accUtils.announce('Choose Kubectl Context dialog loaded.', 'assertive');
+ // open the dialog after the current thread, which is loading this view model.
+ // using oj-dialog initial-visibility="show" causes vertical centering issues.
+ setTimeout(function() {
+ $(DIALOG_SELECTOR)[0].open();
+ }, 1);
+ };
+
+ this.labelMapper = (labelId) => {
+ return i18n.t(`kubectl-choose-context-${labelId}`);
+ };
+
+ this.okInput = () => {
+ $(DIALOG_SELECTOR)[0].close();
+
+ const result = {};
+ result.kubectlContextName = this.selectedKubectlContextName();
+ args.setValue(result);
+ };
+
+ this.cancelInput = () => {
+ $(DIALOG_SELECTOR)[0].close();
+ args.setValue();
+ };
+ }
+
+ /*
+ * Returns a constructor for the ViewModel.
+ */
+ return ChooseKubectlContextDialogModel;
+});
diff --git a/webui/src/js/viewModels/kubectl-page.js b/webui/src/js/viewModels/kubectl-page.js
index aa13703b8..8bf8a1d04 100644
--- a/webui/src/js/viewModels/kubectl-page.js
+++ b/webui/src/js/viewModels/kubectl-page.js
@@ -1,14 +1,16 @@
/**
* @license
- * Copyright (c) 2021, 2022, Oracle and/or its affiliates.
+ * Copyright (c) 2021, 2023, Oracle and/or its affiliates.
* Licensed under The Universal Permissive License (UPL), Version 1.0 as shown at https://oss.oracle.com/licenses/upl/
*/
'use strict';
define(['accUtils', 'knockout', 'models/wkt-project', 'utils/i18n', 'ojs/ojarraydataprovider',
- 'ojs/ojbufferingdataprovider', 'utils/url-catalog', 'utils/k8s-helper', 'utils/common-utilities', 'utils/wkt-logger',
- 'ojs/ojformlayout', 'ojs/ojinputtext', 'ojs/ojselectsingle', 'ojs/ojtable'],
-function(accUtils, ko, project, i18n, ArrayDataProvider, BufferingDataProvider, urlCatalog, k8sHelper, utils, wktLogger) {
+ 'ojs/ojbufferingdataprovider', 'utils/url-catalog', 'utils/k8s-helper', 'utils/common-utilities',
+ 'utils/dialog-helper', 'utils/view-helper', 'utils/wkt-logger', 'ojs/ojformlayout', 'ojs/ojinputtext',
+ 'ojs/ojselectsingle', 'ojs/ojtable'],
+function(accUtils, ko, project, i18n, ArrayDataProvider, BufferingDataProvider, urlCatalog, k8sHelper, utils,
+ dialogHelper, viewHelper, wktLogger) {
function KubectlViewModel() {
this.connected = () => {
@@ -39,6 +41,218 @@ function(accUtils, ko, project, i18n, ArrayDataProvider, BufferingDataProvider,
return this.project.settings.wdtTargetType.value === 'wko';
};
+ this.vzManagedClustersColumnData = [
+ {
+ headerText: this.labelMapper('vz-managed-cluster-name-heading'),
+ sortProperty: 'name'
+ },
+ {
+ headerText: this.labelMapper('vz-managed-cluster-kubeconfig-heading'),
+ sortProperty: 'kubeConfig'
+ },
+ {
+ headerText: this.labelMapper('vz-managed-cluster-kubecontext-heading'),
+ sortProperty: 'kubeContext'
+ },
+ {
+ 'className': 'wkt-table-delete-cell',
+ // 'headerClassName': 'wkt-table-add-header',
+ // 'headerTemplate': 'headerTemplate',
+ 'template': 'actionTemplate',
+ 'sortable': 'disable',
+ width: viewHelper.TEXT_BUTTON_COLUMN_WIDTH
+ },
+ {
+ 'className': 'wkt-table-delete-cell',
+ 'headerClassName': 'wkt-table-add-header',
+ 'headerTemplate': 'headerTemplate',
+ 'template': 'actionTemplate',
+ 'sortable': 'disable',
+ width: viewHelper.BUTTON_COLUMN_WIDTH
+ },
+ ];
+
+ this.vzManagedClustersDP = new BufferingDataProvider(new ArrayDataProvider(
+ this.project.kubectl.vzManagedClusters.observable, {keyAttributes: 'uid'}));
+
+ this.getConnectivityHeading = ko.computed(() => {
+ if (this.usingWko()) {
+ return this.labelMapper('wko-connectivity-heading');
+ } else {
+ return this.labelMapper('vz-connectivity-heading');
+ }
+ }, this);
+
+ this.chooseKubectl = () => {
+ window.api.ipc.invoke('get-kubectl-exe').then(kubectlPath => {
+ if (kubectlPath) {
+ this.project.kubectl.executableFilePath.observable(kubectlPath);
+ }
+ });
+ };
+
+ this.chooseHelm = () => {
+ window.api.ipc.invoke('get-helm-exe').then(helmPath => {
+ if (helmPath) {
+ this.project.kubectl.helmExecutableFilePath.observable(helmPath);
+ }
+ });
+ };
+
+ async function getKubeConfig() {
+ const kubeConfigPath = await window.api.ipc.invoke('get-kube-config-files');
+ wktLogger.debug('get-kube-config-files returned: %s', kubeConfigPath);
+ return kubeConfigPath;
+ }
+
+ this.chooseKubeConfig = () => {
+ getKubeConfig().then(kubeConfigPath => {
+ if (kubeConfigPath) {
+ this.project.kubectl.kubeConfig.observable(kubeConfigPath);
+ }
+ });
+ };
+
+ this.chooseManagedKubeConfig = (event, context) => {
+ const index = context.item.index;
+ const managedClusterData = this.project.kubectl.vzManagedClusters.observable()[index];
+ getKubeConfig().then(kubeConfigPath => {
+ if (kubeConfigPath) {
+ const newManagedClusterData = { ...managedClusterData, kubeConfig:kubeConfigPath };
+ this.project.kubectl.vzManagedClusters.observable.replace(managedClusterData, newManagedClusterData);
+ }
+ });
+ };
+
+ async function getCurrentClusterContext(kubectlExe, options) {
+ return window.api.ipc.invoke('kubectl-get-current-context', kubectlExe, options);
+ }
+
+ this.getCurrentContext = () => {
+ const kubectlExe = this.project.kubectl.executableFilePath.value;
+ const options = { kubeConfig: this.project.kubectl.kubeConfig.value };
+ getCurrentClusterContext(kubectlExe, options).then(results => {
+ if (results.isSuccess) {
+ this.project.kubectl.kubeConfigContextToUse.observable(results.context);
+ } else {
+ const errTitle = i18n.t('kubectl-get-current-context-error-title');
+ const errMessage = i18n.t('kubectl-get-current-context-error-message', { error: results.reason });
+ window.api.ipc.invoke('show-error-message', errTitle, errMessage).then().catch();
+ }
+ });
+ };
+
+ this.getManagedCurrentContext = (event, context) => {
+ const index = context.item.index;
+ const managedClusterData = this.project.kubectl.vzManagedClusters.observable()[index];
+ wktLogger.debug('getting current context for managed cluster" %s', JSON.stringify(managedClusterData));
+
+ const kubectlExe = this.project.kubectl.executableFilePath.value;
+ const options = { kubeConfig: managedClusterData.kubeConfig };
+ getCurrentClusterContext(kubectlExe, options).then(results => {
+ if (results.isSuccess) {
+ const newManagedClusterData = { ...managedClusterData, kubeContext: results.context};
+ this.project.kubectl.vzManagedClusters.observable.replace(managedClusterData, newManagedClusterData);
+ } else {
+ const errTitle = i18n.t('kubectl-get-current-context-error-title');
+ const errMessage = i18n.t('kubectl-get-current-context-error-message', { error: results.reason });
+ window.api.ipc.invoke('show-error-message', errTitle, errMessage).then().catch();
+ }
+ });
+ };
+
+ async function getContext(kubectlExe, options, kubeContext) {
+ const results = await window.api.ipc.invoke('kubectl-get-contexts', kubectlExe, options);
+ if (results.isSuccess) {
+ const availableKubectlContextNames = [];
+ results.contexts.forEach(entry => {
+ availableKubectlContextNames.push({
+ name: entry,
+ label: entry
+ });
+ });
+ const args = {
+ availableKubectlContextNames,
+ selectedKubectlContextName: kubeContext
+ };
+ return await dialogHelper.promptDialog('choose-kubectl-context-dialog', args);
+ } else {
+ const errTitle = i18n.t('kubectl-get-contexts-error-title');
+ const errMessage = i18n.t('kubectl-get-contexts-error-message', { error: results.reason });
+ window.api.ipc.invoke('show-error-message', errTitle, errMessage).then().catch();
+ }
+ }
+
+ this.chooseContext = () => {
+ const kubectlExe = this.project.kubectl.executableFilePath.value;
+ const options = { kubeConfig: this.project.kubectl.kubeConfig.value };
+ const kubeContext = this.project.kubectl.kubeConfigContextToUse.value;
+ getContext(kubectlExe, options, kubeContext).then(result => {
+ if (result?.kubectlContextName) {
+ this.project.kubectl.kubeConfigContextToUse.observable(result.kubectlContextName);
+ }
+ });
+ };
+
+ this.chooseManagedContext = (event, context) => {
+ const index = context.item.index;
+ const managedClusterData = this.project.kubectl.vzManagedClusters.observable()[index];
+
+ const kubectlExe = this.project.kubectl.executableFilePath.value;
+ const options = { kubeConfig: managedClusterData.kubeConfig };
+ const kubeContext = managedClusterData.kubeContext;
+ getContext(kubectlExe, options, kubeContext).then(result => {
+ if (result?.kubectlContextName) {
+ const newManagedClusterData = { ...managedClusterData, kubeContext: result.kubectlContextName };
+ this.project.kubectl.vzManagedClusters.observable.replace(managedClusterData, newManagedClusterData);
+ }
+ });
+ };
+
+ this.chooseAdminContextTooltip = ko.computed(() => {
+ if (this.project.settings.wdtTargetType.observable() === 'vz') {
+ return this.labelMapper('config-vz-choose-admin-context-tooltip');
+ } else {
+ return this.labelMapper('config-wko-choose-context-tooltip');
+ }
+ });
+
+ const generatedManagedClusterNameRegex = /^managed(\d+)$/;
+
+ this.generateNewManagedClusterName = () => {
+ let index = 1;
+ this.project.kubectl.vzManagedClusters.observable().forEach(managedCluster => {
+ const match = managedCluster.name.match(generatedManagedClusterNameRegex);
+ if (match) {
+ const indexFound = Number(match[1]);
+ if (indexFound >= index) {
+ index = indexFound + 1;
+ }
+ }
+ });
+ return `managed${index}`;
+ };
+
+ this.handleAddManagedCluster = () => {
+ const managedClusterToAdd = {
+ uid: utils.getShortUuid(),
+ name: this.generateNewManagedClusterName(),
+ kubeConfig: this.project.kubectl.kubeConfig.value
+ };
+ this.project.kubectl.vzManagedClusters.addNewItem(managedClusterToAdd);
+ };
+
+ this.handleDeleteManagedCluster = (event, context) => {
+ const index = context.item.index;
+ this.project.kubectl.vzManagedClusters.observable.splice(index, 1);
+ };
+
+ this.verifyManagedClusterConnectivity = async (event, context) => {
+ const index = context.item.index;
+ const managedClusterData = this.project.kubectl.vzManagedClusters.observable()[index];
+ await k8sHelper.startVerifyClusterConnectivity(managedClusterData.kubeConfig, managedClusterData.kubeContext);
+ };
+
this.createLink = function (url, label) {
return '' + label + '';
};
@@ -159,45 +373,6 @@ function(accUtils, ko, project, i18n, ArrayDataProvider, BufferingDataProvider,
}
return flavor;
};
-
- this.chooseKubectl = () => {
- window.api.ipc.invoke('get-kubectl-exe').then(kubectlPath => {
- if (kubectlPath) {
- this.project.kubectl.executableFilePath.observable(kubectlPath);
- }
- });
- };
-
- this.chooseHelm = () => {
- window.api.ipc.invoke('get-helm-exe').then(helmPath => {
- if (helmPath) {
- this.project.kubectl.helmExecutableFilePath.observable(helmPath);
- }
- });
- };
-
- this.chooseKubeConfig = () => {
- window.api.ipc.invoke('get-kube-config-files').then(kubeConfigPath => {
- wktLogger.debug('get-kube-config-files returned: %s', kubeConfigPath);
- if (kubeConfigPath) {
- this.project.kubectl.kubeConfig.observable(kubeConfigPath);
- }
- });
- };
-
- this.getCurrentContext = () => {
- const kubectlExe = this.project.kubectl.executableFilePath.value;
- const options = { kubeConfig: this.project.kubectl.kubeConfig.value };
- window.api.ipc.invoke('kubectl-get-current-context', kubectlExe, options).then(results => {
- if (results.isSuccess) {
- this.project.kubectl.kubeConfigContextToUse.observable(results.context);
- } else {
- const errTitle = i18n.t('kubectl-get-current-context-error-title');
- const errMessage = i18n.t('kubectl-get-current-context-error-message', { error: results.reason });
- window.api.ipc.invoke('show-error-message', errTitle, errMessage).then().catch();
- }
- });
- };
}
/*
diff --git a/webui/src/js/views/choose-kubectl-context-dialog.html b/webui/src/js/views/choose-kubectl-context-dialog.html
new file mode 100644
index 000000000..9e6d7abad
--- /dev/null
+++ b/webui/src/js/views/choose-kubectl-context-dialog.html
@@ -0,0 +1,31 @@
+
+