Skip to content

Commit 4edee77

Browse files
committed
Merge branch 'jira-wktui-498-more-file-chooser' into 'main'
Support configuration of Installation Directory Application See merge request weblogic-cloud/weblogic-toolkit-ui!443
2 parents d59e610 + dcb4bd3 commit 4edee77

20 files changed

+752
-109
lines changed

electron/app/js/ipcRendererPreload.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -229,10 +229,12 @@ contextBridge.exposeInMainWorld(
229229
path: {
230230
basename: (filePath, ext) => path.basename(filePath, ext),
231231
dirname: (filePath) => path.dirname(filePath),
232+
exists: (filePath) => path.exists(filePath),
232233
extname: (filePath) => path.extname(filePath),
233234
isAbsolute: (filePath) => path.isAbsolute(filePath),
234235
join: (...paths) => path.join(...paths),
235236
joinAndConvertToUnixPath: (...paths) => path.join(...paths).replaceAll('\\', '/'),
237+
relative: (filePath1, filePath2) => path.relative(filePath1, filePath2),
236238
delimiter: path.delimiter,
237239
isValidFileName: (fileName) => fsUtils.isValidFileName(fileName),
238240
},
@@ -263,9 +265,10 @@ contextBridge.exposeInMainWorld(
263265
}
264266
},
265267
'modelEdit': {
266-
getReadableLabel: aliasName => {
267-
return modelEditUtils.getReadableLabel(aliasName);
268-
}
268+
exists: path => fsUtils.exists(path),
269+
getReadableLabel: aliasName => modelEditUtils.getReadableLabel(aliasName),
270+
isDirectory: path => fsUtils.isDirectory(path),
271+
isFile: path => fsUtils.isFile(path)
269272
},
270273
'utils': {
271274
generateUuid: () => uuid.v4(),

electron/app/locales/en/modeledit.json

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,27 @@
120120
"file-select-type-emptyDir": "Empty Directory",
121121
"file-select-archive-title": "{{attribute}} Location",
122122
"file-select-archive-label": "Location Type",
123-
"file-select-use-file-location": "Use \"{{fileName}}\" on the local file system",
124-
"file-select-add-to-archive": "Add \"{{fileName}}\" to archive file",
123+
"file-select-add-dir-to-archive": "Add this directory to the archive file",
124+
"file-select-add-file-to-archive": "Add this file to the archive file",
125+
"file-select-use-dir-location": "Use this directory path on the target file system",
126+
"file-select-use-file-location": "Use this file path on the target file system",
125127
"file-select-archive-empty-dir": "Use a new empty directory in the archive file",
126-
"file-select-local-empty-dir": "Use an empty directory on the file system",
128+
"file-select-local-empty-dir": "Use a directory on the target file system",
129+
130+
"add-app-use-app-install-dir-label": "Use Application Installation Directory",
131+
"add-app-use-app-install-dir-help": "The application installation directory separates generated configuration files from the core application files, so that configuration files can be easily changed or replaced without disturbing the application itself. The directory structure also helps you to organize and maintain multiple versions of the same application deployment files.",
132+
133+
"add-install-dir-title": "Set Up Application Installation Directory",
134+
"add-install-dir-use-archive-label": "Add Installation Directory to the Archive File",
135+
"add-install-dir-use-archive-help": "Add the installation directory to the archive file, and set the associated paths in the model",
136+
"add-install-dir-install-dir-label": "Application Installation Directory",
137+
"add-install-dir-install-dir-help": "The path to the application's install-root directory",
138+
"add-install-dir-not-directory-error": "Must be a local directory to add to archive",
139+
"add-install-dir-not-file-error": "Must be a local file to add to archive",
140+
"add-install-dir-not-exists-error": "Must exist on local file system to add to archive",
141+
"add-install-dir-not-sub-path-error": "Must be a relative path or a subpath of application installation directory",
142+
"add-install-dir-required-with-plan-dir-error": "Required if plan directory is specified",
143+
"add-install-dir-not-relative-plan-path-error": "Must be a relative path if plan directory is specified",
127144

128145
"options-default-label": "Default",
129146
"options-persist-label": "Persistent",
@@ -439,6 +456,8 @@
439456
"attribute-editor-missing-variable": "Missing variable \"{{variable}}\"",
440457
"attribute-editor-options": "Options",
441458
"attribute-editor-select": "Select",
459+
"attribute-editor-select-directory": "Select Directory",
460+
"attribute-editor-select-file": "Select File",
442461

443462
"invalid-integer-error": "Value must be an integer",
444463
"invalid-double-error": "Value must be a decimal number, such as 4 or 3.5",

webui/src/css/app.css

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858

5959
oj-navigation-list[id="modelDesignNav"].oj-navigationlist-vertical {
6060
--oj-navigation-list-item-min-height: 1.75rem;
61+
margin: 8px 0 6px 0;
6162
}
6263

6364
.oj-navigationlist-depth-1 .oj-navigationlist-item-content {
@@ -1064,6 +1065,17 @@ h6.wkt-under-breadcrumb {
10641065
margin-top: 0.5em !important; /* oj-c-collapsible has no space between header and content */
10651066
}
10661067

1068+
.wkt-file-select-path-label {
1069+
font-size: 0.75em;
1070+
color: var(--wkt-model-edit-label-text-color);
1071+
font-weight: 600;
1072+
margin-bottom: 4px;
1073+
}
1074+
1075+
.wkt-file-select-path {
1076+
overflow-wrap: break-word;
1077+
}
1078+
10671079
.wkt-token-error {
10681080
color: red;
10691081
font-weight: bold;

webui/src/js/utils/modelEdit/file-select-helper.js

Lines changed: 70 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -5,65 +5,79 @@
55
*/
66
'use strict';
77

8-
define(['utils/dialog-helper', 'utils/wkt-logger', 'utils/modelEdit/alias-helper', 'utils/wdt-archive-helper',
9-
'utils/modelEdit/message-helper'],
10-
function (DialogHelper, WktLogger, AliasHelper, ArchiveHelper, MessageHelper) {
8+
define(['utils/dialog-helper', 'utils/wkt-logger', 'utils/modelEdit/model-edit-helper',
9+
'utils/modelEdit/alias-helper', 'utils/wdt-archive-helper', 'utils/modelEdit/message-helper'],
10+
function (DialogHelper, WktLogger, ModelEditHelper, AliasHelper, ArchiveHelper, MessageHelper) {
1111

1212
function FileSelectHelper() {
1313
// support selecting files for attributes
1414

15-
this.selectFile = async(attribute, currentValue) => {
15+
// matching wdtArchive subtypes
16+
const DIR_TYPES = ['dir', 'either', 'emptyDir'];
17+
const FILE_TYPES = ['file', 'either'];
18+
19+
this.canChooseDirectory = attribute => {
20+
return canChoose(DIR_TYPES, attribute);
21+
};
22+
23+
this.canChooseFile = attribute => {
24+
return canChoose(FILE_TYPES, attribute);
25+
};
26+
27+
function canChoose(matchTypes, attribute) {
28+
const archiveTypeKeys = attribute.archiveTypes || [];
29+
for(const archiveTypeKey of archiveTypeKeys) {
30+
const archiveType = ModelEditHelper.getArchiveType(archiveTypeKey) || {};
31+
if(matchTypes.includes(archiveType.subtype)) {
32+
return true;
33+
}
34+
}
35+
const fileOptions = attribute.fileOptions || [];
36+
for(const fileOption of fileOptions) {
37+
if(matchTypes.includes(fileOption.type)) {
38+
return true;
39+
}
40+
}
41+
// if no archive or file options found, allow file or dir
42+
return !archiveTypeKeys.length && !fileOptions.length;
43+
}
44+
45+
this.chooseDirectory = async(attribute, currentValue) => {
46+
return choosePath(attribute, 'dir', currentValue);
47+
};
48+
49+
this.chooseFile = async(attribute, currentValue) => {
50+
return choosePath(attribute, 'file', currentValue);
51+
};
52+
53+
async function choosePath(attribute, matchType, currentValue) {
1654
const aliasPath = AliasHelper.getAliasPath(attribute.path);
1755
const attributeLabel = MessageHelper.getAttributeLabel(attribute, aliasPath);
1856

19-
const archiveTypes = await ArchiveHelper.getEntryTypes();
57+
// build a list of select options based on archive and file options
2058
const selectOptions = [];
2159

2260
// possibly multiple archive types (app, custom?)
2361
const archiveTypeKeys = attribute.archiveTypes || [];
2462
archiveTypeKeys.forEach(archiveTypeKey => {
25-
if (!(archiveTypeKey in archiveTypes)) {
26-
WktLogger.error('Invalid archive type: ' + archiveTypeKey);
63+
const archiveType = ModelEditHelper.getArchiveType(archiveTypeKey);
64+
if (!archiveType) {
2765
return;
2866
}
2967

30-
// file dir either emptyDir
31-
32-
const archiveType = archiveTypes[archiveTypeKey];
3368
const subtype = archiveType.subtype;
3469
const segregateName = getSegregateName(attribute);
70+
const segregateLabel = archiveType.segregatedLabel;
71+
const segregateHelp = archiveType.segregatedHelp;
3572

36-
if (['file', 'either'].includes(subtype)) {
37-
selectOptions.push({
38-
type: 'file',
39-
label: archiveType.fileLabel,
40-
extensions: archiveType.extensions,
41-
archiveType: archiveTypeKey,
42-
segregateLabel: archiveType.segregatedLabel,
43-
segregateHelp: archiveType.segregatedHelp,
44-
segregateName
45-
});
46-
}
47-
48-
if (['dir', 'either'].includes(subtype)) {
49-
selectOptions.push({
50-
type: 'dir',
51-
label: archiveType.dirLabel,
52-
archiveType: archiveTypeKey,
53-
segregateLabel: archiveType.segregatedLabel,
54-
segregateHelp: archiveType.segregatedHelp,
55-
segregateName
56-
});
57-
}
58-
59-
if('emptyDir' === subtype) {
73+
if('emptyDir' === subtype) { // add two specific options
6074
// bypass file selection and add to archive
6175
selectOptions.push({
6276
type: 'emptyDir',
6377
labelKey: 'file-select-archive-empty-dir',
6478
archiveType: archiveTypeKey,
65-
segregateLabel: archiveType.segregatedLabel,
66-
segregateHelp: archiveType.segregatedHelp,
79+
segregateLabel,
80+
segregateHelp,
6781
segregateName
6882
});
6983

@@ -73,6 +87,23 @@ function (DialogHelper, WktLogger, AliasHelper, ArchiveHelper, MessageHelper) {
7387
labelKey: 'file-select-local-empty-dir',
7488
chooserName: attributeLabel
7589
});
90+
91+
} else { // add one option based on match type
92+
const fileMatch = matchType === 'file' && FILE_TYPES.includes(subtype);
93+
const dirMatch = matchType === 'dir' && DIR_TYPES.includes(subtype);
94+
const label = dirMatch ? archiveType.dirLabel : archiveType.fileLabel;
95+
96+
if (dirMatch || fileMatch) {
97+
selectOptions.push({
98+
type: matchType,
99+
label,
100+
extensions: archiveType.extensions,
101+
archiveType: archiveTypeKey,
102+
segregateLabel,
103+
segregateHelp,
104+
segregateName
105+
});
106+
}
76107
}
77108
});
78109

@@ -83,11 +114,10 @@ function (DialogHelper, WktLogger, AliasHelper, ArchiveHelper, MessageHelper) {
83114
selectOptions.push(fileOption);
84115
});
85116

86-
// if no options found, default is simple file or directory
117+
// if archive or file options found, default is choose specified type
87118
if(!selectOptions.length) {
88119
selectOptions.push(
89-
{ type: 'file', chooserName: attributeLabel },
90-
{ type: 'dir', chooserName: attributeLabel }
120+
{ type: matchType, chooserName: attributeLabel }
91121
);
92122
}
93123

@@ -96,17 +126,14 @@ function (DialogHelper, WktLogger, AliasHelper, ArchiveHelper, MessageHelper) {
96126
const selectType = selectOption.type;
97127
if(!['file', 'dir', 'emptyDir'].includes(selectType)) {
98128
WktLogger.error('Invalid selection type: ' + selectType);
99-
return; // cancel
100129
}
101-
102130
const defaultLabel = MessageHelper.t('file-select-type-' + selectOption.type);
103131
selectOption.label = MessageHelper.getLabel(selectOption) || defaultLabel;
104132
});
105133

106134
let selectOption;
107135

108-
// prompt for select option if more than one type is present in metadata
109-
// const fileSelectOptions = attribute.fileSelectOptions || [];
136+
// prompt for select option if more than one type is present.
110137
if(selectOptions.length === 1) {
111138
selectOption = selectOptions[0];
112139
} else {

webui/src/js/utils/modelEdit/meta-helper.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@ define(['utils/modelEdit/metadata/all-metadata'],
8888
return metadata['noSelect'];
8989
};
9090

91+
this.getAddHandler = aliasPath => {
92+
const metadata = this.getMetadata(aliasPath);
93+
return metadata['addHandler'];
94+
};
95+
9196
this.getNameValidators = (/*aliasPath*/) => {
9297
return []; // TODO: implement this when needed
9398
};
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/**
2+
* @license
3+
* Copyright (c) 2025, Oracle and/or its affiliates.
4+
* Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
5+
*/
6+
'use strict';
7+
8+
define(['knockout', 'utils/wkt-logger', 'utils/dialog-helper', 'utils/wdt-archive-helper',
9+
'utils/modelEdit/model-edit-helper', 'utils/modelEdit/navigation-helper'
10+
],
11+
function (ko, WktLogger, DialogHelper, ArchiveHelper, ModelEditHelper, NavigationHelper) {
12+
function MetaMethods() {
13+
14+
// ask about adding Application Installation Directory (structured) app
15+
this.addApplication = async (modelPath, nameValidators) => {
16+
const options = {
17+
modelPath,
18+
nameValidators
19+
};
20+
21+
const newResult = await DialogHelper.promptDialog('modelEdit/new-app-dialog', options);
22+
const newName = newResult.instanceName;
23+
if (!newName) { // cancel
24+
return;
25+
}
26+
27+
let folderContent = {};
28+
29+
if(newResult.useAppInstallDir) {
30+
const appResult = await DialogHelper.promptDialog('modelEdit/app-install-dir-dialog', options);
31+
if (!appResult) { // cancel
32+
return;
33+
}
34+
35+
folderContent = appResult.folderContent;
36+
37+
if(appResult.addToArchive) {
38+
const addOptions = {
39+
fileName: appResult.installDir,
40+
fileType: 'dir'
41+
};
42+
const archivePath = await ArchiveHelper.addToArchive('applicationInstallationDirectory', addOptions);
43+
44+
// absolute paths need correction to wlsdeploy/...
45+
for(const [key, path] of Object.entries(folderContent)) {
46+
if (path && window.api.path.isAbsolute(path)) {
47+
const relativePath = window.api.path.relative(appResult.installDir, path);
48+
folderContent[key] = window.api.path.join(archivePath, relativePath);
49+
}
50+
}
51+
}
52+
}
53+
54+
ModelEditHelper.addFolder(modelPath, newName, folderContent);
55+
56+
NavigationHelper.openNavigation(modelPath); // open parent
57+
};
58+
}
59+
60+
// return a singleton instance
61+
return new MetaMethods();
62+
});

webui/src/js/utils/modelEdit/metadata/Application.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"Application": {
3+
"addHandler": "addApplication",
34
"summaryAttributes": {
45
"SourcePath": {},
56
"Target": {}

webui/src/js/utils/modelEdit/metadata/Server.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1691,7 +1691,7 @@
16911691
"addRemainingAttributes": true
16921692
},
16931693
{
1694-
"type": "folderTab",
1694+
"type": "folder",
16951695
"folder": "ServerFailureTrigger"
16961696
}
16971697
]

webui/src/js/utils/modelEdit/metadata/ServerTemplate.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1688,7 +1688,7 @@
16881688
"addRemainingAttributes": true
16891689
},
16901690
{
1691-
"type": "folderTab",
1691+
"type": "folder",
16921692
"folder": "ServerFailureTrigger"
16931693
}
16941694
]

0 commit comments

Comments
 (0)