Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add User Allowed Pages Filtering - Draft Concept #1160

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

cgjgh
Copy link
Contributor

@cgjgh cgjgh commented Aug 5, 2024

Adds User Allowed Page filtering by utilizing user credentials provided by auth plugins.

Description

This is more a proof of concept PR for user page filtering using auth plugins. With my tests this seems to have been quite robust, looking for additional feedback.
Still needs to be decided whether this functionality should be integrated into D2 directly, added as a plugin or as a 3rd party node.

Working Principle

  • D2 will function exactly as usual unless global.store.enablePageFilter with context file is set to true.

  • For now, UI for Admin Management of users, groups and pages is just a Node-RED flow using D2 widgets.

  • Users and groups are saved to Node-RED file context storage so no database is needed. User allowed pages are then determined on a group basis and saved to and retrieved from global.store[{user}].allowedPages in memory only context.

  • On ui-base emitConfig() all pages, groups and widgets are filtered based on the page they reside in or if they are UI scoped. Therefore, only the allowed pages, groups, and widgets are emitted in the config to the connected dashboards. This could also have added benefit of improved load speeds.

  • Includes an optional zero-trust filtering approach where all users are denied access to all pages by default, or alternatively, enabling filtering for a specific user only if they have been added to user list (default?).

  • D2 pages are automatically populated as form options to ui-form for adding Groups in Admin Management UI, same with groups when adding User

  • Uses msg._client.user.email as user ID, will need to arrive at some sort of standardization across auth plugins concerning this

  • Any users logged in using an auth provider will show up as detected user in UI, which you can then assign groups to.

Data Model

graph TD
    A[User] --> B[Group 1]
    A[User] --> C[Group 2]
    B[Group 1] --> D[Page 1]
    B[Group 1] --> E[Page 2]
    C[Group 2] --> F[Page 3]
    C[Group 2] --> G[Page 4]

    style A fill:#0000FF,stroke:#0000FF,color:#fff,rx:10,ry:10
    style B fill:#008000,stroke:#008000,color:#fff,rx:10,ry:10
    style C fill:#008000,stroke:#008000,color:#fff,rx:10,ry:10
    style D fill:#800080,stroke:#800080,color:#fff,rx:10,ry:10
    style E fill:#800080,stroke:#800080,color:#fff,rx:10,ry:10
    style F fill:#800080,stroke:#800080,color:#fff,rx:10,ry:10
    style G fill:#800080,stroke:#800080,color:#fff,rx:10,ry:10
Loading

As can be seen above pages are assigned to groups and groups are then assigned to the user after which allowed pages are then determined.

Usage

  • Use this branch of D2: https://github.com/cgjgh/node-red-dashboard/tree/User-Page-Filtering

  • Attached below is a Node-RED flow with the necessary D2 UI for Admin Management of users, groups and pages.

  • Integrated into the PR is a test mode which you can toggle in UI and impersonate a specific user, therefore no auth plugins are required for testing.

  • New groups and users can be added with the included ui-form.

  • Table rows can be selected to load a form to edit user or group

  • Since D2 ui-table does not yet support dynamic props, included in the flow below are inject nodes with functions to generate JSON for each table with all columns for pages or groups predefined. This you can then copy from debug window and import and replace the existing tables in flow, or alternatively edit the table node.

Example UI for Admin Management

Controls

  • Enable Page Filtering - turn filtering on or off
  • Enable Zero Trust - turn on to deny access to all pages by default, off to enable filtering only for users existing in user list (prevents locking out users while in development)
  • Enable Test Mode - impersonate a specific user to check page access, current user is defined by the dropdown or textbox - ⚠️Test mode settings are globally applied to anyone using D2, needs to be turned off during normal use⚠️
  • Dropdown allows selecting users existing in user list
  • Textbox allows setting user not existing in user list

Tables

  • First table shows users and what groups they are part of
  • Second table shows users and what pages they are allowed access to
  • Third table shows users detected using dashboard, and whether they exist in user list
  • Fourth table shows groups and what pages are part of the group

Screenshot 2024-08-07 204214

Example Flow

