From 4f16d7c4a7acb1103ecea2fc3f44d6374af10e65 Mon Sep 17 00:00:00 2001 From: Robert Patrick Date: Mon, 6 Feb 2023 18:57:53 -0600 Subject: [PATCH 1/3] revamping archive editor for WDT 3.0 changes --- electron/app/js/fsUtils.js | 23 +- electron/app/js/ipcRendererPreload.js | 5 +- electron/app/js/wdtArchive.js | 448 ++++++++++++++---- electron/app/locales/en/electron.json | 113 ++++- electron/app/locales/en/webui.json | 19 + electron/app/main.js | 13 +- webui/src/js/models/wdt-model-definition.js | 3 +- .../utils/k8s-domain-v9-resource-generator.js | 2 +- webui/src/js/utils/vz-component-deployer.js | 2 +- webui/src/js/utils/wdt-archive-helper.js | 30 -- .../js/viewModels/add-to-archive-dialog.js | 184 ------- .../add-to-archive-selection-dialog.js | 356 ++++++++++++++ webui/src/js/viewModels/model-archive-view.js | 8 +- webui/src/js/viewModels/model-design-view.js | 20 +- webui/src/js/viewModels/text-input-dialog.js | 48 -- .../js/viewModels/vz-component-code-view.js | 2 +- .../add-to-archive-selection-dialog.html | 102 ++++ webui/src/js/views/text-input-dialog.html | 31 -- 18 files changed, 987 insertions(+), 422 deletions(-) delete mode 100644 webui/src/js/utils/wdt-archive-helper.js delete mode 100644 webui/src/js/viewModels/add-to-archive-dialog.js create mode 100644 webui/src/js/viewModels/add-to-archive-selection-dialog.js delete mode 100644 webui/src/js/viewModels/text-input-dialog.js create mode 100644 webui/src/js/views/add-to-archive-selection-dialog.html delete mode 100644 webui/src/js/views/text-input-dialog.html diff --git a/electron/app/js/fsUtils.js b/electron/app/js/fsUtils.js index b5891480b..73fbfbadf 100644 --- a/electron/app/js/fsUtils.js +++ b/electron/app/js/fsUtils.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. */ const { constants } = require('fs'); @@ -35,6 +35,26 @@ async function isDirectory(filePath) { }); } +async function isFile(filePath) { + return new Promise((resolve, reject) => { + fsPromises.lstat(filePath) + .then(stats => { + if (stats) { + resolve(stats.isFile()); + } else { + resolve(false); + } + }) + .catch(err => { + if (err.code === 'ENOENT') { + resolve(false); + } else { + reject(err); + } + }); + }); +} + async function exists(filePath) { return new Promise((resolve, reject) => { fsPromises.lstat(filePath) @@ -358,6 +378,7 @@ module.exports = { getFilesRecursivelyFromDirectory, getRelativePath, isDirectory, + isFile, isRootDirectory, isValidFileName, makeDirectoryIfNotExists, diff --git a/electron/app/js/ipcRendererPreload.js b/electron/app/js/ipcRendererPreload.js index a325dd11f..4e06f4d38 100644 --- a/electron/app/js/ipcRendererPreload.js +++ b/electron/app/js/ipcRendererPreload.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. */ const { contextBridge, ipcRenderer } = require('electron'); @@ -131,7 +131,8 @@ contextBridge.exposeInMainWorld( 'get-archive-entry', 'get-network-settings', 'choose-archive-file', - 'choose-archive-entry', + 'choose-archive-entry-file', + 'add-archive-entry', 'choose-domain-home', 'choose-java-home', 'choose-model-file', diff --git a/electron/app/js/wdtArchive.js b/electron/app/js/wdtArchive.js index b6283cdbb..af9143049 100644 --- a/electron/app/js/wdtArchive.js +++ b/electron/app/js/wdtArchive.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. */ @@ -8,189 +8,275 @@ const path = require('path'); const fs = require('fs'); const fsPromises = require('fs/promises'); const fsUtils = require('./fsUtils'); +const errorUtils = require('./errorUtils'); function getEntryTypes() { // lazy load to allow initialization const i18n = require('./i18next.config'); return { - 'applicationDir': { - name: i18n.t('wdt-archiveType-applicationDir'), - subtype: 'dir', - pathPrefix: 'wlsdeploy/applications/' - }, - 'applicationFile': { - name: i18n.t('wdt-archiveType-applicationFile'), - subtype: 'file', - extensions: ['ear', 'war'], + 'application': { + name: i18n.t('wdt-archiveType-application'), + subtype: 'either', + // XML because you can deploy WebLogic Modules as applications + extensions: ['ear', 'war', 'jar', 'xml'], + dirLabel: i18n.t('wdt-archiveType-application-dirLabel'), + dirHelp: i18n.t('wdt-archiveType-application-dirHelp'), + fileLabel: i18n.t('wdt-archiveType-application-fileLabel'), + fileHelp: i18n.t('wdt-archiveType-application-fileHelp'), pathPrefix: 'wlsdeploy/applications/' }, 'applicationDeploymentPlan': { name: i18n.t('wdt-archiveType-applicationDeploymentPlan'), subtype: 'file', extensions: ['xml'], + fileLabel: i18n.t('wdt-archiveType-applicationDeploymentPlan-fileLabel'), + fileHelp: i18n.t('wdt-archiveType-applicationDeploymentPlan-fileHelp'), pathPrefix: 'wlsdeploy/applications/' }, - 'atpWallet': { - name: i18n.t('wdt-archiveType-atpWallet'), - subtype: 'file', - extensions: ['zip'], - pathPrefix: 'atpwallet/' + 'applicationInstallationDirectory': { + name: i18n.t('wdt-archiveType-applicationInstallationDir'), + subtype: 'dir', + dirLabel: i18n.t('wdt-archiveType-applicationInstallationDir-dirLabel'), + dirHelp: i18n.t('wdt-archiveType-applicationInstallationDir-dirHelp'), + pathPrefix: 'wlsdeploy/structuredApplications/' }, 'classpathLibrary': { name: i18n.t('wdt-archiveType-classpathLibrary'), - subtype: 'file', + subtype: 'either', extensions: ['jar'], + dirLabel: i18n.t('wdt-archiveType-classpathLibrary-dirLabel'), + dirHelp: i18n.t('wdt-archiveType-classpathLibrary-dirHelp'), + fileLabel: i18n.t('wdt-archiveType-classpathLibrary-fileLabel'), + fileHelp: i18n.t('wdt-archiveType-classpathLibrary-fileHelp'), pathPrefix: 'wlsdeploy/classpathLibraries/' }, - 'coherenceStore': { + 'coherenceConfig': { + name: i18n.t('wdt-archiveType-coherenceConfig'), + subtype: 'file', + segregatedLabel: i18n.t('wdt-archiveType-coherenceClusterSegregationLabel'), + segregatedHelp: i18n.t('wdt-archiveType-coherenceClusterSegregationHelp'), + extensions: ['xml'], + fileLabel: i18n.t('wdt-archiveType-coherenceConfig-fileLabel'), + fileHelp: i18n.t('wdt-archiveType-coherenceConfig-fileHelp'), + pathPrefix: 'wlsdeploy/coherence/' + }, + 'coherencePersistenceDirectory': { name: i18n.t('wdt-archiveType-coherenceStore'), subtype: 'emptyDir', + subtypeChoices: [ + { name: 'active', label: i18n.t('wdt-archiveType-coherenceStore-activeLabel')}, + { name: 'snapshot', label: i18n.t('wdt-archiveType-coherenceStore-snapshotLabel')}, + { name: 'trash', label: i18n.t('wdt-archiveType-coherenceStore-trashLabel')} + ], + segregatedLabel: i18n.t('wdt-archiveType-coherenceClusterSegregationLabel'), + segregatedHelp: i18n.t('wdt-archiveType-coherenceClusterSegregationHelp'), + emptyDirLabel: i18n.t('wdt-archiveType-coherenceStore-emptyDirLabel'), + emptyDirHelp: i18n.t('wdt-archiveType-coherenceStore-emptyDirHelp'), pathPrefix: 'wlsdeploy/coherence/' }, - 'customFile': { - name: i18n.t('wdt-archiveType-customFile'), - subtype: 'file', + 'custom': { + name: i18n.t('wdt-archiveType-custom'), + subtype: 'either', + dirLabel: i18n.t('wdt-archiveType-custom-dirLabel'), + dirHelp: i18n.t('wdt-archiveType-custom-dirHelp'), + fileLabel: i18n.t('wdt-archiveType-custom-fileLabel'), + fileHelp: i18n.t('wdt-archiveType-custom-fileHelp'), + pathLabel: i18n.t('wdt-archiveType-custom-pathLabel'), + pathHelp: i18n.t('wdt-archiveType-custom-pathHelp'), pathPrefix: 'wlsdeploy/custom/' }, - 'customDirectory': { - name: i18n.t('wdt-archiveType-customDirectory'), - subtype: 'dir', - pathPrefix: 'wlsdeploy/custom/' + 'databaseWallet': { + name: i18n.t('wdt-archiveType-databaseWallet'), + subtype: 'either', + segregatedLabel: i18n.t('wdt-archiveType-dbWalletSegregationLabel'), + segregatedHelp: i18n.t('wdt-archiveType-dbWalletSegregationHelp'), + dirLabel: i18n.t('wdt-archiveType-databaseWallet-dirLabel'), + dirHelp: i18n.t('wdt-archiveType-databaseWallet-dirHelp'), + fileLabel: i18n.t('wdt-archiveType-databaseWallet-fileLabel'), + fileHelp: i18n.t('wdt-archiveType-databaseWallet-fileHelp'), + pathPrefix: 'wlsdeploy/dbWallets/' }, 'domainBin': { name: i18n.t('wdt-archiveType-domainBin'), subtype: 'file', + fileLabel: i18n.t('wdt-archiveType-domainBin-fileLabel'), + fileHelp: i18n.t('wdt-archiveType-domainBin-fileHelp'), pathPrefix: 'wlsdeploy/domainBin/' }, 'domainLibrary': { name: i18n.t('wdt-archiveType-domainLibrary'), subtype: 'file', extensions: ['jar'], + fileLabel: i18n.t('wdt-archiveType-domainLibrary-fileLabel'), + fileHelp: i18n.t('wdt-archiveType-domainLibrary-fileHelp'), pathPrefix: 'wlsdeploy/domainLibraries/' }, - 'nodeManagerFile': { - name: i18n.t('wdt-archiveType-nodeManagerFile'), + 'fileStore': { + name: i18n.t('wdt-archiveType-fileStore'), + subtype: 'emptyDir', + emptyDirLabel: i18n.t('wdt-archiveType-fileStore-emptyDirLabel'), + emptyDirHelp: i18n.t('wdt-archiveType-fileStore-emptyDirHelp'), + pathPrefix: 'wlsdeploy/stores/' + }, + 'jmsForeignServerBinding': { + name: i18n.t('wdt-archiveType-jmsForeignServerBinding'), + subtype: 'file', + segregatedLabel: i18n.t('wdt-archiveType-jmsForeignServerSegregationLabel'), + segregatedHelp: i18n.t('wdt-archiveType-jmsForeignServerSegregationHelp'), + extensions: ['properties'], + fileLabel: i18n.t('wdt-archiveType-jmsForeignServerBinding-fileLabel'), + fileHelp: i18n.t('wdt-archiveType-jmsForeignServerBinding-fileHelp'), + pathPrefix: 'wlsdeploy/jms/foreignServer/' + }, + 'mimeMapping': { + name: i18n.t('wdt-archiveType-mimeMapping'), subtype: 'file', + extensions: ['properties'], + fileLabel: i18n.t('wdt-archiveType-mimeMapping-fileLabel'), + fileHelp: i18n.t('wdt-archiveType-mimeMapping-fileHelp'), + pathPrefix: 'wlsdeploy/config/' + }, + 'nodeManagerKeystore': { + name: i18n.t('wdt-archiveType-nodeManagerKeystore'), + subtype: 'file', + fileLabel: i18n.t('wdt-archiveType-nodeManagerKeystore-fileLabel'), + fileHelp: i18n.t('wdt-archiveType-nodeManagerKeystore-fileHelp'), pathPrefix: 'wlsdeploy/nodeManager/' }, 'opssWallet': { name: i18n.t('wdt-archiveType-opssWallet'), - subtype: 'file', - extensions: ['zip'], - pathPrefix: 'opsswallet/' + subtype: 'either', + dirLabel: i18n.t('wdt-archiveType-opssWallet-dirLabel'), + dirHelp: i18n.t('wdt-archiveType-opssWallet-dirHelp'), + fileLabel: i18n.t('wdt-archiveType-opssWallet-fileLabel'), + fileHelp: i18n.t('wdt-archiveType-opssWallet-fileHelp'), + pathPrefix: 'wlsdeploy/opsswallet/' }, 'script': { name: i18n.t('wdt-archiveType-script'), subtype: 'file', + fileLabel: i18n.t('wdt-archiveType-script-fileLabel'), + fileHelp: i18n.t('wdt-archiveType-script-fileHelp'), pathPrefix: 'wlsdeploy/scripts/' }, - 'serverFileDirectory': { - name: i18n.t('wdt-archiveType-serverFileDir'), - subtype: 'dir', + 'serverKeystore': { + name: i18n.t('wdt-archiveType-serverKeystore'), + subtype: 'file', + segregatedLabel: i18n.t('wdt-archiveType-serverSegregationLabel'), + segregatedHelp: i18n.t('wdt-archiveType-serverSegregationHelp'), + fileLabel: i18n.t('wdt-archiveType-serverKeystore-fileLabel'), + fileHelp: i18n.t('wdt-archiveType-serverKeystore-fileHelp'), pathPrefix: 'wlsdeploy/servers/' }, - 'sharedLibraryDir': { - name: i18n.t('wdt-archiveType-sharedLibraryDir'), - subtype: 'dir', + 'sharedLibrary': { + name: i18n.t('wdt-archiveType-sharedLibrary'), + subtype: 'either', + extensions: ['ear', 'war', 'jar'], + dirLabel: i18n.t('wdt-archiveType-sharedLibrary-dirLabel'), + dirHelp: i18n.t('wdt-archiveType-sharedLibrary-dirHelp'), + fileLabel: i18n.t('wdt-archiveType-sharedLibrary-fileLabel'), + fileHelp: i18n.t('wdt-archiveType-sharedLibrary-fileHelp'), pathPrefix: 'wlsdeploy/sharedLibraries/' }, - 'sharedLibraryFile': { - name: i18n.t('wdt-archiveType-sharedLibraryFile'), + 'sharedLibraryDeploymentPlan': { + name: i18n.t('wdt-archiveType-sharedLibraryDeploymentPlan'), subtype: 'file', - extensions: ['jar'], + extensions: ['xml'], + fileLabel: i18n.t('wdt-archiveType-sharedLibraryDeploymentPlan-fileLabel'), + fileHelp: i18n.t('wdt-archiveType-sharedLibraryDeploymentPlan-fileHelp'), pathPrefix: 'wlsdeploy/sharedLibraries/' - }, - 'fileStore': { - name: i18n.t('wdt-archiveType-fileStore'), - subtype: 'emptyDir', - pathPrefix: 'wlsdeploy/stores/' } }; } +async function chooseArchiveEntryFile(targetWindow, entryType, fileType, fileExtensions, currentValue) { + const i18n = require('./i18next.config'); -// choose an archive entry of the specified type. -async function chooseArchiveEntry(targetWindow, entryType) { const typeDetail = getEntryTypes()[entryType]; - if(!typeDetail) { - throw new Error(`Unknown archive entry type: ${entryType}`); + if (!typeDetail) { + return Promise.reject(new Error(`Unknown archive entry type: ${entryType}`)); } - // the full file system path, ex. /home/me/abc.jar - let filePath = null; - // the path to an archive member, ex. wlsdeploy/apps/my.war, wlsdeploy/apps/exploded - let archivePath = null; - // the path to use in archive updates (directories end with /) - let archiveUpdatePath = null; - // file paths below a selected directory entry - let childPaths = null; - - const subtype = typeDetail['subtype']; - switch(subtype) { - case 'file': - filePath = await chooseArchiveFileEntry(targetWindow, typeDetail); - if(filePath) { - archivePath = typeDetail['pathPrefix'] + path.basename(filePath); - archiveUpdatePath = archivePath; - } - break; - case 'dir': - filePath = await chooseArchiveDirEntry(targetWindow, typeDetail); - if(filePath) { - archivePath = typeDetail['pathPrefix'] + path.basename(filePath); - archiveUpdatePath = archivePath + '/'; - childPaths = await _getDirectoryPaths(filePath); - } - break; - default: - // note: emptyDir subtype is currently handled on client side - throw new Error(`Unrecognized archive entry subtype: ${subtype}`); - } - - return {filePath: filePath, archivePath: archivePath, archiveUpdatePath: archiveUpdatePath, childPaths: childPaths}; -} - -async function chooseArchiveFileEntry(targetWindow, typeDetail) { - // lazy load to allow initialization - const i18n = require('./i18next.config'); - const title = i18n.t('dialog-chooseArchiveEntry', {entryType: typeDetail.name}); + const openProperty = fileType === 'dir' ? 'openDirectory' : 'openFile'; let options = { title: title, message: title, buttonLabel: i18n.t('button-select'), - properties: ['openFile', 'dontAddToRecent'] + properties: [openProperty, 'dontAddToRecent'] }; - const filterExtensions = typeDetail.extensions; - if(filterExtensions) { + const defaultPath = await _getDefaultPathFromValue(currentValue); + if (defaultPath) { + options.defaultPath = defaultPath; + } + + if (fileType === 'file' && Array.isArray(fileExtensions) && fileExtensions.length > 0) { const filterName = i18n.t('dialog-archiveEntryFilter', {entryType: typeDetail.name}); - options['filters'] = [ - {name: filterName, extensions: filterExtensions} - ]; + options['filters'] = [ {name: filterName, extensions: fileExtensions} ]; } const wktWindow = require('./wktWindow'); return wktWindow.chooseFromFileSystem(targetWindow, options); } -async function chooseArchiveDirEntry(targetWindow, typeDetail) { - // lazy load to allow initialization - const i18n = require('./i18next.config'); +async function addArchiveEntry(targetWindow, entryType, entryData) { + const { getLogger } = require('./wktLogging'); - const title = i18n.t('dialog-chooseArchiveEntry', {entryType: typeDetail.name}); + const typeDetail = getEntryTypes()[entryType]; + if (!typeDetail) { + return Promise.reject(new Error(`Unknown archive entry type: ${entryType}`)); + } - let options = { - title: title, - message: title, - buttonLabel: i18n.t('button-select'), - properties: ['openDirectory', 'dontAddToRecent'] - }; + if (! await _validateArchiveEntryData(targetWindow, entryType, typeDetail, entryData)) { + return Promise.resolve({}); + } - const wktWindow = require('./wktWindow'); - return wktWindow.chooseFromFileSystem(targetWindow, options); + let filePath; + let archivePath = typeDetail['pathPrefix']; + let archiveUpdatePath; + // file paths below a selected directory entry + let childPaths; + + if (entryData.segregatedName) { + archivePath = _joinArchivePaths(archivePath, entryData.segregatedName); + } else if (entryData.customPath) { + archivePath = _joinArchivePaths(archivePath, entryData.customPath); + } + + if (entryData.fileType) { + filePath = entryData.fileName; + // wallets are special in that when adding one as a directory, you are really only + // adding the directory content and not the directory itself into the archive path. + // + if (entryType === 'databaseWallet' || entryType === 'opssWallet') { + if (entryData.fileType === 'file') { + archivePath = _joinArchivePaths(archivePath, path.basename(filePath)); + } + } else { + archivePath = _joinArchivePaths(archivePath, path.basename(filePath)); + } + archiveUpdatePath = archivePath; + + if (entryData.fileType === 'dir') { + if (!archiveUpdatePath.endsWith('/')) { + archiveUpdatePath += '/'; + } + childPaths = await _getDirectoryPaths(filePath); + } + } else { // emptyDir + archivePath = _joinArchivePaths(archivePath, entryData.emptyDirName); + if (archivePath.endsWith('/')) { + archivePath = archivePath.slice(-1); + } + archiveUpdatePath = archivePath + '/'; + } + getLogger().debug(`filePath = ${filePath}, archivePath = ${archivePath}, archiveUpdatePath = ${archiveUpdatePath},` + + ` childPaths = ${childPaths}`); + + return Promise.resolve({filePath, archivePath, archiveUpdatePath, childPaths}); } // Help the model design view properly handle archive entries when using @@ -424,8 +510,162 @@ function _archiveEntryTypeGetOppositeType(archiveEntryTypeName) { return result; } +async function _getDefaultPathFromValue(currentValue) { + return new Promise((resolve, reject) => { + if (!currentValue) { + return resolve(); + } + + const currentPath = path.resolve(currentValue); + fsUtils.isDirectory(currentPath).then(isDir => { + if (isDir) { + resolve(currentPath); + } else { + resolve(path.dirname(currentPath)); + } + }).catch(err => { + reject(new Error(`Failed to get default path from current path ${currentPath}: ` + errorUtils.getErrorMessage(err))); + }); + }); +} + +async function _validateArchiveEntryData(targetWindow, entryType, typeDetail, entryData) { + return new Promise((resolve, reject) => { + switch (entryType) { + case 'application': + case 'classpathLibrary': + case 'custom': + case 'opssWallet': + case 'sharedLibrary': + _validateArchiveEntryFile(targetWindow, entryType, entryData.fileType, entryData.fileName).then(result => { + resolve(result); + }); + break; + + case 'applicationDeploymentPlan': + case 'domainBin': + case 'domainLibrary': + case 'mimeMapping': + case 'nodeManagerKeystore': + case 'script': + case 'sharedLibraryDeploymentPlan': + _validateArchiveEntryFile(targetWindow, entryType, 'file', entryData.fileName).then(result => { + resolve(result); + }); + break; + + case 'applicationInstallationDirectory': + _validateArchiveEntryFile(targetWindow, entryType, 'dir', entryData.fileName).then(result => { + resolve(result); + }); + break; + + case 'databaseWallet': + if (!_validateRequiredString(targetWindow, entryType, typeDetail.segregatedLabel, entryData.segregatedName)) { + resolve(false); + } else { + _validateArchiveEntryFile(targetWindow, entryType, entryData.fileType, entryData.fileName).then(result => { + resolve(result); + }); + } + break; + + case 'coherenceConfig': + case 'jmsForeignServerBinding': + case 'serverKeystore': + if (!_validateRequiredString(targetWindow, entryType, typeDetail.segregatedLabel, entryData.segregatedName)) { + resolve(false); + } else { + _validateArchiveEntryFile(targetWindow, entryType, 'file', entryData.fileName).then(result => { + resolve(result); + }); + } + break; + + case 'coherencePersistenceDirectory': + resolve(_validateRequiredString(targetWindow, entryType, typeDetail.segregatedLabel, entryData.segregatedName) + && _validateRequiredString(targetWindow, entryType, typeDetail.emptyDirLabel, entryData.emptyDirName)); + break; + + case 'fileStore': + resolve(_validateRequiredString(targetWindow, entryType, typeDetail.emptyDirLabel, entryData.emptyDirName)); + break; + + default: + reject(new Error(`Unknown archive entry type: ${entryType}`)); + break; + } + }); +} + +async function _validateArchiveEntryFile(targetWindow, entryType, fileType, filePath) { + const { getLogger } = require('./wktLogging'); + + getLogger().debug(`Entering _validateArchiveEntryFile(${entryType}, ${fileType}, ${filePath})`); + + const title = `Add ${entryType} to archive failed`; + if (!filePath) { + const errMessage = + `Failed to add ${entryType} to archive because the ${fileType === 'dir' ? 'directory' : 'file'} path was empty.`; + _showArchiveEntryAddError(targetWindow, title, errMessage).then(); + return false; + } + + if (fileType === 'dir' && ! await fsUtils.isDirectory(filePath)) { + const errMessage = `Failed to add ${entryType} to archive because the directory ${filePath} ` + + 'either does not exist or is not a directory.'; + _showArchiveEntryAddError(targetWindow, title, errMessage).then(); + return false; + } else if (fileType === 'file' && ! await fsUtils.isFile(filePath)) { + const errMessage = `Failed to add ${entryType} to archive because the file ${filePath} ` + + 'either does not exist or is not a file.'; + _showArchiveEntryAddError(targetWindow, title, errMessage).then(); + return false; + } + return true; +} + +function _validateRequiredString(targetWindow, entryType, fieldName, fieldValue) { + if (!fieldValue) { + const title = `Add ${entryType} to archive failed`; + const errMessage = `The required field ${fieldName} for the ${entryType} was empty.`; + _showArchiveEntryAddError(targetWindow, title, errMessage).then(); + return false; + } + return true; +} + +function _joinArchivePaths(...archivePaths) { + let result = ''; + for (const archivePath of archivePaths) { + // No leading slashes + if (!result) { + result = archivePath; + if (archivePath.startsWith('/')) { + result = archivePath.substring(1); + } + } else { + if (result.endsWith('/') || archivePath.startsWith('/')) { + result += archivePath; + } else { + result += '/' + archivePath; + } + } + } + return result; +} + +async function _showArchiveEntryAddError(targetWindow, title, errMessage) { + const { showErrorMessage } = require('./wktWindow'); + + return new Promise(resolve => { + showErrorMessage(targetWindow, title, errMessage, 'error').then(() => resolve()); + }); +} + module.exports = { getEntryTypes, - chooseArchiveEntry, + addArchiveEntry, + chooseArchiveEntryFile, getArchiveEntry }; diff --git a/electron/app/locales/en/electron.json b/electron/app/locales/en/electron.json index 18e9419b9..6e4987157 100644 --- a/electron/app/locales/en/electron.json +++ b/electron/app/locales/en/electron.json @@ -369,23 +369,120 @@ "user-settings-file-save-failed-error-message": "Failed to save file {{userSettingsFile}}: {{error}}", "user-settings-deep-copy-failed-error-message": "Failed to make a deep copy of the user settings object: {{error}}", - "wdt-archiveType-atpWallet": "ATP Wallet", + "wdt-archiveType-application": "Application", + "wdt-archiveType-application-dirLabel": "Exploded Application Directory", + "wdt-archiveType-application-dirHelp": "Choose the full path to the exploded application directory", + "wdt-archiveType-application-fileLabel": "Application Archive File", + "wdt-archiveType-application-fileHelp": "Choose the full path to the application archive file", + + "wdt-archiveType-applicationDeploymentPlan": "Application Deployment Plan", + "wdt-archiveType-applicationDeploymentPlan-fileLabel": "Application Deployment Plan File", + "wdt-archiveType-applicationDeploymentPlan-fileHelp": "Choose the full path to the application deployment plan file", + + "wdt-archiveType-applicationInstallationDir": "Application Installation Directory", + "wdt-archiveType-applicationInstallationDir-dirLabel": "Application Installation Directory", + "wdt-archiveType-applicationInstallationDir-dirHelp": "Choose the full path to the application installation directory", + + "wdt-archiveType-classpathLibrary": "Classpath Library", + "wdt-archiveType-classpathLibrary-dirLabel": "Classpath Library Directory", + "wdt-archiveType-classpathLibrary-dirHelp": "Choose the full path to the classpath library directory", + "wdt-archiveType-classpathLibrary-fileLabel": "Classpath Library Archive File", + "wdt-archiveType-classpathLibrary-fileHelp": "Choose the full path to the classpath library archive file", + + "wdt-archiveType-coherenceClusterSegregationLabel": "Coherence Cluster Name", + "wdt-archiveType-coherenceClusterSegregationHelp": "Enter the name of the Coherence Cluster", + + "wdt-archiveType-coherenceConfig": "Coherence Configuration File", + "wdt-archiveType-coherenceConfig-fileLabel": "Coherence Configuration File", + "wdt-archiveType-coherenceConfig-fileHelp": "Choose the full path to the Coherence configuration file", + + "wdt-archiveType-coherenceStore": "Coherence Persistence Directory", + "wdt-archiveType-coherenceStore-activeLabel": "Active", + "wdt-archiveType-coherenceStore-snapshotLabel": "Snapshot", + "wdt-archiveType-coherenceStore-trashLabel": "Trash", + "wdt-archiveType-coherenceStore-emptyDirLabel": "Coherence Persistence Directory Name", + "wdt-archiveType-coherenceStore-emptyDirHelp": "Select the name of the Coherence persistence directory", + + "wdt-archiveType-custom": "Custom Content", + "wdt-archiveType-custom-dirLabel": "Custom Content Directory", + "wdt-archiveType-custom-dirHelp": "Choose the full path to the custom content directory", + "wdt-archiveType-custom-fileLabel": "Custom Content File", + "wdt-archiveType-custom-fileHelp": "Choose the full path to the custom content file", + "wdt-archiveType-custom-pathLabel": "Custom Content Path Prefix", + "wdt-archiveType-custom-pathHelp": "Enter the optional path into the custom content area to place the added content. The default is the root location", + + "wdt-archiveType-databaseWallet": "Database Wallet", + "wdt-archiveType-databaseWallet-dirLabel": "Database Wallet Directory", + "wdt-archiveType-databaseWallet-dirHelp": "Choose the full path to the database wallet directory whose contents will be added under the specified wallet", + "wdt-archiveType-databaseWallet-fileLabel": "Database Wallet File", + "wdt-archiveType-databaseWallet-fileHelp": "Choose the full path to the database wallet file to add to the specified wallet", + + "wdt-archiveType-dbWalletSegregationLabel": "Wallet Name", + "wdt-archiveType-dbWalletSegregationHelp": "Enter the name to of the database wallet to use", + + "wdt-archiveType-domainBin": "DOMAIN_HOME/bin Directory Script", + "wdt-archiveType-domainBin-fileLabel": "$DOMAIN_HOME/bin Directory Script File", + "wdt-archiveType-domainBin-fileHelp": "Choose the full path to the $DOMAIN_HOME/bin directory script file", + + "wdt-archiveType-domainLibrary": "DOMAIN_HOME/lib Directory Library", + "wdt-archiveType-domainLibrary-fileLabel": "$DOMAIN_HOME/lib Directory Library File", + "wdt-archiveType-domainLibrary-fileHelp": "Choose the full path to the $DOMAIN_HOME/lib directory library file", + + "wdt-archiveType-fileStore": "File Store Directory", + "wdt-archiveType-fileStore-emptyDirLabel": "File Store Directory Name", + "wdt-archiveType-fileStore-emptyDirHelp": "Enter the name of the File Store directory to add", + + "wdt-archiveType-jmsForeignServerSegregationLabel": "JMS Foreign Server Name", + "wdt-archiveType-jmsForeignServerSegregationHelp": "Enter the name of the JMS Foreign Server Name to use", + + "wdt-archiveType-jmsForeignServerBinding": "JMS Foreign Server Binding", + "wdt-archiveType-jmsForeignServerBinding-fileLabel": "JMS Foreign Server Binding File", + "wdt-archiveType-jmsForeignServerBinding-fileHelp": "Choose the full path to the JMS Foreign Server binding file", + + "wdt-archiveType-mimeMapping": "MIME Mapping File", + "wdt-archiveType-mimeMapping-fileLabel": "MIME Mapping File", + "wdt-archiveType-mimeMapping-fileHelp": "Choose the full path to the MIME mapping file", + + "wdt-archiveType-nodeManagerKeystore": "Node Manager Keystore", + "wdt-archiveType-nodeManagerKeystore-fileLabel": "Node Manager Keystore File", + "wdt-archiveType-nodeManagerKeystore-fileHelp": "Choose the full path to the node manager keystore file", + "wdt-archiveType-opssWallet": "OPSS Wallet", + "wdt-archiveType-opssWallet-dirLabel": "OPSS Wallet Directory", + "wdt-archiveType-opssWallet-dirHelp": "Choose the full path to the OPSS directory whose contents will be added to the OPSS wallet", + "wdt-archiveType-opssWallet-fileLabel": "OPSS Wallet File", + "wdt-archiveType-opssWallet-fileHelp": "Choose the full path to the OPSS wallet file to add to the OPSS wallet", + + "wdt-archiveType-script": "Script", + "wdt-archiveType-script-fileLabel": "Script File", + "wdt-archiveType-script-fileHelp": "Choose the full path to the script file", + + "wdt-archiveType-serverKeystore": "Server Keystore", + "wdt-archiveType-serverKeystore-fileLabel": "Server Keystore File", + "wdt-archiveType-serverKeystore-fileHelp": "Choose the full path to the server keystore file to add under the specific server", + + "wdt-archiveType-serverSegregationLabel": "Server Name", + "wdt-archiveType-serverSegregationHelp": "Enter the name of the server to use", + + "wdt-archiveType-sharedLibrary": "Shared Library", + "wdt-archiveType-sharedLibrary-dirLabel": "Exploded Shared Library Directory", + "wdt-archiveType-sharedLibrary-dirHelp": "Choose the full path to the exploded shared library directory", + "wdt-archiveType-sharedLibrary-fileLabel": "Shared Library Archive File", + "wdt-archiveType-sharedLibrary-fileHelp": "Choose the full path to the shared library archive file", + + "wdt-archiveType-sharedLibraryDeploymentPlan": "Shared Library Deployment Plan", + "wdt-archiveType-sharedLibraryDeploymentPlan-fileLabel": "Shared Library Deployment Plan File", + "wdt-archiveType-sharedLibraryDeploymentPlan-fileHelp": "Choose the full path to the shared library deployment plan file", + + "wdt-archiveType-atpWallet": "ATP Wallet", "wdt-archiveType-applicationFile": "Application File", "wdt-archiveType-applicationDir": "Application Directory", - "wdt-archiveType-applicationDeploymentPlan": "Application Deployment Plan", - "wdt-archiveType-classpathLibrary": "Classpath Library", - "wdt-archiveType-coherenceStore": "Coherence Store Directory", "wdt-archiveType-customFile": "Custom File", "wdt-archiveType-customDirectory": "Custom Directory", - "wdt-archiveType-domainBin": "Domain Script", - "wdt-archiveType-domainLibrary": "Domain Library", "wdt-archiveType-nodeManagerFile": "Node Manager File", - "wdt-archiveType-script": "Script", "wdt-archiveType-serverFileDir": "Server File Directory", "wdt-archiveType-sharedLibraryFile": "Shared Library File", "wdt-archiveType-sharedLibraryDir": "Shared Library Directory", - "wdt-archiveType-fileStore": "File Store Directory", "wdt-archive-invalid-archive-entry-type": "Unknown WebLogic Deploy Tooling archive entry type {{type}}", "wdt-archive-empty-file-system-path": "The path to the archive entry was empty", diff --git a/electron/app/locales/en/webui.json b/electron/app/locales/en/webui.json index d4db90e9a..8aa352706 100644 --- a/electron/app/locales/en/webui.json +++ b/electron/app/locales/en/webui.json @@ -897,6 +897,25 @@ "add-name-to-archive-dialog-label": "{{typeName}} Name", "add-name-to-archive-dialog-help": "Enter a name for the {{typeName}}.", + "add-to-archive-selection-dialog-title": "Add to Archive", + "add-to-archive-selection-dialog-entry-type-label": "Archive Entry Type", + "add-to-archive-selection-dialog-entry-type-help": "Choose the type of archive entry to add", + "add-to-archive-selection-dialog-file-or-directory-selector-label": "File system type to add", + "add-to-archive-selection-dialog-file-or-directory-selector-help": "Select whether you want to add a file or a directory", + "add-to-archive-selection-dialog-file-label": "File", + "add-to-archive-selection-dialog-directory-label": "Directory", + "add-to-archive-selection-dialog-source-file-label": "Source file location", + "add-to-archive-selection-dialog-source-dir-label": "Source directory location", + "add-to-archive-selection-dialog-name-no-forward-slashes": "Value cannot contain any '/' characters", + "add-to-archive-selection-dialog-path-no-leading-trailing-forward-slashes": "Value cannot start or end with a '/' character", + + + + + "add-name-to-archive-selection-dialog-title": "Add {{typeName}} to Archive", + "add-name-to-archive-selection-dialog-label": "{{typeName}} Name", + "add-name-to-archive-selection-dialog-help": "Enter a name for the {{typeName}}.", + "discover-dialog-offline-form-name": "Discover Domain Offline", "discover-dialog-online-form-name": "Discover Domain Online", "discover-dialog-title": "Discover Domain Offline", diff --git a/electron/app/main.js b/electron/app/main.js index d62a8be58..7dced34c4 100644 --- a/electron/app/main.js +++ b/electron/app/main.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. */ const { app, BrowserWindow, dialog, ipcMain, shell } = require('electron'); @@ -485,8 +485,15 @@ class Main { return project.chooseArchiveFile(event.sender.getOwnerBrowserWindow()); }); - ipcMain.handle('choose-archive-entry', async (event, itemType) => { - return wdtArchive.chooseArchiveEntry(event.sender.getOwnerBrowserWindow(), itemType); + ipcMain.handle('choose-archive-entry-file', async (event, itemType, fileType, + fileExtensions, currentValue) => { + + return wdtArchive.chooseArchiveEntryFile(event.sender.getOwnerBrowserWindow(), itemType, fileType, + fileExtensions, currentValue); + }); + + ipcMain.handle('add-archive-entry', async(event, itemType, itemData) => { + return wdtArchive.addArchiveEntry(event.sender.getOwnerBrowserWindow(), itemType, itemData); }); ipcMain.handle('get-archive-entry-types', async () => { diff --git a/webui/src/js/models/wdt-model-definition.js b/webui/src/js/models/wdt-model-definition.js index e0f5cb0db..cafc838eb 100644 --- a/webui/src/js/models/wdt-model-definition.js +++ b/webui/src/js/models/wdt-model-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'; @@ -43,6 +43,7 @@ define(['knockout', 'utils/observable-properties', 'js-yaml', 'utils/validation- this.internal = { wlRemoteConsolePort: ko.observable(), wlRemoteConsoleHome: props.createProperty(window.api.ipc.invoke('wrc-get-home-default-value')), + wlRemoteConsoleLogLevel: undefined, propertiesContent: createPropertiesObject({}) }; diff --git a/webui/src/js/utils/k8s-domain-v9-resource-generator.js b/webui/src/js/utils/k8s-domain-v9-resource-generator.js index 1b7e83447..8b87619df 100644 --- a/webui/src/js/utils/k8s-domain-v9-resource-generator.js +++ b/webui/src/js/utils/k8s-domain-v9-resource-generator.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 (UPL), Version 1.0 as shown at https://oss.oracle.com/licenses/upl/ */ 'use strict'; diff --git a/webui/src/js/utils/vz-component-deployer.js b/webui/src/js/utils/vz-component-deployer.js index 813c13bce..62f55c57f 100644 --- a/webui/src/js/utils/vz-component-deployer.js +++ b/webui/src/js/utils/vz-component-deployer.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. */ 'use strict'; diff --git a/webui/src/js/utils/wdt-archive-helper.js b/webui/src/js/utils/wdt-archive-helper.js deleted file mode 100644 index c5eda1ffa..000000000 --- a/webui/src/js/utils/wdt-archive-helper.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * @license - * Copyright (c) 2021, 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'; - -/** - * An helper for WDT archive operations. - * Returns a singleton. - */ - -define([], - function () { - function WdtArchiveHelper() { - - // select the entry of the specified type to be added. - // update the archive tree, and add an archive update entry to the model. - this.chooseArchiveEntry = async (fileType) => { - return window.api.ipc.invoke('choose-archive-entry', fileType); - }; - - // return available archive entry types and names - this.getEntryTypes = async () => { - return window.api.ipc.invoke('get-archive-entry-types'); - }; - } - - return new WdtArchiveHelper(); - }); diff --git a/webui/src/js/viewModels/add-to-archive-dialog.js b/webui/src/js/viewModels/add-to-archive-dialog.js deleted file mode 100644 index 6dea29701..000000000 --- a/webui/src/js/viewModels/add-to-archive-dialog.js +++ /dev/null @@ -1,184 +0,0 @@ -/** - * @license - * Copyright (c) 2021, 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', 'models/wkt-project', 'utils/wdt-archive-helper', - 'utils/dialog-helper', 'ojs/ojarraydataprovider', 'ojs/ojknockout', 'ojs/ojinputtext', 'ojs/ojlabel', - 'ojs/ojbutton', 'ojs/ojdialog', 'ojs/ojformlayout', 'ojs/ojselectsingle', 'ojs/ojvalidationgroup'], -function(accUtils, ko, i18n, project, archiveHelper, dialogHelper, ArrayDataProvider) { - function AddToArchiveDialogModel() { - - this.connected = () => { - accUtils.announce('Add to archive 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() { - $('#addToArchiveDialog')[0].open(); - }, 1); - }; - - this.labelMapper = (labelId, arg) => { - if (arg) { - return i18n.t(`add-to-archive-dialog-${labelId}`, arg); - } - return i18n.t(`add-to-archive-dialog-${labelId}`); - }; - - this.anyLabelMapper = (labelId, arg) => { - if (arg) { - return i18n.t(labelId, arg); - } - return i18n.t(labelId); - }; - - this.archiveEntryTypes = ko.observableArray(); - this.archiveEntryTypesProvider = new ArrayDataProvider(this.archiveEntryTypes, {keyAttributes: 'key'}); - this.entryTypesMap = null; - - archiveHelper.getEntryTypes().then(entryTypes => { - this.entryTypesMap = entryTypes; - for (const [key, value] of Object.entries(entryTypes)) { - this.archiveEntryTypes.push({key: key, label: value.name}); - this.archiveEntryTypes.sort((a, b) => (a.label > b.label) ? 1 : -1); - } - }); - - this.archiveEntryType = ko.observable(); - - this.addToArchive = () => { - let tracker = document.getElementById('tracker'); - if (tracker.valid === 'valid') { - $('#addToArchiveDialog')[0].close(); - - const entryType = this.entryTypesMap[this.archiveEntryType()]; - if(entryType.subtype === 'emptyDir') { - const prefix = entryType.pathPrefix; - - let textInputConfig = { - title: i18n.t('add-name-to-archive-dialog-title', {typeName: entryType.name}), - label: i18n.t('add-name-to-archive-dialog-label', {typeName: entryType.name}), - help: i18n.t('add-name-to-archive-dialog-help', {typeName: entryType.name}) - }; - dialogHelper.promptDialog('text-input-dialog', textInputConfig) - .then(name => { - // no name means input was cancelled, no need to notify or continue - if(name) { - this._addArchiveUpdate(null, prefix + name + '/'); - this._addToArchiveModel(prefix + name, entryType, null); - } - }); - - } else { - archiveHelper.chooseArchiveEntry(this.archiveEntryType()) - .then((result) => { - // no filePath means selection was cancelled, no need to notify or continue - if(result['filePath']) { - this._addArchiveUpdate(result.filePath, result.archiveUpdatePath); - this._addToArchiveModel(result.archivePath, entryType, result.childPaths); - } - }) - .catch((err) => { - dialogHelper.displayCatchAllError('add-archive-entry', err).then(); - }); - } - - } else { - // show messages on all the components that have messages hidden. - tracker.showMessages(); - tracker.focusOn('@firstInvalidShown'); - } - }; - - this.cancelAdd = () => { - $('#addToArchiveDialog')[0].close(); - }; - - // add an archive update that will be applied to the file on the next save. - this._addArchiveUpdate = (filePath, archivePath) => { - project.wdtModel.addArchiveUpdate('add', archivePath, filePath); - }; - - // add the archive entry to the model, so it will be displayed in the tree. - this._addToArchiveModel = (archivePath, leafEntryType, leafChildPaths) => { - const childList = project.wdtModel.archiveRoots; - const pathNames = archivePath.split('/'); - const leafIsDir = ['dir', 'emptyDir'].includes(leafEntryType.subtype); - this._addToArchiveFolder(pathNames, 0, childList, leafIsDir, leafChildPaths); - }; - - // add the path member to the specified entry list in the archive model tree. - // if already present, recurse to the next path member and list if needed. - // if not present, add the path member and recurse with an empty list if needed. - this._addToArchiveFolder = (pathNames, memberIndex, entryList, leafIsDir, leafChildPaths) => { - const thisMember = pathNames[memberIndex]; - - let matchEntry = false; - for (let entry of entryList()) { - if(entry['title'] === thisMember) { - matchEntry = entry; - break; - } - } - - const isLeaf = memberIndex === pathNames.length - 1; - let children = null; - - if(matchEntry) { - // entry exists in tree - children = matchEntry['children']; - - } else { - // idPath is a unique key for the tree element, and the path used for subsequent archive operations - let idPath = pathNames.slice(0, memberIndex + 1).join('/'); - - // this path member is a directory if it is not the leaf, or the leaf is a directory type - const isDir = !isLeaf || leafIsDir; - - // if this is a directory, append / to the path and add children list. - // the children list will make it render as a folder, even if no children are added. - if (isDir) { - idPath = idPath + '/'; - children = ko.observableArray(); - } - entryList.push({id: idPath, title: thisMember, children: children}); - - if(isLeaf) { - this._addLeafChildPaths(idPath, leafChildPaths); - } - } - - if(!isLeaf) { - // add the next name in the path to the children list - this._addToArchiveFolder(pathNames, memberIndex + 1, children, leafIsDir, leafChildPaths); - } - }; - - // if the new entry is a directory, the result may include its - // child files and folders, so add those to the tree. - // they don't need archive update entries for save. - this._addLeafChildPaths = (parentPath, leafChildPaths) => { - if(leafChildPaths) { - for (const leafPath of leafChildPaths) { - let fullPath = parentPath + leafPath; - let leafIsDir = false; - if(fullPath.endsWith('/')) { - fullPath = fullPath.slice(0, -1); - leafIsDir = true; - } - const pathNames = fullPath.split('/'); - const childList = project.wdtModel.archiveRoots; - this._addToArchiveFolder(pathNames, 0, childList, leafIsDir, null); - } - } - }; - } - - /* - * Returns a constructor for the ViewModel. - */ - return AddToArchiveDialogModel; -}); diff --git a/webui/src/js/viewModels/add-to-archive-selection-dialog.js b/webui/src/js/viewModels/add-to-archive-selection-dialog.js new file mode 100644 index 000000000..db599544e --- /dev/null +++ b/webui/src/js/viewModels/add-to-archive-selection-dialog.js @@ -0,0 +1,356 @@ +/** + * @license + * 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', 'utils/i18n', 'models/wkt-project', 'utils/dialog-helper', 'ojs/ojarraydataprovider', + 'utils/wkt-logger', 'ojs/ojknockout', 'ojs/ojinputtext', 'ojs/ojlabel', 'ojs/ojbutton', 'ojs/ojdialog', + 'ojs/ojformlayout', 'ojs/ojselectsingle', 'ojs/ojvalidationgroup', 'ojs/ojradioset'], +function(accUtils, ko, i18n, project, dialogHelper, ArrayDataProvider, wktLogger) { + + const jqueryDialogName = '#addToArchiveSelectionDialog'; + + function AddToArchiveSelectionDialogModel() { + + let subscriptions = []; + + this.connected = () => { + accUtils.announce('Add to archive selection dialog loaded.', 'assertive'); + + subscriptions.push(this.archiveEntryType.subscribe((newEntryKey) => { + this.handleArchiveEntryTypeChange(newEntryKey); + })); + + // 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() { + $(jqueryDialogName)[0].open(); + }, 1); + }; + + this.disconnected = () => { + subscriptions.forEach((subscription) => { + subscription.dispose(); + }); + }; + + this.labelMapper = (labelId, arg) => { + if (arg) { + return i18n.t(`add-to-archive-selection-dialog-${labelId}`, arg); + } + return i18n.t(`add-to-archive-selection-dialog-${labelId}`); + }; + + this.anyLabelMapper = (labelId, arg) => { + if (arg) { + return i18n.t(labelId, arg); + } + return i18n.t(labelId); + }; + + this.wktLogger = wktLogger; + this.archiveEntryTypes = ko.observableArray(); + this.archiveEntryTypesProvider = new ArrayDataProvider(this.archiveEntryTypes, {keyAttributes: 'key'}); + this.entryTypesMap = null; + + window.api.ipc.invoke('get-archive-entry-types').then(entryTypes => { + this.entryTypesMap = entryTypes; + + // Must initialize these here since the data retrieval is async... + // + this.initializeCustomPathLabelAndHelp(); + this.initializeCoherencePersistenceDirectoryType(); + + for (const [key, value] of Object.entries(entryTypes)) { + this.archiveEntryTypes.push({key: key, label: value.name, ...value}); + this.archiveEntryTypes.sort((a, b) => (a.label > b.label) ? 1 : -1); + } + }); + + this.archiveEntryType = ko.observable(); + this.archiveEntry = ko.observable(); + this.archiveEntryTypeSubtype = ko.observable(); + this.fileOrDirectory = ko.observable('file'); + + this.getFileOrDirectoryValue = () => { + let type = this.archiveEntryTypeSubtype(); + if (type === 'either') { + type = this.fileOrDirectory(); + } else if (type === 'emptyDir') { + type = undefined; + } + return type; + }; + + this.fileNameSourcePath = ko.observable(); + this.fileNameSourceLabel = ko.computed(() => { + const type = this.getFileOrDirectoryValue(); + if (!!type) { + return this.archiveEntry()[`${type}Label`]; + } + return undefined; + }, this); + this.fileNameSourceHelp = ko.computed(() => { + const type = this.getFileOrDirectoryValue(); + if (!!type) { + return this.archiveEntry()[`${type}Help`]; + } + return undefined; + }, this); + + this.segregationName = ko.observable(); + this.segregationLabel = ko.computed(() => { + const entry = this.archiveEntry(); + if (!!entry) { + return entry.segregatedLabel; + } + return undefined; + }, this); + this.segregationHelp = ko.computed(() => { + const entry = this.archiveEntry(); + if (!!entry) { + return entry.segregatedHelp; + } + return undefined; + }, this); + + this.emptyDirValue = ko.observable(); + this.emptyDirLabel = ko.computed(() => { + const entry = this.archiveEntry(); + if (!!entry) { + return entry.emptyDirLabel; + } + return undefined; + }, this); + this.emptyDirHelp = ko.computed(() => { + const entry = this.archiveEntry(); + if (!!entry) { + return entry.emptyDirLabel; + } + return undefined; + }, this); + this.emptyDirIsSelect = + ko.computed(() => this.archiveEntryType() === 'coherencePersistenceDirectory', this); + + this.customPathValue = ko.observable(); + this.customPathLabel = undefined; + this.customPathHelp = undefined; + + this.coherencePersistenceDirectoryTypes = undefined; + this.coherencePersistenceDirectoryTypesDP = undefined; + + this.fileDirectoryRadioButtonData = [ + { id: 'file', value: 'file', label: this.labelMapper('file-label')}, + { id: 'dir', value: 'dir', label: this.labelMapper('directory-label')} + ]; + this.fileDirectoryRadioButtonsDP = + new ArrayDataProvider(this.fileDirectoryRadioButtonData, { keyAttributes: 'id' }); + + this.getEntryForType = (entryType) => { + return this.entryTypesMap[entryType]; + }; + + this.initializeCustomPathLabelAndHelp = () => { + const customEntry = this.getEntryForType('custom'); + this.customPathLabel = ko.observable(customEntry.pathLabel); + this.customPathHelp = ko.observable(customEntry.pathHelp); + }; + + this.initializeCoherencePersistenceDirectoryType = () => { + const coherencePersistenceDirectoryEntry = this.getEntryForType('coherencePersistenceDirectory'); + this.coherencePersistenceDirectoryTypes = ko.observableArray(coherencePersistenceDirectoryEntry.subtypeChoices); + this.coherencePersistenceDirectoryTypesDP = + new ArrayDataProvider(this.coherencePersistenceDirectoryTypes, { keyAttributes: 'name' }); + }; + + this.handleArchiveEntryTypeChange = (newEntryKey) => { + const newEntry = this.getEntryForType(newEntryKey); + this.archiveEntry(newEntry); + this.archiveEntryTypeSubtype(newEntry.subtype); + this.fileOrDirectory('file'); + this.fileNameSourcePath(undefined); + this.segregationName(undefined); + this.emptyDirValue(undefined); + this.customPathValue(undefined); + }; + + this.showFileNameChooser = ko.computed(() => this.archiveEntryTypeSubtype() !== 'emptyDir', this); + this.showFileOrDirectorySelector = ko.computed(() => this.archiveEntryTypeSubtype() === 'either', this); + this.sourceFileNameIsFile = ko.computed(() => this.getFileOrDirectoryValue() === 'file', this); + this.sourceFileNameIsDir = ko.computed(() => this.getFileOrDirectoryValue() === 'dir', this); + this.showSegregatedNameField = ko.computed(() => { + const entry = this.archiveEntry(); + if (!!entry) { + return !!entry.segregatedLabel; + } + return false; + }, this); + this.showEmptyDirField = ko.computed(() => this.archiveEntryTypeSubtype() === 'emptyDir', this); + this.showCustomPathField = ko.computed(() => this.archiveEntryType() === 'custom', this); + + this.segregatedNameValidator = { + validate: (value) => { + if (value && value.includes('/')) { + throw new Error(this.labelMapper('name-no-forward-slashes')); + } + } + }; + + this.customPathValidator = { + validate: (value) => { + if (value && (value.startsWith('/') || value.endsWith('/'))) { + throw new Error(this.labelMapper('path-no-leading-trailing-forward-slashes')); + } + } + }; + + this.chooseSourceLocation = async () => { + const fileChosen = await window.api.ipc.invoke('choose-archive-entry-file', this.archiveEntryType(), + this.getFileOrDirectoryValue(), this.archiveEntry().extensions, this.fileNameSourcePath()); + + if (!!fileChosen) { + this.fileNameSourcePath(fileChosen); + } + }; + + this.addToArchive = async () => { + let tracker = document.getElementById('tracker'); + + if (tracker.valid === 'valid') { + const options = { + type: this.archiveEntryType() + }; + + switch (this.archiveEntryTypeSubtype()) { + case 'file': + case 'dir': + options.fileType = this.archiveEntryTypeSubtype(); + options.fileName = this.fileNameSourcePath(); + break; + + case 'either': + options.fileType = this.fileOrDirectory(); + options.fileName = this.fileNameSourcePath(); + break; + + case 'emptyDir': + options.emptyDirName = this.emptyDirValue(); + break; + } + + if (this.showSegregatedNameField()) { + options.segregatedName = this.segregationName(); + } + + if (this.archiveEntryType() === 'custom' && !!this.customPathValue()) { + options.customPath = this.customPathValue(); + } + + wktLogger.debug(`Calling add-archive-entry for entry type ${this.archiveEntryType()} with options: ${JSON.stringify(options)}`); + window.api.ipc.invoke('add-archive-entry', this.archiveEntryType(), options).then(result => { + // no archivePath means selection was cancelled, no need to notify or continue + if (!!result.archivePath) { + this._addArchiveUpdate(result.filePath, result.archiveUpdatePath); + this._addToArchiveModel(result.archivePath, options.fileType || 'emptyDir', result.childPaths); + } + $(jqueryDialogName)[0].close(); + }).catch(err => { + dialogHelper.displayCatchAllError('add-archive-entry', err).then(); + }); + } else { + tracker.showMessages(); + tracker.focusOn('@firstInvalidShown'); + } + }; + + this.cancelAdd = () => { + $(jqueryDialogName)[0].close(); + }; + + // add an archive update that will be applied to the file on the next save. + this._addArchiveUpdate = (filePath, archivePath) => { + project.wdtModel.addArchiveUpdate('add', archivePath, filePath); + }; + + // add the archive entry to the model, so it will be displayed in the tree. + this._addToArchiveModel = (archivePath, leafEntryFileType, leafChildPaths) => { + const childList = project.wdtModel.archiveRoots; + const pathNames = archivePath.split('/'); + const leafIsDir = leafEntryFileType !== 'file'; + this._addToArchiveFolder(pathNames, 0, childList, leafIsDir, leafChildPaths); + }; + + // add the path member to the specified entry list in the archive model tree. + // if already present, recurse to the next path member and list if needed. + // if not present, add the path member and recurse with an empty list if needed. + this._addToArchiveFolder = (pathNames, memberIndex, entryList, leafIsDir, leafChildPaths) => { + const thisMember = pathNames[memberIndex]; + + let matchEntry = false; + for (let entry of entryList()) { + if(entry['title'] === thisMember) { + matchEntry = entry; + break; + } + } + + const isLeaf = memberIndex === pathNames.length - 1; + let children = null; + + if(matchEntry) { + // entry exists in tree + children = matchEntry['children']; + + } else { + // idPath is a unique key for the tree element, and the path used for subsequent archive operations + let idPath = pathNames.slice(0, memberIndex + 1).join('/'); + + // this path member is a directory if it is not the leaf, or the leaf is a directory type + const isDir = !isLeaf || leafIsDir; + + // if this is a directory, append / to the path and add children list. + // the children list will make it render as a folder, even if no children are added. + if (isDir) { + idPath = idPath + '/'; + children = ko.observableArray(); + } + entryList.push({id: idPath, title: thisMember, children: children}); + + if(isLeaf) { + this._addLeafChildPaths(idPath, leafChildPaths); + } + } + + if(!isLeaf) { + // add the next name in the path to the children list + this._addToArchiveFolder(pathNames, memberIndex + 1, children, leafIsDir, leafChildPaths); + } + }; + + // if the new entry is a directory, the result may include its + // child files and folders, so add those to the tree. + // they don't need archive update entries for save. + this._addLeafChildPaths = (parentPath, leafChildPaths) => { + if(leafChildPaths) { + for (const leafPath of leafChildPaths) { + let fullPath = parentPath + leafPath; + let leafIsDir = false; + if(fullPath.endsWith('/')) { + fullPath = fullPath.slice(0, -1); + leafIsDir = true; + } + const pathNames = fullPath.split('/'); + const childList = project.wdtModel.archiveRoots; + this._addToArchiveFolder(pathNames, 0, childList, leafIsDir, null); + } + } + }; + } + + /* + * Returns a constructor for the ViewModel. + */ + return AddToArchiveSelectionDialogModel; +}); diff --git a/webui/src/js/viewModels/model-archive-view.js b/webui/src/js/viewModels/model-archive-view.js index c71fe2c92..d90de175d 100644 --- a/webui/src/js/viewModels/model-archive-view.js +++ b/webui/src/js/viewModels/model-archive-view.js @@ -1,12 +1,12 @@ /** * @license - * Copyright (c) 2021, 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/ */ define(['accUtils', 'knockout', 'utils/i18n', 'models/wkt-project', 'utils/dialog-helper', 'ojs/ojarraytreedataprovider', 'ojs/ojtoolbar', 'ojs/ojtreeview'], function(accUtils, ko, i18n, project, dialogHelper, ArrayTreeDataProvider) { - function ModelCodeViewModel() { + function ModelArchiveViewModel() { this.connected = () => { accUtils.announce('Archive view loaded.', 'assertive'); @@ -36,7 +36,7 @@ function(accUtils, ko, i18n, project, dialogHelper, ArrayTreeDataProvider) { }; this.addFile = () => { - dialogHelper.openDialog('add-to-archive-dialog', {}); + dialogHelper.openDialog('add-to-archive-selection-dialog', {}); }; this.deleteSelected = () => { @@ -72,5 +72,5 @@ function(accUtils, ko, i18n, project, dialogHelper, ArrayTreeDataProvider) { /* * Returns a constructor for the ViewModel. */ - return ModelCodeViewModel; + return ModelArchiveViewModel; }); diff --git a/webui/src/js/viewModels/model-design-view.js b/webui/src/js/viewModels/model-design-view.js index 1db6c134b..6b5a5f1a5 100644 --- a/webui/src/js/viewModels/model-design-view.js +++ b/webui/src/js/viewModels/model-design-view.js @@ -1,12 +1,12 @@ /** * @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/ */ define(['accUtils', 'utils/i18n', 'knockout', 'models/wkt-project', 'utils/url-catalog', 'utils/view-helper', - 'utils/wkt-logger', 'wrc-frontend/integration/viewModels/utils', 'wdt-model-designer/loader', + 'utils/wkt-logger', 'wrc-frontend/integration/viewModels/utils', 'ojs/ojlogger', 'wdt-model-designer/loader', 'ojs/ojinputtext', 'ojs/ojlabel', 'ojs/ojbutton', 'ojs/ojformlayout'], -function(accUtils, i18n, ko, project, urlCatalog, viewHelper, wktLogger, ViewModelUtils) { +function(accUtils, i18n, ko, project, urlCatalog, viewHelper, wktLogger, ViewModelUtils, ojLogger) { function ModelDesignViewModel() { let subscriptions = []; @@ -20,6 +20,7 @@ function(accUtils, i18n, ko, project, urlCatalog, viewHelper, wktLogger, ViewMod this.connected = () => { accUtils.announce('Model design view loaded.', 'assertive'); + this.restoreOracleJetLogLevel(); this.designer = document.getElementById('WdtModelDesigner'); subscriptions.push(this.project.wdtModel.internal.wlRemoteConsolePort.subscribe((newValue) => { @@ -69,6 +70,7 @@ function(accUtils, i18n, ko, project, urlCatalog, viewHelper, wktLogger, ViewMod } viewHelper.removeEventListenerFromRootElement('searchModel', this.handleSearchModelEvent); + this.setOracleJetLogLevelToOff(); }; this.labelMapper = (labelId, payload) => { @@ -300,6 +302,18 @@ function(accUtils, i18n, ko, project, urlCatalog, viewHelper, wktLogger, ViewMod // this.designer.search(searchModelText); }; + + this.restoreOracleJetLogLevel = () => { + const level = this.project.wdtModel.internal.wlRemoteConsoleLogLevel; + if (typeof level !== 'undefined') { + ojLogger.option('level', level); + } + }; + + this.setOracleJetLogLevelToOff = () => { + this.project.wdtModel.internal.wlRemoteConsoleLogLevel = ojLogger.option('level'); + ojLogger.option('level', ojLogger.LEVEL_NONE); + }; } /* diff --git a/webui/src/js/viewModels/text-input-dialog.js b/webui/src/js/viewModels/text-input-dialog.js deleted file mode 100644 index a83a9385e..000000000 --- a/webui/src/js/viewModels/text-input-dialog.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @license - * Copyright (c) 2021, 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', 'ojs/ojinputtext', 'ojs/ojlabel', - 'ojs/ojbutton', 'ojs/ojdialog', 'ojs/ojformlayout', 'ojs/ojvalidationgroup'], -function(accUtils, ko, i18n) { - function TextInputDialogModel(args) { - - this.connected = () => { - accUtils.announce('Text input 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() { - $('#textInputDialog')[0].open(); - }, 1); - }; - - this.anyLabelMapper = (labelId, arg) => { - return i18n.t(labelId, arg); - }; - - this.title = args.title; - this.label = args.label; - this.help = args.help; - - this.textValue = ko.observable(); - - this.okInput = () => { - $('#textInputDialog')[0].close(); - args.setValue(this.textValue()); - }; - - this.cancelInput = () => { - $('#textInputDialog')[0].close(); - args.setValue(); - }; - } - - /* - * Returns a constructor for the ViewModel. - */ - return TextInputDialogModel; -}); diff --git a/webui/src/js/viewModels/vz-component-code-view.js b/webui/src/js/viewModels/vz-component-code-view.js index 609998d73..ca40a0ae1 100644 --- a/webui/src/js/viewModels/vz-component-code-view.js +++ b/webui/src/js/viewModels/vz-component-code-view.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 (UPL), Version 1.0 as shown at https://oss.oracle.com/licenses/upl/ */ define(['accUtils', 'knockout', 'models/wkt-project', 'utils/vz-component-script-generator', diff --git a/webui/src/js/views/add-to-archive-selection-dialog.html b/webui/src/js/views/add-to-archive-selection-dialog.html new file mode 100644 index 000000000..a713267a1 --- /dev/null +++ b/webui/src/js/views/add-to-archive-selection-dialog.html @@ -0,0 +1,102 @@ + + +
+ +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+ + + + + + +
+
diff --git a/webui/src/js/views/text-input-dialog.html b/webui/src/js/views/text-input-dialog.html deleted file mode 100644 index 6f0ff88bd..000000000 --- a/webui/src/js/views/text-input-dialog.html +++ /dev/null @@ -1,31 +0,0 @@ - - -
- -
-
- -
- - - - -
-
-
- -
- - - - - - -
-
From 9e98b1fc235f38e9de8f9bfbfd2a873521f98e19 Mon Sep 17 00:00:00 2001 From: Robert Patrick Date: Wed, 8 Feb 2023 13:58:57 -0600 Subject: [PATCH 2/3] preparing formal WRC-WKTUI for archive handling --- electron/app/js/ipcRendererPreload.js | 2 +- electron/app/js/wdtArchive.js | 137 +++++++++------ electron/app/locales/en/webui.json | 22 +-- electron/app/main.js | 4 +- webui/src/js/utils/wdt-archive-helper.js | 157 ++++++++++++++++++ webui/src/js/utils/wrc-wdt-archive.js | 93 +++++++++++ .../add-to-archive-selection-dialog.js | 136 ++------------- webui/src/js/viewModels/model-archive-view.js | 6 +- webui/src/js/viewModels/model-design-view.js | 25 +-- 9 files changed, 373 insertions(+), 209 deletions(-) create mode 100644 webui/src/js/utils/wdt-archive-helper.js create mode 100644 webui/src/js/utils/wrc-wdt-archive.js diff --git a/electron/app/js/ipcRendererPreload.js b/electron/app/js/ipcRendererPreload.js index 4e06f4d38..75b750c2a 100644 --- a/electron/app/js/ipcRendererPreload.js +++ b/electron/app/js/ipcRendererPreload.js @@ -128,7 +128,7 @@ contextBridge.exposeInMainWorld( 'get-latest-wko-version-number', 'get-wko-release-versions', 'get-archive-entry-types', - 'get-archive-entry', + 'wrc-get-archive-entry', 'get-network-settings', 'choose-archive-file', 'choose-archive-entry-file', diff --git a/electron/app/js/wdtArchive.js b/electron/app/js/wdtArchive.js index af9143049..1a4bc17a8 100644 --- a/electron/app/js/wdtArchive.js +++ b/electron/app/js/wdtArchive.js @@ -9,6 +9,7 @@ const fs = require('fs'); const fsPromises = require('fs/promises'); const fsUtils = require('./fsUtils'); const errorUtils = require('./errorUtils'); +const { getLogger } = require('./wktLogging'); function getEntryTypes() { // lazy load to allow initialization @@ -279,6 +280,9 @@ async function addArchiveEntry(targetWindow, entryType, entryData) { return Promise.resolve({filePath, archivePath, archiveUpdatePath, childPaths}); } +// NOTE: This is part of the WRC-WKTUI API and is called directly from +// WRC via the IPC channel. +// // Help the model design view properly handle archive entries when using // both text fields and the file chooser. // @@ -297,7 +301,18 @@ async function addArchiveEntry(targetWindow, entryType, entryData) { // the user provided path is a directory and change the archive type // to the matching directory type. // -async function getArchiveEntry(window, archiveEntryType, options) { +// Note that as of today, this API only supports 5 types: +// - applicationFile +// - applicationDir +// - applicationDeploymentPlan +// - sharedLibraryFile +// - sharedLibraryDir +// +// The code below needs to be reviewed before adding new types as it +// may or may not work correctly depending on the entry type. +// +async function wrcGetArchiveEntry(window, archiveEntryTypeName, options) { + getLogger().debug(`Entering wrcGetArchiveEntry with ${archiveEntryTypeName} and ${JSON.stringify(options)}`); if (!options) { options = { providedValue: undefined, @@ -307,38 +322,14 @@ async function getArchiveEntry(window, archiveEntryType, options) { let promise; if (options.showChooser) { - promise = _getArchiveEntryShowChooser(window, archiveEntryType, options); + promise = _getArchiveEntryShowChooser(window, archiveEntryTypeName, options); } else { - promise = _getArchiveEntry(archiveEntryType, options); + promise = _getArchiveEntry(archiveEntryTypeName, options); } return promise; } - -// get a list of paths for all the files and folders in the specified directory. -// the paths should be relative to the directory, and folders should end with / . -async function _getDirectoryPaths(directory) { - const paths = []; - await _addDirectoryPaths(directory, paths, null); - return paths; -} - -async function _addDirectoryPaths(directory, paths, pathPrefix) { - const dirContents = await fsPromises.readdir(directory, {withFileTypes: true}); - for (const entry of dirContents) { - let name = entry.name; - let entryPath = pathPrefix ? pathPrefix + '/' + name : name; - let fullPath = entry.isDirectory() ? entryPath + '/' : entryPath; - paths.push(fullPath); - - if(entry.isDirectory()) { - const subdirectory = directory + '/' + name; - await _addDirectoryPaths(subdirectory, paths, entryPath); - } - } -} - -async function _getArchiveEntryShowChooser(targetWindow, archiveEntryTypeName, archiveEntryTypeOptions) { +async function _getArchiveEntryShowChooser(targetWindow, wrcArchiveEntryTypeName, archiveEntryTypeOptions) { // lazy load to allow initialization const i18n = require('./i18next.config'); const { getLogger } = require('./wktLogging'); @@ -346,15 +337,21 @@ async function _getArchiveEntryShowChooser(targetWindow, archiveEntryTypeName, a if (wktLogger.isDebugEnabled()) { wktLogger.debug('entering _getArchiveEntryShowChooser(%s, %s, %s)', - targetWindow, archiveEntryTypeName, JSON.stringify(archiveEntryTypeOptions)); + targetWindow, wrcArchiveEntryTypeName, JSON.stringify(archiveEntryTypeOptions)); } const result = {}; + let fileType = _getFileTypeFromWrcArchiveEntryTypeName(wrcArchiveEntryTypeName); + const archiveEntryTypeName = _getArchiveEntryTypeNameFromWrcArchiveEntryTypeName(wrcArchiveEntryTypeName); + const archiveEntryType = getEntryTypes()[archiveEntryTypeName]; if (!archiveEntryType) { result.errorMessage = i18n.t('wdt-archive-invalid-archive-entry-type', { type: archiveEntryTypeName }); return result; } + if (!fileType) { + fileType = archiveEntryType.subtype; + } let defaultPath; if (archiveEntryTypeOptions.providedValue) { @@ -362,7 +359,7 @@ async function _getArchiveEntryShowChooser(targetWindow, archiveEntryTypeName, a } const title = i18n.t('dialog-chooseArchiveEntry', {entryType: archiveEntryType.name}); - const chooserType = archiveEntryType.subtype === 'file' ? 'openFile' : 'openDirectory'; + const chooserType = fileType === 'file' ? 'openFile' : 'openDirectory'; let options = { title: title, @@ -375,16 +372,8 @@ async function _getArchiveEntryShowChooser(targetWindow, archiveEntryTypeName, a const { chooseFromFileSystem } = require('./wktWindow'); const fileSystemPath = await chooseFromFileSystem(targetWindow, options); - result.archiveEntryType = archiveEntryTypeName; + result.archiveEntryType = wrcArchiveEntryTypeName; result.filePath = fileSystemPath; - if (result.filePath) { - result.archivePath = archiveEntryType['pathPrefix'] + path.basename(result.filePath); - result.archiveUpdatePath = result.archivePath; - if (chooserType === 'openDirectory') { - result.archiveUpdatePath = `${result.archivePath}/`; - result.childPaths = await _getDirectoryPaths(result.filePath); - } - } if (wktLogger.isDebugEnabled()) { wktLogger.debug('exiting _getArchiveEntryShowChooser() with %s', JSON.stringify(result)); @@ -392,23 +381,30 @@ async function _getArchiveEntryShowChooser(targetWindow, archiveEntryTypeName, a return result; } -async function _getArchiveEntry(archiveEntryTypeName, archiveEntryTypeOptions) { +async function _getArchiveEntry(wrcArchiveEntryTypeName, archiveEntryTypeOptions) { // lazy load to allow initialization const i18n = require('./i18next.config'); const { getLogger } = require('./wktLogging'); const wktLogger = getLogger(); if (wktLogger.isDebugEnabled()) { - wktLogger.debug('entering _getArchiveEntry(%s, %s)', archiveEntryTypeName, JSON.stringify(archiveEntryTypeOptions)); + wktLogger.debug('entering _getArchiveEntry(%s, %s)', wrcArchiveEntryTypeName, JSON.stringify(archiveEntryTypeOptions)); } const result = {}; + let fileType = _getFileTypeFromWrcArchiveEntryTypeName(wrcArchiveEntryTypeName); + const archiveEntryTypeName = _getArchiveEntryTypeNameFromWrcArchiveEntryTypeName(wrcArchiveEntryTypeName); + const archiveEntryTypes = getEntryTypes(); let archiveEntryType = archiveEntryTypes[archiveEntryTypeName]; if (!archiveEntryType) { result.errorMessage = i18n.t('wdt-archive-invalid-archive-entry-type', { type: archiveEntryTypeName }); return result; } + if (!fileType) { + // this should only happen for the applicationDeploymentPlan type + fileType = archiveEntryType.subtype; + } const fileSystemPath = archiveEntryTypeOptions.providedValue; if (!fileSystemPath) { @@ -421,9 +417,11 @@ async function _getArchiveEntry(archiveEntryTypeName, archiveEntryTypeOptions) { return result; } - result.archiveEntryType = archiveEntryTypeName; + result.archiveEntryType = wrcArchiveEntryTypeName; const isDirectory = await fsUtils.isDirectory(fileSystemPath); - if (!_archiveEntryTypesMatch(isDirectory, archiveEntryType)) { + const isFile = await fsUtils.isFile(fileSystemPath); + + if (!_archiveEntryTypesMatch(isDirectory, isFile, fileType)) { if (_archiveEntryTypeHasDualTypes(archiveEntryTypeName)) { result.archiveEntryType = _archiveEntryTypeGetOppositeType(archiveEntryTypeName); } else { @@ -448,12 +446,32 @@ async function _getArchiveEntry(archiveEntryTypeName, archiveEntryTypeOptions) { return result; } -function _archiveEntryTypesMatch(fileSystemPathIsDirectory, archiveEntryType) { +function _getFileTypeFromWrcArchiveEntryTypeName(archiveEntryTypeName) { let result; + if (archiveEntryTypeName.endsWith('Dir')) { + result = 'dir'; + } else if (archiveEntryTypeName.endsWith('File')) { + result = 'file'; + } + return result; +} + +function _getArchiveEntryTypeNameFromWrcArchiveEntryTypeName(wrcArchiveEntryTypeName) { + let archiveEntryTypeName = wrcArchiveEntryTypeName; + if (wrcArchiveEntryTypeName.endsWith('Dir')) { + archiveEntryTypeName = wrcArchiveEntryTypeName.slice(0, -3); + } else if (wrcArchiveEntryTypeName.endsWith('File')) { + archiveEntryTypeName = wrcArchiveEntryTypeName.slice(0, -4); + } + return archiveEntryTypeName; +} + +function _archiveEntryTypesMatch(fileSystemPathIsDirectory, fileSystemPathIsFile, archiveEntrySubtype) { + let result = false; if (fileSystemPathIsDirectory) { - result = archiveEntryType.subtype === 'dir' || archiveEntryType.subtype === 'emptyDir'; - } else { - result = archiveEntryType.subtype === 'file'; + result = archiveEntrySubtype === 'dir' || archiveEntrySubtype === 'emptyDir'; + } else if (fileSystemPathIsFile) { + result = archiveEntrySubtype === 'file'; } return result; } @@ -510,6 +528,29 @@ function _archiveEntryTypeGetOppositeType(archiveEntryTypeName) { return result; } +// get a list of paths for all the files and folders in the specified directory. +// the paths should be relative to the directory, and folders should end with / . +async function _getDirectoryPaths(directory) { + const paths = []; + await _addDirectoryPaths(directory, paths, null); + return paths; +} + +async function _addDirectoryPaths(directory, paths, pathPrefix) { + const dirContents = await fsPromises.readdir(directory, {withFileTypes: true}); + for (const entry of dirContents) { + let name = entry.name; + let entryPath = pathPrefix ? pathPrefix + '/' + name : name; + let fullPath = entry.isDirectory() ? entryPath + '/' : entryPath; + paths.push(fullPath); + + if(entry.isDirectory()) { + const subdirectory = directory + '/' + name; + await _addDirectoryPaths(subdirectory, paths, entryPath); + } + } +} + async function _getDefaultPathFromValue(currentValue) { return new Promise((resolve, reject) => { if (!currentValue) { @@ -667,5 +708,5 @@ module.exports = { getEntryTypes, addArchiveEntry, chooseArchiveEntryFile, - getArchiveEntry + wrcGetArchiveEntry }; diff --git a/electron/app/locales/en/webui.json b/electron/app/locales/en/webui.json index 8aa352706..c9d618075 100644 --- a/electron/app/locales/en/webui.json +++ b/electron/app/locales/en/webui.json @@ -890,13 +890,6 @@ "ingress-code-add-routes-script-title": "Add Ingress Routes Script", "ingress-code-ingress-yaml-title": "Ingress Routes Resource", - "add-to-archive-dialog-title": "Add to Archive", - "add-to-archive-dialog-entry-type-label": "Archive Entry Type", - "add-to-archive-dialog-entry-type-help": "Choose the type of archive entry to add", - "add-name-to-archive-dialog-title": "Add {{typeName}} to Archive", - "add-name-to-archive-dialog-label": "{{typeName}} Name", - "add-name-to-archive-dialog-help": "Enter a name for the {{typeName}}.", - "add-to-archive-selection-dialog-title": "Add to Archive", "add-to-archive-selection-dialog-entry-type-label": "Archive Entry Type", "add-to-archive-selection-dialog-entry-type-help": "Choose the type of archive entry to add", @@ -909,13 +902,6 @@ "add-to-archive-selection-dialog-name-no-forward-slashes": "Value cannot contain any '/' characters", "add-to-archive-selection-dialog-path-no-leading-trailing-forward-slashes": "Value cannot start or end with a '/' character", - - - - "add-name-to-archive-selection-dialog-title": "Add {{typeName}} to Archive", - "add-name-to-archive-selection-dialog-label": "{{typeName}} Name", - "add-name-to-archive-selection-dialog-help": "Enter a name for the {{typeName}}.", - "discover-dialog-offline-form-name": "Discover Domain Offline", "discover-dialog-online-form-name": "Discover Domain Online", "discover-dialog-title": "Discover Domain Offline", @@ -1929,5 +1915,11 @@ "archive-type-SCRIPTS": "Scripts", "archive-type-SERVER_KEYSTORE": "Server Key Stores", "archive-type-SHARED_LIBRARIES": "Shared Libraries", - "archive-type-SHLIB_PLAN": "Shared Library Plans" + "archive-type-SHLIB_PLAN": "Shared Library Plans", + + "wrc-wdt-archive-add-empty-wrc-type-error": "Failed to add to archive because the WebLogic Remote Console entry type was empty.", + "wrc-wdt-archive-add-bad-wrc-type-error": "Failed to add to archive because the WebLogic Remote Console entry type {{wrcType}} was not known.", + "wrc-wdt-archive-add-empty-file-path-error": "Failed to add to archive because WebLogic Remote Console entry type {{wrcType}} requires a non-empty file path.", + "wrc-wdt-archive-add-failed-error": "Failed to add WebLogic Remote Console entry type {{wrcType}} to archive: {{error}}", + "wrc-wdt-archive-remove-empty-path-error": "Failed to remove from archive because the archive path was empty." } diff --git a/electron/app/main.js b/electron/app/main.js index c86ceaf0c..a7f49188d 100644 --- a/electron/app/main.js +++ b/electron/app/main.js @@ -502,8 +502,8 @@ class Main { // This is used by the Model Design View... // - ipcMain.handle('get-archive-entry', async (event, archiveEntryType, options) => { - return wdtArchive.getArchiveEntry(event.sender.getOwnerBrowserWindow(), archiveEntryType, options); + ipcMain.handle('wrc-get-archive-entry', async (event, archiveEntryType, options) => { + return wdtArchive.wrcGetArchiveEntry(event.sender.getOwnerBrowserWindow(), archiveEntryType, options); }); ipcMain.handle('choose-java-home', async (event, defaultPath) => { diff --git a/webui/src/js/utils/wdt-archive-helper.js b/webui/src/js/utils/wdt-archive-helper.js new file mode 100644 index 000000000..a465e733f --- /dev/null +++ b/webui/src/js/utils/wdt-archive-helper.js @@ -0,0 +1,157 @@ +/** + * @license + * 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'; + +/** + * An helper for WDT archive operations. + * Returns a singleton. + */ + +define(['knockout', 'models/wkt-project'], + function (ko, project) { + function WdtArchiveHelper() { + + this.project = project; + + // return available archive entry types and names + this.getEntryTypes = async () => { + return window.api.ipc.invoke('get-archive-entry-types'); + }; + + this.chooseArchiveEntryFile = async (archiveEntryTypeName, fileType, fileExtensions, fileName) => { + return window.api.ipc.invoke('choose-archive-entry-file', archiveEntryTypeName, fileType, + fileExtensions, fileName); + }; + + this.buildAddToArchiveOptions = (archiveEntryTypeName, archiveEntry, filePath, fileType, otherArgs) => { + const options = { + type: archiveEntryTypeName + }; + + switch (archiveEntry.subtype) { + case 'file': + case 'dir': + options.fileType = archiveEntry.subtype; + options.fileName = filePath; + break; + + case 'either': + options.fileType = fileType; + options.fileName = filePath; + break; + + case 'emptyDir': + options.emptyDirName = otherArgs.emptyDirName; + break; + } + + if (archiveEntry.segregatedName) { + options.segregatedName = otherArgs.segregationName; + } + + if (archiveEntryTypeName === 'custom' && !!otherArgs.customPath) { + options.customPath = otherArgs.customPath; + } + + return options; + }; + + this.addToArchive = async (archiveEntryTypeName, options) => { + const result = await window.api.ipc.invoke('add-archive-entry', archiveEntryTypeName, options); + // no archivePath means selection was cancelled, no need to notify or continue + if (!!result.archivePath) { + this._addArchiveUpdate(result.filePath, result.archiveUpdatePath); + this._addToArchiveModel(result.archivePath, options.fileType || 'emptyDir', result.childPaths); + } + return result.archivePath; + }; + + this.removeFromArchive = (archivePath) => { + this.project.wdtModel.addArchiveUpdate('remove', archivePath); + }; + + // add an archive update that will be applied to the file on the next save. + this._addArchiveUpdate = (filePath, archivePath) => { + this.project.wdtModel.addArchiveUpdate('add', archivePath, filePath); + }; + + // add the archive entry to the model, so it will be displayed in the tree. + this._addToArchiveModel = (archivePath, leafEntryFileType, leafChildPaths) => { + const childList = this.project.wdtModel.archiveRoots; + const pathNames = archivePath.split('/'); + const leafIsDir = leafEntryFileType !== 'file'; + this._addToArchiveFolder(pathNames, 0, childList, leafIsDir, leafChildPaths); + }; + + // add the path member to the specified entry list in the archive model tree. + // if already present, recurse to the next path member and list if needed. + // if not present, add the path member and recurse with an empty list if needed. + this._addToArchiveFolder = (pathNames, memberIndex, entryList, leafIsDir, leafChildPaths) => { + const thisMember = pathNames[memberIndex]; + + let matchEntry = false; + for (let entry of entryList()) { + if(entry['title'] === thisMember) { + matchEntry = entry; + break; + } + } + + const isLeaf = memberIndex === pathNames.length - 1; + let children = null; + + if(matchEntry) { + // entry exists in tree + children = matchEntry['children']; + } else { + // idPath is a unique key for the tree element, and the path used for subsequent archive operations + let idPath = pathNames.slice(0, memberIndex + 1).join('/'); + + // this path member is a directory if it is not the leaf, or the leaf is a directory type + const isDir = !isLeaf || leafIsDir; + + // if this is a directory, append / to the path and add children list. + // the children list will make it render as a folder, even if no children are added. + if (isDir) { + idPath = idPath + '/'; + children = ko.observableArray(); + } + entryList.push({id: idPath, title: thisMember, children: children}); + + if(isLeaf) { + this._addLeafChildPaths(idPath, leafChildPaths); + } + } + + if(!isLeaf) { + // add the next name in the path to the children list + this._addToArchiveFolder(pathNames, memberIndex + 1, children, leafIsDir, leafChildPaths); + } + }; + + // if the new entry is a directory, the result may include its + // child files and folders, so add those to the tree. + // they don't need archive update entries for save. + this._addLeafChildPaths = (parentPath, leafChildPaths) => { + if(leafChildPaths) { + for (const leafPath of leafChildPaths) { + let fullPath = parentPath + leafPath; + let leafIsDir = false; + if(fullPath.endsWith('/')) { + fullPath = fullPath.slice(0, -1); + leafIsDir = true; + } + const pathNames = fullPath.split('/'); + const childList = project.wdtModel.archiveRoots; + this._addToArchiveFolder(pathNames, 0, childList, leafIsDir, null); + } + } + }; + } + + return new WdtArchiveHelper(); + } +); diff --git a/webui/src/js/utils/wrc-wdt-archive.js b/webui/src/js/utils/wrc-wdt-archive.js new file mode 100644 index 000000000..7ea85364e --- /dev/null +++ b/webui/src/js/utils/wrc-wdt-archive.js @@ -0,0 +1,93 @@ +/** + * @license + * Copyright (c) 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(['models/wkt-project', 'utils/i18n', 'utils/wdt-archive-helper'], + function(project, i18n, archiveHelper) { + class WrcWdtArchive { + constructor() { + this.project = project; + } + + async addToArchive(wrcEntryTypeName, filePath, otherArgs={}) { + const typeInfo = this._convertWrcType(wrcEntryTypeName); + if (typeof typeInfo === 'undefined') { + return Promise.reject(new Error(i18n.t('wrc-wdt-archive-add-empty-wrc-type-error'))); + } else if (!filePath) { + return Promise.reject(new Error(i18n.t('wrc-wdt-archive-add-empty-file-path-error'))); + } + + const archiveEntryTypeName = typeInfo.entryTypeName; + let filePathType = typeInfo.fileType; + const archiveEntryTypes = await archiveHelper.getEntryTypes(); + const archiveEntry = archiveEntryTypes[archiveEntryTypeName]; + if (typeof archiveEntry === 'undefined') { + return Promise.reject(new Error(i18n.t('wrc-wdt-archive-add-bad-wrc-type-error', {wrcType: wrcEntryTypeName}))); + } + if (!filePathType && (archiveEntry.subtype === 'file' || archiveEntry.subtype === 'dir')) { + filePathType = archiveEntry.subtype; + } + + const options = + archiveHelper.buildAddToArchiveOptions(archiveEntryTypeName, archiveEntry, filePath, filePathType, otherArgs); + + return new Promise((resolve, reject) => { + archiveHelper.addToArchive(archiveEntryTypeName, options).then((archivePath) => { + resolve(archivePath); + }).catch(err => { + const errMessage = err instanceof Error ? err.message : err; + const error = + new Error(i18n.t('wrc-wdt-archive-add-failed-error', {wrcType: wrcEntryTypeName, error: errMessage})); + reject(error); + }); + }); + } + + async removeFromArchive(archivePath) { + return new Promise((resolve, reject) => { + if (archivePath) { + archiveHelper.removeFromArchive(archivePath); + resolve(); + } else { + reject(new Error(i18n.t('wrc-wdt-archive-remove-empty-path-error'))); + } + }); + } + + getExtensionsObject() { + return { + wktui: { + modelArchive: { + addToArchive: this.addToArchive, + removeFromArchive: this.removeFromArchive + } + } + }; + } + + _convertWrcType(wrcEntryTypeName) { + const result = {}; + + if (wrcEntryTypeName) { + if (wrcEntryTypeName.endsWith('File')) { + result.fileType = 'file'; + result.entryTypeName = wrcEntryTypeName.slice(0, -4); + } else if (wrcEntryTypeName.endsWith('Dir')) { + result.fileType = 'dir'; + result.entryTypeName = wrcEntryTypeName.slice(0, -3); + } + else { + result.type = wrcEntryTypeName; + } + } + + return result; + } + } + + return new WrcWdtArchive(); + } +); diff --git a/webui/src/js/viewModels/add-to-archive-selection-dialog.js b/webui/src/js/viewModels/add-to-archive-selection-dialog.js index db599544e..ddaceb075 100644 --- a/webui/src/js/viewModels/add-to-archive-selection-dialog.js +++ b/webui/src/js/viewModels/add-to-archive-selection-dialog.js @@ -5,10 +5,10 @@ */ 'use strict'; -define(['accUtils', 'knockout', 'utils/i18n', 'models/wkt-project', 'utils/dialog-helper', 'ojs/ojarraydataprovider', - 'utils/wkt-logger', 'ojs/ojknockout', 'ojs/ojinputtext', 'ojs/ojlabel', 'ojs/ojbutton', 'ojs/ojdialog', - 'ojs/ojformlayout', 'ojs/ojselectsingle', 'ojs/ojvalidationgroup', 'ojs/ojradioset'], -function(accUtils, ko, i18n, project, dialogHelper, ArrayDataProvider, wktLogger) { +define(['accUtils', 'knockout', 'utils/i18n', 'models/wkt-project', 'utils/dialog-helper', 'utils/wdt-archive-helper', + 'ojs/ojarraydataprovider', 'utils/wkt-logger', 'ojs/ojknockout', 'ojs/ojinputtext', 'ojs/ojlabel', 'ojs/ojbutton', + 'ojs/ojdialog', 'ojs/ojformlayout', 'ojs/ojselectsingle', 'ojs/ojvalidationgroup', 'ojs/ojradioset'], +function(accUtils, ko, i18n, project, dialogHelper, archiveHelper, ArrayDataProvider, wktLogger) { const jqueryDialogName = '#addToArchiveSelectionDialog'; @@ -55,7 +55,7 @@ function(accUtils, ko, i18n, project, dialogHelper, ArrayDataProvider, wktLogger this.archiveEntryTypesProvider = new ArrayDataProvider(this.archiveEntryTypes, {keyAttributes: 'key'}); this.entryTypesMap = null; - window.api.ipc.invoke('get-archive-entry-types').then(entryTypes => { + archiveHelper.getEntryTypes().then(entryTypes => { this.entryTypesMap = entryTypes; // Must initialize these here since the data retrieval is async... @@ -207,7 +207,7 @@ function(accUtils, ko, i18n, project, dialogHelper, ArrayDataProvider, wktLogger }; this.chooseSourceLocation = async () => { - const fileChosen = await window.api.ipc.invoke('choose-archive-entry-file', this.archiveEntryType(), + const fileChosen = await archiveHelper.chooseArchiveEntryFile(this.archiveEntryType(), this.getFileOrDirectoryValue(), this.archiveEntry().extensions, this.fileNameSourcePath()); if (!!fileChosen) { @@ -219,42 +219,15 @@ function(accUtils, ko, i18n, project, dialogHelper, ArrayDataProvider, wktLogger let tracker = document.getElementById('tracker'); if (tracker.valid === 'valid') { - const options = { - type: this.archiveEntryType() - }; - - switch (this.archiveEntryTypeSubtype()) { - case 'file': - case 'dir': - options.fileType = this.archiveEntryTypeSubtype(); - options.fileName = this.fileNameSourcePath(); - break; - - case 'either': - options.fileType = this.fileOrDirectory(); - options.fileName = this.fileNameSourcePath(); - break; - - case 'emptyDir': - options.emptyDirName = this.emptyDirValue(); - break; - } - - if (this.showSegregatedNameField()) { - options.segregatedName = this.segregationName(); - } - - if (this.archiveEntryType() === 'custom' && !!this.customPathValue()) { - options.customPath = this.customPathValue(); - } - - wktLogger.debug(`Calling add-archive-entry for entry type ${this.archiveEntryType()} with options: ${JSON.stringify(options)}`); - window.api.ipc.invoke('add-archive-entry', this.archiveEntryType(), options).then(result => { - // no archivePath means selection was cancelled, no need to notify or continue - if (!!result.archivePath) { - this._addArchiveUpdate(result.filePath, result.archiveUpdatePath); - this._addToArchiveModel(result.archivePath, options.fileType || 'emptyDir', result.childPaths); - } + const options = archiveHelper.buildAddToArchiveOptions(this.archiveEntryType(), this.archiveEntry(), + this.fileNameSourcePath(), this.fileOrDirectory(), { + emptyDirName: this.emptyDirValue(), + segregationName: this.segregationName(), + customPath: this.customPathValue() + }); + + wktLogger.debug(`Calling addToArchive for entry type ${this.archiveEntryType()} with options: ${JSON.stringify(options)}`); + archiveHelper.addToArchive(this.archiveEntryType(), options).then(() => { $(jqueryDialogName)[0].close(); }).catch(err => { dialogHelper.displayCatchAllError('add-archive-entry', err).then(); @@ -268,85 +241,6 @@ function(accUtils, ko, i18n, project, dialogHelper, ArrayDataProvider, wktLogger this.cancelAdd = () => { $(jqueryDialogName)[0].close(); }; - - // add an archive update that will be applied to the file on the next save. - this._addArchiveUpdate = (filePath, archivePath) => { - project.wdtModel.addArchiveUpdate('add', archivePath, filePath); - }; - - // add the archive entry to the model, so it will be displayed in the tree. - this._addToArchiveModel = (archivePath, leafEntryFileType, leafChildPaths) => { - const childList = project.wdtModel.archiveRoots; - const pathNames = archivePath.split('/'); - const leafIsDir = leafEntryFileType !== 'file'; - this._addToArchiveFolder(pathNames, 0, childList, leafIsDir, leafChildPaths); - }; - - // add the path member to the specified entry list in the archive model tree. - // if already present, recurse to the next path member and list if needed. - // if not present, add the path member and recurse with an empty list if needed. - this._addToArchiveFolder = (pathNames, memberIndex, entryList, leafIsDir, leafChildPaths) => { - const thisMember = pathNames[memberIndex]; - - let matchEntry = false; - for (let entry of entryList()) { - if(entry['title'] === thisMember) { - matchEntry = entry; - break; - } - } - - const isLeaf = memberIndex === pathNames.length - 1; - let children = null; - - if(matchEntry) { - // entry exists in tree - children = matchEntry['children']; - - } else { - // idPath is a unique key for the tree element, and the path used for subsequent archive operations - let idPath = pathNames.slice(0, memberIndex + 1).join('/'); - - // this path member is a directory if it is not the leaf, or the leaf is a directory type - const isDir = !isLeaf || leafIsDir; - - // if this is a directory, append / to the path and add children list. - // the children list will make it render as a folder, even if no children are added. - if (isDir) { - idPath = idPath + '/'; - children = ko.observableArray(); - } - entryList.push({id: idPath, title: thisMember, children: children}); - - if(isLeaf) { - this._addLeafChildPaths(idPath, leafChildPaths); - } - } - - if(!isLeaf) { - // add the next name in the path to the children list - this._addToArchiveFolder(pathNames, memberIndex + 1, children, leafIsDir, leafChildPaths); - } - }; - - // if the new entry is a directory, the result may include its - // child files and folders, so add those to the tree. - // they don't need archive update entries for save. - this._addLeafChildPaths = (parentPath, leafChildPaths) => { - if(leafChildPaths) { - for (const leafPath of leafChildPaths) { - let fullPath = parentPath + leafPath; - let leafIsDir = false; - if(fullPath.endsWith('/')) { - fullPath = fullPath.slice(0, -1); - leafIsDir = true; - } - const pathNames = fullPath.split('/'); - const childList = project.wdtModel.archiveRoots; - this._addToArchiveFolder(pathNames, 0, childList, leafIsDir, null); - } - } - }; } /* diff --git a/webui/src/js/viewModels/model-archive-view.js b/webui/src/js/viewModels/model-archive-view.js index d90de175d..6eaa5dc53 100644 --- a/webui/src/js/viewModels/model-archive-view.js +++ b/webui/src/js/viewModels/model-archive-view.js @@ -3,9 +3,9 @@ * 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/ */ -define(['accUtils', 'knockout', 'utils/i18n', 'models/wkt-project', 'utils/dialog-helper', +define(['accUtils', 'knockout', 'utils/i18n', 'models/wkt-project', 'utils/dialog-helper', 'utils/wdt-archive-helper', 'ojs/ojarraytreedataprovider', 'ojs/ojtoolbar', 'ojs/ojtreeview'], -function(accUtils, ko, i18n, project, dialogHelper, ArrayTreeDataProvider) { +function(accUtils, ko, i18n, project, dialogHelper, archiveHelper, ArrayTreeDataProvider) { function ModelArchiveViewModel() { this.connected = () => { @@ -46,7 +46,7 @@ function(accUtils, ko, i18n, project, dialogHelper, ArrayTreeDataProvider) { this._deleteArchiveNode(path, project.wdtModel.archiveRoots); // add a 'remove' operation to be applied on save - project.wdtModel.addArchiveUpdate('remove', path); + archiveHelper.removeFromArchive(path); }; // recursively search the archive tree for a node matching the ID, diff --git a/webui/src/js/viewModels/model-design-view.js b/webui/src/js/viewModels/model-design-view.js index 6b5a5f1a5..19470329a 100644 --- a/webui/src/js/viewModels/model-design-view.js +++ b/webui/src/js/viewModels/model-design-view.js @@ -3,10 +3,11 @@ * 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/ */ -define(['accUtils', 'utils/i18n', 'knockout', 'models/wkt-project', 'utils/url-catalog', 'utils/view-helper', - 'utils/wkt-logger', 'wrc-frontend/integration/viewModels/utils', 'ojs/ojlogger', 'wdt-model-designer/loader', - 'ojs/ojinputtext', 'ojs/ojlabel', 'ojs/ojbutton', 'ojs/ojformlayout'], -function(accUtils, i18n, ko, project, urlCatalog, viewHelper, wktLogger, ViewModelUtils, ojLogger) { +define(['accUtils', 'utils/i18n', 'knockout', 'models/wkt-project', 'utils/url-catalog', 'utils/wrc-wdt-archive', + 'utils/view-helper', 'utils/wkt-logger', 'wrc-frontend/integration/viewModels/utils', 'ojs/ojlogger', + 'wdt-model-designer/loader', 'ojs/ojinputtext', 'ojs/ojlabel', 'ojs/ojbutton', 'ojs/ojformlayout'], +function(accUtils, i18n, ko, project, urlCatalog, wrcArchiveHelper, viewHelper, wktLogger, ViewModelUtils, + ojLogger) { function ModelDesignViewModel() { let subscriptions = []; @@ -49,7 +50,6 @@ function(accUtils, i18n, ko, project, urlCatalog, viewHelper, wktLogger, ViewMod if (this.designer) { viewHelper.componentReady(this.designer).then(() => { this.showWdtModelDesigner(port, this.designer); - this.designer.addEventListener('archiveUpdated', this.archiveUpdated); }); } } @@ -66,7 +66,6 @@ function(accUtils, i18n, ko, project, urlCatalog, viewHelper, wktLogger, ViewMod // Do not stringify the dataProvider to the log since it may contain credentials... wktLogger.debug('disconnected() dataProvider'); this.designer.deactivateProvider(this.dataProvider); - this.designer.removeEventListener('archiveUpdated', this.archiveUpdated); } viewHelper.removeEventListenerFromRootElement('searchModel', this.handleSearchModelEvent); @@ -163,6 +162,7 @@ function(accUtils, i18n, ko, project, urlCatalog, viewHelper, wktLogger, ViewMod // this.providerActivated = (event) => { this.dataProvider = event.detail.value; + this.dataProvider.putValue('extensions', wrcArchiveHelper.getExtensionsObject()); wktLogger.debug('Received providerActivated event with dataProvider'); this.designer.selectLastVisitedSlice(); }; @@ -207,19 +207,6 @@ function(accUtils, i18n, ko, project, urlCatalog, viewHelper, wktLogger, ViewMod } }; - this.archiveUpdated = (event) => { - const options = event.detail.options; - wktLogger.debug('Received archiveUpdated event'); - switch (options.operation) { - case 'add': - this.project.wdtModel.addArchiveUpdate(options.operation, options.archivePath, options.filePath); - break; - case 'remove': - this.project.wdtModel.addArchiveUpdate(options.operation, options.path); - break; - } - }; - // Triggered when WDT Model File provider has been deactivated with the WRC backend. // this.providerDeactivated = (event) => { From 27dc3fe4e9cd5b1b834c08ae8388bc000a0cc2ac Mon Sep 17 00:00:00 2001 From: Robert Patrick Date: Wed, 8 Feb 2023 14:45:30 -0600 Subject: [PATCH 3/3] adopting dev version of WRC 2.4.2 --- webui/package-lock.json | 14 +++++++------- webui/package.json | 2 +- webui/src/js/utils/wrc-wdt-archive.js | 5 +---- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/webui/package-lock.json b/webui/package-lock.json index 49a0ce7be..ed7a88f23 100644 --- a/webui/package-lock.json +++ b/webui/package-lock.json @@ -6,7 +6,7 @@ "": { "dependencies": { "@oracle/oraclejet": "^13.1.7", - "@oracle/wrc-jet-pack": "^2.4.1", + "@oracle/wrc-jet-pack": "~2.4.2-develop", "ace-builds": "^1.15.0", "i18next": "^22.4.9", "jquery": "^3.6.3", @@ -921,9 +921,9 @@ "integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==" }, "node_modules/@oracle/wrc-jet-pack": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@oracle/wrc-jet-pack/-/wrc-jet-pack-2.4.1.tgz", - "integrity": "sha512-44B1X1u2EoC/Jrq6VbgyQRmB5gZLL0cgvyIJSUE6yGGGb4GtaH5hhKiEZlDD7EY74oZ3afjEtMYLcEVa383vNw==", + "version": "2.4.2-develop.202302081956", + "resolved": "https://artifacthub-phx.oci.oraclecorp.com/api/npm/npmjs-remote/@oracle/wrc-jet-pack/-/wrc-jet-pack-2.4.2-develop.202302081956.tgz", + "integrity": "sha512-oMjsXubEqlYFsrHECFw6TF6u9ZbRw1NOlVhlZNbovLZXmVYExgNk2vaIvUexKvd3DfTLTMDDXW0sgSGsqzgM2Q==", "engines": { "node": ">=4.0.0" } @@ -7818,9 +7818,9 @@ } }, "@oracle/wrc-jet-pack": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@oracle/wrc-jet-pack/-/wrc-jet-pack-2.4.1.tgz", - "integrity": "sha512-44B1X1u2EoC/Jrq6VbgyQRmB5gZLL0cgvyIJSUE6yGGGb4GtaH5hhKiEZlDD7EY74oZ3afjEtMYLcEVa383vNw==" + "version": "2.4.2-develop.202302081956", + "resolved": "https://artifacthub-phx.oci.oraclecorp.com/api/npm/npmjs-remote/@oracle/wrc-jet-pack/-/wrc-jet-pack-2.4.2-develop.202302081956.tgz", + "integrity": "sha512-oMjsXubEqlYFsrHECFw6TF6u9ZbRw1NOlVhlZNbovLZXmVYExgNk2vaIvUexKvd3DfTLTMDDXW0sgSGsqzgM2Q==" }, "@tootallnate/once": { "version": "2.0.0", diff --git a/webui/package.json b/webui/package.json index ccb13f38f..34a7f814c 100644 --- a/webui/package.json +++ b/webui/package.json @@ -11,7 +11,7 @@ }, "dependencies": { "@oracle/oraclejet": "^13.1.7", - "@oracle/wrc-jet-pack": "^2.4.1", + "@oracle/wrc-jet-pack": "~2.4.2-develop", "ace-builds": "^1.15.0", "i18next": "^22.4.9", "jquery": "^3.6.3", diff --git a/webui/src/js/utils/wrc-wdt-archive.js b/webui/src/js/utils/wrc-wdt-archive.js index 7ea85364e..7dd0a55d1 100644 --- a/webui/src/js/utils/wrc-wdt-archive.js +++ b/webui/src/js/utils/wrc-wdt-archive.js @@ -60,10 +60,7 @@ define(['models/wkt-project', 'utils/i18n', 'utils/wdt-archive-helper'], getExtensionsObject() { return { wktui: { - modelArchive: { - addToArchive: this.addToArchive, - removeFromArchive: this.removeFromArchive - } + modelArchive: this } }; }