diff --git a/src/api/CustomUI.ts b/src/api/CustomUI.ts index 206dc71b2..948e24f01 100644 --- a/src/api/CustomUI.ts +++ b/src/api/CustomUI.ts @@ -49,9 +49,10 @@ export class Section { return this; } - addCheckbox(id: string, label: string, description?: string, checked?: boolean) { + addCheckbox(id: string, label: string, description?: string, checked?: boolean, readonly: boolean = false) { const checkbox = new Field('checkbox', id, label, description); checkbox.default = checked ? 'checked' : ''; + checkbox.readonly = readonly; this.addField(checkbox); return this; } @@ -99,9 +100,10 @@ export class Section { return this; } - addSelect(id: string, label: string, items: SelectItem[], description?: string) { + addSelect(id: string, label: string, items: SelectItem[], description?: string, readonly = false) { const select = new Field('select', id, label, description); select.items = items; + select.readonly = readonly; this.addField(select); return this; } diff --git a/src/api/IBMi.ts b/src/api/IBMi.ts index 46af114f2..9908a7609 100644 --- a/src/api/IBMi.ts +++ b/src/api/IBMi.ts @@ -25,6 +25,8 @@ export interface MemberParts extends IBMiMember { const CCSID_SYSVAL = -2; const bashShellPath = '/QOpenSys/pkgs/bin/bash'; +const SERVER_SETTINGS_PATH = `/etc/code4i/settings.json`; + const remoteApps = [ // All names MUST also be defined as key in 'remoteFeatures' below!! { path: `/usr/bin/`, @@ -74,12 +76,13 @@ export default class IBMi { aspInfo: { [id: number]: string } = {}; remoteFeatures: { [name: string]: string | undefined }; variantChars: { american: string, local: string }; - /** * Strictly for storing errors from sendCommand. * Used when creating issues on GitHub. * */ lastErrors: object[] = []; + + loadedServerSettings: string[] = []; config?: ConnectionConfiguration.Parameters; content = new IBMiContent(this); shell?: string; @@ -172,6 +175,9 @@ export default class IBMi { //Load existing config this.config = await ConnectionConfiguration.load(this.currentConnectionName); + //Check and load server settings + await this.loadServerSettings(); + // Load cached server settings. const cachedServerSettings: CachedServerSettings = GlobalStorage.get().getServerSettingsCache(this.currentConnectionName); // Reload server settings? @@ -1079,6 +1085,40 @@ export default class IBMi { vscode.window.showInformationMessage(`Disconnected from ${this.currentHost}.`); } + async loadServerSettings() { + const content = this.content; + + const exists = await content?.testStreamFile(SERVER_SETTINGS_PATH, `r`); + if (exists) { + const settings = await content?.downloadStreamfile(SERVER_SETTINGS_PATH); + if (settings) { + try { + // Ready in the new settings + const asJson = JSON.parse(settings); + + // Apply the settings over the top + this.config = { + ...this.config, + ...asJson + } + + // Store the changes locally to be nice + await ConnectionConfiguration.update(this.config!); + + // Mark which config is managed by the server + this.loadedServerSettings = Object.keys(asJson); + + } catch (e) { + this.appendOutput(`Was not able to read server settings as JSON.\n`); + } + } else { + this.appendOutput(`Failed to load server settings.\n`); + } + } else { + this.appendOutput(`Server settings not found.\n`); + } + } + /** * SQL only available when runner is installed and CCSID is valid. */ diff --git a/src/webviews/settings/index.ts b/src/webviews/settings/index.ts index 9c1540748..1c2aee501 100644 --- a/src/webviews/settings/index.ts +++ b/src/webviews/settings/index.ts @@ -36,6 +36,7 @@ export class SettingsUI { vscode.commands.registerCommand(`code-for-ibmi.showAdditionalSettings`, async (server?: Server, tab?: string) => { const connectionSettings = GlobalConfiguration.get(`connectionSettings`); const connection = instance.getConnection(); + let alreadyManaged: string[] = []; const passwordAuthorisedExtensions = instance.getStorage()?.getAuthorisedExtensions() || []; let config: ConnectionConfiguration.Parameters; @@ -48,6 +49,7 @@ export class SettingsUI { if (connection && config) { // Reload config to initialize any new config parameters. config = await ConnectionConfiguration.load(config.name); + alreadyManaged = connection.loadedServerSettings; } else { vscode.window.showErrorMessage(`No connection is active.`); return; @@ -59,27 +61,27 @@ export class SettingsUI { const featuresTab = new Section(); featuresTab - .addCheckbox(`quickConnect`, `Quick Connect`, `When enabled, server settings from previous connection will be used, resulting in much quicker connection. If server settings are changed, right-click the connection in Connection Browser and select Connect and Reload Server Settings to refresh the cache.`, config.quickConnect) - .addCheckbox(`showDescInLibList`, `Show description of libraries in User Library List view`, `When enabled, library text and attribute will be shown in User Library List. It is recommended to also enable SQL for this.`, config.showDescInLibList) - .addCheckbox(`showHiddenFiles`, `Show hidden files and directories in IFS browser.`, `When disabled, hidden files and directories (i.e. names starting with '.') will not be shown in the IFS browser, except for special config files.`, config.showHiddenFiles) - .addCheckbox(`autoSortIFSShortcuts`, `Sort IFS shortcuts automatically`, `Automatically sort the shortcuts in IFS browser when shortcut is added or removed.`, config.autoSortIFSShortcuts) - .addCheckbox(`autoConvertIFSccsid`, `Support EBCDIC streamfiles`, `Enable converting EBCDIC to UTF-8 when opening streamfiles. When disabled, assumes all streamfiles are in UTF8. When enabled, will open streamfiles regardless of encoding. May slow down open and save operations.

You can find supported CCSIDs with /usr/bin/iconv -l`, config.autoConvertIFSccsid) + .addCheckbox(`quickConnect`, `Quick Connect`, `When enabled, server settings from previous connection will be used, resulting in much quicker connection. If server settings are changed, right-click the connection in Connection Browser and select Connect and Reload Server Settings to refresh the cache.`, config.quickConnect, alreadyManaged.includes(`quickConnect`)) + .addCheckbox(`showDescInLibList`, `Show description of libraries in User Library List view`, `When enabled, library text and attribute will be shown in User Library List. It is recommended to also enable SQL for this.`, config.showDescInLibList, alreadyManaged.includes(`showDescInLibList`)) + .addCheckbox(`showHiddenFiles`, `Show hidden files and directories in IFS browser.`, `When disabled, hidden files and directories (i.e. names starting with '.') will not be shown in the IFS browser, except for special config files.`, config.showHiddenFiles, alreadyManaged.includes(`showHiddenFiles`)) + .addCheckbox(`autoSortIFSShortcuts`, `Sort IFS shortcuts automatically`, `Automatically sort the shortcuts in IFS browser when shortcut is added or removed.`, config.autoSortIFSShortcuts, alreadyManaged.includes(`autoSortIFSShortcuts`)) + .addCheckbox(`autoConvertIFSccsid`, `Support EBCDIC streamfiles`, `Enable converting EBCDIC to UTF-8 when opening streamfiles. When disabled, assumes all streamfiles are in UTF8. When enabled, will open streamfiles regardless of encoding. May slow down open and save operations.

You can find supported CCSIDs with /usr/bin/iconv -l`, config.autoConvertIFSccsid, alreadyManaged.includes(`autoConvertIFSccsid`)) .addHorizontalRule() - .addCheckbox(`autoSaveBeforeAction`, `Auto Save for Actions`, `When current editor has unsaved changes, automatically save it before running an action.`, config.autoSaveBeforeAction) - .addInput(`hideCompileErrors`, `Errors to ignore`, `A comma delimited list of errors to be hidden from the result of an Action in the EVFEVENT file. Useful for codes like RNF5409.`, { default: config.hideCompileErrors.join(`, `) }) + .addCheckbox(`autoSaveBeforeAction`, `Auto Save for Actions`, `When current editor has unsaved changes, automatically save it before running an action.`, config.autoSaveBeforeAction, alreadyManaged.includes(`autoSaveBeforeAction`)) + .addInput(`hideCompileErrors`, `Errors to ignore`, `A comma delimited list of errors to be hidden from the result of an Action in the EVFEVENT file. Useful for codes like RNF5409.`, { default: config.hideCompileErrors.join(`, `), readonly: alreadyManaged.includes(`hideCompileErrors`) }) const tempDataTab = new Section(); tempDataTab - .addInput(`tempLibrary`, `Temporary library`, `Temporary library. Cannot be QTEMP.`, { default: config.tempLibrary, minlength: 1, maxlength: 10 }) - .addInput(`tempDir`, `Temporary IFS directory`, `Directory that will be used to write temporary files to. User must be authorized to create new files in this directory.`, { default: config.tempDir, minlength: 1 }) - .addCheckbox(`autoClearTempData`, `Clear temporary data automatically`, `Automatically clear temporary data in the chosen temporary library when it's done with and on startup. Deletes all *FILE objects that start with O_ in the chosen temporary library.`, config.autoClearTempData); + .addInput(`tempLibrary`, `Temporary library`, `Temporary library. Cannot be QTEMP.`, { default: config.tempLibrary, minlength: 1, maxlength: 10, readonly: alreadyManaged.includes(`tempLibrary`) }) + .addInput(`tempDir`, `Temporary IFS directory`, `Directory that will be used to write temporary files to. User must be authorized to create new files in this directory.`, { default: config.tempDir, minlength: 1, readonly: alreadyManaged.includes(`tempDir`) }) + .addCheckbox(`autoClearTempData`, `Clear temporary data automatically`, `Automatically clear temporary data in the chosen temporary library when it's done with and on startup. Deletes all *FILE objects that start with O_ in the chosen temporary library.`, config.autoClearTempData, alreadyManaged.includes(`autoClearTempData`)) const sourceTab = new Section(); sourceTab - .addInput(`sourceASP`, `Source ASP`, `If source files live within a specific ASP, please specify it here. Leave blank otherwise. You can ignore this if you have access to QSYS2.ASP_INFO as Code for IBM i will fetch ASP information automatically.`, { default: config.sourceASP }) - .addInput(`sourceFileCCSID`, `Source file CCSID`, `The CCSID of source files on your system. You should only change this setting from *FILE if you have a source file that is 65535 - otherwise use *FILE. Note that this config is used to fetch all members. If you have any source files using 65535, you have bigger problems.`, { default: config.sourceFileCCSID, minlength: 1, maxlength: 5 }) + .addInput(`sourceASP`, `Source ASP`, `If source files live within a specific ASP, please specify it here. Leave blank otherwise. You can ignore this if you have access to QSYS2.ASP_INFO as Code for IBM i will fetch ASP information automatically.`, { default: config.sourceASP, readonly: alreadyManaged.includes(`sourceASP`)}) + .addInput(`sourceFileCCSID`, `Source file CCSID`, `The CCSID of source files on your system. You should only change this setting from *FILE if you have a source file that is 65535 - otherwise use *FILE. Note that this config is used to fetch all members. If you have any source files using 65535, you have bigger problems.`, { default: config.sourceFileCCSID, minlength: 1, maxlength: 5, readonly: alreadyManaged.includes(`sourceFileCCSID`)}) .addHorizontalRule() - .addCheckbox(`enableSourceDates`, `Enable Source Dates`, `When enabled, source dates will be retained and updated when editing source members. Requires restart when changed.`, config.enableSourceDates) + .addCheckbox(`enableSourceDates`, `Enable Source Dates`, `When enabled, source dates will be retained and updated when editing source members. Requires restart when changed.`, config.enableSourceDates, alreadyManaged.includes(`enableSourceDates`)) .addSelect(`sourceDateMode`, `Source date tracking mode`, [ { selected: config.sourceDateMode === `edit`, @@ -93,8 +95,8 @@ export class SettingsUI { description: `Diff mode`, text: `Track changes using the diff mechanism. Before the document is saved, it is compared to the original state to determine the changed lines. (Test enhancement)`, }, - ], `Determine which method should be used to track changes while editing source members.`) - .addCheckbox(`sourceDateGutter`, `Source Dates in Gutter`, `When enabled, source dates will be displayed in the gutter.`, config.sourceDateGutter) + ], `Determine which method should be used to track changes while editing source members.`, alreadyManaged.includes(`sourceDateMode`)) + .addCheckbox(`sourceDateGutter`, `Source Dates in Gutter`, `When enabled, source dates will be displayed in the gutter.`, config.sourceDateGutter, alreadyManaged.includes(`sourceDateGutter`)) .addHorizontalRule() .addSelect(`defaultDeploymentMethod`, `Default Deployment Method`, [ { @@ -133,10 +135,10 @@ export class SettingsUI { description: `All`, text: `Every file in the local workspace`, } - ], `Set your Default Deployment Method. This is used when deploying from the local workspace to the server.`) + ], `Set your Default Deployment Method. This is used when deploying from the local workspace to the server.`, alreadyManaged.includes(`defaultDeploymentMethod`)) .addHorizontalRule() - .addCheckbox(`readOnlyMode`, `Read only mode`, `When enabled, source members and IFS files will always be opened in read-only mode.`, config.readOnlyMode) - .addInput(`protectedPaths`, `Protected paths`, `A comma separated list of libraries and/or IFS directories whose members will always be opened in read-only mode. (Example: QGPL, /home/QSECOFR, MYLIB, /QIBM)`, { default: config.protectedPaths.join(`, `) }); + .addCheckbox(`readOnlyMode`, `Read only mode`, `When enabled, source members and IFS files will always be opened in read-only mode.`, config.readOnlyMode, alreadyManaged.includes(`readOnlyMode`)) + .addInput(`protectedPaths`, `Protected paths`, `A comma separated list of libraries and/or IFS directories whose members will always be opened in read-only mode. (Example: QGPL, /home/QSECOFR, MYLIB, /QIBM)`, { default: config.protectedPaths.join(`, `), readonly: alreadyManaged.includes(`protectedPaths`) }); const terminalsTab = new Section(); if (connection && connection.remoteFeatures.tn5250) { @@ -151,7 +153,7 @@ export class SettingsUI { value: encoding, description: encoding, text: encoding, - }))], `The encoding for the 5250 emulator.`) + }))], `The encoding for the 5250 emulator.`, alreadyManaged.includes(`encodingFor5250`)) .addSelect(`terminalFor5250`, `5250 Terminal Type`, [ { selected: config.terminalFor5250 === `default`, @@ -165,9 +167,9 @@ export class SettingsUI { description: terminal.key, text: terminal.text, })) - ], `The terminal type for the 5250 emulator.`) - .addCheckbox(`setDeviceNameFor5250`, `Set Device Name for 5250`, `When enabled, the user will be able to enter a device name before the terminal starts.`, config.setDeviceNameFor5250) - .addInput(`connectringStringFor5250`, `Connection string for 5250`, `Default is localhost. A common SSL string is ssl:localhost 992`, { default: config.connectringStringFor5250 }); + ], `The terminal type for the 5250 emulator.`, alreadyManaged.includes(`terminalFor5250`)) + .addCheckbox(`setDeviceNameFor5250`, `Set Device Name for 5250`, `When enabled, the user will be able to enter a device name before the terminal starts.`, config.setDeviceNameFor5250, alreadyManaged.includes(`setDeviceNameFor5250`)) + .addInput(`connectringStringFor5250`, `Connection string for 5250`, `Default is localhost. A common SSL string is ssl:localhost 992`, { default: config.connectringStringFor5250, readonly: alreadyManaged.includes(`connectringStringFor5250`)}); } else if (connection) { terminalsTab.addParagraph('Enable 5250 emulation to change these settings'); } else { @@ -185,13 +187,13 @@ export class SettingsUI { } debuggerTab.addParagraph(``); - debuggerTab.addCheckbox(`debugUpdateProductionFiles`, `Update production files`, `Determines whether the job being debugged can update objects in production (*PROD) libraries.`, config.debugUpdateProductionFiles) - .addCheckbox(`debugEnableDebugTracing`, `Debug trace`, `Tells the debug service to send more data to the client. Only useful for debugging issues in the service. Not recommended for general debugging.`, config.debugEnableDebugTracing); + debuggerTab.addCheckbox(`debugUpdateProductionFiles`, `Update production files`, `Determines whether the job being debugged can update objects in production (*PROD) libraries.`, config.debugUpdateProductionFiles, alreadyManaged.includes(`debugUpdateProductionFiles`)) + .addCheckbox(`debugEnableDebugTracing`, `Debug trace`, `Tells the debug service to send more data to the client. Only useful for debugging issues in the service. Not recommended for general debugging.`, config.debugEnableDebugTracing, alreadyManaged.includes(`debugEnableDebugTracing`)); if (!isManaged()) { debuggerTab .addHorizontalRule() - .addCheckbox(`debugIsSecure`, `Debug securely`, `Tells the debug service to authenticate by server and client certificates. Ensure that the client certificate is imported when enabled.`, config.debugIsSecure); + .addCheckbox(`debugIsSecure`, `Debug securely`, `Tells the debug service to authenticate by server and client certificates. Ensure that the client certificate is imported when enabled.`, config.debugIsSecure, alreadyManaged.includes(`debugIsSecure`)); if (await certificates.remoteCertificatesExists()) { let localCertificateIssue; try {