[{"id":"c275a550508f2f50","type":"tab","label":"User","disabled":false,"info":"","env":[]},{"id":"b01a7f358921f6e1","type":"group","z":"c275a550508f2f50","name":"Pages","style":{"label":true,"fill":"#ff3f3f","fill-opacity":"0.36"},"nodes":["c5e07223dc4787ff","29211b7058716fbb","34ae08d0dfde34d7","10156a887ed3eb01"],"x":54,"y":3059,"w":472,"h":142},{"id":"2581466c8ac74d6c","type":"group","z":"c275a550508f2f50","name":"Update D2 Pages Store","style":{"label":true,"fill":"#ffC000","fill-opacity":"0.23"},"nodes":["d0aec935392702c3","11a9618bb6b0419f","580fe638d2d43d01","ea3642efc21bbe67"],"x":54,"y":3239,"w":552,"h":82},{"id":"905a7ade5b6cee0d","type":"group","z":"c275a550508f2f50","name":"Controls","style":{"label":true,"fill":"#92d04f","fill-opacity":"0.21"},"nodes":["8953aa11e22503de","6d16952d5dd0d1b3","f1ed50b5de82c861","f86d6c5dd866d7e5","d41090f34a7ef23e","0406d543c452071d","854c1f11e6771b18","775aef81998cb83b","96360dbc503f4abb","b54301b6d51962ee","38492251fe84de53","31a7d1ae2a9b7fd2","b98f362ae15b6992","1c39e4dcae50c53c","3d9724b55bb4335d","3793c8f48c13eb81","c546151e1135c613","557b3bfde0876e41","ca9214bdc798bd5b","312badac1a05eaf7","b3d02b74a8bf8fa6","1923fa98a25a19be","a99cb3727b1b773c","c4c926b68b2ddecd","9b4a1c1f450a1707","44a20fd541406bf8","6f755ee364ac28d4","088003f165850d29","ec3459d11d71a84b","1809aca9c0040ced","ee4bd33780a7a1af","6aa565099777bb94","0839cfa308379b16"],"x":44,"y":59,"w":1428,"h":922},{"id":"04a18f84bae380a1","type":"group","z":"c275a550508f2f50","name":"Groups","style":{"label":true,"fill":"#b797cf","fill-opacity":"0.36"},"nodes":["c6b142bafa279f69","342799a731ab31c0","a7e26a4fa00e2b81","c5fb686807126585","73daf448dc1e0af0","0aaa5d8e550e342e","76cc9cd46dc76ea3","95d00a1ee99fceb6","77d4771d4b75d940","ab83d66fde5556c9","bf8cd1334df4c59c","62a588b70da9a5a7","d514a2d4bfc50a1f","cf4abef9f09bbeee","f2fd23b510799915","f52f364ca3373a50","a992b3df54e67162","b2f9f88b5e42f924","624b151fc3f2f17c","1041aaa17515e09e","140d99ca4406e286","5013fc202dac79f0","97468d31bf2edd06","417ffb194871218d","b9b44fdb1933b28d","e10d8a115d1d621f","6fbb4ddaf29eeab9","3ca35dbf0a2e867b","d6eb9e7a66a61720","969c118a48d8c2f2","03853180584f2f8e","18bdf618829b6d01","bcfa37bedc75cd78","7ff0e8f02594b201","17f25a4ed0b3fed5","9f7f77e77419637b","87b6cd0b70195476","57d5105a953b5e03"],"x":54,"y":2199,"w":1262,"h":828},{"id":"242bbd18beceb976","type":"group","z":"c275a550508f2f50","name":"Users","style":{"label":true,"color":"#a4a4a4","fill":"#7fb7df"},"nodes":["8395226c2c87a9f3","af6b68965cda3f40","82b7f76ca7b5e9c8","858ff53e297eae3e","cdb0002b14421744","1dc1b773ca3acabe","4ea8bec446a20e62","2ddc7abce67f6d42","4cf94e95e81d24c1","67d8390bb12d0c88","5e947194e3b20859","19b7619ced560f68","d74675736d36cec3","9ddb099320f233eb","0729d41f66c08a5b","d711c195d98c1c3b","f9a58ebe6dd62fa5","8b08298218344041","19d9592a8825f2a9","6399aab40004d7e8","8c11fc2aec1fd404","77c2a9825a6710f1","f7854590a87e2b1e","8fd58f21dad7e897","041d10cecb9c6d31","2047356e453c0709","bd0981988f88a09e","b11bbd1a7f2a9b79","03fec09b70b9ccf6","0a6588adf422964a","8bafa59b9582bf8c","63edb80d3dd59987","4f10823155a434b8","a2b9f21968f8b794","8f95fdc7a9a308c5","218a1354fa1a08f0","6043d5e0daac0c0c","62499543ebca3c15"],"x":44,"y":1019,"w":1432,"h":1168},{"id":"44a20fd541406bf8","type":"group","z":"c275a550508f2f50","g":"905a7ade5b6cee0d","name":"User Testing","style":{"label":true,"fill":"#92d04f","fill-opacity":"0.23"},"nodes":["b50b1e4220931d9a","090ba7b0f55ac85c","c98811e5cc260bc1","1ef44e0d6dc7779c","9bf86043e33f1aa5","27d0d86090482032","9e9c2ac290f63b6b","c80a096dc824cf2c","19238c5111904985","c5a22dbee07d2081","6e7aa2ca57362c4e","f15e005a50b5dfcd","7c25f60fe4772d43","9751dd78676d89fd","e346e8f3b9a3df89","0ee5cca8faf1dcf6"],"x":94,"y":579,"w":1352,"h":322},{"id":"8f95fdc7a9a308c5","type":"group","z":"c275a550508f2f50","g":"242bbd18beceb976","name":"Detected Users","style":{"label":true},"nodes":["b2e006af9461bba7","886d1368a6bd59e9","6cc269d62faa6d88","5541735dd4bc28c8","8bcc70dd54577997","adc733247291f48a","0a09c1e653401ed1","71d502af477ac8f2","dfdb5f11db8d4509","5c769109f753a581","4ee1530551ef397d","6be0b4e7cf9c8c7b"],"x":94,"y":1439,"w":1182,"h":162},{"id":"218a1354fa1a08f0","type":"group","z":"c275a550508f2f50","g":"242bbd18beceb976","name":"Dynamic Form Group Options","style":{"label":true},"nodes":["07fa4a9eeecc68d1","4dc612947796b930","dae32910c25a2c6d","c20357d0de3e28c1"],"x":94,"y":1619,"w":432,"h":122},{"id":"6043d5e0daac0c0c","type":"group","z":"c275a550508f2f50","g":"242bbd18beceb976","name":"UI Table Generation","style":{"label":true},"nodes":["03f33f9b0c125ce8","1d2cda69d18030d3","0982609507c0bcaf","9be2c4ceec013205","2fb7743c83c10399","99ba0c5bb2d0c25d","8886eee721c52644","3f1e175be86faa2f"],"x":94,"y":1919,"w":852,"h":242},{"id":"62499543ebca3c15","type":"group","z":"c275a550508f2f50","g":"242bbd18beceb976","name":"User Form Switch","style":{"label":true},"nodes":["34eb7e70653c117e","f8860a44ff8f5590","9b01cbf6ee0609b1","6cbb4600ce684614","3b2f57711b0e833b","3ffc2d372122442c","b59e92ae7e2fc69e"],"x":94,"y":1759,"w":782,"h":142},{"id":"9f7f77e77419637b","type":"group","z":"c275a550508f2f50","g":"04a18f84bae380a1","name":"Dynamic Form Page Options","style":{"label":true},"nodes":["5a4e0a25c38a573e","edfd17a8ddae0a04","2e8ab025583a971f"],"x":94,"y":2619,"w":642,"h":82},{"id":"87b6cd0b70195476","type":"group","z":"c275a550508f2f50","g":"04a18f84bae380a1","name":"UI Table Generation","style":{"label":true},"nodes":["295a895856ac01ae","525758679ea98b45","3db8a04af876efba","d5775746e9db4921"],"x":94,"y":2879,"w":852,"h":122},{"id":"57d5105a953b5e03","type":"group","z":"c275a550508f2f50","g":"04a18f84bae380a1","name":"Group Form Switch","style":{"label":true},"nodes":["91a00f8184ef648d","34a8be69fb19a7df","7c9b47aee9fe3fb5","602332e5170eabcf","fded05d01450d3a4","296b844216b62db5","7b6cf171660625e0"],"x":94,"y":2719,"w":782,"h":142},{"id":"4f10823155a434b8","type":"junction","z":"c275a550508f2f50","g":"242bbd18beceb976","x":620,"y":1260,"wires":[["858ff53e297eae3e","d711c195d98c1c3b","f9a58ebe6dd62fa5"]]},{"id":"a2b9f21968f8b794","type":"junction","z":"c275a550508f2f50","g":"242bbd18beceb976","x":1060,"y":1240,"wires":[["8b08298218344041","5e947194e3b20859"]]},{"id":"e346e8f3b9a3df89","type":"junction","z":"c275a550508f2f50","g":"44a20fd541406bf8","x":680,"y":700,"wires":[["6e7aa2ca57362c4e","c98811e5cc260bc1"]]},{"id":"bcfa37bedc75cd78","type":"junction","z":"c275a550508f2f50","g":"04a18f84bae380a1","x":660,"y":2400,"wires":[["c6b142bafa279f69"]]},{"id":"6be0b4e7cf9c8c7b","type":"junction","z":"c275a550508f2f50","g":"8f95fdc7a9a308c5","x":820,"y":1540,"wires":[["71d502af477ac8f2","dfdb5f11db8d4509"]]},{"id":"c6b142bafa279f69","type":"function","z":"c275a550508f2f50","g":"04a18f84bae380a1","name":"function 29","func":"// Get the global groups object\nlet groups = msg.payload;\nif (!groups) {return {}}\n\n// Iterate over the groups object and update keys\nlet updatedGroups = Object.keys(groups).map(groupKey => {\n    let group = groups[groupKey];\n    let updatedGroup = { group: group.group };\n\n    // Iterate over the keys in the group object\n    for (let key in group) {\n        if (key !== 'group') {\n            updatedGroup[key] = group[key];\n        }\n    }\n    return updatedGroup;\n});\n\n// Assign the updated array to msg.payload\nmsg.payload = updatedGroups;\n\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":670,"y":2480,"wires":[["ab83d66fde5556c9","d514a2d4bfc50a1f","1041aaa17515e09e"]]},{"id":"342799a731ab31c0","type":"function","z":"c275a550508f2f50","g":"04a18f84bae380a1","name":"function 30","func":"// Get the global groups object\nlet groups = global.get('groups', \"file\") || {};\n\n// Get the group from the payload\nlet payloadGroup = msg.payload;\n\ngroups[payloadGroup.group] = payloadGroup;\n\n// Update the global groups object\nglobal.set('groups', groups, \"file\");\nmsg.payload = groups\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":450,"y":2480,"wires":[["c6b142bafa279f69","97468d31bf2edd06"]]},{"id":"a7e26a4fa00e2b81","type":"link out","z":"c275a550508f2f50","g":"04a18f84bae380a1","name":"Groups Table To Form","mode":"link","links":["73daf448dc1e0af0"],"x":1275,"y":2480,"wires":[]},{"id":"c5fb686807126585","type":"ui-form","z":"c275a550508f2f50","g":"04a18f84bae380a1","name":"","group":"2be74441489df30a","label":"","order":2,"width":0,"height":0,"options":[{"label":"Group","key":"group","type":"text","required":true,"rows":null}],"formValue":{"group":""},"payload":"","submit":"submit","cancel":"clear","resetOnSubmit":true,"topic":"topic","topicType":"msg","splitLayout":false,"className":"","passthru":false,"x":240,"y":2480,"wires":[["342799a731ab31c0","140d99ca4406e286"]]},{"id":"73daf448dc1e0af0","type":"link in","z":"c275a550508f2f50","g":"04a18f84bae380a1","name":"Groups Form In","links":["a7e26a4fa00e2b81","edfd17a8ddae0a04"],"x":95,"y":2480,"wires":[["c5fb686807126585","969c118a48d8c2f2"]]},{"id":"5a4e0a25c38a573e","type":"inject","z":"c275a550508f2f50","g":"9f7f77e77419637b","name":"Generate Dynamic Form Options from Pages","props":[],"repeat":"","crontab":"","once":true,"onceDelay":"1","topic":"","x":310,"y":2660,"wires":[["2e8ab025583a971f"]]},{"id":"edfd17a8ddae0a04","type":"link out","z":"c275a550508f2f50","g":"9f7f77e77419637b","name":"Dynamic Pages Out","mode":"link","links":["73daf448dc1e0af0"],"x":695,"y":2660,"wires":[]},{"id":"0aaa5d8e550e342e","type":"inject","z":"c275a550508f2f50","g":"04a18f84bae380a1","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":"1","topic":"","payload":"#:(file)::groups","payloadType":"global","x":440,"y":2440,"wires":[["bcfa37bedc75cd78"]]},{"id":"76cc9cd46dc76ea3","type":"inject","z":"c275a550508f2f50","g":"04a18f84bae380a1","name":"Clear Groups","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":290,"y":2320,"wires":[["95d00a1ee99fceb6"]]},{"id":"95d00a1ee99fceb6","type":"change","z":"c275a550508f2f50","g":"04a18f84bae380a1","name":"","rules":[{"t":"set","p":"#:(file)::groups","pt":"global","to":"null","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":470,"y":2320,"wires":[["bcfa37bedc75cd78"]]},{"id":"77d4771d4b75d940","type":"link out","z":"c275a550508f2f50","g":"04a18f84bae380a1","name":"Dynamic Groups Out","mode":"link","links":["dae32910c25a2c6d"],"x":985,"y":2440,"wires":[]},{"id":"ab83d66fde5556c9","type":"change","z":"c275a550508f2f50","g":"04a18f84bae380a1","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"{}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":860,"y":2440,"wires":[["77d4771d4b75d940"]]},{"id":"bf8cd1334df4c59c","type":"inject","z":"c275a550508f2f50","g":"04a18f84bae380a1","name":"Show Groups","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"#:(file)::groups","payloadType":"global","x":290,"y":2240,"wires":[["62a588b70da9a5a7"]]},{"id":"62a588b70da9a5a7","type":"debug","z":"c275a550508f2f50","g":"04a18f84bae380a1","name":"debug 123","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":470,"y":2240,"wires":[]},{"id":"d514a2d4bfc50a1f","type":"link out","z":"c275a550508f2f50","g":"04a18f84bae380a1","name":"Groups Out","mode":"link","links":["11a9618bb6b0419f"],"x":795,"y":2520,"wires":[]},{"id":"d0aec935392702c3","type":"function","z":"c275a550508f2f50","g":"2581466c8ac74d6c","name":"function 37","func":"// Get the global context\nlet users = global.get('users', \"file\") || {};\nlet groups = global.get('groups', \"file\") || {};\nlet store = global.get('store') || {};\nlet enableFilter = msg.enableFilter;\n\n// Iterate through all users\nfor (let userID in users) {\n    let user = users[userID];\n\n    // Collect properties with a value of true\n    let userGroups = Object.keys(user).filter(key => user[key] === true);\n\n    // Initialize allowedPages and disallowedPages arrays\n    let allowedPages = [];\n    let disallowedPages = [];\n\n    // Iterate through all groups\n    for (let group in groups) {\n        // Iterate through all pages in each group\n        for (let page in groups[group]) {\n            // If group matches any of the properties collected above and page is allowed\n            if (userGroups.includes(group) && groups[group][page] === true) {\n                // Add page to allowedPages array if not already included\n                if (!allowedPages.includes(page)) {\n                    allowedPages.push(page);\n                }\n                // Remove page from disallowedPages array if it's there\n                const index = disallowedPages.indexOf(page);\n                if (index > -1) {\n                    disallowedPages.splice(index, 1);\n                }\n            } else if (page == \"group\") {\n                // Skip the group property\n            } else {\n                // Add page to disallowedPages array if not already included and not in allowedPages\n                if (!disallowedPages.includes(page) && !allowedPages.includes(page)) {\n                    disallowedPages.push(page);\n                }\n            }\n        }\n    }\n\n    // Add pages from store.pages not found in groups[group] with a value of false\n    if (store.pages) {\n        for (let page in store.pages) {\n            if (!allowedPages.includes(page) && !disallowedPages.includes(page)) {\n                disallowedPages.push(page);\n            }\n        }\n    }\n\n    // Remove pages that don't exist in store.pages\n    allowedPages = allowedPages.filter(page => store.pages && store.pages.hasOwnProperty(page));\n    disallowedPages = disallowedPages.filter(page => store.pages && store.pages.hasOwnProperty(page));\n\n    // Set the global context user object with the allowed and disallowed pages\n    users[userID].allowedPages = allowedPages;\n    users[userID].disallowedPages = disallowedPages;\n\n    // Set store based on enableFilter\n    if (enableFilter) {\n        store[userID] = store[userID] || {};\n        store[userID].allowedPages = allowedPages;\n        store[userID].disallowedPages = disallowedPages;\n    } else {\n        store[userID] = store[userID] || {};\n        store[userID].allowedPages = null;\n        store[userID].disallowedPages = null;\n    }\n}\n\n// Update the global users and store objects\nglobal.set('users', users, \"file\");\nglobal.set('store', store);\n\n// Return the message object\nmsg.payload = users\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":450,"y":3280,"wires":[["ea3642efc21bbe67"]]},{"id":"11a9618bb6b0419f","type":"link in","z":"c275a550508f2f50","g":"2581466c8ac74d6c","name":"Update D2 Allowed User Pages In","links":["d514a2d4bfc50a1f","f1ed50b5de82c861","f9a58ebe6dd62fa5","fe36d6c3127d2301"],"x":95,"y":3280,"wires":[["580fe638d2d43d01"]]},{"id":"c5e07223dc4787ff","type":"inject","z":"c275a550508f2f50","g":"b01a7f358921f6e1","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"store.pages","payloadType":"global","x":190,"y":3100,"wires":[["29211b7058716fbb"]]},{"id":"29211b7058716fbb","type":"debug","z":"c275a550508f2f50","g":"b01a7f358921f6e1","name":"debug 131","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":370,"y":3100,"wires":[]},{"id":"8953aa11e22503de","type":"ui-switch","z":"c275a550508f2f50","g":"905a7ade5b6cee0d","name":"Enable User Page Filtering","label":"Enable User Page Filtering","group":"2e9bdb3039494f60","order":1,"width":0,"height":0,"passthru":true,"topic":"topic","topicType":"msg","style":"","className":"","onvalue":"true","onvalueType":"bool","onicon":"","oncolor":"","offvalue":"false","offvalueType":"bool","officon":"","offcolor":"","x":520,"y":100,"wires":[["6d16952d5dd0d1b3","b54301b6d51962ee"]]},{"id":"6d16952d5dd0d1b3","type":"change","z":"c275a550508f2f50","g":"905a7ade5b6cee0d","name":"","rules":[{"t":"set","p":"#:(file)::store.enablePageFilter","pt":"global","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":820,"y":100,"wires":[["f1ed50b5de82c861"]]},{"id":"580fe638d2d43d01","type":"change","z":"c275a550508f2f50","g":"2581466c8ac74d6c","name":"","rules":[{"t":"set","p":"enableFilter","pt":"msg","to":"#:(file)::store.enablePageFilter","tot":"global"}],"action":"","property":"","from":"","to":"","reg":false,"x":240,"y":3280,"wires":[["d0aec935392702c3"]]},{"id":"f1ed50b5de82c861","type":"link out","z":"c275a550508f2f50","g":"905a7ade5b6cee0d","name":"Enable Filter Out","mode":"link","links":["11a9618bb6b0419f"],"x":1005,"y":100,"wires":[]},{"id":"f86d6c5dd866d7e5","type":"ui-control","z":"c275a550508f2f50","g":"905a7ade5b6cee0d","name":"","ui":"c531a07af3871771","events":"all","x":660,"y":360,"wires":[[]]},{"id":"d41090f34a7ef23e","type":"link in","z":"c275a550508f2f50","g":"905a7ade5b6cee0d","name":"UI-Control In","links":["6cbb4600ce684614","91a00f8184ef648d","b98f362ae15b6992"],"x":545,"y":380,"wires":[["f86d6c5dd866d7e5"]]},{"id":"34eb7e70653c117e","type":"change","z":"c275a550508f2f50","g":"62499543ebca3c15","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"{\"pages\":{\"show\":[],\"hide\":[]},\"groups\":{\"show\":[],\"hide\":[\"Admin:Add/Edit User\"]}}","tot":"json"},{"t":"set","p":"selectedUser","pt":"flow","to":"","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":700,"y":1860,"wires":[["6cbb4600ce684614"]]},{"id":"91a00f8184ef648d","type":"link out","z":"c275a550508f2f50","g":"57d5105a953b5e03","name":"Ad/Edit Group Switch Out","mode":"link","links":["d41090f34a7ef23e"],"x":835,"y":2800,"wires":[]},{"id":"34a8be69fb19a7df","type":"change","z":"c275a550508f2f50","g":"57d5105a953b5e03","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"{\"pages\":{\"show\":[],\"hide\":[]},\"groups\":{\"show\":[\"Admin:Add/Edit Group\"],\"hide\":[]}}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":710,"y":2780,"wires":[["91a00f8184ef648d"]]},{"id":"7c9b47aee9fe3fb5","type":"change","z":"c275a550508f2f50","g":"57d5105a953b5e03","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"{\"pages\":{\"show\":[],\"hide\":[]},\"groups\":{\"show\":[],\"hide\":[\"Admin:Add/Edit Group\"]}}","tot":"json"},{"t":"set","p":"selectedGroup","pt":"flow","to":"","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":710,"y":2820,"wires":[["91a00f8184ef648d"]]},{"id":"602332e5170eabcf","type":"switch","z":"c275a550508f2f50","g":"57d5105a953b5e03","name":"","property":"payload","propertyType":"msg","rules":[{"t":"true"},{"t":"false"}],"checkall":"true","repair":false,"outputs":2,"x":560,"y":2800,"wires":[["34a8be69fb19a7df"],["7c9b47aee9fe3fb5"]]},{"id":"fded05d01450d3a4","type":"ui-switch","z":"c275a550508f2f50","g":"57d5105a953b5e03","name":"Show/Hide Group Edit","label":"","group":"28dc6505d21a866e","order":3,"width":0,"height":0,"passthru":true,"topic":"topic","topicType":"msg","style":"","className":"","onvalue":"true","onvalueType":"bool","onicon":"mdi-eye-off-outline","oncolor":"","offvalue":"false","offvalueType":"bool","officon":"mdi-account-multiple-plus-outline","offcolor":"","x":380,"y":2800,"wires":[["602332e5170eabcf"]]},{"id":"296b844216b62db5","type":"inject","z":"c275a550508f2f50","g":"57d5105a953b5e03","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"false","payloadType":"bool","x":190,"y":2800,"wires":[["fded05d01450d3a4"]]},{"id":"0406d543c452071d","type":"ui-event","z":"c275a550508f2f50","g":"905a7ade5b6cee0d","ui":"c531a07af3871771","name":"","x":150,"y":260,"wires":[["854c1f11e6771b18"]]},{"id":"854c1f11e6771b18","type":"switch","z":"c275a550508f2f50","g":"905a7ade5b6cee0d","name":"","property":"payload.page.name","propertyType":"msg","rules":[{"t":"eq","v":"Admin","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":270,"y":260,"wires":[["1923fa98a25a19be"]]},{"id":"775aef81998cb83b","type":"change","z":"c275a550508f2f50","g":"905a7ade5b6cee0d","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"false","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":900,"y":300,"wires":[["96360dbc503f4abb"]]},{"id":"96360dbc503f4abb","type":"link out","z":"c275a550508f2f50","g":"905a7ade5b6cee0d","name":"Hide Add Groups","mode":"link","links":["b59e92ae7e2fc69e","7b6cf171660625e0","064431be344e52b0","900457fcc017a9be","22776d4ad3fc6a75"],"x":1025,"y":300,"wires":[]},{"id":"7b6cf171660625e0","type":"link in","z":"c275a550508f2f50","g":"57d5105a953b5e03","name":"Add/Edit Groups Switch In","links":["96360dbc503f4abb","f2fd23b510799915","5013fc202dac79f0","17f25a4ed0b3fed5"],"x":225,"y":2760,"wires":[["fded05d01450d3a4"]]},{"id":"cf4abef9f09bbeee","type":"change","z":"c275a550508f2f50","g":"04a18f84bae380a1","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"true","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":980,"y":2520,"wires":[["f2fd23b510799915"]]},{"id":"f2fd23b510799915","type":"link out","z":"c275a550508f2f50","g":"04a18f84bae380a1","name":"Groups Table To Switch","mode":"link","links":["7b6cf171660625e0"],"x":1095,"y":2520,"wires":[]},{"id":"f52f364ca3373a50","type":"delay","z":"c275a550508f2f50","g":"04a18f84bae380a1","name":"","pauseType":"delay","timeout":"250","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":1170,"y":2480,"wires":[["a7e26a4fa00e2b81"]]},{"id":"b54301b6d51962ee","type":"switch","z":"c275a550508f2f50","g":"905a7ade5b6cee0d","name":"","property":"payload","propertyType":"msg","rules":[{"t":"true"},{"t":"false"}],"checkall":"true","repair":false,"outputs":2,"x":730,"y":200,"wires":[["38492251fe84de53"],["31a7d1ae2a9b7fd2","775aef81998cb83b","6f755ee364ac28d4"]]},{"id":"38492251fe84de53","type":"change","z":"c275a550508f2f50","g":"905a7ade5b6cee0d","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"{\"pages\":{\"show\":[],\"hide\":[]},\"groups\":{\"show\":[\"Admin:Users\",\"Admin:Groups\"],\"hide\":[]}}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":910,"y":180,"wires":[["b98f362ae15b6992"]]},{"id":"31a7d1ae2a9b7fd2","type":"change","z":"c275a550508f2f50","g":"905a7ade5b6cee0d","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"{\"pages\":{\"show\":[],\"hide\":[]},\"groups\":{\"show\":[],\"hide\":[\"Admin:Users\",\"Admin:Groups\"]}}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":910,"y":220,"wires":[["b98f362ae15b6992"]]},{"id":"b98f362ae15b6992","type":"link out","z":"c275a550508f2f50","g":"905a7ade5b6cee0d","name":"Hide Groups Out","mode":"link","links":["d41090f34a7ef23e"],"x":1045,"y":200,"wires":[]},{"id":"1c39e4dcae50c53c","type":"inject","z":"c275a550508f2f50","g":"905a7ade5b6cee0d","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"#:(file)::store.enablePageFilter","payloadType":"global","x":230,"y":100,"wires":[["8953aa11e22503de"]]},{"id":"3d9724b55bb4335d","type":"inject","z":"c275a550508f2f50","g":"905a7ade5b6cee0d","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"false","payloadType":"bool","x":310,"y":140,"wires":[["8953aa11e22503de"]]},{"id":"3793c8f48c13eb81","type":"inject","z":"c275a550508f2f50","g":"905a7ade5b6cee0d","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"true","payloadType":"bool","x":310,"y":180,"wires":[["8953aa11e22503de"]]},{"id":"34ae08d0dfde34d7","type":"change","z":"c275a550508f2f50","g":"b01a7f358921f6e1","name":"","rules":[{"t":"delete","p":"store.pages","pt":"global"}],"action":"","property":"","from":"","to":"","reg":false,"x":390,"y":3160,"wires":[[]]},{"id":"10156a887ed3eb01","type":"inject","z":"c275a550508f2f50","g":"b01a7f358921f6e1","name":"Delete Pages","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":170,"y":3160,"wires":[["34ae08d0dfde34d7"]]},{"id":"2e8ab025583a971f","type":"function","z":"c275a550508f2f50","g":"9f7f77e77419637b","name":"function 38","func":"let pages = global.get('store.pages') || {};\n\n// Initialize the output array\nlet output = [];\n\noutput.push({\n    \"type\": \"text\",\n    \"label\": \"Group\",\n    \"key\": \"group\",\n    \"required\": true\n});\n\n// Iterate through the pages object\nfor (let page in pages) {\n    // Add each page to the output array\n    output.push({\n        \"type\": \"switch\",\n        \"label\": pages[page].name,\n        \"key\": pages[page].id\n    });\n}\n\n// Return the output array\nmsg.ui_update= {\"options\": output};\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":570,"y":2660,"wires":[["edfd17a8ddae0a04"]]},{"id":"8395226c2c87a9f3","type":"function","z":"c275a550508f2f50","g":"242bbd18beceb976","name":"function 27","func":"// Get the global users object\nlet users = global.get('users', \"file\") || {};\n\n// Get the user from the payload\nlet payloadUser = msg.payload;\n\n// Check if the user exists in the global users object\nif (users[payloadUser.email]) {\n    // User exists, overwrite the user\n    users[payloadUser.email] = payloadUser;\n} else {\n    // User doesn't exist, add the user\n    users[payloadUser.email] = payloadUser;\n}\n\n// Update the global users object\nglobal.set('users', users, \"file\");\nmsg.payload = users\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":450,"y":1260,"wires":[["4f10823155a434b8"]]},{"id":"af6b68965cda3f40","type":"ui-form","z":"c275a550508f2f50","g":"242bbd18beceb976","name":"","group":"20f878a97dc00643","label":"","order":2,"width":0,"height":0,"options":[{"label":"Name","key":"name","type":"text","required":true,"rows":null}],"formValue":{"name":""},"payload":"","submit":"submit","cancel":"clear","resetOnSubmit":true,"topic":"topic","topicType":"msg","splitLayout":"","className":"","passthru":false,"x":250,"y":1260,"wires":[["8395226c2c87a9f3","8c11fc2aec1fd404"]]},{"id":"82b7f76ca7b5e9c8","type":"link in","z":"c275a550508f2f50","g":"242bbd18beceb976","name":"Users Form In","links":["c20357d0de3e28c1","cdb0002b14421744","4375a2a37df6775d","0a09c1e653401ed1","07dbb91ed7ae9711"],"x":85,"y":1260,"wires":[["af6b68965cda3f40","bd0981988f88a09e"]]},{"id":"858ff53e297eae3e","type":"function","z":"c275a550508f2f50","g":"242bbd18beceb976","name":"function 28","func":"let users = msg.payload;\nif (!users) { return {} }\n\n// Convert the object to an array and filter out allowedPages and disallowedPages\nlet output = Object.keys(users).map(key => {\n    let user = users[key];\n    delete user.allowedPages;\n    delete user.disallowedPages;\n    return user;\n});\n\n// Assign the array to msg.payload\nmsg.payload = output;\n\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":750,"y":1220,"wires":[["f7854590a87e2b1e"]]},{"id":"cdb0002b14421744","type":"link out","z":"c275a550508f2f50","g":"242bbd18beceb976","name":"Users Table To Form","mode":"link","links":["82b7f76ca7b5e9c8"],"x":1435,"y":1220,"wires":[]},{"id":"1dc1b773ca3acabe","type":"inject","z":"c275a550508f2f50","g":"242bbd18beceb976","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":"1","topic":"","payload":"#:(file)::users","payloadType":"global","x":450,"y":1300,"wires":[["4f10823155a434b8"]]},{"id":"4ea8bec446a20e62","type":"inject","z":"c275a550508f2f50","g":"242bbd18beceb976","name":"Clear Users","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":310,"y":1140,"wires":[["2ddc7abce67f6d42"]]},{"id":"2ddc7abce67f6d42","type":"change","z":"c275a550508f2f50","g":"242bbd18beceb976","name":"","rules":[{"t":"set","p":"#:(file)::users","pt":"global","to":"null","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":480,"y":1140,"wires":[["4f10823155a434b8"]]},{"id":"4cf94e95e81d24c1","type":"inject","z":"c275a550508f2f50","g":"242bbd18beceb976","name":"Show Users","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"#:(file)::users","payloadType":"global","x":310,"y":1060,"wires":[["67d8390bb12d0c88"]]},{"id":"67d8390bb12d0c88","type":"debug","z":"c275a550508f2f50","g":"242bbd18beceb976","name":"debug 121","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":490,"y":1060,"wires":[]},{"id":"07fa4a9eeecc68d1","type":"function","z":"c275a550508f2f50","g":"218a1354fa1a08f0","name":"function 34","func":"let groups = global.get('groups', \"file\") || {};\n\n// Initialize the output array\nlet output = [];\n\noutput.push({\n    \"type\": \"text\",\n    \"label\": \"Name\",\n    \"key\": \"name\",\n    \"required\": true\n});\n\noutput.push({\n    \"type\": \"email\",\n    \"label\": \"E-Mail\",\n    \"key\": \"email\",\n    \"required\": true\n});\n\n\n\n// Iterate through the pages object\nfor (let group in groups) {\n    // Add each page to the output array\n    output.push({\n        \"type\": \"switch\",\n        \"label\": group,\n        \"key\": group\n    });\n}\n\n// Return the output array\nmsg.ui_update= {\"options\": output};\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":350,"y":1680,"wires":[["c20357d0de3e28c1"]]},{"id":"4dc612947796b930","type":"inject","z":"c275a550508f2f50","g":"218a1354fa1a08f0","name":"","props":[],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","x":190,"y":1700,"wires":[["07fa4a9eeecc68d1"]]},{"id":"dae32910c25a2c6d","type":"link in","z":"c275a550508f2f50","g":"218a1354fa1a08f0","name":"User Dynamic Groups","links":["77d4771d4b75d940"],"x":215,"y":1660,"wires":[["07fa4a9eeecc68d1"]]},{"id":"c20357d0de3e28c1","type":"link out","z":"c275a550508f2f50","g":"218a1354fa1a08f0","name":"Dynamic Groups Out","mode":"link","links":["82b7f76ca7b5e9c8"],"x":485,"y":1680,"wires":[]},{"id":"f8860a44ff8f5590","type":"ui-switch","z":"c275a550508f2f50","g":"62499543ebca3c15","name":"Show/Hide Edit User","label":"","group":"cff93908e2eeb0bf","order":7,"width":0,"height":0,"passthru":true,"topic":"topic","topicType":"msg","style":"","className":"","onvalue":"true","onvalueType":"bool","onicon":"mdi-eye-off-outline","oncolor":"","offvalue":"false","offvalueType":"bool","officon":"mdi-account-plus","offcolor":"","x":370,"y":1840,"wires":[["3b2f57711b0e833b"]]},{"id":"9b01cbf6ee0609b1","type":"change","z":"c275a550508f2f50","g":"62499543ebca3c15","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"{\"pages\":{\"show\":[],\"hide\":[]},\"groups\":{\"show\":[\"Admin:Add/Edit User\"],\"hide\":[]}}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":700,"y":1820,"wires":[["6cbb4600ce684614"]]},{"id":"6cbb4600ce684614","type":"link out","z":"c275a550508f2f50","g":"62499543ebca3c15","name":"Add/Edit User Switch Out","mode":"link","links":["d41090f34a7ef23e"],"x":835,"y":1840,"wires":[]},{"id":"3b2f57711b0e833b","type":"switch","z":"c275a550508f2f50","g":"62499543ebca3c15","name":"","property":"payload","propertyType":"msg","rules":[{"t":"true"},{"t":"false"}],"checkall":"true","repair":false,"outputs":2,"x":550,"y":1840,"wires":[["9b01cbf6ee0609b1"],["34eb7e70653c117e"]]},{"id":"3ffc2d372122442c","type":"inject","z":"c275a550508f2f50","g":"62499543ebca3c15","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"false","payloadType":"bool","x":190,"y":1840,"wires":[["f8860a44ff8f5590"]]},{"id":"b59e92ae7e2fc69e","type":"link in","z":"c275a550508f2f50","g":"62499543ebca3c15","name":"Add/Edit User Switch In","links":["19b7619ced560f68","96360dbc503f4abb","77c2a9825a6710f1","8bafa59b9582bf8c","e369cbba9a657327","8bcc70dd54577997","a294bf2db6e57dcb"],"x":225,"y":1800,"wires":[["f8860a44ff8f5590"]]},{"id":"5e947194e3b20859","type":"change","z":"c275a550508f2f50","g":"242bbd18beceb976","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"true","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":1160,"y":1260,"wires":[["19b7619ced560f68"]]},{"id":"19b7619ced560f68","type":"link out","z":"c275a550508f2f50","g":"242bbd18beceb976","name":"Users Table To Switch","mode":"link","links":["b59e92ae7e2fc69e"],"x":1275,"y":1260,"wires":[]},{"id":"d74675736d36cec3","type":"delay","z":"c275a550508f2f50","g":"242bbd18beceb976","name":"","pauseType":"delay","timeout":"250","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":1330,"y":1220,"wires":[["cdb0002b14421744"]]},{"id":"9ddb099320f233eb","type":"inject","z":"c275a550508f2f50","g":"242bbd18beceb976","name":"Init Users","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"{\"[email protected]\":{\"name\":\"Administrator\",\"email\":\"[email protected]\",\"Admin\":true,\"Standard\":false},\"[email protected]\":{\"name\":\"Standard User\",\"email\":\"[email protected]\",\"Admin\":false,\"Standard\":true}}","payloadType":"json","x":240,"y":1100,"wires":[["8fd58f21dad7e897"]]},{"id":"0729d41f66c08a5b","type":"change","z":"c275a550508f2f50","g":"242bbd18beceb976","name":"Set Users","rules":[{"t":"set","p":"#:(file)::users","pt":"global","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":500,"y":1100,"wires":[["4f10823155a434b8"]]},{"id":"a992b3df54e67162","type":"inject","z":"c275a550508f2f50","g":"04a18f84bae380a1","name":"Init Groups","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":"1","topic":"","payload":"#:(file)::groups","payloadType":"global","x":210,"y":2280,"wires":[["3ca35dbf0a2e867b"]]},{"id":"b2f9f88b5e42f924","type":"change","z":"c275a550508f2f50","g":"04a18f84bae380a1","name":"Set groups","rules":[{"t":"set","p":"#:(file)::groups","pt":"global","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":530,"y":2280,"wires":[["bcfa37bedc75cd78"]]},{"id":"c546151e1135c613","type":"ui-switch","z":"c275a550508f2f50","g":"905a7ade5b6cee0d","name":"Enable Zero Trust","label":"Enable Zero Trust","group":"2e9bdb3039494f60","order":2,"width":0,"height":0,"passthru":true,"topic":"topic","topicType":"msg","style":"","className":"","onvalue":"true","onvalueType":"bool","onicon":"","oncolor":"","offvalue":"false","offvalueType":"bool","officon":"","offcolor":"","x":430,"y":480,"wires":[["b3d02b74a8bf8fa6"]]},{"id":"557b3bfde0876e41","type":"inject","z":"c275a550508f2f50","g":"905a7ade5b6cee0d","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"#:(file)::store.zeroTrust","payloadType":"global","x":190,"y":460,"wires":[["c546151e1135c613"]]},{"id":"ca9214bdc798bd5b","type":"inject","z":"c275a550508f2f50","g":"905a7ade5b6cee0d","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"false","payloadType":"bool","x":240,"y":500,"wires":[["c546151e1135c613"]]},{"id":"312badac1a05eaf7","type":"inject","z":"c275a550508f2f50","g":"905a7ade5b6cee0d","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"true","payloadType":"bool","x":240,"y":540,"wires":[["c546151e1135c613"]]},{"id":"b3d02b74a8bf8fa6","type":"change","z":"c275a550508f2f50","g":"905a7ade5b6cee0d","name":"","rules":[{"t":"set","p":"#:(file)::store.zeroTrust","pt":"global","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":670,"y":480,"wires":[[]]},{"id":"624b151fc3f2f17c","type":"change","z":"c275a550508f2f50","g":"04a18f84bae380a1","name":"","rules":[{"t":"set","p":"selectedGroup","pt":"flow","to":"payload.group","tot":"msg"},{"t":"set","p":"group","pt":"msg","to":"payload.group","tot":"msg"},{"t":"set","p":"payload","pt":"msg","to":"#:(file)::groups[msg.payload.group]","tot":"global"}],"action":"","property":"","from":"","to":"","reg":false,"x":1000,"y":2480,"wires":[["f52f364ca3373a50"]]},{"id":"1923fa98a25a19be","type":"switch","z":"c275a550508f2f50","g":"905a7ade5b6cee0d","name":"","property":"topic","propertyType":"msg","rules":[{"t":"eq","v":"$pageview","vt":"str"},{"t":"eq","v":"$pageleave","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":390,"y":260,"wires":[["a99cb3727b1b773c","c4c926b68b2ddecd"],["775aef81998cb83b"]]},{"id":"a99cb3727b1b773c","type":"change","z":"c275a550508f2f50","g":"905a7ade5b6cee0d","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"#:(file)::store.enablePageFilter","tot":"global"}],"action":"","property":"","from":"","to":"","reg":false,"x":550,"y":200,"wires":[["b54301b6d51962ee"]]},{"id":"b50b1e4220931d9a","type":"ui-switch","z":"c275a550508f2f50","g":"44a20fd541406bf8","name":"Enable Test Mode","label":"Enable Test Mode","group":"2e9bdb3039494f60","order":3,"width":"0","height":"0","passthru":true,"topic":"topic","topicType":"msg","style":"","className":"","onvalue":"true","onvalueType":"bool","onicon":"","oncolor":"","offvalue":"false","offvalueType":"bool","officon":"","offcolor":"","x":350,"y":700,"wires":[["090ba7b0f55ac85c"]]},{"id":"090ba7b0f55ac85c","type":"change","z":"c275a550508f2f50","g":"44a20fd541406bf8","name":"","rules":[{"t":"set","p":"store.testMode","pt":"global","to":"payload","tot":"msg"},{"t":"set","p":"enabled","pt":"msg","to":"payload","tot":"msg"},{"t":"delete","p":"payload","pt":"msg"},{"t":"set","p":"payload","pt":"msg","to":"store.testUser","tot":"global"}],"action":"","property":"","from":"","to":"","reg":false,"x":540,"y":700,"wires":[["e346e8f3b9a3df89"]]},{"id":"c98811e5cc260bc1","type":"ui-text-input","z":"c275a550508f2f50","g":"44a20fd541406bf8","group":"2e9bdb3039494f60","name":"Test User","label":"Type Test User ID (Email)","order":7,"width":"0","height":"0","topic":"topic","topicType":"msg","mode":"email","tooltip":"","delay":300,"passthru":true,"sendOnDelay":false,"sendOnBlur":true,"sendOnEnter":true,"className":"","clearable":false,"sendOnClear":false,"icon":"","iconPosition":"left","iconInnerPosition":"inside","x":760,"y":720,"wires":[["9bf86043e33f1aa5"]]},{"id":"1ef44e0d6dc7779c","type":"change","z":"c275a550508f2f50","g":"44a20fd541406bf8","name":"","rules":[{"t":"set","p":"store.testUser","pt":"global","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1110,"y":700,"wires":[["9751dd78676d89fd"]]},{"id":"9bf86043e33f1aa5","type":"switch","z":"c275a550508f2f50","g":"44a20fd541406bf8","name":"","property":"payload","propertyType":"msg","rules":[{"t":"nempty"}],"checkall":"true","repair":false,"outputs":1,"x":930,"y":700,"wires":[["1ef44e0d6dc7779c"]]},{"id":"27d0d86090482032","type":"inject","z":"c275a550508f2f50","g":"44a20fd541406bf8","name":"Disable Input","props":[{"p":"enabled","v":"false","vt":"bool"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","x":410,"y":760,"wires":[["19238c5111904985"]]},{"id":"9e9c2ac290f63b6b","type":"inject","z":"c275a550508f2f50","g":"44a20fd541406bf8","name":"Admin","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"[email protected]","payloadType":"str","x":550,"y":820,"wires":[["e346e8f3b9a3df89"]]},{"id":"c80a096dc824cf2c","type":"inject","z":"c275a550508f2f50","g":"44a20fd541406bf8","name":"Standard User","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[email protected]","payloadType":"str","x":530,"y":860,"wires":[["e346e8f3b9a3df89"]]},{"id":"19238c5111904985","type":"switch","z":"c275a550508f2f50","g":"44a20fd541406bf8","name":"","property":"store.testMode","propertyType":"global","rules":[{"t":"false"},{"t":"null"}],"checkall":"false","repair":false,"outputs":2,"x":550,"y":760,"wires":[["e346e8f3b9a3df89"],["e346e8f3b9a3df89"]]},{"id":"c4c926b68b2ddecd","type":"change","z":"c275a550508f2f50","g":"905a7ade5b6cee0d","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"store.testUser","tot":"global"},{"t":"set","p":"enabled","pt":"msg","to":"store.testMode","tot":"global"}],"action":"","property":"","from":"","to":"","reg":false,"x":550,"y":240,"wires":[["9b4a1c1f450a1707"]]},{"id":"9b4a1c1f450a1707","type":"link out","z":"c275a550508f2f50","g":"905a7ade5b6cee0d","name":"link out 10","mode":"link","links":["0ee5cca8faf1dcf6","886d1368a6bd59e9","e9ad25ad55dcd9af"],"x":675,"y":240,"wires":[]},{"id":"c5a22dbee07d2081","type":"inject","z":"c275a550508f2f50","g":"44a20fd541406bf8","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"false","payloadType":"bool","x":190,"y":700,"wires":[["b50b1e4220931d9a"]]},{"id":"d711c195d98c1c3b","type":"function","z":"c275a550508f2f50","g":"242bbd18beceb976","name":"function 45","func":"let dashboardPages = global.get('store.pages');\nif (!msg.payload) { return {} }\n\nlet payloadArray = Object.values(msg.payload);\n\nlet transformedPayload = payloadArray.map(user => {\n    let transformedUser = {\n        \"name\": user.name,\n        \"email\": user.email\n    };\n\n    for (let key in dashboardPages) {\n        let pageID = dashboardPages[key].id;\n        if (user.allowedPages?.includes(key)) {\n            transformedUser[pageID] = true;\n        } else if (user.disallowedPages?.includes(key)) {\n            transformedUser[pageID] = false;\n        } else {\n            transformedUser[pageID] = false;\n        }\n    }\n\n    return transformedUser;\n});\n\nmsg.payload = transformedPayload;\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":750,"y":1260,"wires":[["6399aab40004d7e8"]]},{"id":"f9a58ebe6dd62fa5","type":"link out","z":"c275a550508f2f50","g":"242bbd18beceb976","name":"Update Allowed User Pages","mode":"link","links":["11a9618bb6b0419f"],"x":695,"y":1300,"wires":[]},{"id":"8b08298218344041","type":"change","z":"c275a550508f2f50","g":"242bbd18beceb976","name":"","rules":[{"t":"set","p":"selectedUser","pt":"flow","to":"payload.email","tot":"msg","dc":true},{"t":"set","p":"user","pt":"msg","to":"payload.email","tot":"msg"},{"t":"set","p":"payload","pt":"msg","to":"#:(file)::users[msg.payload.email]","tot":"global"}],"action":"","property":"","from":"","to":"","reg":false,"x":1160,"y":1220,"wires":[["d74675736d36cec3"]]},{"id":"03f33f9b0c125ce8","type":"function","z":"c275a550508f2f50","g":"6043d5e0daac0c0c","name":"function 46","func":"function generateUniqueId() {\n    return 'xxxxxxxxxxxxxxxx'.replace(/[xy]/g, function (c) {\n        const r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);\n        return v.toString(16);\n    });\n}\n\nlet outputTable = [{\n    \"id\": generateUniqueId(),\n    \"type\": \"ui-table\",\n    \"name\": \"User Groups Table\",\n    \"label\": \"User Groups\",\n    \"order\": 1,\n    \"width\": 0,\n    \"height\": 0,\n    \"maxrows\": 0,\n    \"passthru\": false,\n    \"autocols\": false,\n    \"showSearch\": false,\n    \"selectionType\": \"click\",\n    \"columns\": [\n        {\n            \"title\": \"Name\",\n            \"key\": \"name\",\n            \"type\": \"text\",\n            \"width\": \"\",\n            \"align\": \"start\"\n        },\n        {\n            \"title\": \"Email\",\n            \"key\": \"email\",\n            \"type\": \"text\",\n            \"width\": \"\",\n            \"align\": \"start\"\n        }\n    ]\n}];\n\nmsg.payload;\n\nfor (const key in msg.payload) {\n    if (msg.payload.hasOwnProperty(key)) {\n        outputTable[0].columns.push({\n            \"title\": key,\n            \"key\": key,\n            \"type\": \"tickcross\",\n            \"width\": \"\",\n            \"align\": \"start\"\n        });\n    }\n}\n\nmsg.payload = outputTable;\n\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":610,"y":2000,"wires":[["9be2c4ceec013205"]]},{"id":"1d2cda69d18030d3","type":"comment","z":"c275a550508f2f50","g":"6043d5e0daac0c0c","name":"Use this to generate JSON for D2 UI-Table with predifined columns for each defined group","info":"","x":430,"y":1960,"wires":[]},{"id":"0982609507c0bcaf","type":"inject","z":"c275a550508f2f50","g":"6043d5e0daac0c0c","name":"Generate D2 UI Table for Users from Groups","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"#:(file)::groups","payloadType":"global","x":310,"y":2000,"wires":[["03f33f9b0c125ce8"]]},{"id":"9be2c4ceec013205","type":"debug","z":"c275a550508f2f50","g":"6043d5e0daac0c0c","name":"Output D2 UI Table","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":810,"y":2000,"wires":[]},{"id":"2fb7743c83c10399","type":"inject","z":"c275a550508f2f50","g":"6043d5e0daac0c0c","name":"Generate D2 UI Table for Users from D2 Pages","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"store.pages","payloadType":"global","x":320,"y":2120,"wires":[["8886eee721c52644"]]},{"id":"99ba0c5bb2d0c25d","type":"comment","z":"c275a550508f2f50","g":"6043d5e0daac0c0c","name":"Use this to generate JSON for D2 UI-Table with predifined columns for each page in Dashboard 2.0","info":"","x":460,"y":2080,"wires":[]},{"id":"8886eee721c52644","type":"function","z":"c275a550508f2f50","g":"6043d5e0daac0c0c","name":"function 47","func":"function generateUniqueId() {\n    return 'xxxxxxxxxxxxxxxx'.replace(/[xy]/g, function (c) {\n        const r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);\n        return v.toString(16);\n    });\n}\n\nlet outputTable = [{\n    \"id\": generateUniqueId(),\n    \"type\": \"ui-table\",\n    \"name\": \"User Pages Table\",\n    \"label\": \"User Pages\",\n    \"order\": 1,\n    \"width\": 0,\n    \"height\": 0,\n    \"maxrows\": 0,\n    \"passthru\": false,\n    \"autocols\": false,\n    \"showSearch\": false,\n    \"selectionType\": \"click\",\n    \"columns\": [\n        {\n            \"title\": \"Name\",\n            \"key\": \"name\",\n            \"type\": \"text\",\n            \"width\": \"\",\n            \"align\": \"start\"\n        },\n        {\n            \"title\": \"Email\",\n            \"key\": \"email\",\n            \"type\": \"text\",\n            \"width\": \"\",\n            \"align\": \"start\"\n        }\n    ]\n}];\n\nmsg.payload;\n\nfor (const key in msg.payload) {\n    if (msg.payload.hasOwnProperty(key)) {\n        outputTable[0].columns.push({\n            \"title\": msg.payload[key].name,\n            \"key\": msg.payload[key].id,\n            \"type\": \"tickcross\",\n            \"width\": \"\",\n            \"align\": \"start\"\n        });\n    }\n}\n\nmsg.payload = outputTable;\n\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":610,"y":2120,"wires":[["3f1e175be86faa2f"]]},{"id":"3f1e175be86faa2f","type":"debug","z":"c275a550508f2f50","g":"6043d5e0daac0c0c","name":"Output D2 UI Table","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":810,"y":2120,"wires":[]},{"id":"295a895856ac01ae","type":"inject","z":"c275a550508f2f50","g":"87b6cd0b70195476","name":"Generate D2 UI Table for Groups from D2 Pages","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"store.pages","payloadType":"global","x":320,"y":2960,"wires":[["525758679ea98b45"]]},{"id":"525758679ea98b45","type":"function","z":"c275a550508f2f50","g":"87b6cd0b70195476","name":"function 40","func":"function generateUniqueId() {\n    return 'xxxxxxxxxxxxxxxx'.replace(/[xy]/g, function (c) {\n        const r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);\n        return v.toString(16);\n    });\n}\n\nlet outputTable = [{\n    \"id\": generateUniqueId(),\n    \"type\": \"ui-table\",\n    \"name\": \"Groups Table\",\n    \"label\": \"Groups\",\n    \"order\": 1,\n    \"width\": 0,\n    \"height\": 0,\n    \"maxrows\": 0,\n    \"passthru\": false,\n    \"autocols\": false,\n    \"showSearch\": false,\n    \"selectionType\": \"click\",\n    \"columns\": [\n        {\n            \"title\": \"Group\",\n            \"key\": \"group\",\n            \"type\": \"text\",\n            \"width\": \"\",\n            \"align\": \"start\"\n        }\n    ]\n}];\n\nmsg.payload;\n\nfor (const key in msg.payload) {\n    if (msg.payload.hasOwnProperty(key)) {\n        outputTable[0].columns.push({\n            \"title\": msg.payload[key].name,\n            \"key\": msg.payload[key].id,\n            \"type\": \"tickcross\",\n            \"width\": \"\",\n            \"align\": \"start\"\n        });\n    }\n}\n\nmsg.payload = outputTable\n\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":610,"y":2960,"wires":[["3db8a04af876efba"]]},{"id":"3db8a04af876efba","type":"debug","z":"c275a550508f2f50","g":"87b6cd0b70195476","name":"Output D2 UI Table","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":810,"y":2960,"wires":[]},{"id":"d5775746e9db4921","type":"comment","z":"c275a550508f2f50","g":"87b6cd0b70195476","name":"Use this to generate JSON for D2 UI-Table with predifined columns for each page in Dashboard 2.0","info":"","x":460,"y":2920,"wires":[]},{"id":"ea3642efc21bbe67","type":"link out","z":"c275a550508f2f50","g":"2581466c8ac74d6c","name":"Allowed Pages Updated","mode":"link","links":["19d9592a8825f2a9"],"x":565,"y":3280,"wires":[]},{"id":"19d9592a8825f2a9","type":"link in","z":"c275a550508f2f50","g":"242bbd18beceb976","name":"Uptate User Tables","links":["ea3642efc21bbe67","417ffb194871218d"],"x":615,"y":1300,"wires":[["d711c195d98c1c3b"]]},{"id":"6e7aa2ca57362c4e","type":"ui-dropdown","z":"c275a550508f2f50","g":"44a20fd541406bf8","group":"2e9bdb3039494f60","name":"Select Test User","label":"Select Existing User:","tooltip":"","order":6,"width":0,"height":0,"passthru":true,"multiple":false,"chips":false,"clearable":false,"options":[],"payload":"","topic":"topic","topicType":"msg","className":"","typeIsComboBox":false,"x":780,"y":680,"wires":[["9bf86043e33f1aa5"]]},{"id":"f15e005a50b5dfcd","type":"function","z":"c275a550508f2f50","g":"44a20fd541406bf8","name":"function 48","func":"const keys = Object.keys(msg.payload);\n\n\nmsg.ui_update = {\"options\": keys};\nmsg.payload = msg.defaultUser;\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":590,"y":620,"wires":[["6e7aa2ca57362c4e"]]},{"id":"7c25f60fe4772d43","type":"inject","z":"c275a550508f2f50","g":"44a20fd541406bf8","name":"Add Users","props":[{"p":"payload"},{"p":"defaultUser","v":"[email protected]","vt":"str"}],"repeat":"","crontab":"","once":true,"onceDelay":"1","topic":"","payload":"#:(file)::users","payloadType":"global","x":430,"y":620,"wires":[["f15e005a50b5dfcd"]]},{"id":"9751dd78676d89fd","type":"ui-text","z":"c275a550508f2f50","g":"44a20fd541406bf8","group":"2e9bdb3039494f60","order":5,"width":0,"height":0,"name":"","label":"Current Test User","format":"{{msg.payload}}","layout":"col-center","style":false,"font":"","fontSize":16,"color":"#717171","className":"","x":1330,"y":700,"wires":[]},{"id":"0ee5cca8faf1dcf6","type":"link in","z":"c275a550508f2f50","g":"44a20fd541406bf8","name":"link in 2","links":["9b4a1c1f450a1707"],"x":595,"y":660,"wires":[["e346e8f3b9a3df89"]]},{"id":"6f755ee364ac28d4","type":"link out","z":"c275a550508f2f50","g":"905a7ade5b6cee0d","name":"link out 11","mode":"link","links":["088003f165850d29"],"x":845,"y":260,"wires":[]},{"id":"088003f165850d29","type":"link in","z":"c275a550508f2f50","g":"905a7ade5b6cee0d","name":"link in 3","links":["6f755ee364ac28d4"],"x":255,"y":420,"wires":[["c546151e1135c613"]]},{"id":"ec3459d11d71a84b","type":"ui-button","z":"c275a550508f2f50","g":"905a7ade5b6cee0d","group":"2e9bdb3039494f60","name":"","label":"Force All Connected Users Refresh","order":4,"width":"6","height":"1","emulateClick":false,"tooltip":"","color":"","bgcolor":"","className":"center","icon":"","iconPosition":"left","payload":"","payloadType":"str","topic":"topic","topicType":"msg","buttonColor":"","textColor":"","iconColor":"","x":230,"y":340,"wires":[["1809aca9c0040ced"]]},{"id":"1809aca9c0040ced","type":"change","z":"c275a550508f2f50","g":"905a7ade5b6cee0d","name":"","rules":[{"t":"delete","p":"_event","pt":"msg"},{"t":"delete","p":"_client","pt":"msg"},{"t":"delete","p":"_msgid","pt":"msg"},{"t":"delete","p":"topic","pt":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":480,"y":340,"wires":[["f86d6c5dd866d7e5"]]},{"id":"1041aaa17515e09e","type":"ui-table","z":"c275a550508f2f50","g":"04a18f84bae380a1","group":"28dc6505d21a866e","name":"Groups Table","label":"Groups","order":2,"width":0,"height":0,"maxrows":0,"passthru":false,"autocols":false,"showSearch":false,"selectionType":"click","columns":[{"title":"Group","key":"group","type":"text","width":"","align":"start"},{"title":"Standard User","key":"59c0745856a0d4c2","type":"tickcross","width":"","align":"start"},{"title":"No Admin","key":"63dcc2dbd1c01473","type":"tickcross","width":"","align":"start"},{"title":"Admin","key":"c0679934c108bae4","type":"tickcross","width":"","align":"start"}],"x":830,"y":2480,"wires":[["624b151fc3f2f17c","cf4abef9f09bbeee"]]},{"id":"6399aab40004d7e8","type":"ui-table","z":"c275a550508f2f50","g":"242bbd18beceb976","group":"cff93908e2eeb0bf","name":"User Pages Table","label":"User Pages","order":4,"width":0,"height":0,"maxrows":0,"passthru":false,"autocols":false,"showSearch":false,"selectionType":"click","columns":[{"title":"Name","key":"name","type":"text","width":"","align":"start"},{"title":"Email","key":"email","type":"text","width":"","align":"start"},{"title":"Standard User","key":"59c0745856a0d4c2","type":"tickcross","width":"","align":"start"},{"title":"No Admin","key":"63dcc2dbd1c01473","type":"tickcross","width":"","align":"start"},{"title":"Admin","key":"c0679934c108bae4","type":"tickcross","width":"","align":"start"}],"x":950,"y":1260,"wires":[["a2b9f21968f8b794"]]},{"id":"8c11fc2aec1fd404","type":"change","z":"c275a550508f2f50","g":"242bbd18beceb976","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"false","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":420,"y":1360,"wires":[["77c2a9825a6710f1"]]},{"id":"77c2a9825a6710f1","type":"link out","z":"c275a550508f2f50","g":"242bbd18beceb976","name":"User Form Submitted","mode":"link","links":["b59e92ae7e2fc69e"],"x":535,"y":1360,"wires":[]},{"id":"140d99ca4406e286","type":"change","z":"c275a550508f2f50","g":"04a18f84bae380a1","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"false","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":390,"y":2520,"wires":[["5013fc202dac79f0"]]},{"id":"5013fc202dac79f0","type":"link out","z":"c275a550508f2f50","g":"04a18f84bae380a1","name":"Group Form Submitted","mode":"link","links":["7b6cf171660625e0"],"x":505,"y":2520,"wires":[]},{"id":"97468d31bf2edd06","type":"function","z":"c275a550508f2f50","g":"04a18f84bae380a1","name":"function 49","func":"// Function node code\nconst users = global.get('users', 'file');\nconst groups = msg.payload;\nlet changed = false\n\n// Extract top-level keys from groups\nconst groupKeys = Object.keys(groups);\n\nfor (const userKey in users) {\n    const user = users[userKey];\n    // Create a new user object to maintain the order\n    const newUser = {\n        name: user.name,\n        email: user.email\n    };\n\n    // Insert group keys before allowedPages and disallowedPages\n    for (const groupKey of groupKeys) {\n        if (!(groupKey in user)) {\n            newUser[groupKey] = false;\n            changed = true\n        } else {\n            newUser[groupKey] = user[groupKey];\n        }\n    }\n\n    // Add existing keys to the new user object\n    for (const key in user) {\n        if (!groupKeys.includes(key) && key !== 'name' && key !== 'email') {\n            newUser[key] = user[key];\n        }\n    }\n\n    // Update the user object\n    users[userKey] = newUser;\n}\n\nglobal.set('users', users, 'file');\nmsg.payload = users;\nmsg.changed = changed\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":690,"y":2560,"wires":[["417ffb194871218d","e10d8a115d1d621f"]]},{"id":"417ffb194871218d","type":"link out","z":"c275a550508f2f50","g":"04a18f84bae380a1","name":"link out 12","mode":"link","links":["19d9592a8825f2a9"],"x":805,"y":2560,"wires":[]},{"id":"b9b44fdb1933b28d","type":"change","z":"c275a550508f2f50","g":"04a18f84bae380a1","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"Update your User Groups table columns to reflect changes. You can also generate a new table using the Nodered function ","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1000,"y":2600,"wires":[["6fbb4ddaf29eeab9"]]},{"id":"e10d8a115d1d621f","type":"switch","z":"c275a550508f2f50","g":"04a18f84bae380a1","name":"","property":"changed","propertyType":"msg","rules":[{"t":"true"}],"checkall":"true","repair":false,"outputs":1,"x":850,"y":2600,"wires":[["b9b44fdb1933b28d"]]},{"id":"ee4bd33780a7a1af","type":"ui-notification","z":"c275a550508f2f50","g":"905a7ade5b6cee0d","ui":"c531a07af3871771","position":"top right","colorDefault":true,"color":"#000000","displayTime":"3","showCountdown":true,"outputs":1,"allowDismiss":true,"dismissText":"Close","raw":false,"className":"","name":"","x":250,"y":940,"wires":[[]]},{"id":"6aa565099777bb94","type":"link in","z":"c275a550508f2f50","g":"905a7ade5b6cee0d","name":"Notify","links":["6fbb4ddaf29eeab9"],"x":115,"y":940,"wires":[["ee4bd33780a7a1af"]]},{"id":"6fbb4ddaf29eeab9","type":"link out","z":"c275a550508f2f50","g":"04a18f84bae380a1","name":"link out 13","mode":"link","links":["6aa565099777bb94"],"x":1105,"y":2600,"wires":[]},{"id":"f7854590a87e2b1e","type":"ui-table","z":"c275a550508f2f50","g":"242bbd18beceb976","group":"cff93908e2eeb0bf","name":"User Groups Table","label":"User Groups","order":2,"width":0,"height":0,"maxrows":0,"passthru":false,"autocols":false,"showSearch":false,"selectionType":"click","columns":[{"title":"Name","key":"name","type":"text","width":"","align":"start"},{"title":"Email","key":"email","type":"text","width":"","align":"start"},{"title":"Standard","key":"Standard","type":"tickcross","width":"","align":"start"},{"title":"Admin","key":"Admin","type":"tickcross","width":"","align":"start"}],"x":950,"y":1220,"wires":[["a2b9f21968f8b794"]]},{"id":"8fd58f21dad7e897","type":"switch","z":"c275a550508f2f50","g":"242bbd18beceb976","name":"","property":"#:(file)::users","propertyType":"global","rules":[{"t":"empty"},{"t":"null"}],"checkall":"false","repair":false,"outputs":2,"x":370,"y":1100,"wires":[["0729d41f66c08a5b"],["0729d41f66c08a5b"]]},{"id":"ed2394cd1034cc8f","type":"ui-button","z":"c275a550508f2f50","group":"40f2324fbcb36b7c","name":"Standard User Page Button","label":"button","order":1,"width":0,"height":0,"emulateClick":false,"tooltip":"","color":"","bgcolor":"","className":"","icon":"","iconPosition":"left","payload":"","payloadType":"str","topic":"topic","topicType":"msg","buttonColor":"","textColor":"","iconColor":"","x":180,"y":3360,"wires":[[]]},{"id":"539da757e3e3addd","type":"ui-button","z":"c275a550508f2f50","group":"96c1619765e025ac","name":"No Admin Page Button","label":"button","order":1,"width":0,"height":0,"emulateClick":false,"tooltip":"","color":"","bgcolor":"","className":"","icon":"","iconPosition":"left","payload":"","payloadType":"str","topic":"topic","topicType":"msg","buttonColor":"","textColor":"","iconColor":"","x":160,"y":3420,"wires":[[]]},{"id":"3ca35dbf0a2e867b","type":"function","z":"c275a550508f2f50","g":"04a18f84bae380a1","name":"function 50","func":"// Function Node Code\nif (!msg.payload) {\n    const pages = global.get('store').pages;\n\n    const groups = {\n        Admin: { group: \"Admin\" },\n        Standard: { group: \"Standard\" }\n    };\n\n    for (const key in pages) {\n        if (pages.hasOwnProperty(key)) {\n            const page = pages[key];\n            if (page.name === \"Admin\") {\n                groups.Admin[key] = true;\n                groups.Standard[key] = false;\n            } else if (page.name === \"No Admins\") {\n                groups.Admin[key] = false;\n                groups.Standard[key] = true;\n            } else if (page.name === \"Standard User\") {\n                groups.Standard[key] = true;\n                groups.Admin[key] = true;\n            } else {\n                groups.Standard[key] = false;\n                groups.Admin[key] = true;\n            }\n        }\n    }\n    msg.payload = groups;\n    return msg;\n}\n\n\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":370,"y":2280,"wires":[["b2f9f88b5e42f924"]]},{"id":"041d10cecb9c6d31","type":"ui-text","z":"c275a550508f2f50","g":"242bbd18beceb976","group":"cff93908e2eeb0bf","order":3,"width":0,"height":0,"name":"User Pages","label":"User Pages","format":"{{msg.payload}}","layout":"row-center","style":true,"font":"","fontSize":"20","color":"#717171","className":"","x":1170,"y":1360,"wires":[]},{"id":"2047356e453c0709","type":"ui-text","z":"c275a550508f2f50","g":"242bbd18beceb976","group":"cff93908e2eeb0bf","order":1,"width":0,"height":0,"name":"User Groups","label":"User Groups","format":"{{msg.payload}}","layout":"row-center","style":true,"font":"","fontSize":"20","color":"#717171","className":"","x":1170,"y":1320,"wires":[]},{"id":"d6eb9e7a66a61720","type":"ui-text","z":"c275a550508f2f50","g":"04a18f84bae380a1","group":"28dc6505d21a866e","order":1,"width":0,"height":0,"name":"Group Pages","label":"Group Pages","format":"{{msg.payload}}","layout":"row-center","style":true,"font":"","fontSize":"20","color":"#717171","className":"","x":1090,"y":2800,"wires":[]},{"id":"0839cfa308379b16","type":"ui-template","z":"c275a550508f2f50","g":"905a7ade5b6cee0d","group":"","page":"c0679934c108bae4","ui":"","name":"","order":0,"width":0,"height":0,"head":"","format":".center {\n  margin: 0 auto;\n  width: 75%; /* Adjust the width as needed */\n}","storeOutMessages":true,"passthru":true,"resendOnRefresh":true,"templateScope":"page:style","className":"center","x":910,"y":400,"wires":[[]]},{"id":"969c118a48d8c2f2","type":"ui-template","z":"c275a550508f2f50","g":"04a18f84bae380a1","group":"2be74441489df30a","page":"","ui":"","name":"","order":1,"width":0,"height":0,"head":"","format":"<template>\n    <div class=\"stack-view\">\n        <div class=\"header\">\n            <h4>\n                <v-icon>mdi-account-multiple-outline</v-icon> Group Management -</span>\n            </h4>\n            <h3 style=\"text-align: center; flex-grow: 1;\">\n                <span>{{ group || 'Add Group' }}</span>\n            </h3>\n            <div class=\"button-group\">\n                <v-btn v-if=\"group\" @click=\"createGroup\" variant=\"plain\">\n                    <v-icon class=\"mdi-36px\" color=\"green\">mdi-account-multiple-plus</v-icon>\n                </v-btn>\n                <v-btn v-if=\"group\" @click=\"showDialog\" variant=\"plain\">\n                    <v-icon class=\"mdi-36px\" color=\"red\">mdi-account-multiple-remove-outline</v-icon>\n                </v-btn>\n            </div>\n        </div>\n        <v-dialog v-model=\"dialog\" persistent max-width=\"400px\">\n            <v-card>\n                <v-card-title>\n                    <div>\n                        <span class=\"headline\">Are you sure you want to delete group?</span>\n                        <br>\n                        <span style=\"color: red;\">{{ group }}</span>\n                    </div>\n                </v-card-title>\n                <v-card-actions>\n                    <v-spacer></v-spacer>\n                    <v-btn color=\"gray\" text @click=\"closeDialog\">Cancel</v-btn>\n                    <v-btn color=\"red\" text @click=\"deleteGroup\">\n                        <v-icon>mdi-account-multiple-remove-outline</v-icon> Delete\n                    </v-btn>\n                </v-card-actions>\n            </v-card>\n        </v-dialog>\n    </div>\n</template>\n\n<script>\n    export default {\n        data() {\n            return {\n                dialog: false,\n                group: \"\",\n                disabled: true\n            }\n        },\n        methods: {\n            showDialog() {\n                this.dialog = true;\n            },\n            setGroup(g) {\n                this.group = g;\n                this.disabled = this.group === \"\";\n            },\n            closeDialog() {\n                this.dialog = false;\n            },\n            deleteGroup() {\n                this.dialog = false;\n                this.send({ payload: 'delete' });\n            },\n            createGroup() {\n                this.send({ payload: 'new' });\n            }\n        },\n        mounted() {\n            this.$socket.on('msg-input:' + this.id, (msg) => {\n                if (msg && msg.group) {\n                    this.setGroup(msg.group);\n                }\n            });\n        },\n    }\n</script>\n\n<style scoped>\n    .stack-view {\n        display: flex;\n        flex-direction: column;\n        gap: 10px;\n    }\n\n    .header {\n        display: flex;\n        justify-content: space-between;\n        align-items: center;\n    }\n\n    .button-group {\n        display: flex;\n        gap: 10px;\n    }\n</style>","storeOutMessages":true,"passthru":false,"resendOnRefresh":true,"templateScope":"local","className":"","x":160,"y":2360,"wires":[["03853180584f2f8e"]]},{"id":"03853180584f2f8e","type":"switch","z":"c275a550508f2f50","g":"04a18f84bae380a1","name":"","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"delete","vt":"str"},{"t":"eq","v":"new","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":290,"y":2360,"wires":[["18bdf618829b6d01"],["7ff0e8f02594b201"]]},{"id":"18bdf618829b6d01","type":"change","z":"c275a550508f2f50","g":"04a18f84bae380a1","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"selectedGroup","tot":"flow"},{"t":"delete","p":"#:(file)::groups[msg.payload]","pt":"global"},{"t":"set","p":"payload","pt":"msg","to":"#:(file)::groups","tot":"global"}],"action":"","property":"","from":"","to":"","reg":false,"x":440,"y":2360,"wires":[["bcfa37bedc75cd78"]]},{"id":"bd0981988f88a09e","type":"ui-template","z":"c275a550508f2f50","g":"242bbd18beceb976","group":"20f878a97dc00643","page":"","ui":"","name":"","order":1,"width":0,"height":0,"head":"","format":"<template>\n    <div class=\"stack-view\">\n        <div class=\"header\">\n            <h4>\n                <v-icon>mdi-account-outline</v-icon> User Management -</span>\n            </h4>\n            <h3 style=\"text-align: center; flex-grow: 1;\">\n                <span>{{ user || 'Add User' }}</span>\n            </h3>\n            <div class=\"button-group\">\n                <v-btn v-if=\"user\" @click=\"createUser\" variant=\"plain\">\n                    <v-icon class=\"mdi-36px\" color=\"green\">mdi-account-plus</v-icon>\n                </v-btn>\n                <v-btn v-if=\"user\" @click=\"showDialog\" variant=\"plain\">\n                    <v-icon class=\"mdi-36px\" color=\"red\">mdi-account-remove-outline</v-icon>\n                </v-btn>\n            </div>\n        </div>\n        <v-dialog v-model=\"dialog\" persistent max-width=\"400px\">\n            <v-card>\n                <v-card-title>\n                    <div>\n                        <span class=\"headline\">Are you sure you want to delete user?</span>\n                        <br>\n                        <span style=\"color: red;\">{{ user }}</span>\n                    </div>\n                </v-card-title>\n                <v-card-actions>\n                    <v-spacer></v-spacer>\n                    <v-btn color=\"gray\" text @click=\"closeDialog\">Cancel</v-btn>\n                    <v-btn color=\"red\" text @click=\"deleteUser\">\n                        <v-icon>mdi-account-remove-outline</v-icon> Delete\n                    </v-btn>\n                </v-card-actions>\n            </v-card>\n        </v-dialog>\n    </div>\n</template>\n\n<script>\n    export default {\n        data() {\n            return {\n                dialog: false,\n                user: \"\",\n                disabled: true\n            }\n        },\n        methods: {\n            showDialog() {\n                this.dialog = true;\n            },\n            setUser(u) {\n                this.user = u;\n                this.disabled = this.user === \"\";\n            },\n            closeDialog() {\n                this.dialog = false;\n            },\n            deleteUser() {\n                this.dialog = false;\n                this.send({ payload: 'delete' });\n            },\n            createUser() {\n                this.send({ payload: 'new' });\n            }\n        },\n        mounted() {\n            this.$socket.on('msg-input:' + this.id, (msg) => {\n                if (msg && msg.user) {\n                    this.setUser(msg.user);\n                }\n            });\n        },\n    }\n</script>\n\n<style scoped>\n    .stack-view {\n        display: flex;\n        flex-direction: column;\n        gap: 10px;\n    }\n\n    .header {\n        display: flex;\n        justify-content: space-between;\n        align-items: center;\n    }\n\n    .button-group {\n        display: flex;\n        gap: 10px;\n    }\n</style>","storeOutMessages":true,"passthru":false,"resendOnRefresh":true,"templateScope":"local","className":"","x":140,"y":1200,"wires":[["b11bbd1a7f2a9b79"]]},{"id":"b11bbd1a7f2a9b79","type":"switch","z":"c275a550508f2f50","g":"242bbd18beceb976","name":"","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"delete","vt":"str"},{"t":"eq","v":"new","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":270,"y":1200,"wires":[["03fec09b70b9ccf6"],["0a6588adf422964a"]]},{"id":"03fec09b70b9ccf6","type":"change","z":"c275a550508f2f50","g":"242bbd18beceb976","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"selectedUser","tot":"flow"},{"t":"delete","p":"#:(file)::users[msg.payload]","pt":"global"},{"t":"set","p":"payload","pt":"msg","to":"#:(file)::user","tot":"global"}],"action":"","property":"","from":"","to":"","reg":false,"x":480,"y":1180,"wires":[["4f10823155a434b8"]]},{"id":"7ff0e8f02594b201","type":"trigger","z":"c275a550508f2f50","g":"04a18f84bae380a1","name":"","op1":"false","op2":"true","op1type":"bool","op2type":"bool","duration":"250","extend":false,"overrideDelay":false,"units":"ms","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":440,"y":2400,"wires":[["17f25a4ed0b3fed5"]]},{"id":"17f25a4ed0b3fed5","type":"link out","z":"c275a550508f2f50","g":"04a18f84bae380a1","name":"link out 14","mode":"link","links":["7b6cf171660625e0"],"x":555,"y":2400,"wires":[]},{"id":"0a6588adf422964a","type":"trigger","z":"c275a550508f2f50","g":"242bbd18beceb976","name":"","op1":"false","op2":"true","op1type":"bool","op2type":"bool","duration":"250","extend":false,"overrideDelay":false,"units":"ms","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":440,"y":1220,"wires":[["8bafa59b9582bf8c"]]},{"id":"8bafa59b9582bf8c","type":"link out","z":"c275a550508f2f50","g":"242bbd18beceb976","name":"link out 15","mode":"link","links":["b59e92ae7e2fc69e"],"x":555,"y":1220,"wires":[]},{"id":"b2e006af9461bba7","type":"inject","z":"c275a550508f2f50","g":"8f95fdc7a9a308c5","name":"","props":[],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","x":190,"y":1500,"wires":[["5c769109f753a581"]]},{"id":"886d1368a6bd59e9","type":"link in","z":"c275a550508f2f50","g":"8f95fdc7a9a308c5","name":"Detected Users Table In","links":["9b4a1c1f450a1707"],"x":215,"y":1540,"wires":[["5c769109f753a581"]]},{"id":"63edb80d3dd59987","type":"ui-text","z":"c275a550508f2f50","g":"242bbd18beceb976","group":"cff93908e2eeb0bf","order":5,"width":0,"height":0,"name":"Detected Users","label":"Detected Users","format":"{{msg.payload}}","layout":"row-center","style":true,"font":"","fontSize":"20","color":"#717171","className":"","x":1180,"y":1400,"wires":[]},{"id":"6cc269d62faa6d88","type":"switch","z":"c275a550508f2f50","g":"8f95fdc7a9a308c5","name":"","property":"payload.exists","propertyType":"msg","rules":[{"t":"true"},{"t":"else"}],"checkall":"false","repair":false,"outputs":2,"x":730,"y":1500,"wires":[["5541735dd4bc28c8"],["6be0b4e7cf9c8c7b"]]},{"id":"5541735dd4bc28c8","type":"change","z":"c275a550508f2f50","g":"8f95fdc7a9a308c5","name":"","rules":[{"t":"set","p":"payload.email","pt":"msg","to":"payload.user","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":940,"y":1480,"wires":[["a2b9f21968f8b794"]]},{"id":"8bcc70dd54577997","type":"link out","z":"c275a550508f2f50","g":"8f95fdc7a9a308c5","name":"Users Table To Switch","mode":"link","links":["b59e92ae7e2fc69e"],"x":1015,"y":1560,"wires":[]},{"id":"adc733247291f48a","type":"delay","z":"c275a550508f2f50","g":"8f95fdc7a9a308c5","name":"","pauseType":"delay","timeout":"250","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":1130,"y":1520,"wires":[["0a09c1e653401ed1"]]},{"id":"0a09c1e653401ed1","type":"link out","z":"c275a550508f2f50","g":"8f95fdc7a9a308c5","name":"Users Table To Form","mode":"link","links":["82b7f76ca7b5e9c8"],"x":1235,"y":1520,"wires":[]},{"id":"71d502af477ac8f2","type":"change","z":"c275a550508f2f50","g":"8f95fdc7a9a308c5","name":"","rules":[{"t":"set","p":"payload.email","pt":"msg","to":"payload.user","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":940,"y":1520,"wires":[["adc733247291f48a"]]},{"id":"dfdb5f11db8d4509","type":"trigger","z":"c275a550508f2f50","g":"8f95fdc7a9a308c5","name":"","op1":"false","op2":"true","op1type":"bool","op2type":"bool","duration":"50","extend":false,"overrideDelay":false,"units":"ms","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":910,"y":1560,"wires":[["8bcc70dd54577997"]]},{"id":"5c769109f753a581","type":"function","z":"c275a550508f2f50","g":"8f95fdc7a9a308c5","name":"function 52","func":"// Read from context\nlet inputMap = global.get(\"store.detectedUsers\") ?? {};;\nlet usersObject = global.get(\"users\", \"file\") ?? {};\n\nlet outputArray = Object.keys(inputMap).map(email => {\n    return {\n        user: email,\n        exists: usersObject.hasOwnProperty(email),\n        lastDetected: inputMap[email].lastDetected ? new Date(inputMap[email].lastDetected * 1000).toLocaleString() : null\n    };\n});\n\nmsg.payload = outputArray;\n\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":330,"y":1500,"wires":[["4ee1530551ef397d"]]},{"id":"4ee1530551ef397d","type":"ui-table","z":"c275a550508f2f50","g":"8f95fdc7a9a308c5","group":"cff93908e2eeb0bf","name":"Previously Detected Users","label":"text","order":6,"width":0,"height":0,"maxrows":0,"passthru":false,"autocols":false,"showSearch":false,"selectionType":"click","columns":[{"title":"User","key":"user","type":"text","width":"","align":"start"},{"title":"Exists","key":"exists","type":"tickcross","width":"","align":"start"},{"title":"Last Detected","key":"lastDetected","type":"text","width":"","align":"start"}],"x":540,"y":1500,"wires":[["6cc269d62faa6d88"]]},{"id":"2be74441489df30a","type":"ui-group","name":"Add/Edit Group","page":"c0679934c108bae4","width":"6","height":"1","order":5,"showTitle":true,"className":"","visible":false,"disabled":"false"},{"id":"2e9bdb3039494f60","type":"ui-group","name":"Enable User Page Filtering","page":"c0679934c108bae4","width":"6","height":"1","order":1,"showTitle":false,"className":"","visible":true,"disabled":"false"},{"id":"c531a07af3871771","type":"ui-base","name":"My Dashboard","path":"/dashboard","includeClientData":true,"acceptsClientConfig":["ui-control","ui-notification","ui-button"],"showPathInSidebar":false,"showPageTitle":true,"navigationStyle":"default","titleBarStyle":"fixed"},{"id":"28dc6505d21a866e","type":"ui-group","name":"Groups","page":"c0679934c108bae4","width":"6","height":"1","order":4,"showTitle":true,"className":"","visible":true,"disabled":"false"},{"id":"20f878a97dc00643","type":"ui-group","name":"Add/Edit User","page":"c0679934c108bae4","width":"6","height":"1","order":3,"showTitle":true,"className":"","visible":false,"disabled":"false"},{"id":"cff93908e2eeb0bf","type":"ui-group","name":"Users","page":"c0679934c108bae4","width":"6","height":"1","order":2,"showTitle":true,"className":"","visible":true,"disabled":"false"},{"id":"40f2324fbcb36b7c","type":"ui-group","name":"Standard User","page":"59c0745856a0d4c2","width":"6","height":"1","order":1,"showTitle":true,"className":"","visible":"true","disabled":"false"},{"id":"96c1619765e025ac","type":"ui-group","name":"No Admins","page":"63dcc2dbd1c01473","width":"6","height":"1","order":1,"showTitle":true,"className":"","visible":"true","disabled":"false"},{"id":"c0679934c108bae4","type":"ui-page","name":"Admin","ui":"c531a07af3871771","path":"/admin","icon":"mdi-shield-account","layout":"notebook","theme":"d78b12b39e6e0a71","order":7,"className":"","visible":"true","disabled":"false"},{"id":"59c0745856a0d4c2","type":"ui-page","name":"Standard User","ui":"c531a07af3871771","path":"/standard","icon":"mdi-account","layout":"grid","theme":"d78b12b39e6e0a71","order":1,"className":"","visible":true,"disabled":false},{"id":"63dcc2dbd1c01473","type":"ui-page","name":"No Admins","ui":"c531a07af3871771","path":"/noadmin","icon":"mdi-shield-off-outline","layout":"grid","theme":"d78b12b39e6e0a71","order":2,"className":"","visible":"true","disabled":"false"},{"id":"d78b12b39e6e0a71","type":"ui-theme","name":"Dark","colors":{"surface":"#0d0d0d","primary":"#0094ce","bgPage":"#303030","groupBg":"#1f1f1f","groupOutline":"#999999"},"sizes":{"pagePadding":"12px","groupGap":"12px","groupBorderRadius":"20px","widgetGap":"12px"}}]

Related Issue(s)

Checklist

  • I have read the contribution guidelines
  • Suitable unit/system level tests have been added and they pass
  • Documentation has been updated
    • Upgrade instructions
    • Configuration details
    • Concepts
  • Changes flowforge.yml?
    • Issue/PR raised on FlowFuse/helm to update ConfigMap Template
    • Issue/PR raised on FlowFuse/CloudProject to update values for Staging/Production

Labels

  • Includes a DB migration? -> add the area:migration label

cgjgh added 2 commits August 4, 2024 20:30
Adds User Allowed Page filtering by utilizing user credentials provided by auth plugins. Allowed pages are retrieved from global.store[user].allowedpages.
@cgjgh cgjgh changed the title Add User Allowed Pages Filtering - Draft Add User Allowed Pages Filtering - Draft Concept Aug 8, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant