﻿<?xml version="1.0" encoding="utf-8"?>
<!--
    Co-authored by Max Törnblom
-->
<EXOconfigData format="Rwav" version="2023.1.100.39">
  <Object type="View">
    <Attribute name="Name">ApplicationLoggingConfiguration</Attribute>
    <Attribute name="Width">610</Attribute>
    <Attribute name="Height">300</Attribute>
    <Attribute name="Zoom">100%</Attribute>
    <Attribute name="ScaleValue">1.00</Attribute>
    <Attribute name="OnOpen">this.view.Save_Button.enabled(false)</Attribute>
    <Attribute name="ServerSideFunctionRPC">accounts.%account%.services.ssf.execute</Attribute>
    <Attribute name="ServerSideJS">// The url that we post queries to.
const queryUrl = 'http://localhost/arrigo/api/graphql';

// Function for posting queries to the API.
const postQuery = async (callInfo, payload) =&gt; {
    console.log('In postQuery');
    
    // All requests must be authenticated/authorized.
    // The callInfo object has the current token from the user,
    // so we don't have to login.
    // Also note the the payload/query must be posted as json.
    const response = await callInfo.context.fetch(queryUrl, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${callInfo.token}`
        },
        body: JSON.stringify(payload),
    });

    return await response.json();
}

// Returns the name of the currently selected application, based
// on its index in the TextSelect element.
// If new applications are added, they must be appended here as well
// (in the same order as entered in the Select_Application element).
const _getAppName = (appValue) =&gt; {
    console.log('In _getAppName', appValue);
    
    // Convert appValue from string to number.
    appValue = parseInt(appValue);

    // Pick the correct name.
    const appNames = [
        "arrigo-api",
        "arrigo-sf",
        "arrigo-licensemanager",
        "arrigo-wamp-host",
        "arrigo-services-chart",
        "arrigo-services-state",
        "arrigo-services-db",
        "arrigo-dc",
        "arrigo-ssf",
        "arrigo-volumes",
        "arrigo-configtranspiler"
    ];
    
    const appName = appNames[appValue] || 'unknown';
    
    return appName;
}

// Gets the logging rules for a specific application.
const _getRulesForApp = async (appName, callInfo) =&gt; {
    console.log('In _getRulesForApp');
    
    // Build the query by adding the appName and requesting all the
    // available properties.
    const query = {
        query: `{
            loggingRules(appName: "${appName}") {
                final
                logger
                minLevel
                finalMinLevel
                name
                writeTo
            }
        }`
    };
    
    // Post the query and return the result.    
    const response = await postQuery(callInfo, query);
    return response.data.loggingRules;
}

// These are all the exposed ServerSideFunctions.
return {

    // Returns the name of the currently selected application (args.appValue).
    // A wrapper for '_getAppName'
    getAppName: async (args, callInfo) =&gt; {
        console.log('In getAppName');
        return _getAppName(args.appValue);
    },
    
    // Checks the current log settings (in the view) and compares them
    // against the stored rules. Returns 'true' if changes are detected.
    shouldEnableSaveButton: async (args, callInfo) =&gt; {
        console.log('In shouldEnableSaveButton');

        // Get the current rules.
    	const appName = await _getAppName(args.appValue);
        const rules = await _getRulesForApp(appName, callInfo);

        const programRule = rules.find(element =&gt; element.name === 'Program');
        const wampRule = rules.find(element =&gt; element.name === 'Wamp');
        
        // Store all the true/false results in an array.
        const shouldEnable = [];

        // Compare checkbox values against the current rules.
        // If they differ we set that specific place in the array to 'true'.
        shouldEnable[0] = programRule.writeTo === "file" ? args.programFile !== 1 : args.programFile === 1;
        shouldEnable[1] = programRule.writeTo === "console" ? args.programConsole !== 1 : args.programConsole === 1;
        shouldEnable[2] = programRule.writeTo === "gelf" ? args.programOnline !== 1 : args.programOnline === 1;
        
        // If the "wampRule" isn't defined, we exclude all those options from the comparison.
        if (wampRule) {
            shouldEnable[3] = wampRule.writeTo === "file" ? args.wampFile !== 1 : args.wampFile === 1;
            shouldEnable[4] = wampRule.writeTo === "console" ? args.wampConsole !== 1 : args.wampConsole === 1;
            shouldEnable[5] = wampRule.writeTo === "gelf" ? args.wampOnline !== 1 : args.wampOnline === 1;
        
            wampRule.minLevel = wampRule.finalMinLevel;
        }
        
        // The log levels are checked a bit differently, but the logic is the same.
        if (programRule.minLevel === "Info" &amp;&amp; args.programInfo === 1) {
            shouldEnable[6] = false;
        } else if (programRule.minLevel === "Debug" &amp;&amp; args.programDebug === 1) {
            shouldEnable[6] = false;
        } else {
            shouldEnable[6] = true;
        }
        
        // If the "wampRule" isn't defined, we exclude all those options from the comparison.
        if (wampRule) {
            if (wampRule.finalMinLevel === "Info" &amp;&amp; args.wampInfo === 1) {
                shouldEnable[7] = false;
            } else if (wampRule.finalMinLevel === "Debug" &amp;&amp; args.wampDebug === 1) {
                shouldEnable[7] = false;
            } else {
                shouldEnable[7] = true;
            }
        }
        
        // Check if any of the checks indicated changes.
        const trueOrFalse = shouldEnable.includes(true);
        
        // Always return an object, 
        return { trueOrFalse };
    },
    
    // Gets the logging rules for an application.
    // A wrapper for '_getRulesForApp'.
    getRulesForApp: async (args, callInfo) =&gt; {
        console.log('In getRulesForApp');

        const appName = args.appName;
        const rules = await _getRulesForApp(appName, callInfo);

        // The Wamp rule must uses 'finalMinLevel' whilst the Program rule uses 'minLevel'.
        // But since we use 'minLevel' for all comparisons, we simply fix/change that here.
        const wampRule = rules.find(element =&gt; element.name === 'Wamp');
        if (wampRule) wampRule.minLevel = wampRule.finalMinLevel;

        return rules;
    },
    
    // Sets the logging rules for an application.
    // Note that we must set _all_ rules, not just the changes.
    setRulesForApp: async (args, callInfo) =&gt; {
        console.log('In setRulesForApp');

        const appName = args.appName;
        const wampRule = args.wampRule;
        const programRule = args.programRule;
        
        // Build the query by adding the appName and rules.
        // The Wamp rule must set 'finalMinLevel' whilst the Program rule
        // must set 'minLevel'.
        // And the Wamp rule is only included if it is set/defined
        // (not all applications have a separate Wamp rule).
        const query = {
            query: `mutation {
                setLoggingRules(
                    appName: "${appName}"
                    loggingRules: [
                    ${wampRule ? 
                        `{
                            name: "Wamp"
                            logger: "WampSharp.*"
                            finalMinLevel: "${wampRule.minLevel}"
                            final: "true"
                            writeTo: "${wampRule.writeTo}"
                        },` : ''
                    }
                        {
                            name: "Program"
                            logger: "*"
                            minLevel: "${programRule.minLevel}"
                            final: "false"
                            writeTo: "${programRule.writeTo}"
                        }
                    ]
                )
            }`
        };
        
        // Post the query to the API.
        const response = await postQuery(callInfo, query);

        // Return the result.
        return response.data.setLoggingRules;
    }
}</Attribute>
    <Object type="ArgumentsFolder">
      <Attribute name="Name">Arguments</Attribute>
      <Attribute name="Comment">This folder contains all arguments for the view. The arguments can be sent to the view when it is used in run-time.</Attribute>
    </Object>
    <Object type="ElementsFolder">
      <Attribute name="Name">Elements</Attribute>
      <Attribute name="Comment">This folder contains all visual elements for the view.</Attribute>
      <Object type="TextSelect">
        <Attribute name="Name">Select_Application</Attribute>
        <Attribute name="Title">Select application</Attribute>
        <Attribute name="Texts">Arrigo API,Arrigo Scada Function,License Manager,Arrigo Wamp Host,Chart Service,State Service,DB Service,Domain Controller,ServerSideFunctions,Volumes,ConfigTranspiler</Attribute>
        <Attribute name="ManeuverStyle">Directly</Attribute>
        <Attribute name="TitlePosition">Above</Attribute>
        <Attribute name="TitleAlignment">Center</Attribute>
        <Attribute name="Left">15</Attribute>
        <Attribute name="Top">30</Attribute>
        <Attribute name="Width">185</Attribute>
        <Attribute name="Height">20</Attribute>
        <Attribute name="TitleWidth">200</Attribute>
        <Attribute name="OnManeuver">// We wrap all the logic as an async function.
// This means that we avoid the callback-based flow that usually
// clutters the code. In an async function, we can instead await the result
// from other async invocations.
// But please note that we have no control over when all of the code
// is run, as we execute our anonymous function at the end and exit.
// The only thing we know is that we are guaranteed that the code is run
// at some (very close) point in time.
(async () =&gt; {
    // Get the currently selected application and then
    // the current rules for that app.
    const appValue = this.value();
	const appName = await this.view.call('.getAppName', { appValue });
    const rules = await this.view.call('.getRulesForApp', { appName });

    // Find the two rule blocks in the returned object.
    // "Wamp" is the name/technology of the internal message bus
    // in Arrigo Local.
    const programRule = rules.find(element =&gt; element.name === 'Program');    
    const wampRule = rules.find(element =&gt; element.name === 'Wamp');
    
    // Make variables of all the texts used in the comparisons
    // to avoid code duplication.
    const levelInfo = 'Info';
    const levelDebug = 'Debug';
    const targetFile = 'file';
    const targetConsole = 'console';
    const targetOnline = 'gelf';
    
    // Not all applications have a Wamp rule, so we should make all
    // those element invisible if no Wamp rule is returned.
    // We once again use the naming scheme in the view to our advantage,
    // making all elements with a name beginning with "Wamp" invisible
    // if no rule was found.
    //
    // We iterate over all the properties/entries of the parent element.
    for (const [key, element] of Object.entries(this.parent)) {
        // For all Wamp elements, we let the presence of
        // a Wamp rule determine the visibility.
        if (key.startsWith("Wamp")) {
            element.visible(wampRule !== undefined);
        }
    }
    
    // Set all the checkboxes according to the rules.
    this.view.Program_Level_Info_CB.value(programRule.minLevel === levelInfo ? 1 : 0);
    this.view.Program_Level_Debug_CB.value(programRule.minLevel === levelDebug ? 1 : 0);
    this.view.Program_Target_File_CB.value(programRule.writeTo === targetFile ? 1 : 0);
    this.view.Program_Target_Console_CB.value(programRule.writeTo === targetConsole ? 1 : 0);
    this.view.Program_Target_Online_CB.value(programRule.writeTo === targetOnline ? 1 : 0);

    this.view.Wamp_Level_Info_CB.value(wampRule.minLevel === levelInfo ? 1 : 0);
    this.view.Wamp_Level_Debug_CB.value(wampRule.minLevel === levelDebug ? 1 : 0);
    this.view.Wamp_Target_File_CB.value(wampRule.writeTo === targetFile ? 1 : 0);
    this.view.Wamp_Target_Console_CB.value(wampRule.writeTo === targetConsole ? 1 : 0);
    this.view.Wamp_Target_Online_CB.value(wampRule.writeTo === targetOnline ? 1 : 0);
})();</Attribute>
      </Object>
      <Object type="ElementsGroup">
        <Attribute name="Name">Application</Attribute>
        <Object type="Text">
          <Attribute name="Name">Program_Border</Attribute>
          <Attribute name="ShowTitle">No</Attribute>
          <Attribute name="Border">Yes</Attribute>
          <Attribute name="Left">15</Attribute>
          <Attribute name="Top">75</Attribute>
          <Attribute name="Width">270</Attribute>
          <Attribute name="Height">155</Attribute>
        </Object>
        <Object type="Static">
          <Attribute name="Name">Program_Title</Attribute>
          <Attribute name="Title">Application</Attribute>
          <Attribute name="FontSize">Medium</Attribute>
          <Attribute name="VisualStyle">Shady</Attribute>
          <Attribute name="Left">15</Attribute>
          <Attribute name="Top">75</Attribute>
          <Attribute name="Width">270</Attribute>
          <Attribute name="Height">25</Attribute>
        </Object>
        <Object type="Static">
          <Attribute name="Name">Program_Level_Title</Attribute>
          <Attribute name="Title">Log level:</Attribute>
          <Attribute name="Left">25</Attribute>
          <Attribute name="Top">110</Attribute>
          <Attribute name="Width">80</Attribute>
          <Attribute name="Height">20</Attribute>
        </Object>
        <Object type="Static">
          <Attribute name="Name">Program_Target_Title</Attribute>
          <Attribute name="Title">Send logs to:</Attribute>
          <Attribute name="Left">170</Attribute>
          <Attribute name="Top">110</Attribute>
          <Attribute name="Width">105</Attribute>
          <Attribute name="Height">20</Attribute>
        </Object>
        <Object type="Symbol">
          <Attribute name="Name">Program_Level_Info_CB</Attribute>
          <Attribute name="UserDescription"></Attribute>
          <Attribute name="Pictures">Shared:\Symbols\Checkbox.svg</Attribute>
          <Attribute name="ManeuverStyle">SVGInteraction</Attribute>
          <Attribute name="Style">SVGAnimation</Attribute>
          <Attribute name="VisualStyle">Command</Attribute>
          <Attribute name="Left">25</Attribute>
          <Attribute name="Top">140</Attribute>
          <Attribute name="Width">20</Attribute>
          <Attribute name="Height">20</Attribute>
          <Attribute name="OnManeuver">// Prevent the checkbox from being selected if no application selection has been made.
// [TextSelect].value() otherwise returns the index of the selected item ('0', '1' etc.).
if (this.view.Select_Application.value() === '') {
    this.value(0);
    return;
}

(async () =&gt; {
    // This prevents the checkbox from being deselected.
    // Once it has been selected you have to click on another checkbox to deselect this one.
    // "this" refers to the currently clicked checkbox.
    if (this.value() === 0) {
        this.value(1);
    }

    // By using some clever naming conventions on the elements, we can deselect all
    // the other checkboxes in the same "group" without having to hard-code anything.
    //
    // We start by getting the prefix of the currently clicked element:
    // "Program_Level_Info_CB" -&gt; "Program_Level"
    const prefix = this.name().split('_').slice(0,2).join('_');
    
    // We iterate over all the properties/entries of the parent element.
    for (const [key, element] of Object.entries(this.parent)) {
    
        // This is the current element. Move on to the next.
        if (key === this.name()) continue;
        
        // If the element has the correct prefix, and is a checkbox (name ending with 'CB'),
        // we set its value to 0 and thereby deselect it.
        if (key.startsWith(prefix) &amp;&amp; key.endsWith('CB')) {
            element.value(0);
        }
    }
    
    // Invoke the SSF code to check if the Save button should be enabled.
    // We must pass in all the state from the view so the function can
    // compare them against the current settings.
    const result = await this.view.call('.shouldEnableSaveButton', {
        appValue: this.view.Select_Application.value(),
        
        programInfo: this.view.Program_Level_Info_CB.value(),
        programDebug: this.view.Program_Level_Debug_CB.value(),
        programFile: this.view.Program_Target_File_CB.value(),
        programConsole: this.view.Program_Target_Console_CB.value(),
        programOnline: this.view.Program_Target_Online_CB.value(),
        
        wampInfo: this.view.Wamp_Level_Info_CB.value(),
        wampDebug: this.view.Wamp_Level_Debug_CB.value(),
        wampFile: this.view.Wamp_Target_File_CB.value(),
        wampConsole: this.view.Wamp_Target_Console_CB.value(),
        wampOnline: this.view.Wamp_Target_Online_CB.value()
    });

    // Enable/disable the button depending on the result.
    this.view.Save_Button.enabled(result.trueOrFalse);
})();</Attribute>
        </Object>
        <Object type="Static">
          <Attribute name="Name">Program_Level_Info_Title</Attribute>
          <Attribute name="Title">Info (default)</Attribute>
          <Attribute name="UserDescription"></Attribute>
          <Attribute name="Left">50</Attribute>
          <Attribute name="Top">140</Attribute>
          <Attribute name="Width">80</Attribute>
          <Attribute name="Height">20</Attribute>
          <Attribute name="TitleHeight"></Attribute>
        </Object>
        <Object type="Symbol">
          <Attribute name="Name">Program_Level_Debug_CB</Attribute>
          <Attribute name="UserDescription"></Attribute>
          <Attribute name="Pictures">Shared:\Symbols\Checkbox.svg</Attribute>
          <Attribute name="ManeuverStyle">SVGInteraction</Attribute>
          <Attribute name="Style">SVGAnimation</Attribute>
          <Attribute name="VisualStyle">Command</Attribute>
          <Attribute name="Left">25</Attribute>
          <Attribute name="Top">165</Attribute>
          <Attribute name="Width">20</Attribute>
          <Attribute name="Height">20</Attribute>
          <Attribute name="OnManeuver">if (this.view.Select_Application.value() === '') {
    this.value(0);
    return;
}

(async () =&gt; {
    if (this.value() === 0) {
        this.value(1);
    }

    const prefix = this.name().split('_').slice(0,2).join('_');
    
    for (const [key, element] of Object.entries(this.parent)) {
        if (key === this.name()) continue;
        
        if (key.startsWith(prefix) &amp;&amp; key.endsWith('CB')) {
            element.value(0);
        }
    }
    
    const result = await this.view.call('.shouldEnableSaveButton', {
        appValue: this.view.Select_Application.value(),
        
        programInfo: this.view.Program_Level_Info_CB.value(),
        programDebug: this.view.Program_Level_Debug_CB.value(),
        programFile: this.view.Program_Target_File_CB.value(),
        programConsole: this.view.Program_Target_Console_CB.value(),
        programOnline: this.view.Program_Target_Online_CB.value(),
        
        wampInfo: this.view.Wamp_Level_Info_CB.value(),
        wampDebug: this.view.Wamp_Level_Debug_CB.value(),
        wampFile: this.view.Wamp_Target_File_CB.value(),
        wampConsole: this.view.Wamp_Target_Console_CB.value(),
        wampOnline: this.view.Wamp_Target_Online_CB.value()
    });

    this.view.Save_Button.enabled(result.trueOrFalse);
})();</Attribute>
        </Object>
        <Object type="Static">
          <Attribute name="Name">Program_Level_Debug_Title</Attribute>
          <Attribute name="Title">Debug</Attribute>
          <Attribute name="UserDescription"></Attribute>
          <Attribute name="Left">50</Attribute>
          <Attribute name="Top">165</Attribute>
          <Attribute name="Width">80</Attribute>
          <Attribute name="Height">20</Attribute>
          <Attribute name="TitleHeight"></Attribute>
        </Object>
        <Object type="Symbol">
          <Attribute name="Name">Program_Target_File_CB</Attribute>
          <Attribute name="UserDescription">Log to file</Attribute>
          <Attribute name="Pictures">Shared:\Symbols\Checkbox.svg</Attribute>
          <Attribute name="ManeuverStyle">SVGInteraction</Attribute>
          <Attribute name="Style">SVGAnimation</Attribute>
          <Attribute name="VisualStyle">Command</Attribute>
          <Attribute name="Left">170</Attribute>
          <Attribute name="Top">140</Attribute>
          <Attribute name="Width">20</Attribute>
          <Attribute name="Height">20</Attribute>
          <Attribute name="OnManeuver">if (this.view.Select_Application.value() === '') {
    this.value(0);
    return;
}

(async () =&gt; {
    if (this.value() === 0) {
        this.value(1);
    }

    const prefix = this.name().split('_').slice(0,2).join('_');
    
    for (const [key, element] of Object.entries(this.parent)) {
        if (key === this.name()) continue;
        
        if (key.startsWith(prefix) &amp;&amp; key.endsWith('CB')) {
            element.value(0);
        }
    }
    
    const result = await this.view.call('.shouldEnableSaveButton', {
        appValue: this.view.Select_Application.value(),
        
        programInfo: this.view.Program_Level_Info_CB.value(),
        programDebug: this.view.Program_Level_Debug_CB.value(),
        programFile: this.view.Program_Target_File_CB.value(),
        programConsole: this.view.Program_Target_Console_CB.value(),
        programOnline: this.view.Program_Target_Online_CB.value(),
        
        wampInfo: this.view.Wamp_Level_Info_CB.value(),
        wampDebug: this.view.Wamp_Level_Debug_CB.value(),
        wampFile: this.view.Wamp_Target_File_CB.value(),
        wampConsole: this.view.Wamp_Target_Console_CB.value(),
        wampOnline: this.view.Wamp_Target_Online_CB.value()
    });

    this.view.Save_Button.enabled(result.trueOrFalse);
})();</Attribute>
        </Object>
        <Object type="Static">
          <Attribute name="Name">Program_Target_File_Title</Attribute>
          <Attribute name="Title">File</Attribute>
          <Attribute name="UserDescription">Log to file</Attribute>
          <Attribute name="Left">195</Attribute>
          <Attribute name="Top">140</Attribute>
          <Attribute name="Width">80</Attribute>
          <Attribute name="Height">20</Attribute>
          <Attribute name="TitleHeight"></Attribute>
        </Object>
        <Object type="Symbol">
          <Attribute name="Name">Program_Target_Console_CB</Attribute>
          <Attribute name="UserDescription">Log to console</Attribute>
          <Attribute name="Pictures">Shared:\Symbols\Checkbox.svg</Attribute>
          <Attribute name="ManeuverStyle">SVGInteraction</Attribute>
          <Attribute name="Style">SVGAnimation</Attribute>
          <Attribute name="VisualStyle">Command</Attribute>
          <Attribute name="Left">170</Attribute>
          <Attribute name="Top">170</Attribute>
          <Attribute name="Width">20</Attribute>
          <Attribute name="Height">20</Attribute>
          <Attribute name="OnManeuver">if (this.view.Select_Application.value() === '') {
    this.value(0);
    return;
}

(async () =&gt; {
    if (this.value() === 0) {
        this.value(1);
    }

    const prefix = this.name().split('_').slice(0,2).join('_');
    
    for (const [key, element] of Object.entries(this.parent)) {
        if (key === this.name()) continue;
        
        if (key.startsWith(prefix) &amp;&amp; key.endsWith('CB')) {
            element.value(0);
        }
    }
    
    const result = await this.view.call('.shouldEnableSaveButton', {
        appValue: this.view.Select_Application.value(),
        
        programInfo: this.view.Program_Level_Info_CB.value(),
        programDebug: this.view.Program_Level_Debug_CB.value(),
        programFile: this.view.Program_Target_File_CB.value(),
        programConsole: this.view.Program_Target_Console_CB.value(),
        programOnline: this.view.Program_Target_Online_CB.value(),
        
        wampInfo: this.view.Wamp_Level_Info_CB.value(),
        wampDebug: this.view.Wamp_Level_Debug_CB.value(),
        wampFile: this.view.Wamp_Target_File_CB.value(),
        wampConsole: this.view.Wamp_Target_Console_CB.value(),
        wampOnline: this.view.Wamp_Target_Online_CB.value()
    });

    this.view.Save_Button.enabled(result.trueOrFalse);
})();</Attribute>
        </Object>
        <Object type="Static">
          <Attribute name="Name">Program_Target_Console_Title</Attribute>
          <Attribute name="Title">Console</Attribute>
          <Attribute name="UserDescription">Log to console</Attribute>
          <Attribute name="Left">195</Attribute>
          <Attribute name="Top">170</Attribute>
          <Attribute name="Width">80</Attribute>
          <Attribute name="Height">20</Attribute>
          <Attribute name="TitleHeight"></Attribute>
        </Object>
        <Object type="Symbol">
          <Attribute name="Name">Program_Target_Online_CB</Attribute>
          <Attribute name="UserDescription">Log online</Attribute>
          <Attribute name="Pictures">Shared:\Symbols\Checkbox.svg</Attribute>
          <Attribute name="ManeuverStyle">SVGInteraction</Attribute>
          <Attribute name="Style">SVGAnimation</Attribute>
          <Attribute name="VisualStyle">Command</Attribute>
          <Attribute name="Left">170</Attribute>
          <Attribute name="Top">200</Attribute>
          <Attribute name="Width">20</Attribute>
          <Attribute name="Height">20</Attribute>
          <Attribute name="OnManeuver">if (this.view.Select_Application.value() === '') {
    this.value(0);
    return;
}

(async () =&gt; {
    if (this.value() === 0) {
        this.value(1);
    }

    const prefix = this.name().split('_').slice(0,2).join('_');
    
    for (const [key, element] of Object.entries(this.parent)) {
        if (key === this.name()) continue;
        
        if (key.startsWith(prefix) &amp;&amp; key.endsWith('CB')) {
            element.value(0);
        }
    }
    
    const result = await this.view.call('.shouldEnableSaveButton', {
        appValue: this.view.Select_Application.value(),
        
        programInfo: this.view.Program_Level_Info_CB.value(),
        programDebug: this.view.Program_Level_Debug_CB.value(),
        programFile: this.view.Program_Target_File_CB.value(),
        programConsole: this.view.Program_Target_Console_CB.value(),
        programOnline: this.view.Program_Target_Online_CB.value(),
        
        wampInfo: this.view.Wamp_Level_Info_CB.value(),
        wampDebug: this.view.Wamp_Level_Debug_CB.value(),
        wampFile: this.view.Wamp_Target_File_CB.value(),
        wampConsole: this.view.Wamp_Target_Console_CB.value(),
        wampOnline: this.view.Wamp_Target_Online_CB.value()
    });

    this.view.Save_Button.enabled(result.trueOrFalse);
})();</Attribute>
        </Object>
        <Object type="Static">
          <Attribute name="Name">Program_Target_Online_Title</Attribute>
          <Attribute name="Title">Online</Attribute>
          <Attribute name="UserDescription">Log online</Attribute>
          <Attribute name="Left">195</Attribute>
          <Attribute name="Top">200</Attribute>
          <Attribute name="Width">80</Attribute>
          <Attribute name="Height">20</Attribute>
          <Attribute name="TitleHeight"></Attribute>
        </Object>
      </Object>
      <Object type="ElementsGroup">
        <Attribute name="Name">InternalMessageBus</Attribute>
        <Object type="Text">
          <Attribute name="Name">Wamp_Border</Attribute>
          <Attribute name="ShowTitle">No</Attribute>
          <Attribute name="Border">Yes</Attribute>
          <Attribute name="Left">325</Attribute>
          <Attribute name="Top">75</Attribute>
          <Attribute name="Width">270</Attribute>
          <Attribute name="Height">155</Attribute>
        </Object>
        <Object type="Static">
          <Attribute name="Name">Wamp_Title</Attribute>
          <Attribute name="Title">Internal message bus</Attribute>
          <Attribute name="FontSize">Medium</Attribute>
          <Attribute name="VisualStyle">Shady</Attribute>
          <Attribute name="Left">325</Attribute>
          <Attribute name="Top">75</Attribute>
          <Attribute name="Width">270</Attribute>
          <Attribute name="Height">25</Attribute>
        </Object>
        <Object type="Static">
          <Attribute name="Name">Wamp_Level_Title</Attribute>
          <Attribute name="Title">Log level:</Attribute>
          <Attribute name="Left">335</Attribute>
          <Attribute name="Top">110</Attribute>
          <Attribute name="Width">80</Attribute>
          <Attribute name="Height">20</Attribute>
        </Object>
        <Object type="Static">
          <Attribute name="Name">Wamp_Target_Title</Attribute>
          <Attribute name="Title">Send logs to:</Attribute>
          <Attribute name="Left">480</Attribute>
          <Attribute name="Top">110</Attribute>
          <Attribute name="Width">105</Attribute>
          <Attribute name="Height">20</Attribute>
        </Object>
        <Object type="Symbol">
          <Attribute name="Name">Wamp_Level_Info_CB</Attribute>
          <Attribute name="UserDescription"></Attribute>
          <Attribute name="Pictures">Shared:\Symbols\Checkbox.svg</Attribute>
          <Attribute name="ManeuverStyle">SVGInteraction</Attribute>
          <Attribute name="Style">SVGAnimation</Attribute>
          <Attribute name="VisualStyle">Command</Attribute>
          <Attribute name="Left">335</Attribute>
          <Attribute name="Top">140</Attribute>
          <Attribute name="Width">20</Attribute>
          <Attribute name="Height">20</Attribute>
          <Attribute name="OnManeuver">if (this.view.Select_Application.value() === '') {
    this.value(0);
    return;
}

(async () =&gt; {
    if (this.value() === 0) {
        this.value(1);
    }

    const prefix = this.name().split('_').slice(0,2).join('_');
    
    for (const [key, element] of Object.entries(this.parent)) {
        if (key === this.name()) continue;
        
        if (key.startsWith(prefix) &amp;&amp; key.endsWith('CB')) {
            element.value(0);
        }
    }
    
    const result = await this.view.call('.shouldEnableSaveButton', {
        appValue: this.view.Select_Application.value(),
        
        programInfo: this.view.Program_Level_Info_CB.value(),
        programDebug: this.view.Program_Level_Debug_CB.value(),
        programFile: this.view.Program_Target_File_CB.value(),
        programConsole: this.view.Program_Target_Console_CB.value(),
        programOnline: this.view.Program_Target_Online_CB.value(),
        
        wampInfo: this.view.Wamp_Level_Info_CB.value(),
        wampDebug: this.view.Wamp_Level_Debug_CB.value(),
        wampFile: this.view.Wamp_Target_File_CB.value(),
        wampConsole: this.view.Wamp_Target_Console_CB.value(),
        wampOnline: this.view.Wamp_Target_Online_CB.value()
    });

    this.view.Save_Button.enabled(result.trueOrFalse);
})();</Attribute>
        </Object>
        <Object type="Static">
          <Attribute name="Name">Wamp_Level_Info_Title</Attribute>
          <Attribute name="Title">Info (default)</Attribute>
          <Attribute name="UserDescription"></Attribute>
          <Attribute name="Left">360</Attribute>
          <Attribute name="Top">140</Attribute>
          <Attribute name="Width">80</Attribute>
          <Attribute name="Height">20</Attribute>
          <Attribute name="TitleHeight"></Attribute>
        </Object>
        <Object type="Symbol">
          <Attribute name="Name">Wamp_Level_Debug_CB</Attribute>
          <Attribute name="UserDescription"></Attribute>
          <Attribute name="Pictures">Shared:\Symbols\Checkbox.svg</Attribute>
          <Attribute name="ManeuverStyle">SVGInteraction</Attribute>
          <Attribute name="Style">SVGAnimation</Attribute>
          <Attribute name="VisualStyle">Command</Attribute>
          <Attribute name="Left">335</Attribute>
          <Attribute name="Top">165</Attribute>
          <Attribute name="Width">20</Attribute>
          <Attribute name="Height">20</Attribute>
          <Attribute name="OnManeuver">if (this.view.Select_Application.value() === '') {
    this.value(0);
    return;
}

(async () =&gt; {
    if (this.value() === 0) {
        this.value(1);
    }

    const prefix = this.name().split('_').slice(0,2).join('_');
    
    for (const [key, element] of Object.entries(this.parent)) {
        if (key === this.name()) continue;
        
        if (key.startsWith(prefix) &amp;&amp; key.endsWith('CB')) {
            element.value(0);
        }
    }
    
    const result = await this.view.call('.shouldEnableSaveButton', {
        appValue: this.view.Select_Application.value(),
        
        programInfo: this.view.Program_Level_Info_CB.value(),
        programDebug: this.view.Program_Level_Debug_CB.value(),
        programFile: this.view.Program_Target_File_CB.value(),
        programConsole: this.view.Program_Target_Console_CB.value(),
        programOnline: this.view.Program_Target_Online_CB.value(),
        
        wampInfo: this.view.Wamp_Level_Info_CB.value(),
        wampDebug: this.view.Wamp_Level_Debug_CB.value(),
        wampFile: this.view.Wamp_Target_File_CB.value(),
        wampConsole: this.view.Wamp_Target_Console_CB.value(),
        wampOnline: this.view.Wamp_Target_Online_CB.value()
    });

    this.view.Save_Button.enabled(result.trueOrFalse);
})();</Attribute>
        </Object>
        <Object type="Static">
          <Attribute name="Name">Wamp_Level_Debug_Title</Attribute>
          <Attribute name="Title">Debug</Attribute>
          <Attribute name="UserDescription"></Attribute>
          <Attribute name="Left">360</Attribute>
          <Attribute name="Top">165</Attribute>
          <Attribute name="Width">80</Attribute>
          <Attribute name="Height">20</Attribute>
          <Attribute name="TitleHeight"></Attribute>
        </Object>
        <Object type="Symbol">
          <Attribute name="Name">Wamp_Target_File_CB</Attribute>
          <Attribute name="UserDescription">Log to file</Attribute>
          <Attribute name="Pictures">Shared:\Symbols\Checkbox.svg</Attribute>
          <Attribute name="ManeuverStyle">SVGInteraction</Attribute>
          <Attribute name="Style">SVGAnimation</Attribute>
          <Attribute name="VisualStyle">Command</Attribute>
          <Attribute name="Left">480</Attribute>
          <Attribute name="Top">140</Attribute>
          <Attribute name="Width">20</Attribute>
          <Attribute name="Height">20</Attribute>
          <Attribute name="OnManeuver">if (this.view.Select_Application.value() === '') {
    this.value(0);
    return;
}

(async () =&gt; {
    if (this.value() === 0) {
        this.value(1);
    }

    const prefix = this.name().split('_').slice(0,2).join('_');
    
    for (const [key, element] of Object.entries(this.parent)) {
        if (key === this.name()) continue;
        
        if (key.startsWith(prefix) &amp;&amp; key.endsWith('CB')) {
            element.value(0);
        }
    }
    
    const result = await this.view.call('.shouldEnableSaveButton', {
        appValue: this.view.Select_Application.value(),
        
        programInfo: this.view.Program_Level_Info_CB.value(),
        programDebug: this.view.Program_Level_Debug_CB.value(),
        programFile: this.view.Program_Target_File_CB.value(),
        programConsole: this.view.Program_Target_Console_CB.value(),
        programOnline: this.view.Program_Target_Online_CB.value(),
        
        wampInfo: this.view.Wamp_Level_Info_CB.value(),
        wampDebug: this.view.Wamp_Level_Debug_CB.value(),
        wampFile: this.view.Wamp_Target_File_CB.value(),
        wampConsole: this.view.Wamp_Target_Console_CB.value(),
        wampOnline: this.view.Wamp_Target_Online_CB.value()
    });

    this.view.Save_Button.enabled(result.trueOrFalse);
})();</Attribute>
        </Object>
        <Object type="Static">
          <Attribute name="Name">Wamp_Target_File_Title</Attribute>
          <Attribute name="Title">File</Attribute>
          <Attribute name="UserDescription">Log to file</Attribute>
          <Attribute name="Left">505</Attribute>
          <Attribute name="Top">140</Attribute>
          <Attribute name="Width">80</Attribute>
          <Attribute name="Height">20</Attribute>
          <Attribute name="TitleHeight"></Attribute>
        </Object>
        <Object type="Symbol">
          <Attribute name="Name">Wamp_Target_Console_CB</Attribute>
          <Attribute name="UserDescription">Log to console</Attribute>
          <Attribute name="Pictures">Shared:\Symbols\Checkbox.svg</Attribute>
          <Attribute name="ManeuverStyle">SVGInteraction</Attribute>
          <Attribute name="Style">SVGAnimation</Attribute>
          <Attribute name="VisualStyle">Command</Attribute>
          <Attribute name="Left">480</Attribute>
          <Attribute name="Top">170</Attribute>
          <Attribute name="Width">20</Attribute>
          <Attribute name="Height">20</Attribute>
          <Attribute name="OnManeuver">if (this.view.Select_Application.value() === '') {
    this.value(0);
    return;
}

(async () =&gt; {
    if (this.value() === 0) {
        this.value(1);
    }

    const prefix = this.name().split('_').slice(0,2).join('_');
    
    for (const [key, element] of Object.entries(this.parent)) {
        if (key === this.name()) continue;
        
        if (key.startsWith(prefix) &amp;&amp; key.endsWith('CB')) {
            element.value(0);
        }
    }
    
    const result = await this.view.call('.shouldEnableSaveButton', {
        appValue: this.view.Select_Application.value(),
        
        programInfo: this.view.Program_Level_Info_CB.value(),
        programDebug: this.view.Program_Level_Debug_CB.value(),
        programFile: this.view.Program_Target_File_CB.value(),
        programConsole: this.view.Program_Target_Console_CB.value(),
        programOnline: this.view.Program_Target_Online_CB.value(),
        
        wampInfo: this.view.Wamp_Level_Info_CB.value(),
        wampDebug: this.view.Wamp_Level_Debug_CB.value(),
        wampFile: this.view.Wamp_Target_File_CB.value(),
        wampConsole: this.view.Wamp_Target_Console_CB.value(),
        wampOnline: this.view.Wamp_Target_Online_CB.value()
    });

    this.view.Save_Button.enabled(result.trueOrFalse);
})();</Attribute>
        </Object>
        <Object type="Static">
          <Attribute name="Name">Wamp_Target_Console_Title</Attribute>
          <Attribute name="Title">Console</Attribute>
          <Attribute name="UserDescription">Log to console</Attribute>
          <Attribute name="Left">505</Attribute>
          <Attribute name="Top">170</Attribute>
          <Attribute name="Width">80</Attribute>
          <Attribute name="Height">20</Attribute>
          <Attribute name="TitleHeight"></Attribute>
        </Object>
        <Object type="Symbol">
          <Attribute name="Name">Wamp_Target_Online_CB</Attribute>
          <Attribute name="UserDescription">Log online</Attribute>
          <Attribute name="Pictures">Shared:\Symbols\Checkbox.svg</Attribute>
          <Attribute name="ManeuverStyle">SVGInteraction</Attribute>
          <Attribute name="Style">SVGAnimation</Attribute>
          <Attribute name="VisualStyle">Command</Attribute>
          <Attribute name="Left">480</Attribute>
          <Attribute name="Top">200</Attribute>
          <Attribute name="Width">20</Attribute>
          <Attribute name="Height">20</Attribute>
          <Attribute name="OnManeuver">if (this.view.Select_Application.value() === '') {
    this.value(0);
    return;
}

(async () =&gt; {
    if (this.value() === 0) {
        this.value(1);
    }

    const prefix = this.name().split('_').slice(0,2).join('_');
    
    for (const [key, element] of Object.entries(this.parent)) {
        if (key === this.name()) continue;
        
        if (key.startsWith(prefix) &amp;&amp; key.endsWith('CB')) {
            element.value(0);
        }
    }
    
    const result = await this.view.call('.shouldEnableSaveButton', {
        appValue: this.view.Select_Application.value(),
        
        programInfo: this.view.Program_Level_Info_CB.value(),
        programDebug: this.view.Program_Level_Debug_CB.value(),
        programFile: this.view.Program_Target_File_CB.value(),
        programConsole: this.view.Program_Target_Console_CB.value(),
        programOnline: this.view.Program_Target_Online_CB.value(),
        
        wampInfo: this.view.Wamp_Level_Info_CB.value(),
        wampDebug: this.view.Wamp_Level_Debug_CB.value(),
        wampFile: this.view.Wamp_Target_File_CB.value(),
        wampConsole: this.view.Wamp_Target_Console_CB.value(),
        wampOnline: this.view.Wamp_Target_Online_CB.value()
    });

    this.view.Save_Button.enabled(result.trueOrFalse);
})();</Attribute>
        </Object>
        <Object type="Static">
          <Attribute name="Name">Wamp_Target_Online_Title</Attribute>
          <Attribute name="Title">Online</Attribute>
          <Attribute name="UserDescription">Log online</Attribute>
          <Attribute name="Left">505</Attribute>
          <Attribute name="Top">200</Attribute>
          <Attribute name="Width">80</Attribute>
          <Attribute name="Height">20</Attribute>
          <Attribute name="TitleHeight"></Attribute>
        </Object>
      </Object>
      <Object type="ElementsGroup">
        <Attribute name="Name">SaveConfiguration</Attribute>
        <Object type="Button">
          <Attribute name="Name">Save_Button</Attribute>
          <Attribute name="Title">Save</Attribute>
          <Attribute name="Left">15</Attribute>
          <Attribute name="Top">265</Attribute>
          <Attribute name="Width">120</Attribute>
          <Attribute name="Height">20</Attribute>
          <Attribute name="OnManeuver">// Function for hiding an element. Used at the end of the main
// code block to hide the status message.
const hideElement = (element) =&gt; {
    element.visible(false)   
}

(async () =&gt; {
    // Make variables of all the texts used in the comparisons
    // to avoid code duplication.
    // Also set default values for target and level.
    const levelInfo = 'Info';
    const levelDebug = 'Debug';
    const targetFile = 'file';
    const targetConsole = 'console';
    const targetOnline = 'gelf';
    
    let programLevel = '';
    let programTarget = '';
    let wampLevel = '';
    let wampTarget = '';

    // Get the current state of all the checkboxes and map them to their corresponding string values.
    programLevel = this.view.Program_Level_Info_CB.value() ? levelInfo :
                   this.view.Program_Level_Debug_CB.value() ? levelDebug :
                   levelInfo; // Default value.
                   
    programTarget = this.view.Program_Target_File_CB.value() ? targetFile :
                    this.view.Program_Target_Console_CB.value() ? targetConsole :
                    this.view.Program_Target_Online_CB.value() ? targetOnline :
                    targetFile;

    wampLevel = this.view.Wamp_Level_Info_CB.value() ? levelInfo :
                this.view.Wamp_Level_Debug_CB.value() ? levelDebug :
                levelInfo;
                
    wampTarget = this.view.Wamp_Target_File_CB.value() ? targetFile :
                 this.view.Wamp_Target_Console_CB.value() ? targetConsole :
                 this.view.Wamp_Target_Online_CB.value() ? targetOnline :
                 targetFile;
    
    // Create the new rules.
    const programRule = { minLevel: programLevel, writeTo: programTarget}  
    
    // We use the visibility of the Wamp_Border element to determine whether the Wamp rule
    // should be included or not.
    // Not all applications have a Wamp rule, so when the rules are read from the API
    // we hide all the Wamp elements in the view if no rule is present.
    const wampRule = this.view.Wamp_Border.visible() &amp;&amp; { minLevel: wampLevel, writeTo: wampTarget };
    
    // Get the name of the currently selected application.
    const appValue = this.view.Select_Application.value();
	const appName = await this.view.call('.getAppName', { appValue });
    
    // Set the new rules.
    const saveOk = await this.view.call('.setRulesForApp', { appName, wampRule, programRule })
    
    let element;
    
    // If everything went ok we make the "Save successful" element visible
    // and disable the save button.
    // Otherwise we make the "Failed to save" element visible.
    // Note that we store the displayed element so we can hide it later.
    if (saveOk) {
        this.view.Save_Success.visible(true)
        element = this.view.Save_Success
        this.view.Save_Button.enabled(false);
    } else {
        this.view.Save_Error.visible(true)
        element = this.view.Save_Error
    }
    
    // Use the previously defined 'hideElement' function to hide
    // the element after 3 seconds.
    setTimeout(hideElement, 3000, element);
})();</Attribute>
        </Object>
        <Object type="Static">
          <Attribute name="Name">Save_Error</Attribute>
          <Attribute name="Title">An error occurred while saving settings</Attribute>
          <Attribute name="VisualStyle">Alarmed</Attribute>
          <Attribute name="Visible">No</Attribute>
          <Attribute name="Left">155</Attribute>
          <Attribute name="Top">265</Attribute>
          <Attribute name="Width">255</Attribute>
          <Attribute name="Height">20</Attribute>
        </Object>
        <Object type="Static">
          <Attribute name="Name">Save_Success</Attribute>
          <Attribute name="Title">Settings saved!</Attribute>
          <Attribute name="VisualStyle">Dark</Attribute>
          <Attribute name="Visible">No</Attribute>
          <Attribute name="Left">155</Attribute>
          <Attribute name="Top">265</Attribute>
          <Attribute name="Width">99</Attribute>
          <Attribute name="Height">20</Attribute>
        </Object>
      </Object>
    </Object>
  </Object>
</EXOconfigData>